diff --git a/.reviewboardrc b/.reviewboardrc deleted file mode 100644 index 42c50853f..000000000 --- a/.reviewboardrc +++ /dev/null @@ -1,2 +0,0 @@ -REVIEWBOARD_URL = "https://git.reviewboard.kde.org" -REPOSITORY = 'git://anongit.kde.org/kwin' diff --git a/CMakeLists.txt b/CMakeLists.txt index cec69e504..4f17fc477 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,703 +1,735 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(KWIN) -set(PROJECT_VERSION "5.11.90") +set(PROJECT_VERSION "5.12.80") set(PROJECT_VERSION_MAJOR 5) -set(QT_MIN_VERSION "5.7.0") -set(KF5_MIN_VERSION "5.34.0") +set(QT_MIN_VERSION "5.9.0") +set(KF5_MIN_VERSION "5.42.0") set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ) -find_package(ECM 0.0.11 REQUIRED NO_MODULE) +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 + Sensors Script UiTools Widgets X11Extras ) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" TYPE OPTIONAL ) add_feature_info("Qt5Test" Qt5Test_FOUND "Required for building tests") if (NOT Qt5Test_FOUND) set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") endif() include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0 -DQT_USE_QSTRINGBUILDER) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-inconsistent-missing-override") endif() find_package(Qt5Multimedia QUIET) set_package_properties(Qt5Multimedia PROPERTIES PURPOSE "Runtime-only dependency for effect video playback" TYPE RUNTIME ) # required frameworks by Core find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Config ConfigWidgets CoreAddons Crash GlobalAccel I18n Init Notifications Package Plasma WidgetsAddons WindowSystem IconThemes IdleTime Wayland ) # required frameworks by config modules find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Declarative KCMUtils KIO TextWidgets NewStuff Service XmlGui ) find_package(Threads) set_package_properties(Threads PROPERTIES PURPOSE "Needed for VirtualTerminal support in KWin Wayland" TYPE REQUIRED ) # optional frameworks find_package(KF5Activities ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Activities PROPERTIES PURPOSE "Enable building of KWin with kactivities support" TYPE OPTIONAL ) add_feature_info("KF5Activities" KF5Activities_FOUND "Enable building of KWin with kactivities support") find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5DocTools PROPERTIES PURPOSE "Enable building documentation" TYPE OPTIONAL ) add_feature_info("KF5DocTools" KF5DocTools_FOUND "Enable building documentation") find_package(KDecoration2 CONFIG REQUIRED) find_package(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 "http://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.5) +find_package(Libinput 1.9) set_package_properties(Libinput PROPERTIES TYPE OPTIONAL PURPOSE "Required for input handling on Wayland.") find_package(UDev) set_package_properties(UDev PROPERTIES URL "http://www.freedesktop.org/software/systemd/libudev/" DESCRIPTION "Linux device library." TYPE OPTIONAL PURPOSE "Required for input handling on Wayland." ) set(HAVE_INPUT FALSE) if (Libinput_FOUND AND UDEV_FOUND) set(HAVE_INPUT TRUE) endif() set(HAVE_UDEV FALSE) if (UDEV_FOUND) set(HAVE_UDEV TRUE) endif() 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 AND UDEV_FOUND) set(HAVE_DRM TRUE) endif() find_package(gbm) set_package_properties(gbm PROPERTIES TYPE OPTIONAL PURPOSE "Required for egl ouput of drm backend.") set(HAVE_GBM FALSE) if(HAVE_DRM AND gbm_FOUND) set(HAVE_GBM TRUE) endif() find_package(libhybris) set_package_properties(libhybris PROPERTIES TYPE OPTIONAL PURPOSE "Required for libhybris backend") set(HAVE_LIBHYBRIS ${libhybris_FOUND}) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" TYPE REQUIRED ) add_feature_info("XInput" X11_Xinput_FOUND "Required for poll-free mouse cursor updates") set(HAVE_X11_XINPUT ${X11_Xinput_FOUND}) # All the required XCB components find_package(XCB 1.10 REQUIRED COMPONENTS XCB XFIXES DAMAGE COMPOSITE SHAPE SYNC RENDER RANDR KEYSYMS IMAGE SHM GLX CURSOR ICCCM ) set_package_properties(XCB PROPERTIES TYPE REQUIRED) # and the optional XCB dependencies if (XCB_ICCCM_VERSION VERSION_LESS "0.4") set(XCB_ICCCM_FOUND FALSE) endif() add_feature_info("XCB-ICCCM" XCB_ICCCM_FOUND "Required for building test applications for KWin") find_package(X11_XCB) set_package_properties(X11_XCB PROPERTIES PURPOSE "Required for building X11 windowed backend of kwin_wayland" TYPE OPTIONAL) # dependencies for QPA plugin -if(Qt5Core_VERSION VERSION_LESS "5.8.0") - find_package(Qt5PlatformSupport REQUIRED) -else() - find_package(Qt5FontDatabaseSupport REQUIRED) - find_package(Qt5ThemeSupport REQUIRED) - find_package(Qt5EventDispatcherSupport REQUIRED) -endif() +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 "http://www.freetype.org" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Fontconfig REQUIRED) set_package_properties(Fontconfig PROPERTIES DESCRIPTION "Font access configuration library" URL "http://www.freedesktop.org/wiki/Software/fontconfig" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Xwayland) set_package_properties(Xwayland PROPERTIES URL "http://x.org" DESCRIPTION "Xwayland X server" TYPE RUNTIME PURPOSE "Needed for running kwin_wayland" ) 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) configure_file(config-kwin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.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") ########### 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 workspace.cpp dbusinterface.cpp abstract_client.cpp client.cpp client_machine.cpp cursor.cpp debug_console.cpp tabgroup.cpp focuschain.cpp globalshortcuts.cpp input.cpp input_event.cpp input_event_spy.cpp keyboard_input.cpp keyboard_layout.cpp keyboard_layout_switching.cpp keyboard_repeat.cpp pointer_input.cpp touch_input.cpp netinfo.cpp placement.cpp atoms.cpp utils.cpp layers.cpp main.cpp options.cpp outline.cpp events.cpp killwindow.cpp geometrytip.cpp screens.cpp shadow.cpp sm.cpp group.cpp manage.cpp overlaywindow.cpp activation.cpp useractions.cpp geometry.cpp rules.cpp composite.cpp toplevel.cpp unmanaged.cpp scene.cpp screenlockerwatcher.cpp thumbnailitem.cpp lanczosfilter.cpp deleted.cpp effects.cpp effectloader.cpp virtualdesktops.cpp xcbutils.cpp x11eventfilter.cpp logind.cpp onscreennotification.cpp osd.cpp screenedge.cpp scripting/scripting.cpp scripting/workspace_wrapper.cpp scripting/meta.cpp scripting/scriptedeffect.cpp scripting/scriptingutils.cpp scripting/timer.cpp scripting/scripting_model.cpp scripting/dbuscall.cpp scripting/screenedgeitem.cpp scripting/scripting_logging.cpp decorations/decoratedclient.cpp decorations/decorationbridge.cpp decorations/decorationpalette.cpp decorations/settings.cpp decorations/decorationrenderer.cpp decorations/decorations_logging.cpp platform.cpp shell_client.cpp wayland_server.cpp wayland_cursor_theme.cpp virtualkeyboard.cpp virtualkeyboard_dbus.cpp appmenu.cpp modifier_only_shortcuts.cpp xkb.cpp gestures.cpp popup_input_filter.cpp + colorcorrection/manager.cpp + colorcorrection/colorcorrectdbusinterface.cpp + colorcorrection/suncalc.cpp abstract_opengl_context_attribute_builder.cpp egl_context_attribute_builder.cpp was_user_interaction_x11_filter.cpp moving_client_x11_filter.cpp window_property_notify_x11_filter.cpp rootinfo_filter.cpp + orientation_sensor.cpp + idle_inhibition.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) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} tabbox/tabbox.cpp tabbox/clientmodel.cpp tabbox/desktopchain.cpp tabbox/desktopmodel.cpp tabbox/switcheritem.cpp tabbox/tabboxconfig.cpp tabbox/tabboxhandler.cpp tabbox/tabbox_logging.cpp tabbox/x11_filter.cpp ) endif() if(KWIN_BUILD_ACTIVITIES) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} activities.cpp ) endif() if(UDEV_FOUND) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} udev.cpp ) endif() if(HAVE_INPUT) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} libinput/context.cpp libinput/connection.cpp libinput/device.cpp libinput/events.cpp libinput/libinput_logging.cpp ) if (HAVE_LINUX_VT_H) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} virtual_terminal.cpp ) endif() 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_interface( kwin_KDEINIT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_dbus_interface( kwin_KDEINIT_SRCS org.kde.kappmenu.xml appmenu_interface ) qt5_add_resources( kwin_KDEINIT_SRCS resources.qrc ) ki18n_wrap_ui(kwin_KDEINIT_SRCS debug_console.ui shortcutdialog.ui ) ########### target link libraries ############### set(kwin_OWN_LIBS kwineffects kwin4_effect_builtins ) set(kwin_QT_LIBS Qt5::Concurrent Qt5::DBus Qt5::Quick + Qt5::Sensors Qt5::Script ) set(kwin_KDE_LIBS KF5::ConfigCore KF5::CoreAddons KF5::ConfigWidgets KF5::GlobalAccel KF5::GlobalAccelPrivate KF5::I18n KF5::Notifications KF5::Package KF5::Plasma KF5::WindowSystem + KF5::QuickAddons KDecoration2::KDecoration KDecoration2::KDecoration2Private PW::KScreenLocker ) set(kwin_XLIB_LIBS ${X11_X11_LIB} ${X11_ICE_LIB} ${X11_SM_LIB} ) set(kwin_XCB_LIBS XCB::XCB XCB::XFIXES XCB::DAMAGE XCB::COMPOSITE XCB::SHAPE XCB::SYNC XCB::RENDER XCB::RANDR XCB::KEYSYMS XCB::SHM XCB::GLX XCB::ICCCM ) set(kwin_WAYLAND_LIBS XKB::XKB KF5::WaylandClient KF5::WaylandServer Wayland::Cursor ${CMAKE_THREAD_LIBS_INIT} ) if(KWIN_BUILD_ACTIVITIES) set(kwin_KDE_LIBS ${kwin_KDE_LIBS} KF5::Activities) endif() set(kwinLibs ${kwin_OWN_LIBS} ${kwin_QT_LIBS} ${kwin_KDE_LIBS} ${kwin_XLIB_LIBS} ${kwin_XCB_LIBS} ${kwin_WAYLAND_LIBS} ) if(UDEV_FOUND) set(kwinLibs ${kwinLibs} ${UDEV_LIBS}) endif() if(HAVE_INPUT) set(kwinLibs ${kwinLibs} Libinput::Libinput) endif() add_library(kwin SHARED ${kwin_KDEINIT_SRCS}) set_target_properties(kwin PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) target_link_libraries(kwin ${kwinLibs}) generate_export_header(kwin EXPORT_FILE_NAME kwin_export.h) target_link_libraries(kwin kwinglutils ${epoxy_LIBRARY}) 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} ) -add_executable(kwin_wayland main_wayland.cpp) +add_executable(kwin_wayland tabletmodemanager.cpp main_wayland.cpp) target_link_libraries(kwin_wayland kwin) 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.xml org.kde.kwin.Compositing.xml + org.kde.kwin.ColorCorrect.xml org.kde.kwin.Effects.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kwin_export.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel) # Install the KWin/Script service type install( FILES scripting/kwinscript.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) ecm_install_icons( ICONS 16-apps-kwin.png 32-apps-kwin.png 48-apps-kwin.png sc-apps-kwin.svgz DESTINATION ${ICON_INSTALL_DIR} THEME hicolor ) add_subdirectory(qml) add_subdirectory(autotests) add_subdirectory(tests) add_subdirectory(packageplugins) if (KF5DocTools_FOUND) add_subdirectory(doc) endif() 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/abstract_client.cpp b/abstract_client.cpp index 0a56538c4..10bd76b66 100644 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1,1800 +1,1804 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "abstract_client.h" #include "appmenu.h" #include "decorations/decoratedclient.h" #include "decorations/decorationpalette.h" #include "decorations/decorationbridge.h" #include "cursor.h" #include "effects.h" #include "focuschain.h" #include "outline.h" #include "screens.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "screenedge.h" #include "tabgroup.h" #include "useractions.h" #include "workspace.h" #include "wayland_server.h" #include #include #include #include #include namespace KWin { QHash> AbstractClient::s_palettes; std::shared_ptr AbstractClient::s_defaultPalette; AbstractClient::AbstractClient() : Toplevel() #ifdef KWIN_BUILD_TABBOX , m_tabBoxClient(QSharedPointer(new TabBox::TabBoxClientImpl(this))) #endif , m_colorScheme(QStringLiteral("kdeglobals")) { connect(this, &AbstractClient::geometryShapeChanged, this, &AbstractClient::geometryChanged); auto signalMaximizeChanged = static_cast(&AbstractClient::clientMaximizedStateChanged); connect(this, signalMaximizeChanged, this, &AbstractClient::geometryChanged); connect(this, &AbstractClient::clientStepUserMovedResized, this, &AbstractClient::geometryChanged); connect(this, &AbstractClient::clientStartUserMovedResized, this, &AbstractClient::moveResizedChanged); connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::moveResizedChanged); connect(this, &AbstractClient::clientStartUserMovedResized, this, &AbstractClient::removeCheckScreenConnection); connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::setupCheckScreenConnection); connect(this, &AbstractClient::paletteChanged, this, &AbstractClient::triggerDecorationRepaint); connect(Decoration::DecorationBridge::self(), &QObject::destroyed, this, &AbstractClient::destroyDecoration); // replace on-screen-display on size changes connect(this, &AbstractClient::geometryShapeChanged, this, [this] (Toplevel *c, const QRect &old) { Q_UNUSED(c) if (isOnScreenDisplay() && !geometry().isEmpty() && old.size() != geometry().size() && !isInitialPositionSet()) { GeometryUpdatesBlocker blocker(this); QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); Placement::self()->place(this, area); setGeometryRestore(geometry()); } } ); connect(this, &AbstractClient::paddingChanged, this, [this]() { m_visibleRectBeforeGeometryUpdate = visibleRect(); }); connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] { emit hasApplicationMenuChanged(hasApplicationMenu()); }); } AbstractClient::~AbstractClient() { assert(m_blockGeometryUpdates == 0); Q_ASSERT(m_decoration.decoration == nullptr); } void AbstractClient::updateMouseGrab() { } -bool AbstractClient::belongToSameApplication(const AbstractClient *c1, const AbstractClient *c2, bool active_hack) +bool AbstractClient::belongToSameApplication(const AbstractClient *c1, const AbstractClient *c2, SameApplicationChecks checks) { - return c1->belongsToSameApplication(c2, active_hack); + return c1->belongsToSameApplication(c2, checks); } bool AbstractClient::isTransient() const { return false; } TabGroup *AbstractClient::tabGroup() const { return nullptr; } bool AbstractClient::untab(const QRect &toGeometry, bool clientRemoved) { Q_UNUSED(toGeometry) Q_UNUSED(clientRemoved) return false; } bool AbstractClient::isCurrentTab() const { return true; } xcb_timestamp_t AbstractClient::userTime() const { return XCB_TIME_CURRENT_TIME; } void AbstractClient::setSkipSwitcher(bool set) { set = rules()->checkSkipSwitcher(set); if (set == skipSwitcher()) return; m_skipSwitcher = set; updateWindowRules(Rules::SkipSwitcher); emit skipSwitcherChanged(); } void AbstractClient::setSkipPager(bool b) { b = rules()->checkSkipPager(b); if (b == skipPager()) return; m_skipPager = b; doSetSkipPager(); - info->setState(b ? NET::SkipPager : NET::States(0), NET::SkipPager); updateWindowRules(Rules::SkipPager); emit skipPagerChanged(); } void AbstractClient::doSetSkipPager() { } void AbstractClient::setSkipTaskbar(bool b) { int was_wants_tab_focus = wantsTabFocus(); if (b == skipTaskbar()) return; m_skipTaskbar = b; doSetSkipTaskbar(); updateWindowRules(Rules::SkipTaskbar); if (was_wants_tab_focus != wantsTabFocus()) { FocusChain::self()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update); } emit skipTaskbarChanged(); } void AbstractClient::setOriginalSkipTaskbar(bool b) { m_originalSkipTaskbar = rules()->checkSkipTaskbar(b); setSkipTaskbar(m_originalSkipTaskbar); } void AbstractClient::doSetSkipTaskbar() { } void AbstractClient::setIcon(const QIcon &icon) { m_icon = icon; emit iconChanged(); } void AbstractClient::setActive(bool act) { if (m_active == act) { return; } m_active = act; const int ruledOpacity = m_active ? rules()->checkOpacityActive(qRound(opacity() * 100.0)) : rules()->checkOpacityInactive(qRound(opacity() * 100.0)); setOpacity(ruledOpacity / 100.0); workspace()->setActiveClient(act ? this : NULL); if (!m_active) cancelAutoRaise(); if (!m_active && shadeMode() == ShadeActivated) setShade(ShadeNormal); StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active windows may get different layer auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isFullScreen()) // fullscreens go high even if their transient is active workspace()->updateClientLayer(*it); doSetActive(); emit activeChanged(); updateMouseGrab(); } void AbstractClient::doSetActive() { } Layer AbstractClient::layer() const { if (m_layer == UnknownLayer) const_cast< AbstractClient* >(this)->m_layer = belongsToLayer(); return m_layer; } void AbstractClient::updateLayer() { if (layer() == belongsToLayer()) return; StackingUpdatesBlocker blocker(workspace()); invalidateLayer(); // invalidate, will be updated when doing restacking for (auto it = transients().constBegin(), end = transients().constEnd(); it != end; ++it) (*it)->updateLayer(); } void AbstractClient::invalidateLayer() { m_layer = UnknownLayer; } Layer AbstractClient::belongsToLayer() const { // NOTICE while showingDesktop, desktops move to the AboveLayer // (interchangeable w/ eg. yakuake etc. which will at first remain visible) // and the docks move into the NotificationLayer (which is between Above- and // ActiveLayer, so that active fullscreen windows will still cover everything) // Since the desktop is also activated, nothing should be in the ActiveLayer, though if (isDesktop()) return workspace()->showingDesktop() ? AboveLayer : DesktopLayer; if (isSplash()) // no damn annoying splashscreens return NormalLayer; // getting in the way of everything else if (isDock()) { if (workspace()->showingDesktop()) return NotificationLayer; return layerForDock(); } if (isOnScreenDisplay()) return OnScreenDisplayLayer; if (isNotification()) return NotificationLayer; if (workspace()->showingDesktop() && belongsToDesktop()) { return AboveLayer; } if (keepBelow()) return BelowLayer; if (isActiveFullScreen()) return ActiveLayer; if (keepAbove()) return AboveLayer; return NormalLayer; } bool AbstractClient::belongsToDesktop() const { return false; } Layer AbstractClient::layerForDock() const { // slight hack for the 'allow window to cover panel' Kicker setting // don't move keepbelow docks below normal window, but only to the same // layer, so that both may be raised to cover the other if (keepBelow()) return NormalLayer; if (keepAbove()) // slight hack for the autohiding panels return AboveLayer; return DockLayer; } void AbstractClient::setKeepAbove(bool b) { b = rules()->checkKeepAbove(b); if (b && !rules()->checkKeepBelow(false)) setKeepBelow(false); if (b == keepAbove()) { // force hint change if different if (info && bool(info->state() & NET::KeepAbove) != keepAbove()) info->setState(keepAbove() ? NET::KeepAbove : NET::States(0), NET::KeepAbove); return; } m_keepAbove = b; if (info) { info->setState(keepAbove() ? NET::KeepAbove : NET::States(0), NET::KeepAbove); } workspace()->updateClientLayer(this); updateWindowRules(Rules::Above); doSetKeepAbove(); emit keepAboveChanged(m_keepAbove); } void AbstractClient::doSetKeepAbove() { } void AbstractClient::setKeepBelow(bool b) { b = rules()->checkKeepBelow(b); if (b && !rules()->checkKeepAbove(false)) setKeepAbove(false); if (b == keepBelow()) { // force hint change if different if (info && bool(info->state() & NET::KeepBelow) != keepBelow()) info->setState(keepBelow() ? NET::KeepBelow : NET::States(0), NET::KeepBelow); return; } m_keepBelow = b; if (info) { info->setState(keepBelow() ? NET::KeepBelow : NET::States(0), NET::KeepBelow); } workspace()->updateClientLayer(this); updateWindowRules(Rules::Below); doSetKeepBelow(); emit keepBelowChanged(m_keepBelow); } void AbstractClient::doSetKeepBelow() { } void AbstractClient::startAutoRaise() { delete m_autoRaiseTimer; m_autoRaiseTimer = new QTimer(this); connect(m_autoRaiseTimer, &QTimer::timeout, this, &AbstractClient::autoRaise); m_autoRaiseTimer->setSingleShot(true); m_autoRaiseTimer->start(options->autoRaiseInterval()); } void AbstractClient::cancelAutoRaise() { delete m_autoRaiseTimer; m_autoRaiseTimer = nullptr; } void AbstractClient::autoRaise() { workspace()->raiseClient(this); cancelAutoRaise(); } bool AbstractClient::wantsTabFocus() const { return (isNormalWindow() || isDialog()) && wantsInput(); } bool AbstractClient::isSpecialWindow() const { // TODO return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay(); } void AbstractClient::demandAttention(bool set) { if (isActive()) set = false; if (m_demandsAttention == set) return; m_demandsAttention = set; if (info) { info->setState(set ? NET::DemandsAttention : NET::States(0), NET::DemandsAttention); } workspace()->clientAttentionChanged(this, set); emit demandsAttentionChanged(); } void AbstractClient::setDesktop(int desktop) { const int numberOfDesktops = VirtualDesktopManager::self()->count(); if (desktop != NET::OnAllDesktops) // Do range check desktop = qMax(1, qMin(numberOfDesktops, desktop)); desktop = qMin(numberOfDesktops, rules()->checkDesktop(desktop)); if (m_desktop == desktop) return; int was_desk = m_desktop; const bool wasOnCurrentDesktop = isOnCurrentDesktop(); m_desktop = desktop; if (info) { info->setDesktop(desktop); } if ((was_desk == NET::OnAllDesktops) != (desktop == NET::OnAllDesktops)) { // onAllDesktops changed workspace()->updateOnAllDesktopsOfTransients(this); } auto transients_stacking_order = workspace()->ensureStackingOrder(transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) (*it)->setDesktop(desktop); if (isModal()) // if a modal dialog is moved, move the mainwindow with it as otherwise // the (just moved) modal dialog will confusingly return to the mainwindow with // the next desktop change { foreach (AbstractClient * c2, mainClients()) c2->setDesktop(desktop); } doSetDesktop(desktop, was_desk); FocusChain::self()->update(this, FocusChain::MakeFirst); updateWindowRules(Rules::Desktop); emit desktopChanged(); if (wasOnCurrentDesktop != isOnCurrentDesktop()) emit desktopPresenceChanged(this, was_desk); } void AbstractClient::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) } void AbstractClient::setOnAllDesktops(bool b) { if ((b && isOnAllDesktops()) || (!b && !isOnAllDesktops())) return; if (b) setDesktop(NET::OnAllDesktops); else setDesktop(VirtualDesktopManager::self()->current()); } bool AbstractClient::isShadeable() const { return false; } void AbstractClient::setShade(bool set) { set ? setShade(ShadeNormal) : setShade(ShadeNone); } void AbstractClient::setShade(ShadeMode mode) { Q_UNUSED(mode) } ShadeMode AbstractClient::shadeMode() const { return ShadeNone; } AbstractClient::Position AbstractClient::titlebarPosition() const { // TODO: still needed, remove? return PositionTop; } bool AbstractClient::titlebarPositionUnderMouse() const { if (!isDecorated()) { return false; } const auto sectionUnderMouse = decoration()->sectionUnderMouse(); if (sectionUnderMouse == Qt::TitleBarArea) { return true; } // check other sections based on titlebarPosition switch (titlebarPosition()) { case AbstractClient::PositionTop: return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::TopSection || sectionUnderMouse == Qt::TopRightSection); case AbstractClient::PositionLeft: return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::LeftSection || sectionUnderMouse == Qt::BottomLeftSection); case AbstractClient::PositionRight: return (sectionUnderMouse == Qt::BottomRightSection || sectionUnderMouse == Qt::RightSection || sectionUnderMouse == Qt::TopRightSection); case AbstractClient::PositionBottom: return (sectionUnderMouse == Qt::BottomLeftSection || sectionUnderMouse == Qt::BottomSection || sectionUnderMouse == Qt::BottomRightSection); default: // nothing return false; } } void AbstractClient::setMinimized(bool set) { set ? minimize() : unminimize(); } void AbstractClient::minimize(bool avoid_animation) { if (!isMinimizable() || isMinimized()) return; if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(0, NET::Shaded); m_minimized = true; doMinimize(); updateWindowRules(Rules::Minimize); FocusChain::self()->update(this, FocusChain::MakeFirstMinimized); // TODO: merge signal with s_minimized emit clientMinimized(this, !avoid_animation); emit minimizedChanged(); } void AbstractClient::unminimize(bool avoid_animation) { if (!isMinimized()) return; if (rules()->checkMinimize(false)) { return; } if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(NET::Shaded, NET::Shaded); m_minimized = false; doMinimize(); updateWindowRules(Rules::Minimize); emit clientUnminimized(this, !avoid_animation); emit minimizedChanged(); } void AbstractClient::doMinimize() { } QPalette AbstractClient::palette() const { if (!m_palette) { return QPalette(); } return m_palette->palette(); } const Decoration::DecorationPalette *AbstractClient::decorationPalette() const { return m_palette.get(); } void AbstractClient::updateColorScheme(QString path) { if (path.isEmpty()) { path = QStringLiteral("kdeglobals"); } if (!m_palette || m_colorScheme != path) { m_colorScheme = path; if (m_palette) { disconnect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange); } auto it = s_palettes.find(m_colorScheme); if (it == s_palettes.end() || it->expired()) { m_palette = std::make_shared(m_colorScheme); if (m_palette->isValid()) { s_palettes[m_colorScheme] = m_palette; } else { if (!s_defaultPalette) { s_defaultPalette = std::make_shared(QStringLiteral("kdeglobals")); s_palettes[QStringLiteral("kdeglobals")] = s_defaultPalette; } m_palette = s_defaultPalette; } if (m_colorScheme == QStringLiteral("kdeglobals")) { s_defaultPalette = m_palette; } } else { m_palette = it->lock(); } connect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange); emit paletteChanged(palette()); } } void AbstractClient::handlePaletteChange() { emit paletteChanged(palette()); } void AbstractClient::keepInArea(QRect area, bool partial) { if (partial) { // increase the area so that can have only 100 pixels in the area area.setLeft(qMin(area.left() - width() + 100, area.left())); area.setTop(qMin(area.top() - height() + 100, area.top())); area.setRight(qMax(area.right() + width() - 100, area.right())); area.setBottom(qMax(area.bottom() + height() - 100, area.bottom())); } if (!partial) { // resize to fit into area if (area.width() < width() || area.height() < height()) resizeWithChecks(qMin(area.width(), width()), qMin(area.height(), height())); } int tx = x(), ty = y(); if (geometry().right() > area.right() && width() <= area.width()) tx = area.right() - width() + 1; if (geometry().bottom() > area.bottom() && height() <= area.height()) ty = area.bottom() - height() + 1; if (!area.contains(geometry().topLeft())) { if (tx < area.x()) tx = area.x(); if (ty < area.y()) ty = area.y(); } if (tx != x() || ty != y()) move(tx, ty); } QSize AbstractClient::maxSize() const { return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX)); } QSize AbstractClient::minSize() const { return rules()->checkMinSize(QSize(0, 0)); } void AbstractClient::updateMoveResize(const QPointF ¤tGlobalCursor) { handleMoveResize(pos(), currentGlobalCursor.toPoint()); } bool AbstractClient::hasStrut() const { return false; } void AbstractClient::setupWindowManagementInterface() { if (m_windowManagementInterface) { // already setup return; } if (!waylandServer() || !surface()) { return; } if (!waylandServer()->windowManagement()) { return; } using namespace KWayland::Server; auto w = waylandServer()->windowManagement()->createWindow(waylandServer()->windowManagement()); w->setTitle(caption()); w->setVirtualDesktop(isOnAllDesktops() ? 0 : desktop() - 1); w->setActive(isActive()); w->setFullscreen(isFullScreen()); w->setKeepAbove(keepAbove()); w->setKeepBelow(keepBelow()); w->setMaximized(maximizeMode() == KWin::MaximizeFull); w->setMinimized(isMinimized()); w->setOnAllDesktops(isOnAllDesktops()); w->setDemandsAttention(isDemandingAttention()); w->setCloseable(isCloseable()); w->setMaximizeable(isMaximizable()); w->setMinimizeable(isMinimizable()); w->setFullscreenable(isFullScreenable()); w->setIcon(icon()); auto updateAppId = [this, w] { w->setAppId(QString::fromUtf8(m_desktopFileName.isEmpty() ? resourceClass() : m_desktopFileName)); }; updateAppId(); w->setSkipTaskbar(skipTaskbar()); w->setPid(pid()); w->setShadeable(isShadeable()); w->setShaded(isShade()); w->setResizable(isResizable()); w->setMovable(isMovable()); w->setVirtualDesktopChangeable(true); // FIXME Matches Client::actionSupported(), but both should be implemented. w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); w->setGeometry(geom); connect(this, &AbstractClient::skipTaskbarChanged, w, [w, this] { w->setSkipTaskbar(skipTaskbar()); } ); connect(this, &AbstractClient::captionChanged, w, [w, this] { w->setTitle(caption()); }); connect(this, &AbstractClient::desktopChanged, w, [w, this] { if (isOnAllDesktops()) { w->setOnAllDesktops(true); return; } w->setVirtualDesktop(desktop() - 1); w->setOnAllDesktops(false); } ); connect(this, &AbstractClient::activeChanged, w, [w, this] { w->setActive(isActive()); }); connect(this, &AbstractClient::fullScreenChanged, w, [w, this] { w->setFullscreen(isFullScreen()); }); connect(this, &AbstractClient::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove); connect(this, &AbstractClient::keepBelowChanged, w, &PlasmaWindowInterface::setKeepBelow); connect(this, &AbstractClient::minimizedChanged, w, [w, this] { w->setMinimized(isMinimized()); }); connect(this, static_cast(&AbstractClient::clientMaximizedStateChanged), w, [w] (KWin::AbstractClient *c, MaximizeMode mode) { Q_UNUSED(c); w->setMaximized(mode == KWin::MaximizeFull); } ); connect(this, &AbstractClient::demandsAttentionChanged, w, [w, this] { w->setDemandsAttention(isDemandingAttention()); }); connect(this, &AbstractClient::iconChanged, w, [w, this] { w->setIcon(icon()); } ); connect(this, &AbstractClient::windowClassChanged, w, updateAppId); connect(this, &AbstractClient::desktopFileNameChanged, w, updateAppId); connect(this, &AbstractClient::shadeChanged, w, [w, this] { w->setShaded(isShade()); }); connect(this, &AbstractClient::transientChanged, w, [w, this] { w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); } ); connect(this, &AbstractClient::geometryChanged, w, [w, this] { w->setGeometry(geom); } ); connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); }); connect(w, &PlasmaWindowInterface::moveRequested, this, [this] { Cursor::setPos(geometry().center()); performMouseCommand(Options::MouseMove, Cursor::pos()); } ); connect(w, &PlasmaWindowInterface::resizeRequested, this, [this] { Cursor::setPos(geometry().bottomRight()); performMouseCommand(Options::MouseResize, Cursor::pos()); } ); connect(w, &PlasmaWindowInterface::virtualDesktopRequested, this, [this] (quint32 desktop) { workspace()->sendClientToDesktop(this, desktop + 1, true); } ); connect(w, &PlasmaWindowInterface::fullscreenRequested, this, [this] (bool set) { setFullScreen(set, false); } ); connect(w, &PlasmaWindowInterface::minimizedRequested, this, [this] (bool set) { if (set) { minimize(); } else { unminimize(); } } ); connect(w, &PlasmaWindowInterface::maximizedRequested, this, [this] (bool set) { maximize(set ? MaximizeFull : MaximizeRestore); } ); connect(w, &PlasmaWindowInterface::keepAboveRequested, this, [this] (bool set) { setKeepAbove(set); } ); connect(w, &PlasmaWindowInterface::keepBelowRequested, this, [this] (bool set) { setKeepBelow(set); } ); connect(w, &PlasmaWindowInterface::demandsAttentionRequested, this, [this] (bool set) { demandAttention(set); } ); connect(w, &PlasmaWindowInterface::activeRequested, this, [this] (bool set) { if (set) { workspace()->activateClient(this, true); } } ); connect(w, &PlasmaWindowInterface::shadedRequested, this, [this] (bool set) { setShade(set); } ); m_windowManagementInterface = w; } void AbstractClient::destroyWindowManagementInterface() { if (m_windowManagementInterface) { m_windowManagementInterface->unmap(); m_windowManagementInterface = nullptr; } } Options::MouseCommand AbstractClient::getMouseCommand(Qt::MouseButton button, bool *handled) const { *handled = false; if (button == Qt::NoButton) { return Options::MouseNothing; } if (isActive()) { if (options->isClickRaise()) { *handled = true; return Options::MouseActivateRaiseAndPassClick; } } else { *handled = true; switch (button) { case Qt::LeftButton: return options->commandWindow1(); case Qt::MiddleButton: return options->commandWindow2(); case Qt::RightButton: return options->commandWindow3(); default: // all other buttons pass Activate & Pass Client return Options::MouseActivateAndPassClick; } } return Options::MouseNothing; } Options::MouseCommand AbstractClient::getWheelCommand(Qt::Orientation orientation, bool *handled) const { *handled = false; if (orientation != Qt::Vertical) { return Options::MouseNothing; } if (!isActive()) { *handled = true; return options->commandWindowWheel(); } return Options::MouseNothing; } bool AbstractClient::performMouseCommand(Options::MouseCommand cmd, const QPoint &globalPos) { bool replay = false; switch(cmd) { case Options::MouseRaise: workspace()->raiseClient(this); break; case Options::MouseLower: { workspace()->lowerClient(this); // used to be activateNextClient(this), then topClientOnDesktop // since this is a mouseOp it's however safe to use the client under the mouse instead if (isActive() && options->focusPolicyIsReasonable()) { AbstractClient *next = workspace()->clientUnderMouse(screen()); if (next && next != this) workspace()->requestFocus(next, false); } break; } case Options::MouseOperationsMenu: if (isActive() && options->isClickRaise()) autoRaise(); workspace()->showWindowMenu(QRect(globalPos, globalPos), this); break; case Options::MouseToggleRaiseAndLower: workspace()->raiseOrLowerClient(this); break; case Options::MouseActivateAndRaise: { replay = isActive(); // for clickraise mode bool mustReplay = !rules()->checkAcceptFocus(acceptsFocus()); if (mustReplay) { ToplevelList::const_iterator it = workspace()->stackingOrder().constEnd(), begin = workspace()->stackingOrder().constBegin(); while (mustReplay && --it != begin && *it != this) { AbstractClient *c = qobject_cast(*it); if (!c || (c->keepAbove() && !keepAbove()) || (keepBelow() && !c->keepBelow())) continue; // can never raise above "it" mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->geometry().intersects(geometry())); } } workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); screens()->setCurrent(globalPos); replay = replay || mustReplay; break; } case Options::MouseActivateAndLower: workspace()->requestFocus(this); workspace()->lowerClient(this); screens()->setCurrent(globalPos); replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); break; case Options::MouseActivate: replay = isActive(); // for clickraise mode workspace()->takeActivity(this, Workspace::ActivityFocus); screens()->setCurrent(globalPos); replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); break; case Options::MouseActivateRaiseAndPassClick: workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); screens()->setCurrent(globalPos); replay = true; break; case Options::MouseActivateAndPassClick: workspace()->takeActivity(this, Workspace::ActivityFocus); screens()->setCurrent(globalPos); replay = true; break; case Options::MouseMaximize: maximize(MaximizeFull); break; case Options::MouseRestore: maximize(MaximizeRestore); break; case Options::MouseMinimize: minimize(); break; case Options::MouseAbove: { StackingUpdatesBlocker blocker(workspace()); if (keepBelow()) setKeepBelow(false); else setKeepAbove(true); break; } case Options::MouseBelow: { StackingUpdatesBlocker blocker(workspace()); if (keepAbove()) setKeepAbove(false); else setKeepBelow(true); break; } case Options::MousePreviousDesktop: workspace()->windowToPreviousDesktop(this); break; case Options::MouseNextDesktop: workspace()->windowToNextDesktop(this); break; case Options::MouseOpacityMore: if (!isDesktop()) // No point in changing the opacity of the desktop setOpacity(qMin(opacity() + 0.1, 1.0)); break; case Options::MouseOpacityLess: if (!isDesktop()) // No point in changing the opacity of the desktop setOpacity(qMax(opacity() - 0.1, 0.1)); break; case Options::MousePreviousTab: if (tabGroup()) tabGroup()->activatePrev(); break; case Options::MouseNextTab: if (tabGroup()) tabGroup()->activateNext(); break; case Options::MouseClose: closeWindow(); break; case Options::MouseActivateRaiseAndMove: case Options::MouseActivateRaiseAndUnrestrictedMove: workspace()->raiseClient(this); workspace()->requestFocus(this); screens()->setCurrent(globalPos); // fallthrough case Options::MouseMove: case Options::MouseUnrestrictedMove: { if (!isMovableAcrossScreens()) break; if (isMoveResize()) finishMoveResize(false); setMoveResizePointerMode(PositionCenter); setMoveResizePointerButtonDown(true); setMoveOffset(QPoint(globalPos.x() - x(), globalPos.y() - y())); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize((cmd == Options::MouseActivateRaiseAndUnrestrictedMove || cmd == Options::MouseUnrestrictedMove)); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); break; } case Options::MouseResize: case Options::MouseUnrestrictedResize: { if (!isResizable() || isShade()) break; if (isMoveResize()) finishMoveResize(false); setMoveResizePointerButtonDown(true); const QPoint moveOffset = QPoint(globalPos.x() - x(), globalPos.y() - y()); // map from global setMoveOffset(moveOffset); int x = moveOffset.x(), y = moveOffset.y(); bool left = x < width() / 3; bool right = x >= 2 * width() / 3; bool top = y < height() / 3; bool bot = y >= 2 * height() / 3; Position mode; if (top) mode = left ? PositionTopLeft : (right ? PositionTopRight : PositionTop); else if (bot) mode = left ? PositionBottomLeft : (right ? PositionBottomRight : PositionBottom); else mode = (x < width() / 2) ? PositionLeft : PositionRight; setMoveResizePointerMode(mode); setInvertedMoveOffset(rect().bottomRight() - moveOffset); setUnrestrictedMoveResize((cmd == Options::MouseUnrestrictedResize)); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); break; } case Options::MouseDragTab: case Options::MouseNothing: default: replay = true; break; } return replay; } void AbstractClient::setTransientFor(AbstractClient *transientFor) { if (transientFor == this) { // cannot be transient for one self return; } if (m_transientFor == transientFor) { return; } m_transientFor = transientFor; emit transientChanged(); } const AbstractClient *AbstractClient::transientFor() const { return m_transientFor; } AbstractClient *AbstractClient::transientFor() { return m_transientFor; } bool AbstractClient::hasTransientPlacementHint() const { return false; } QPoint AbstractClient::transientPlacementHint() const { return QPoint(); } bool AbstractClient::hasTransient(const AbstractClient *c, bool indirect) const { Q_UNUSED(indirect); return c->transientFor() == this; } QList< AbstractClient* > AbstractClient::mainClients() const { if (const AbstractClient *t = transientFor()) { return QList{const_cast< AbstractClient* >(t)}; } return QList(); } QList AbstractClient::allMainClients() const { auto result = mainClients(); foreach (const auto *cl, result) { result += cl->allMainClients(); } return result; } void AbstractClient::setModal(bool m) { // Qt-3.2 can have even modal normal windows :( if (m_modal == m) return; m_modal = m; emit modalChanged(); // Changing modality for a mapped window is weird (?) // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG } bool AbstractClient::isModal() const { return m_modal; } void AbstractClient::addTransient(AbstractClient *cl) { assert(!m_transients.contains(cl)); assert(cl != this); m_transients.append(cl); } void AbstractClient::removeTransient(AbstractClient *cl) { m_transients.removeAll(cl); if (cl->transientFor() == this) { cl->setTransientFor(nullptr); } } void AbstractClient::removeTransientFromList(AbstractClient *cl) { m_transients.removeAll(cl); } bool AbstractClient::isActiveFullScreen() const { if (!isFullScreen()) return false; const auto ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker // according to NETWM spec implementation notes suggests // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer. // we'll also take the screen into account - return ac && (ac == this || ac->screen() != screen()); + return ac && (ac == this || ac->screen() != screen()|| ac->allMainClients().contains(const_cast(this))); } #define BORDER(which) \ int AbstractClient::border##which() const \ { \ return isDecorated() ? decoration()->border##which() : 0; \ } BORDER(Bottom) BORDER(Left) BORDER(Right) BORDER(Top) #undef BORDER QSize AbstractClient::sizeForClientSize(const QSize &wsize, Sizemode mode, bool noframe) const { Q_UNUSED(mode) Q_UNUSED(noframe) return wsize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); } void AbstractClient::addRepaintDuringGeometryUpdates() { const QRect deco_rect = visibleRect(); addLayerRepaint(m_visibleRectBeforeGeometryUpdate); addLayerRepaint(deco_rect); // trigger repaint of window's new location m_visibleRectBeforeGeometryUpdate = deco_rect; } void AbstractClient::updateGeometryBeforeUpdateBlocking() { m_geometryBeforeUpdateBlocking = geom; } void AbstractClient::updateTabGroupStates(TabGroup::States) { } void AbstractClient::doMove(int, int) { } void AbstractClient::updateInitialMoveResizeGeometry() { m_moveResize.initialGeometry = geometry(); m_moveResize.geometry = m_moveResize.initialGeometry; m_moveResize.startScreen = screen(); } void AbstractClient::updateCursor() { Position m = moveResizePointerMode(); if (!isResizable() || isShade()) m = PositionCenter; Qt::CursorShape c = Qt::ArrowCursor; switch(m) { case PositionTopLeft: case PositionBottomRight: c = Qt::SizeFDiagCursor; break; case PositionBottomLeft: case PositionTopRight: c = Qt::SizeBDiagCursor; break; case PositionTop: case PositionBottom: c = Qt::SizeVerCursor; break; case PositionLeft: case PositionRight: c = Qt::SizeHorCursor; break; default: if (isMoveResize()) c = Qt::SizeAllCursor; else c = Qt::ArrowCursor; break; } if (c == m_moveResize.cursor) return; m_moveResize.cursor = c; emit moveResizeCursorChanged(c); } void AbstractClient::leaveMoveResize() { workspace()->setClientIsMoving(nullptr); setMoveResize(false); if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) ScreenEdges::self()->reserveDesktopSwitching(false, Qt::Vertical|Qt::Horizontal); if (isElectricBorderMaximizing()) { outline()->hide(); elevate(false); } } bool AbstractClient::s_haveResizeEffect = false; void AbstractClient::updateHaveResizeEffect() { s_haveResizeEffect = effects && static_cast(effects)->provides(Effect::Resize); } bool AbstractClient::doStartMoveResize() { return true; } void AbstractClient::positionGeometryTip() { } void AbstractClient::doPerformMoveResize() { } bool AbstractClient::isWaitingForMoveResizeSync() const { return false; } void AbstractClient::doResizeSync() { } void AbstractClient::checkQuickTilingMaximizationZones(int xroot, int yroot) { QuickTileMode mode = QuickTileFlag::None; bool innerBorder = false; for (int i=0; i < screens()->count(); ++i) { if (!screens()->geometry(i).contains(QPoint(xroot, yroot))) continue; auto isInScreen = [i](const QPoint &pt) { for (int j = 0; j < screens()->count(); ++j) { if (j == i) continue; if (screens()->geometry(j).contains(pt)) { return true; } } return false; }; QRect area = workspace()->clientArea(MaximizeArea, QPoint(xroot, yroot), desktop()); if (options->electricBorderTiling()) { if (xroot <= area.x() + 20) { mode |= QuickTileFlag::Left; innerBorder = isInScreen(QPoint(area.x() - 1, yroot)); } else if (xroot >= area.x() + area.width() - 20) { mode |= QuickTileFlag::Right; innerBorder = isInScreen(QPoint(area.right() + 1, yroot)); } } if (mode != QuickTileMode(QuickTileFlag::None)) { if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio()) mode |= QuickTileFlag::Top; else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio()) mode |= QuickTileFlag::Bottom; } else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) { mode = QuickTileFlag::Maximize; innerBorder = isInScreen(QPoint(xroot, area.y() - 1)); } break; // no point in checking other screens to contain this... "point"... } if (mode != electricBorderMode()) { setElectricBorderMode(mode); if (innerBorder) { if (!m_electricMaximizingDelay) { m_electricMaximizingDelay = new QTimer(this); m_electricMaximizingDelay->setInterval(250); m_electricMaximizingDelay->setSingleShot(true); connect(m_electricMaximizingDelay, &QTimer::timeout, [this]() { if (isMove()) setElectricBorderMaximizing(electricBorderMode() != QuickTileMode(QuickTileFlag::None)); }); } m_electricMaximizingDelay->start(); } else { setElectricBorderMaximizing(mode != QuickTileMode(QuickTileFlag::None)); } } } void AbstractClient::keyPressEvent(uint key_code) { if (!isMove() && !isResize()) return; bool is_control = key_code & Qt::CTRL; bool is_alt = key_code & Qt::ALT; key_code = key_code & ~Qt::KeyboardModifierMask; int delta = is_control ? 1 : is_alt ? 32 : 8; QPoint pos = Cursor::pos(); switch(key_code) { case Qt::Key_Left: pos.rx() -= delta; break; case Qt::Key_Right: pos.rx() += delta; break; case Qt::Key_Up: pos.ry() -= delta; break; case Qt::Key_Down: pos.ry() += delta; break; case Qt::Key_Space: case Qt::Key_Return: case Qt::Key_Enter: finishMoveResize(false); setMoveResizePointerButtonDown(false); updateCursor(); break; case Qt::Key_Escape: finishMoveResize(true); setMoveResizePointerButtonDown(false); updateCursor(); break; default: return; } Cursor::setPos(pos); } QSize AbstractClient::resizeIncrements() const { return QSize(1, 1); } void AbstractClient::dontMoveResize() { setMoveResizePointerButtonDown(false); stopDelayedMoveResize(); if (isMoveResize()) finishMoveResize(false); } AbstractClient::Position AbstractClient::mousePosition() const { if (isDecorated()) { switch (decoration()->sectionUnderMouse()) { case Qt::BottomLeftSection: return PositionBottomLeft; case Qt::BottomRightSection: return PositionBottomRight; case Qt::BottomSection: return PositionBottom; case Qt::LeftSection: return PositionLeft; case Qt::RightSection: return PositionRight; case Qt::TopSection: return PositionTop; case Qt::TopLeftSection: return PositionTopLeft; case Qt::TopRightSection: return PositionTopRight; default: return PositionCenter; } } return PositionCenter; } void AbstractClient::endMoveResize() { setMoveResizePointerButtonDown(false); stopDelayedMoveResize(); if (isMoveResize()) { finishMoveResize(false); setMoveResizePointerMode(mousePosition()); } updateCursor(); } void AbstractClient::destroyDecoration() { delete m_decoration.decoration; m_decoration.decoration = nullptr; } bool AbstractClient::decorationHasAlpha() const { if (!isDecorated() || decoration()->isOpaque()) { // either no decoration or decoration has alpha disabled return false; } return true; } void AbstractClient::triggerDecorationRepaint() { if (isDecorated()) { decoration()->update(); } } void AbstractClient::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); top = QRect(r.x(), r.y(), r.width(), borderTop()); bottom = QRect(r.x(), r.y() + r.height() - borderBottom(), r.width(), borderBottom()); left = QRect(r.x(), r.y() + top.height(), borderLeft(), r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight(), r.y() + top.height(), borderRight(), r.height() - top.height() - bottom.height()); } void AbstractClient::processDecorationMove(const QPoint &localPos, const QPoint &globalPos) { if (isMoveResizePointerButtonDown()) { handleMoveResize(localPos.x(), localPos.y(), globalPos.x(), globalPos.y()); return; } // TODO: handle modifiers Position newmode = mousePosition(); if (newmode != moveResizePointerMode()) { setMoveResizePointerMode(newmode); updateCursor(); } } bool AbstractClient::processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu) { Options::MouseCommand com = Options::MouseNothing; bool active = isActive(); if (!wantsInput()) // we cannot be active, use it anyway active = true; // check whether it is a double click if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) { if (m_decoration.doubleClickTimer.isValid()) { const qint64 interval = m_decoration.doubleClickTimer.elapsed(); m_decoration.doubleClickTimer.invalidate(); if (interval > QGuiApplication::styleHints()->mouseDoubleClickInterval()) { m_decoration.doubleClickTimer.start(); // expired -> new first click and pot. init } else { Workspace::self()->performWindowOperation(this, options->operationTitlebarDblClick()); dontMoveResize(); return false; } } else { m_decoration.doubleClickTimer.start(); // new first click and pot. init, could be invalidated by release - see below } } if (event->button() == Qt::LeftButton) com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); else if (event->button() == Qt::MidButton) com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); else if (event->button() == Qt::RightButton) com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); if (event->button() == Qt::LeftButton && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching && com != Options::MouseMinimize // mouse release event && com != Options::MouseDragTab) { setMoveResizePointerMode(mousePosition()); setMoveResizePointerButtonDown(true); setMoveOffset(event->pos()); setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); startDelayedMoveResize(); updateCursor(); } // In the new API the decoration may process the menu action to display an inactive tab's menu. // If the event is unhandled then the core will create one for the active window in the group. if (!ignoreMenu || com != Options::MouseOperationsMenu) performMouseCommand(com, event->globalPos()); return !( // Return events that should be passed to the decoration in the new API com == Options::MouseRaise || com == Options::MouseOperationsMenu || com == Options::MouseActivateAndRaise || com == Options::MouseActivate || com == Options::MouseActivateRaiseAndPassClick || com == Options::MouseActivateAndPassClick || com == Options::MouseDragTab || com == Options::MouseNothing); } void AbstractClient::processDecorationButtonRelease(QMouseEvent *event) { if (isDecorated()) { if (event->isAccepted() || !titlebarPositionUnderMouse()) { invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick } } if (event->buttons() == Qt::NoButton) { setMoveResizePointerButtonDown(false); stopDelayedMoveResize(); if (isMoveResize()) { finishMoveResize(false); setMoveResizePointerMode(mousePosition()); } updateCursor(); } } void AbstractClient::startDecorationDoubleClickTimer() { m_decoration.doubleClickTimer.start(); } void AbstractClient::invalidateDecorationDoubleClickTimer() { m_decoration.doubleClickTimer.invalidate(); } bool AbstractClient::providesContextHelp() const { return false; } void AbstractClient::showContextHelp() { } QPointer AbstractClient::decoratedClient() const { return m_decoration.client; } void AbstractClient::setDecoratedClient(QPointer< Decoration::DecoratedClientImpl > client) { m_decoration.client = client; } void AbstractClient::enterEvent(const QPoint &globalPos) { // TODO: shade hover if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown()) return; if (options->isAutoRaise() && !isDesktop() && !isDock() && workspace()->focusChangeEnabled() && globalPos != workspace()->focusMousePosition() && workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), options->isSeparateScreenFocus() ? screen() : -1) != this) { startAutoRaise(); } if (isDesktop() || isDock()) return; // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus // change came because of window changes (e.g. closing a window) - #92290 if (options->focusPolicy() != Options::FocusFollowsMouse || globalPos != workspace()->focusMousePosition()) { workspace()->requestDelayFocus(this); } } void AbstractClient::leaveEvent() { cancelAutoRaise(); workspace()->cancelDelayFocus(); // TODO: shade hover // TODO: send hover leave to deco // TODO: handle Options::FocusStrictlyUnderMouse } QRect AbstractClient::iconGeometry() const { if (!windowManagementInterface() || !waylandServer()) { // window management interface is only available if the surface is mapped return QRect(); } int minDistance = INT_MAX; AbstractClient *candidatePanel = nullptr; QRect candidateGeom; for (auto i = windowManagementInterface()->minimizedGeometries().constBegin(), end = windowManagementInterface()->minimizedGeometries().constEnd(); i != end; ++i) { AbstractClient *client = waylandServer()->findAbstractClient(i.key()); if (!client) { continue; } const int distance = QPoint(client->pos() - pos()).manhattanLength(); if (distance < minDistance) { minDistance = distance; candidatePanel = client; candidateGeom = i.value(); } } if (!candidatePanel) { return QRect(); } return candidateGeom.translated(candidatePanel->pos()); } QRect AbstractClient::inputGeometry() const { if (isDecorated()) { return Toplevel::inputGeometry() + decoration()->resizeOnlyBorders(); } return Toplevel::inputGeometry(); } bool AbstractClient::dockWantsInput() const { return false; } void AbstractClient::setDesktopFileName(const QByteArray &name) { if (name == m_desktopFileName) { return; } m_desktopFileName = name; emit desktopFileNameChanged(); } QString AbstractClient::iconFromDesktopFile() const { if (m_desktopFileName.isEmpty()) { return QString(); } QString desktopFile = QString::fromUtf8(m_desktopFileName); if (!desktopFile.endsWith(QLatin1String(".desktop"))) { desktopFile.append(QLatin1String(".desktop")); } KDesktopFile df(desktopFile); return df.readIcon(); } bool AbstractClient::hasApplicationMenu() const { return ApplicationMenu::self()->applicationMenuEnabled() && !m_applicationMenuServiceName.isEmpty() && !m_applicationMenuObjectPath.isEmpty(); } void AbstractClient::updateApplicationMenuServiceName(const QString &serviceName) { const bool old_hasApplicationMenu = hasApplicationMenu(); m_applicationMenuServiceName = serviceName; const bool new_hasApplicationMenu = hasApplicationMenu(); if (old_hasApplicationMenu != new_hasApplicationMenu) { emit hasApplicationMenuChanged(new_hasApplicationMenu); } } void AbstractClient::updateApplicationMenuObjectPath(const QString &objectPath) { const bool old_hasApplicationMenu = hasApplicationMenu(); m_applicationMenuObjectPath = objectPath; const bool new_hasApplicationMenu = hasApplicationMenu(); if (old_hasApplicationMenu != new_hasApplicationMenu) { emit hasApplicationMenuChanged(new_hasApplicationMenu); } } void AbstractClient::setApplicationMenuActive(bool applicationMenuActive) { if (m_applicationMenuActive != applicationMenuActive) { m_applicationMenuActive = applicationMenuActive; emit applicationMenuActiveChanged(applicationMenuActive); } } void AbstractClient::showApplicationMenu(int actionId) { if (isDecorated()) { decoration()->showApplicationMenu(actionId); } else { // we don't know where the application menu button will be, show it in the top left corner instead Workspace::self()->showApplicationMenu(QRect(), this, actionId); } } bool AbstractClient::unresponsive() const { return m_unresponsive; } void AbstractClient::setUnresponsive(bool unresponsive) { if (m_unresponsive != unresponsive) { m_unresponsive = unresponsive; emit unresponsiveChanged(m_unresponsive); emit captionChanged(); } } QString AbstractClient::shortcutCaptionSuffix() const { if (shortcut().isEmpty()) { return QString(); } return QLatin1String(" {") + shortcut().toString() + QLatin1Char('}'); } AbstractClient *AbstractClient::findClientWithSameCaption() const { auto fetchNameInternalPredicate = [this](const AbstractClient *cl) { return (!cl->isSpecialWindow() || cl->isToolbar()) && cl != this && cl->captionNormal() == captionNormal() && cl->captionSuffix() == captionSuffix(); }; return workspace()->findAbstractClient(fetchNameInternalPredicate); } QString AbstractClient::caption() const { QString cap = captionNormal() + captionSuffix(); if (unresponsive()) { cap += QLatin1String(" "); cap += i18nc("Application is not responding, appended to window title", "(Not Responding)"); } return cap; } void AbstractClient::removeRule(Rules* rule) { m_rules.remove(rule); } void AbstractClient::discardTemporaryRules() { m_rules.discardTemporary(); } void AbstractClient::evaluateWindowRules() { setupWindowRules(true); applyWindowRules(); } void AbstractClient::setOnActivities(QStringList newActivitiesList) { Q_UNUSED(newActivitiesList) } +void AbstractClient::checkNoBorder() +{ + setNoBorder(false); +} + } diff --git a/abstract_client.h b/abstract_client.h index 4e1ffd715..b96429354 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -1,1176 +1,1182 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_ABSTRACT_CLIENT_H #define KWIN_ABSTRACT_CLIENT_H #include "toplevel.h" #include "options.h" #include "rules.h" #include "tabgroup.h" #include #include #include namespace KWayland { namespace Server { class PlasmaWindowInterface; } } namespace KDecoration2 { class Decoration; } namespace KWin { namespace TabBox { class TabBoxClientImpl; } namespace Decoration { class DecoratedClientImpl; class DecorationPalette; } class KWIN_EXPORT AbstractClient : public Toplevel { Q_OBJECT /** * Whether this Client is fullScreen. A Client might either be fullScreen due to the _NET_WM property * or through a legacy support hack. The fullScreen state can only be changed if the Client does not * use the legacy hack. To be sure whether the state changed, connect to the notify signal. **/ Q_PROPERTY(bool fullScreen READ isFullScreen WRITE setFullScreen NOTIFY fullScreenChanged) /** * Whether the Client can be set to fullScreen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool fullScreenable READ isFullScreenable) /** * Whether this Client is the currently visible Client in its Client Group (Window Tabs). * For change connect to the visibleChanged signal on the Client's Group. **/ Q_PROPERTY(bool isCurrentTab READ isCurrentTab) /** * Whether this Client is active or not. Use Workspace::activateClient() to activate a Client. * @see Workspace::activateClient **/ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) /** * The desktop this Client is on. If the Client is on all desktops the property has value -1. **/ Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) /** * Whether the Client is on all desktops. That is desktop is -1. **/ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops WRITE setOnAllDesktops NOTIFY desktopChanged) /** * Indicates that the window should not be included on a taskbar. **/ Q_PROPERTY(bool skipTaskbar READ skipTaskbar WRITE setSkipTaskbar NOTIFY skipTaskbarChanged) /** * Indicates that the window should not be included on a Pager. **/ Q_PROPERTY(bool skipPager READ skipPager WRITE setSkipPager NOTIFY skipPagerChanged) /** * Whether the Client should be excluded from window switching effects. **/ Q_PROPERTY(bool skipSwitcher READ skipSwitcher WRITE setSkipSwitcher NOTIFY skipSwitcherChanged) /** * Whether the window can be closed by the user. The value is evaluated each time the getter is called. * Because of that no changed signal is provided. **/ Q_PROPERTY(bool closeable READ isCloseable) Q_PROPERTY(QIcon icon READ icon NOTIFY iconChanged) /** * Whether the Client is set to be kept above other windows. **/ Q_PROPERTY(bool keepAbove READ keepAbove WRITE setKeepAbove NOTIFY keepAboveChanged) /** * Whether the Client is set to be kept below other windows. **/ Q_PROPERTY(bool keepBelow READ keepBelow WRITE setKeepBelow NOTIFY keepBelowChanged) /** * Whether the Client can be shaded. The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool shadeable READ isShadeable) /** * Whether the Client is shaded. **/ Q_PROPERTY(bool shade READ isShade WRITE setShade NOTIFY shadeChanged) /** * Whether the Client can be minimized. The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool minimizable READ isMinimizable) /** * Whether the Client is minimized. **/ Q_PROPERTY(bool minimized READ isMinimized WRITE setMinimized NOTIFY minimizedChanged) /** * The optional geometry representing the minimized Client in e.g a taskbar. * See _NET_WM_ICON_GEOMETRY at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. **/ Q_PROPERTY(QRect iconGeometry READ iconGeometry) /** * Returns whether the window is any of special windows types (desktop, dock, splash, ...), * i.e. window types that usually don't have a window frame and the user does not use window * management (moving, raising,...) on them. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. **/ Q_PROPERTY(bool specialWindow READ isSpecialWindow) /** * Whether window state _NET_WM_STATE_DEMANDS_ATTENTION is set. This state indicates that some * action in or with the window happened. For example, it may be set by the Window Manager if * the window requested activation but the Window Manager refused it, or the application may set * it if it finished some work. This state may be set by both the Client and the Window Manager. * It should be unset by the Window Manager when it decides the window got the required attention * (usually, that it got activated). **/ Q_PROPERTY(bool demandsAttention READ isDemandingAttention WRITE demandAttention NOTIFY demandsAttentionChanged) /** * The Caption of the Client. Read from WM_NAME property together with a suffix for hostname and shortcut. * To read only the caption as provided by WM_NAME, use the getter with an additional @c false value. **/ Q_PROPERTY(QString caption READ caption NOTIFY captionChanged) /** * Minimum size as specified in WM_NORMAL_HINTS **/ Q_PROPERTY(QSize minSize READ minSize) /** * Maximum size as specified in WM_NORMAL_HINTS **/ Q_PROPERTY(QSize maxSize READ maxSize) /** * Whether the Client can accept keyboard focus. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. **/ Q_PROPERTY(bool wantsInput READ wantsInput) /** * Whether the Client is a transient Window to another Window. * @see transientFor **/ Q_PROPERTY(bool transient READ isTransient NOTIFY transientChanged) /** * The Client to which this Client is a transient if any. **/ Q_PROPERTY(KWin::AbstractClient *transientFor READ transientFor NOTIFY transientChanged) /** * Whether the Client represents a modal window. **/ Q_PROPERTY(bool modal READ isModal NOTIFY modalChanged) /** * The geometry of this Client. Be aware that depending on resize mode the geometryChanged signal * might be emitted at each resize step or only at the end of the resize operation. **/ Q_PROPERTY(QRect geometry READ geometry WRITE setGeometry) /** * Whether the Client is currently being moved by the user. * Notify signal is emitted when the Client starts or ends move/resize mode. **/ Q_PROPERTY(bool move READ isMove NOTIFY moveResizedChanged) /** * Whether the Client is currently being resized by the user. * Notify signal is emitted when the Client starts or ends move/resize mode. **/ Q_PROPERTY(bool resize READ isResize NOTIFY moveResizedChanged) /** * Whether the decoration is currently using an alpha channel. **/ Q_PROPERTY(bool decorationHasAlpha READ decorationHasAlpha) /** * Whether the window has a decoration or not. * This property is not allowed to be set by applications themselves. * The decision whether a window has a border or not belongs to the window manager. * If this property gets abused by application developers, it will be removed again. **/ Q_PROPERTY(bool noBorder READ noBorder WRITE setNoBorder) /** * Whether the Client provides context help. Mostly needed by decorations to decide whether to * show the help button or not. **/ Q_PROPERTY(bool providesContextHelp READ providesContextHelp CONSTANT) /** * Whether the Client can be maximized both horizontally and vertically. * The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool maximizable READ isMaximizable) /** * Whether the Client is moveable. Even if it is not moveable, it might be possible to move * it to another screen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. * @see moveableAcrossScreens **/ Q_PROPERTY(bool moveable READ isMovable) /** * Whether the Client can be moved to another screen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. * @see moveable **/ Q_PROPERTY(bool moveableAcrossScreens READ isMovableAcrossScreens) /** * Whether the Client can be resized. The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool resizeable READ isResizable) /** * The desktop file name of the application this AbstractClient belongs to. * * This is either the base name without full path and without file extension of the * desktop file for the window's application (e.g. "org.kde.foo"). * * The application's desktop file name can also be the full path to the desktop file * (e.g. "/opt/kde/share/org.kde.foo.desktop") in case it's not in a standard location. **/ Q_PROPERTY(QByteArray desktopFileName READ desktopFileName NOTIFY desktopFileNameChanged) /** * Whether an application menu is available for this Client */ Q_PROPERTY(bool hasApplicationMenu READ hasApplicationMenu NOTIFY hasApplicationMenuChanged) /** * Whether the application menu for this Client is currently opened */ Q_PROPERTY(bool applicationMenuActive READ applicationMenuActive NOTIFY applicationMenuActiveChanged) /** * Whether this client is unresponsive. * * When an application failed to react on a ping request in time, it is * considered unresponsive. This usually indicates that the application froze or crashed. */ Q_PROPERTY(bool unresponsive READ unresponsive NOTIFY unresponsiveChanged) public: virtual ~AbstractClient(); QWeakPointer tabBoxClient() const { return m_tabBoxClient.toWeakRef(); } bool isFirstInTabBox() const { return m_firstInTabBox; } bool skipSwitcher() const { return m_skipSwitcher; } void setSkipSwitcher(bool set); bool skipTaskbar() const { return m_skipTaskbar; } void setSkipTaskbar(bool set); void setOriginalSkipTaskbar(bool set); bool originalSkipTaskbar() const { return m_originalSkipTaskbar; } bool skipPager() const { return m_skipPager; } void setSkipPager(bool set); const QIcon &icon() const { return m_icon; } bool isActive() const { return m_active; } /** * Sets the client's active state to \a act. * * This function does only change the visual appearance of the client, * it does not change the focus setting. Use * Workspace::activateClient() or Workspace::requestFocus() instead. * * If a client receives or looses the focus, it calls setActive() on * its own. **/ void setActive(bool); bool keepAbove() const { return m_keepAbove; } void setKeepAbove(bool); bool keepBelow() const { return m_keepBelow; } void setKeepBelow(bool); void demandAttention(bool set = true); bool isDemandingAttention() const { return m_demandsAttention; } void cancelAutoRaise(); bool wantsTabFocus() const; QPoint clientPos() const override { return QPoint(borderLeft(), borderTop()); } virtual void updateMouseGrab(); /** * @returns The caption consisting of @link{captionNormal} and @link{captionSuffix} * @see captionNormal * @see captionSuffix **/ QString caption() const; /** * @returns The caption as set by the AbstractClient without any suffix. * @see caption * @see captionSuffix **/ virtual QString captionNormal() const = 0; /** * @returns The suffix added to the caption (e.g. shortcut, machine name, etc.) * @see caption * @see captionNormal **/ virtual QString captionSuffix() const = 0; virtual bool isCloseable() const = 0; // TODO: remove boolean trap virtual bool isShown(bool shaded_is_shown) const = 0; virtual bool isHiddenInternal() const = 0; // TODO: remove boolean trap virtual void hideClient(bool hide) = 0; - virtual bool isFullScreenable() const = 0; + bool isFullScreenable() const; + bool isFullScreenable(bool fullscreen_hack) const; virtual bool isFullScreen() const = 0; // TODO: remove boolean trap virtual AbstractClient *findModal(bool allow_itself = false) = 0; virtual bool isTransient() const; /** * @returns Whether there is a hint available to place the AbstractClient on it's parent, default @c false. * @see transientPlacementHint **/ virtual bool hasTransientPlacementHint() const; /** * @returns The recommended position of the transient in parent coordinates **/ virtual QPoint transientPlacementHint() const; const AbstractClient* transientFor() const; AbstractClient* transientFor(); /** * @returns @c true if c is the transient_for window for this client, * or recursively the transient_for window * @todo: remove boolean trap **/ virtual bool hasTransient(const AbstractClient* c, bool indirect) const; const QList& transients() const; // Is not indirect virtual void removeTransient(AbstractClient* cl); virtual QList mainClients() const; // Call once before loop , is not indirect QList allMainClients() const; // Call once before loop , is indirect /** * Returns true for "special" windows and false for windows which are "normal" * (normal=window which has a border, can be moved by the user, can be closed, etc.) * true for Desktop, Dock, Splash, Override and TopMenu (and Toolbar??? - for now) * false for Normal, Dialog, Utility and Menu (and Toolbar??? - not yet) TODO */ bool isSpecialWindow() const; void sendToScreen(int screen); const QKeySequence &shortcut() const { return _shortcut; } void setShortcut(const QString &cut); virtual bool performMouseCommand(Options::MouseCommand, const QPoint &globalPos); void setOnAllDesktops(bool set); void setDesktop(int); int desktop() const override { return m_desktop; } void setMinimized(bool set); /** * Minimizes this client plus its transients */ void minimize(bool avoid_animation = false); void unminimize(bool avoid_animation = false); bool isMinimized() const { return m_minimized; } virtual void setFullScreen(bool set, bool user = true) = 0; virtual TabGroup *tabGroup() const; Q_INVOKABLE virtual bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false); virtual bool isCurrentTab() const; virtual QRect geometryRestore() const = 0; virtual MaximizeMode maximizeMode() const = 0; void maximize(MaximizeMode); void setMaximize(bool vertically, bool horizontally); virtual bool noBorder() const = 0; virtual void setNoBorder(bool set) = 0; virtual void blockActivityUpdates(bool b = true) = 0; QPalette palette() const; const Decoration::DecorationPalette *decorationPalette() const; virtual bool isResizable() const = 0; virtual bool isMovable() const = 0; virtual bool isMovableAcrossScreens() const = 0; /** * @c true only for @c ShadeNormal **/ bool isShade() const { return shadeMode() == ShadeNormal; } /** * Default implementation returns @c ShadeNone **/ virtual ShadeMode shadeMode() const; // Prefer isShade() void setShade(bool set); /** * Default implementation does nothing **/ virtual void setShade(ShadeMode mode); /** * Whether the Client can be shaded. Default implementation returns @c false. **/ virtual bool isShadeable() const; virtual bool isMaximizable() const = 0; virtual bool isMinimizable() const = 0; virtual QRect iconGeometry() const; virtual bool userCanSetFullScreen() const = 0; virtual bool userCanSetNoBorder() const = 0; + virtual void checkNoBorder(); virtual void setOnActivities(QStringList newActivitiesList); virtual void setOnAllActivities(bool set) = 0; const WindowRules* rules() const { return &m_rules; } void removeRule(Rules* r); void setupWindowRules(bool ignore_temporary); void evaluateWindowRules(); void applyWindowRules(); virtual void takeFocus() = 0; virtual bool wantsInput() const = 0; /** * Whether a dock window wants input. * * By default KWin doesn't pass focus to a dock window unless a force activate * request is provided. * * This method allows to have dock windows take focus also through flags set on * the window. * * The default implementation returns @c false. **/ virtual bool dockWantsInput() const; void checkWorkspacePosition(QRect oldGeometry = QRect(), int oldDesktop = -2, QRect oldClientGeometry = QRect()); virtual xcb_timestamp_t userTime() const; virtual void updateWindowRules(Rules::Types selection); void growHorizontal(); void shrinkHorizontal(); void growVertical(); void shrinkVertical(); void updateMoveResize(const QPointF ¤tGlobalCursor); /** * Ends move resize when all pointer buttons are up again. **/ void endMoveResize(); void keyPressEvent(uint key_code); void enterEvent(const QPoint &globalPos); void leaveEvent(); /** * These values represent positions inside an area */ enum Position { // without prefix, they'd conflict with Qt::TopLeftCorner etc. :( PositionCenter = 0x00, PositionLeft = 0x01, PositionRight = 0x02, PositionTop = 0x04, PositionBottom = 0x08, PositionTopLeft = PositionLeft | PositionTop, PositionTopRight = PositionRight | PositionTop, PositionBottomLeft = PositionLeft | PositionBottom, PositionBottomRight = PositionRight | PositionBottom }; Position titlebarPosition() const; bool titlebarPositionUnderMouse() const; // a helper for the workspace window packing. tests for screen validity and updates since in maximization case as with normal moving void packTo(int left, int top); /** Set the quick tile mode ("snap") of this window. * This will also handle preserving and restoring of window geometry as necessary. * @param mode The tile mode (left/right) to give this window. */ void setQuickTileMode(QuickTileMode mode, bool keyboard = false); QuickTileMode quickTileMode() const { return QuickTileMode(m_quickTileMode); } Layer layer() const override; void updateLayer(); enum ForceGeometry_t { NormalGeometrySet, ForceGeometrySet }; void move(int x, int y, ForceGeometry_t force = NormalGeometrySet); void move(const QPoint &p, ForceGeometry_t force = NormalGeometrySet); virtual void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) = 0; void resizeWithChecks(const QSize& s, ForceGeometry_t force = NormalGeometrySet); void keepInArea(QRect area, bool partial = false); virtual QSize minSize() const; virtual QSize maxSize() const; virtual void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) = 0; void setGeometry(const QRect& r, ForceGeometry_t force = NormalGeometrySet); /// How to resize the window in order to obey constains (mainly aspect ratios) enum Sizemode { SizemodeAny, SizemodeFixedW, ///< Try not to affect width SizemodeFixedH, ///< Try not to affect height SizemodeMax ///< Try not to make it larger in either direction }; /** *Calculate the appropriate frame size for the given client size @p wsize. * * @p wsize is adapted according to the window's size hints (minimum, maximum and incremental size changes). * * Default implementation returns the passed in @p wsize. */ virtual QSize sizeForClientSize(const QSize &wsize, Sizemode mode = SizemodeAny, bool noframe = false) const; QSize adjustedSize(const QSize&, Sizemode mode = SizemodeAny) const; QSize adjustedSize() const; bool isMove() const { return isMoveResize() && moveResizePointerMode() == PositionCenter; } bool isResize() const { return isMoveResize() && moveResizePointerMode() != PositionCenter; } /** * Cursor shape for move/resize mode. **/ Qt::CursorShape cursor() const { return m_moveResize.cursor; } virtual bool hasStrut() const; void setModal(bool modal); bool isModal() const; /** * Determines the mouse command for the given @p button in the current state. * * The @p handled argument specifies whether the button was handled or not. * This value should be used to determine whether the mouse button should be * passed to the AbstractClient or being filtered out. **/ Options::MouseCommand getMouseCommand(Qt::MouseButton button, bool *handled) const; Options::MouseCommand getWheelCommand(Qt::Orientation orientation, bool *handled) const; // decoration related KDecoration2::Decoration *decoration() { return m_decoration.decoration; } const KDecoration2::Decoration *decoration() const { return m_decoration.decoration; } bool isDecorated() const { return m_decoration.decoration != nullptr; } QPointer decoratedClient() const; void setDecoratedClient(QPointer client); bool decorationHasAlpha() const; void triggerDecorationRepaint(); virtual void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const; void processDecorationMove(const QPoint &localPos, const QPoint &globalPos); bool processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu = false); void processDecorationButtonRelease(QMouseEvent *event); /** * TODO: fix boolean traps **/ virtual void updateDecoration(bool check_workspace_pos, bool force = false) = 0; /** * Returns whether the window provides context help or not. If it does, * you should show a help menu item or a help button like '?' and call * contextHelp() if this is invoked. * * Default implementation returns @c false. * @see showContextHelp; */ virtual bool providesContextHelp() const; /** * Invokes context help on the window. Only works if the window * actually provides context help. * * Default implementation does nothing. * * @see providesContextHelp() */ virtual void showContextHelp(); QRect inputGeometry() const override; /** * Restores the AbstractClient after it had been hidden due to show on screen edge functionality. * The AbstractClient also gets raised (e.g. Panel mode windows can cover) and the AbstractClient * gets informed in a window specific way that it is shown and raised again. **/ virtual void showOnScreenEdge() = 0; QByteArray desktopFileName() const { return m_desktopFileName; } /** * Tries to terminate the process of this AbstractClient. * * Implementing subclasses can perform a windowing system solution for terminating. **/ virtual void killWindow() = 0; - // TODO: remove boolean trap - static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false); + enum class SameApplicationCheck { + RelaxedForActive = 1 << 0, + AllowCrossProcesses = 1 << 1 + }; + Q_DECLARE_FLAGS(SameApplicationChecks, SameApplicationCheck) + static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, SameApplicationChecks checks = SameApplicationChecks()); bool hasApplicationMenu() const; bool applicationMenuActive() const { return m_applicationMenuActive; } void setApplicationMenuActive(bool applicationMenuActive); QString applicationMenuServiceName() const { return m_applicationMenuServiceName; } QString applicationMenuObjectPath() const { return m_applicationMenuObjectPath; } /** * Request showing the application menu bar * @param actionId The DBus menu ID of the action that should be highlighted, 0 for the root menu */ void showApplicationMenu(int actionId); bool unresponsive() const; virtual bool isInitialPositionSet() const { return false; } public Q_SLOTS: virtual void closeWindow() = 0; Q_SIGNALS: void fullScreenChanged(); void skipTaskbarChanged(); void skipPagerChanged(); void skipSwitcherChanged(); void iconChanged(); void activeChanged(); void keepAboveChanged(bool); void keepBelowChanged(bool); /** * Emitted whenever the demands attention state changes. **/ void demandsAttentionChanged(); void desktopPresenceChanged(KWin::AbstractClient*, int); // to be forwarded by Workspace void desktopChanged(); void shadeChanged(); void minimizedChanged(); void clientMinimized(KWin::AbstractClient* client, bool animate); void clientUnminimized(KWin::AbstractClient* client, bool animate); void paletteChanged(const QPalette &p); void captionChanged(); void clientMaximizedStateChanged(KWin::AbstractClient*, MaximizeMode); void clientMaximizedStateChanged(KWin::AbstractClient* c, bool h, bool v); void transientChanged(); void modalChanged(); void quickTileModeChanged(); void moveResizedChanged(); void moveResizeCursorChanged(Qt::CursorShape); void clientStartUserMovedResized(KWin::AbstractClient*); void clientStepUserMovedResized(KWin::AbstractClient *, const QRect&); void clientFinishUserMovedResized(KWin::AbstractClient*); void closeableChanged(bool); void minimizeableChanged(bool); void shadeableChanged(bool); void maximizeableChanged(bool); void desktopFileNameChanged(); void hasApplicationMenuChanged(bool); void applicationMenuActiveChanged(bool); void unresponsiveChanged(bool); protected: AbstractClient(); void setFirstInTabBox(bool enable) { m_firstInTabBox = enable; } void setIcon(const QIcon &icon); void startAutoRaise(); void autoRaise(); /** * Whether the window accepts focus. * The difference to wantsInput is that the implementation should not check rules and return * what the window effectively supports. **/ virtual bool acceptsFocus() const = 0; /** * Called from ::setActive once the active value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. **/ virtual void doSetActive(); /** * Called from ::setKeepAbove once the keepBelow value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. **/ virtual void doSetKeepAbove(); /** * Called from ::setKeepBelow once the keepBelow value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. **/ virtual void doSetKeepBelow(); /** * Called from ::setDeskop once the desktop value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. * @param desktop The new desktop the Client is on * @param was_desk The desktop the Client was on before **/ virtual void doSetDesktop(int desktop, int was_desk); /** * Called from ::minimize and ::unminimize once the minimized value got updated, but before the * changed signal is emitted. * * Default implementation does nothig. **/ virtual void doMinimize(); - // TODO: remove boolean trap - virtual bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const = 0; + virtual bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const = 0; virtual void doSetSkipTaskbar(); virtual void doSetSkipPager(); void setupWindowManagementInterface(); void destroyWindowManagementInterface(); void updateColorScheme(QString path); virtual void updateColorScheme() = 0; void setTransientFor(AbstractClient *transientFor); virtual void addTransient(AbstractClient* cl); /** * Just removes the @p cl from the transients without any further checks. **/ void removeTransientFromList(AbstractClient* cl); Layer belongsToLayer() const; virtual bool belongsToDesktop() const; void invalidateLayer(); - virtual bool isActiveFullScreen() const; + bool isActiveFullScreen() const; virtual Layer layerForDock() const; // electric border / quick tiling void setElectricBorderMode(QuickTileMode mode); QuickTileMode electricBorderMode() const { return m_electricMode; } void setElectricBorderMaximizing(bool maximizing); bool isElectricBorderMaximizing() const { return m_electricMaximizing; } QRect electricBorderMaximizeGeometry(QPoint pos, int desktop); void updateQuickTileMode(QuickTileMode newMode) { m_quickTileMode = newMode; } KWayland::Server::PlasmaWindowInterface *windowManagementInterface() const { return m_windowManagementInterface; } // geometry handling void checkOffscreenPosition(QRect *geom, const QRect &screenArea); int borderLeft() const; int borderRight() const; int borderTop() const; int borderBottom() const; virtual void changeMaximize(bool horizontal, bool vertical, bool adjust) = 0; virtual void setGeometryRestore(const QRect &geo) = 0; /** * Called from move after updating the geometry. Can be reimplemented to perform specific tasks. * The base implementation does nothing. **/ virtual void doMove(int x, int y); void blockGeometryUpdates(bool block); void blockGeometryUpdates(); void unblockGeometryUpdates(); bool areGeometryUpdatesBlocked() const; enum PendingGeometry_t { PendingGeometryNone, PendingGeometryNormal, PendingGeometryForced }; PendingGeometry_t pendingGeometryUpdate() const; void setPendingGeometryUpdate(PendingGeometry_t update); QRect geometryBeforeUpdateBlocking() const { return m_geometryBeforeUpdateBlocking; } void updateGeometryBeforeUpdateBlocking(); /** * Schedules a repaint for the visibleRect before and after a * geometry update. The current visibleRect is stored for the * next time this method is called as the before geometry. **/ void addRepaintDuringGeometryUpdates(); /** * Convenient method to update the TabGroup states if there is one present. * Marked as virtual as TabGroup does not yet handle AbstractClient, but only * subclasses of AbstractClient. Given that the default implementation does nothing. **/ virtual void updateTabGroupStates(TabGroup::States states); /** * @returns whether the Client is currently in move resize mode **/ bool isMoveResize() const { return m_moveResize.enabled; } /** * Sets whether the Client is in move resize mode to @p enabled. **/ void setMoveResize(bool enabled) { m_moveResize.enabled = enabled; } /** * @returns whether the move resize mode is unrestricted. **/ bool isUnrestrictedMoveResize() const { return m_moveResize.unrestricted; } /** * Sets whether move resize mode is unrestricted to @p set. **/ void setUnrestrictedMoveResize(bool set) { m_moveResize.unrestricted = set; } QPoint moveOffset() const { return m_moveResize.offset; } void setMoveOffset(const QPoint &offset) { m_moveResize.offset = offset; } QPoint invertedMoveOffset() const { return m_moveResize.invertedOffset; } void setInvertedMoveOffset(const QPoint &offset) { m_moveResize.invertedOffset = offset; } QRect initialMoveResizeGeometry() const { return m_moveResize.initialGeometry; } /** * Sets the initial move resize geometry to the current geometry. **/ void updateInitialMoveResizeGeometry(); QRect moveResizeGeometry() const { return m_moveResize.geometry; } void setMoveResizeGeometry(const QRect &geo) { m_moveResize.geometry = geo; } Position moveResizePointerMode() const { return m_moveResize.pointer; } void setMoveResizePointerMode(Position mode) { m_moveResize.pointer = mode; } bool isMoveResizePointerButtonDown() const { return m_moveResize.buttonDown; } void setMoveResizePointerButtonDown(bool down) { m_moveResize.buttonDown = down; } int moveResizeStartScreen() const { return m_moveResize.startScreen; } void checkUnrestrictedMoveResize(); /** * Sets an appropriate cursor shape for the logical mouse position. */ void updateCursor(); void startDelayedMoveResize(); void stopDelayedMoveResize(); bool startMoveResize(); /** * Called from @link startMoveResize. * * Implementing classes should return @c false if starting move resize should * get aborted. In that case @link startMoveResize will also return @c false. * * Base implementation returns @c true. **/ virtual bool doStartMoveResize(); void finishMoveResize(bool cancel); /** * Leaves the move resize mode. * * Inheriting classes must invoke the base implementation which * ensures that the internal mode is properly ended. **/ virtual void leaveMoveResize(); virtual void positionGeometryTip(); void performMoveResize(); /** * Called from performMoveResize() after actually performing the change of geometry. * Implementing subclasses can perform windowing system specific handling here. * * Default implementation does nothing. **/ virtual void doPerformMoveResize(); /* * Checks if the mouse cursor is near the edge of the screen and if so * activates quick tiling or maximization */ void checkQuickTilingMaximizationZones(int xroot, int yroot); /** * Whether a sync request is still pending. * Default implementation returns @c false. **/ virtual bool isWaitingForMoveResizeSync() const; /** * Called during handling a resize. Implementing subclasses can use this * method to perform windowing system specific syncing. * * Default implementation does nothing. **/ virtual void doResizeSync(); void handleMoveResize(int x, int y, int x_root, int y_root); void handleMoveResize(const QPoint &local, const QPoint &global); void dontMoveResize(); virtual QSize resizeIncrements() const; /** * Returns the position depending on the Decoration's section under mouse. * If no decoration it returns PositionCenter. **/ Position mousePosition() const; static bool haveResizeEffect() { return s_haveResizeEffect; } static void updateHaveResizeEffect(); static void resetHaveResizeEffect() { s_haveResizeEffect = false; } void setDecoration(KDecoration2::Decoration *decoration) { m_decoration.decoration = decoration; } virtual void destroyDecoration(); void startDecorationDoubleClickTimer(); void invalidateDecorationDoubleClickTimer(); void setDesktopFileName(const QByteArray &name); QString iconFromDesktopFile() const; void updateApplicationMenuServiceName(const QString &serviceName); void updateApplicationMenuObjectPath(const QString &objectPath); void setUnresponsive(bool unresponsive); virtual void setShortcutInternal(); QString shortcutCaptionSuffix() const; virtual void updateCaption() = 0; /** * Looks for another AbstractClient with same @link{captionNormal} and @link{captionSuffix}. * If no such AbstractClient exists @c nullptr is returned. **/ AbstractClient *findClientWithSameCaption() const; void finishWindowRules(); void discardTemporaryRules(); private: void handlePaletteChange(); QSharedPointer m_tabBoxClient; bool m_firstInTabBox = false; bool m_skipTaskbar = false; /** * Unaffected by KWin **/ bool m_originalSkipTaskbar = false; bool m_skipPager = false; bool m_skipSwitcher = false; QIcon m_icon; bool m_active = false; bool m_keepAbove = false; bool m_keepBelow = false; bool m_demandsAttention = false; bool m_minimized = false; QTimer *m_autoRaiseTimer = nullptr; int m_desktop = 0; // 0 means not on any desktop yet QString m_colorScheme; std::shared_ptr m_palette; static QHash> s_palettes; static std::shared_ptr s_defaultPalette; KWayland::Server::PlasmaWindowInterface *m_windowManagementInterface = nullptr; AbstractClient *m_transientFor = nullptr; QList m_transients; bool m_modal = false; Layer m_layer = UnknownLayer; // electric border/quick tiling QuickTileMode m_electricMode = QuickTileFlag::None; bool m_electricMaximizing = false; /** The quick tile mode of this window. */ int m_quickTileMode = int(QuickTileFlag::None); QTimer *m_electricMaximizingDelay = nullptr; // geometry int m_blockGeometryUpdates = 0; // > 0 = New geometry is remembered, but not actually set PendingGeometry_t m_pendingGeometryUpdate = PendingGeometryNone; friend class GeometryUpdatesBlocker; QRect m_visibleRectBeforeGeometryUpdate; QRect m_geometryBeforeUpdateBlocking; struct { bool enabled = false; bool unrestricted = false; QPoint offset; QPoint invertedOffset; QRect initialGeometry; QRect geometry; Position pointer = PositionCenter; bool buttonDown = false; Qt::CursorShape cursor = Qt::ArrowCursor; int startScreen = 0; QTimer *delayedTimer = nullptr; } m_moveResize; struct { KDecoration2::Decoration *decoration = nullptr; QPointer client; QElapsedTimer doubleClickTimer; } m_decoration; QByteArray m_desktopFileName; bool m_applicationMenuActive = false; QString m_applicationMenuServiceName; QString m_applicationMenuObjectPath; bool m_unresponsive = false; QKeySequence _shortcut; WindowRules m_rules; static bool s_haveResizeEffect; }; /** * Helper for AbstractClient::blockGeometryUpdates() being called in pairs (true/false) */ class GeometryUpdatesBlocker { public: explicit GeometryUpdatesBlocker(AbstractClient* c) : cl(c) { cl->blockGeometryUpdates(true); } ~GeometryUpdatesBlocker() { cl->blockGeometryUpdates(false); } private: AbstractClient* cl; }; inline void AbstractClient::move(const QPoint& p, ForceGeometry_t force) { move(p.x(), p.y(), force); } inline void AbstractClient::resizeWithChecks(const QSize& s, AbstractClient::ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), force); } inline void AbstractClient::setGeometry(const QRect& r, ForceGeometry_t force) { setGeometry(r.x(), r.y(), r.width(), r.height(), force); } inline const QList& AbstractClient::transients() const { return m_transients; } inline bool AbstractClient::areGeometryUpdatesBlocked() const { return m_blockGeometryUpdates != 0; } inline void AbstractClient::blockGeometryUpdates() { m_blockGeometryUpdates++; } inline void AbstractClient::unblockGeometryUpdates() { m_blockGeometryUpdates--; } inline AbstractClient::PendingGeometry_t AbstractClient::pendingGeometryUpdate() const { return m_pendingGeometryUpdate; } inline void AbstractClient::setPendingGeometryUpdate(PendingGeometry_t update) { m_pendingGeometryUpdate = update; } } Q_DECLARE_METATYPE(KWin::AbstractClient*) Q_DECLARE_METATYPE(QList) +Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::AbstractClient::SameApplicationChecks) #endif diff --git a/activation.cpp b/activation.cpp index 9ded1a056..c048390cb 100644 --- a/activation.cpp +++ b/activation.cpp @@ -1,893 +1,893 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to window activation and focus stealing prevention. */ #include "client.h" #include "cursor.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include #include #include #include #include "atoms.h" #include "group.h" #include "rules.h" #include "screens.h" #include "useractions.h" #include namespace KWin { /* Prevention of focus stealing: KWin tries to prevent unwanted changes of focus, that would result from mapping a new window. Also, some nasty applications may try to force focus change even in cases when ICCCM 4.2.7 doesn't allow it (e.g. they may try to activate their main window because the user definitely "needs" to see something happened - misusing of QWidget::setActiveWindow() may be such case). There are 4 ways how a window may become active: - the user changes the active window (e.g. focus follows mouse, clicking on some window's titlebar) - the change of focus will be done by KWin, so there's nothing to solve in this case - the change of active window will be requested using the _NET_ACTIVE_WINDOW message (handled in RootInfo::changeActiveWindow()) - such requests will be obeyed, because this request is meant mainly for e.g. taskbar asking the WM to change the active window as a result of some user action. Normal applications should use this request only rarely in special cases. See also below the discussion of _NET_ACTIVE_WINDOW_TRANSFER. - the change of active window will be done by performing XSetInputFocus() on a window that's not currently active. ICCCM 4.2.7 describes when the application may perform change of input focus. In order to handle misbehaving applications, KWin will try to detect focus changes to windows that don't belong to currently active application, and restore focus back to the currently active window, instead of activating the window that got focus (unfortunately there's no way to FocusChangeRedirect similar to e.g. SubstructureRedirect, so there will be short time when the focus will be changed). The check itself that's done is Workspace::allowClientActivation() (see below). - a new window will be mapped - this is the most complicated case. If the new window belongs to the currently active application, it may be safely mapped on top and activated. The same if there's no active window, or the active window is the desktop. These checks are done by Workspace::allowClientActivation(). Following checks need to compare times. One time is the timestamp of last user action in the currently active window, the other time is the timestamp of the action that originally caused mapping of the new window (e.g. when the application was started). If the first time is newer than the second one, the window will not be activated, as that indicates futher user actions took place after the action leading to this new mapped window. This check is done by Workspace::allowClientActivation(). There are several ways how to get the timestamp of action that caused the new mapped window (done in Client::readUserTimeMapTimestamp()) : - the window may have the _NET_WM_USER_TIME property. This way the application may either explicitly request that the window is not activated (by using 0 timestamp), or the property contains the time of last user action in the application. - KWin itself tries to detect time of last user action in every window, by watching KeyPress and ButtonPress events on windows. This way some events may be missed (if they don't propagate to the toplevel window), but it's good as a fallback for applications that don't provide _NET_WM_USER_TIME, and missing some events may at most lead to unwanted focus stealing. - the timestamp may come from application startup notification. Application startup notification, if it exists for the new mapped window, should include time of the user action that caused it. - if there's no timestamp available, it's checked whether the new window belongs to some already running application - if yes, the timestamp will be 0 (i.e. refuse activation) - if the window is from session restored window, the timestamp will be 0 too, unless this application was the active one at the time when the session was saved, in which case the window will be activated if there wasn't any user interaction since the time KWin was started. - as the last resort, the _KDE_NET_USER_CREATION_TIME timestamp is used. For every toplevel window that is created (see CreateNotify handling), this property is set to the at that time current time. Since at this time it's known that the new window doesn't belong to any existing application (better said, the application doesn't have any other window mapped), it is either the very first window of the application, or it is the only window of the application that was hidden before. The latter case is handled by removing the property from windows before withdrawing them, making the timestamp empty for next mapping of the window. In the sooner case, the timestamp will be used. This helps in case when an application is launched without application startup notification, it creates its mainwindow, and starts its initialization (that may possibly take long time). The timestamp used will be older than any user action done after launching this application. - if no timestamp is found at all, the window is activated. The check whether two windows belong to the same application (same process) is done in Client::belongToSameApplication(). Not 100% reliable, but hopefully 99,99% reliable. As a somewhat special case, window activation is always enabled when session saving is in progress. When session saving, the session manager allows only one application to interact with the user. Not allowing window activation in such case would result in e.g. dialogs not becoming active, so focus stealing prevention would cause here more harm than good. Windows that attempted to become active but KWin prevented this will be marked as demanding user attention. They'll get the _NET_WM_STATE_DEMANDS_ATTENTION state, and the taskbar should mark them specially (blink, etc.). The state will be reset when the window eventually really becomes active. There are two more ways how a window can become obtrusive, window stealing focus: By showing above the active window, by either raising itself, or by moving itself on the active desktop. - KWin will refuse raising non-active window above the active one, unless they belong to the same application. Applications shouldn't raise their windows anyway (unless the app wants to raise one of its windows above another of its windows). - KWin activates windows moved to the current desktop (as that seems logical from the user's point of view, after sending the window there directly from KWin, or e.g. using pager). This means applications shouldn't send their windows to another desktop (SELI TODO - but what if they do?) Special cases I can think of: - konqueror reusing, i.e. kfmclient tells running Konqueror instance to open new window - without focus stealing prevention - no problem - with ASN (application startup notification) - ASN is forwarded, and because it's newer than the instance's user timestamp, it takes precedence - without ASN - user timestamp needs to be reset, otherwise it would be used, and it's old; moreover this new window mustn't be detected as window belonging to already running application, or it wouldn't be activated - see Client::sameAppWindowRoleMatch() for the (rather ugly) hack - konqueror preloading, i.e. window is created in advance, and kfmclient tells this Konqueror instance to show it later - without focus stealing prevention - no problem - with ASN - ASN is forwarded, and because it's newer than the instance's user timestamp, it takes precedence - without ASN - user timestamp needs to be reset, otherwise it would be used, and it's old; also, creation timestamp is changed to the time the instance starts (re-)initializing the window, this ensures creation timestamp will still work somewhat even in this case - KUniqueApplication - when the window is already visible, and the new instance wants it to activate - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem - with ASN - ASN is forwarded, and set on the already visible window, KWin treats the window as new with that ASN - without ASN - _NET_ACTIVE_WINDOW as application request is used, and there's no really usable timestamp, only timestamp from the time the (new) application instance was started, so KWin will activate the window *sigh* - the bad thing here is that there's absolutely no chance to recognize the case of starting this KUniqueApp from Konsole (and thus wanting the already visible window to become active) from the case when something started this KUniqueApp without ASN (in which case the already visible window shouldn't become active) - the only solution is using ASN for starting applications, at least silent (i.e. without feedback) - when one application wants to activate another application's window (e.g. KMail activating already running KAddressBook window ?) - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem - with ASN - can't be here, it's the KUniqueApp case then - without ASN - _NET_ACTIVE_WINDOW as application request should be used, KWin will activate the new window depending on the timestamp and whether it belongs to the currently active application _NET_ACTIVE_WINDOW usage: data.l[0]= 1 ->app request = 2 ->pager request = 0 - backwards compatibility data.l[1]= timestamp */ //**************************************** // Workspace //**************************************** /*! Informs the workspace about the active client, i.e. the client that has the focus (or None if no client has the focus). This functions is called by the client itself that gets focus. It has no other effect than fixing the focus chain and the return value of activeClient(). And of course, to propagate the active client to the world. */ void Workspace::setActiveClient(AbstractClient* c) { if (active_client == c) return; if (active_popup && active_popup_client != c && set_active_client_recursion == 0) closeActivePopup(); if (m_userActionsMenu->hasClient() && !m_userActionsMenu->isMenuClient(c) && set_active_client_recursion == 0) { m_userActionsMenu->close(); } StackingUpdatesBlocker blocker(this); ++set_active_client_recursion; updateFocusMousePosition(Cursor::pos()); if (active_client != NULL) { // note that this may call setActiveClient( NULL ), therefore the recursion counter active_client->setActive(false); } active_client = c; Q_ASSERT(c == NULL || c->isActive()); if (active_client) { last_active_client = active_client; FocusChain::self()->update(active_client, FocusChain::MakeFirst); active_client->demandAttention(false); // activating a client can cause a non active fullscreen window to loose the ActiveLayer status on > 1 screens if (screens()->count() > 1) { for (auto it = m_allClients.begin(); it != m_allClients.end(); ++it) { if (*it != active_client && (*it)->layer() == ActiveLayer && (*it)->screen() == active_client->screen()) { updateClientLayer(*it); } } } } updateToolWindows(false); if (c) disableGlobalShortcutsForClient(c->rules()->checkDisableGlobalShortcuts(false)); else disableGlobalShortcutsForClient(false); updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active if (rootInfo()) { rootInfo()->setActiveClient(active_client); } emit clientActivated(active_client); --set_active_client_recursion; } /*! Tries to activate the client \a c. This function performs what you expect when clicking the respective entry in a taskbar: showing and raising the client (this may imply switching to the another virtual desktop) and putting the focus onto it. Once X really gave focus to the client window as requested, the client itself will call setActiveClient() and the operation is complete. This may not happen with certain focus policies, though. \sa stActiveClient(), requestFocus() */ void Workspace::activateClient(AbstractClient* c, bool force) { if (c == NULL) { focusToNull(); setActiveClient(NULL); return; } raiseClient(c); if (!c->isOnCurrentDesktop()) { ++block_focus; VirtualDesktopManager::self()->setCurrent(c->desktop()); --block_focus; } #ifdef KWIN_BUILD_ACTIVITIES if (!c->isOnCurrentActivity()) { ++block_focus; //DBUS! Activities::self()->setCurrent(c->activities().first()); //first isn't necessarily best, but it's easiest --block_focus; } #endif if (c->isMinimized()) c->unminimize(); // ensure the window is really visible - could eg. be a hidden utility window, see bug #348083 c->hideClient(false); // TODO force should perhaps allow this only if the window already contains the mouse if (options->focusPolicyIsReasonable() || force) requestFocus(c, force); // Don't update user time for clients that have focus stealing workaround. // As they usually belong to the current active window but fail to provide // this information, updating their user time would make the user time // of the currently active window old, and reject further activation for it. // E.g. typing URL in minicli which will show kio_uiserver dialog (with workaround), // and then kdesktop shows dialog about SSL certificate. // This needs also avoiding user creation time in Client::readUserTimeMapTimestamp(). if (Client *client = dynamic_cast(c)) { // updateUserTime is X11 specific client->updateUserTime(); } } /*! Tries to activate the client by asking X for the input focus. This function does not perform any show, raise or desktop switching. See Workspace::activateClient() instead. \sa Workspace::activateClient() */ void Workspace::requestFocus(AbstractClient* c, bool force) { takeActivity(c, force ? ActivityFocusForce : ActivityFocus); } void Workspace::takeActivity(AbstractClient* c, ActivityFlags flags) { // the 'if ( c == active_client ) return;' optimization mustn't be done here if (!focusChangeEnabled() && (c != active_client)) flags &= ~ActivityFocus; if (!c) { focusToNull(); return; } if (flags & ActivityFocus) { AbstractClient* modal = c->findModal(); if (modal != NULL && modal != c) { if (!modal->isOnDesktop(c->desktop())) modal->setDesktop(c->desktop()); if (!modal->isShown(true) && !modal->isMinimized()) // forced desktop or utility window activateClient(modal); // activating a minimized blocked window will unminimize its modal implicitly // if the click was inside the window (i.e. handled is set), // but it has a modal, there's no need to use handled mode, because // the modal doesn't get the click anyway // raising of the original window needs to be still done if (flags & ActivityRaise) raiseClient(c); c = modal; } cancelDelayFocus(); } if (!flags.testFlag(ActivityFocusForce) && (c->isDock() || c->isSplash())) { // toplevel menus and dock windows don't take focus if not forced // and don't have a flag that they take focus if (!c->dockWantsInput()) { flags &= ~ActivityFocus; } } if (c->isShade()) { if (c->wantsInput() && (flags & ActivityFocus)) { // client cannot accept focus, but at least the window should be active (window menu, et. al. ) c->setActive(true); focusToNull(); } flags &= ~ActivityFocus; } if (c->tabGroup() && c->tabGroup()->current() != c) c->tabGroup()->setCurrent(dynamic_cast(c)); if (!c->isShown(true)) { // shouldn't happen, call activateClient() if needed qCWarning(KWIN_CORE) << "takeActivity: not shown" ; return; } if (flags & ActivityFocus) c->takeFocus(); if (flags & ActivityRaise) workspace()->raiseClient(c); if (!c->isOnActiveScreen()) screens()->setCurrent(c->screen()); } /*! Informs the workspace that the client \a c has been hidden. If it was the active client (or to-become the active client), the workspace activates another one. \a c may already be destroyed */ void Workspace::clientHidden(AbstractClient* c) { assert(!c->isShown(true) || !c->isOnCurrentDesktop() || !c->isOnCurrentActivity()); activateNextClient(c); } AbstractClient *Workspace::clientUnderMouse(int screen) const { ToplevelList::const_iterator it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { AbstractClient *client = qobject_cast(*(--it)); if (!client) { continue; } // rule out clients which are not really visible. // the screen test is rather superfluous for xrandr & twinview since the geometry would differ -> TODO: might be dropped if (!(client->isShown(false) && client->isOnCurrentDesktop() && client->isOnCurrentActivity() && client->isOnScreen(screen))) continue; if (client->geometry().contains(Cursor::pos())) { return client; } } return 0; } // deactivates 'c' and activates next client bool Workspace::activateNextClient(AbstractClient* c) { // if 'c' is not the active or the to-become active one, do nothing if (!(c == active_client || (should_get_focus.count() > 0 && c == should_get_focus.last()))) return false; closeActivePopup(); if (c != NULL) { if (c == active_client) setActiveClient(NULL); should_get_focus.removeAll(c); } // if blocking focus, move focus to the desktop later if needed // in order to avoid flickering if (!focusChangeEnabled()) { focusToNull(); return true; } if (!options->focusPolicyIsReasonable()) return false; AbstractClient* get_focus = NULL; // precedence on keeping the current tabgroup active. to the user that's the same window if (c && c->tabGroup() && c->isShown(false)) { if (c == c->tabGroup()->current()) c->tabGroup()->activateNext(); get_focus = c->tabGroup()->current(); if (get_focus == c) // single tab case - should not happen get_focus = NULL; } const int desktop = VirtualDesktopManager::self()->current(); if (!get_focus && showingDesktop()) get_focus = findDesktop(true, desktop); // to not break the state if (!get_focus && options->isNextFocusPrefersMouse()) { get_focus = clientUnderMouse(c ? c->screen() : screens()->current()); if (get_focus && (get_focus == c || get_focus->isDesktop())) { // should rather not happen, but it cannot get the focus. rest of usability is tested above get_focus = NULL; } } if (!get_focus) { // no suitable window under the mouse -> find sth. else // first try to pass the focus to the (former) active clients leader if (c && c->isTransient()) { auto leaders = c->mainClients(); if (leaders.count() == 1 && FocusChain::self()->isUsableFocusCandidate(leaders.at(0), c)) { get_focus = leaders.at(0); raiseClient(get_focus); // also raise - we don't know where it came from } } if (!get_focus) { // nope, ask the focus chain for the next candidate get_focus = FocusChain::self()->nextForDesktop(c, desktop); } } if (get_focus == NULL) // last chance: focus the desktop get_focus = findDesktop(true, desktop); if (get_focus != NULL) requestFocus(get_focus); else focusToNull(); return true; } void Workspace::setCurrentScreen(int new_screen) { if (new_screen < 0 || new_screen >= screens()->count()) return; if (!options->focusPolicyIsReasonable()) return; closeActivePopup(); const int desktop = VirtualDesktopManager::self()->current(); AbstractClient *get_focus = FocusChain::self()->getForActivation(desktop, new_screen); if (get_focus == NULL) get_focus = findDesktop(true, desktop); if (get_focus != NULL && get_focus != mostRecentlyActivatedClient()) requestFocus(get_focus); screens()->setCurrent(new_screen); } void Workspace::gotFocusIn(const AbstractClient* c) { if (should_get_focus.contains(const_cast< AbstractClient* >(c))) { // remove also all sooner elements that should have got FocusIn, // but didn't for some reason (and also won't anymore, because they were sooner) while (should_get_focus.first() != c) should_get_focus.pop_front(); should_get_focus.pop_front(); // remove 'c' } } void Workspace::setShouldGetFocus(AbstractClient* c) { should_get_focus.append(c); updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active } namespace FSP { enum Level { None = 0, Low, Medium, High, Extreme }; } // focus_in -> the window got FocusIn event // ignore_desktop - call comes from _NET_ACTIVE_WINDOW message, don't refuse just because of window // is on a different desktop bool Workspace::allowClientActivation(const KWin::AbstractClient *c, xcb_timestamp_t time, bool focus_in, bool ignore_desktop) { // options->focusStealingPreventionLevel : // 0 - none - old KWin behaviour, new windows always get focus // 1 - low - focus stealing prevention is applied normally, when unsure, activation is allowed // 2 - normal - focus stealing prevention is applied normally, when unsure, activation is not allowed, // this is the default // 3 - high - new window gets focus only if it belongs to the active application, // or when no window is currently active // 4 - extreme - no window gets focus without user intervention if (time == -1U) time = c->userTime(); int level = c->rules()->checkFSP(options->focusStealingPreventionLevel()); if (session_saving && level <= FSP::Medium) { // <= normal return true; } AbstractClient* ac = mostRecentlyActivatedClient(); if (focus_in) { if (should_get_focus.contains(const_cast< AbstractClient* >(c))) return true; // FocusIn was result of KWin's action // Before getting FocusIn, the active Client already // got FocusOut, and therefore got deactivated. ac = last_active_client; } if (time == 0) { // explicitly asked not to get focus if (!c->rules()->checkAcceptFocus(false)) return false; } const int protection = ac ? ac->rules()->checkFPP(2) : 0; // stealing is unconditionally allowed (NETWM behavior) if (level == FSP::None || protection == FSP::None) return true; // The active client "grabs" the focus or stealing is generally forbidden if (level == FSP::Extreme || protection == FSP::Extreme) return false; // Desktop switching is only allowed in the "no protection" case if (!ignore_desktop && !c->isOnCurrentDesktop()) return false; // allow only with level == 0 // No active client, it's ok to pass focus // NOTICE that extreme protection needs to be handled before to allow protection on unmanged windows if (ac == NULL || ac->isDesktop()) { qCDebug(KWIN_CORE) << "Activation: No client active, allowing"; return true; // no active client -> always allow } // TODO window urgency -> return true? // Unconditionally allow intra-client passing around for lower stealing protections // unless the active client has High interest - if (AbstractClient::belongToSameApplication(c, ac, true) && protection < FSP::High) { + if (AbstractClient::belongToSameApplication(c, ac, AbstractClient::SameApplicationCheck::RelaxedForActive) && protection < FSP::High) { qCDebug(KWIN_CORE) << "Activation: Belongs to active application"; return true; } if (!c->isOnCurrentDesktop()) // we allowed explicit self-activation across virtual desktops return false; // inside a client or if no client was active, but not otherwise // High FPS, not intr-client change. Only allow if the active client has only minor interest if (level > FSP::Medium && protection > FSP::Low) return false; if (time == -1U) { // no time known qCDebug(KWIN_CORE) << "Activation: No timestamp at all"; // Only allow for Low protection unless active client has High interest in focus if (level < FSP::Medium && protection < FSP::High) return true; // no timestamp at all, don't activate - because there's also creation timestamp // done on CreateNotify, this case should happen only in case application // maps again already used window, i.e. this won't happen after app startup return false; } // Low or medium FSP, usertime comparism is possible Time user_time = ac->userTime(); qCDebug(KWIN_CORE) << "Activation, compared:" << c << ":" << time << ":" << user_time << ":" << (NET::timestampCompare(time, user_time) >= 0); return NET::timestampCompare(time, user_time) >= 0; // time >= user_time } // basically the same like allowClientActivation(), this time allowing // a window to be fully raised upon its own request (XRaiseWindow), // if refused, it will be raised only on top of windows belonging // to the same application bool Workspace::allowFullClientRaising(const KWin::AbstractClient *c, xcb_timestamp_t time) { int level = c->rules()->checkFSP(options->focusStealingPreventionLevel()); if (session_saving && level <= 2) { // <= normal return true; } AbstractClient* ac = mostRecentlyActivatedClient(); if (level == 0) // none return true; if (level == 4) // extreme return false; if (ac == NULL || ac->isDesktop()) { qCDebug(KWIN_CORE) << "Raising: No client active, allowing"; return true; // no active client -> always allow } // TODO window urgency -> return true? - if (AbstractClient::belongToSameApplication(c, ac, true)) { + if (AbstractClient::belongToSameApplication(c, ac, AbstractClient::SameApplicationCheck::RelaxedForActive)) { qCDebug(KWIN_CORE) << "Raising: Belongs to active application"; return true; } if (level == 3) // high return false; xcb_timestamp_t user_time = ac->userTime(); qCDebug(KWIN_CORE) << "Raising, compared:" << time << ":" << user_time << ":" << (NET::timestampCompare(time, user_time) >= 0); return NET::timestampCompare(time, user_time) >= 0; // time >= user_time } // called from Client after FocusIn that wasn't initiated by KWin and the client // wasn't allowed to activate void Workspace::restoreFocus() { // this updateXTime() is necessary - as FocusIn events don't have // a timestamp *sigh*, kwin's timestamp would be older than the timestamp // that was used by whoever caused the focus change, and therefore // the attempt to restore the focus would fail due to old timestamp updateXTime(); if (should_get_focus.count() > 0) requestFocus(should_get_focus.last()); else if (last_active_client) requestFocus(last_active_client); } void Workspace::clientAttentionChanged(AbstractClient* c, bool set) { if (set) { attention_chain.removeAll(c); attention_chain.prepend(c); } else attention_chain.removeAll(c); emit clientDemandsAttentionChanged(c, set); } //******************************************** // Client //******************************************** /*! Updates the user time (time of last action in the active window). This is called inside kwin for every action with the window that qualifies for user interaction (clicking on it, activate it externally, etc.). */ void Client::updateUserTime(xcb_timestamp_t time) { // copied in Group::updateUserTime if (time == XCB_TIME_CURRENT_TIME) { updateXTime(); time = xTime(); } if (time != -1U && (m_userTime == XCB_TIME_CURRENT_TIME || NET::timestampCompare(time, m_userTime) > 0)) { // time > user_time m_userTime = time; shade_below = NULL; // do not hover re-shade a window after it got interaction } group()->updateUserTime(m_userTime); } xcb_timestamp_t Client::readUserCreationTime() const { Xcb::Property prop(false, window(), atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 0, 1); return prop.value(-1); } xcb_timestamp_t Client::readUserTimeMapTimestamp(const KStartupInfoId *asn_id, const KStartupInfoData *asn_data, bool session) const { xcb_timestamp_t time = info->userTime(); //qDebug() << "User timestamp, initial:" << time; //^^ this deadlocks kwin --replace sometimes. // newer ASN timestamp always replaces user timestamp, unless user timestamp is 0 // helps e.g. with konqy reusing if (asn_data != NULL && time != 0) { if (asn_id->timestamp() != 0 && (time == -1U || NET::timestampCompare(asn_id->timestamp(), time) > 0)) { time = asn_id->timestamp(); } } qCDebug(KWIN_CORE) << "User timestamp, ASN:" << time; if (time == -1U) { // The window doesn't have any timestamp. // If it's the first window for its application // (i.e. there's no other window from the same app), // use the _KDE_NET_WM_USER_CREATION_TIME trick. // Otherwise, refuse activation of a window // from already running application if this application // is not the active one (unless focus stealing prevention is turned off). Client* act = dynamic_cast(workspace()->mostRecentlyActivatedClient()); - if (act != NULL && !belongToSameApplication(act, this, true)) { + if (act != NULL && !belongToSameApplication(act, this, SameApplicationCheck::RelaxedForActive)) { bool first_window = true; auto sameApplicationActiveHackPredicate = [this](const Client *cl) { // ignore already existing splashes, toolbars, utilities and menus, // as the app may show those before the main window return !cl->isSplash() && !cl->isToolbar() && !cl->isUtility() && !cl->isMenu() - && cl != this && Client::belongToSameApplication(cl, this, true); + && cl != this && Client::belongToSameApplication(cl, this, SameApplicationCheck::RelaxedForActive); }; if (isTransient()) { auto clientMainClients = [this] () -> ClientList { ClientList ret; const auto mcs = mainClients(); for (auto mc: mcs) { if (Client *c = dynamic_cast(mc)) { ret << c; } } return ret; }; if (act->hasTransient(this, true)) ; // is transient for currently active window, even though it's not // the same app (e.g. kcookiejar dialog) -> allow activation else if (groupTransient() && findInList(clientMainClients(), sameApplicationActiveHackPredicate) == NULL) ; // standalone transient else first_window = false; } else { if (workspace()->findClient(sameApplicationActiveHackPredicate)) first_window = false; } // don't refuse if focus stealing prevention is turned off if (!first_window && rules()->checkFSP(options->focusStealingPreventionLevel()) > 0) { qCDebug(KWIN_CORE) << "User timestamp, already exists:" << 0; return 0; // refuse activation } } // Creation time would just mess things up during session startup, // as possibly many apps are started up at the same time. // If there's no active window yet, no timestamp will be needed, // as plain Workspace::allowClientActivation() will return true // in such case. And if there's already active window, // it's better not to activate the new one. // Unless it was the active window at the time // of session saving and there was no user interaction yet, // this check will be done in manage(). if (session) return -1U; time = readUserCreationTime(); } qCDebug(KWIN_CORE) << "User timestamp, final:" << this << ":" << time; return time; } xcb_timestamp_t Client::userTime() const { xcb_timestamp_t time = m_userTime; if (time == 0) // doesn't want focus after showing return 0; assert(group() != NULL); if (time == -1U || (group()->userTime() != -1U && NET::timestampCompare(group()->userTime(), time) > 0)) time = group()->userTime(); return time; } void Client::doSetActive() { updateUrgency(); // demand attention again if it's still urgent } void Client::startupIdChanged() { KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); if (!asn_valid) return; // If the ASN contains desktop, move it to the desktop, otherwise move it to the current // desktop (since the new ASN should make the window act like if it's a new application // launched). However don't affect the window's desktop if it's set to be on all desktops. int desktop = VirtualDesktopManager::self()->current(); if (asn_data.desktop() != 0) desktop = asn_data.desktop(); if (!isOnAllDesktops()) workspace()->sendClientToDesktop(this, desktop, true); if (asn_data.xinerama() != -1) workspace()->sendClientToScreen(this, asn_data.xinerama()); Time timestamp = asn_id.timestamp(); if (timestamp != 0) { bool activate = workspace()->allowClientActivation(this, timestamp); if (asn_data.desktop() != 0 && !isOnCurrentDesktop()) activate = false; // it was started on different desktop than current one if (activate) workspace()->activateClient(this); else demandAttention(); } } void Client::updateUrgency() { if (info->urgency()) demandAttention(); } //**************************************** // Group //**************************************** void Group::startupIdChanged() { KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(leader_wid, asn_id, asn_data); if (!asn_valid) return; if (asn_id.timestamp() != 0 && user_time != -1U && NET::timestampCompare(asn_id.timestamp(), user_time) > 0) { user_time = asn_id.timestamp(); } } void Group::updateUserTime(xcb_timestamp_t time) { // copy of Client::updateUserTime if (time == XCB_CURRENT_TIME) { updateXTime(); time = xTime(); } if (time != -1U && (user_time == XCB_CURRENT_TIME || NET::timestampCompare(time, user_time) > 0)) // time > user_time user_time = time; } } // namespace diff --git a/appmenu.cpp b/appmenu.cpp index 4ce96f511..eda2d1fab 100644 --- a/appmenu.cpp +++ b/appmenu.cpp @@ -1,111 +1,118 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (c) 2011 Lionel Chauvin Copyright (c) 2011,2012 Cédric Bellegarde Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "appmenu.h" #include "client.h" #include "workspace.h" #include #include +#include using namespace KWin; KWIN_SINGLETON_FACTORY(ApplicationMenu) +static const QString s_viewService(QStringLiteral("org.kde.kappmenuview")); + ApplicationMenu::ApplicationMenu(QObject *parent) : QObject(parent) , m_appmenuInterface(new OrgKdeKappmenuInterface(QStringLiteral("org.kde.kappmenu"), QStringLiteral("/KAppMenu"), QDBusConnection::sessionBus(), this)) { connect(m_appmenuInterface, &OrgKdeKappmenuInterface::showRequest, this, &ApplicationMenu::slotShowRequest); connect(m_appmenuInterface, &OrgKdeKappmenuInterface::menuShown, this, &ApplicationMenu::slotMenuShown); connect(m_appmenuInterface, &OrgKdeKappmenuInterface::menuHidden, this, &ApplicationMenu::slotMenuHidden); - connect(m_appmenuInterface, &OrgKdeKappmenuInterface::reconfigured, this, &ApplicationMenu::slotReconfigured); - - updateApplicationMenuEnabled(); + + m_kappMenuWatcher = new QDBusServiceWatcher(QStringLiteral("org.kde.kappmenu"), QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForRegistration|QDBusServiceWatcher::WatchForUnregistration, this); + + connect(m_kappMenuWatcher, &QDBusServiceWatcher::serviceRegistered, + this, [this] () { + m_applicationMenuEnabled = true; + emit applicationMenuEnabledChanged(true); + }); + connect(m_kappMenuWatcher, &QDBusServiceWatcher::serviceUnregistered, + this, [this] () { + m_applicationMenuEnabled = false; + emit applicationMenuEnabledChanged(false); + }); + + m_applicationMenuEnabled = QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.kappmenu")); } ApplicationMenu::~ApplicationMenu() { s_self = nullptr; } -void ApplicationMenu::slotReconfigured() -{ - updateApplicationMenuEnabled(); -} - bool ApplicationMenu::applicationMenuEnabled() const { return m_applicationMenuEnabled; } -void ApplicationMenu::updateApplicationMenuEnabled() +void ApplicationMenu::setViewEnabled(bool enabled) { - const bool old_enabled = m_applicationMenuEnabled; - - KConfigGroup config(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("Appmenu Style")); - const QString &menuStyle = config.readEntry(QStringLiteral("Style")); - - const bool enabled = (menuStyle == QLatin1String("Decoration")); - - if (old_enabled != enabled) { - m_applicationMenuEnabled = enabled; - emit applicationMenuEnabledChanged(enabled); + if (enabled) { + QDBusConnection::sessionBus().interface()->registerService(s_viewService, + QDBusConnectionInterface::QueueService, + QDBusConnectionInterface::DontAllowReplacement); + } else { + QDBusConnection::sessionBus().interface()->unregisterService(s_viewService); } } void ApplicationMenu::slotShowRequest(const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId) { if (AbstractClient *c = findAbstractClientWithApplicationMenu(serviceName, menuObjectPath)) { c->showApplicationMenu(actionId); } } void ApplicationMenu::slotMenuShown(const QString &serviceName, const QDBusObjectPath &menuObjectPath) { if (AbstractClient *c = findAbstractClientWithApplicationMenu(serviceName, menuObjectPath)) { c->setApplicationMenuActive(true); } } void ApplicationMenu::slotMenuHidden(const QString &serviceName, const QDBusObjectPath &menuObjectPath) { if (AbstractClient *c = findAbstractClientWithApplicationMenu(serviceName, menuObjectPath)) { c->setApplicationMenuActive(false); } } void ApplicationMenu::showApplicationMenu(const QPoint &p, AbstractClient *c, int actionId) { m_appmenuInterface->showMenu(p.x(), p.y(), c->applicationMenuServiceName(), QDBusObjectPath(c->applicationMenuObjectPath()), actionId); } AbstractClient *ApplicationMenu::findAbstractClientWithApplicationMenu(const QString &serviceName, const QDBusObjectPath &menuObjectPath) { if (serviceName.isEmpty() || menuObjectPath.path().isEmpty()) { return nullptr; } return Workspace::self()->findAbstractClient([&](const AbstractClient *c) { return c->applicationMenuServiceName() == serviceName && c->applicationMenuObjectPath() == menuObjectPath.path(); }); } diff --git a/appmenu.h b/appmenu.h index 1a6af23e2..3e0ec8e0c 100644 --- a/appmenu.h +++ b/appmenu.h @@ -1,74 +1,75 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (c) 2011 Lionel Chauvin Copyright (c) 2011,2012 Cédric Bellegarde Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_APPLICATIONMENU_H #define KWIN_APPLICATIONMENU_H // KWin #include // Qt #include // xcb #include class QPoint; class OrgKdeKappmenuInterface; class QDBusObjectPath; +class QDBusServiceWatcher; namespace KWin { class AbstractClient; class ApplicationMenu : public QObject { Q_OBJECT public: ~ApplicationMenu() override; void showApplicationMenu(const QPoint &pos, AbstractClient *c, int actionId); bool applicationMenuEnabled() const; + void setViewEnabled(bool enabled); + signals: void applicationMenuEnabledChanged(bool enabled); private Q_SLOTS: - void slotReconfigured(); void slotShowRequest(const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId); void slotMenuShown(const QString &serviceName, const QDBusObjectPath &menuObjectPath); void slotMenuHidden(const QString &serviceName, const QDBusObjectPath &menuObjectPath); private: - void updateApplicationMenuEnabled(); - OrgKdeKappmenuInterface *m_appmenuInterface; + QDBusServiceWatcher *m_kappMenuWatcher; AbstractClient *findAbstractClientWithApplicationMenu(const QString &serviceName, const QDBusObjectPath &menuObjectPath); bool m_applicationMenuEnabled = false; KWIN_SINGLETON(ApplicationMenu) }; } #endif // KWIN_APPLICATIONMENU_H diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index f15229b48..b5cfbc826 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,422 +1,447 @@ add_definitions(-DKWIN_UNIT_TEST) remove_definitions(-DQT_USE_QSTRINGBUILDER) add_subdirectory(libkwineffects) add_subdirectory(libxrenderutils) add_subdirectory(integration) if (HAVE_INPUT) add_subdirectory(libinput) endif() +if (HAVE_DRM) + add_subdirectory(drm) +endif() add_subdirectory(tabbox) ######################################################## # Test ScreenPaintData ######################################################## set( testScreenPaintData_SRCS test_screen_paint_data.cpp ) add_executable(testScreenPaintData ${testScreenPaintData_SRCS}) target_link_libraries( testScreenPaintData kwineffects Qt5::Test Qt5::Widgets KF5::WindowSystem) -add_test(kwin-testScreenPaintData testScreenPaintData) +add_test(NAME kwin-testScreenPaintData COMMAND testScreenPaintData) ecm_mark_as_test(testScreenPaintData) ######################################################## # Test WindowPaintData ######################################################## set( testWindowPaintData_SRCS test_window_paint_data.cpp ) add_executable(testWindowPaintData ${testWindowPaintData_SRCS}) target_link_libraries( testWindowPaintData kwineffects Qt5::Widgets Qt5::Test ) -add_test(kwin-testWindowPaintData testWindowPaintData) +add_test(NAME kwin-testWindowPaintData COMMAND testWindowPaintData) ecm_mark_as_test(testWindowPaintData) ######################################################## # Test VirtualDesktopManager ######################################################## set( testVirtualDesktops_SRCS test_virtual_desktops.cpp ../virtualdesktops.cpp ) add_executable(testVirtualDesktops ${testVirtualDesktops_SRCS}) target_link_libraries( testVirtualDesktops Qt5::Test Qt5::Widgets KF5::I18n KF5::GlobalAccel KF5::ConfigCore KF5::WindowSystem ) -add_test(kwin-testVirtualDesktops testVirtualDesktops) +add_test(NAME kwin-testVirtualDesktops COMMAND testVirtualDesktops) ecm_mark_as_test(testVirtualDesktops) ######################################################## # Test ClientMachine ######################################################## set( testClientMachine_SRCS test_client_machine.cpp ../client_machine.cpp ) add_executable( testClientMachine ${testClientMachine_SRCS} ) set_target_properties(testClientMachine PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_link_libraries( testClientMachine Qt5::Concurrent Qt5::Test Qt5::X11Extras Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB XCB::XFIXES ${X11_X11_LIB} # to make jenkins happy ) -add_test(kwin-testClientMachine testClientMachine) +add_test(NAME kwin-testClientMachine COMMAND testClientMachine) ecm_mark_as_test(testClientMachine) ######################################################## # Test XcbWrapper ######################################################## set( testXcbWrapper_SRCS test_xcb_wrapper.cpp ) add_executable( testXcbWrapper ${testXcbWrapper_SRCS} ) target_link_libraries( testXcbWrapper Qt5::Test Qt5::X11Extras Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB ) -add_test(kwin-testXcbWrapper testXcbWrapper) +add_test(NAME kwin-testXcbWrapper COMMAND testXcbWrapper) ecm_mark_as_test(testXcbWrapper) if (XCB_ICCCM_FOUND) add_executable( testXcbSizeHints test_xcb_size_hints.cpp ) set_target_properties(testXcbSizeHints PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_link_libraries( testXcbSizeHints Qt5::Test Qt5::X11Extras + Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB XCB::ICCCM ) - add_test(kwin-testXcbSizeHints testXcbSizeHints) + add_test(NAME kwin-testXcbSizeHints COMMAND testXcbSizeHints) ecm_mark_as_test(testXcbSizeHints) endif() ######################################################## # Test XcbWindow ######################################################## set( testXcbWindow_SRCS test_xcb_window.cpp ) add_executable( testXcbWindow ${testXcbWindow_SRCS} ) target_link_libraries( testXcbWindow Qt5::Test Qt5::X11Extras Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB ) -add_test(kwin-testXcbWindow testXcbWindow) +add_test(NAME kwin-testXcbWindow COMMAND testXcbWindow) ecm_mark_as_test(testXcbWindow) ######################################################## # Test BuiltInEffectLoader ######################################################## set( testBuiltInEffectLoader_SRCS test_builtin_effectloader.cpp mock_effectshandler.cpp ../effectloader.cpp ) add_executable( testBuiltInEffectLoader ${testBuiltInEffectLoader_SRCS}) set_target_properties(testBuiltInEffectLoader PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_link_libraries(testBuiltInEffectLoader Qt5::Concurrent Qt5::Test Qt5::X11Extras KF5::Package kwineffects kwin4_effect_builtins ) -add_test(kwin-testBuiltInEffectLoader testBuiltInEffectLoader) +add_test(NAME kwin-testBuiltInEffectLoader COMMAND testBuiltInEffectLoader) ecm_mark_as_test(testBuiltInEffectLoader) ######################################################## # Test ScriptedEffectLoader ######################################################## include_directories(${KWIN_SOURCE_DIR}) set( testScriptedEffectLoader_SRCS test_scripted_effectloader.cpp mock_abstract_client.cpp mock_effectshandler.cpp mock_screens.cpp mock_workspace.cpp ../effectloader.cpp ../scripting/scriptedeffect.cpp ../scripting/scriptingutils.cpp ../scripting/scripting_logging.cpp ../screens.cpp + ../orientation_sensor.cpp ) kconfig_add_kcfg_files(testScriptedEffectLoader_SRCS ../settings.kcfgc) +qt5_add_dbus_adaptor( testScriptedEffectLoader_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../org.kde.kwin.OrientationSensor.xml ${CMAKE_CURRENT_SOURCE_DIR}/../orientation_sensor.h KWin::OrientationSensor) add_executable( testScriptedEffectLoader ${testScriptedEffectLoader_SRCS}) target_link_libraries(testScriptedEffectLoader Qt5::Concurrent Qt5::Qml Qt5::Script + Qt5::Sensors Qt5::Test Qt5::X11Extras KF5::ConfigGui KF5::GlobalAccel KF5::I18n + KF5::Notifications KF5::Package kwineffects kwin4_effect_builtins ) -add_test(kwin-testScriptedEffectLoader testScriptedEffectLoader) +add_test(NAME kwin-testScriptedEffectLoader COMMAND testScriptedEffectLoader) ecm_mark_as_test(testScriptedEffectLoader) ######################################################## # Test PluginEffectLoader ######################################################## set( testPluginEffectLoader_SRCS test_plugin_effectloader.cpp mock_effectshandler.cpp ../effectloader.cpp ) add_executable( testPluginEffectLoader ${testPluginEffectLoader_SRCS}) target_link_libraries(testPluginEffectLoader Qt5::Concurrent Qt5::Test Qt5::X11Extras KF5::Package kwineffects kwin4_effect_builtins ) -add_test(kwin-testPluginEffectLoader testPluginEffectLoader) +add_test(NAME kwin-testPluginEffectLoader COMMAND testPluginEffectLoader) ecm_mark_as_test(testPluginEffectLoader) ######################################################## # FakeEffectPlugin ######################################################## add_library(fakeeffectplugin MODULE fakeeffectplugin.cpp) set_target_properties(fakeeffectplugin PROPERTIES PREFIX "") target_link_libraries(fakeeffectplugin kwineffects) ######################################################## # FakeEffectPlugin-Version ######################################################## add_library(effectversionplugin MODULE fakeeffectplugin_version.cpp) set_target_properties(effectversionplugin PROPERTIES PREFIX "") target_link_libraries(effectversionplugin kwineffects) ######################################################## # Test Screens ######################################################## set( testScreens_SRCS test_screens.cpp mock_abstract_client.cpp mock_client.cpp mock_screens.cpp mock_workspace.cpp ../screens.cpp ../x11eventfilter.cpp + ../orientation_sensor.cpp ) kconfig_add_kcfg_files(testScreens_SRCS ../settings.kcfgc) +qt5_add_dbus_adaptor( testScreens_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../org.kde.kwin.OrientationSensor.xml ${CMAKE_CURRENT_SOURCE_DIR}/../orientation_sensor.h KWin::OrientationSensor) add_executable( testScreens ${testScreens_SRCS}) target_include_directories(testScreens BEFORE PRIVATE ./) target_link_libraries(testScreens + Qt5::DBus + Qt5::Sensors Qt5::Test Qt5::X11Extras + Qt5::Widgets KF5::ConfigCore KF5::ConfigGui + KF5::I18n + KF5::Notifications KF5::WindowSystem ) -add_test(kwin_testScreens testScreens) +add_test(NAME kwin_testScreens COMMAND testScreens) ecm_mark_as_test(testScreens) ######################################################## # Test XrandRScreens ######################################################## set( testXRandRScreens_SRCS test_xrandr_screens.cpp mock_abstract_client.cpp mock_client.cpp mock_screens.cpp mock_workspace.cpp ../screens.cpp ../plugins/platforms/x11/standalone/screens_xrandr.cpp ../xcbutils.cpp # init of extensions ../x11eventfilter.cpp + ../orientation_sensor.cpp ) kconfig_add_kcfg_files(testXRandRScreens_SRCS ../settings.kcfgc) +qt5_add_dbus_adaptor( testXRandRScreens_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../org.kde.kwin.OrientationSensor.xml ${CMAKE_CURRENT_SOURCE_DIR}/../orientation_sensor.h KWin::OrientationSensor) add_executable( testXRandRScreens ${testXRandRScreens_SRCS} ) target_link_libraries( testXRandRScreens Qt5::Test + Qt5::DBus Qt5::Gui + Qt5::Sensors + Qt5::Widgets KF5::ConfigCore KF5::ConfigGui + KF5::I18n + KF5::Notifications KF5::WindowSystem XCB::XCB XCB::RANDR XCB::XFIXES XCB::SYNC XCB::COMPOSITE XCB::DAMAGE XCB::GLX XCB::SHM ) -add_test(kwin-testXRandRScreens testXRandRScreens) +add_test(NAME kwin-testXRandRScreens COMMAND testXRandRScreens) ecm_mark_as_test(testXRandRScreens) ######################################################## # Test ScreenEdges ######################################################## set( testScreenEdges_SRCS test_screen_edges.cpp mock_abstract_client.cpp mock_client.cpp mock_screens.cpp mock_workspace.cpp ../atoms.cpp ../gestures.cpp ../screens.cpp ../screenedge.cpp ../virtualdesktops.cpp ../xcbutils.cpp # init of extensions ../plugins/platforms/x11/standalone/edge.cpp + ../orientation_sensor.cpp ) kconfig_add_kcfg_files(testScreenEdges_SRCS ../settings.kcfgc) qt5_add_dbus_interface( testScreenEdges_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../org.freedesktop.ScreenSaver.xml screenlocker_interface) +qt5_add_dbus_adaptor( testScreenEdges_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../org.kde.kwin.OrientationSensor.xml ${CMAKE_CURRENT_SOURCE_DIR}/../orientation_sensor.h KWin::OrientationSensor) add_executable( testScreenEdges ${testScreenEdges_SRCS}) set_target_properties(testScreenEdges PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_include_directories(testScreenEdges BEFORE PRIVATE ./) target_link_libraries(testScreenEdges Qt5::DBus + Qt5::Sensors Qt5::Test Qt5::X11Extras KF5::ConfigCore KF5::ConfigGui KF5::I18n KF5::GlobalAccel + KF5::Notifications KF5::WindowSystem XCB::XCB XCB::RANDR XCB::XFIXES XCB::SYNC XCB::COMPOSITE XCB::DAMAGE XCB::GLX XCB::SHM ) -add_test(kwin_testScreenEdges testScreenEdges) +add_test(NAME kwin_testScreenEdges COMMAND testScreenEdges) ecm_mark_as_test(testScreenEdges) ######################################################## # Test OnScreenNotification ######################################################## set( testOnScreenNotification_SRCS onscreennotificationtest.cpp ../onscreennotification.cpp ../input_event_spy.cpp ) add_executable( testOnScreenNotification ${testOnScreenNotification_SRCS}) target_link_libraries(testOnScreenNotification Qt5::Test Qt5::Widgets # QAction include Qt5::Quick KF5::ConfigCore ) -add_test(kwin-testOnScreenNotification testOnScreenNotification) +add_test(NAME kwin-testOnScreenNotification COMMAND testOnScreenNotification) ecm_mark_as_test(testOnScreenNotification) ######################################################## # Test Gestures ######################################################## set( testGestures_SRCS test_gestures.cpp ../gestures.cpp ) add_executable( testGestures ${testGestures_SRCS}) target_link_libraries(testGestures Qt5::Test ) -add_test(kwin-testGestures testGestures) +add_test(NAME kwin-testGestures COMMAND testGestures) ecm_mark_as_test(testGestures) ######################################################## # Test X11 TimestampUpdate ######################################################## add_executable(testX11TimestampUpdate test_x11_timestamp_update.cpp) -target_compile_definitions(testX11TimestampUpdate PRIVATE KWINBACKENDPATH="${CMAKE_BINARY_DIR}/plugins/platforms/x11/standalone/KWinX11Platform.so") target_link_libraries(testX11TimestampUpdate Qt5::Test KF5::CoreAddons kwin ) -add_test(kwin-testX11TimestampUpdate testX11TimestampUpdate) +add_test(NAME kwin-testX11TimestampUpdate COMMAND testX11TimestampUpdate) ecm_mark_as_test(testX11TimestampUpdate) set(testOpenGLContextAttributeBuilder_SRCS opengl_context_attribute_builder_test.cpp ../abstract_opengl_context_attribute_builder.cpp ../egl_context_attribute_builder.cpp ) if(HAVE_EPOXY_GLX) set(testOpenGLContextAttributeBuilder_SRCS ${testOpenGLContextAttributeBuilder_SRCS} ../plugins/platforms/x11/standalone/glx_context_attribute_builder.cpp) endif() add_executable(testOpenGLContextAttributeBuilder ${testOpenGLContextAttributeBuilder_SRCS}) target_link_libraries(testOpenGLContextAttributeBuilder Qt5::Test) -add_test(kwin-testOpenGLContextAttributeBuilder testOpenGLContextAttributeBuilder) +add_test(NAME kwin-testOpenGLContextAttributeBuilder COMMAND testOpenGLContextAttributeBuilder) ecm_mark_as_test(testOpenGLContextAttributeBuilder) set(testXkb_SRCS test_xkb.cpp ../xkb.cpp ) add_executable(testXkb ${testXkb_SRCS}) target_link_libraries(testXkb Qt5::Test Qt5::Gui Qt5::Widgets KF5::ConfigCore KF5::WindowSystem KF5::WaylandServer XKB::XKB ) -add_test(kwin-testXkb testXkb) +add_test(NAME kwin-testXkb COMMAND testXkb) ecm_mark_as_test(testXkb) if(HAVE_GBM) add_executable(testGbmSurface test_gbm_surface.cpp ../plugins/platforms/drm/gbm_surface.cpp) target_link_libraries(testGbmSurface Qt5::Test) - add_test(kwin-testGbmSurface testGbmSurface) + add_test(NAME kwin-testGbmSurface COMMAND testGbmSurface) ecm_mark_as_test(testGbmSurface) endif() add_executable(testVirtualKeyboardDBus test_virtualkeyboard_dbus.cpp ../virtualkeyboard_dbus.cpp) target_link_libraries(testVirtualKeyboardDBus Qt5::Test Qt5::DBus ) -add_test(kwin-testVirtualKeyboardDBus testVirtualKeyboardDBus) +add_test(NAME kwin-testVirtualKeyboardDBus COMMAND testVirtualKeyboardDBus) ecm_mark_as_test(testVirtualKeyboardDBus) diff --git a/autotests/drm/CMakeLists.txt b/autotests/drm/CMakeLists.txt new file mode 100644 index 000000000..2968762c2 --- /dev/null +++ b/autotests/drm/CMakeLists.txt @@ -0,0 +1,26 @@ +include_directories(${Libdrm_INCLUDE_DIRS}) + +set(mockDRM_SRCS + mock_drm.cpp + ../../plugins/platforms/drm/drm_buffer.cpp + ../../plugins/platforms/drm/drm_object.cpp + ../../plugins/platforms/drm/drm_object_connector.cpp + ../../plugins/platforms/drm/drm_object_plane.cpp + ../../plugins/platforms/drm/logging.cpp +) + +add_library(mockDrm STATIC ${mockDRM_SRCS}) +target_link_libraries(mockDrm Qt5::Gui) +ecm_mark_as_test(mockDrm) + +function(drmTest) + set(oneValueArgs NAME) + set(multiValueArgs SRCS ) + cmake_parse_arguments(ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + add_executable(${ARGS_NAME} ${ARGS_SRCS}) + target_link_libraries(${ARGS_NAME} mockDrm Qt5::Test) + add_test(NAME kwin-drm-${ARGS_NAME} COMMAND ${ARGS_NAME}) + ecm_mark_as_test(${ARGS_NAME}) +endfunction() + +drmTest(NAME objecttest SRCS objecttest.cpp) diff --git a/autotests/drm/mock_drm.cpp b/autotests/drm/mock_drm.cpp new file mode 100644 index 000000000..70123e266 --- /dev/null +++ b/autotests/drm/mock_drm.cpp @@ -0,0 +1,78 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 "mock_drm.h" + +#include +#include + +static QMap> s_drmProperties{}; + +namespace MockDrm +{ + +void addDrmModeProperties(int fd, const QVector<_drmModeProperty> &properties) +{ + s_drmProperties.insert(fd, properties); +} + +} + +int drmModeAtomicAddProperty(drmModeAtomicReqPtr req, uint32_t object_id, uint32_t property_id, uint64_t value) +{ + Q_UNUSED(req) + Q_UNUSED(object_id) + Q_UNUSED(property_id) + Q_UNUSED(value) + return 0; +} + +drmModePropertyPtr drmModeGetProperty(int fd, uint32_t propertyId) +{ + auto it = s_drmProperties.find(fd); + if (it == s_drmProperties.end()) { + return nullptr; + } + auto it2 = std::find_if(it->constBegin(), it->constEnd(), + [propertyId] (const auto &property) { + return property.prop_id == propertyId; + } + ); + if (it2 == it->constEnd()) { + return nullptr; + } + + auto *property = new _drmModeProperty; + property->prop_id = it2->prop_id; + property->flags = it2->flags; + strcpy(property->name, it2->name); + property->count_values = it2->count_values; + property->values = it2->values; + property->count_enums = it2->count_enums; + property->enums = it2->enums; + property->count_blobs = it2->count_blobs; + property->blob_ids = it2->blob_ids; + + return property; +} + +void drmModeFreeProperty(drmModePropertyPtr ptr) +{ + delete ptr; +} diff --git a/plugins/platforms/drm/drm_object_connector.h b/autotests/drm/mock_drm.h similarity index 59% copy from plugins/platforms/drm/drm_object_connector.h copy to autotests/drm/mock_drm.h index 0f6fcdbd4..ae8076977 100644 --- a/plugins/platforms/drm/drm_object_connector.h +++ b/autotests/drm/mock_drm.h @@ -1,57 +1,32 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2016 Roman Gilg +Copyright (C) 2017 Martin Flöser This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ -#ifndef KWIN_DRM_OBJECT_CONNECTOR_H -#define KWIN_DRM_OBJECT_CONNECTOR_H +#pragma once +#include +#include +#include -#include "drm_object.h" +#include -namespace KWin +namespace MockDrm { -class DrmConnector : public DrmObject -{ -public: - DrmConnector(uint32_t connector_id, DrmBackend *backend); - - virtual ~DrmConnector(); - - bool atomicInit(); - - enum class PropertyIndex { - CrtcId = 0, - Count - }; - - QVector encoders() { - return m_encoders; - } - - bool initProps(); - bool isConnected(); - - -private: - QVector m_encoders; -}; +void addDrmModeProperties(int fd, const QVector<_drmModeProperty> &properties); } - -#endif - diff --git a/autotests/drm/objecttest.cpp b/autotests/drm/objecttest.cpp new file mode 100644 index 000000000..384268b6c --- /dev/null +++ b/autotests/drm/objecttest.cpp @@ -0,0 +1,218 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 "mock_drm.h" +#include "../../plugins/platforms/drm/drm_object.h" +#include + +class MockDrmObject : public KWin::DrmObject +{ +public: + MockDrmObject(uint32_t id, int fd) + : DrmObject(id, fd) + { + } + ~MockDrmObject() override {} + bool atomicInit() override; + bool initProps() override; + + void setProperties(uint32_t count, uint32_t *props, uint64_t *values) { + m_count = count; + m_props = props; + m_values = values; + } + + QByteArray name(int prop) const { + auto property = DrmObject::m_props.at(prop); + if (!property) { + return QByteArray(); + } + return property->name(); + } + + uint32_t propertyId(int prop) const { + auto property = DrmObject::m_props.at(prop); + if (!property) { + return 0xFFFFFFFFu; + } + return property->propId(); + } + +private: + uint32_t m_count = 0; + uint32_t *m_props = nullptr; + uint64_t *m_values = nullptr; +}; + +bool MockDrmObject::atomicInit() +{ + return initProps(); +} + +bool MockDrmObject::initProps() +{ + setPropertyNames({"foo", "bar", "baz"}); + drmModeObjectProperties properties{m_count, m_props, m_values}; + for (int i = 0; i < 3; i++) { + initProp(i, &properties); + } + return false; +} + +class ObjectTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testId_data(); + void testId(); + void testFd_data(); + void testFd(); + void testOutput(); + void testInitProperties(); +}; + +void ObjectTest::testId_data() +{ + QTest::addColumn("id"); + + QTest::newRow("0") << 0u; + QTest::newRow("1") << 1u; + QTest::newRow("10") << 10u; + QTest::newRow("uint max") << 0xFFFFFFFFu; +} + +void ObjectTest::testId() +{ + QFETCH(quint32, id); + MockDrmObject object{id, -1}; + QCOMPARE(object.id(), id); +} + +void ObjectTest::testFd_data() +{ + QTest::addColumn("fd"); + + QTest::newRow("-1") << -1; + QTest::newRow("0") << 0; + QTest::newRow("1") << 1; + QTest::newRow("2") << 2; + QTest::newRow("100") << 100; + QTest::newRow("int max") << 0x7FFFFFFF; +} + +void ObjectTest::testFd() +{ + QFETCH(int, fd); + MockDrmObject object{0, fd}; + QCOMPARE(object.fd(), fd); +} + +namespace KWin +{ +struct DrmOutput { + int foo; +}; +} + +void ObjectTest::testOutput() +{ + MockDrmObject object{0, 1}; + + QVERIFY(!object.output()); + + KWin::DrmOutput output{2}; + object.setOutput(&output); + QCOMPARE(object.output(), &output); + QCOMPARE(object.output()->foo, 2); +} + +void ObjectTest::testInitProperties() +{ + MockDrmObject object{0, 20}; + uint32_t propertiesIds[] = { 0, 1, 2, 3}; + uint64_t values[] = { 0, 2, 10, 20 }; + object.setProperties(4, propertiesIds, values); + + MockDrm::addDrmModeProperties(20, QVector<_drmModeProperty>{ + _drmModeProperty{ + 0, + 0, + "foo\0", + 0, + nullptr, + 0, + nullptr, + 0, + nullptr + }, + _drmModeProperty{ + 1, + 0, + "foobar\0", + 0, + nullptr, + 0, + nullptr, + 0, + nullptr + }, + _drmModeProperty{ + 2, + 0, + "baz\0", + 0, + nullptr, + 0, + nullptr, + 0, + nullptr + }, + _drmModeProperty{ + 3, + 0, + "foobarbaz\0", + 0, + nullptr, + 0, + nullptr, + 0, + nullptr + } + }); + + object.atomicInit(); + + // verify the names + QCOMPARE(object.name(0), QByteArrayLiteral("foo")); + QCOMPARE(object.name(1), QByteArray()); + QCOMPARE(object.name(2), QByteArrayLiteral("baz")); + + // verify the property ids + QCOMPARE(object.propertyId(0), 0u); + QCOMPARE(object.propertyId(1), 0xFFFFFFFFu); + QCOMPARE(object.propertyId(2), 2u); + + // doesn't have enums + QCOMPARE(object.propHasEnum(0, 0), false); + QCOMPARE(object.propHasEnum(1, 0), false); + QCOMPARE(object.propHasEnum(2, 0), false); +} + +QTEST_GUILESS_MAIN(ObjectTest) +#include "objecttest.moc" diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index 3d36b06c7..bb3cbb3b8 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -1,80 +1,80 @@ -add_definitions(-DKWINBACKENDPATH="${CMAKE_BINARY_DIR}/plugins/platforms/virtual/KWinWaylandVirtualBackend.so") -add_definitions(-DKWINQPAPATH="${CMAKE_BINARY_DIR}/plugins/qpa/") add_subdirectory(helper) add_library(KWinIntegrationTestFramework STATIC kwin_wayland_test.cpp test_helpers.cpp) target_link_libraries(KWinIntegrationTestFramework kwin Qt5::Test) function(integrationTest) set(optionArgs WAYLAND_ONLY) set(oneValueArgs NAME) set(multiValueArgs SRCS LIBS) cmake_parse_arguments(ARGS "${optionArgs}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${ARGS_NAME} ${ARGS_SRCS}) target_link_libraries(${ARGS_NAME} KWinIntegrationTestFramework kwin Qt5::Test ${ARGS_LIBS}) - add_test(NAME kwin-${ARGS_NAME} COMMAND dbus-run-session ${CMAKE_CURRENT_BINARY_DIR}/${ARGS_NAME}) + add_test(NAME kwin-${ARGS_NAME} COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME}) if (${ARGS_WAYLAND_ONLY}) add_executable(${ARGS_NAME}_waylandonly ${ARGS_SRCS}) set_target_properties(${ARGS_NAME}_waylandonly PROPERTIES COMPILE_DEFINITIONS "NO_XWAYLAND") target_link_libraries(${ARGS_NAME}_waylandonly KWinIntegrationTestFramework kwin Qt5::Test ${ARGS_LIBS}) - add_test(NAME kwin-${ARGS_NAME}-waylandonly COMMAND dbus-run-session ${CMAKE_CURRENT_BINARY_DIR}/${ARGS_NAME}_waylandonly) + add_test(NAME kwin-${ARGS_NAME}-waylandonly COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME}_waylandonly) endif() endfunction() integrationTest(WAYLAND_ONLY NAME testStart SRCS start_test.cpp) integrationTest(WAYLAND_ONLY NAME testTransientNoInput SRCS transient_no_input_test.cpp) integrationTest(NAME testDontCrashGlxgears SRCS dont_crash_glxgears.cpp) integrationTest(NAME testLockScreen SRCS lockscreen.cpp) integrationTest(WAYLAND_ONLY NAME testDecorationInput SRCS decoration_input_test.cpp) integrationTest(WAYLAND_ONLY NAME testInternalWindow SRCS internal_window.cpp) integrationTest(WAYLAND_ONLY NAME testTouchInput SRCS touch_input_test.cpp) integrationTest(WAYLAND_ONLY NAME testInputStackingOrder SRCS input_stacking_order.cpp) integrationTest(NAME testPointerInput SRCS pointer_input.cpp) integrationTest(NAME testPlatformCursor SRCS platformcursor.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashCancelAnimation SRCS dont_crash_cancel_animation.cpp) integrationTest(WAYLAND_ONLY NAME testTransientPlacmenet SRCS transient_placement.cpp) integrationTest(NAME testDebugConsole SRCS debug_console_test.cpp) integrationTest(NAME testDontCrashEmptyDeco SRCS dont_crash_empty_deco.cpp) integrationTest(WAYLAND_ONLY NAME testPlasmaSurface SRCS plasma_surface_test.cpp) integrationTest(WAYLAND_ONLY NAME testMaximized SRCS maximize_test.cpp) integrationTest(WAYLAND_ONLY NAME testShellClient SRCS shell_client_test.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashNoBorder SRCS dont_crash_no_border.cpp) integrationTest(NAME testXClipboardSync SRCS xclipboardsync_test.cpp) integrationTest(WAYLAND_ONLY NAME testSceneOpenGL SRCS scene_opengl_test.cpp generic_scene_opengl_test.cpp) integrationTest(WAYLAND_ONLY NAME testSceneOpenGLES SRCS scene_opengl_es_test.cpp generic_scene_opengl_test.cpp) integrationTest(WAYLAND_ONLY NAME testNoXdgRuntimeDir SRCS no_xdg_runtime_dir_test.cpp) integrationTest(WAYLAND_ONLY NAME testScreenChanges SRCS screen_changes_test.cpp) integrationTest(NAME testModiferOnlyShortcut SRCS modifier_only_shortcut_test.cpp) integrationTest(WAYLAND_ONLY NAME testTabBox SRCS tabbox_test.cpp) integrationTest(WAYLAND_ONLY NAME testWindowSelection SRCS window_selection_test.cpp) integrationTest(WAYLAND_ONLY NAME testPointerConstraints SRCS pointer_constraints_test.cpp) integrationTest(WAYLAND_ONLY NAME testKeyboardLayout SRCS keyboard_layout_test.cpp) integrationTest(WAYLAND_ONLY NAME testKeymapCreationFailure SRCS keymap_creation_failure_test.cpp) integrationTest(WAYLAND_ONLY NAME testShowingDesktop SRCS showing_desktop_test.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashUseractionsMenu SRCS dont_crash_useractions_menu.cpp) integrationTest(WAYLAND_ONLY NAME testKWinBindings SRCS kwinbindings_test.cpp) integrationTest(WAYLAND_ONLY NAME testVirtualDesktop SRCS virtual_desktop_test.cpp) integrationTest(WAYLAND_ONLY NAME testShellClientRules SRCS shell_client_rules_test.cpp) +integrationTest(WAYLAND_ONLY NAME testIdleInhibition SRCS idle_inhibition_test.cpp) +integrationTest(WAYLAND_ONLY NAME testColorCorrectNightColor SRCS colorcorrect_nightcolor_test.cpp) if (XCB_ICCCM_FOUND) integrationTest(NAME testMoveResize SRCS move_resize_window_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testStruts SRCS struts_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testShade SRCS shade_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testDontCrashAuroraeDestroyDeco SRCS dont_crash_aurorae_destroy_deco.cpp LIBS XCB::ICCCM) integrationTest(NAME testPlasmaWindow SRCS plasmawindow_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testScreenEdgeClientShow SRCS screenedge_client_show_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testX11DesktopWindow SRCS desktop_window_x11_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testXwaylandInput SRCS xwayland_input_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testWindowRules SRCS window_rules_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testX11Client SRCS x11_client_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testQuickTiling SRCS quick_tiling_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testGlobalShortcuts SRCS globalshortcuts_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testSceneQPainter SRCS scene_qpainter_test.cpp LIBS XCB::ICCCM) if (KWIN_BUILD_ACTIVITIES) integrationTest(NAME testActivities SRCS activities_test.cpp LIBS XCB::ICCCM) endif() endif() add_subdirectory(scripting) add_subdirectory(effects) diff --git a/autotests/integration/colorcorrect_nightcolor_test.cpp b/autotests/integration/colorcorrect_nightcolor_test.cpp new file mode 100644 index 000000000..ecc319c91 --- /dev/null +++ b/autotests/integration/colorcorrect_nightcolor_test.cpp @@ -0,0 +1,334 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "kwin_wayland_test.h" + +#include "platform.h" +#include "wayland_server.h" +#include "colorcorrection/manager.h" +#include "colorcorrection/constants.h" + +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_colorcorrect_nightcolor-0"); + +class ColorCorrectNightColorTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testConfigRead_data(); + void testConfigRead(); + void testChangeConfiguration_data(); + void testChangeConfiguration(); + void testAutoLocationUpdate(); +}; + +void ColorCorrectNightColorTest::initTestCase() +{ + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); + QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + kwinApp()->start(); + QVERIFY(workspaceCreatedSpy.wait()); + waylandServer()->initWorkspace(); +} + +void ColorCorrectNightColorTest::init() +{ +} + +void ColorCorrectNightColorTest::cleanup() +{ +} + +void ColorCorrectNightColorTest::testConfigRead_data() +{ + QTest::addColumn("active"); + QTest::addColumn("mode"); + QTest::addColumn("nightTemperature"); + QTest::addColumn("latitudeFixed"); + QTest::addColumn("longitudeFixed"); + QTest::addColumn("morningBeginFixed"); + QTest::addColumn("eveningBeginFixed"); + QTest::addColumn("transitionTime"); + QTest::addColumn("success"); + + QTest::newRow("activeMode0") << "true" << 0 << 4500 << 45.5 << 35.1 << "0600" << "1800" << 30 << true; + QTest::newRow("activeMode1") << "true" << 1 << 2500 << -10.5 << -8. << "0020" << "2000" << 60 << true; + QTest::newRow("notActiveMode2") << "false" << 2 << 5000 << 90. << -180. << "0600" << "1800" << 1 << true; + QTest::newRow("wrongData1") << "fa" << 3 << 7000 << 91. << -181. << "060" << "800" << 999999 << false; + QTest::newRow("wrongData2") << "fa" << 3 << 7000 << 91. << -181. << "060" << "800" << -2 << false; +} + +void ColorCorrectNightColorTest::testConfigRead() +{ + QFETCH(QString, active); + QFETCH(int, mode); + QFETCH(int, nightTemperature); + QFETCH(double, latitudeFixed); + QFETCH(double, longitudeFixed); + QFETCH(QString, morningBeginFixed); + QFETCH(QString, eveningBeginFixed); + QFETCH(int, transitionTime); + QFETCH(bool, success); + + const bool activeDefault = true; + const int modeDefault = 0; + const int nightTemperatureUpperEnd = ColorCorrect::NEUTRAL_TEMPERATURE; + const double latitudeFixedDefault = 0; + const double longitudeFixedDefault = 0; + const QTime morningBeginFixedDefault = QTime(6,0,0); + const QTime eveningBeginFixedDefault = QTime(18,0,0); + const int transitionTimeDefault = 30; + + KConfigGroup cfgGroup = kwinApp()->config()->group("NightColor"); + + cfgGroup.writeEntry("Active", activeDefault); + cfgGroup.writeEntry("Mode", modeDefault); + cfgGroup.writeEntry("NightTemperature", nightTemperatureUpperEnd); + cfgGroup.writeEntry("LatitudeFixed", latitudeFixedDefault); + cfgGroup.writeEntry("LongitudeFixed", longitudeFixedDefault); + cfgGroup.writeEntry("MorningBeginFixed", morningBeginFixedDefault.toString("hhmm")); + cfgGroup.writeEntry("EveningBeginFixed", eveningBeginFixedDefault.toString("hhmm")); + cfgGroup.writeEntry("TransitionTime", transitionTimeDefault); + + ColorCorrect::Manager *manager = kwinApp()->platform()->colorCorrectManager(); + manager->reparseConfigAndReset(); + auto info = manager->info(); + QVERIFY(!info.isEmpty()); + + QCOMPARE(info.value("Active").toBool(), activeDefault); + QCOMPARE(info.value("Mode").toInt(), modeDefault); + QCOMPARE(info.value("NightTemperature").toInt(), nightTemperatureUpperEnd); + QCOMPARE(info.value("LatitudeFixed").toDouble(), latitudeFixedDefault); + QCOMPARE(info.value("LongitudeFixed").toDouble(), longitudeFixedDefault); + QCOMPARE(QTime::fromString(info.value("MorningBeginFixed").toString(), Qt::ISODate), morningBeginFixedDefault); + QCOMPARE(QTime::fromString(info.value("EveningBeginFixed").toString(), Qt::ISODate), eveningBeginFixedDefault); + QCOMPARE(info.value("TransitionTime").toInt(), transitionTimeDefault); + + cfgGroup.writeEntry("Active", active); + cfgGroup.writeEntry("Mode", mode); + cfgGroup.writeEntry("NightTemperature", nightTemperature); + cfgGroup.writeEntry("LatitudeFixed", latitudeFixed); + cfgGroup.writeEntry("LongitudeFixed", longitudeFixed); + cfgGroup.writeEntry("MorningBeginFixed", morningBeginFixed); + cfgGroup.writeEntry("EveningBeginFixed", eveningBeginFixed); + cfgGroup.writeEntry("TransitionTime", transitionTime); + + manager->reparseConfigAndReset(); + info = manager->info(); + QVERIFY(!info.isEmpty()); + + if (success) { + QCOMPARE(info.value("Active").toBool() ? QString("true") : QString("false"), active); + QCOMPARE(info.value("Mode").toInt(), mode); + QCOMPARE(info.value("NightTemperature").toInt(), nightTemperature); + QCOMPARE(info.value("LatitudeFixed").toDouble(), latitudeFixed); + QCOMPARE(info.value("LongitudeFixed").toDouble(), longitudeFixed); + QCOMPARE(QTime::fromString(info.value("MorningBeginFixed").toString(), Qt::ISODate), QTime::fromString(morningBeginFixed, "hhmm")); + QCOMPARE(QTime::fromString(info.value("EveningBeginFixed").toString(), Qt::ISODate), QTime::fromString(eveningBeginFixed, "hhmm")); + QCOMPARE(info.value("TransitionTime").toInt(), transitionTime); + } else { + QCOMPARE(info.value("Active").toBool(), activeDefault); + QCOMPARE(info.value("Mode").toInt(), modeDefault); + QCOMPARE(info.value("NightTemperature").toInt(), nightTemperatureUpperEnd); + QCOMPARE(info.value("LatitudeFixed").toDouble(), latitudeFixedDefault); + QCOMPARE(info.value("LongitudeFixed").toDouble(), longitudeFixedDefault); + QCOMPARE(QTime::fromString(info.value("MorningBeginFixed").toString(), Qt::ISODate), morningBeginFixedDefault); + QCOMPARE(QTime::fromString(info.value("EveningBeginFixed").toString(), Qt::ISODate), eveningBeginFixedDefault); + QCOMPARE(info.value("TransitionTime").toInt(), transitionTimeDefault); + } +} + +void ColorCorrectNightColorTest::testChangeConfiguration_data() +{ + QTest::addColumn("activeReadIn"); + QTest::addColumn("modeReadIn"); + QTest::addColumn("nightTemperatureReadIn"); + QTest::addColumn("latitudeFixedReadIn"); + QTest::addColumn("longitudeFixedReadIn"); + QTest::addColumn("morBeginFixedReadIn"); + QTest::addColumn("eveBeginFixedReadIn"); + QTest::addColumn("transitionTimeReadIn"); + QTest::addColumn("successReadIn"); + + QTest::newRow("data0") << true << 0 << 4500 << 45.5 << 35.1 << QTime(6,0,0) << QTime(18,0,0) << 30 << true; + QTest::newRow("data1") << true << 1 << 2500 << -10.5 << -8. << QTime(0,2,0) << QTime(20,0,0) << 60 << true; + QTest::newRow("data2") << false << 2 << 5000 << 90. << -180. << QTime(6,0,0) << QTime(19,1,1) << 1 << true; + QTest::newRow("wrongData0") << true << 3 << 4500 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << 30 << false; + QTest::newRow("wrongData1") << true << 0 << 500 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << 30 << false; + QTest::newRow("wrongData2") << true << 0 << 7000 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << 30 << false; + QTest::newRow("wrongData3") << true << 0 << 4500 << 91. << -181. << QTime(6,0,0) << QTime(18,0,0) << 30 << false; + QTest::newRow("wrongData4") << true << 0 << 4500 << 0. << 0. << QTime(18,0,0) << QTime(6,0,0) << 30 << false; + QTest::newRow("wrongData5") << true << 0 << 4500 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << 0 << false; + QTest::newRow("wrongData6") << true << 0 << 4500 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << -1 << false; + QTest::newRow("wrongData7") << true << 0 << 4500 << 0. << 0. << QTime(12,0,0) << QTime(12,30,0) << 30 << false; + QTest::newRow("wrongData8") << true << 0 << 4500 << 0. << 0. << QTime(1,0,0) << QTime(23,30,0) << 90 << false; +} + +void ColorCorrectNightColorTest::testChangeConfiguration() +{ + QFETCH(bool, activeReadIn); + QFETCH(int, modeReadIn); + QFETCH(int, nightTemperatureReadIn); + QFETCH(double, latitudeFixedReadIn); + QFETCH(double, longitudeFixedReadIn); + QFETCH(QTime, morBeginFixedReadIn); + QFETCH(QTime, eveBeginFixedReadIn); + QFETCH(int, transitionTimeReadIn); + QFETCH(bool, successReadIn); + + const bool activeDefault = true; + const int modeDefault = 0; + const int nightTemperatureDefault = ColorCorrect::DEFAULT_NIGHT_TEMPERATURE; + const double latitudeFixedDefault = 0; + const double longitudeFixedDefault = 0; + const QTime morningBeginFixedDefault = QTime(6,0,0); + const QTime eveningBeginFixedDefault = QTime(18,0,0); + const int transitionTimeDefault = 30; + + // init with default values + bool active = activeDefault; + int mode = modeDefault; + int nightTemperature = nightTemperatureDefault; + double latitudeFixed = latitudeFixedDefault; + double longitudeFixed = longitudeFixedDefault; + QTime morningBeginFixed = morningBeginFixedDefault; + QTime eveningBeginFixed = eveningBeginFixedDefault; + int transitionTime = transitionTimeDefault; + + bool activeExpect = activeDefault; + int modeExpect = modeDefault; + int nightTemperatureExpect = nightTemperatureDefault; + double latitudeFixedExpect = latitudeFixedDefault; + double longitudeFixedExpect = longitudeFixedDefault; + QTime morningBeginFixedExpect = morningBeginFixedDefault; + QTime eveningBeginFixedExpect = eveningBeginFixedDefault; + int transitionTimeExpect = transitionTimeDefault; + + QHash data; + + auto setData = [&active, &mode, &nightTemperature, + &latitudeFixed, &longitudeFixed, + &morningBeginFixed, &eveningBeginFixed, &transitionTime, + &data]() { + data["Active"] = active; + data["Mode"] = mode; + data["NightTemperature"] = nightTemperature; + + data["LatitudeFixed"] = latitudeFixed; + data["LongitudeFixed"] = longitudeFixed; + + data["MorningBeginFixed"] = morningBeginFixed.toString(Qt::ISODate); + data["EveningBeginFixed"] = eveningBeginFixed.toString(Qt::ISODate); + data["TransitionTime"] = transitionTime; + }; + + auto compareValues = [&activeExpect, &modeExpect, &nightTemperatureExpect, + &latitudeFixedExpect, &longitudeFixedExpect, + &morningBeginFixedExpect, &eveningBeginFixedExpect, + &transitionTimeExpect](QHash info) { + QCOMPARE(info.value("Active").toBool(), activeExpect); + QCOMPARE(info.value("Mode").toInt(), modeExpect); + QCOMPARE(info.value("NightTemperature").toInt(), nightTemperatureExpect); + QCOMPARE(info.value("LatitudeFixed").toDouble(), latitudeFixedExpect); + QCOMPARE(info.value("LongitudeFixed").toDouble(), longitudeFixedExpect); + QCOMPARE(info.value("MorningBeginFixed").toString(), morningBeginFixedExpect.toString(Qt::ISODate)); + QCOMPARE(info.value("EveningBeginFixed").toString(), eveningBeginFixedExpect.toString(Qt::ISODate)); + QCOMPARE(info.value("TransitionTime").toInt(), transitionTimeExpect); + }; + + ColorCorrect::Manager *manager = kwinApp()->platform()->colorCorrectManager(); + + // test with default values + setData(); + manager->changeConfiguration(data); + compareValues(manager->info()); + + // set to test values + active = activeReadIn; + mode = modeReadIn; + nightTemperature = nightTemperatureReadIn; + latitudeFixed = latitudeFixedReadIn; + longitudeFixed = longitudeFixedReadIn; + morningBeginFixed = morBeginFixedReadIn; + eveningBeginFixed = eveBeginFixedReadIn; + transitionTime = transitionTimeReadIn; + + if (successReadIn) { + activeExpect = activeReadIn; + modeExpect = modeReadIn; + nightTemperatureExpect = nightTemperatureReadIn; + latitudeFixedExpect = latitudeFixedReadIn; + longitudeFixedExpect = longitudeFixedReadIn; + morningBeginFixedExpect = morBeginFixedReadIn; + eveningBeginFixedExpect = eveBeginFixedReadIn; + transitionTimeExpect = transitionTimeReadIn; + } + + // test with test values + setData(); + QCOMPARE(manager->changeConfiguration(data), successReadIn); + compareValues(manager->info()); +} + +void ColorCorrectNightColorTest::testAutoLocationUpdate() +{ + ColorCorrect::Manager *manager = kwinApp()->platform()->colorCorrectManager(); + auto info = manager->info(); + QCOMPARE(info.value("LatitudeAuto").toDouble(), 0.); + QCOMPARE(info.value("LongitudeAuto").toDouble(), 0.); + + // wrong latitude value + manager->autoLocationUpdate(91, 15); + info = manager->info(); + QCOMPARE(info.value("LatitudeAuto").toDouble(), 0.); + QCOMPARE(info.value("LongitudeAuto").toDouble(), 0.); + + // wrong longitude value + manager->autoLocationUpdate(50, -181); + info = manager->info(); + QCOMPARE(info.value("LatitudeAuto").toDouble(), 0.); + QCOMPARE(info.value("LongitudeAuto").toDouble(), 0.); + + // change + manager->autoLocationUpdate(50, -180); + info = manager->info(); + QCOMPARE(info.value("LatitudeAuto").toDouble(), 50.); + QCOMPARE(info.value("LongitudeAuto").toDouble(), -180.); + + // small deviation only + manager->autoLocationUpdate(51.5, -179.5); + info = manager->info(); + QCOMPARE(info.value("LongitudeAuto").toDouble(), -180.); + QCOMPARE(info.value("LatitudeAuto").toDouble(), 50.); +} + +WAYLANDTEST_MAIN(ColorCorrectNightColorTest) +#include "colorcorrect_nightcolor_test.moc" diff --git a/autotests/integration/decoration_input_test.cpp b/autotests/integration/decoration_input_test.cpp index f426b2a86..85d22e243 100644 --- a/autotests/integration/decoration_input_test.cpp +++ b/autotests/integration/decoration_input_test.cpp @@ -1,591 +1,763 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "pointer_input.h" #include "touch_input.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(Qt::WindowFrameSection) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_decoration_input-0"); class DecorationInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testAxis_data(); void testAxis(); void testDoubleClick_data(); void testDoubleClick(); void testDoubleTap_data(); void testDoubleTap(); void testHover_data(); void testHover(); void testPressToMove_data(); void testPressToMove(); void testTapToMove_data(); void testTapToMove(); void testResizeOutsideWindow_data(); void testResizeOutsideWindow(); + void testModifierClickUnrestrictedMove_data(); + void testModifierClickUnrestrictedMove(); + void testModifierScrollOpacity_data(); + void testModifierScrollOpacity(); private: AbstractClient *showWindow(Test::ShellSurfaceType type); }; #define MOTION(target) \ kwinApp()->platform()->pointerMotion(target, timestamp++) #define PRESS \ kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++) #define RELEASE \ kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++) AbstractClient *DecorationInputTest::showWindow(Test::ShellSurfaceType type) { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; Surface *surface = Test::createSurface(Test::waylandCompositor()); VERIFY(surface); auto shellSurface = Test::createShellSurface(type, surface, surface); VERIFY(shellSurface); auto deco = Test::waylandServerSideDecoration()->create(surface, surface); QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged); VERIFY(decoSpy.isValid()); VERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); VERIFY(decoSpy.wait()); COMPARE(deco->mode(), ServerSideDecoration::Mode::Server); // let's render auto c = Test::renderAndWaitForShown(surface, QSize(500, 50), Qt::blue); VERIFY(c); COMPARE(workspace()->activeClient(), c); #undef VERIFY #undef COMPARE return c; } void DecorationInputTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // change some options KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group(QStringLiteral("MouseBindings")).writeEntry("CommandTitlebarWheel", QStringLiteral("above/below")); config->group(QStringLiteral("Windows")).writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops")); config->group(QStringLiteral("Desktops")).writeEntry("Number", 2); config->sync(); kwinApp()->setConfig(config); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void DecorationInputTest::init() { using namespace KWayland::Client; QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration)); QVERIFY(Test::waitForWaylandPointer()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void DecorationInputTest::cleanup() { Test::destroyWaylandConnection(); } void DecorationInputTest::testAxis_data() { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); QTest::addColumn("type"); QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topLeft|xdgv5") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdgv5") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdgv5") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topLeft|xdgv6") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("top|xdgv6") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topRight|xdgv6") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV6; } void DecorationInputTest::testAxis() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QCOMPARE(c->titlebarPosition(), AbstractClient::PositionTop); QVERIFY(!c->keepAbove()); QVERIFY(!c->keepBelow()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); QVERIFY(!input()->pointer()->decoration().isNull()); QCOMPARE(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), Qt::TitleBarArea); // TODO: mouse wheel direction looks wrong to me // simulate wheel kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(c->keepBelow()); QVERIFY(!c->keepAbove()); kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(!c->keepAbove()); kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(c->keepAbove()); // test top most deco pixel, BUG: 362860 c->move(0, 0); QFETCH(QPoint, decoPoint); MOTION(decoPoint); QVERIFY(!input()->pointer()->decoration().isNull()); QCOMPARE(input()->pointer()->decoration()->client(), c); QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(!c->keepAbove()); } void DecorationInputTest::testDoubleClick_data() { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); QTest::addColumn("type"); QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topLeft|xdgv5") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdgv5") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdgv5") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topLeft|xdgv6") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("top|xdgv6") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topRight|xdgv6") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV6; } void KWin::DecorationInputTest::testDoubleClick() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QVERIFY(!c->isOnAllDesktops()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); // double click PRESS; RELEASE; PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); // double click again PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); PRESS; RELEASE; QVERIFY(!c->isOnAllDesktops()); // test top most deco pixel, BUG: 362860 c->move(0, 0); QFETCH(QPoint, decoPoint); MOTION(decoPoint); QVERIFY(!input()->pointer()->decoration().isNull()); QCOMPARE(input()->pointer()->decoration()->client(), c); QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); // double click PRESS; RELEASE; QVERIFY(!c->isOnAllDesktops()); PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); } void DecorationInputTest::testDoubleTap_data() { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); QTest::addColumn("type"); QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topLeft|xdgv5") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdgv5") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdgv5") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topLeft|xdgv6") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("top|xdgv6") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topRight|xdgv6") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV6; } void KWin::DecorationInputTest::testDoubleTap() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QVERIFY(!c->isOnAllDesktops()); quint32 timestamp = 1; const QPoint tapPoint(c->geometry().center().x(), c->clientPos().y() / 2); // double tap kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(c->isOnAllDesktops()); // double tap again kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(c->isOnAllDesktops()); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(!c->isOnAllDesktops()); // test top most deco pixel, BUG: 362860 c->move(0, 0); QFETCH(QPoint, decoPoint); // double click kwinApp()->platform()->touchDown(0, decoPoint, timestamp++); QVERIFY(!input()->touch()->decoration().isNull()); QCOMPARE(input()->touch()->decoration()->client(), c); QTEST(input()->touch()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(!c->isOnAllDesktops()); kwinApp()->platform()->touchDown(0, decoPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(c->isOnAllDesktops()); } void DecorationInputTest::testHover_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void DecorationInputTest::testHover() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); // our left border is moved out of the visible area, so move the window to a better place c->move(QPoint(20, 0)); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); QCOMPARE(c->cursor(), Qt::ArrowCursor); MOTION(QPoint(20, 0)); QCOMPARE(c->cursor(), Qt::SizeFDiagCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, 0)); QCOMPARE(c->cursor(), Qt::SizeVerCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, 0)); QCOMPARE(c->cursor(), Qt::SizeBDiagCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, c->height() / 2)); QCOMPARE(c->cursor(), Qt::SizeHorCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, c->height() - 1)); QCOMPARE(c->cursor(), Qt::SizeFDiagCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, c->height() - 1)); QCOMPARE(c->cursor(), Qt::SizeVerCursor); MOTION(QPoint(c->geometry().x(), c->height() - 1)); QCOMPARE(c->cursor(), Qt::SizeBDiagCursor); MOTION(QPoint(c->geometry().x(), c->height() / 2)); QCOMPARE(c->cursor(), Qt::SizeHorCursor); MOTION(c->geometry().center()); QEXPECT_FAIL("", "Cursor not set back on leave", Continue); QCOMPARE(c->cursor(), Qt::ArrowCursor); } void DecorationInputTest::testPressToMove_data() { QTest::addColumn("offset"); QTest::addColumn("offset2"); QTest::addColumn("offset3"); QTest::addColumn("type"); QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To right|xdgv5") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To left|xdgv5") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To bottom|xdgv5") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To top|xdgv5") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To right|xdgv6") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To left|xdgv6") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To bottom|xdgv6") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To top|xdgv6") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV6; } void DecorationInputTest::testPressToMove() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); QCOMPARE(c->cursor(), Qt::ArrowCursor); PRESS; QVERIFY(!c->isMove()); QFETCH(QPoint, offset); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset); const QPoint oldPos = c->pos(); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 1); RELEASE; QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); QCOMPARE(c->pos(), oldPos + offset); // again PRESS; QVERIFY(!c->isMove()); QFETCH(QPoint, offset2); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 2); QFETCH(QPoint, offset3); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3); RELEASE; QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); // TODO: the offset should also be included QCOMPARE(c->pos(), oldPos + offset2 + offset3); } void DecorationInputTest::testTapToMove_data() { QTest::addColumn("offset"); QTest::addColumn("offset2"); QTest::addColumn("offset3"); QTest::addColumn("type"); QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To right|xdgv5") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To left|xdgv5") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To bottom|xdgv5") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To top|xdgv5") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To right|xdgv6") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To left|xdgv6") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To bottom|xdgv6") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To top|xdgv6") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV6; } void DecorationInputTest::testTapToMove() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); quint32 timestamp = 1; QPoint p = QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2); kwinApp()->platform()->touchDown(0, p, timestamp++); QVERIFY(!c->isMove()); QFETCH(QPoint, offset); QCOMPARE(input()->touch()->decorationPressId(), 0); kwinApp()->platform()->touchMotion(0, p + offset, timestamp++); const QPoint oldPos = c->pos(); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 1); kwinApp()->platform()->touchUp(0, timestamp++); QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); QCOMPARE(c->pos(), oldPos + offset); // again kwinApp()->platform()->touchDown(1, p + offset, timestamp++); QCOMPARE(input()->touch()->decorationPressId(), 1); QVERIFY(!c->isMove()); QFETCH(QPoint, offset2); kwinApp()->platform()->touchMotion(1, QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2, timestamp++); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 2); QFETCH(QPoint, offset3); kwinApp()->platform()->touchMotion(1, QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3, timestamp++); kwinApp()->platform()->touchUp(1, timestamp++); QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); // TODO: the offset should also be included QCOMPARE(c->pos(), oldPos + offset2 + offset3); } void DecorationInputTest::testResizeOutsideWindow_data() { QTest::addColumn("type"); QTest::addColumn("edge"); QTest::addColumn("expectedCursor"); QTest::newRow("wlShell - left") << Test::ShellSurfaceType::WlShell << Qt::LeftEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV5 - left") << Test::ShellSurfaceType::XdgShellV5 << Qt::LeftEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV6 - left") << Test::ShellSurfaceType::XdgShellV6 << Qt::LeftEdge << Qt::SizeHorCursor; QTest::newRow("wlShell - right") << Test::ShellSurfaceType::WlShell << Qt::RightEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV5 - right") << Test::ShellSurfaceType::XdgShellV5 << Qt::RightEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV6 - right") << Test::ShellSurfaceType::XdgShellV6 << Qt::RightEdge << Qt::SizeHorCursor; QTest::newRow("wlShell - bottom") << Test::ShellSurfaceType::WlShell << Qt::BottomEdge << Qt::SizeVerCursor; QTest::newRow("xdgShellV5 - bottom") << Test::ShellSurfaceType::XdgShellV5 << Qt::BottomEdge << Qt::SizeVerCursor; QTest::newRow("xdgShellV6 - bottom") << Test::ShellSurfaceType::XdgShellV6 << Qt::BottomEdge << Qt::SizeVerCursor; } void DecorationInputTest::testResizeOutsideWindow() { // this test verifies that one can resize the window outside the decoration with NoSideBorder // first adjust config kwinApp()->config()->group("org.kde.kdecoration2").writeEntry("BorderSize", QStringLiteral("None")); kwinApp()->config()->sync(); workspace()->slotReconfigure(); // now create window QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QVERIFY(c->geometry() != c->inputGeometry()); QVERIFY(c->inputGeometry().contains(c->geometry())); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); // go to border quint32 timestamp = 1; QFETCH(Qt::Edge, edge); switch (edge) { case Qt::LeftEdge: MOTION(QPoint(c->geometry().x() -1, c->geometry().center().y())); break; case Qt::RightEdge: MOTION(QPoint(c->geometry().x() + c->geometry().width() +1, c->geometry().center().y())); break; case Qt::BottomEdge: MOTION(QPoint(c->geometry().center().x(), c->geometry().y() + c->geometry().height() + 1)); break; default: break; } QVERIFY(!c->geometry().contains(KWin::Cursor::pos())); // pressing should trigger resize PRESS; QVERIFY(!c->isResize()); QVERIFY(startMoveResizedSpy.wait()); QVERIFY(c->isResize()); RELEASE; QVERIFY(!c->isResize()); } +void DecorationInputTest::testModifierClickUnrestrictedMove_data() +{ + QTest::addColumn("modifierKey"); + QTest::addColumn("mouseButton"); + QTest::addColumn("modKey"); + QTest::addColumn("capsLock"); + QTest::addColumn("surfaceType"); + + const QString alt = QStringLiteral("Alt"); + const QString meta = QStringLiteral("Meta"); + + const QVector> surfaceTypes{ + {Test::ShellSurfaceType::WlShell, QByteArrayLiteral("WlShell")}, + {Test::ShellSurfaceType::XdgShellV5, QByteArrayLiteral("XdgShellV5")}, + }; + + for (const auto &type: surfaceTypes) { + QTest::newRow("Left Alt + Left Click" + type.second) << KEY_LEFTALT << BTN_LEFT << alt << false << type.first; + QTest::newRow("Left Alt + Right Click" + type.second) << KEY_LEFTALT << BTN_RIGHT << alt << false << type.first; + QTest::newRow("Left Alt + Middle Click" + type.second) << KEY_LEFTALT << BTN_MIDDLE << alt << false << type.first; + QTest::newRow("Right Alt + Left Click" + type.second) << KEY_RIGHTALT << BTN_LEFT << alt << false << type.first; + QTest::newRow("Right Alt + Right Click" + type.second) << KEY_RIGHTALT << BTN_RIGHT << alt << false << type.first; + QTest::newRow("Right Alt + Middle Click" + type.second) << KEY_RIGHTALT << BTN_MIDDLE << alt << false << type.first; + // now everything with meta + QTest::newRow("Left Meta + Left Click" + type.second) << KEY_LEFTMETA << BTN_LEFT << meta << false << type.first; + QTest::newRow("Left Meta + Right Click" + type.second) << KEY_LEFTMETA << BTN_RIGHT << meta << false << type.first; + QTest::newRow("Left Meta + Middle Click" + type.second) << KEY_LEFTMETA << BTN_MIDDLE << meta << false << type.first; + QTest::newRow("Right Meta + Left Click" + type.second) << KEY_RIGHTMETA << BTN_LEFT << meta << false << type.first; + QTest::newRow("Right Meta + Right Click" + type.second) << KEY_RIGHTMETA << BTN_RIGHT << meta << false << type.first; + QTest::newRow("Right Meta + Middle Click" + type.second) << KEY_RIGHTMETA << BTN_MIDDLE << meta << false << type.first; + + // and with capslock + QTest::newRow("Left Alt + Left Click/CapsLock" + type.second) << KEY_LEFTALT << BTN_LEFT << alt << true << type.first; + QTest::newRow("Left Alt + Right Click/CapsLock" + type.second) << KEY_LEFTALT << BTN_RIGHT << alt << true << type.first; + QTest::newRow("Left Alt + Middle Click/CapsLock" + type.second) << KEY_LEFTALT << BTN_MIDDLE << alt << true << type.first; + QTest::newRow("Right Alt + Left Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_LEFT << alt << true << type.first; + QTest::newRow("Right Alt + Right Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_RIGHT << alt << true << type.first; + QTest::newRow("Right Alt + Middle Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_MIDDLE << alt << true << type.first; + // now everything with meta + QTest::newRow("Left Meta + Left Click/CapsLock" + type.second) << KEY_LEFTMETA << BTN_LEFT << meta << true << type.first; + QTest::newRow("Left Meta + Right Click/CapsLock" + type.second) << KEY_LEFTMETA << BTN_RIGHT << meta << true << type.first; + QTest::newRow("Left Meta + Middle Click/CapsLock" + type.second) << KEY_LEFTMETA << BTN_MIDDLE << meta << true << type.first; + QTest::newRow("Right Meta + Left Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_LEFT << meta << true << type.first; + QTest::newRow("Right Meta + Right Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_RIGHT << meta << true << type.first; + QTest::newRow("Right Meta + Middle Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_MIDDLE << meta << true << type.first; + } +} + +void DecorationInputTest::testModifierClickUnrestrictedMove() +{ + // this test ensures that Alt+mouse button press triggers unrestricted move + + // first modify the config for this run + QFETCH(QString, modKey); + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", modKey); + group.writeEntry("CommandAll1", "Move"); + group.writeEntry("CommandAll2", "Move"); + group.writeEntry("CommandAll3", "Move"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); + + // create a window + QFETCH(Test::ShellSurfaceType, surfaceType); + AbstractClient *c = showWindow(surfaceType); + QVERIFY(c); + QVERIFY(c->isDecorated()); + QVERIFY(!c->noBorder()); + c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); + // move cursor on window + Cursor::setPos(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); + + // simulate modifier+click + quint32 timestamp = 1; + QFETCH(bool, capsLock); + if (capsLock) { + kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + } + QFETCH(int, modifierKey); + QFETCH(int, mouseButton); + kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++); + QVERIFY(!c->isMove()); + kwinApp()->platform()->pointerButtonPressed(mouseButton, timestamp++); + QVERIFY(c->isMove()); + // release modifier should not change it + kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); + QVERIFY(c->isMove()); + // but releasing the key should end move/resize + kwinApp()->platform()->pointerButtonReleased(mouseButton, timestamp++); + QVERIFY(!c->isMove()); + if (capsLock) { + kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + } +} + +void DecorationInputTest::testModifierScrollOpacity_data() +{ + QTest::addColumn("modifierKey"); + QTest::addColumn("modKey"); + QTest::addColumn("capsLock"); + QTest::addColumn("surfaceType"); + + const QString alt = QStringLiteral("Alt"); + const QString meta = QStringLiteral("Meta"); + + const QVector> surfaceTypes{ + {Test::ShellSurfaceType::WlShell, QByteArrayLiteral("WlShell")}, + {Test::ShellSurfaceType::XdgShellV5, QByteArrayLiteral("XdgShellV5")}, + }; + + for (const auto &type: surfaceTypes) { + QTest::newRow("Left Alt" + type.second) << KEY_LEFTALT << alt << false << type.first; + QTest::newRow("Right Alt" + type.second) << KEY_RIGHTALT << alt << false << type.first; + QTest::newRow("Left Meta" + type.second) << KEY_LEFTMETA << meta << false << type.first; + QTest::newRow("Right Meta" + type.second) << KEY_RIGHTMETA << meta << false << type.first; + QTest::newRow("Left Alt/CapsLock" + type.second) << KEY_LEFTALT << alt << true << type.first; + QTest::newRow("Right Alt/CapsLock" + type.second) << KEY_RIGHTALT << alt << true << type.first; + QTest::newRow("Left Meta/CapsLock" + type.second) << KEY_LEFTMETA << meta << true << type.first; + QTest::newRow("Right Meta/CapsLock" + type.second) << KEY_RIGHTMETA << meta << true << type.first; + } +} + +void DecorationInputTest::testModifierScrollOpacity() +{ + // this test verifies that mod+wheel performs a window operation + + // first modify the config for this run + QFETCH(QString, modKey); + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", modKey); + group.writeEntry("CommandAllWheel", "change opacity"); + group.sync(); + workspace()->slotReconfigure(); + + QFETCH(Test::ShellSurfaceType, surfaceType); + AbstractClient *c = showWindow(surfaceType); + QVERIFY(c); + QVERIFY(c->isDecorated()); + QVERIFY(!c->noBorder()); + c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); + // move cursor on window + Cursor::setPos(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); + // set the opacity to 0.5 + c->setOpacity(0.5); + QCOMPARE(c->opacity(), 0.5); + + // simulate modifier+wheel + quint32 timestamp = 1; + QFETCH(bool, capsLock); + if (capsLock) { + kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + } + QFETCH(int, modifierKey); + kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++); + kwinApp()->platform()->pointerAxisVertical(-5, timestamp++); + QCOMPARE(c->opacity(), 0.6); + kwinApp()->platform()->pointerAxisVertical(5, timestamp++); + QCOMPARE(c->opacity(), 0.5); + kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); + if (capsLock) { + kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + } +} + } WAYLANDTEST_MAIN(KWin::DecorationInputTest) #include "decoration_input_test.moc" diff --git a/autotests/integration/effects/CMakeLists.txt b/autotests/integration/effects/CMakeLists.txt index d1d638352..294acb01f 100644 --- a/autotests/integration/effects/CMakeLists.txt +++ b/autotests/integration/effects/CMakeLists.txt @@ -1,5 +1,6 @@ if (XCB_ICCCM_FOUND) integrationTest(NAME testTranslucency SRCS translucency_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testSlidingPopups SRCS slidingpopups_test.cpp LIBS XCB::ICCCM) endif() integrationTest(NAME testFade SRCS fade_test.cpp) +integrationTest(WAYLAND_ONLY NAME testEffectWindowGeometry SRCS windowgeometry_test.cpp) diff --git a/autotests/integration/effects/windowgeometry_test.cpp b/autotests/integration/effects/windowgeometry_test.cpp new file mode 100644 index 000000000..18e5d3cde --- /dev/null +++ b/autotests/integration/effects/windowgeometry_test.cpp @@ -0,0 +1,99 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "kwin_wayland_test.h" +#include "composite.h" +#include "effects.h" +#include "effectloader.h" +#include "cursor.h" +#include "platform.h" +#include "shell_client.h" +#include "wayland_server.h" +#include "workspace.h" +#include "effect_builtins.h" + +#include + +#include +#include +#include + +using namespace KWin; +using namespace KWayland::Client; +static const QString s_socketName = QStringLiteral("wayland_test_effects_windowgeometry-0"); + +class WindowGeometryTest : public QObject +{ +Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testStartup(); +}; + +void WindowGeometryTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + + // 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")); + ScriptedEffectLoader loader; + const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); + for (QString name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + plugins.writeEntry(BuiltInEffects::nameForEffect(BuiltInEffect::WindowGeometry) + QStringLiteral("Enabled"), true); + + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); + kwinApp()->start(); + QVERIFY(workspaceCreatedSpy.wait()); + QVERIFY(KWin::Compositor::self()); +} + +void WindowGeometryTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void WindowGeometryTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void WindowGeometryTest::testStartup() +{ + // just a test to load the effect to verify it doesn't crash + EffectsHandlerImpl *e = static_cast(effects); + QVERIFY(e->isEffectLoaded(BuiltInEffects::nameForEffect(BuiltInEffect::WindowGeometry))); +} + +WAYLANDTEST_MAIN(WindowGeometryTest) +#include "windowgeometry_test.moc" diff --git a/autotests/integration/idle_inhibition_test.cpp b/autotests/integration/idle_inhibition_test.cpp new file mode 100644 index 000000000..bf3d653a4 --- /dev/null +++ b/autotests/integration/idle_inhibition_test.cpp @@ -0,0 +1,129 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "kwin_wayland_test.h" +#include "shell_client.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include +#include + +#include +#include + +using namespace KWin; +using namespace KWayland::Client; +using KWayland::Server::IdleInterface; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_idle_inhbition_test-0"); + +class TestIdleInhibition : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testInhibit_data(); + void testInhibit(); +}; + +void TestIdleInhibition::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + + kwinApp()->start(); + QVERIFY(workspaceCreatedSpy.wait()); + waylandServer()->initWorkspace(); +} + +void TestIdleInhibition::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::IdleInhibition)); + +} + +void TestIdleInhibition::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TestIdleInhibition::testInhibit_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; +} + +void TestIdleInhibition::testInhibit() +{ + auto idle = waylandServer()->display()->findChild(); + QVERIFY(idle); + QVERIFY(!idle->isInhibited()); + QSignalSpy inhibitedSpy(idle, &IdleInterface::inhibitedChanged); + QVERIFY(inhibitedSpy.isValid()); + + // now create window + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + + // not yet inhibited + QVERIFY(!idle->isInhibited()); + + // now create inhibition on window + QScopedPointer inhibitor(Test::waylandIdleInhibitManager()->createInhibitor(surface.data())); + QVERIFY(inhibitor->isValid()); + // this should inhibit our server object + QVERIFY(inhibitedSpy.wait()); + QVERIFY(idle->isInhibited()); + + // deleting the object should uninhibit again + inhibitor.reset(); + QVERIFY(inhibitedSpy.wait()); + QVERIFY(!idle->isInhibited()); + + // inhibit again and destroy window + Test::waylandIdleInhibitManager()->createInhibitor(surface.data(), surface.data()); + QVERIFY(inhibitedSpy.wait()); + QVERIFY(idle->isInhibited()); + + shellSurface.reset(); + if (type == Test::ShellSurfaceType::WlShell) { + surface.reset(); + } + QVERIFY(Test::waitForWindowDestroyed(c)); + QTRY_VERIFY(!idle->isInhibited()); + QCOMPARE(inhibitedSpy.count(), 4); +} + +WAYLANDTEST_MAIN(TestIdleInhibition) +#include "idle_inhibition_test.moc" diff --git a/autotests/integration/kwin_wayland_test.cpp b/autotests/integration/kwin_wayland_test.cpp index 6adf2512f..1ff183aa0 100644 --- a/autotests/integration/kwin_wayland_test.cpp +++ b/autotests/integration/kwin_wayland_test.cpp @@ -1,296 +1,296 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "../../platform.h" #include "../../composite.h" #include "../../effects.h" #include "../../wayland_server.h" #include "../../workspace.h" #include "../../xcbutils.h" #include #include #include #include #include #include #include // system #include #include #include namespace KWin { static void readDisplay(int pipe); WaylandTestApplication::WaylandTestApplication(OperationMode mode, int &argc, char **argv) : Application(mode, argc, argv) { QStandardPaths::setTestModeEnabled(true); #ifdef KWIN_BUILD_ACTIVITIES setUseKActivities(false); #endif qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); - initPlatform(KPluginMetaData(QStringLiteral(KWINBACKENDPATH))); + initPlatform(KPluginMetaData(QStringLiteral("KWinWaylandVirtualBackend.so"))); WaylandServer::create(this); } WaylandTestApplication::~WaylandTestApplication() { kwinApp()->platform()->setOutputsEnabled(false); // need to unload all effects prior to destroying X connection as they might do X calls // also before destroy Workspace, as effects might call into Workspace if (effects) { static_cast(effects)->unloadAllEffects(); } destroyWorkspace(); waylandServer()->dispatch(); disconnect(m_xwaylandFailConnection); if (x11Connection()) { Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); destroyAtoms(); emit x11ConnectionAboutToBeDestroyed(); xcb_disconnect(x11Connection()); setX11Connection(nullptr); } if (m_xwaylandProcess) { m_xwaylandProcess->terminate(); while (m_xwaylandProcess->state() != QProcess::NotRunning) { processEvents(QEventLoop::WaitForMoreEvents); } waylandServer()->destroyXWaylandConnection(); } if (QStyle *s = style()) { s->unpolish(this); } waylandServer()->terminateClientConnections(); destroyCompositor(); } void WaylandTestApplication::performStartup() { // first load options - done internally by a different thread createOptions(); waylandServer()->createInternalConnection(); // try creating the Wayland Backend createInput(); createBackend(); } void WaylandTestApplication::createBackend() { Platform *platform = kwinApp()->platform(); connect(platform, &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); connect(platform, &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; ::exit(1); } ); platform->init(); } void WaylandTestApplication::continueStartupWithScreens() { disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); createScreens(); if (operationMode() == OperationModeWaylandOnly) { createCompositor(); connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen); return; } createCompositor(); connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::startXwaylandServer); } void WaylandTestApplication::continueStartupWithSceen() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen); createWorkspace(); } void WaylandTestApplication::continueStartupWithX() { createX11Connection(); xcb_connection_t *c = x11Connection(); if (!c) { // about to quit return; } QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(c), QSocketNotifier::Read, this); auto processXcbEvents = [this, c] { while (auto event = xcb_poll_for_event(c)) { updateX11Time(event); long result = 0; if (QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result)) { free(event); continue; } if (Workspace::self()) { Workspace::self()->workspaceEvent(event); } free(event); } xcb_flush(c); }; connect(notifier, &QSocketNotifier::activated, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); // create selection owner for WM_S0 - magic X display number expected by XWayland KSelectionOwner owner("WM_S0", c, x11RootWindow()); owner.claim(true); createAtoms(); setupEventFilters(); // Check whether another windowmanager is running const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; ScopedCPointer redirectCheck(xcb_request_check(connection(), xcb_change_window_attributes_checked(connection(), rootWindow(), XCB_CW_EVENT_MASK, maskValues))); if (!redirectCheck.isNull()) { ::exit(1); } createWorkspace(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort } void WaylandTestApplication::createX11Connection() { int screenNumber = 0; xcb_connection_t *c = nullptr; if (m_xcbConnectionFd == -1) { c = xcb_connect(nullptr, &screenNumber); } else { c = xcb_connect_to_fd(m_xcbConnectionFd, nullptr); } if (int error = xcb_connection_has_error(c)) { std::cerr << "FATAL ERROR: Creating connection to XServer failed: " << error << std::endl; exit(1); return; } setX11Connection(c); // we don't support X11 multi-head in Wayland setX11ScreenNumber(screenNumber); setX11RootWindow(defaultScreen()->root); } void WaylandTestApplication::startXwaylandServer() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::startXwaylandServer); int pipeFds[2]; if (pipe(pipeFds) != 0) { std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl; exit(1); return; } int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; exit(1); return; } int fd = dup(sx[1]); if (fd < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; exit(20); return; } const int waylandSocket = waylandServer()->createXWaylandConnection(); if (waylandSocket == -1) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(1); return; } const int wlfd = dup(waylandSocket); if (wlfd < 0) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(20); return; } m_xcbConnectionFd = sx[0]; m_xwaylandProcess = new QProcess(kwinApp()); m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); m_xwaylandProcess->setProcessEnvironment(env); m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"), QString::number(pipeFds[1]), QStringLiteral("-rootless"), QStringLiteral("-wm"), QString::number(fd)}); m_xwaylandFailConnection = connect(m_xwaylandProcess, static_cast(&QProcess::error), this, [] (QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl; } else { std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl; } exit(1); } ); const int xDisplayPipe = pipeFds[0]; connect(m_xwaylandProcess, &QProcess::started, this, [this, xDisplayPipe] { QFutureWatcher *watcher = new QFutureWatcher(this); QObject::connect(watcher, &QFutureWatcher::finished, this, &WaylandTestApplication::continueStartupWithX, Qt::QueuedConnection); QObject::connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater, Qt::QueuedConnection); watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe)); } ); m_xwaylandProcess->start(); close(pipeFds[1]); } static void readDisplay(int pipe) { QFile readPipe; if (!readPipe.open(pipe, QIODevice::ReadOnly)) { std::cerr << "FATAL ERROR failed to open pipe to start X Server" << std::endl; exit(1); } QByteArray displayNumber = readPipe.readLine(); displayNumber.prepend(QByteArray(":")); displayNumber.remove(displayNumber.size() -1, 1); std::cout << "X-Server started on display " << displayNumber.constData() << std::endl; setenv("DISPLAY", displayNumber.constData(), true); // close our pipe close(pipe); } } diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index c79b38f39..6d95fe4dc 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -1,194 +1,205 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_WAYLAND_TEST_H #define KWIN_WAYLAND_TEST_H #include "../../main.h" // Qt #include namespace KWayland { namespace Client { +class AppMenuManager; class ConnectionThread; class Compositor; +class IdleInhibitManager; class PlasmaShell; class PlasmaWindowManagement; class PointerConstraints; class Seat; class ServerSideDecorationManager; class Shell; class ShellSurface; class ShmPool; class Surface; class XdgShellSurface; } } namespace KWin { class AbstractClient; class ShellClient; class WaylandTestApplication : public Application { Q_OBJECT public: WaylandTestApplication(OperationMode mode, int &argc, char **argv); virtual ~WaylandTestApplication(); protected: void performStartup() override; private: void createBackend(); void createX11Connection(); void continueStartupWithScreens(); void continueStartupWithSceen(); void continueStartupWithX(); void startXwaylandServer(); int m_xcbConnectionFd = -1; QProcess *m_xwaylandProcess = nullptr; QMetaObject::Connection m_xwaylandFailConnection; }; namespace Test { enum class AdditionalWaylandInterface { Seat = 1 << 0, Decoration = 1 << 1, PlasmaShell = 1 << 2, WindowManagement = 1 << 3, - PointerConstraints = 1 << 4 + PointerConstraints = 1 << 4, + IdleInhibition = 1 << 5, + AppMenu = 1 << 6 }; Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) /** * Creates a Wayland Connection in a dedicated thread and creates various * client side objects which can be used to create windows. * @returns @c true if created successfully, @c false if there was an error * @see destroyWaylandConnection **/ bool setupWaylandConnection(AdditionalWaylandInterfaces flags = AdditionalWaylandInterfaces()); /** * Destroys the Wayland Connection created with @link{setupWaylandConnection}. * This can be called from cleanup in order to ensure that no Wayland Connection * leaks into the next test method. * @see setupWaylandConnection */ void destroyWaylandConnection(); KWayland::Client::ConnectionThread *waylandConnection(); KWayland::Client::Compositor *waylandCompositor(); KWayland::Client::Shell *waylandShell(); KWayland::Client::ShmPool *waylandShmPool(); KWayland::Client::Seat *waylandSeat(); KWayland::Client::ServerSideDecorationManager *waylandServerSideDecoration(); KWayland::Client::PlasmaShell *waylandPlasmaShell(); KWayland::Client::PlasmaWindowManagement *waylandWindowManagement(); KWayland::Client::PointerConstraints *waylandPointerConstraints(); +KWayland::Client::IdleInhibitManager *waylandIdleInhibitManager(); +KWayland::Client::AppMenuManager *waylandAppMenuManager(); + bool waitForWaylandPointer(); bool waitForWaylandTouch(); bool waitForWaylandKeyboard(); void flushWaylandConnection(); KWayland::Client::Surface *createSurface(QObject *parent = nullptr); enum class ShellSurfaceType { WlShell, XdgShellV5, XdgShellV6 }; QObject *createShellSurface(ShellSurfaceType type, KWayland::Client::Surface *surface, QObject *parent = nullptr); KWayland::Client::ShellSurface *createShellSurface(KWayland::Client::Surface *surface, QObject *parent = nullptr); KWayland::Client::XdgShellSurface *createXdgShellV5Surface(KWayland::Client::Surface *surface, QObject *parent = nullptr); +KWayland::Client::XdgShellSurface *createXdgShellV6Surface(KWayland::Client::Surface *surface, QObject *parent = nullptr); /** * Creates a shared memory buffer of @p size in @p color and attaches it to the @p surface. * The @p surface gets damaged and committed, thus it's rendered. **/ void render(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format = QImage::Format_ARGB32_Premultiplied); /** * Creates a shared memory buffer using the supplied image @p img and attaches it to the @p surface */ void render(KWayland::Client::Surface *surface, const QImage &img); /** * Waits till a new ShellClient is shown and returns the created ShellClient. * If no ShellClient gets shown during @p timeout @c null is returned. **/ ShellClient *waitForWaylandWindowShown(int timeout = 5000); /** * Combination of @link{render} and @link{waitForWaylandWindowShown}. **/ ShellClient *renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format = QImage::Format_ARGB32, int timeout = 5000); /** * Waits for the @p client to be destroyed. **/ bool waitForWindowDestroyed(AbstractClient *client); /** * Locks the screen and waits till the screen is locked. * @returns @c true if the screen could be locked, @c false otherwise **/ bool lockScreen(); /** * Unlocks the screen and waits till the screen is unlocked. * @returns @c true if the screen could be unlocked, @c false otherwise **/ bool unlockScreen(); } } Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Test::AdditionalWaylandInterfaces) Q_DECLARE_METATYPE(KWin::Test::ShellSurfaceType) #define WAYLANDTEST_MAIN_HELPER(TestObject, DPI, OperationMode) \ int main(int argc, char *argv[]) \ { \ setenv("QT_QPA_PLATFORM", "wayland-org.kde.kwin.qpa", true); \ - setenv("QT_QPA_PLATFORM_PLUGIN_PATH", KWINQPAPATH, true); \ + setenv("QT_QPA_PLATFORM_PLUGIN_PATH", QFileInfo(QString::fromLocal8Bit(argv[0])).absolutePath().toLocal8Bit().constData(), true); \ setenv("KWIN_FORCE_OWN_QPA", "1", true); \ DPI; \ KWin::WaylandTestApplication app(OperationMode, argc, argv); \ app.setAttribute(Qt::AA_Use96Dpi, true); \ + const auto ownPath = app.libraryPaths().last(); \ + app.removeLibraryPath(ownPath); \ + app.addLibraryPath(ownPath); \ TestObject tc; \ return QTest::qExec(&tc, argc, argv); \ } #ifdef NO_XWAYLAND #define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject, QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling), KWin::Application::OperationModeWaylandOnly) #else #define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject, QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling), KWin::Application::OperationModeXwayland) #endif #endif diff --git a/autotests/integration/quick_tiling_test.cpp b/autotests/integration/quick_tiling_test.cpp index 690d2e7e0..7fb844c08 100644 --- a/autotests/integration/quick_tiling_test.cpp +++ b/autotests/integration/quick_tiling_test.cpp @@ -1,788 +1,838 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "client.h" #include "cursor.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include "scripting/scripting.h" #include #include #include #include +#include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::QuickTileMode) Q_DECLARE_METATYPE(KWin::MaximizeMode) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0"); class QuickTilingTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testQuickTiling_data(); void testQuickTiling(); void testQuickMaximizing_data(); void testQuickMaximizing(); void testQuickTilingKeyboardMove_data(); void testQuickTilingKeyboardMove(); void testQuickTilingPointerMove_data(); void testQuickTilingPointerMove(); + void testQuickTilingPointerMoveXdgShell_data(); + void testQuickTilingPointerMoveXdgShell(); void testX11QuickTiling_data(); void testX11QuickTiling(); void testX11QuickTilingAfterVertMaximize_data(); void testX11QuickTilingAfterVertMaximize(); void testShortcut_data(); void testShortcut(); void testScript_data(); void testScript(); private: KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Shell *m_shell = nullptr; }; void QuickTilingTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("MaximizeMode"); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // set custom config which disables the Outline KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup group = config->group("Outline"); group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); group.sync(); kwinApp()->setConfig(config); qputenv("XKB_DEFAULT_RULES", "evdev"); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); } void QuickTilingTest::init() { QVERIFY(Test::setupWaylandConnection()); m_connection = Test::waylandConnection(); m_compositor = Test::waylandCompositor(); m_shell = Test::waylandShell(); screens()->setCurrent(0); } void QuickTilingTest::cleanup() { Test::destroyWaylandConnection(); } void QuickTilingTest::testQuickTiling_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); QTest::addColumn("secondScreen"); QTest::addColumn("expectedModeAfterToggle"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024) << QRect(1280, 0, 640, 1024) << FLAG(Right); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512) << QRect(1280, 0, 1280, 512) << FLAG(Top); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024) << QRect(1920, 0, 640, 1024) << QuickTileMode(); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512) << QRect(1280, 512, 1280, 512) << FLAG(Bottom); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512) << QRect(1280, 0, 640, 512) << (FLAG(Right) | FLAG(Top)); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512) << QRect(1920, 0, 640, 512) << QuickTileMode(); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512) << QRect(1280, 512, 640, 512) << (FLAG(Right) | FLAG(Bottom)); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512) << QRect(1920, 512, 640, 512) << QuickTileMode(); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QuickTileMode(); #undef FLAG } void QuickTilingTest::testQuickTiling() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QFETCH(QuickTileMode, mode); QFETCH(QRect, expectedGeometry); c->setQuickTileMode(mode, true); QCOMPARE(quickTileChangedSpy.count(), 1); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), mode); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), expectedGeometry.size()); // attach a new image Test::render(surface.data(), expectedGeometry.size(), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(c->geometry(), expectedGeometry); // send window to other screen QCOMPARE(c->screen(), 0); c->sendToScreen(1); QCOMPARE(c->screen(), 1); // quick tile should not be changed QCOMPARE(c->quickTileMode(), mode); QTEST(c->geometry(), "secondScreen"); // now try to toggle again c->setQuickTileMode(mode, true); QTEST(c->quickTileMode(), "expectedModeAfterToggle"); } void QuickTilingTest::testQuickMaximizing_data() { QTest::addColumn("mode"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("maximize") << FLAG(Maximize); QTest::newRow("none") << FLAG(None); #undef FLAG } void QuickTilingTest::testQuickMaximizing() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy maximizeChangedSpy1(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,MaximizeMode))); QVERIFY(maximizeChangedSpy1.isValid()); QSignalSpy maximizeChangedSpy2(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,bool,bool))); QVERIFY(maximizeChangedSpy2.isValid()); c->setQuickTileMode(QuickTileFlag::Maximize, true); QCOMPARE(quickTileChangedSpy.count(), 1); QCOMPARE(maximizeChangedSpy1.count(), 1); QCOMPARE(maximizeChangedSpy1.first().first().value(), c); QCOMPARE(maximizeChangedSpy1.first().last().value(), MaximizeFull); QCOMPARE(maximizeChangedSpy2.count(), 1); QCOMPARE(maximizeChangedSpy2.first().first().value(), c); QCOMPARE(maximizeChangedSpy2.first().at(1).toBool(), true); QCOMPARE(maximizeChangedSpy2.first().at(2).toBool(), true); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), QuickTileFlag::Maximize); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), QSize(1280, 1024)); // attach a new image Test::render(surface.data(), QSize(1280, 1024), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 2); QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // go back to quick tile none QFETCH(QuickTileMode, mode); c->setQuickTileMode(mode, true); QCOMPARE(quickTileChangedSpy.count(), 2); QCOMPARE(maximizeChangedSpy1.count(), 2); QCOMPARE(maximizeChangedSpy1.last().first().value(), c); QCOMPARE(maximizeChangedSpy1.last().last().value(), MaximizeRestore); QCOMPARE(maximizeChangedSpy2.count(), 2); QCOMPARE(maximizeChangedSpy2.last().first().value(), c); QCOMPARE(maximizeChangedSpy2.last().at(1).toBool(), false); QCOMPARE(maximizeChangedSpy2.last().at(2).toBool(), false); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); // geometry not yet changed QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 2); QCOMPARE(sizeChangeSpy.last().first().toSize(), QSize(100, 50)); // render again Test::render(surface.data(), QSize(100, 50), Qt::yellow); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 4); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); } void QuickTilingTest::testQuickTilingKeyboardMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } void QuickTilingTest::testQuickTilingKeyboardMove() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); while (Cursor::pos().x() > targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_LEFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFT, timestamp++); } while (Cursor::pos().x() < targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); } while (Cursor::pos().y() < targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_DOWN, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_DOWN, timestamp++); } while (Cursor::pos().y() > targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_UP, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_UP, timestamp++); } kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_ENTER, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_ENTER, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->getMovingClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); } void QuickTilingTest::testQuickTilingPointerMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } void QuickTilingTest::testQuickTilingPointerMove() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(targetPos, timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->getMovingClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); + QTRY_COMPARE(sizeChangeSpy.count(), 1); +} + + +void QuickTilingTest::testQuickTilingPointerMoveXdgShell_data() +{ + QTest::addColumn("targetPos"); + QTest::addColumn("expectedMode"); + + QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); + QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); + QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); + QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); + QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); + QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); +} + +void QuickTilingTest::testQuickTilingPointerMoveXdgShell() +{ + using namespace KWayland::Client; + + QScopedPointer surface(Test::createSurface()); + QVERIFY(!surface.isNull()); + + QScopedPointer shellSurface(Test::createXdgShellV6Surface(surface.data())); + QVERIFY(!shellSurface.isNull()); + QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); + QVERIFY(configureRequestedSpy.isValid()); + // let's render + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + + QVERIFY(c); + QCOMPARE(workspace()->activeClient(), c); + QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); + QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + QCOMPARE(c->maximizeMode(), MaximizeRestore); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 2); + + QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); + QVERIFY(quickTileChangedSpy.isValid()); + + workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); + QCOMPARE(c, workspace()->getMovingClient()); + QCOMPARE(Cursor::pos(), QPoint(49, 24)); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 3); + + QFETCH(QPoint, targetPos); + quint32 timestamp = 1; + kwinApp()->platform()->pointerMotion(targetPos, timestamp++); + kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); + kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); + QCOMPARE(Cursor::pos(), targetPos); + QVERIFY(!workspace()->getMovingClient()); + + QCOMPARE(quickTileChangedSpy.count(), 1); + QTEST(c->quickTileMode(), "expectedMode"); + QVERIFY(configureRequestedSpy.wait()); + QEXPECT_FAIL("", "BUG 388072", Continue); + QCOMPARE(configureRequestedSpy.count(), 4); + QEXPECT_FAIL("", "BUG 388072", Continue); + QCOMPARE(false, configureRequestedSpy.last().first().toSize().isEmpty()); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void QuickTilingTest::testX11QuickTiling_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); QTest::addColumn("screen"); QTest::addColumn("modeAfterToggle"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024) << 0 << QuickTileMode(); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512) << 1 << FLAG(Top); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024) << 1 << FLAG(Left); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512) << 1 << FLAG(Bottom); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512) << 0 << QuickTileMode(); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512) << 1 << (FLAG(Left) | FLAG(Top)); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512) << 0 << QuickTileMode(); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512) << 1 << (FLAG(Left) | FLAG(Bottom)); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024) << 0 << QuickTileMode(); #undef FLAG } void QuickTilingTest::testX11QuickTiling() { QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); // now quick tile QSignalSpy quickTileChangedSpy(client, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); const QRect origGeo = client->geometry(); QFETCH(QuickTileMode, mode); client->setQuickTileMode(mode, true); QCOMPARE(client->quickTileMode(), mode); QTEST(client->geometry(), "expectedGeometry"); QCOMPARE(client->geometryRestore(), origGeo); QEXPECT_FAIL("maximize", "For maximize we get two changed signals", Continue); QCOMPARE(quickTileChangedSpy.count(), 1); // quick tile to same edge again should also act like send to screen QCOMPARE(client->screen(), 0); client->setQuickTileMode(mode, true); QTEST(client->screen(), "screen"); QTEST(client->quickTileMode(), "modeAfterToggle"); QCOMPARE(client->geometryRestore(), origGeo); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); c.reset(); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); } void QuickTilingTest::testX11QuickTilingAfterVertMaximize_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024); #undef FLAG } void QuickTilingTest::testX11QuickTilingAfterVertMaximize() { QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); const QRect origGeo = client->geometry(); QCOMPARE(client->maximizeMode(), MaximizeRestore); // vertically maximize the window client->maximize(client->maximizeMode() ^ MaximizeVertical); QCOMPARE(client->geometry().width(), origGeo.width()); QCOMPARE(client->height(), screens()->size(client->screen()).height()); QCOMPARE(client->geometryRestore(), origGeo); // now quick tile QSignalSpy quickTileChangedSpy(client, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QFETCH(QuickTileMode, mode); client->setQuickTileMode(mode, true); - QEXPECT_FAIL("left", "Quick tiling not working", Continue); - QEXPECT_FAIL("right", "Quick tiling not working", Continue); - QEXPECT_FAIL("top", "Quick tiling not working", Continue); - QEXPECT_FAIL("bottom", "Quick tiling not working", Continue); - QEXPECT_FAIL("top left", "Quick tiling not working", Continue); - QEXPECT_FAIL("top right", "Quick tiling not working", Continue); - QEXPECT_FAIL("bottom left", "Quick tiling not working", Continue); - QEXPECT_FAIL("bottom right", "Quick tiling not working", Continue); QCOMPARE(client->quickTileMode(), mode); - QEXPECT_FAIL("left", "Quick tiling not working", Continue); - QEXPECT_FAIL("right", "Quick tiling not working", Continue); - QEXPECT_FAIL("top", "Quick tiling not working", Continue); - QEXPECT_FAIL("bottom", "Quick tiling not working", Continue); - QEXPECT_FAIL("top left", "Quick tiling not working", Continue); - QEXPECT_FAIL("top right", "Quick tiling not working", Continue); - QEXPECT_FAIL("bottom left", "Quick tiling not working", Continue); - QEXPECT_FAIL("bottom right", "Quick tiling not working", Continue); QTEST(client->geometry(), "expectedGeometry"); QEXPECT_FAIL("", "We get two changed events", Continue); QCOMPARE(quickTileChangedSpy.count(), 1); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); c.reset(); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); } void QuickTilingTest::testShortcut_data() { QTest::addColumn("shortcut"); QTest::addColumn("expectedMode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("top") << QStringLiteral("Window Quick Tile Top") << FLAG(Top) << QRect(0, 0, 1280, 512); QTest::newRow("left") << QStringLiteral("Window Quick Tile Left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("bottom") << QStringLiteral("Window Quick Tile Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); QTest::newRow("right") << QStringLiteral("Window Quick Tile Right") << FLAG(Right) << QRect(640, 0, 640, 1024); QTest::newRow("top right") << QStringLiteral("Window Quick Tile Top Right") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); QTest::newRow("top left") << QStringLiteral("Window Quick Tile Top Left") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); QTest::newRow("bottom right") << QStringLiteral("Window Quick Tile Bottom Right") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); QTest::newRow("bottom left") << QStringLiteral("Window Quick Tile Bottom Left") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); #undef FLAG } void QuickTilingTest::testShortcut() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QFETCH(QString, shortcut); QFETCH(QuickTileMode, expectedMode); QFETCH(QRect, expectedGeometry); // invoke global shortcut through dbus auto msg = QDBusMessage::createMethodCall( QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/component/kwin"), QStringLiteral("org.kde.kglobalaccel.Component"), QStringLiteral("invokeShortcut")); msg.setArguments(QList{shortcut}); QDBusConnection::sessionBus().asyncCall(msg); QVERIFY(quickTileChangedSpy.wait()); QCOMPARE(quickTileChangedSpy.count(), 1); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), expectedMode); // but we got requested a new geometry QTRY_COMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), expectedGeometry.size()); // attach a new image Test::render(surface.data(), expectedGeometry.size(), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(c->geometry(), expectedGeometry); } void QuickTilingTest::testScript_data() { QTest::addColumn("action"); QTest::addColumn("expectedMode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("top") << QStringLiteral("Top") << FLAG(Top) << QRect(0, 0, 1280, 512); QTest::newRow("left") << QStringLiteral("Left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("bottom") << QStringLiteral("Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); QTest::newRow("right") << QStringLiteral("Right") << FLAG(Right) << QRect(640, 0, 640, 1024); QTest::newRow("top right") << QStringLiteral("TopRight") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); QTest::newRow("top left") << QStringLiteral("TopLeft") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); QTest::newRow("bottom right") << QStringLiteral("BottomRight") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); QTest::newRow("bottom left") << QStringLiteral("BottomLeft") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); #undef FLAG } void QuickTilingTest::testScript() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QVERIFY(Scripting::self()); QTemporaryFile tmpFile; QVERIFY(tmpFile.open()); QTextStream out(&tmpFile); QFETCH(QString, action); out << "workspace.slotWindowQuickTile" << action << "()"; out.flush(); QFETCH(QuickTileMode, expectedMode); QFETCH(QRect, expectedGeometry); const int id = Scripting::self()->loadScript(tmpFile.fileName()); QVERIFY(id != -1); QVERIFY(Scripting::self()->isScriptLoaded(tmpFile.fileName())); auto s = Scripting::self()->findScript(tmpFile.fileName()); QVERIFY(s); QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); QVERIFY(runningChangedSpy.isValid()); s->run(); QVERIFY(quickTileChangedSpy.wait()); QCOMPARE(quickTileChangedSpy.count(), 1); QCOMPARE(runningChangedSpy.count(), 1); QCOMPARE(runningChangedSpy.first().first().toBool(), true); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), expectedMode); // but we got requested a new geometry QTRY_COMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), expectedGeometry.size()); // attach a new image Test::render(surface.data(), expectedGeometry.size(), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(c->geometry(), expectedGeometry); } } WAYLANDTEST_MAIN(KWin::QuickTilingTest) #include "quick_tiling_test.moc" diff --git a/autotests/integration/shell_client_rules_test.cpp b/autotests/integration/shell_client_rules_test.cpp index 7f6fd6c93..89659d611 100644 --- a/autotests/integration/shell_client_rules_test.cpp +++ b/autotests/integration/shell_client_rules_test.cpp @@ -1,169 +1,329 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2017 Martin Flöser This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "rules.h" #include "screens.h" #include "shell_client.h" #include "virtualdesktops.h" #include "wayland_server.h" #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_shell_client_rules-0"); class TestShellClientRules : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testApplyInitialDesktop_data(); void testApplyInitialDesktop(); void testApplyInitialMinimize_data(); void testApplyInitialMinimize(); + void testApplyInitialSkipTaskbar_data(); + void testApplyInitialSkipTaskbar(); + void testApplyInitialSkipPager_data(); + void testApplyInitialSkipPager(); + void testApplyInitialSkipSwitcher_data(); + void testApplyInitialSkipSwitcher(); + void testApplyInitialKeepAbove_data(); + void testApplyInitialKeepAbove(); + void testApplyInitialKeepBelow_data(); + void testApplyInitialKeepBelow(); + void testApplyInitialShortcut_data(); + void testApplyInitialShortcut(); }; void TestShellClientRules::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TestShellClientRules::init() { VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); screens()->setCurrent(0); } void TestShellClientRules::cleanup() { Test::destroyWaylandConnection(); } -void TestShellClientRules::testApplyInitialDesktop_data() -{ - QTest::addColumn("type"); - QTest::addColumn("ruleNumber"); - - QTest::newRow("wlShell|Force") << Test::ShellSurfaceType::WlShell << 2; - QTest::newRow("xdgShellV5|Force") << Test::ShellSurfaceType::XdgShellV5 << 2; - QTest::newRow("xdgShellV6|Force") << Test::ShellSurfaceType::XdgShellV6 << 2; - - QTest::newRow("wlShell|Apply") << Test::ShellSurfaceType::WlShell << 3; - QTest::newRow("xdgShellV5|Apply") << Test::ShellSurfaceType::XdgShellV5 << 3; - QTest::newRow("xdgShellV6|Apply") << Test::ShellSurfaceType::XdgShellV6 << 3; - - QTest::newRow("wlShell|ApplyNow") << Test::ShellSurfaceType::WlShell << 5; - QTest::newRow("xdgShellV5|ApplyNow") << Test::ShellSurfaceType::XdgShellV5 << 5; - QTest::newRow("xdgShellV6|ApplyNow") << Test::ShellSurfaceType::XdgShellV6 << 5; - - QTest::newRow("wlShell|ForceTemporarily") << Test::ShellSurfaceType::WlShell << 6; - QTest::newRow("xdgShellV5|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV5 << 6; - QTest::newRow("xdgShellV6|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV6 << 6; +#define TEST_DATA( name ) \ +void TestShellClientRules::name##_data() \ +{ \ + QTest::addColumn("type"); \ + QTest::addColumn("ruleNumber"); \ + QTest::newRow("wlShell|Force") << Test::ShellSurfaceType::WlShell << 2; \ + QTest::newRow("xdgShellV5|Force") << Test::ShellSurfaceType::XdgShellV5 << 2; \ + QTest::newRow("xdgShellV6|Force") << Test::ShellSurfaceType::XdgShellV6 << 2; \ + QTest::newRow("wlShell|Apply") << Test::ShellSurfaceType::WlShell << 3; \ + QTest::newRow("xdgShellV5|Apply") << Test::ShellSurfaceType::XdgShellV5 << 3; \ + QTest::newRow("xdgShellV6|Apply") << Test::ShellSurfaceType::XdgShellV6 << 3; \ + QTest::newRow("wlShell|ApplyNow") << Test::ShellSurfaceType::WlShell << 5; \ + QTest::newRow("xdgShellV5|ApplyNow") << Test::ShellSurfaceType::XdgShellV5 << 5; \ + QTest::newRow("xdgShellV6|ApplyNow") << Test::ShellSurfaceType::XdgShellV6 << 5; \ + QTest::newRow("wlShell|ForceTemporarily") << Test::ShellSurfaceType::WlShell << 6; \ + QTest::newRow("xdgShellV5|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV5 << 6; \ + QTest::newRow("xdgShellV6|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV6 << 6; \ } +TEST_DATA(testApplyInitialDesktop) + void TestShellClientRules::testApplyInitialDesktop() { // ensure we have two desktops and are on first desktop VirtualDesktopManager::self()->setCount(2); VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); // install the temporary rule QFETCH(int, ruleNumber); QString rule = QStringLiteral("desktop=2\ndesktoprule=%1").arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktop(), 2); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->isActive(), true); + QCOMPARE(c->skipTaskbar(), false); + QCOMPARE(c->skipPager(), false); + QCOMPARE(c->skipSwitcher(), false); + QCOMPARE(c->keepAbove(), false); + QCOMPARE(c->keepBelow(), false); + QCOMPARE(c->shortcut(), QKeySequence()); } -void TestShellClientRules::testApplyInitialMinimize_data() +TEST_DATA(testApplyInitialMinimize) + +void TestShellClientRules::testApplyInitialMinimize() { - QTest::addColumn("type"); - QTest::addColumn("ruleNumber"); + // install the temporary rule + QFETCH(int, ruleNumber); + QString rule = QStringLiteral("minimize=1\nminimizerule=%1").arg(ruleNumber); + QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(c->desktop(), 1); + QCOMPARE(c->isMinimized(), true); + QCOMPARE(c->isActive(), false); + c->setMinimized(false); + QCOMPARE(c->isMinimized(), false); + QCOMPARE(c->skipTaskbar(), false); + QCOMPARE(c->skipPager(), false); + QCOMPARE(c->skipSwitcher(), false); + QCOMPARE(c->keepAbove(), false); + QCOMPARE(c->keepBelow(), false); + QCOMPARE(c->shortcut(), QKeySequence()); +} - QTest::newRow("wlShell|Force") << Test::ShellSurfaceType::WlShell << 2; - QTest::newRow("xdgShellV5|Force") << Test::ShellSurfaceType::XdgShellV5 << 2; - QTest::newRow("xdgShellV6|Force") << Test::ShellSurfaceType::XdgShellV6 << 2; +TEST_DATA(testApplyInitialSkipTaskbar) - QTest::newRow("wlShell|Apply") << Test::ShellSurfaceType::WlShell << 3; - QTest::newRow("xdgShellV5|Apply") << Test::ShellSurfaceType::XdgShellV5 << 3; - QTest::newRow("xdgShellV6|Apply") << Test::ShellSurfaceType::XdgShellV6 << 3; +void TestShellClientRules::testApplyInitialSkipTaskbar() +{ + // install the temporary rule + QFETCH(int, ruleNumber); + QString rule = QStringLiteral("skiptaskbar=true\nskiptaskbarrule=%1").arg(ruleNumber); + QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); - QTest::newRow("wlShell|ApplyNow") << Test::ShellSurfaceType::WlShell << 5; - QTest::newRow("xdgShellV5|ApplyNow") << Test::ShellSurfaceType::XdgShellV5 << 5; - QTest::newRow("xdgShellV6|ApplyNow") << Test::ShellSurfaceType::XdgShellV6 << 5; + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); - QTest::newRow("wlShell|ForceTemporarily") << Test::ShellSurfaceType::WlShell << 6; - QTest::newRow("xdgShellV5|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV5 << 6; - QTest::newRow("xdgShellV6|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV6 << 6; + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(c->desktop(), 1); + QCOMPARE(c->isMinimized(), false); + QCOMPARE(c->isActive(), true); + QCOMPARE(c->skipTaskbar(), true); + QCOMPARE(c->skipPager(), false); + QCOMPARE(c->skipSwitcher(), false); + QCOMPARE(c->keepAbove(), false); + QCOMPARE(c->keepBelow(), false); + QCOMPARE(c->shortcut(), QKeySequence()); } -void TestShellClientRules::testApplyInitialMinimize() +TEST_DATA(testApplyInitialSkipPager) + +void TestShellClientRules::testApplyInitialSkipPager() { // install the temporary rule QFETCH(int, ruleNumber); - QString rule = QStringLiteral("minimize=1\nminimizerule=%1").arg(ruleNumber); + QString rule = QStringLiteral("skippager=true\nskippagerrule=%1").arg(ruleNumber); + QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(c->desktop(), 1); + QCOMPARE(c->isMinimized(), false); + QCOMPARE(c->isActive(), true); + QCOMPARE(c->skipTaskbar(), false); + QCOMPARE(c->skipPager(), true); + QCOMPARE(c->skipSwitcher(), false); + QCOMPARE(c->keepAbove(), false); + QCOMPARE(c->keepBelow(), false); + QCOMPARE(c->shortcut(), QKeySequence()); +} + +TEST_DATA(testApplyInitialSkipSwitcher) + +void TestShellClientRules::testApplyInitialSkipSwitcher() +{ + // install the temporary rule + QFETCH(int, ruleNumber); + QString rule = QStringLiteral("skipswitcher=true\nskipswitcherrule=%1").arg(ruleNumber); + QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(c->desktop(), 1); + QCOMPARE(c->isMinimized(), false); + QCOMPARE(c->isActive(), true); + QCOMPARE(c->skipTaskbar(), false); + QCOMPARE(c->skipPager(), false); + QCOMPARE(c->skipSwitcher(), true); + QCOMPARE(c->keepAbove(), false); + QCOMPARE(c->keepBelow(), false); + QCOMPARE(c->shortcut(), QKeySequence()); +} + +TEST_DATA(testApplyInitialKeepAbove) + +void TestShellClientRules::testApplyInitialKeepAbove() +{ + // install the temporary rule + QFETCH(int, ruleNumber); + QString rule = QStringLiteral("above=true\naboverule=%1").arg(ruleNumber); + QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(c->desktop(), 1); + QCOMPARE(c->isMinimized(), false); + QCOMPARE(c->isActive(), true); + QCOMPARE(c->skipTaskbar(), false); + QCOMPARE(c->skipPager(), false); + QCOMPARE(c->skipSwitcher(), false); + QCOMPARE(c->keepAbove(), true); + QCOMPARE(c->keepBelow(), false); + QCOMPARE(c->shortcut(), QKeySequence()); +} + +TEST_DATA(testApplyInitialKeepBelow) + +void TestShellClientRules::testApplyInitialKeepBelow() +{ + // install the temporary rule + QFETCH(int, ruleNumber); + QString rule = QStringLiteral("below=true\nbelowrule=%1").arg(ruleNumber); + QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(c->desktop(), 1); + QCOMPARE(c->isMinimized(), false); + QCOMPARE(c->isActive(), true); + QCOMPARE(c->skipTaskbar(), false); + QCOMPARE(c->skipPager(), false); + QCOMPARE(c->skipSwitcher(), false); + QCOMPARE(c->keepAbove(), false); + QCOMPARE(c->keepBelow(), true); + QCOMPARE(c->shortcut(), QKeySequence()); +} + +TEST_DATA(testApplyInitialShortcut) + +void TestShellClientRules::testApplyInitialShortcut() +{ + // install the temporary rule + QFETCH(int, ruleNumber); + const QKeySequence sequence{Qt::ControlModifier + Qt::ShiftModifier + Qt::MetaModifier + Qt::AltModifier + Qt::Key_Space}; + QString rule = QStringLiteral("shortcut=%1\nshortcutrule=%2").arg(sequence.toString()).arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktop(), 1); - QCOMPARE(c->isMinimized(), true); - QCOMPARE(c->isActive(), false); - c->setMinimized(false); QCOMPARE(c->isMinimized(), false); + QCOMPARE(c->isActive(), true); + QCOMPARE(c->skipTaskbar(), false); + QCOMPARE(c->skipPager(), false); + QCOMPARE(c->skipSwitcher(), false); + QCOMPARE(c->keepAbove(), false); + QCOMPARE(c->keepBelow(), false); + QCOMPARE(c->shortcut(), sequence); } WAYLANDTEST_MAIN(TestShellClientRules) #include "shell_client_rules_test.moc" diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp index b31a642d7..adfe9bb53 100644 --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -1,863 +1,986 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "effects.h" #include "platform.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" +#include + #include #include #include #include #include #include #include +#include #include #include #include + // system #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_shell_client-0"); class TestShellClient : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMapUnmapMap_data(); void testMapUnmapMap(); void testDesktopPresenceChanged(); void testTransientPositionAfterRemap(); void testWindowOutputs_data(); void testWindowOutputs(); void testMinimizeActiveWindow_data(); void testMinimizeActiveWindow(); void testFullscreen_data(); void testFullscreen(); + void testUserCanSetFullscreen_data(); + void testUserCanSetFullscreen(); + void testUserSetFullscreenWlShell(); + void testUserSetFullscreenXdgShell_data(); + void testUserSetFullscreenXdgShell(); void testMaximizedToFullscreen_data(); void testMaximizedToFullscreen(); void testWindowOpensLargerThanScreen_data(); void testWindowOpensLargerThanScreen(); void testHidden_data(); void testHidden(); void testDesktopFileName(); void testCaptionSimplified(); void testCaptionMultipleWindows(); void testUnresponsiveWindow_data(); void testUnresponsiveWindow(); void testX11WindowId_data(); void testX11WindowId(); + void testAppMenu(); }; void TestShellClient::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TestShellClient::init() { - QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration | + Test::AdditionalWaylandInterface::AppMenu)); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(1280, 512)); } void TestShellClient::cleanup() { Test::destroyWaylandConnection(); } void TestShellClient::testMapUnmapMap_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testMapUnmapMap() { // this test verifies that mapping a previously mapped window works correctly QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QSignalSpy effectsWindowShownSpy(effects, &EffectsHandler::windowShown); QVERIFY(effectsWindowShownSpy.isValid()); QSignalSpy effectsWindowHiddenSpy(effects, &EffectsHandler::windowHidden); QVERIFY(effectsWindowHiddenSpy.isValid()); QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); // now let's render Test::render(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(clientAddedSpy.isEmpty()); QVERIFY(clientAddedSpy.wait()); auto client = clientAddedSpy.first().first().value(); QVERIFY(client); QVERIFY(client->isShown(true)); QCOMPARE(client->isHiddenInternal(), false); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->depth(), 32); QVERIFY(client->hasAlpha()); QCOMPARE(workspace()->activeClient(), client); QVERIFY(effectsWindowShownSpy.isEmpty()); QVERIFY(client->isMaximizable()); QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QVERIFY(client->isResizable()); QVERIFY(client->property("maximizable").toBool()); QVERIFY(client->property("moveable").toBool()); QVERIFY(client->property("moveableAcrossScreens").toBool()); QVERIFY(client->property("resizeable").toBool()); // now unmap QSignalSpy hiddenSpy(client, &ShellClient::windowHidden); QVERIFY(hiddenSpy.isValid()); QSignalSpy windowClosedSpy(client, &ShellClient::windowClosed); QVERIFY(windowClosedSpy.isValid()); surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(hiddenSpy.wait()); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), true); QVERIFY(windowClosedSpy.isEmpty()); QVERIFY(!workspace()->activeClient()); QCOMPARE(effectsWindowHiddenSpy.count(), 1); QCOMPARE(effectsWindowHiddenSpy.first().first().value(), client->effectWindow()); QSignalSpy windowShownSpy(client, &ShellClient::windowShown); QVERIFY(windowShownSpy.isValid()); Test::render(surface.data(), QSize(100, 50), Qt::blue, QImage::Format_RGB32); QCOMPARE(clientAddedSpy.count(), 1); QVERIFY(windowShownSpy.wait()); QCOMPARE(windowShownSpy.count(), 1); QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), false); QCOMPARE(client->depth(), 24); QVERIFY(!client->hasAlpha()); QCOMPARE(workspace()->activeClient(), client); QCOMPARE(effectsWindowShownSpy.count(), 1); QCOMPARE(effectsWindowShownSpy.first().first().value(), client->effectWindow()); // let's unmap again surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(hiddenSpy.wait()); QCOMPARE(hiddenSpy.count(), 2); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), true); QVERIFY(windowClosedSpy.isEmpty()); QCOMPARE(effectsWindowHiddenSpy.count(), 2); QCOMPARE(effectsWindowHiddenSpy.last().first().value(), client->effectWindow()); shellSurface.reset(); surface.reset(); QVERIFY(windowClosedSpy.wait()); QCOMPARE(windowClosedSpy.count(), 1); QCOMPARE(effectsWindowHiddenSpy.count(), 2); } void TestShellClient::testDesktopPresenceChanged() { // this test verifies that the desktop presence changed signals are properly emitted QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktop(), 1); effects->setNumberOfDesktops(4); QSignalSpy desktopPresenceChangedClientSpy(c, &ShellClient::desktopPresenceChanged); QVERIFY(desktopPresenceChangedClientSpy.isValid()); QSignalSpy desktopPresenceChangedWorkspaceSpy(workspace(), &Workspace::desktopPresenceChanged); QVERIFY(desktopPresenceChangedWorkspaceSpy.isValid()); QSignalSpy desktopPresenceChangedEffectsSpy(effects, &EffectsHandler::desktopPresenceChanged); QVERIFY(desktopPresenceChangedEffectsSpy.isValid()); // let's change the desktop workspace()->sendClientToDesktop(c, 2, false); QCOMPARE(c->desktop(), 2); QCOMPARE(desktopPresenceChangedClientSpy.count(), 1); QCOMPARE(desktopPresenceChangedWorkspaceSpy.count(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.count(), 1); // verify the arguments QCOMPARE(desktopPresenceChangedClientSpy.first().at(0).value(), c); QCOMPARE(desktopPresenceChangedClientSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(0).value(), c); QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(0).value(), c->effectWindow()); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(2).toInt(), 2); } void TestShellClient::testTransientPositionAfterRemap() { // this test simulates the situation that a transient window gets reused and the parent window // moved between the two usages QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); // create the Transient window QScopedPointer transientSurface(Test::createSurface()); QScopedPointer transientShellSurface(Test::createShellSurface(transientSurface.data())); transientShellSurface->setTransient(surface.data(), QPoint(5, 10)); auto transient = Test::renderAndWaitForShown(transientSurface.data(), QSize(50, 40), Qt::blue); QVERIFY(transient); QCOMPARE(transient->geometry(), QRect(c->geometry().topLeft() + QPoint(5, 10), QSize(50, 40))); // unmap the transient QSignalSpy windowHiddenSpy(transient, &ShellClient::windowHidden); QVERIFY(windowHiddenSpy.isValid()); transientSurface->attachBuffer(Buffer::Ptr()); transientSurface->commit(Surface::CommitFlag::None); QVERIFY(windowHiddenSpy.wait()); // now move the parent surface c->setGeometry(c->geometry().translated(5, 10)); // now map the transient again QSignalSpy windowShownSpy(transient, &ShellClient::windowShown); QVERIFY(windowShownSpy.isValid()); Test::render(transientSurface.data(), QSize(50, 40), Qt::blue); QVERIFY(windowShownSpy.wait()); QCOMPARE(transient->geometry(), QRect(c->geometry().topLeft() + QPoint(5, 10), QSize(50, 40))); } void TestShellClient::testWindowOutputs_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testWindowOutputs() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto size = QSize(200,200); QSignalSpy outputEnteredSpy(surface.data(), &Surface::outputEntered); QSignalSpy outputLeftSpy(surface.data(), &Surface::outputLeft); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue); //move to be in the first screen c->setGeometry(QRect(QPoint(100,100), size)); //we don't don't know where the compositor first placed this window, //this might fire, it might not outputEnteredSpy.wait(5); outputEnteredSpy.clear(); QCOMPARE(surface->outputs().count(), 1); QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(0,0)); //move to overlapping both first and second screen c->setGeometry(QRect(QPoint(1250,100), size)); QVERIFY(outputEnteredSpy.wait()); QCOMPARE(outputEnteredSpy.count(), 1); QCOMPARE(outputLeftSpy.count(), 0); QCOMPARE(surface->outputs().count(), 2); QVERIFY(surface->outputs()[0] != surface->outputs()[1]); //move entirely into second screen c->setGeometry(QRect(QPoint(1400,100), size)); QVERIFY(outputLeftSpy.wait()); QCOMPARE(outputEnteredSpy.count(), 1); QCOMPARE(outputLeftSpy.count(), 1); QCOMPARE(surface->outputs().count(), 1); QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(1280,0)); } void TestShellClient::testMinimizeActiveWindow_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testMinimizeActiveWindow() { // this test verifies that when minimizing the active window it gets deactivated QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); workspace()->slotWindowMinimize(); QVERIFY(!c->isShown(true)); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(!c->isActive()); QVERIFY(!workspace()->activeClient()); QVERIFY(c->isMinimized()); // unminimize again c->unminimize(); QVERIFY(!c->isMinimized()); QVERIFY(c->isActive()); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); QCOMPARE(workspace()->activeClient(), c); } void TestShellClient::testFullscreen_data() { QTest::addColumn("type"); QTest::addColumn("decoMode"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV6 - deco") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Server; } void TestShellClient::testFullscreen() { // this test verifies that a window can be properly fullscreened QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->layer(), NormalLayer); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QCOMPARE(c->sizeForClientSize(c->clientSize()), c->geometry().size()); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); // fullscreen the window switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setFullscreen(); break; case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: qobject_cast(shellSurface.data())->setFullscreen(true); break; default: Q_UNREACHABLE(); break; } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); QCOMPARE(sizeChangeRequestedSpy.first().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QVERIFY(geometryChangedSpy.isEmpty()); // render at the new size Test::render(surface.data(), sizeChangeRequestedSpy.first().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 1); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.first().first().toSize())); QCOMPARE(c->layer(), ActiveLayer); // swap back to normal switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setToplevel(); break; case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: qobject_cast(shellSurface.data())->setFullscreen(false); break; default: Q_UNREACHABLE(); break; } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->layer(), NormalLayer); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } +void TestShellClient::testUserCanSetFullscreen_data() +{ + QTest::addColumn("type"); + QTest::addColumn("expected"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << false; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << true; + QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << true; +} + +void TestShellClient::testUserCanSetFullscreen() +{ + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QVERIFY(c->isActive()); + QVERIFY(!c->isFullScreen()); + QTEST(c->userCanSetFullScreen(), "expected"); +} + +void TestShellClient::testUserSetFullscreenWlShell() +{ + // wlshell cannot sync fullscreen to the client + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createShellSurface(surface.data())); + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QVERIFY(c->isActive()); + QVERIFY(!c->isFullScreen()); + QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); + QVERIFY(fullscreenChangedSpy.isValid()); + c->setFullScreen(true); + QCOMPARE(fullscreenChangedSpy.count(), 0); + QVERIFY(!c->isFullScreen()); +} + +void TestShellClient::testUserSetFullscreenXdgShell_data() +{ + QTest::addColumn("type"); + + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; +} + +void TestShellClient::testUserSetFullscreenXdgShell() +{ + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(dynamic_cast(Test::createShellSurface(type, surface.data()))); + QVERIFY(!shellSurface.isNull()); + QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); + QVERIFY(configureRequestedSpy.isValid()); + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QVERIFY(c->isActive()); + QVERIFY(!c->isFullScreen()); + + // two, one for initial sync, second as it becomes active + QTRY_COMPARE(configureRequestedSpy.count(), 2); + + QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); + QVERIFY(fullscreenChangedSpy.isValid()); + c->setFullScreen(true); + QCOMPARE(c->isFullScreen(), true); + configureRequestedSpy.clear(); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 1); + QCOMPARE(configureRequestedSpy.first().at(0).toSize(), screens()->size(0)); + const auto states = configureRequestedSpy.first().at(1).value(); + QVERIFY(states.testFlag(KWayland::Client::XdgShellSurface::State::Fullscreen)); + QVERIFY(states.testFlag(KWayland::Client::XdgShellSurface::State::Activated)); + QVERIFY(!states.testFlag(KWayland::Client::XdgShellSurface::State::Maximized)); + QVERIFY(!states.testFlag(KWayland::Client::XdgShellSurface::State::Resizing)); + QCOMPARE(fullscreenChangedSpy.count(), 1); + QVERIFY(c->isFullScreen()); + + shellSurface->ackConfigure(configureRequestedSpy.first().at(2).value()); + + // unset fullscreen again + c->setFullScreen(false); + QCOMPARE(c->isFullScreen(), false); + configureRequestedSpy.clear(); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 1); + QCOMPARE(configureRequestedSpy.first().at(0).toSize(), QSize(100, 50)); + QVERIFY(!configureRequestedSpy.first().at(1).value().testFlag(KWayland::Client::XdgShellSurface::State::Fullscreen)); + QCOMPARE(fullscreenChangedSpy.count(), 2); + QVERIFY(!c->isFullScreen()); +} void TestShellClient::testMaximizedToFullscreen_data() { QTest::addColumn("type"); QTest::addColumn("decoMode"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV6 - deco") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Server; } void TestShellClient::testMaximizedToFullscreen() { // this test verifies that a window can be properly fullscreened after maximizing QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); // change to maximize switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setMaximized(); break; case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: qobject_cast(shellSurface.data())->setMaximized(true); break; default: Q_UNREACHABLE(); break; } QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(geometryChangedSpy.isEmpty(), false); geometryChangedSpy.clear(); // fullscreen the window switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setFullscreen(); break; case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: qobject_cast(shellSurface.data())->setFullscreen(true); break; default: Q_UNREACHABLE(); break; } QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QVERIFY(geometryChangedSpy.isEmpty()); // render at the new size Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 1); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.last().first().toSize())); sizeChangeRequestedSpy.clear(); // swap back to normal switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setToplevel(); break; case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: qobject_cast(shellSurface.data())->setFullscreen(false); break; default: Q_UNREACHABLE(); break; } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); QEXPECT_FAIL("wlShell - deco", "With decoration incorrect geometry requested", Continue); QEXPECT_FAIL("xdgShellV5 - deco", "With decoration incorrect geometry requested", Continue); QEXPECT_FAIL("xdgShellV6 - deco", "With decoration incorrect geometry requested", Continue); QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } void TestShellClient::testWindowOpensLargerThanScreen_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testWindowOpensLargerThanScreen() { // this test creates a window which is as large as the screen, but is decorated // the window should get resized to fit into the screen, BUG: 366632 QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), ServerSideDecoration::Mode::Server); auto c = Test::renderAndWaitForShown(surface.data(), screens()->size(0), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->clientSize(), screens()->size(0)); QVERIFY(c->isDecorated()); QEXPECT_FAIL("", "BUG 366632", Continue); QVERIFY(sizeChangeRequestedSpy.wait()); } void TestShellClient::testHidden_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testHidden() { // this test verifies that when hiding window it doesn't get shown QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); c->hideClient(true); QVERIFY(!c->isShown(true)); QVERIFY(!c->isActive()); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); // unhide again c->hideClient(false); QVERIFY(c->isShown(true)); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); //QCOMPARE(workspace()->activeClient(), c); } void TestShellClient::testDesktopFileName() { QIcon::setThemeName(QStringLiteral("breeze")); // this test verifies that desktop file name is passed correctly to the window QScopedPointer surface(Test::createSurface()); // only xdg-shell as ShellSurface misses the setter QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); shellSurface->setAppId(QByteArrayLiteral("org.kde.foo")); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.foo")); // the desktop file does not exist, so icon should be generic Wayland QCOMPARE(c->icon().name(), QStringLiteral("wayland")); QSignalSpy desktopFileNameChangedSpy(c, &AbstractClient::desktopFileNameChanged); QVERIFY(desktopFileNameChangedSpy.isValid()); QSignalSpy iconChangedSpy(c, &ShellClient::iconChanged); QVERIFY(iconChangedSpy.isValid()); shellSurface->setAppId(QByteArrayLiteral("org.kde.bar")); QVERIFY(desktopFileNameChangedSpy.wait()); QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.bar")); // icon should still be wayland QCOMPARE(c->icon().name(), QStringLiteral("wayland")); QVERIFY(iconChangedSpy.isEmpty()); const QString dfPath = QFINDTESTDATA("data/example.desktop"); shellSurface->setAppId(dfPath.toUtf8()); QVERIFY(desktopFileNameChangedSpy.wait()); QCOMPARE(iconChangedSpy.count(), 1); QCOMPARE(QString::fromUtf8(c->desktopFileName()), dfPath); QCOMPARE(c->icon().name(), QStringLiteral("kwin")); } void TestShellClient::testCaptionSimplified() { // this test verifies that caption is properly trimmed // see BUG 323798 comment #12 QScopedPointer surface(Test::createSurface()); // only done for xdg-shell as ShellSurface misses the setter QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); const QString origTitle = QString::fromUtf8(QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox")); shellSurface->setTitle(origTitle); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->caption() != origTitle); QCOMPARE(c->caption(), origTitle.simplified()); } void TestShellClient::testCaptionMultipleWindows() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); shellSurface->setTitle(QStringLiteral("foo")); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->caption(), QStringLiteral("foo")); QCOMPARE(c->captionNormal(), QStringLiteral("foo")); QCOMPARE(c->captionSuffix(), QString()); QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface2.data()))); shellSurface2->setTitle(QStringLiteral("foo")); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::blue); QVERIFY(c2); QCOMPARE(c2->caption(), QStringLiteral("foo <2>")); QCOMPARE(c2->captionNormal(), QStringLiteral("foo")); QCOMPARE(c2->captionSuffix(), QStringLiteral(" <2>")); QScopedPointer surface3(Test::createSurface()); QScopedPointer shellSurface3(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface3.data()))); shellSurface3->setTitle(QStringLiteral("foo")); auto c3 = Test::renderAndWaitForShown(surface3.data(), QSize(100, 50), Qt::blue); QVERIFY(c3); QCOMPARE(c3->caption(), QStringLiteral("foo <3>")); QCOMPARE(c3->captionNormal(), QStringLiteral("foo")); QCOMPARE(c3->captionSuffix(), QStringLiteral(" <3>")); QScopedPointer surface4(Test::createSurface()); QScopedPointer shellSurface4(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface4.data()))); shellSurface4->setTitle(QStringLiteral("bar")); auto c4 = Test::renderAndWaitForShown(surface4.data(), QSize(100, 50), Qt::blue); QVERIFY(c4); QCOMPARE(c4->caption(), QStringLiteral("bar")); QCOMPARE(c4->captionNormal(), QStringLiteral("bar")); QCOMPARE(c4->captionSuffix(), QString()); QSignalSpy captionChangedSpy(c4, &ShellClient::captionChanged); QVERIFY(captionChangedSpy.isValid()); shellSurface4->setTitle(QStringLiteral("foo")); QVERIFY(captionChangedSpy.wait()); QCOMPARE(captionChangedSpy.count(), 1); QCOMPARE(c4->caption(), QStringLiteral("foo <4>")); QCOMPARE(c4->captionNormal(), QStringLiteral("foo")); QCOMPARE(c4->captionSuffix(), QStringLiteral(" <4>")); } void TestShellClient::testUnresponsiveWindow_data() { QTest::addColumn("shellInterface");//see env selection in qwaylandintegration.cpp QTest::addColumn("socketMode"); //wl-shell ping is not implemented //QTest::newRow("wl-shell display") << "wl-shell" << false; //QTest::newRow("wl-shell socket") << "wl-shell" << true; QTest::newRow("xdgv5 display") << "xdg-shell-v5" << false; QTest::newRow("xdgv5 socket") << "xdg-shell-v5" << true; QTest::newRow("xdgv6 display") << "xdg-shell-v6" << false; QTest::newRow("xdgv6 socket") << "xdg-shell-v6" << true; } void TestShellClient::testUnresponsiveWindow() { // this test verifies that killWindow properly terminates a process // for this an external binary is launched - const QString kill = QFINDTESTDATA(QStringLiteral("helper/kill")); + const QString kill = QFINDTESTDATA(QStringLiteral("kill")); QVERIFY(!kill.isEmpty()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); QScopedPointer process(new QProcess); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QFETCH(QString, shellInterface); QFETCH(bool, socketMode); env.insert("QT_WAYLAND_SHELL_INTEGRATION", shellInterface); if (socketMode) { int sx[2]; QVERIFY(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) >= 0); waylandServer()->display()->createClient(sx[0]); int socket = dup(sx[1]); QVERIFY(socket != -1); env.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); env.remove("WAYLAND_DISPLAY"); } else { env.insert("WAYLAND_DISPLAY", s_socketName); } process->setProcessEnvironment(env); process->setProcessChannelMode(QProcess::ForwardedChannels); process->setProgram(kill); process->start(); QVERIFY(process->waitForStarted()); AbstractClient *killClient = nullptr; QVERIFY(shellClientAddedSpy.wait()); killClient = shellClientAddedSpy.first().first().value(); QSignalSpy unresponsiveSpy(killClient, &AbstractClient::unresponsiveChanged); QSignalSpy killedSpy(process.data(), static_cast(&QProcess::finished)); QSignalSpy deletedSpy(killClient, &QObject::destroyed); qint64 startTime = QDateTime::currentMSecsSinceEpoch(); //wait for the process to be frozen QTest::qWait(10); //pretend the user clicked the close button killClient->closeWindow(); //client should not yet be marked unresponsive nor killed QVERIFY(!killClient->unresponsive()); QVERIFY(killedSpy.isEmpty()); QVERIFY(unresponsiveSpy.wait()); //client should be marked unresponsive but not killed auto elapsed1 = QDateTime::currentMSecsSinceEpoch() - startTime; QVERIFY(elapsed1 > 900 && elapsed1 < 1200); //ping timer is 1s, but coarse timers on a test across two processes means we need a fuzzy compare QVERIFY(killClient->unresponsive()); QVERIFY(killedSpy.isEmpty()); QVERIFY(deletedSpy.wait()); if (!socketMode) { //process was killed - because we're across process this could happen in either order QVERIFY(killedSpy.count() || killedSpy.wait()); } auto elapsed2 = QDateTime::currentMSecsSinceEpoch() - startTime; QVERIFY(elapsed2 > 1800); //second ping comes in a second later } void TestShellClient::testX11WindowId_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testX11WindowId() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->windowId() != 0); QCOMPARE(c->window(), 0u); } +void TestShellClient::testAppMenu() +{ + //register a faux appmenu client + QVERIFY (QDBusConnection::sessionBus().registerService("org.kde.kappmenu")); + + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV6, surface.data())); + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QScopedPointer menu(Test::waylandAppMenuManager()->create(surface.data())); + QSignalSpy spy(c, &ShellClient::hasApplicationMenuChanged); + menu->setAddress("service.name", "object/path"); + spy.wait(); + QCOMPARE(c->hasApplicationMenu(), true); + QCOMPARE(c->applicationMenuServiceName(), QString("service.name")); + QCOMPARE(c->applicationMenuObjectPath(), QString("object/path")); + + QVERIFY (QDBusConnection::sessionBus().unregisterService("org.kde.kappmenu")); +} + + WAYLANDTEST_MAIN(TestShellClient) #include "shell_client_test.moc" diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index 05c8dc990..a94f7e8d8 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -1,508 +1,539 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "shell_client.h" #include "screenlockerwatcher.h" #include "wayland_server.h" #include #include #include +#include #include #include #include #include #include #include #include #include #include #include +#include #include #include //screenlocker #include #include // system #include #include #include using namespace KWayland::Client; namespace KWin { namespace Test { static struct { ConnectionThread *connection = nullptr; EventQueue *queue = nullptr; Compositor *compositor = nullptr; ServerSideDecorationManager *decoration = nullptr; Shell *shell = nullptr; XdgShell *xdgShellV5 = nullptr; XdgShell *xdgShellV6 = nullptr; ShmPool *shm = nullptr; Seat *seat = nullptr; PlasmaShell *plasmaShell = nullptr; PlasmaWindowManagement *windowManagement = nullptr; PointerConstraints *pointerConstraints = nullptr; Registry *registry = nullptr; QThread *thread = nullptr; QVector outputs; + IdleInhibitManager *idleInhibit = nullptr; + AppMenuManager *appMenu = nullptr; } s_waylandConnection; bool setupWaylandConnection(AdditionalWaylandInterfaces flags) { if (s_waylandConnection.connection) { return false; } int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { return false; } KWin::waylandServer()->display()->createClient(sx[0]); // setup connection s_waylandConnection.connection = new ConnectionThread; QSignalSpy connectedSpy(s_waylandConnection.connection, &ConnectionThread::connected); if (!connectedSpy.isValid()) { return false; } s_waylandConnection.connection->setSocketFd(sx[1]); s_waylandConnection.thread = new QThread(kwinApp()); s_waylandConnection.connection->moveToThread(s_waylandConnection.thread); s_waylandConnection.thread->start(); s_waylandConnection.connection->initConnection(); if (!connectedSpy.wait()) { return false; } s_waylandConnection.queue = new EventQueue; s_waylandConnection.queue->setup(s_waylandConnection.connection); if (!s_waylandConnection.queue->isValid()) { return false; } Registry *registry = new Registry; s_waylandConnection.registry = registry; registry->setEventQueue(s_waylandConnection.queue); QObject::connect(registry, &Registry::outputAnnounced, [=](quint32 name, quint32 version) { auto output = registry->createOutput(name, version, s_waylandConnection.registry); s_waylandConnection.outputs << output; QObject::connect(output, &Output::removed, [=]() { output->deleteLater(); s_waylandConnection.outputs.removeOne(output); }); }); QSignalSpy allAnnounced(registry, &Registry::interfacesAnnounced); if (!allAnnounced.isValid()) { return false; } registry->create(s_waylandConnection.connection); if (!registry->isValid()) { return false; } registry->setup(); if (!allAnnounced.wait()) { return false; } s_waylandConnection.compositor = registry->createCompositor(registry->interface(Registry::Interface::Compositor).name, registry->interface(Registry::Interface::Compositor).version); if (!s_waylandConnection.compositor->isValid()) { return false; } s_waylandConnection.shm = registry->createShmPool(registry->interface(Registry::Interface::Shm).name, registry->interface(Registry::Interface::Shm).version); if (!s_waylandConnection.shm->isValid()) { return false; } s_waylandConnection.shell = registry->createShell(registry->interface(Registry::Interface::Shell).name, registry->interface(Registry::Interface::Shell).version); if (!s_waylandConnection.shell->isValid()) { return false; } s_waylandConnection.xdgShellV5 = registry->createXdgShell(registry->interface(Registry::Interface::XdgShellUnstableV5).name, registry->interface(Registry::Interface::XdgShellUnstableV5).version); if (!s_waylandConnection.xdgShellV5->isValid()) { return false; } s_waylandConnection.xdgShellV6 = registry->createXdgShell(registry->interface(Registry::Interface::XdgShellUnstableV6).name, registry->interface(Registry::Interface::XdgShellUnstableV6).version); if (!s_waylandConnection.xdgShellV6->isValid()) { return false; } if (flags.testFlag(AdditionalWaylandInterface::Seat)) { s_waylandConnection.seat = registry->createSeat(registry->interface(Registry::Interface::Seat).name, registry->interface(Registry::Interface::Seat).version); if (!s_waylandConnection.seat->isValid()) { return false; } } if (flags.testFlag(AdditionalWaylandInterface::Decoration)) { s_waylandConnection.decoration = registry->createServerSideDecorationManager(registry->interface(Registry::Interface::ServerSideDecorationManager).name, registry->interface(Registry::Interface::ServerSideDecorationManager).version); if (!s_waylandConnection.decoration->isValid()) { return false; } } if (flags.testFlag(AdditionalWaylandInterface::PlasmaShell)) { s_waylandConnection.plasmaShell = registry->createPlasmaShell(registry->interface(Registry::Interface::PlasmaShell).name, registry->interface(Registry::Interface::PlasmaShell).version); if (!s_waylandConnection.plasmaShell->isValid()) { return false; } } if (flags.testFlag(AdditionalWaylandInterface::WindowManagement)) { s_waylandConnection.windowManagement = registry->createPlasmaWindowManagement(registry->interface(Registry::Interface::PlasmaWindowManagement).name, registry->interface(Registry::Interface::PlasmaWindowManagement).version); if (!s_waylandConnection.windowManagement->isValid()) { return false; } } if (flags.testFlag(AdditionalWaylandInterface::PointerConstraints)) { s_waylandConnection.pointerConstraints = registry->createPointerConstraints(registry->interface(Registry::Interface::PointerConstraintsUnstableV1).name, registry->interface(Registry::Interface::PointerConstraintsUnstableV1).version); if (!s_waylandConnection.pointerConstraints->isValid()) { return false; } } + if (flags.testFlag(AdditionalWaylandInterface::IdleInhibition)) { + s_waylandConnection.idleInhibit = registry->createIdleInhibitManager(registry->interface(Registry::Interface::IdleInhibitManagerUnstableV1).name, + registry->interface(Registry::Interface::IdleInhibitManagerUnstableV1).version); + if (!s_waylandConnection.idleInhibit->isValid()) { + return false; + } + } + if (flags.testFlag(AdditionalWaylandInterface::AppMenu)) { + s_waylandConnection.appMenu = registry->createAppMenuManager(registry->interface(Registry::Interface::AppMenu).name, registry->interface(Registry::Interface::AppMenu).version); + if (!s_waylandConnection.appMenu->isValid()) { + return false; + } + } return true; } void destroyWaylandConnection() { delete s_waylandConnection.compositor; s_waylandConnection.compositor = nullptr; delete s_waylandConnection.windowManagement; s_waylandConnection.windowManagement = nullptr; delete s_waylandConnection.plasmaShell; s_waylandConnection.plasmaShell = nullptr; delete s_waylandConnection.decoration; s_waylandConnection.decoration = nullptr; delete s_waylandConnection.decoration; s_waylandConnection.decoration = nullptr; delete s_waylandConnection.seat; s_waylandConnection.seat = nullptr; delete s_waylandConnection.pointerConstraints; s_waylandConnection.pointerConstraints = nullptr; delete s_waylandConnection.xdgShellV5; s_waylandConnection.xdgShellV5 = nullptr; delete s_waylandConnection.xdgShellV6; s_waylandConnection.xdgShellV6 = nullptr; delete s_waylandConnection.shell; s_waylandConnection.shell = nullptr; + delete s_waylandConnection.idleInhibit; + s_waylandConnection.idleInhibit = nullptr; delete s_waylandConnection.shm; s_waylandConnection.shm = nullptr; delete s_waylandConnection.queue; s_waylandConnection.queue = nullptr; delete s_waylandConnection.registry; s_waylandConnection.registry = nullptr; + delete s_waylandConnection.appMenu; + s_waylandConnection.appMenu = nullptr; if (s_waylandConnection.thread) { QSignalSpy spy(s_waylandConnection.connection, &QObject::destroyed); s_waylandConnection.connection->deleteLater(); if (spy.isEmpty()) { QVERIFY(spy.wait()); } s_waylandConnection.thread->quit(); s_waylandConnection.thread->wait(); delete s_waylandConnection.thread; s_waylandConnection.thread = nullptr; s_waylandConnection.connection = nullptr; } } ConnectionThread *waylandConnection() { return s_waylandConnection.connection; } Compositor *waylandCompositor() { return s_waylandConnection.compositor; } Shell *waylandShell() { return s_waylandConnection.shell; } ShmPool *waylandShmPool() { return s_waylandConnection.shm; } Seat *waylandSeat() { return s_waylandConnection.seat; } ServerSideDecorationManager *waylandServerSideDecoration() { return s_waylandConnection.decoration; } PlasmaShell *waylandPlasmaShell() { return s_waylandConnection.plasmaShell; } PlasmaWindowManagement *waylandWindowManagement() { return s_waylandConnection.windowManagement; } PointerConstraints *waylandPointerConstraints() { return s_waylandConnection.pointerConstraints; } +IdleInhibitManager *waylandIdleInhibitManager() +{ + return s_waylandConnection.idleInhibit; +} + +AppMenuManager* waylandAppMenuManager() +{ + return s_waylandConnection.appMenu; +} + bool waitForWaylandPointer() { if (!s_waylandConnection.seat) { return false; } QSignalSpy hasPointerSpy(s_waylandConnection.seat, &Seat::hasPointerChanged); if (!hasPointerSpy.isValid()) { return false; } return hasPointerSpy.wait(); } bool waitForWaylandTouch() { if (!s_waylandConnection.seat) { return false; } QSignalSpy hasTouchSpy(s_waylandConnection.seat, &Seat::hasTouchChanged); if (!hasTouchSpy.isValid()) { return false; } return hasTouchSpy.wait(); } bool waitForWaylandKeyboard() { if (!s_waylandConnection.seat) { return false; } QSignalSpy hasKeyboardSpy(s_waylandConnection.seat, &Seat::hasKeyboardChanged); if (!hasKeyboardSpy.isValid()) { return false; } return hasKeyboardSpy.wait(); } void render(Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format) { QImage img(size, format); img.fill(color); render(surface, img); } void render(Surface *surface, const QImage &img) { surface->attachBuffer(s_waylandConnection.shm->createBuffer(img)); surface->damage(QRect(QPoint(0, 0), img.size())); surface->commit(Surface::CommitFlag::None); } ShellClient *waitForWaylandWindowShown(int timeout) { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); if (!clientAddedSpy.isValid()) { return nullptr; } if (!clientAddedSpy.wait(timeout)) { return nullptr; } return clientAddedSpy.first().first().value(); } ShellClient *renderAndWaitForShown(Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format, int timeout) { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); if (!clientAddedSpy.isValid()) { return nullptr; } render(surface, size, color, format); flushWaylandConnection(); if (!clientAddedSpy.wait(timeout)) { return nullptr; } return clientAddedSpy.first().first().value(); } void flushWaylandConnection() { if (s_waylandConnection.connection) { s_waylandConnection.connection->flush(); } } Surface *createSurface(QObject *parent) { if (!s_waylandConnection.compositor) { return nullptr; } auto s = s_waylandConnection.compositor->createSurface(parent); if (!s->isValid()) { delete s; return nullptr; } return s; } ShellSurface *createShellSurface(Surface *surface, QObject *parent) { if (!s_waylandConnection.shell) { return nullptr; } auto s = s_waylandConnection.shell->createSurface(surface, parent); if (!s->isValid()) { delete s; return nullptr; } return s; } XdgShellSurface *createXdgShellV5Surface(Surface *surface, QObject *parent) { if (!s_waylandConnection.xdgShellV5) { return nullptr; } auto s = s_waylandConnection.xdgShellV5->createSurface(surface, parent); if (!s->isValid()) { delete s; return nullptr; } return s; } XdgShellSurface *createXdgShellV6Surface(Surface *surface, QObject *parent) { if (!s_waylandConnection.xdgShellV6) { return nullptr; } auto s = s_waylandConnection.xdgShellV6->createSurface(surface, parent); if (!s->isValid()) { delete s; return nullptr; } return s; } QObject *createShellSurface(ShellSurfaceType type, KWayland::Client::Surface *surface, QObject *parent) { switch (type) { case ShellSurfaceType::WlShell: return createShellSurface(surface, parent); case ShellSurfaceType::XdgShellV5: return createXdgShellV5Surface(surface, parent); case ShellSurfaceType::XdgShellV6: return createXdgShellV6Surface(surface, parent); default: Q_UNREACHABLE(); return nullptr; } } bool waitForWindowDestroyed(AbstractClient *client) { QSignalSpy destroyedSpy(client, &QObject::destroyed); if (!destroyedSpy.isValid()) { return false; } return destroyedSpy.wait(); } bool lockScreen() { if (waylandServer()->isScreenLocked()) { return false; } QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); if (!lockStateChangedSpy.isValid()) { return false; } ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); if (lockStateChangedSpy.count() != 1) { return false; } if (!waylandServer()->isScreenLocked()) { return false; } if (!ScreenLockerWatcher::self()->isLocked()) { QSignalSpy lockedSpy(ScreenLockerWatcher::self(), &ScreenLockerWatcher::locked); if (!lockedSpy.isValid()) { return false; } if (!lockedSpy.wait()) { return false; } if (!ScreenLockerWatcher::self()->isLocked()) { return false; } } return true; } bool unlockScreen() { QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); if (!lockStateChangedSpy.isValid()) { return false; } using namespace ScreenLocker; const auto children = KSldApp::self()->children(); for (auto it = children.begin(); it != children.end(); ++it) { if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) { continue; } QMetaObject::invokeMethod(*it, "requestUnlock"); break; } if (waylandServer()->isScreenLocked()) { lockStateChangedSpy.wait(); } if (waylandServer()->isScreenLocked()) { return true; } if (ScreenLockerWatcher::self()->isLocked()) { QSignalSpy lockedSpy(ScreenLockerWatcher::self(), &ScreenLockerWatcher::locked); if (!lockedSpy.isValid()) { return false; } if (!lockedSpy.wait()) { return false; } if (ScreenLockerWatcher::self()->isLocked()) { return false; } } return true; } } } diff --git a/autotests/integration/x11_client_test.cpp b/autotests/integration/x11_client_test.cpp index 49187e55b..f718e7bfb 100644 --- a/autotests/integration/x11_client_test.cpp +++ b/autotests/integration/x11_client_test.cpp @@ -1,527 +1,604 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" +#include "atoms.h" #include "composite.h" #include "effects.h" #include "effectloader.h" #include "cursor.h" #include "platform.h" #include "screens.h" #include "shell_client.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_x11_client-0"); class X11ClientTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testCaptionSimplified(); void testFullscreenLayerWithActiveWaylandWindow(); void testFocusInWithWaylandLastActiveWindow(); void testX11WindowId(); void testCaptionChanges(); void testCaptionWmName(); void testCaptionMultipleWindows(); + void testFullscreenWindowGroups(); }; void X11ClientTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QVERIFY(KWin::Compositor::self()); waylandServer()->initWorkspace(); } void X11ClientTest::init() { QVERIFY(Test::setupWaylandConnection()); } void X11ClientTest::cleanup() { Test::destroyWaylandConnection(); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void X11ClientTest::testCaptionSimplified() { // this test verifies that caption is properly trimmed // see BUG 323798 comment #12 QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); QVERIFY(windowAddedSpy.isValid()); // create an xcb window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo winInfo(c.data(), w, rootWindow(), NET::Properties(), NET::Properties2()); const QByteArray origTitle = QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox"); winInfo.setName(origTitle.constData()); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QVERIFY(client->caption() != QString::fromUtf8(origTitle)); QCOMPARE(client->caption(), QString::fromUtf8(origTitle).simplified()); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); xcb_destroy_window(c.data(), w); c.reset(); } void X11ClientTest::testFullscreenLayerWithActiveWaylandWindow() { // this test verifies that an X11 fullscreen window does not stay in the active layer // when a Wayland window is active, see BUG: 375759 QCOMPARE(screens()->count(), 1); // first create an X11 window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QVERIFY(!client->isFullScreen()); QVERIFY(client->isActive()); QCOMPARE(client->layer(), NormalLayer); workspace()->slotWindowFullScreen(); QVERIFY(client->isFullScreen()); QCOMPARE(client->layer(), ActiveLayer); QCOMPARE(workspace()->stackingOrder().last(), client); // now let's open a Wayland window QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(waylandClient); QVERIFY(waylandClient->isActive()); QCOMPARE(waylandClient->layer(), NormalLayer); QCOMPARE(workspace()->stackingOrder().last(), waylandClient); QCOMPARE(workspace()->xStackingOrder().last(), waylandClient); QCOMPARE(client->layer(), NormalLayer); // now activate fullscreen again workspace()->activateClient(client); QTRY_VERIFY(client->isActive()); QCOMPARE(client->layer(), ActiveLayer); QCOMPARE(workspace()->stackingOrder().last(), client); QCOMPARE(workspace()->xStackingOrder().last(), client); // activate wayland window again workspace()->activateClient(waylandClient); QTRY_VERIFY(waylandClient->isActive()); QCOMPARE(workspace()->stackingOrder().last(), waylandClient); QCOMPARE(workspace()->xStackingOrder().last(), waylandClient); // back to x window workspace()->activateClient(client); QTRY_VERIFY(client->isActive()); // remove fullscreen QVERIFY(client->isFullScreen()); workspace()->slotWindowFullScreen(); QVERIFY(!client->isFullScreen()); // and fullscreen again workspace()->slotWindowFullScreen(); QVERIFY(client->isFullScreen()); QCOMPARE(workspace()->stackingOrder().last(), client); QCOMPARE(workspace()->xStackingOrder().last(), client); // activate wayland window again workspace()->activateClient(waylandClient); QTRY_VERIFY(waylandClient->isActive()); QCOMPARE(workspace()->stackingOrder().last(), waylandClient); QCOMPARE(workspace()->xStackingOrder().last(), waylandClient); // back to X11 window workspace()->activateClient(client); QTRY_VERIFY(client->isActive()); // remove fullscreen QVERIFY(client->isFullScreen()); workspace()->slotWindowFullScreen(); QVERIFY(!client->isFullScreen()); // and fullscreen through X API NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); info.setState(NET::FullScreen, NET::FullScreen); NETRootInfo rootInfo(c.data(), NET::Properties()); rootInfo.setActiveWindow(w, NET::FromApplication, XCB_CURRENT_TIME, XCB_WINDOW_NONE); xcb_flush(c.data()); QTRY_VERIFY(client->isFullScreen()); QCOMPARE(workspace()->stackingOrder().last(), client); QCOMPARE(workspace()->xStackingOrder().last(), client); // activate wayland window again workspace()->activateClient(waylandClient); QTRY_VERIFY(waylandClient->isActive()); QCOMPARE(workspace()->stackingOrder().last(), waylandClient); QCOMPARE(workspace()->xStackingOrder().last(), waylandClient); QCOMPARE(client->layer(), NormalLayer); // close the window shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(waylandClient)); QTRY_VERIFY(client->isActive()); QCOMPARE(client->layer(), ActiveLayer); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_flush(c.data()); } void X11ClientTest::testFocusInWithWaylandLastActiveWindow() { // this test verifies that Workspace::allowClientActivation does not crash if last client was a Wayland client // create an X11 window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QVERIFY(client->isActive()); // create Wayland window QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(waylandClient); QVERIFY(waylandClient->isActive()); // activate no window workspace()->setActiveClient(nullptr); QVERIFY(!waylandClient->isActive()); QVERIFY(!workspace()->activeClient()); // and close Wayland window again shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(waylandClient)); // and try to activate the x11 client through X11 api const auto cookie = xcb_set_input_focus_checked(c.data(), XCB_INPUT_FOCUS_NONE, w, XCB_CURRENT_TIME); auto error = xcb_request_check(c.data(), cookie); QVERIFY(!error); // this accesses last_active_client on trying to activate QTRY_VERIFY(client->isActive()); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_flush(c.data()); } void X11ClientTest::testX11WindowId() { // create an X11 window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->windowId(), w); QVERIFY(client->isActive()); QCOMPARE(client->window(), w); NETRootInfo rootInfo(c.data(), NET::WMAllProperties); QCOMPARE(rootInfo.activeWindow(), client->window()); // activate a wayland window QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(waylandClient); QVERIFY(waylandClient->isActive()); xcb_flush(kwinApp()->x11Connection()); NETRootInfo rootInfo2(c.data(), NET::WMAllProperties); QCOMPARE(rootInfo2.activeWindow(), 0u); // back to X11 client shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(waylandClient)); QTRY_VERIFY(client->isActive()); NETRootInfo rootInfo3(c.data(), NET::WMAllProperties); QCOMPARE(rootInfo3.activeWindow(), client->window()); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_flush(c.data()); } void X11ClientTest::testCaptionChanges() { // verifies that caption is updated correctly when the X11 window updates it // BUG: 383444 QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); info.setName("foo"); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->windowId(), w); QCOMPARE(client->caption(), QStringLiteral("foo")); QSignalSpy captionChangedSpy(client, &Client::captionChanged); QVERIFY(captionChangedSpy.isValid()); info.setName("bar"); xcb_flush(c.data()); QVERIFY(captionChangedSpy.wait()); QCOMPARE(client->caption(), QStringLiteral("bar")); // and destroy the window again QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); xcb_unmap_window(c.data(), w); xcb_flush(c.data()); QVERIFY(windowClosedSpy.wait()); xcb_destroy_window(c.data(), w); c.reset(); } void X11ClientTest::testCaptionWmName() { // this test verifies that a caption set through WM_NAME is read correctly // open glxgears as that one only uses WM_NAME QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded); QVERIFY(clientAddedSpy.isValid()); QProcess glxgears; glxgears.start(QStringLiteral("glxgears")); QVERIFY(glxgears.waitForStarted()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(workspace()->clientList().count(), 1); Client *glxgearsClient = workspace()->clientList().first(); QCOMPARE(glxgearsClient->caption(), QStringLiteral("glxgears")); glxgears.terminate(); QVERIFY(glxgears.waitForFinished()); } void X11ClientTest::testCaptionMultipleWindows() { // BUG 384760 // create first window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); info.setName("foo"); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->windowId(), w); QCOMPARE(client->caption(), QStringLiteral("foo")); // create second window with same caption xcb_window_t w2 = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w2, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_icccm_set_wm_normal_hints(c.data(), w2, &hints); NETWinInfo info2(c.data(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); info2.setName("foo"); info2.setIconName("foo"); xcb_map_window(c.data(), w2); xcb_flush(c.data()); windowCreatedSpy.clear(); QVERIFY(windowCreatedSpy.wait()); Client *client2 = windowCreatedSpy.first().first().value(); QVERIFY(client2); QCOMPARE(client2->windowId(), w2); QCOMPARE(client2->caption(), QStringLiteral("foo <2>\u200E")); NETWinInfo info3(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2()); QCOMPARE(QByteArray(info3.visibleName()), QByteArrayLiteral("foo <2>\u200E")); QCOMPARE(QByteArray(info3.visibleIconName()), QByteArrayLiteral("foo <2>\u200E")); QSignalSpy captionChangedSpy(client2, &Client::captionChanged); QVERIFY(captionChangedSpy.isValid()); NETWinInfo info4(c.data(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); info4.setName("foobar"); info4.setIconName("foobar"); xcb_map_window(c.data(), w2); xcb_flush(c.data()); QVERIFY(captionChangedSpy.wait()); QCOMPARE(client2->caption(), QStringLiteral("foobar")); NETWinInfo info5(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2()); QCOMPARE(QByteArray(info5.visibleName()), QByteArray()); QTRY_COMPARE(QByteArray(info5.visibleIconName()), QByteArray()); } + +void X11ClientTest::testFullscreenWindowGroups() +{ + // this test creates an X11 window and puts it to full screen + // then a second window is created which is in the same window group + // BUG: 388310 + + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t w = xcb_generate_id(c.data()); + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); + xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w); + xcb_map_window(c.data(), w); + xcb_flush(c.data()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); + QVERIFY(windowCreatedSpy.isValid()); + QVERIFY(windowCreatedSpy.wait()); + Client *client = windowCreatedSpy.first().first().value(); + QVERIFY(client); + QCOMPARE(client->windowId(), w); + QCOMPARE(client->isActive(), true); + + QCOMPARE(client->isFullScreen(), false); + QCOMPARE(client->layer(), NormalLayer); + workspace()->slotWindowFullScreen(); + QCOMPARE(client->isFullScreen(), true); + QCOMPARE(client->layer(), ActiveLayer); + + // now let's create a second window + windowCreatedSpy.clear(); + xcb_window_t w2 = xcb_generate_id(c.data()); + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w2, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints2; + memset(&hints2, 0, sizeof(hints2)); + xcb_icccm_size_hints_set_position(&hints2, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints2, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.data(), w2, &hints2); + xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w2, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w); + xcb_map_window(c.data(), w2); + xcb_flush(c.data()); + + QVERIFY(windowCreatedSpy.wait()); + Client *client2 = windowCreatedSpy.first().first().value(); + QVERIFY(client2); + QVERIFY(client != client2); + QCOMPARE(client2->windowId(), w2); + QCOMPARE(client2->isActive(), true); + QCOMPARE(client2->group(), client->group()); + // first client should be moved back to normal layer + QCOMPARE(client->isActive(), false); + QCOMPARE(client->isFullScreen(), true); + QCOMPARE(client->layer(), NormalLayer); + + // activating the fullscreen window again, should move it to active layer + workspace()->activateClient(client); + QTRY_COMPARE(client->layer(), ActiveLayer); +} + WAYLANDTEST_MAIN(X11ClientTest) #include "x11_client_test.moc" diff --git a/autotests/integration/xclipboardsync_test.cpp b/autotests/integration/xclipboardsync_test.cpp index d3300f6d9..212eee217 100644 --- a/autotests/integration/xclipboardsync_test.cpp +++ b/autotests/integration/xclipboardsync_test.cpp @@ -1,183 +1,183 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_kwin_xclipboard_sync-0"); class XClipboardSyncTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanup(); void testSync_data(); void testSync(); private: QProcess *m_copyProcess = nullptr; QProcess *m_pasteProcess = nullptr; }; void XClipboardSyncTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); // wait till the xclipboard sync data device is created while (waylandServer()->xclipboardSyncDataDevice().isNull()) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } QVERIFY(!waylandServer()->xclipboardSyncDataDevice().isNull()); } void XClipboardSyncTest::cleanup() { if (m_copyProcess) { m_copyProcess->terminate(); QVERIFY(m_copyProcess->waitForFinished()); m_copyProcess = nullptr; } if (m_pasteProcess) { m_pasteProcess->terminate(); QVERIFY(m_pasteProcess->waitForFinished()); m_pasteProcess = nullptr; } } void XClipboardSyncTest::testSync_data() { QTest::addColumn("copyPlatform"); QTest::addColumn("pastePlatform"); QTest::newRow("x11-wayland") << QStringLiteral("xcb") << QStringLiteral("wayland"); QTest::newRow("wayland-x11") << QStringLiteral("wayland") << QStringLiteral("xcb"); } void XClipboardSyncTest::testSync() { // this test verifies the syncing of X11 to Wayland clipboard - const QString copy = QFINDTESTDATA(QStringLiteral("helper/copy")); + const QString copy = QFINDTESTDATA(QStringLiteral("copy")); QVERIFY(!copy.isEmpty()); - const QString paste = QFINDTESTDATA(QStringLiteral("helper/paste")); + const QString paste = QFINDTESTDATA(QStringLiteral("paste")); QVERIFY(!paste.isEmpty()); QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded); QVERIFY(clientAddedSpy.isValid()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); QSignalSpy clipboardChangedSpy(waylandServer()->xclipboardSyncDataDevice().data(), &KWayland::Server::DataDeviceInterface::selectionChanged); QVERIFY(clipboardChangedSpy.isValid()); QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); QFETCH(QString, copyPlatform); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), copyPlatform); environment.insert(QStringLiteral("WAYLAND_DISPLAY"), s_socketName); m_copyProcess = new QProcess(); m_copyProcess->setProcessEnvironment(environment); m_copyProcess->setProcessChannelMode(QProcess::ForwardedChannels); m_copyProcess->setProgram(copy); m_copyProcess->start(); QVERIFY(m_copyProcess->waitForStarted()); AbstractClient *copyClient = nullptr; if (copyPlatform == QLatin1String("xcb")) { QVERIFY(clientAddedSpy.wait()); copyClient = clientAddedSpy.first().first().value(); } else { QVERIFY(shellClientAddedSpy.wait()); copyClient = shellClientAddedSpy.first().first().value(); } QVERIFY(copyClient); if (workspace()->activeClient() != copyClient) { workspace()->activateClient(copyClient); } QCOMPARE(workspace()->activeClient(), copyClient); if (copyPlatform == QLatin1String("xcb")) { QVERIFY(clipboardChangedSpy.isEmpty()); QVERIFY(clipboardChangedSpy.wait()); } else { // TODO: it would be better to be able to connect to a signal, instead of waiting // the idea is to make sure that the clipboard is updated, thus we need to give it // enough time before starting the paste process which creates another window QTest::qWait(250); } // start the paste process m_pasteProcess = new QProcess(); QSignalSpy finishedSpy(m_pasteProcess, static_cast(&QProcess::finished)); QVERIFY(finishedSpy.isValid()); QFETCH(QString, pastePlatform); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), pastePlatform); m_pasteProcess->setProcessEnvironment(environment); m_pasteProcess->setProcessChannelMode(QProcess::ForwardedChannels); m_pasteProcess->setProgram(paste); m_pasteProcess->start(); QVERIFY(m_pasteProcess->waitForStarted()); AbstractClient *pasteClient = nullptr; if (pastePlatform == QLatin1String("xcb")) { QVERIFY(clientAddedSpy.wait()); pasteClient = clientAddedSpy.last().first().value(); } else { QVERIFY(shellClientAddedSpy.wait()); pasteClient = shellClientAddedSpy.last().first().value(); } QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(shellClientAddedSpy.count(), 1); QVERIFY(pasteClient); if (workspace()->activeClient() != pasteClient) { QSignalSpy clientActivatedSpy(workspace(), &Workspace::clientActivated); QVERIFY(clientActivatedSpy.isValid()); workspace()->activateClient(pasteClient); QVERIFY(clientActivatedSpy.wait()); } QTRY_COMPARE(workspace()->activeClient(), pasteClient); QVERIFY(finishedSpy.wait()); QCOMPARE(finishedSpy.first().first().toInt(), 0); delete m_pasteProcess; m_pasteProcess = nullptr; } WAYLANDTEST_MAIN(XClipboardSyncTest) #include "xclipboardsync_test.moc" diff --git a/autotests/libinput/CMakeLists.txt b/autotests/libinput/CMakeLists.txt index 79686097a..bc0fff699 100644 --- a/autotests/libinput/CMakeLists.txt +++ b/autotests/libinput/CMakeLists.txt @@ -1,99 +1,113 @@ include_directories(${Libinput_INCLUDE_DIRS}) include_directories(${UDEV_INCLUDE_DIR}) ######################################################## # Test Devices ######################################################## set( testLibinputDevice_SRCS device_test.cpp mock_libinput.cpp ../../libinput/device.cpp ) add_executable(testLibinputDevice ${testLibinputDevice_SRCS}) -target_link_libraries( testLibinputDevice Qt5::Test Qt5::DBus KF5::ConfigCore) -add_test(kwin-testLibinputDevice testLibinputDevice) +target_link_libraries( testLibinputDevice Qt5::Test Qt5::DBus Qt5::Gui KF5::ConfigCore) +add_test(NAME kwin-testLibinputDevice COMMAND testLibinputDevice) ecm_mark_as_test(testLibinputDevice) ######################################################## # Test Key Event ######################################################## set( testLibinputKeyEvent_SRCS key_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../libinput/events.cpp ) add_executable(testLibinputKeyEvent ${testLibinputKeyEvent_SRCS}) target_link_libraries( testLibinputKeyEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) -add_test(kwin-testLibinputKeyEvent testLibinputKeyEvent) +add_test(NAME kwin-testLibinputKeyEvent COMMAND testLibinputKeyEvent) ecm_mark_as_test(testLibinputKeyEvent) ######################################################## # Test Pointer Event ######################################################## set( testLibinputPointerEvent_SRCS pointer_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../libinput/events.cpp ) add_executable(testLibinputPointerEvent ${testLibinputPointerEvent_SRCS}) target_link_libraries( testLibinputPointerEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) -add_test(kwin-testLibinputPointerEvent testLibinputPointerEvent) +add_test(NAME kwin-testLibinputPointerEvent COMMAND testLibinputPointerEvent) ecm_mark_as_test(testLibinputPointerEvent) ######################################################## # Test Touch Event ######################################################## set( testLibinputTouchEvent_SRCS touch_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../libinput/events.cpp ) add_executable(testLibinputTouchEvent ${testLibinputTouchEvent_SRCS}) target_link_libraries( testLibinputTouchEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) -add_test(kwin-testLibinputTouchEvent testLibinputTouchEvent) +add_test(NAME kwin-testLibinputTouchEvent COMMAND testLibinputTouchEvent) ecm_mark_as_test(testLibinputTouchEvent) ######################################################## # Test Gesture Event ######################################################## set( testLibinputGestureEvent_SRCS gesture_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../libinput/events.cpp ) add_executable(testLibinputGestureEvent ${testLibinputGestureEvent_SRCS}) target_link_libraries( testLibinputGestureEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) -add_test(kwin-testLibinputGestureEvent testLibinputGestureEvent) +add_test(NAME kwin-testLibinputGestureEvent COMMAND testLibinputGestureEvent) ecm_mark_as_test(testLibinputGestureEvent) +######################################################## +# Test Switch Event +######################################################## +set( testLibinputSwitchEvent_SRCS + switch_event_test.cpp + mock_libinput.cpp + ../../libinput/device.cpp + ../../libinput/events.cpp + ) +add_executable(testLibinputSwitchEvent ${testLibinputSwitchEvent_SRCS}) +target_link_libraries(testLibinputSwitchEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) +add_test(NAME kwin-testLibinputSwitchEvent COMMAND testLibinputSwitchEvent) +ecm_mark_as_test(testLibinputSwitchEvent) + ######################################################## # Test Context ######################################################## set( testLibinputContext_SRCS context_test.cpp mock_libinput.cpp mock_udev.cpp ../../libinput/context.cpp ../../libinput/device.cpp ../../libinput/events.cpp ../../libinput/libinput_logging.cpp ../../logind.cpp ) add_executable(testLibinputContext ${testLibinputContext_SRCS}) target_link_libraries( testLibinputContext Qt5::DBus Qt5::Test Qt5::Widgets KF5::ConfigCore KF5::WindowSystem ) -add_test(kwin-testLibinputContext testLibinputContext) +add_test(NAME kwin-testLibinputContext COMMAND testLibinputContext) ecm_mark_as_test(testLibinputContext) ######################################################## # Test Input Events ######################################################## set( testInputEvents_SRCS input_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../input_event.cpp ) add_executable(testInputEvents ${testInputEvents_SRCS}) target_link_libraries( testInputEvents Qt5::Test Qt5::DBus Qt5::Gui KF5::ConfigCore) -add_test(kwin-testInputEvents testInputEvents) +add_test(NAME kwin-testInputEvents COMMAND testInputEvents) ecm_mark_as_test(testInputEvents) diff --git a/autotests/libinput/device_test.cpp b/autotests/libinput/device_test.cpp index 805fab8e4..a93cc4325 100644 --- a/autotests/libinput/device_test.cpp +++ b/autotests/libinput/device_test.cpp @@ -1,2099 +1,2198 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "mock_libinput.h" #include "../../libinput/device.h" #include #include #include #include using namespace KWin::LibInput; class TestLibinputDevice : public QObject { Q_OBJECT private Q_SLOTS: void testStaticGetter(); void testDeviceType_data(); void testDeviceType(); void testGestureSupport_data(); void testGestureSupport(); void testNames_data(); void testNames(); void testProduct(); void testVendor(); void testTapFingerCount(); void testSize_data(); void testSize(); void testDefaultPointerAcceleration_data(); void testDefaultPointerAcceleration(); void testDefaultPointerAccelerationProfileFlat_data(); void testDefaultPointerAccelerationProfileFlat(); void testDefaultPointerAccelerationProfileAdaptive_data(); void testDefaultPointerAccelerationProfileAdaptive(); void testLeftHandedEnabledByDefault_data(); void testLeftHandedEnabledByDefault(); void testTapEnabledByDefault_data(); void testTapEnabledByDefault(); void testMiddleEmulationEnabledByDefault_data(); void testMiddleEmulationEnabledByDefault(); void testNaturalScrollEnabledByDefault_data(); void testNaturalScrollEnabledByDefault(); void testScrollTwoFingerEnabledByDefault_data(); void testScrollTwoFingerEnabledByDefault(); void testScrollEdgeEnabledByDefault_data(); void testScrollEdgeEnabledByDefault(); void testScrollOnButtonDownEnabledByDefault_data(); void testScrollOnButtonDownEnabledByDefault(); void testDisableWhileTypingEnabledByDefault_data(); void testDisableWhileTypingEnabledByDefault(); void testLmrTapButtonMapEnabledByDefault_data(); void testLmrTapButtonMapEnabledByDefault(); void testSupportsDisableWhileTyping_data(); void testSupportsDisableWhileTyping(); void testSupportsPointerAcceleration_data(); void testSupportsPointerAcceleration(); void testSupportsLeftHanded_data(); void testSupportsLeftHanded(); void testSupportsCalibrationMatrix_data(); void testSupportsCalibrationMatrix(); void testSupportsDisableEvents_data(); void testSupportsDisableEvents(); void testSupportsDisableEventsOnExternalMouse_data(); void testSupportsDisableEventsOnExternalMouse(); void testSupportsMiddleEmulation_data(); void testSupportsMiddleEmulation(); void testSupportsNaturalScroll_data(); void testSupportsNaturalScroll(); void testSupportsScrollTwoFinger_data(); void testSupportsScrollTwoFinger(); void testSupportsScrollEdge_data(); void testSupportsScrollEdge(); void testSupportsScrollOnButtonDown_data(); void testSupportsScrollOnButtonDown(); void testDefaultScrollButton_data(); void testDefaultScrollButton(); void testPointerAcceleration_data(); void testPointerAcceleration(); void testLeftHanded_data(); void testLeftHanded(); void testSupportedButtons_data(); void testSupportedButtons(); void testAlphaNumericKeyboard_data(); void testAlphaNumericKeyboard(); void testEnabled_data(); void testEnabled(); void testTapToClick_data(); void testTapToClick(); void testTapAndDragEnabledByDefault_data(); void testTapAndDragEnabledByDefault(); void testTapAndDrag_data(); void testTapAndDrag(); void testTapDragLockEnabledByDefault_data(); void testTapDragLockEnabledByDefault(); void testTapDragLock_data(); void testTapDragLock(); void testMiddleEmulation_data(); void testMiddleEmulation(); void testNaturalScroll_data(); void testNaturalScroll(); void testScrollTwoFinger_data(); void testScrollTwoFinger(); void testScrollEdge_data(); void testScrollEdge(); void testScrollButtonDown_data(); void testScrollButtonDown(); void testScrollButton_data(); void testScrollButton(); void testDisableWhileTyping_data(); void testDisableWhileTyping(); void testLmrTapButtonMap_data(); void testLmrTapButtonMap(); void testLoadEnabled_data(); void testLoadEnabled(); void testLoadPointerAcceleration_data(); void testLoadPointerAcceleration(); void testLoadPointerAccelerationProfile_data(); void testLoadPointerAccelerationProfile(); void testLoadTapToClick_data(); void testLoadTapToClick(); void testLoadTapAndDrag_data(); void testLoadTapAndDrag(); void testLoadTapDragLock_data(); void testLoadTapDragLock(); void testLoadMiddleButtonEmulation_data(); void testLoadMiddleButtonEmulation(); void testLoadNaturalScroll_data(); void testLoadNaturalScroll(); void testLoadScrollMethod_data(); void testLoadScrollMethod(); void testLoadScrollButton_data(); void testLoadScrollButton(); void testLoadDisableWhileTyping_data(); void testLoadDisableWhileTyping(); void testLoadLmrTapButtonMap_data(); void testLoadLmrTapButtonMap(); void testLoadLeftHanded_data(); void testLoadLeftHanded(); + void testScreenId(); + void testOrientation_data(); + void testOrientation(); + void testCalibrationWithDefault(); + void testSwitch_data(); + void testSwitch(); }; void TestLibinputDevice::testStaticGetter() { // this test verifies that the static getter for Device works as expected QVERIFY(Device::devices().isEmpty()); // create some device libinput_device device1; libinput_device device2; // at the moment not yet known to Device QVERIFY(!Device::getDevice(&device1)); QVERIFY(!Device::getDevice(&device2)); QVERIFY(Device::devices().isEmpty()); // now create a Device for one Device *d1 = new Device(&device1); QCOMPARE(Device::devices().count(), 1); QCOMPARE(Device::devices().first(), d1); QCOMPARE(Device::getDevice(&device1), d1); QVERIFY(!Device::getDevice(&device2)); // and a second Device Device *d2 = new Device(&device2); QCOMPARE(Device::devices().count(), 2); QCOMPARE(Device::devices().first(), d1); QCOMPARE(Device::devices().last(), d2); QCOMPARE(Device::getDevice(&device1), d1); QCOMPARE(Device::getDevice(&device2), d2); // now delete d1 delete d1; QCOMPARE(Device::devices().count(), 1); QCOMPARE(Device::devices().first(), d2); QCOMPARE(Device::getDevice(&device2), d2); QVERIFY(!Device::getDevice(&device1)); // and delete d2 delete d2; QVERIFY(!Device::getDevice(&device1)); QVERIFY(!Device::getDevice(&device2)); QVERIFY(Device::devices().isEmpty()); } void TestLibinputDevice::testDeviceType_data() { QTest::addColumn("keyboard"); QTest::addColumn("pointer"); QTest::addColumn("touch"); QTest::addColumn("tabletTool"); + QTest::addColumn("switchDevice"); - QTest::newRow("keyboard") << true << false << false << false; - QTest::newRow("pointer") << false << true << false << false; - QTest::newRow("touch") << false << false << true << false; - QTest::newRow("keyboard/pointer") << true << true << false << false; - QTest::newRow("keyboard/touch") << true << false << true << false; - QTest::newRow("pointer/touch") << false << true << true << false; - QTest::newRow("keyboard/pointer/touch") << true << true << true << false; - QTest::newRow("tabletTool") << false << false << false << true; + QTest::newRow("keyboard") << true << false << false << false << false; + QTest::newRow("pointer") << false << true << false << false << false; + QTest::newRow("touch") << false << false << true << false << false; + QTest::newRow("keyboard/pointer") << true << true << false << false << false; + QTest::newRow("keyboard/touch") << true << false << true << false << false; + QTest::newRow("pointer/touch") << false << true << true << false << false; + QTest::newRow("keyboard/pointer/touch") << true << true << true << false << false; + QTest::newRow("tabletTool") << false << false << false << true << false; + QTest::newRow("switch") << false << false << false << false << true; } void TestLibinputDevice::testDeviceType() { // this test verifies that the device type is recognized correctly QFETCH(bool, keyboard); QFETCH(bool, pointer); QFETCH(bool, touch); QFETCH(bool, tabletTool); + QFETCH(bool, switchDevice); libinput_device device; device.keyboard = keyboard; device.pointer = pointer; device.touch = touch; device.tabletTool = tabletTool; + device.switchDevice = switchDevice; Device d(&device); QCOMPARE(d.isKeyboard(), keyboard); QCOMPARE(d.property("keyboard").toBool(), keyboard); QCOMPARE(d.isPointer(), pointer); QCOMPARE(d.property("pointer").toBool(), pointer); QCOMPARE(d.isTouch(), touch); QCOMPARE(d.property("touch").toBool(), touch); QCOMPARE(d.isTabletPad(), false); QCOMPARE(d.property("tabletPad").toBool(), false); QCOMPARE(d.isTabletTool(), tabletTool); QCOMPARE(d.property("tabletTool").toBool(), tabletTool); + QCOMPARE(d.isSwitch(), switchDevice); + QCOMPARE(d.property("switchDevice").toBool(), switchDevice); QCOMPARE(d.device(), &device); } void TestLibinputDevice::testGestureSupport_data() { QTest::addColumn("supported"); QTest::newRow("supported") << true; QTest::newRow("not supported") << false; } void TestLibinputDevice::testGestureSupport() { // this test verifies whether the Device supports gestures QFETCH(bool, supported); libinput_device device; device.gestureSupported = supported; Device d(&device); QCOMPARE(d.supportsGesture(), supported); QCOMPARE(d.property("gestureSupport").toBool(), supported); } void TestLibinputDevice::testNames_data() { QTest::addColumn("name"); QTest::addColumn("sysName"); QTest::addColumn("outputName"); QTest::newRow("empty") << QByteArray() << QByteArrayLiteral("event1") << QByteArray(); QTest::newRow("set") << QByteArrayLiteral("awesome test device") << QByteArrayLiteral("event0") << QByteArrayLiteral("hdmi0"); } void TestLibinputDevice::testNames() { // this test verifies the various name properties of the Device QFETCH(QByteArray, name); QFETCH(QByteArray, sysName); QFETCH(QByteArray, outputName); libinput_device device; device.name = name; device.sysName = sysName; device.outputName = outputName; Device d(&device); QCOMPARE(d.name().toUtf8(), name); QCOMPARE(d.property("name").toString().toUtf8(), name); QCOMPARE(d.sysName().toUtf8(), sysName); QCOMPARE(d.property("sysName").toString().toUtf8(), sysName); QCOMPARE(d.outputName().toUtf8(), outputName); QCOMPARE(d.property("outputName").toString().toUtf8(), outputName); } void TestLibinputDevice::testProduct() { // this test verifies the product property libinput_device device; device.product = 100u; Device d(&device); QCOMPARE(d.product(), 100u); QCOMPARE(d.property("product").toUInt(), 100u); } void TestLibinputDevice::testVendor() { // this test verifies the vendor property libinput_device device; device.vendor = 200u; Device d(&device); QCOMPARE(d.vendor(), 200u); QCOMPARE(d.property("vendor").toUInt(), 200u); } void TestLibinputDevice::testTapFingerCount() { // this test verifies the tap finger count property libinput_device device; device.tapFingerCount = 3; Device d(&device); QCOMPARE(d.tapFingerCount(), 3); QCOMPARE(d.property("tapFingerCount").toInt(), 3); } void TestLibinputDevice::testSize_data() { QTest::addColumn("setSize"); QTest::addColumn("returnValue"); QTest::addColumn("expectedSize"); QTest::newRow("10/20") << QSizeF(10.5, 20.2) << 0 << QSizeF(10.5, 20.2); QTest::newRow("failure") << QSizeF(10, 20) << 1 << QSizeF(); } void TestLibinputDevice::testSize() { // this test verifies that getting the size works correctly including failures QFETCH(QSizeF, setSize); QFETCH(int, returnValue); libinput_device device; device.deviceSize = setSize; device.deviceSizeReturnValue = returnValue; Device d(&device); QTEST(d.size(), "expectedSize"); QTEST(d.property("size").toSizeF(), "expectedSize"); } void TestLibinputDevice::testLeftHandedEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testLeftHandedEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.leftHandedEnabledByDefault = enabled; Device d(&device); QCOMPARE(d.leftHandedEnabledByDefault(), enabled); QCOMPARE(d.property("leftHandedEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testTapEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testTapEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.tapEnabledByDefault = enabled; Device d(&device); QCOMPARE(d.tapToClickEnabledByDefault(), enabled); QCOMPARE(d.property("tapToClickEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testMiddleEmulationEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testMiddleEmulationEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.middleEmulationEnabledByDefault = enabled; Device d(&device); QCOMPARE(d.middleEmulationEnabledByDefault(), enabled); QCOMPARE(d.property("middleEmulationEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testNaturalScrollEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testNaturalScrollEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.naturalScrollEnabledByDefault = enabled; Device d(&device); QCOMPARE(d.naturalScrollEnabledByDefault(), enabled); QCOMPARE(d.property("naturalScrollEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testScrollTwoFingerEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testScrollTwoFingerEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.defaultScrollMethod = enabled ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; Device d(&device); QCOMPARE(d.scrollTwoFingerEnabledByDefault(), enabled); QCOMPARE(d.property("scrollTwoFingerEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testScrollEdgeEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testScrollEdgeEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.defaultScrollMethod = enabled ? LIBINPUT_CONFIG_SCROLL_EDGE : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; Device d(&device); QCOMPARE(d.scrollEdgeEnabledByDefault(), enabled); QCOMPARE(d.property("scrollEdgeEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testDefaultPointerAccelerationProfileFlat_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testDefaultPointerAccelerationProfileFlat() { QFETCH(bool, enabled); libinput_device device; device.defaultPointerAccelerationProfile = enabled ? LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT : LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; Device d(&device); QCOMPARE(d.defaultPointerAccelerationProfileFlat(), enabled); QCOMPARE(d.property("defaultPointerAccelerationProfileFlat").toBool(), enabled); } void TestLibinputDevice::testDefaultPointerAccelerationProfileAdaptive_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testDefaultPointerAccelerationProfileAdaptive() { QFETCH(bool, enabled); libinput_device device; device.defaultPointerAccelerationProfile = enabled ? LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE : LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; Device d(&device); QCOMPARE(d.defaultPointerAccelerationProfileAdaptive(), enabled); QCOMPARE(d.property("defaultPointerAccelerationProfileAdaptive").toBool(), enabled); } void TestLibinputDevice::testScrollOnButtonDownEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testScrollOnButtonDownEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.defaultScrollMethod = enabled ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; Device d(&device); QCOMPARE(d.scrollOnButtonDownEnabledByDefault(), enabled); QCOMPARE(d.property("scrollOnButtonDownEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testDefaultScrollButton_data() { QTest::addColumn("button"); QTest::newRow("0") << 0u; QTest::newRow("BTN_LEFT") << quint32(BTN_LEFT); QTest::newRow("BTN_RIGHT") << quint32(BTN_RIGHT); QTest::newRow("BTN_MIDDLE") << quint32(BTN_MIDDLE); QTest::newRow("BTN_SIDE") << quint32(BTN_SIDE); QTest::newRow("BTN_EXTRA") << quint32(BTN_EXTRA); QTest::newRow("BTN_FORWARD") << quint32(BTN_FORWARD); QTest::newRow("BTN_BACK") << quint32(BTN_BACK); QTest::newRow("BTN_TASK") << quint32(BTN_TASK); } void TestLibinputDevice::testDefaultScrollButton() { libinput_device device; QFETCH(quint32, button); device.defaultScrollButton = button; Device d(&device); QCOMPARE(d.defaultScrollButton(), button); QCOMPARE(d.property("defaultScrollButton").value(), button); } void TestLibinputDevice::testSupportsDisableWhileTyping_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsDisableWhileTyping() { QFETCH(bool, enabled); libinput_device device; device.supportsDisableWhileTyping = enabled; Device d(&device); QCOMPARE(d.supportsDisableWhileTyping(), enabled); QCOMPARE(d.property("supportsDisableWhileTyping").toBool(), enabled); } void TestLibinputDevice::testSupportsPointerAcceleration_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsPointerAcceleration() { QFETCH(bool, enabled); libinput_device device; device.supportsPointerAcceleration = enabled; Device d(&device); QCOMPARE(d.supportsPointerAcceleration(), enabled); QCOMPARE(d.property("supportsPointerAcceleration").toBool(), enabled); } void TestLibinputDevice::testSupportsLeftHanded_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsLeftHanded() { QFETCH(bool, enabled); libinput_device device; device.supportsLeftHanded = enabled; Device d(&device); QCOMPARE(d.supportsLeftHanded(), enabled); QCOMPARE(d.property("supportsLeftHanded").toBool(), enabled); } void TestLibinputDevice::testSupportsCalibrationMatrix_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsCalibrationMatrix() { QFETCH(bool, enabled); libinput_device device; device.supportsCalibrationMatrix = enabled; Device d(&device); QCOMPARE(d.supportsCalibrationMatrix(), enabled); QCOMPARE(d.property("supportsCalibrationMatrix").toBool(), enabled); } void TestLibinputDevice::testSupportsDisableEvents_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsDisableEvents() { QFETCH(bool, enabled); libinput_device device; device.supportsDisableEvents = enabled; Device d(&device); QCOMPARE(d.supportsDisableEvents(), enabled); QCOMPARE(d.property("supportsDisableEvents").toBool(), enabled); } void TestLibinputDevice::testSupportsDisableEventsOnExternalMouse_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsDisableEventsOnExternalMouse() { QFETCH(bool, enabled); libinput_device device; device.supportsDisableEventsOnExternalMouse = enabled; Device d(&device); QCOMPARE(d.supportsDisableEventsOnExternalMouse(), enabled); QCOMPARE(d.property("supportsDisableEventsOnExternalMouse").toBool(), enabled); } void TestLibinputDevice::testSupportsMiddleEmulation_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsMiddleEmulation() { QFETCH(bool, enabled); libinput_device device; device.supportsMiddleEmulation = enabled; Device d(&device); QCOMPARE(d.supportsMiddleEmulation(), enabled); QCOMPARE(d.property("supportsMiddleEmulation").toBool(), enabled); } void TestLibinputDevice::testSupportsNaturalScroll_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsNaturalScroll() { QFETCH(bool, enabled); libinput_device device; device.supportsNaturalScroll = enabled; Device d(&device); QCOMPARE(d.supportsNaturalScroll(), enabled); QCOMPARE(d.property("supportsNaturalScroll").toBool(), enabled); } void TestLibinputDevice::testSupportsScrollTwoFinger_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsScrollTwoFinger() { QFETCH(bool, enabled); libinput_device device; device.supportedScrollMethods = enabled ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; Device d(&device); QCOMPARE(d.supportsScrollTwoFinger(), enabled); QCOMPARE(d.property("supportsScrollTwoFinger").toBool(), enabled); } void TestLibinputDevice::testSupportsScrollEdge_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsScrollEdge() { QFETCH(bool, enabled); libinput_device device; device.supportedScrollMethods = enabled ? LIBINPUT_CONFIG_SCROLL_EDGE : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; Device d(&device); QCOMPARE(d.supportsScrollEdge(), enabled); QCOMPARE(d.property("supportsScrollEdge").toBool(), enabled); } void TestLibinputDevice::testSupportsScrollOnButtonDown_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testSupportsScrollOnButtonDown() { QFETCH(bool, enabled); libinput_device device; device.supportedScrollMethods = enabled ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; Device d(&device); QCOMPARE(d.supportsScrollOnButtonDown(), enabled); QCOMPARE(d.property("supportsScrollOnButtonDown").toBool(), enabled); } void TestLibinputDevice::testDefaultPointerAcceleration_data() { QTest::addColumn("accel"); QTest::newRow("-1.0") << -1.0; QTest::newRow("-0.5") << -0.5; QTest::newRow("0.0") << 0.0; QTest::newRow("0.3") << 0.3; QTest::newRow("1.0") << 1.0; } void TestLibinputDevice::testDefaultPointerAcceleration() { QFETCH(qreal, accel); libinput_device device; device.defaultPointerAcceleration = accel; Device d(&device); QCOMPARE(d.defaultPointerAcceleration(), accel); QCOMPARE(d.property("defaultPointerAcceleration").toReal(), accel); } void TestLibinputDevice::testPointerAcceleration_data() { QTest::addColumn("supported"); QTest::addColumn("setShouldFail"); QTest::addColumn("accel"); QTest::addColumn("setAccel"); QTest::addColumn("expectedAccel"); QTest::addColumn("expectedChanged"); QTest::newRow("-1 -> 2.0") << true << false << -1.0 << 2.0 << 1.0 << true; QTest::newRow("0 -> -1.0") << true << false << 0.0 << -1.0 << -1.0 << true; QTest::newRow("1 -> 1") << true << false << 1.0 << 1.0 << 1.0 << false; QTest::newRow("unsupported") << false << false << 0.0 << 1.0 << 0.0 << false; QTest::newRow("set fails") << true << true << -1.0 << 1.0 << -1.0 << false; } void TestLibinputDevice::testPointerAcceleration() { QFETCH(bool, supported); QFETCH(bool, setShouldFail); QFETCH(qreal, accel); libinput_device device; device.supportsPointerAcceleration = supported; device.pointerAcceleration = accel; device.setPointerAccelerationReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.pointerAcceleration(), accel); QCOMPARE(d.property("pointerAcceleration").toReal(), accel); QSignalSpy pointerAccelChangedSpy(&d, &Device::pointerAccelerationChanged); QVERIFY(pointerAccelChangedSpy.isValid()); QFETCH(qreal, setAccel); d.setPointerAcceleration(setAccel); QTEST(d.pointerAcceleration(), "expectedAccel"); QTEST(!pointerAccelChangedSpy.isEmpty(), "expectedChanged"); } void TestLibinputDevice::testLeftHanded_data() { QTest::addColumn("supported"); QTest::addColumn("setShouldFail"); QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("expectedValue"); QTest::newRow("unsupported/true") << false << false << true << false << false; QTest::newRow("unsupported/false") << false << false << false << true << false; QTest::newRow("true -> false") << true << false << true << false << false; QTest::newRow("false -> true") << true << false << false << true << true; QTest::newRow("set fails") << true << true << true << false << true; QTest::newRow("true -> true") << true << false << true << true << true; QTest::newRow("false -> false") << true << false << false << false << false; } void TestLibinputDevice::testLeftHanded() { QFETCH(bool, supported); QFETCH(bool, setShouldFail); QFETCH(bool, initValue); libinput_device device; device.supportsLeftHanded = supported; device.leftHanded = initValue; device.setLeftHandedReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isLeftHanded(), supported && initValue); QCOMPARE(d.property("leftHanded").toBool(), supported && initValue); QSignalSpy leftHandedChangedSpy(&d, &Device::leftHandedChanged); QVERIFY(leftHandedChangedSpy.isValid()); QFETCH(bool, setValue); d.setLeftHanded(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isLeftHanded(), expectedValue); QCOMPARE(leftHandedChangedSpy.isEmpty(), (supported && initValue) == expectedValue); } void TestLibinputDevice::testSupportedButtons_data() { QTest::addColumn("isPointer"); QTest::addColumn("setButtons"); QTest::addColumn("expectedButtons"); QTest::newRow("left") << true << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(Qt::LeftButton); QTest::newRow("right") << true << Qt::MouseButtons(Qt::RightButton) << Qt::MouseButtons(Qt::RightButton); QTest::newRow("middle") << true << Qt::MouseButtons(Qt::MiddleButton) << Qt::MouseButtons(Qt::MiddleButton); QTest::newRow("extra1") << true << Qt::MouseButtons(Qt::ExtraButton1) << Qt::MouseButtons(Qt::ExtraButton1); QTest::newRow("extra2") << true << Qt::MouseButtons(Qt::ExtraButton2) << Qt::MouseButtons(Qt::ExtraButton2); QTest::newRow("back") << true << Qt::MouseButtons(Qt::BackButton) << Qt::MouseButtons(Qt::BackButton); QTest::newRow("forward") << true << Qt::MouseButtons(Qt::ForwardButton) << Qt::MouseButtons(Qt::ForwardButton); QTest::newRow("task") << true << Qt::MouseButtons(Qt::TaskButton) << Qt::MouseButtons(Qt::TaskButton); QTest::newRow("no pointer/left") << false << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(); QTest::newRow("no pointer/right") << false << Qt::MouseButtons(Qt::RightButton) << Qt::MouseButtons(); QTest::newRow("no pointer/middle") << false << Qt::MouseButtons(Qt::MiddleButton) << Qt::MouseButtons(); QTest::newRow("no pointer/extra1") << false << Qt::MouseButtons(Qt::ExtraButton1) << Qt::MouseButtons(); QTest::newRow("no pointer/extra2") << false << Qt::MouseButtons(Qt::ExtraButton2) << Qt::MouseButtons(); QTest::newRow("no pointer/back") << false << Qt::MouseButtons(Qt::BackButton) << Qt::MouseButtons(); QTest::newRow("no pointer/forward") << false << Qt::MouseButtons(Qt::ForwardButton) << Qt::MouseButtons(); QTest::newRow("no pointer/task") << false << Qt::MouseButtons(Qt::TaskButton) << Qt::MouseButtons(); QTest::newRow("all") << true << Qt::MouseButtons(Qt::LeftButton | Qt::RightButton | Qt::MiddleButton | Qt::ExtraButton1 | Qt::ExtraButton2 | Qt::BackButton | Qt::ForwardButton | Qt::TaskButton) << Qt::MouseButtons(Qt::LeftButton | Qt::RightButton | Qt::MiddleButton | Qt::ExtraButton1 | Qt::ExtraButton2 | Qt::BackButton | Qt::ForwardButton | Qt::TaskButton); } void TestLibinputDevice::testSupportedButtons() { libinput_device device; QFETCH(bool, isPointer); device.pointer = isPointer; QFETCH(Qt::MouseButtons, setButtons); device.supportedButtons = setButtons; Device d(&device); QCOMPARE(d.isPointer(), isPointer); QTEST(d.supportedButtons(), "expectedButtons"); } void TestLibinputDevice::testAlphaNumericKeyboard_data() { QTest::addColumn>("supportedKeys"); QTest::addColumn("isAlpha"); QVector keys; for (int i = KEY_1; i <= KEY_0; i++) { keys << i; QByteArray row = QByteArrayLiteral("number"); row.append(QByteArray::number(i)); QTest::newRow(row.constData()) << keys << false; } for (int i = KEY_Q; i <= KEY_P; i++) { keys << i; QByteArray row = QByteArrayLiteral("alpha"); row.append(QByteArray::number(i)); QTest::newRow(row.constData()) << keys << false; } for (int i = KEY_A; i <= KEY_L; i++) { keys << i; QByteArray row = QByteArrayLiteral("alpha"); row.append(QByteArray::number(i)); QTest::newRow(row.constData()) << keys << false; } for (int i = KEY_Z; i < KEY_M; i++) { keys << i; QByteArray row = QByteArrayLiteral("alpha"); row.append(QByteArray::number(i)); QTest::newRow(row.constData()) << keys << false; } // adding a different key should not result in it becoming alphanumeric keyboard keys << KEY_SEMICOLON; QTest::newRow("semicolon") << keys << false; // last but not least the M which should turn everything on keys << KEY_M; QTest::newRow("alphanumeric") << keys << true; } void TestLibinputDevice::testAlphaNumericKeyboard() { QFETCH(QVector, supportedKeys); libinput_device device; device.keyboard = true; device.keys = supportedKeys; Device d(&device); QCOMPARE(d.isKeyboard(), true); QTEST(d.isAlphaNumericKeyboard(), "isAlpha"); } void TestLibinputDevice::testEnabled_data() { QTest::addColumn("supported"); QTest::addColumn("setShouldFail"); QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("expectedValue"); QTest::newRow("unsupported/true") << false << false << true << false << true; QTest::newRow("unsupported/false") << false << false << false << true << true; QTest::newRow("true -> false") << true << false << true << false << false; QTest::newRow("false -> true") << true << false << false << true << true; QTest::newRow("set fails") << true << true << true << false << true; QTest::newRow("true -> true") << true << false << true << true << true; QTest::newRow("false -> false") << true << false << false << false << false; } void TestLibinputDevice::testEnabled() { libinput_device device; QFETCH(bool, supported); QFETCH(bool, setShouldFail); QFETCH(bool, initValue); device.supportsDisableEvents = supported; device.enabled = initValue; device.setEnableModeReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isEnabled(), !supported || initValue); QCOMPARE(d.property("enabled").toBool(), !supported || initValue); QSignalSpy enabledChangedSpy(&d, &Device::enabledChanged); QVERIFY(enabledChangedSpy.isValid()); QFETCH(bool, setValue); d.setEnabled(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isEnabled(), expectedValue); } void TestLibinputDevice::testTapToClick_data() { QTest::addColumn("fingerCount"); QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::newRow("unsupported") << 0 << false << true << true << false; QTest::newRow("true -> false") << 1 << true << false << false << false; QTest::newRow("false -> true") << 2 << false << true << false << true; QTest::newRow("set fails") << 3 << true << false << true << true; QTest::newRow("true -> true") << 2 << true << true << false << true; QTest::newRow("false -> false") << 1 << false << false << false << false; } void TestLibinputDevice::testTapToClick() { libinput_device device; QFETCH(int, fingerCount); QFETCH(bool, initValue); QFETCH(bool, setShouldFail); device.tapFingerCount = fingerCount; device.tapToClick = initValue; device.setTapToClickReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.tapFingerCount(), fingerCount); QCOMPARE(d.isTapToClick(), initValue); QCOMPARE(d.property("tapToClick").toBool(), initValue); QSignalSpy tapToClickChangedSpy(&d, &Device::tapToClickChanged); QVERIFY(tapToClickChangedSpy.isValid()); QFETCH(bool, setValue); d.setTapToClick(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isTapToClick(), expectedValue); QCOMPARE(tapToClickChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testTapAndDragEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testTapAndDragEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.tapAndDragEnabledByDefault = enabled; Device d(&device); QCOMPARE(d.tapAndDragEnabledByDefault(), enabled); QCOMPARE(d.property("tapAndDragEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testTapAndDrag_data() { QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::newRow("true -> false") << true << false << false << false; QTest::newRow("false -> true") << false << true << false << true; QTest::newRow("set fails") << true << false << true << true; QTest::newRow("true -> true") << true << true << false << true; QTest::newRow("false -> false") << false << false << false << false; } void TestLibinputDevice::testTapAndDrag() { libinput_device device; QFETCH(bool, initValue); QFETCH(bool, setShouldFail); device.tapAndDrag = initValue; device.setTapAndDragReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isTapAndDrag(), initValue); QCOMPARE(d.property("tapAndDrag").toBool(), initValue); QSignalSpy tapAndDragChangedSpy(&d, &Device::tapAndDragChanged); QVERIFY(tapAndDragChangedSpy.isValid()); QFETCH(bool, setValue); d.setTapAndDrag(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isTapAndDrag(), expectedValue); QCOMPARE(tapAndDragChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testTapDragLockEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testTapDragLockEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.tapDragLockEnabledByDefault = enabled; Device d(&device); QCOMPARE(d.tapDragLockEnabledByDefault(), enabled); QCOMPARE(d.property("tapDragLockEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testTapDragLock_data() { QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::newRow("true -> false") << true << false << false << false; QTest::newRow("false -> true") << false << true << false << true; QTest::newRow("set fails") << true << false << true << true; QTest::newRow("true -> true") << true << true << false << true; QTest::newRow("false -> false") << false << false << false << false; } void TestLibinputDevice::testTapDragLock() { libinput_device device; QFETCH(bool, initValue); QFETCH(bool, setShouldFail); device.tapDragLock = initValue; device.setTapDragLockReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isTapDragLock(), initValue); QCOMPARE(d.property("tapDragLock").toBool(), initValue); QSignalSpy tapDragLockChangedSpy(&d, &Device::tapDragLockChanged); QVERIFY(tapDragLockChangedSpy.isValid()); QFETCH(bool, setValue); d.setTapDragLock(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isTapDragLock(), expectedValue); QCOMPARE(tapDragLockChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testMiddleEmulation_data() { QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::addColumn("supportsMiddleButton"); QTest::newRow("true -> false") << true << false << false << false << true; QTest::newRow("false -> true") << false << true << false << true << true; QTest::newRow("set fails") << true << false << true << true << true; QTest::newRow("true -> true") << true << true << false << true << true; QTest::newRow("false -> false") << false << false << false << false << true; QTest::newRow("false -> true, unsupported") << false << true << true << false << false; } void TestLibinputDevice::testMiddleEmulation() { libinput_device device; QFETCH(bool, initValue); QFETCH(bool, setShouldFail); QFETCH(bool, supportsMiddleButton); device.supportsMiddleEmulation = supportsMiddleButton; device.middleEmulation = initValue; device.setMiddleEmulationReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isMiddleEmulation(), initValue); QCOMPARE(d.property("middleEmulation").toBool(), initValue); QSignalSpy middleEmulationChangedSpy(&d, &Device::middleEmulationChanged); QVERIFY(middleEmulationChangedSpy.isValid()); QFETCH(bool, setValue); d.setMiddleEmulation(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isMiddleEmulation(), expectedValue); QCOMPARE(d.property("middleEmulation").toBool(), expectedValue); QCOMPARE(middleEmulationChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testNaturalScroll_data() { QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::addColumn("supportsNaturalScroll"); QTest::newRow("true -> false") << true << false << false << false << true; QTest::newRow("false -> true") << false << true << false << true << true; QTest::newRow("set fails") << true << false << true << true << true; QTest::newRow("true -> true") << true << true << false << true << true; QTest::newRow("false -> false") << false << false << false << false << true; QTest::newRow("false -> true, unsupported") << false << true << true << false << false; } void TestLibinputDevice::testNaturalScroll() { libinput_device device; QFETCH(bool, initValue); QFETCH(bool, setShouldFail); QFETCH(bool, supportsNaturalScroll); device.supportsNaturalScroll = supportsNaturalScroll; device.naturalScroll = initValue; device.setNaturalScrollReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isNaturalScroll(), initValue); QCOMPARE(d.property("naturalScroll").toBool(), initValue); QSignalSpy naturalScrollChangedSpy(&d, &Device::naturalScrollChanged); QVERIFY(naturalScrollChangedSpy.isValid()); QFETCH(bool, setValue); d.setNaturalScroll(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isNaturalScroll(), expectedValue); QCOMPARE(d.property("naturalScroll").toBool(), expectedValue); QCOMPARE(naturalScrollChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testScrollTwoFinger_data() { QTest::addColumn("initValue"); QTest::addColumn("otherValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::addColumn("supportsScrollTwoFinger"); QTest::newRow("true -> false") << true << false << false << false << false << true; QTest::newRow("other -> false") << false << true << false << false << false << true; QTest::newRow("false -> true") << false << false << true << false << true << true; QTest::newRow("set fails") << true << false << false << true << true << true; QTest::newRow("true -> true") << true << false << true << false << true << true; QTest::newRow("false -> false") << false << false << false << false << false << true; QTest::newRow("false -> true, unsupported") << false << false << true << true << false << false; } void TestLibinputDevice::testScrollTwoFinger() { libinput_device device; QFETCH(bool, initValue); QFETCH(bool, otherValue); QFETCH(bool, setShouldFail); QFETCH(bool, supportsScrollTwoFinger); device.supportedScrollMethods = (supportsScrollTwoFinger ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL) | LIBINPUT_CONFIG_SCROLL_EDGE; device.scrollMethod = initValue ? LIBINPUT_CONFIG_SCROLL_2FG : otherValue ? LIBINPUT_CONFIG_SCROLL_EDGE : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; device.setScrollMethodReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isScrollTwoFinger(), initValue); QCOMPARE(d.property("scrollTwoFinger").toBool(), initValue); QCOMPARE(d.property("scrollEdge").toBool(), otherValue); QSignalSpy scrollMethodChangedSpy(&d, &Device::scrollMethodChanged); QVERIFY(scrollMethodChangedSpy.isValid()); QFETCH(bool, setValue); d.setScrollTwoFinger(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isScrollTwoFinger(), expectedValue); QCOMPARE(d.property("scrollTwoFinger").toBool(), expectedValue); QCOMPARE(scrollMethodChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testScrollEdge_data() { QTest::addColumn("initValue"); QTest::addColumn("otherValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::addColumn("supportsScrollEdge"); QTest::newRow("true -> false") << true << false << false << false << false << true; QTest::newRow("other -> false") << false << true << false << false << false << true; QTest::newRow("false -> true") << false << false << true << false << true << true; QTest::newRow("set fails") << true << false << false << true << true << true; QTest::newRow("true -> true") << true << false << true << false << true << true; QTest::newRow("false -> false") << false << false << false << false << false << true; QTest::newRow("false -> true, unsupported") << false << false << true << true << false << false; } void TestLibinputDevice::testScrollEdge() { libinput_device device; QFETCH(bool, initValue); QFETCH(bool, otherValue); QFETCH(bool, setShouldFail); QFETCH(bool, supportsScrollEdge); device.supportedScrollMethods = (supportsScrollEdge ? LIBINPUT_CONFIG_SCROLL_EDGE : LIBINPUT_CONFIG_SCROLL_NO_SCROLL) | LIBINPUT_CONFIG_SCROLL_2FG; device.scrollMethod = initValue ? LIBINPUT_CONFIG_SCROLL_EDGE : otherValue ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; device.setScrollMethodReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isScrollEdge(), initValue); QCOMPARE(d.property("scrollEdge").toBool(), initValue); QCOMPARE(d.property("scrollTwoFinger").toBool(), otherValue); QSignalSpy scrollMethodChangedSpy(&d, &Device::scrollMethodChanged); QVERIFY(scrollMethodChangedSpy.isValid()); QFETCH(bool, setValue); d.setScrollEdge(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isScrollEdge(), expectedValue); QCOMPARE(d.property("scrollEdge").toBool(), expectedValue); QCOMPARE(scrollMethodChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testScrollButtonDown_data() { QTest::addColumn("initValue"); QTest::addColumn("otherValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::addColumn("supportsScrollButtonDown"); QTest::newRow("true -> false") << true << false << false << false << false << true; QTest::newRow("other -> false") << false << true << false << false << false << true; QTest::newRow("false -> true") << false << false << true << false << true << true; QTest::newRow("set fails") << true << false << false << true << true << true; QTest::newRow("true -> true") << true << false << true << false << true << true; QTest::newRow("false -> false") << false << false << false << false << false << true; QTest::newRow("false -> true, unsupported") << false << false << true << true << false << false; } void TestLibinputDevice::testScrollButtonDown() { libinput_device device; QFETCH(bool, initValue); QFETCH(bool, otherValue); QFETCH(bool, setShouldFail); QFETCH(bool, supportsScrollButtonDown); device.supportedScrollMethods = (supportsScrollButtonDown ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : LIBINPUT_CONFIG_SCROLL_NO_SCROLL) | LIBINPUT_CONFIG_SCROLL_2FG; device.scrollMethod = initValue ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : otherValue ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; device.setScrollMethodReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isScrollOnButtonDown(), initValue); QCOMPARE(d.property("scrollOnButtonDown").toBool(), initValue); QCOMPARE(d.property("scrollTwoFinger").toBool(), otherValue); QSignalSpy scrollMethodChangedSpy(&d, &Device::scrollMethodChanged); QVERIFY(scrollMethodChangedSpy.isValid()); QFETCH(bool, setValue); d.setScrollOnButtonDown(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isScrollOnButtonDown(), expectedValue); QCOMPARE(d.property("scrollOnButtonDown").toBool(), expectedValue); QCOMPARE(scrollMethodChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testScrollButton_data() { QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("expectedValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("scrollOnButton"); QTest::newRow("BTN_LEFT -> BTN_RIGHT") << quint32(BTN_LEFT) << quint32(BTN_RIGHT) << quint32(BTN_RIGHT) << false << true; QTest::newRow("BTN_LEFT -> BTN_LEFT") << quint32(BTN_LEFT) << quint32(BTN_LEFT) << quint32(BTN_LEFT) << false << true; QTest::newRow("set should fail") << quint32(BTN_LEFT) << quint32(BTN_RIGHT) << quint32(BTN_LEFT) << true << true; QTest::newRow("not scroll on button") << quint32(BTN_LEFT) << quint32(BTN_RIGHT) << quint32(BTN_LEFT) << false << false; } void TestLibinputDevice::testScrollButton() { libinput_device device; QFETCH(quint32, initValue); QFETCH(bool, setShouldFail); QFETCH(bool, scrollOnButton); device.scrollButton = initValue; device.supportedScrollMethods = scrollOnButton ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; device.setScrollButtonReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.scrollButton(), initValue); QCOMPARE(d.property("scrollButton").value(), initValue); QSignalSpy scrollButtonChangedSpy(&d, &Device::scrollButtonChanged); QVERIFY(scrollButtonChangedSpy.isValid()); QFETCH(quint32, setValue); d.setScrollButton(setValue); QFETCH(quint32, expectedValue); QCOMPARE(d.scrollButton(), expectedValue); QCOMPARE(d.property("scrollButton").value(), expectedValue); QCOMPARE(scrollButtonChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testDisableWhileTypingEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testDisableWhileTypingEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.disableWhileTypingEnabledByDefault = enabled ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED; Device d(&device); QCOMPARE(d.disableWhileTypingEnabledByDefault(), enabled); QCOMPARE(d.property("disableWhileTypingEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testLmrTapButtonMapEnabledByDefault_data() { QTest::addColumn("enabled"); QTest::newRow("enabled") << true; QTest::newRow("disabled") << false; } void TestLibinputDevice::testLmrTapButtonMapEnabledByDefault() { QFETCH(bool, enabled); libinput_device device; device.defaultTapButtonMap = enabled ? LIBINPUT_CONFIG_TAP_MAP_LMR : LIBINPUT_CONFIG_TAP_MAP_LRM; Device d(&device); QCOMPARE(d.lmrTapButtonMapEnabledByDefault(), enabled); QCOMPARE(d.property("lmrTapButtonMapEnabledByDefault").toBool(), enabled); } void TestLibinputDevice::testLmrTapButtonMap_data() { QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::addColumn("fingerCount"); QTest::newRow("true -> false") << true << false << false << false << 3; QTest::newRow("false -> true") << false << true << false << true << 3; QTest::newRow("true -> false") << true << false << false << false << 2; QTest::newRow("false -> true") << false << true << false << true << 2; QTest::newRow("set fails") << true << false << true << true << 3; QTest::newRow("true -> true") << true << true << false << true << 3; QTest::newRow("false -> false") << false << false << false << false << 3; QTest::newRow("true -> true") << true << true << false << true << 2; QTest::newRow("false -> false") << false << false << false << false << 2; QTest::newRow("false -> true, fingerCount 0") << false << true << true << false << 0; // TODO: is this a fail in libinput? //QTest::newRow("false -> true, fingerCount 1") << false << true << true << false << 1; } void TestLibinputDevice::testLmrTapButtonMap() { libinput_device device; QFETCH(bool, initValue); QFETCH(bool, setShouldFail); QFETCH(int, fingerCount); device.tapFingerCount = fingerCount; device.tapButtonMap = initValue ? LIBINPUT_CONFIG_TAP_MAP_LMR : LIBINPUT_CONFIG_TAP_MAP_LRM; device.setTapButtonMapReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.lmrTapButtonMap(), initValue); QCOMPARE(d.property("lmrTapButtonMap").toBool(), initValue); QSignalSpy tapButtonMapChangedSpy(&d, &Device::tapButtonMapChanged); QVERIFY(tapButtonMapChangedSpy.isValid()); QFETCH(bool, setValue); d.setLmrTapButtonMap(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.lmrTapButtonMap(), expectedValue); QCOMPARE(d.property("lmrTapButtonMap").toBool(), expectedValue); QCOMPARE(tapButtonMapChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testDisableWhileTyping_data() { QTest::addColumn("initValue"); QTest::addColumn("setValue"); QTest::addColumn("setShouldFail"); QTest::addColumn("expectedValue"); QTest::addColumn("supportsDisableWhileTyping"); QTest::newRow("true -> false") << true << false << false << false << true; QTest::newRow("false -> true") << false << true << false << true << true; QTest::newRow("set fails") << true << false << true << true << true; QTest::newRow("true -> true") << true << true << false << true << true; QTest::newRow("false -> false") << false << false << false << false << true; QTest::newRow("false -> true, unsupported") << false << true << true << false << false; } void TestLibinputDevice::testDisableWhileTyping() { libinput_device device; QFETCH(bool, initValue); QFETCH(bool, setShouldFail); QFETCH(bool, supportsDisableWhileTyping); device.supportsDisableWhileTyping = supportsDisableWhileTyping; device.disableWhileTyping = initValue ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED; device.setDisableWhileTypingReturnValue = setShouldFail; Device d(&device); QCOMPARE(d.isDisableWhileTyping(), initValue); QCOMPARE(d.property("disableWhileTyping").toBool(), initValue); QSignalSpy disableWhileTypingChangedSpy(&d, &Device::disableWhileTypingChanged); QVERIFY(disableWhileTypingChangedSpy.isValid()); QFETCH(bool, setValue); d.setDisableWhileTyping(setValue); QFETCH(bool, expectedValue); QCOMPARE(d.isDisableWhileTyping(), expectedValue); QCOMPARE(d.property("disableWhileTyping").toBool(), expectedValue); QCOMPARE(disableWhileTypingChangedSpy.isEmpty(), initValue == expectedValue); } void TestLibinputDevice::testLoadEnabled_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("false -> true") << false << true; QTest::newRow("true -> false") << true << false; QTest::newRow("true -> true") << true << true; QTest::newRow("false -> false") << false << false; } void TestLibinputDevice::testLoadEnabled() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(bool, configValue); QFETCH(bool, initValue); inputConfig.writeEntry("Enabled", configValue); libinput_device device; device.supportsDisableEvents = true; device.enabled = initValue; device.setEnableModeReturnValue = false; Device d(&device); QCOMPARE(d.isEnabled(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.isEnabled(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.isEnabled(), configValue); // and try to store if (configValue != initValue) { d.setEnabled(initValue); QCOMPARE(inputConfig.readEntry("Enabled", configValue), initValue); } } void TestLibinputDevice::testLoadPointerAcceleration_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("-0.2 -> 0.9") << -0.2 << 0.9; QTest::newRow("0.0 -> -1.0") << 0.0 << -1.0; QTest::newRow("0.123 -> -0.456") << 0.123 << -0.456; QTest::newRow("0.7 -> 0.7") << 0.7 << 0.7; } void TestLibinputDevice::testLoadPointerAcceleration() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(qreal, configValue); QFETCH(qreal, initValue); inputConfig.writeEntry("PointerAcceleration", configValue); libinput_device device; device.supportsPointerAcceleration = true; device.pointerAcceleration = initValue; device.setPointerAccelerationReturnValue = false; Device d(&device); QCOMPARE(d.pointerAcceleration(), initValue); QCOMPARE(d.property("pointerAcceleration").toReal(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.pointerAcceleration(), initValue); QCOMPARE(d.property("pointerAcceleration").toReal(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.pointerAcceleration(), configValue); QCOMPARE(d.property("pointerAcceleration").toReal(), configValue); // and try to store if (configValue != initValue) { d.setPointerAcceleration(initValue); QCOMPARE(inputConfig.readEntry("PointerAcceleration", configValue), initValue); } } void TestLibinputDevice::testLoadPointerAccelerationProfile_data() { QTest::addColumn("initValue"); QTest::addColumn("initValuePropNameString"); QTest::addColumn("configValue"); QTest::addColumn("configValuePropNameString"); QTest::newRow("pointerAccelerationProfileFlat -> pointerAccelerationProfileAdaptive") << (quint32) LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT << "pointerAccelerationProfileFlat" << (quint32) LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE << "pointerAccelerationProfileAdaptive"; QTest::newRow("pointerAccelerationProfileAdaptive -> pointerAccelerationProfileFlat") << (quint32) LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE << "pointerAccelerationProfileAdaptive" << (quint32) LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT << "pointerAccelerationProfileFlat"; QTest::newRow("pointerAccelerationProfileAdaptive -> pointerAccelerationProfileAdaptive") << (quint32) LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE << "pointerAccelerationProfileAdaptive" << (quint32) LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE << "pointerAccelerationProfileAdaptive"; } void TestLibinputDevice::testLoadPointerAccelerationProfile() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(quint32, initValue); QFETCH(quint32, configValue); QFETCH(QString, initValuePropNameString); QFETCH(QString, configValuePropNameString); QByteArray initValuePropName = initValuePropNameString.toLatin1(); QByteArray configValuePropName = configValuePropNameString.toLatin1(); inputConfig.writeEntry("PointerAccelerationProfile", configValue); libinput_device device; device.supportedPointerAccelerationProfiles = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT | LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; device.pointerAccelerationProfile = (libinput_config_accel_profile) initValue; device.setPointerAccelerationProfileReturnValue = false; Device d(&device); QCOMPARE(d.property(initValuePropName).toBool(), true); QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.property(initValuePropName).toBool(), true); QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.property(initValuePropName).toBool(), initValue == configValue); QCOMPARE(d.property(configValuePropName).toBool(), true); // and try to store if (configValue != initValue) { d.setProperty(initValuePropName, true); QCOMPARE(inputConfig.readEntry("PointerAccelerationProfile", configValue), initValue); } } void TestLibinputDevice::testLoadTapToClick_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("false -> true") << false << true; QTest::newRow("true -> false") << true << false; QTest::newRow("true -> true") << true << true; QTest::newRow("false -> false") << false << false; } void TestLibinputDevice::testLoadTapToClick() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(bool, configValue); QFETCH(bool, initValue); inputConfig.writeEntry("TapToClick", configValue); libinput_device device; device.tapFingerCount = 2; device.tapToClick = initValue; device.setTapToClickReturnValue = false; Device d(&device); QCOMPARE(d.isTapToClick(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.isTapToClick(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.isTapToClick(), configValue); // and try to store if (configValue != initValue) { d.setTapToClick(initValue); QCOMPARE(inputConfig.readEntry("TapToClick", configValue), initValue); } } void TestLibinputDevice::testLoadTapAndDrag_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("false -> true") << false << true; QTest::newRow("true -> false") << true << false; QTest::newRow("true -> true") << true << true; QTest::newRow("false -> false") << false << false; } void TestLibinputDevice::testLoadTapAndDrag() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(bool, configValue); QFETCH(bool, initValue); inputConfig.writeEntry("TapAndDrag", configValue); libinput_device device; device.tapAndDrag = initValue; device.setTapAndDragReturnValue = false; Device d(&device); QCOMPARE(d.isTapAndDrag(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.isTapAndDrag(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.isTapAndDrag(), configValue); // and try to store if (configValue != initValue) { d.setTapAndDrag(initValue); QCOMPARE(inputConfig.readEntry("TapAndDrag", configValue), initValue); } } void TestLibinputDevice::testLoadTapDragLock_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("false -> true") << false << true; QTest::newRow("true -> false") << true << false; QTest::newRow("true -> true") << true << true; QTest::newRow("false -> false") << false << false; } void TestLibinputDevice::testLoadTapDragLock() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(bool, configValue); QFETCH(bool, initValue); inputConfig.writeEntry("TapDragLock", configValue); libinput_device device; device.tapDragLock = initValue; device.setTapDragLockReturnValue = false; Device d(&device); QCOMPARE(d.isTapDragLock(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.isTapDragLock(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.isTapDragLock(), configValue); // and try to store if (configValue != initValue) { d.setTapDragLock(initValue); QCOMPARE(inputConfig.readEntry("TapDragLock", configValue), initValue); } } void TestLibinputDevice::testLoadMiddleButtonEmulation_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("false -> true") << false << true; QTest::newRow("true -> false") << true << false; QTest::newRow("true -> true") << true << true; QTest::newRow("false -> false") << false << false; } void TestLibinputDevice::testLoadMiddleButtonEmulation() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(bool, configValue); QFETCH(bool, initValue); inputConfig.writeEntry("MiddleButtonEmulation", configValue); libinput_device device; device.supportsMiddleEmulation = true; device.middleEmulation = initValue; device.setMiddleEmulationReturnValue = false; Device d(&device); QCOMPARE(d.isMiddleEmulation(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.isMiddleEmulation(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.isMiddleEmulation(), configValue); // and try to store if (configValue != initValue) { d.setMiddleEmulation(initValue); QCOMPARE(inputConfig.readEntry("MiddleButtonEmulation", configValue), initValue); } } void TestLibinputDevice::testLoadNaturalScroll_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("false -> true") << false << true; QTest::newRow("true -> false") << true << false; QTest::newRow("true -> true") << true << true; QTest::newRow("false -> false") << false << false; } void TestLibinputDevice::testLoadNaturalScroll() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(bool, configValue); QFETCH(bool, initValue); inputConfig.writeEntry("NaturalScroll", configValue); libinput_device device; device.supportsNaturalScroll = true; device.naturalScroll = initValue; device.setNaturalScrollReturnValue = false; Device d(&device); QCOMPARE(d.isNaturalScroll(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.isNaturalScroll(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.isNaturalScroll(), configValue); // and try to store if (configValue != initValue) { d.setNaturalScroll(initValue); QCOMPARE(inputConfig.readEntry("NaturalScroll", configValue), initValue); } } void TestLibinputDevice::testLoadScrollMethod_data() { QTest::addColumn("initValue"); QTest::addColumn("initValuePropNameString"); QTest::addColumn("configValue"); QTest::addColumn("configValuePropNameString"); QTest::newRow("scrollTwoFinger -> scrollEdge") << (quint32) LIBINPUT_CONFIG_SCROLL_2FG << "scrollTwoFinger" << (quint32) LIBINPUT_CONFIG_SCROLL_EDGE << "scrollEdge"; QTest::newRow("scrollOnButtonDown -> scrollTwoFinger") << (quint32) LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN << "scrollOnButtonDown" << (quint32) LIBINPUT_CONFIG_SCROLL_2FG << "scrollTwoFinger"; QTest::newRow("scrollEdge -> scrollEdge") << (quint32) LIBINPUT_CONFIG_SCROLL_EDGE << "scrollEdge" << (quint32) LIBINPUT_CONFIG_SCROLL_EDGE << "scrollEdge"; } void TestLibinputDevice::testLoadScrollMethod() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(quint32, initValue); QFETCH(quint32, configValue); QFETCH(QString, initValuePropNameString); QFETCH(QString, configValuePropNameString); QByteArray initValuePropName = initValuePropNameString.toLatin1(); QByteArray configValuePropName = configValuePropNameString.toLatin1(); inputConfig.writeEntry("ScrollMethod", configValue); libinput_device device; device.supportedScrollMethods = LIBINPUT_CONFIG_SCROLL_2FG | LIBINPUT_CONFIG_SCROLL_EDGE | LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; device.scrollMethod = (libinput_config_scroll_method) initValue; device.setScrollMethodReturnValue = false; Device d(&device); QCOMPARE(d.property(initValuePropName).toBool(), true); QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.property(initValuePropName).toBool(), true); QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.property(initValuePropName).toBool(), initValue == configValue); QCOMPARE(d.property(configValuePropName).toBool(), true); // and try to store if (configValue != initValue) { d.setProperty(initValuePropName, true); QCOMPARE(inputConfig.readEntry("ScrollMethod", configValue), initValue); } } void TestLibinputDevice::testLoadScrollButton_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("BTN_LEFT -> BTN_RIGHT") << quint32(BTN_LEFT) << quint32(BTN_RIGHT); QTest::newRow("BTN_LEFT -> BTN_LEFT") << quint32(BTN_LEFT) << quint32(BTN_LEFT); } void TestLibinputDevice::testLoadScrollButton() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(quint32, configValue); QFETCH(quint32, initValue); inputConfig.writeEntry("ScrollButton", configValue); libinput_device device; device.supportedScrollMethods = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; device.scrollMethod = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; device.scrollButton = initValue; device.setScrollButtonReturnValue = false; Device d(&device); QCOMPARE(d.isScrollOnButtonDown(), true); QCOMPARE(d.scrollButton(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.isScrollOnButtonDown(), true); QCOMPARE(d.scrollButton(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.isScrollOnButtonDown(), true); QCOMPARE(d.scrollButton(), configValue); // and try to store if (configValue != initValue) { d.setScrollButton(initValue); QCOMPARE(inputConfig.readEntry("ScrollButton", configValue), initValue); } } void TestLibinputDevice::testLoadLeftHanded_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("false -> true") << false << true; QTest::newRow("true -> false") << true << false; QTest::newRow("true -> true") << true << true; QTest::newRow("false -> false") << false << false; } void TestLibinputDevice::testLoadLeftHanded() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(bool, configValue); QFETCH(bool, initValue); inputConfig.writeEntry("LeftHanded", configValue); libinput_device device; device.supportsLeftHanded = true; device.leftHanded = initValue; device.setLeftHandedReturnValue = false; Device d(&device); QCOMPARE(d.isLeftHanded(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.isLeftHanded(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.isLeftHanded(), configValue); // and try to store if (configValue != initValue) { d.setLeftHanded(initValue); QCOMPARE(inputConfig.readEntry("LeftHanded", configValue), initValue); } } void TestLibinputDevice::testLoadDisableWhileTyping_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("false -> true") << false << true; QTest::newRow("true -> false") << true << false; QTest::newRow("true -> true") << true << true; QTest::newRow("false -> false") << false << false; } void TestLibinputDevice::testLoadDisableWhileTyping() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(bool, configValue); QFETCH(bool, initValue); inputConfig.writeEntry("DisableWhileTyping", configValue); libinput_device device; device.supportsDisableWhileTyping = true; device.disableWhileTyping = initValue ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED; device.setDisableWhileTypingReturnValue = false; Device d(&device); QCOMPARE(d.isDisableWhileTyping(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.isDisableWhileTyping(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.isDisableWhileTyping(), configValue); // and try to store if (configValue != initValue) { d.setDisableWhileTyping(initValue); QCOMPARE(inputConfig.readEntry("DisableWhileTyping", configValue), initValue); } } void TestLibinputDevice::testLoadLmrTapButtonMap_data() { QTest::addColumn("initValue"); QTest::addColumn("configValue"); QTest::newRow("false -> true") << false << true; QTest::newRow("true -> false") << true << false; QTest::newRow("true -> true") << true << true; QTest::newRow("false -> false") << false << false; } void TestLibinputDevice::testLoadLmrTapButtonMap() { auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup inputConfig(config, QStringLiteral("Test")); QFETCH(bool, configValue); QFETCH(bool, initValue); inputConfig.writeEntry("LmrTapButtonMap", configValue); libinput_device device; device.tapFingerCount = 3; device.tapButtonMap = initValue ? LIBINPUT_CONFIG_TAP_MAP_LMR : LIBINPUT_CONFIG_TAP_MAP_LRM; device.setTapButtonMapReturnValue = false; Device d(&device); QCOMPARE(d.lmrTapButtonMap(), initValue); // no config group set, should not change d.loadConfiguration(); QCOMPARE(d.lmrTapButtonMap(), initValue); // set the group d.setConfig(inputConfig); d.loadConfiguration(); QCOMPARE(d.lmrTapButtonMap(), configValue); // and try to store if (configValue != initValue) { d.setLmrTapButtonMap(initValue); QCOMPARE(inputConfig.readEntry("LmrTapButtonMap", configValue), initValue); } } +void TestLibinputDevice::testScreenId() +{ + libinput_device device; + Device d(&device); + QCOMPARE(d.screenId(), 0); + d.setScreenId(1); + QCOMPARE(d.screenId(), 1); +} + +void TestLibinputDevice::testOrientation_data() +{ + QTest::addColumn("orientation"); + QTest::addColumn("m11"); + QTest::addColumn("m12"); + QTest::addColumn("m13"); + QTest::addColumn("m21"); + QTest::addColumn("m22"); + QTest::addColumn("m23"); + QTest::addColumn("defaultIsIdentity"); + + QTest::newRow("Primary") << Qt::PrimaryOrientation << 1.0f << 2.0f << 3.0f << 4.0f << 5.0f << 6.0f << false; + QTest::newRow("Landscape") << Qt::LandscapeOrientation << 1.0f << 2.0f << 3.0f << 4.0f << 5.0f << 6.0f << false; + QTest::newRow("Portrait") << Qt::PortraitOrientation << 0.0f << -1.0f << 1.0f << 1.0f << 0.0f << 0.0f << true; + QTest::newRow("InvertedLandscape") << Qt::InvertedLandscapeOrientation << -1.0f << 0.0f << 1.0f << 0.0f << -1.0f << 1.0f << true; + QTest::newRow("InvertedPortrait") << Qt::InvertedPortraitOrientation << 0.0f << 1.0f << 0.0f << -1.0f << 0.0f << 1.0f << true; +} + +void TestLibinputDevice::testOrientation() +{ + libinput_device device; + device.supportsCalibrationMatrix = true; + device.defaultCalibrationMatrix = std::array{{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}}; + QFETCH(bool, defaultIsIdentity); + device.defaultCalibrationMatrixIsIdentity = defaultIsIdentity; + Device d(&device); + QFETCH(Qt::ScreenOrientation, orientation); + d.setOrientation(orientation); + QTEST(device.calibrationMatrix[0], "m11"); + QTEST(device.calibrationMatrix[1], "m12"); + QTEST(device.calibrationMatrix[2], "m13"); + QTEST(device.calibrationMatrix[3], "m21"); + QTEST(device.calibrationMatrix[4], "m22"); + QTEST(device.calibrationMatrix[5], "m23"); +} + +void TestLibinputDevice::testCalibrationWithDefault() +{ + libinput_device device; + device.supportsCalibrationMatrix = true; + device.defaultCalibrationMatrix = std::array{{2.0, 3.0, 0.0, 4.0, 5.0, 0.0}}; + device.defaultCalibrationMatrixIsIdentity = false; + Device d(&device); + d.setOrientation(Qt::PortraitOrientation); + QCOMPARE(device.calibrationMatrix[0], 3.0f); + QCOMPARE(device.calibrationMatrix[1], -2.0f); + QCOMPARE(device.calibrationMatrix[2], 2.0f); + QCOMPARE(device.calibrationMatrix[3], 5.0f); + QCOMPARE(device.calibrationMatrix[4], -4.0f); + QCOMPARE(device.calibrationMatrix[5], 4.0f); +} + +void TestLibinputDevice::testSwitch_data() +{ + QTest::addColumn("lid"); + QTest::addColumn("tablet"); + + QTest::newRow("lid") << true << false; + QTest::newRow("tablet") << false << true; +} + +void TestLibinputDevice::testSwitch() +{ + libinput_device device; + device.switchDevice = true; + QFETCH(bool, lid); + QFETCH(bool, tablet); + device.lidSwitch = lid; + device.tabletModeSwitch = tablet; + + Device d(&device); + QCOMPARE(d.isSwitch(), true); + QCOMPARE(d.isLidSwitch(), lid); + QCOMPARE(d.property("lidSwitch").toBool(), lid); + QCOMPARE(d.isTabletModeSwitch(), tablet); + QCOMPARE(d.property("tabletModeSwitch").toBool(), tablet); +} + QTEST_GUILESS_MAIN(TestLibinputDevice) #include "device_test.moc" diff --git a/autotests/libinput/input_event_test.cpp b/autotests/libinput/input_event_test.cpp index f588016f9..c9373aa3c 100644 --- a/autotests/libinput/input_event_test.cpp +++ b/autotests/libinput/input_event_test.cpp @@ -1,153 +1,184 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "mock_libinput.h" #include "../../libinput/device.h" #include "../input_event.h" #include +Q_DECLARE_METATYPE(KWin::SwitchEvent::State); + using namespace KWin; using namespace KWin::LibInput; class InputEventsTest : public QObject { Q_OBJECT private Q_SLOTS: void testInitMouseEvent_data(); void testInitMouseEvent(); void testInitKeyEvent_data(); void testInitKeyEvent(); void testInitWheelEvent_data(); void testInitWheelEvent(); + void testInitSwitchEvent_data(); + void testInitSwitchEvent(); }; void InputEventsTest::testInitMouseEvent_data() { QTest::addColumn("type"); QTest::newRow("Press") << QEvent::MouseButtonPress; QTest::newRow("Release") << QEvent::MouseButtonRelease; QTest::newRow("Move") << QEvent::MouseMove; } void InputEventsTest::testInitMouseEvent() { // this test verifies that a MouseEvent is constructed correctly // first create the test LibInput::Device libinput_device device; Device d(&device); QFETCH(QEvent::Type, type); // now create our own event MouseEvent event(type, QPointF(100, 200), Qt::LeftButton, Qt::LeftButton | Qt::RightButton, Qt::ShiftModifier | Qt::ControlModifier, 300, QSizeF(1, 2), QSizeF(3, 4), quint64(-1), &d); // and verify the contract of QMouseEvent QCOMPARE(event.type(), type); QCOMPARE(event.globalPos(), QPoint(100, 200)); QCOMPARE(event.screenPos(), QPointF(100, 200)); QCOMPARE(event.localPos(), QPointF(100, 200)); QCOMPARE(event.button(), Qt::LeftButton); QCOMPARE(event.buttons(), Qt::LeftButton | Qt::RightButton); QCOMPARE(event.modifiers(), Qt::ShiftModifier | Qt::ControlModifier); QCOMPARE(event.timestamp(), 300ul); // and our custom argument QCOMPARE(event.device(), &d); QCOMPARE(event.delta(), QSizeF(1, 2)); QCOMPARE(event.deltaUnaccelerated(), QSizeF(3, 4)); QCOMPARE(event.timestampMicroseconds(), quint64(-1)); } void InputEventsTest::testInitKeyEvent_data() { QTest::addColumn("type"); QTest::addColumn("autorepeat"); QTest::newRow("Press") << QEvent::KeyPress << false; QTest::newRow("Repeat") << QEvent::KeyPress << true; QTest::newRow("Release") << QEvent::KeyRelease << false; } void InputEventsTest::testInitKeyEvent() { // this test verifies that a KeyEvent is constructed correctly // first create the test LibInput::Device libinput_device device; Device d(&device); // setup event QFETCH(QEvent::Type, type); QFETCH(bool, autorepeat); KeyEvent event(type, Qt::Key_Space, Qt::ShiftModifier | Qt::ControlModifier, 200, 300, QStringLiteral(" "), autorepeat, 400, &d); // and verify the contract of QKeyEvent QCOMPARE(event.type(), type); QCOMPARE(event.isAutoRepeat(), autorepeat); QCOMPARE(event.key(), int(Qt::Key_Space)); QCOMPARE(event.nativeScanCode(), 200u); QCOMPARE(event.nativeVirtualKey(), 300u); QCOMPARE(event.text(), QStringLiteral(" ")); QCOMPARE(event.count(), 1); QCOMPARE(event.nativeModifiers(), 0u); QCOMPARE(event.modifiers(), Qt::ShiftModifier | Qt::ControlModifier); QCOMPARE(event.timestamp(), 400ul); // and our custom argument QCOMPARE(event.device(), &d); } void InputEventsTest::testInitWheelEvent_data() { QTest::addColumn("orientation"); QTest::addColumn("delta"); QTest::addColumn("expectedAngleDelta"); QTest::newRow("horiz") << Qt::Horizontal << 3.0 << QPoint(3, 0); QTest::newRow("vert") << Qt::Vertical << 2.0 << QPoint(0, 2); } void InputEventsTest::testInitWheelEvent() { // this test verifies that a WheelEvent is constructed correctly // first create the test LibInput::Device libinput_device device; Device d(&device); // setup event QFETCH(Qt::Orientation, orientation); QFETCH(qreal, delta); WheelEvent event(QPointF(100, 200), delta, orientation, Qt::LeftButton | Qt::RightButton, Qt::ShiftModifier | Qt::ControlModifier, 300, &d); // compare QWheelEvent contract QCOMPARE(event.type(), QEvent::Wheel); QCOMPARE(event.posF(), QPointF(100, 200)); QCOMPARE(event.globalPosF(), QPointF(100, 200)); QCOMPARE(event.buttons(), Qt::LeftButton | Qt::RightButton); QCOMPARE(event.modifiers(), Qt::ShiftModifier | Qt::ControlModifier); QCOMPARE(event.timestamp(), 300ul); QTEST(event.angleDelta(), "expectedAngleDelta"); // and our custom argument QCOMPARE(event.device(), &d); } +void InputEventsTest::testInitSwitchEvent_data() +{ + QTest::addColumn("state"); + QTest::addColumn("timestamp"); + QTest::addColumn("micro"); + + QTest::newRow("on") << SwitchEvent::State::On << 23u << quint64{23456790}; + QTest::newRow("off") << SwitchEvent::State::Off << 456892u << quint64{45689235987}; +} + +void InputEventsTest::testInitSwitchEvent() +{ + // this test verifies that a SwitchEvent is constructed correctly + libinput_device device; + Device d(&device); + + QFETCH(SwitchEvent::State, state); + QFETCH(quint32, timestamp); + QFETCH(quint64, micro); + SwitchEvent event(state, timestamp, micro, &d); + + QCOMPARE(event.state(), state); + QCOMPARE(event.timestamp(), ulong(timestamp)); + QCOMPARE(event.timestampMicroseconds(), micro); + QCOMPARE(event.device(), &d); +} + QTEST_GUILESS_MAIN(InputEventsTest) #include "input_event_test.moc" diff --git a/autotests/libinput/mock_libinput.cpp b/autotests/libinput/mock_libinput.cpp index f416e4e07..a412078dd 100644 --- a/autotests/libinput/mock_libinput.cpp +++ b/autotests/libinput/mock_libinput.cpp @@ -1,796 +1,858 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include #include "mock_libinput.h" #include #include int libinput_device_keyboard_has_key(struct libinput_device *device, uint32_t code) { return device->keys.contains(code); } int libinput_device_has_capability(struct libinput_device *device, enum libinput_device_capability capability) { switch (capability) { case LIBINPUT_DEVICE_CAP_KEYBOARD: return device->keyboard; case LIBINPUT_DEVICE_CAP_POINTER: return device->pointer; case LIBINPUT_DEVICE_CAP_TOUCH: return device->touch; case LIBINPUT_DEVICE_CAP_GESTURE: return device->gestureSupported; case LIBINPUT_DEVICE_CAP_TABLET_TOOL: return device->tabletTool; + case LIBINPUT_DEVICE_CAP_SWITCH: + return device->switchDevice; default: return 0; } } const char *libinput_device_get_name(struct libinput_device *device) { return device->name.constData(); } const char *libinput_device_get_sysname(struct libinput_device *device) { return device->sysName.constData(); } const char *libinput_device_get_output_name(struct libinput_device *device) { return device->outputName.constData(); } unsigned int libinput_device_get_id_product(struct libinput_device *device) { return device->product; } unsigned int libinput_device_get_id_vendor(struct libinput_device *device) { return device->vendor; } int libinput_device_config_tap_get_finger_count(struct libinput_device *device) { return device->tapFingerCount; } enum libinput_config_tap_state libinput_device_config_tap_get_enabled(struct libinput_device *device) { if (device->tapToClick) { return LIBINPUT_CONFIG_TAP_ENABLED; } else { return LIBINPUT_CONFIG_TAP_DISABLED; } } enum libinput_config_status libinput_device_config_tap_set_enabled(struct libinput_device *device, enum libinput_config_tap_state enable) { if (device->setTapToClickReturnValue == 0) { device->tapToClick = (enable == LIBINPUT_CONFIG_TAP_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_tap_state libinput_device_config_tap_get_default_enabled(struct libinput_device *device) { if (device->tapEnabledByDefault) { return LIBINPUT_CONFIG_TAP_ENABLED; } else { return LIBINPUT_CONFIG_TAP_DISABLED; } } enum libinput_config_drag_state libinput_device_config_tap_get_default_drag_enabled(struct libinput_device *device) { if (device->tapAndDragEnabledByDefault) { return LIBINPUT_CONFIG_DRAG_ENABLED; } else { return LIBINPUT_CONFIG_DRAG_DISABLED; } } enum libinput_config_drag_state libinput_device_config_tap_get_drag_enabled(struct libinput_device *device) { if (device->tapAndDrag) { return LIBINPUT_CONFIG_DRAG_ENABLED; } else { return LIBINPUT_CONFIG_DRAG_DISABLED; } } enum libinput_config_status libinput_device_config_tap_set_drag_enabled(struct libinput_device *device, enum libinput_config_drag_state enable) { if (device->setTapAndDragReturnValue == 0) { device->tapAndDrag = (enable == LIBINPUT_CONFIG_DRAG_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_drag_lock_state libinput_device_config_tap_get_default_drag_lock_enabled(struct libinput_device *device) { if (device->tapDragLockEnabledByDefault) { return LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; } else { return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; } } enum libinput_config_drag_lock_state libinput_device_config_tap_get_drag_lock_enabled(struct libinput_device *device) { if (device->tapDragLock) { return LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; } else { return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; } } enum libinput_config_status libinput_device_config_tap_set_drag_lock_enabled(struct libinput_device *device, enum libinput_config_drag_lock_state enable) { if (device->setTapDragLockReturnValue == 0) { device->tapDragLock = (enable == LIBINPUT_CONFIG_DRAG_LOCK_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } int libinput_device_config_dwt_is_available(struct libinput_device *device) { return device->supportsDisableWhileTyping; } enum libinput_config_status libinput_device_config_dwt_set_enabled(struct libinput_device *device, enum libinput_config_dwt_state state) { if (device->setDisableWhileTypingReturnValue == 0) { if (!device->supportsDisableWhileTyping) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->disableWhileTyping = state; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_dwt_state libinput_device_config_dwt_get_enabled(struct libinput_device *device) { return device->disableWhileTyping; } enum libinput_config_dwt_state libinput_device_config_dwt_get_default_enabled(struct libinput_device *device) { return device->disableWhileTypingEnabledByDefault; } int libinput_device_config_accel_is_available(struct libinput_device *device) { return device->supportsPointerAcceleration; } int libinput_device_config_calibration_has_matrix(struct libinput_device *device) { return device->supportsCalibrationMatrix; } +enum libinput_config_status libinput_device_config_calibration_set_matrix(struct libinput_device *device, const float matrix[6]) +{ + for (std::size_t i = 0; i < 6; i++) { + device->calibrationMatrix[i] = matrix[i]; + } + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + +int libinput_device_config_calibration_get_default_matrix(struct libinput_device *device, float matrix[6]) +{ + for (std::size_t i = 0; i < 6; i++) { + matrix[i] = device->defaultCalibrationMatrix[i]; + } + return device->defaultCalibrationMatrixIsIdentity ? 0 : 1; +} + int libinput_device_config_left_handed_is_available(struct libinput_device *device) { return device->supportsLeftHanded; } uint32_t libinput_device_config_send_events_get_modes(struct libinput_device *device) { uint32_t modes = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; if (device->supportsDisableEvents) { modes |= LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } if (device->supportsDisableEventsOnExternalMouse) { modes |= LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; } return modes; } int libinput_device_config_left_handed_get(struct libinput_device *device) { return device->leftHanded; } double libinput_device_config_accel_get_default_speed(struct libinput_device *device) { return device->defaultPointerAcceleration; } int libinput_device_config_left_handed_get_default(struct libinput_device *device) { return device->leftHandedEnabledByDefault; } double libinput_device_config_accel_get_speed(struct libinput_device *device) { return device->pointerAcceleration; } uint32_t libinput_device_config_accel_get_profiles(struct libinput_device *device) { return device->supportedPointerAccelerationProfiles; } enum libinput_config_accel_profile libinput_device_config_accel_get_default_profile(struct libinput_device *device) { return device->defaultPointerAccelerationProfile; } enum libinput_config_status libinput_device_config_accel_set_profile(struct libinput_device *device, enum libinput_config_accel_profile profile) { if (device->setPointerAccelerationProfileReturnValue == 0) { if (!(device->supportedPointerAccelerationProfiles & profile) && profile!= LIBINPUT_CONFIG_ACCEL_PROFILE_NONE) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->pointerAccelerationProfile = profile; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_accel_profile libinput_device_config_accel_get_profile(struct libinput_device *device) { return device->pointerAccelerationProfile; } uint32_t libinput_device_config_send_events_get_mode(struct libinput_device *device) { if (device->enabled) { return LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; } else { // TODO: disabled on eternal mouse return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } } struct libinput_device *libinput_device_ref(struct libinput_device *device) { return device; } struct libinput_device *libinput_device_unref(struct libinput_device *device) { return device; } int libinput_device_get_size(struct libinput_device *device, double *width, double *height) { if (device->deviceSizeReturnValue) { return device->deviceSizeReturnValue; } if (width) { *width = device->deviceSize.width(); } if (height) { *height = device->deviceSize.height(); } return device->deviceSizeReturnValue; } int libinput_device_pointer_has_button(struct libinput_device *device, uint32_t code) { switch (code) { case BTN_LEFT: return device->supportedButtons.testFlag(Qt::LeftButton); case BTN_MIDDLE: return device->supportedButtons.testFlag(Qt::MiddleButton); case BTN_RIGHT: return device->supportedButtons.testFlag(Qt::RightButton); case BTN_SIDE: return device->supportedButtons.testFlag(Qt::ExtraButton1); case BTN_EXTRA: return device->supportedButtons.testFlag(Qt::ExtraButton2); case BTN_BACK: return device->supportedButtons.testFlag(Qt::BackButton); case BTN_FORWARD: return device->supportedButtons.testFlag(Qt::ForwardButton); case BTN_TASK: return device->supportedButtons.testFlag(Qt::TaskButton); default: return 0; } } enum libinput_config_status libinput_device_config_left_handed_set(struct libinput_device *device, int left_handed) { if (device->setLeftHandedReturnValue == 0) { device->leftHanded = left_handed; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_status libinput_device_config_accel_set_speed(struct libinput_device *device, double speed) { if (device->setPointerAccelerationReturnValue == 0) { device->pointerAcceleration = speed; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_status libinput_device_config_send_events_set_mode(struct libinput_device *device, uint32_t mode) { if (device->setEnableModeReturnValue == 0) { device->enabled = (mode == LIBINPUT_CONFIG_SEND_EVENTS_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_event_type libinput_event_get_type(struct libinput_event *event) { return event->type; } struct libinput_device *libinput_event_get_device(struct libinput_event *event) { return event->device; } void libinput_event_destroy(struct libinput_event *event) { delete event; } struct libinput_event_keyboard *libinput_event_get_keyboard_event(struct libinput_event *event) { if (event->type == LIBINPUT_EVENT_KEYBOARD_KEY) { return reinterpret_cast(event); } return nullptr; } struct libinput_event_pointer *libinput_event_get_pointer_event(struct libinput_event *event) { if (event->type == LIBINPUT_EVENT_POINTER_MOTION || event->type == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE || event->type == LIBINPUT_EVENT_POINTER_BUTTON || event->type == LIBINPUT_EVENT_POINTER_AXIS) { return reinterpret_cast(event); } return nullptr; } struct libinput_event_touch *libinput_event_get_touch_event(struct libinput_event *event) { if (event->type == LIBINPUT_EVENT_TOUCH_DOWN || event->type == LIBINPUT_EVENT_TOUCH_UP || event->type == LIBINPUT_EVENT_TOUCH_MOTION || event->type == LIBINPUT_EVENT_TOUCH_CANCEL || event->type == LIBINPUT_EVENT_TOUCH_FRAME) { return reinterpret_cast(event); } return nullptr; } struct libinput_event_gesture *libinput_event_get_gesture_event(struct libinput_event *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_BEGIN || event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_PINCH_END || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_END) { return reinterpret_cast(event); } return nullptr; } int libinput_event_gesture_get_cancelled(struct libinput_event_gesture *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_END || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_END) { return event->cancelled; } return 0; } uint32_t libinput_event_gesture_get_time(struct libinput_event_gesture *event) { return event->time; } int libinput_event_gesture_get_finger_count(struct libinput_event_gesture *event) { return event->fingerCount; } double libinput_event_gesture_get_dx(struct libinput_event_gesture *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE) { return event->delta.width(); } return 0.0; } double libinput_event_gesture_get_dy(struct libinput_event_gesture *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE) { return event->delta.height(); } return 0.0; } double libinput_event_gesture_get_scale(struct libinput_event_gesture *event) { switch (event->type) { case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: return 1.0; case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: case LIBINPUT_EVENT_GESTURE_PINCH_END: return event->scale; default: return 0.0; } } double libinput_event_gesture_get_angle_delta(struct libinput_event_gesture *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE) { return event->angleDelta; } return 0.0; } uint32_t libinput_event_keyboard_get_key(struct libinput_event_keyboard *event) { return event->key; } enum libinput_key_state libinput_event_keyboard_get_key_state(struct libinput_event_keyboard *event) { return event->state; } uint32_t libinput_event_keyboard_get_time(struct libinput_event_keyboard *event) { return event->time; } double libinput_event_pointer_get_absolute_x(struct libinput_event_pointer *event) { return event->absolutePos.x(); } double libinput_event_pointer_get_absolute_y(struct libinput_event_pointer *event) { return event->absolutePos.y(); } double libinput_event_pointer_get_absolute_x_transformed(struct libinput_event_pointer *event, uint32_t width) { double deviceWidth = 0.0; double deviceHeight = 0.0; libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); return event->absolutePos.x() / deviceWidth * width; } double libinput_event_pointer_get_absolute_y_transformed(struct libinput_event_pointer *event, uint32_t height) { double deviceWidth = 0.0; double deviceHeight = 0.0; libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); return event->absolutePos.y() / deviceHeight * height; } double libinput_event_pointer_get_dx(struct libinput_event_pointer *event) { return event->delta.width(); } double libinput_event_pointer_get_dy(struct libinput_event_pointer *event) { return event->delta.height(); } double libinput_event_pointer_get_dx_unaccelerated(struct libinput_event_pointer *event) { return event->delta.width(); } double libinput_event_pointer_get_dy_unaccelerated(struct libinput_event_pointer *event) { return event->delta.height(); } uint32_t libinput_event_pointer_get_time(struct libinput_event_pointer *event) { return event->time; } uint64_t libinput_event_pointer_get_time_usec(struct libinput_event_pointer *event) { return quint64(event->time * 1000); } uint32_t libinput_event_pointer_get_button(struct libinput_event_pointer *event) { return event->button; } enum libinput_button_state libinput_event_pointer_get_button_state(struct libinput_event_pointer *event) { return event->buttonState; } int libinput_event_pointer_has_axis(struct libinput_event_pointer *event, enum libinput_pointer_axis axis) { if (axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) { return event->verticalAxis; } else { return event->horizontalAxis; } } double libinput_event_pointer_get_axis_value(struct libinput_event_pointer *event, enum libinput_pointer_axis axis) { if (axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) { return event->verticalAxisValue; } else { return event->horizontalAxisValue; } } uint32_t libinput_event_touch_get_time(struct libinput_event_touch *event) { return event->time; } double libinput_event_touch_get_x(struct libinput_event_touch *event) { return event->absolutePos.x(); } double libinput_event_touch_get_y(struct libinput_event_touch *event) { return event->absolutePos.y(); } double libinput_event_touch_get_x_transformed(struct libinput_event_touch *event, uint32_t width) { double deviceWidth = 0.0; double deviceHeight = 0.0; libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); return event->absolutePos.x() / deviceWidth * width; } double libinput_event_touch_get_y_transformed(struct libinput_event_touch *event, uint32_t height) { double deviceWidth = 0.0; double deviceHeight = 0.0; libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); return event->absolutePos.y() / deviceHeight * height; } int32_t libinput_event_touch_get_slot(struct libinput_event_touch *event) { return event->slot; } struct libinput *libinput_udev_create_context(const struct libinput_interface *interface, void *user_data, struct udev *udev) { if (!udev) { return nullptr; } Q_UNUSED(interface) Q_UNUSED(user_data) return new libinput; } void libinput_log_set_priority(struct libinput *libinput, enum libinput_log_priority priority) { Q_UNUSED(libinput) Q_UNUSED(priority) } void libinput_log_set_handler(struct libinput *libinput, libinput_log_handler log_handler) { Q_UNUSED(libinput) Q_UNUSED(log_handler) } struct libinput *libinput_unref(struct libinput *libinput) { libinput->refCount--; if (libinput->refCount == 0) { delete libinput; return nullptr; } return libinput; } int libinput_udev_assign_seat(struct libinput *libinput, const char *seat_id) { if (libinput->assignSeatRetVal == 0) { libinput->seat = QByteArray(seat_id); } return libinput->assignSeatRetVal; } int libinput_get_fd(struct libinput *libinput) { Q_UNUSED(libinput) return -1; } int libinput_dispatch(struct libinput *libinput) { Q_UNUSED(libinput) return 0; } struct libinput_event *libinput_get_event(struct libinput *libinput) { Q_UNUSED(libinput) return nullptr; } void libinput_suspend(struct libinput *libinput) { Q_UNUSED(libinput) } int libinput_resume(struct libinput *libinput) { Q_UNUSED(libinput) return 0; } int libinput_device_config_middle_emulation_is_available(struct libinput_device *device) { return device->supportsMiddleEmulation; } enum libinput_config_status libinput_device_config_middle_emulation_set_enabled(struct libinput_device *device, enum libinput_config_middle_emulation_state enable) { if (device->setMiddleEmulationReturnValue == 0) { if (!device->supportsMiddleEmulation) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->middleEmulation = (enable == LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_middle_emulation_state libinput_device_config_middle_emulation_get_enabled(struct libinput_device *device) { if (device->middleEmulation) { return LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; } else { return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; } } enum libinput_config_middle_emulation_state libinput_device_config_middle_emulation_get_default_enabled(struct libinput_device *device) { if (device->middleEmulationEnabledByDefault) { return LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; } else { return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; } } int libinput_device_config_scroll_has_natural_scroll(struct libinput_device *device) { return device->supportsNaturalScroll; } enum libinput_config_status libinput_device_config_scroll_set_natural_scroll_enabled(struct libinput_device *device, int enable) { if (device->setNaturalScrollReturnValue == 0) { if (!device->supportsNaturalScroll) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->naturalScroll = enable; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } int libinput_device_config_scroll_get_natural_scroll_enabled(struct libinput_device *device) { return device->naturalScroll; } int libinput_device_config_scroll_get_default_natural_scroll_enabled(struct libinput_device *device) { return device->naturalScrollEnabledByDefault; } enum libinput_config_tap_button_map libinput_device_config_tap_get_default_button_map(struct libinput_device *device) { return device->defaultTapButtonMap; } enum libinput_config_status libinput_device_config_tap_set_button_map(struct libinput_device *device, enum libinput_config_tap_button_map map) { if (device->setTapButtonMapReturnValue == 0) { if (device->tapFingerCount == 0) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->tapButtonMap = map; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_tap_button_map libinput_device_config_tap_get_button_map(struct libinput_device *device) { return device->tapButtonMap; } uint32_t libinput_device_config_scroll_get_methods(struct libinput_device *device) { return device->supportedScrollMethods; } enum libinput_config_scroll_method libinput_device_config_scroll_get_default_method(struct libinput_device *device) { return device->defaultScrollMethod; } enum libinput_config_status libinput_device_config_scroll_set_method(struct libinput_device *device, enum libinput_config_scroll_method method) { if (device->setScrollMethodReturnValue == 0) { if (!(device->supportedScrollMethods & method) && method != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->scrollMethod = method; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_scroll_method libinput_device_config_scroll_get_method(struct libinput_device *device) { return device->scrollMethod; } enum libinput_config_status libinput_device_config_scroll_set_button(struct libinput_device *device, uint32_t button) { if (device->setScrollButtonReturnValue == 0) { if (!(device->supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN)) { return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; } device->scrollButton = button; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } uint32_t libinput_device_config_scroll_get_button(struct libinput_device *device) { return device->scrollButton; } uint32_t libinput_device_config_scroll_get_default_button(struct libinput_device *device) { return device->defaultScrollButton; } + +int libinput_device_switch_has_switch(struct libinput_device *device, enum libinput_switch sw) +{ + switch (sw) { + case LIBINPUT_SWITCH_LID: + return device->lidSwitch; + case LIBINPUT_SWITCH_TABLET_MODE: + return device->tabletModeSwitch; + default: + Q_UNREACHABLE(); + } + return 0; +} + +struct libinput_event_switch *libinput_event_get_switch_event(struct libinput_event *event) +{ + if (event->type == LIBINPUT_EVENT_SWITCH_TOGGLE) { + return reinterpret_cast(event); + } else { + return nullptr; + } +} + +enum libinput_switch_state libinput_event_switch_get_switch_state(struct libinput_event_switch *event) +{ + switch (event->state) { + case libinput_event_switch::State::On: + return LIBINPUT_SWITCH_STATE_ON; + case libinput_event_switch::State::Off: + return LIBINPUT_SWITCH_STATE_OFF; + default: + Q_UNREACHABLE(); + } +} + +uint32_t libinput_event_switch_get_time(struct libinput_event_switch *event) +{ + return event->time;; +} + +uint64_t libinput_event_switch_get_time_usec(struct libinput_event_switch *event) +{ + return event->timeMicroseconds; +} diff --git a/autotests/libinput/mock_libinput.h b/autotests/libinput/mock_libinput.h index 5444c1727..404b0d64f 100644 --- a/autotests/libinput/mock_libinput.h +++ b/autotests/libinput/mock_libinput.h @@ -1,139 +1,159 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef MOCK_LIBINPUT_H #define MOCK_LIBINPUT_H #include #include #include #include #include +#include + struct libinput_device { bool keyboard = false; bool pointer = false; bool touch = false; bool tabletTool = false; bool gestureSupported = false; + bool switchDevice = false; QByteArray name; QByteArray sysName = QByteArrayLiteral("event0"); QByteArray outputName; quint32 product = 0; quint32 vendor = 0; int tapFingerCount = 0; QSizeF deviceSize; int deviceSizeReturnValue = 0; bool tapEnabledByDefault = false; bool tapToClick = false; bool tapAndDragEnabledByDefault = false; bool tapAndDrag = false; bool tapDragLockEnabledByDefault = false; bool tapDragLock = false; bool supportsDisableWhileTyping = false; bool supportsPointerAcceleration = false; bool supportsLeftHanded = false; bool supportsCalibrationMatrix = false; bool supportsDisableEvents = false; bool supportsDisableEventsOnExternalMouse = false; bool supportsMiddleEmulation = false; bool supportsNaturalScroll = false; quint32 supportedScrollMethods = 0; bool middleEmulationEnabledByDefault = false; bool middleEmulation = false; enum libinput_config_tap_button_map defaultTapButtonMap = LIBINPUT_CONFIG_TAP_MAP_LRM; enum libinput_config_tap_button_map tapButtonMap = LIBINPUT_CONFIG_TAP_MAP_LRM; int setTapButtonMapReturnValue = 0; enum libinput_config_dwt_state disableWhileTypingEnabledByDefault = LIBINPUT_CONFIG_DWT_DISABLED; enum libinput_config_dwt_state disableWhileTyping = LIBINPUT_CONFIG_DWT_DISABLED; int setDisableWhileTypingReturnValue = 0; qreal defaultPointerAcceleration = 0.0; qreal pointerAcceleration = 0.0; int setPointerAccelerationReturnValue = 0; bool leftHandedEnabledByDefault = false; bool leftHanded = false; int setLeftHandedReturnValue = 0; bool naturalScrollEnabledByDefault = false; bool naturalScroll = false; int setNaturalScrollReturnValue = 0; enum libinput_config_scroll_method defaultScrollMethod = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; enum libinput_config_scroll_method scrollMethod = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; int setScrollMethodReturnValue = 0; quint32 defaultScrollButton = 0; quint32 scrollButton = 0; int setScrollButtonReturnValue = 0; Qt::MouseButtons supportedButtons; QVector keys; bool enabled = true; int setEnableModeReturnValue = 0; int setTapToClickReturnValue = 0; int setTapAndDragReturnValue = 0; int setTapDragLockReturnValue = 0; int setMiddleEmulationReturnValue = 0; quint32 supportedPointerAccelerationProfiles = 0; enum libinput_config_accel_profile defaultPointerAccelerationProfile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; enum libinput_config_accel_profile pointerAccelerationProfile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; bool setPointerAccelerationProfileReturnValue = 0; + std::array defaultCalibrationMatrix{{1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f}}; + std::array calibrationMatrix{{1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f}}; + bool defaultCalibrationMatrixIsIdentity = true; + + bool lidSwitch = false; + bool tabletModeSwitch = false; }; struct libinput_event { libinput_device *device = nullptr; libinput_event_type type = LIBINPUT_EVENT_NONE; quint32 time = 0; }; struct libinput_event_keyboard : libinput_event { libinput_event_keyboard() { type = LIBINPUT_EVENT_KEYBOARD_KEY; } libinput_key_state state = LIBINPUT_KEY_STATE_RELEASED; quint32 key = 0; }; struct libinput_event_pointer : libinput_event { libinput_button_state buttonState = LIBINPUT_BUTTON_STATE_RELEASED; quint32 button = 0; bool verticalAxis = false; bool horizontalAxis = false; qreal horizontalAxisValue = 0.0; qreal verticalAxisValue = 0.0; QSizeF delta; QPointF absolutePos; }; struct libinput_event_touch : libinput_event { qint32 slot = -1; QPointF absolutePos; }; struct libinput_event_gesture : libinput_event { int fingerCount = 0; bool cancelled = false; QSizeF delta = QSizeF(0, 0); qreal scale = 0.0; qreal angleDelta = 0.0; }; +struct libinput_event_switch : libinput_event { + enum class State { + Off, + On + }; + State state = State::Off; + quint64 timeMicroseconds = 0; +}; + struct libinput { int refCount = 1; QByteArray seat; int assignSeatRetVal = 0; }; #endif diff --git a/autotests/libinput/switch_event_test.cpp b/autotests/libinput/switch_event_test.cpp new file mode 100644 index 000000000..6d5924533 --- /dev/null +++ b/autotests/libinput/switch_event_test.cpp @@ -0,0 +1,99 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 "mock_libinput.h" +#include "../../libinput/device.h" +#include "../../libinput/events.h" + +#include + +#include + +Q_DECLARE_METATYPE(KWin::LibInput::SwitchEvent::State) + +using namespace KWin::LibInput; + +class TestLibinputSwitchEvent : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void testToggled_data(); + void testToggled(); + +private: + std::unique_ptr m_nativeDevice; + std::unique_ptr m_device; +}; + +void TestLibinputSwitchEvent::init() +{ + m_nativeDevice = std::make_unique(); + m_nativeDevice->switchDevice = true; + m_device = std::make_unique(m_nativeDevice.get()); +} + +void TestLibinputSwitchEvent::cleanup() +{ + m_device.reset(); + m_nativeDevice.reset(); +} + +void TestLibinputSwitchEvent::testToggled_data() +{ + QTest::addColumn("state"); + + QTest::newRow("on") << KWin::LibInput::SwitchEvent::State::On; + QTest::newRow("off") << KWin::LibInput::SwitchEvent::State::Off; +} + +void TestLibinputSwitchEvent::testToggled() +{ + libinput_event_switch *nativeEvent = new libinput_event_switch; + nativeEvent->type = LIBINPUT_EVENT_SWITCH_TOGGLE; + nativeEvent->device = m_nativeDevice.get(); + QFETCH(KWin::LibInput::SwitchEvent::State, state); + switch (state) { + case SwitchEvent::State::Off: + nativeEvent->state = libinput_event_switch::State::Off; + break; + case SwitchEvent::State::On: + nativeEvent->state = libinput_event_switch::State::On; + break; + default: + Q_UNREACHABLE(); + } + nativeEvent->time = 23; + nativeEvent->timeMicroseconds = 23456789; + + QScopedPointer event(Event::create(nativeEvent)); + auto se = dynamic_cast(event.data()); + QVERIFY(se); + QCOMPARE(se->device(), m_device.get()); + QCOMPARE(se->nativeDevice(), m_nativeDevice.get()); + QCOMPARE(se->type(), LIBINPUT_EVENT_SWITCH_TOGGLE); + QCOMPARE(se->state(), state); + QCOMPARE(se->time(), 23u); + QCOMPARE(se->timeMicroseconds(), 23456789u); +} + +QTEST_GUILESS_MAIN(TestLibinputSwitchEvent) +#include "switch_event_test.moc" diff --git a/autotests/libkwineffects/CMakeLists.txt b/autotests/libkwineffects/CMakeLists.txt index e175cca7a..06ff1a50b 100644 --- a/autotests/libkwineffects/CMakeLists.txt +++ b/autotests/libkwineffects/CMakeLists.txt @@ -1,19 +1,19 @@ include(ECMMarkAsTest) macro(KWINEFFECTS_UNIT_TESTS) foreach(_testname ${ARGN}) add_executable(${_testname} ${_testname}.cpp) - add_test(kwineffects-${_testname} ${_testname}) + add_test(NAME kwineffects-${_testname} COMMAND ${_testname}) target_link_libraries(${_testname} Qt5::Test kwineffects) ecm_mark_as_test(${_testname}) endforeach() endmacro() kwineffects_unit_tests( windowquadlisttest ) add_executable(kwinglplatformtest kwinglplatformtest.cpp mock_gl.cpp ../../libkwineffects/kwinglplatform.cpp) -add_test(kwineffects-kwinglplatformtest kwinglplatformtest) +add_test(NAME kwineffects-kwinglplatformtest COMMAND kwinglplatformtest) target_link_libraries(kwinglplatformtest Qt5::Test Qt5::Gui Qt5::X11Extras KF5::ConfigCore XCB::XCB) ecm_mark_as_test(kwinglplatformtest) diff --git a/autotests/libkwineffects/data/glplatform/llvmpipe-5.0 b/autotests/libkwineffects/data/glplatform/llvmpipe-5.0 new file mode 100644 index 000000000..56aa352f2 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/llvmpipe-5.0 @@ -0,0 +1,22 @@ +[Driver] +Vendor=VMware, Inc. +Renderer=llvmpipe (LLVM 5.0, 256 bits) +Version=3.0 Mesa 17.2.6 +ShadingLanguageVersion=1.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +SoftwareEmulation=true +GLVersion=3,0 +GLSLVersion=1,30 +MesaVersion=17,2,6 +GalliumVersion=0,4 +DriverVersion=17,2,6 +Driver=12 +ChipClass=99999 +Compositor=9 + diff --git a/autotests/libxrenderutils/CMakeLists.txt b/autotests/libxrenderutils/CMakeLists.txt index b78b18efc..062dc186d 100644 --- a/autotests/libxrenderutils/CMakeLists.txt +++ b/autotests/libxrenderutils/CMakeLists.txt @@ -1,13 +1,13 @@ add_executable(blendPictureTest blendpicture_test.cpp) set_target_properties(blendPictureTest PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") -add_test(xrenderutils-blendPictureTest blendPictureTest) +add_test(NAME xrenderutils-blendPictureTest COMMAND blendPictureTest) target_link_libraries(blendPictureTest kwinxrenderutils Qt5::Test Qt5::Gui Qt5::X11Extras XCB::XCB XCB::RENDER XCB::XFIXES ) ecm_mark_as_test(blendPictureTest) diff --git a/autotests/tabbox/CMakeLists.txt b/autotests/tabbox/CMakeLists.txt index e214f8999..38ad4204c 100644 --- a/autotests/tabbox/CMakeLists.txt +++ b/autotests/tabbox/CMakeLists.txt @@ -1,94 +1,94 @@ include_directories(${KWIN_SOURCE_DIR}) add_definitions(-DKWIN_UNIT_TEST) ######################################################## # Test TabBox::ClientModel ######################################################## set( testTabBoxClientModel_SRCS ../../tabbox/clientmodel.cpp ../../tabbox/desktopmodel.cpp ../../tabbox/tabboxconfig.cpp ../../tabbox/tabboxhandler.cpp ../../tabbox/tabbox_logging.cpp test_tabbox_clientmodel.cpp mock_tabboxhandler.cpp mock_tabboxclient.cpp ) add_executable( testTabBoxClientModel ${testTabBoxClientModel_SRCS} ) set_target_properties(testTabBoxClientModel PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_link_libraries( testTabBoxClientModel Qt5::Core - Qt5::Gui + Qt5::Widgets Qt5::Script Qt5::Quick Qt5::Test Qt5::X11Extras KF5::ConfigCore KF5::I18n KF5::Package KF5::WindowSystem XCB::XCB ) -add_test(kwin-testTabBoxClientModel testTabBoxClientModel) +add_test(NAME kwin-testTabBoxClientModel COMMAND testTabBoxClientModel) ecm_mark_as_test(testTabBoxClientModel) ######################################################## # Test TabBox::TabBoxHandler ######################################################## set( testTabBoxHandler_SRCS ../../tabbox/clientmodel.cpp ../../tabbox/desktopmodel.cpp ../../tabbox/tabboxconfig.cpp ../../tabbox/tabboxhandler.cpp ../../tabbox/tabbox_logging.cpp test_tabbox_handler.cpp mock_tabboxhandler.cpp mock_tabboxclient.cpp ) add_executable( testTabBoxHandler ${testTabBoxHandler_SRCS} ) set_target_properties(testTabBoxHandler PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_link_libraries( testTabBoxHandler Qt5::Core - Qt5::Gui + Qt5::Widgets Qt5::Script Qt5::Quick Qt5::Test Qt5::X11Extras KF5::ConfigCore KF5::I18n KF5::Package KF5::WindowSystem XCB::XCB ) -add_test(kwin-testTabBoxHandler testTabBoxHandler) +add_test(NAME kwin-testTabBoxHandler COMMAND testTabBoxHandler) ecm_mark_as_test(testTabBoxHandler) ######################################################## # Test TabBox::TabBoxConfig ######################################################## set( testTabBoxConfig_SRCS ../../tabbox/tabboxconfig.cpp ../../tabbox/tabbox_logging.cpp test_tabbox_config.cpp ) add_executable( testTabBoxConfig ${testTabBoxConfig_SRCS} ) target_link_libraries( testTabBoxConfig Qt5::Core Qt5::Test ) -add_test(kwin-testTabBoxConfig testTabBoxConfig) +add_test(NAME kwin-testTabBoxConfig COMMAND testTabBoxConfig) ecm_mark_as_test(testTabBoxConfig) ######################################################## # Test TabBox::DesktopChainManager ######################################################## set( testDesktopChain_SRCS ../../tabbox/desktopchain.cpp ../../tabbox/tabbox_logging.cpp test_desktopchain.cpp ) add_executable( testDesktopChain ${testDesktopChain_SRCS} ) target_link_libraries( testDesktopChain Qt5::Core Qt5::Test ) -add_test(kwin-testDesktopChain testDesktopChain) +add_test(NAME kwin-testDesktopChain COMMAND testDesktopChain) ecm_mark_as_test(testDesktopChain) diff --git a/autotests/test_x11_timestamp_update.cpp b/autotests/test_x11_timestamp_update.cpp index f3619d818..9bfbe19e0 100644 --- a/autotests/test_x11_timestamp_update.cpp +++ b/autotests/test_x11_timestamp_update.cpp @@ -1,126 +1,126 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2017 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 #include #include #include "main.h" #include "utils.h" namespace KWin { class X11TestApplication : public Application { Q_OBJECT public: X11TestApplication(int &argc, char **argv); virtual ~X11TestApplication(); protected: void performStartup() override; }; X11TestApplication::X11TestApplication(int &argc, char **argv) : Application(OperationModeX11, argc, argv) { setX11Connection(QX11Info::connection()); setX11RootWindow(QX11Info::appRootWindow()); - initPlatform(KPluginMetaData(QStringLiteral(KWINBACKENDPATH))); + initPlatform(KPluginMetaData(QStringLiteral("KWinX11Platform.so"))); } X11TestApplication::~X11TestApplication() { } void X11TestApplication::performStartup() { } } class X11TimestampUpdateTest : public QObject { Q_OBJECT private Q_SLOTS: void testGrabAfterServerTime(); void testBeforeLastGrabTime(); }; void X11TimestampUpdateTest::testGrabAfterServerTime() { // this test tries to grab the X keyboard with a timestamp in future // that should fail, but after updating the X11 timestamp, it should // work again KWin::updateXTime(); QCOMPARE(KWin::grabXKeyboard(), true); KWin::ungrabXKeyboard(); // now let's change the timestamp KWin::kwinApp()->setX11Time(KWin::xTime() + 5 * 60 * 1000); // now grab keyboard should fail QCOMPARE(KWin::grabXKeyboard(), false); // let's update timestamp, now it should work again KWin::updateXTime(); QCOMPARE(KWin::grabXKeyboard(), true); KWin::ungrabXKeyboard(); } void X11TimestampUpdateTest::testBeforeLastGrabTime() { // this test tries to grab the X keyboard with a timestamp before the // last grab time on the server. That should fail, but after updating the X11 // timestamp it should work again // first set the grab timestamp KWin::updateXTime(); QCOMPARE(KWin::grabXKeyboard(), true); KWin::ungrabXKeyboard(); // now go to past const auto timestamp = KWin::xTime(); KWin::kwinApp()->setX11Time(KWin::xTime() - 5 * 60 * 1000, KWin::Application::TimestampUpdate::Always); QCOMPARE(KWin::xTime(), timestamp - 5 * 60 * 1000); // now grab keyboard should fail QCOMPARE(KWin::grabXKeyboard(), false); // let's update timestamp, now it should work again KWin::updateXTime(); QVERIFY(KWin::xTime() >= timestamp); QCOMPARE(KWin::grabXKeyboard(), true); KWin::ungrabXKeyboard(); } int main(int argc, char *argv[]) { setenv("QT_QPA_PLATFORM", "xcb", true); KWin::X11TestApplication app(argc, argv); app.setAttribute(Qt::AA_Use96Dpi, true); X11TimestampUpdateTest tc; return QTest::qExec(&tc, argc, argv); } #include "test_x11_timestamp_update.moc" diff --git a/client.cpp b/client.cpp index e973d3dfb..23fca9222 100644 --- a/client.cpp +++ b/client.cpp @@ -1,2174 +1,2174 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "client.h" // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client_machine.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "focuschain.h" #include "group.h" #include "shadow.h" #include "workspace.h" #include "screenedge.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include // KDE #include #include #include // Qt #include #include #include #include #include // XLib #include #include #include // system #include #include // Put all externs before the namespace statement to allow the linker // to resolve them properly namespace KWin { const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | // need this, too! XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Creating a client: // - only by calling Workspace::createClient() // - it creates a new client and calls manage() for it // // Destroying a client: // - destroyClient() - only when the window itself has been destroyed // - releaseWindow() - the window is kept, only the client itself is destroyed /** * \class Client client.h * \brief The Client class encapsulates a window decoration frame. */ /** * This ctor is "dumb" - it only initializes data. All the real initialization * is done in manage(). */ Client::Client() : AbstractClient() , m_client() , m_wrapper() , m_frame() , m_activityUpdatesBlocked(false) , m_blockedActivityUpdatesRequireTransients(false) , m_moveResizeGrabWindow() , move_resize_has_keyboard_grab(false) , m_managed(false) , m_transientForId(XCB_WINDOW_NONE) , m_originalTransientForId(XCB_WINDOW_NONE) , shade_below(NULL) , m_motif(atoms->motif_wm_hints) , blocks_compositing(false) , shadeHoverTimer(NULL) , m_colormap(XCB_COLORMAP_NONE) , in_group(NULL) , tab_group(NULL) , ping_timer(NULL) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet , allowed_actions(0) , shade_geometry_change(false) , sm_stacking_order(-1) , activitiesDefined(false) , sessionActivityOverride(false) , needsXWindowMove(false) , m_decoInputExtent() , m_focusOutTimer(nullptr) , m_clientSideDecorated(false) { // TODO: Do all as initialization syncRequest.counter = syncRequest.alarm = XCB_NONE; syncRequest.timeout = syncRequest.failsafeTimeout = NULL; syncRequest.lastTimestamp = xTime(); syncRequest.isPending = false; // Set the initial mapping state mapping_state = Withdrawn; info = NULL; shade_mode = ShadeNone; deleting = false; fullscreen_mode = FullScreenNone; hidden = false; noborder = false; app_noborder = false; ignore_focus_stealing = false; check_active_modal = false; max_mode = MaximizeRestore; //Client to workspace connections require that each //client constructed be connected to the workspace wrapper geom = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) client_size = QSize(100, 100); ready_for_painting = false; // wait for first damage or sync reply connect(clientMachine(), &ClientMachine::localhostChanged, this, &Client::updateCaption); connect(options, &Options::condensedTitleChanged, this, &Client::updateCaption); connect(this, &Client::moveResizeCursorChanged, this, [this] (Qt::CursorShape cursor) { xcb_cursor_t nativeCursor = Cursor::x11Cursor(cursor); m_frame.defineCursor(nativeCursor); if (m_decoInputExtent.isValid()) m_decoInputExtent.defineCursor(nativeCursor); if (isMoveResize()) { // changing window attributes doesn't change cursor if there's pointer grab active xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(), XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW); } }); // SELI TODO: Initialize xsizehints?? } /** * "Dumb" destructor. */ Client::~Client() { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } //SWrapper::Client::clientRelease(this); if (syncRequest.alarm != XCB_NONE) xcb_sync_destroy_alarm(connection(), syncRequest.alarm); assert(!isMoveResize()); assert(m_client == XCB_WINDOW_NONE); assert(m_wrapper == XCB_WINDOW_NONE); //assert( frameId() == None ); assert(!check_active_modal); for (auto it = m_connections.constBegin(); it != m_connections.constEnd(); ++it) { disconnect(*it); } } // Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly void Client::deleteClient(Client* c) { delete c; } /** * Releases the window. The client has done its job and the window is still existing. */ void Client::releaseWindow(bool on_shutdown) { assert(!deleting); deleting = true; destroyWindowManagementInterface(); Deleted* del = NULL; if (!on_shutdown) { del = Deleted::create(this); } if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); // Grab X during the release to make removing of properties, setting to withdrawn state // and repareting to root an atomic operation (http://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) grabXServer(); exportMappingState(WithdrawnState); setModal(false); // Otherwise its mainwindow wouldn't get focus hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags) if (!on_shutdown) workspace()->clientHidden(this); m_frame.unmap(); // Destroying decoration would cause ugly visual effect destroyDecoration(); cleanGrouping(); if (!on_shutdown) { workspace()->removeClient(this); // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7) info->setDesktop(0); info->setState(0, info->state()); // Reset all state flags } else untab(); xcb_connection_t *c = connection(); m_client.deleteProperty(atoms->kde_net_wm_user_creation_time); m_client.deleteProperty(atoms->net_frame_extents); m_client.deleteProperty(atoms->kde_net_wm_frame_strut); m_client.reparent(rootWindow(), x(), y()); xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client); m_client.selectInput(XCB_EVENT_MASK_NO_EVENT); if (on_shutdown) // Map the window, so it can be found after another WM is started m_client.map(); // TODO: Preserve minimized, shaded etc. state? else // Make sure it's not mapped if the app unmapped it (#65279). The app // may do map+unmap before we initially map the window by calling rawShow() from manage(). m_client.unmap(); m_client.reset(); m_wrapper.reset(); m_frame.reset(); //frame = None; unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry if (!on_shutdown) { disownDataPassedToDeleted(); del->unrefWindow(); } deleteClient(this); ungrabXServer(); } /** * Like releaseWindow(), but this one is called when the window has been already destroyed * (E.g. The application closed it) */ void Client::destroyClient() { assert(!deleting); deleting = true; destroyWindowManagementInterface(); Deleted* del = Deleted::create(this); if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(ReleaseReason::Destroyed); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); setModal(false); hidden = true; // So that it's not considered visible anymore workspace()->clientHidden(this); destroyDecoration(); cleanGrouping(); workspace()->removeClient(this); m_client.reset(); // invalidate m_wrapper.reset(); m_frame.reset(); //frame = None; unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry disownDataPassedToDeleted(); del->unrefWindow(); deleteClient(this); } void Client::updateInputWindow() { if (!Xcb::Extensions::self()->isShapeInputAvailable()) return; QRegion region; if (!noBorder() && isDecorated()) { const QMargins &r = decoration()->resizeOnlyBorders(); const int left = r.left(); const int top = r.top(); const int right = r.right(); const int bottom = r.bottom(); if (left != 0 || top != 0 || right != 0 || bottom != 0) { region = QRegion(-left, -top, decoration()->size().width() + left + right, decoration()->size().height() + top + bottom); region = region.subtracted(decoration()->rect()); } } if (region.isEmpty()) { m_decoInputExtent.reset(); return; } QRect bounds = region.boundingRect(); input_offset = bounds.topLeft(); // Move the bounding rect to screen coordinates bounds.translate(geometry().topLeft()); // Move the region to input window coordinates region.translate(-input_offset); if (!m_decoInputExtent.isValid()) { const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; const uint32_t values[] = {true, XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION }; m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values); if (mapping_state == Mapped) m_decoInputExtent.map(); } else { m_decoInputExtent.setGeometry(bounds); } const QVector rects = Xcb::regionToRects(region); xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_decoInputExtent, 0, 0, rects.count(), rects.constData()); } void Client::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); updateInputWindow(); blockGeometryUpdates(false); updateFrameExtents(); } void Client::createDecoration(const QRect& oldgeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow); connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &Client::updateInputWindow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { updateFrameExtents(); GeometryUpdatesBlocker blocker(this); // TODO: this is obviously idempotent // calculateGravitation(true) would have to operate on the old border sizes // move(calculateGravitation(true)); // move(calculateGravitation(false)); QRect oldgeom = geometry(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, &Client::updateInputWindow); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, &Client::updateInputWindow); } setDecoration(decoration); move(calculateGravitation(false)); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (Compositor::compositing()) { discardWindowPixmap(); } emit geometryShapeChanged(this, oldgeom); } void Client::destroyDecoration() { QRect oldgeom = geometry(); if (isDecorated()) { QPoint grav = calculateGravitation(true); AbstractClient::destroyDecoration(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); move(grav); if (compositing()) discardWindowPixmap(); if (!deleting) { emit geometryShapeChanged(this, oldgeom); } } m_decoInputExtent.reset(); } void Client::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); NETStrut strut = info->frameOverlap(); // Ignore the overlap strut when compositing is disabled if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) { top = QRect(r.x(), r.y(), r.width(), r.height() / 3); left = QRect(r.x(), r.y() + top.height(), width() / 2, r.height() / 3); right = QRect(r.x() + left.width(), r.y() + top.height(), r.width() - left.width(), left.height()); bottom = QRect(r.x(), r.y() + top.height() + left.height(), r.width(), r.height() - left.height() - top.height()); return; } top = QRect(r.x(), r.y(), r.width(), borderTop() + strut.top); bottom = QRect(r.x(), r.y() + r.height() - borderBottom() - strut.bottom, r.width(), borderBottom() + strut.bottom); left = QRect(r.x(), r.y() + top.height(), borderLeft() + strut.left, r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight() - strut.right, r.y() + top.height(), borderRight() + strut.right, r.height() - top.height() - bottom.height()); } QRect Client::transparentRect() const { if (isShade()) return QRect(); NETStrut strut = info->frameOverlap(); // Ignore the strut when compositing is disabled or the decoration doesn't support it if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) return QRect(); const QRect r = QRect(clientPos(), clientSize()) .adjusted(strut.left, strut.top, -strut.right, -strut.bottom); if (r.isValid()) return r; return QRect(); } void Client::detectNoBorder() { if (shape()) { noborder = true; app_noborder = true; return; } switch(windowType()) { case NET::Desktop : case NET::Dock : case NET::TopMenu : case NET::Splash : case NET::Notification : case NET::OnScreenDisplay : noborder = true; app_noborder = true; break; case NET::Unknown : case NET::Normal : case NET::Toolbar : case NET::Menu : case NET::Dialog : case NET::Utility : noborder = false; break; default: abort(); } // NET::Override is some strange beast without clear definition, usually // just meaning "noborder", so let's treat it only as such flag, and ignore it as // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) if (info->windowType(NET::OverrideMask) == NET::Override) { noborder = true; app_noborder = true; } } void Client::updateFrameExtents() { NETStrut strut; strut.left = borderLeft(); strut.right = borderRight(); strut.top = borderTop(); strut.bottom = borderBottom(); info->setFrameExtents(strut); } Xcb::Property Client::fetchGtkFrameExtents() const { return Xcb::Property(false, m_client, atoms->gtk_frame_extents, XCB_ATOM_CARDINAL, 0, 4); } void Client::readGtkFrameExtents(Xcb::Property &prop) { m_clientSideDecorated = !prop.isNull() && prop->type != 0; emit clientSideDecoratedChanged(); } void Client::detectGtkFrameExtents() { Xcb::Property prop = fetchGtkFrameExtents(); readGtkFrameExtents(prop); } /** * Resizes the decoration, and makes sure the decoration widget gets resize event * even if the size hasn't changed. This is needed to make sure the decoration * re-layouts (e.g. when maximization state changes, * the decoration may alter some borders, but the actual size * of the decoration stays the same). */ void Client::resizeDecoration() { triggerDecorationRepaint(); updateInputWindow(); } bool Client::noBorder() const { return noborder || isFullScreen(); } bool Client::userCanSetNoBorder() const { return !isFullScreen() && !isShade() && !tabGroup(); } void Client::setNoBorder(bool set) { if (!userCanSetNoBorder()) return; set = rules()->checkNoBorder(set); if (noborder == set) return; noborder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void Client::checkNoBorder() { setNoBorder(app_noborder); } bool Client::wantsShadowToBeRendered() const { return !isFullScreen() && maximizeMode() != MaximizeFull; } void Client::updateShape() { if (shape()) { // Workaround for #19644 - Shaped windows shouldn't have decoration if (!app_noborder) { // Only when shape is detected for the first time, still let the user to override app_noborder = true; noborder = rules()->checkNoBorder(true); updateDecoration(true); } if (noBorder()) { xcb_shape_combine(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, frameId(), clientPos().x(), clientPos().y(), window()); } } else if (app_noborder) { xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE); detectNoBorder(); app_noborder = noborder; noborder = rules()->checkNoBorder(noborder || m_motif.noBorder()); updateDecoration(true); } // Decoration mask (i.e. 'else' here) setting is done in setMask() // when the decoration calls it or when the decoration is created/destroyed updateInputShape(); if (compositing()) { addRepaintFull(); addWorkspaceRepaint(visibleRect()); // In case shape change removes part of this window } emit geometryShapeChanged(this, geometry()); } static Xcb::Window shape_helper_window(XCB_WINDOW_NONE); void Client::cleanupX11() { shape_helper_window.reset(); } void Client::updateInputShape() { if (hiddenPreview()) // Sets it to none, don't change return; if (Xcb::Extensions::self()->isShapeInputAvailable()) { // There appears to be no way to find out if a window has input // shape set or not, so always propagate the input shape // (it's the same like the bounding shape by default). // Also, build the shape using a helper window, not directly // in the frame window, because the sequence set-shape-to-frame, // remove-shape-of-client, add-input-shape-of-client has the problem // that after the second step there's a hole in the input shape // until the real shape of the client is added and that can make // the window lose focus (which is a problem with mouse focus policies) // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better if (!shape_helper_window.isValid()) shape_helper_window.create(QRect(0, 0, 1, 1)); shape_helper_window.resize(width(), height()); xcb_connection_t *c = connection(); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, 0, 0, frameId()); xcb_shape_combine(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, frameId(), 0, 0, shape_helper_window); } } void Client::hideClient(bool hide) { if (hidden == hide) return; hidden = hide; updateVisibility(); } /** * Returns whether the window is minimizable or not */ bool Client::isMinimizable() const { if (isSpecialWindow() && !isTransient()) return false; if (!rules()->checkMinimize(true)) return false; if (isTransient()) { // #66868 - Let other xmms windows be minimized when the mainwindow is minimized bool shown_mainwindow = false; auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) shown_mainwindow = true; if (!shown_mainwindow) return true; } #if 0 // This is here because kicker's taskbar doesn't provide separate entries // for windows with an explicitly given parent // TODO: perhaps this should be redone // Disabled for now, since at least modal dialogs should be minimizable // (resulting in the mainwindow being minimized too). if (transientFor() != NULL) return false; #endif if (!wantsTabFocus()) // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ? return false; return true; } void Client::doMinimize() { updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients(this); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Minimized); } QRect Client::iconGeometry() const { NETRect r = info->iconGeometry(); QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height); if (geom.isValid()) return geom; else { // Check all mainwindows of this window (recursively) foreach (AbstractClient * amainwin, mainClients()) { Client *mainwin = dynamic_cast(amainwin); if (!mainwin) { continue; } geom = mainwin->iconGeometry(); if (geom.isValid()) return geom; } // No mainwindow (or their parents) with icon geometry was found return AbstractClient::iconGeometry(); } } bool Client::isShadeable() const { return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone)); } void Client::setShade(ShadeMode mode) { if (mode == ShadeHover && isMove()) return; // causes geometry breaks and is probably nasty if (isSpecialWindow() || noBorder()) mode = ShadeNone; mode = rules()->checkShade(mode); if (shade_mode == mode) return; bool was_shade = isShade(); ShadeMode was_shade_mode = shade_mode; shade_mode = mode; // Decorations may turn off some borders when shaded // this has to happen _before_ the tab alignment since it will restrict the minimum geometry #if 0 if (decoration) decoration->borders(border_left, border_right, border_top, border_bottom); #endif // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Shaded); if (was_shade == isShade()) { // Decoration may want to update after e.g. hover-shade changes emit shadeChanged(); return; // No real change in shaded state } assert(isDecorated()); // noborder windows can't be shaded GeometryUpdatesBlocker blocker(this); // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere if (isShade()) { // shade_mode == ShadeNormal addWorkspaceRepaint(visibleRect()); // Shade shade_geometry_change = true; QSize s(sizeForClientSize(QSize(clientSize()))); s.setHeight(borderTop() + borderBottom()); m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_wrapper.unmap(); m_client.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(IconicState); plainResize(s); shade_geometry_change = false; if (was_shade_mode == ShadeHover) { if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1) workspace()->restack(this, shade_below, true); if (isActive()) workspace()->activateNextClient(this); } else if (isActive()) { workspace()->focusToNull(); } } else { shade_geometry_change = true; if (decoratedClient()) decoratedClient()->signalShadeChange(); QSize s(sizeForClientSize(clientSize())); shade_geometry_change = false; plainResize(s); geom_restore = geometry(); if ((shade_mode == ShadeHover || shade_mode == ShadeActivated) && rules()->checkAcceptFocus(info->input())) setActive(true); if (shade_mode == ShadeHover) { ToplevelList order = workspace()->stackingOrder(); // invalidate, since "this" could be the topmost toplevel and shade_below dangeling shade_below = NULL; // this is likely related to the index parameter?! for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) { shade_below = qobject_cast(order.at(idx)); if (shade_below) { break; } } if (shade_below && shade_below->isNormalWindow()) workspace()->raiseClient(this); else shade_below = NULL; } m_wrapper.map(); m_client.map(); exportMappingState(NormalState); if (isActive()) workspace()->requestFocus(this); } info->setState(isShade() ? NET::Shaded : NET::States(0), NET::Shaded); info->setState(isShown(false) ? NET::States(0) : NET::Hidden, NET::Hidden); discardWindowPixmap(); updateVisibility(); updateAllowedActions(); updateWindowRules(Rules::Shade); emit shadeChanged(); } void Client::shadeHover() { setShade(ShadeHover); cancelShadeHoverTimer(); } void Client::shadeUnhover() { if (!tabGroup() || tabGroup()->current() == this || tabGroup()->current()->shadeMode() == ShadeNormal) setShade(ShadeNormal); cancelShadeHoverTimer(); } void Client::cancelShadeHoverTimer() { delete shadeHoverTimer; shadeHoverTimer = 0; } void Client::toggleShade() { // If the mode is ShadeHover or ShadeActive, cancel shade too setShade(shade_mode == ShadeNone ? ShadeNormal : ShadeNone); } void Client::updateVisibility() { if (deleting) return; if (hidden && isCurrentTab()) { info->setState(NET::Hidden, NET::Hidden); setSkipTaskbar(true); // Also hide from taskbar if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } if (isCurrentTab()) setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden' if (isMinimized()) { info->setState(NET::Hidden, NET::Hidden); if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } info->setState(0, NET::Hidden); if (!isOnCurrentDesktop()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } if (!isOnCurrentActivity()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } internalShow(); } /** * Sets the client window's mapping state. Possible values are * WithdrawnState, IconicState, NormalState. */ void Client::exportMappingState(int s) { assert(m_client != XCB_WINDOW_NONE); assert(!deleting || s == WithdrawnState); if (s == WithdrawnState) { m_client.deleteProperty(atoms->wm_state); return; } assert(s == NormalState || s == IconicState); int32_t data[2]; data[0] = s; data[1] = XCB_NONE; m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data); } void Client::internalShow() { if (mapping_state == Mapped) return; MappingState old = mapping_state; mapping_state = Mapped; if (old == Unmapped || old == Withdrawn) map(); if (old == Kept) { m_decoInputExtent.map(); updateHiddenPreview(); } emit windowShown(this); } void Client::internalHide() { if (mapping_state == Unmapped) return; MappingState old = mapping_state; mapping_state = Unmapped; if (old == Mapped || old == Kept) unmap(); if (old == Kept) updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); } void Client::internalKeep() { assert(compositing()); if (mapping_state == Kept) return; MappingState old = mapping_state; mapping_state = Kept; if (old == Unmapped || old == Withdrawn) map(); m_decoInputExtent.unmap(); if (isActive()) workspace()->focusToNull(); // get rid of input focus, bug #317484 updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } /** * Maps (shows) the client. Note that it is mapping state of the frame, * not necessarily the client window itself (i.e. a shaded window is here * considered mapped, even though it is in IconicState). */ void Client::map() { // XComposite invalidates backing pixmaps on unmap (minimize, different // virtual desktop, etc.). We kept the last known good pixmap around // for use in effects, but now we want to have access to the new pixmap if (compositing()) discardWindowPixmap(); m_frame.map(); if (!isShade()) { m_wrapper.map(); m_client.map(); m_decoInputExtent.map(); exportMappingState(NormalState); } else exportMappingState(IconicState); addLayerRepaint(visibleRect()); } /** * Unmaps the client. Again, this is about the frame. */ void Client::unmap() { // Here it may look like a race condition, as some other client might try to unmap // the window between these two XSelectInput() calls. However, they're supposed to // use XWithdrawWindow(), which also sends a synthetic event to the root window, // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify // will be missed is also very minimal, so I don't think it's needed to grab the server // here. m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_frame.unmap(); m_wrapper.unmap(); m_client.unmap(); m_decoInputExtent.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(IconicState); } /** * XComposite doesn't keep window pixmaps of unmapped windows, which means * there wouldn't be any previews of windows that are minimized or on another * virtual desktop. Therefore rawHide() actually keeps such windows mapped. * However special care needs to be taken so that such windows don't interfere. * Therefore they're put very low in the stacking order and they have input shape * set to none, which hopefully is enough. If there's no input shape available, * then it's hoped that there will be some other desktop above it *shrug*. * Using normal shape would be better, but that'd affect other things, e.g. painting * of the actual preview. */ void Client::updateHiddenPreview() { if (hiddenPreview()) { workspace()->forceRestacking(); if (Xcb::Extensions::self()->isShapeInputAvailable()) { xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, NULL); } } else { workspace()->forceRestacking(); updateInputShape(); } } void Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3, xcb_timestamp_t timestamp) { xcb_client_message_event_t ev; memset(&ev, 0, sizeof(ev)); ev.response_type = XCB_CLIENT_MESSAGE; ev.window = w; ev.type = a; ev.format = 32; ev.data.data32[0] = protocol; ev.data.data32[1] = timestamp; ev.data.data32[2] = data1; ev.data.data32[3] = data2; ev.data.data32[4] = data3; uint32_t eventMask = 0; if (w == rootWindow()) { eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic! } xcb_send_event(connection(), false, w, eventMask, reinterpret_cast(&ev)); xcb_flush(connection()); } /** * Returns whether the window may be closed (have a close button) */ bool Client::isCloseable() const { return rules()->checkCloseable(m_motif.close() && !isSpecialWindow()); } /** * Closes the window by either sending a delete_window message or using XKill. */ void Client::closeWindow() { if (!isCloseable()) return; // Update user time, because the window may create a confirming dialog. updateUserTime(); if (info->supportsProtocol(NET::DeleteWindowProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window); pingWindow(); } else // Client will not react on wm_delete_window. We have not choice // but destroy his connection to the XServer. killWindow(); } /** * Kills the window via XKill */ void Client::killWindow() { qCDebug(KWIN_CORE) << "Client::killWindow():" << caption(); killProcess(false); m_client.kill(); // Always kill this client at the server destroyClient(); } /** * Send a ping to the window using _NET_WM_PING if possible if it * doesn't respond within a reasonable time, it will be killed. */ void Client::pingWindow() { if (!info->supportsProtocol(NET::PingProtocol)) return; // Can't ping :( if (options->killPingTimeout() == 0) return; // Turned off if (ping_timer != NULL) return; // Pinging already ping_timer = new QTimer(this); connect(ping_timer, &QTimer::timeout, this, [this]() { if (unresponsive()) { qCDebug(KWIN_CORE) << "Final ping timeout, asking to kill:" << caption(); ping_timer->deleteLater(); ping_timer = nullptr; killProcess(true, m_pingTimestamp); return; } qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); setUnresponsive(true); ping_timer->start(); } ); ping_timer->setSingleShot(true); // we'll run the timer twice, at first we'll desaturate the window // and the second time we'll show the "do you want to kill" prompt ping_timer->start(options->killPingTimeout() / 2); m_pingTimestamp = xTime(); workspace()->sendPingToWindow(window(), m_pingTimestamp); } void Client::gotPing(xcb_timestamp_t timestamp) { // Just plain compare is not good enough because of 64bit and truncating and whatnot if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0) return; delete ping_timer; ping_timer = NULL; setUnresponsive(false); if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } } void Client::killProcess(bool ask, xcb_timestamp_t timestamp) { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive return; Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME); pid_t pid = info->pid(); if (pid <= 0 || clientMachine()->hostName().isEmpty()) // Needed properties missing return; qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")"; if (!ask) { if (!clientMachine()->isLocal()) { QStringList lst; lst << QString::fromUtf8(clientMachine()->hostName()) << QStringLiteral("kill") << QString::number(pid); QProcess::startDetached(QStringLiteral("xon"), lst); } else ::kill(pid, SIGTERM); } else { QString hostname = clientMachine()->isLocal() ? QStringLiteral("localhost") : QString::fromUtf8(clientMachine()->hostName()); QProcess::startDetached(QStringLiteral(KWIN_KILLER_BIN), QStringList() << QStringLiteral("--pid") << QString::number(unsigned(pid)) << QStringLiteral("--hostname") << hostname << QStringLiteral("--windowname") << captionNormal() << QStringLiteral("--applicationname") << QString::fromUtf8(resourceClass()) << QStringLiteral("--wid") << QString::number(window()) << QStringLiteral("--timestamp") << QString::number(timestamp), QString(), &m_killHelperPID); } } void Client::doSetSkipTaskbar() { info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(0), NET::SkipTaskbar); } void Client::doSetSkipPager() { info->setState(skipPager() ? NET::SkipPager : NET::States(0), NET::SkipPager); } void Client::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) updateVisibility(); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Desktop); } /** * Sets whether the client is on @p activity. * If you remove it from its last activity, then it's on all activities. * * Note: If it was on all activities and you try to remove it from one, nothing will happen; * I don't think that's an important enough use case to handle here. */ void Client::setOnActivity(const QString &activity, bool enable) { #ifdef KWIN_BUILD_ACTIVITIES if (! Activities::self()) { return; } QStringList newActivitiesList = activities(); if (newActivitiesList.contains(activity) == enable) //nothing to do return; if (enable) { QStringList allActivities = Activities::self()->all(); if (!allActivities.contains(activity)) //bogus ID return; newActivitiesList.append(activity); } else newActivitiesList.removeOne(activity); setOnActivities(newActivitiesList); #else Q_UNUSED(activity) Q_UNUSED(enable) #endif } /** * set exactly which activities this client is on */ void Client::setOnActivities(QStringList newActivitiesList) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } QString joinedActivitiesList = newActivitiesList.join(QStringLiteral(",")); joinedActivitiesList = rules()->checkActivity(joinedActivitiesList, false); newActivitiesList = joinedActivitiesList.split(u',', QString::SkipEmptyParts); QStringList allActivities = Activities::self()->all(); auto it = newActivitiesList.begin(); while (it != newActivitiesList.end()) { if (! allActivities.contains(*it)) { it = newActivitiesList.erase(it); } else { it++; } } if (// If we got the request to be on all activities explicitly newActivitiesList.isEmpty() || joinedActivitiesList == Activities::nullUuid() || // If we got a list of activities that covers all activities (newActivitiesList.count() > 1 && newActivitiesList.count() == allActivities.count())) { activityList.clear(); const QByteArray nullUuid = Activities::nullUuid().toUtf8(); m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData()); } else { QByteArray joined = joinedActivitiesList.toAscii(); activityList = newActivitiesList; m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData()); } updateActivities(false); #else Q_UNUSED(newActivitiesList) #endif } void Client::blockActivityUpdates(bool b) { if (b) { ++m_activityUpdatesBlocked; } else { Q_ASSERT(m_activityUpdatesBlocked); --m_activityUpdatesBlocked; if (!m_activityUpdatesBlocked) updateActivities(m_blockedActivityUpdatesRequireTransients); } } /** * update after activities changed */ void Client::updateActivities(bool includeTransients) { if (m_activityUpdatesBlocked) { m_blockedActivityUpdatesRequireTransients |= includeTransients; return; } emit activitiesChanged(this); m_blockedActivityUpdatesRequireTransients = false; // reset FocusChain::self()->update(this, FocusChain::MakeFirst); updateVisibility(); updateWindowRules(Rules::Activity); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Activity); } /** * Returns the list of activities the client window is on. * if it's on all activities, the list will be empty. * Don't use this, use isOnActivity() and friends (from class Toplevel) */ QStringList Client::activities() const { if (sessionActivityOverride) { return QStringList(); } return activityList; } /** * if @p on is true, sets on all activities. * if it's false, sets it to only be on the current activity */ void Client::setOnAllActivities(bool on) { #ifdef KWIN_BUILD_ACTIVITIES if (on == isOnAllActivities()) return; if (on) { setOnActivities(QStringList()); } else { setOnActivity(Activities::self()->current(), true); } #else Q_UNUSED(on) #endif } /** * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS */ void Client::takeFocus() { if (rules()->checkAcceptFocus(info->input())) m_client.focus(); else demandAttention(false); // window cannot take input, at least withdraw urgency if (info->supportsProtocol(NET::TakeFocusProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus, 0, 0, 0, XCB_CURRENT_TIME); } workspace()->setShouldGetFocus(this); bool breakShowingDesktop = !keepAbove(); if (breakShowingDesktop) { foreach (const Client *c, group()->members()) { if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } /** * Returns whether the window provides context help or not. If it does, * you should show a help menu item or a help button like '?' and call * contextHelp() if this is invoked. * * \sa contextHelp() */ bool Client::providesContextHelp() const { return info->supportsProtocol(NET::ContextHelpProtocol); } /** * Invokes context help on the window. Only works if the window * actually provides context help. * * \sa providesContextHelp() */ void Client::showContextHelp() { if (info->supportsProtocol(NET::ContextHelpProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); } } /** * Fetches the window's caption (WM_NAME property). It will be * stored in the client's caption(). */ void Client::fetchName() { setCaption(readName()); } static inline QString readNameProperty(xcb_window_t w, xcb_atom_t atom) { const auto cookie = xcb_icccm_get_text_property_unchecked(connection(), w, atom); xcb_icccm_get_text_property_reply_t reply; if (xcb_icccm_get_wm_name_reply(connection(), cookie, &reply, nullptr)) { QString retVal; if (reply.encoding == atoms->utf8_string) { retVal = QString::fromUtf8(QByteArray(reply.name, reply.name_len)); } else if (reply.encoding == XCB_ATOM_STRING) { retVal = QString::fromLocal8Bit(QByteArray(reply.name, reply.name_len)); } xcb_icccm_get_text_property_reply_wipe(&reply); return retVal.simplified(); } return QString(); } QString Client::readName() const { if (info->name() && info->name()[0] != '\0') return QString::fromUtf8(info->name()).simplified(); else { return readNameProperty(window(), XCB_ATOM_WM_NAME); } } // The list is taken from http://www.unicode.org/reports/tr9/ (#154840) static const QChar LRM(0x200E); void Client::setCaption(const QString& _s, bool force) { if (!force && _s == cap_normal) return; QString s(_s); for (int i = 0; i < s.length(); ++i) if (!s[i].isPrint()) s[i] = QChar(u' '); const bool changed = (s != cap_normal); cap_normal = s; if (!force && !changed) { emit captionChanged(); return; } bool reset_name = force; bool was_suffix = (!cap_suffix.isEmpty()); cap_suffix.clear(); QString machine_suffix; if (!options->condensedTitle()) { // machine doesn't qualify for "clean" if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal()) machine_suffix = QLatin1String(" <@") + QString::fromUtf8(clientMachine()->hostName()) + QLatin1Char('>') + LRM; } QString shortcut_suffix = shortcutCaptionSuffix(); cap_suffix = machine_suffix + shortcut_suffix; if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { int i = 2; do { cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM; i++; } while (findClientWithSameCaption()); info->setVisibleName(caption().toUtf8().constData()); reset_name = false; } if ((was_suffix && cap_suffix.isEmpty()) || reset_name) { // If it was new window, it may have old value still set, if the window is reused info->setVisibleName(""); info->setVisibleIconName(""); } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData()); emit captionChanged(); } void Client::updateCaption() { setCaption(cap_normal, true); } void Client::fetchIconicName() { QString s; if (info->iconName() && info->iconName()[0] != '\0') s = QString::fromUtf8(info->iconName()); else s = readNameProperty(window(), XCB_ATOM_WM_ICON_NAME); if (s != cap_iconic) { bool was_set = !cap_iconic.isEmpty(); cap_iconic = s; if (!cap_suffix.isEmpty()) { if (!cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(s + cap_suffix).toUtf8().constData()); else if (was_set) info->setVisibleIconName(""); } } } bool Client::tabTo(Client *other, bool behind, bool activate) { Q_ASSERT(other && other != this); if (tab_group && tab_group == other->tabGroup()) { // special case: move inside group tab_group->move(this, other, behind); return true; } GeometryUpdatesBlocker blocker(this); const bool wasBlocking = signalsBlocked(); blockSignals(true); // prevent client emitting "retabbed to nowhere" cause it's about to be entabbed the next moment untab(); blockSignals(wasBlocking); TabGroup *newGroup = other->tabGroup() ? other->tabGroup() : new TabGroup(other); if (!newGroup->add(this, other, behind, activate)) { if (newGroup->count() < 2) { // adding "c" to "to" failed for whatever reason newGroup->remove(other); delete newGroup; } return false; } return true; } bool Client::untab(const QRect &toGeometry, bool clientRemoved) { TabGroup *group = tab_group; if (group && group->remove(this)) { // remove sets the tabgroup to "0", therefore the pointer is cached if (group->isEmpty()) { delete group; } if (clientRemoved) return true; // there's been a broadcast signal that this client is now removed - don't touch it setClientShown(!(isMinimized() || isShade())); bool keepSize = toGeometry.size() == size(); bool changedSize = false; if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { changedSize = true; setQuickTileMode(QuickTileFlag::None); // if we leave a quicktiled group, assume that the user wants to untile } if (toGeometry.isValid()) { if (maximizeMode() != MaximizeRestore) { changedSize = true; maximize(MaximizeRestore); // explicitly calling for a geometry -> unmaximize } if (keepSize && changedSize) { geom_restore = geometry(); // checkWorkspacePosition() invokes it QPoint cpoint = Cursor::pos(); QPoint point = cpoint; point.setX((point.x() - toGeometry.x()) * geom_restore.width() / toGeometry.width()); point.setY((point.y() - toGeometry.y()) * geom_restore.height() / toGeometry.height()); geom_restore.moveTo(cpoint-point); } else { geom_restore = toGeometry; // checkWorkspacePosition() invokes it } setGeometry(geom_restore); checkWorkspacePosition(); } return true; } return false; } void Client::setTabGroup(TabGroup *group) { tab_group = group; if (group) { unsigned long data[] = {qHash(group)}; //->id(); m_client.changeProperty(atoms->kde_net_wm_tab_group, XCB_ATOM_CARDINAL, 32, 1, data); } else m_client.deleteProperty(atoms->kde_net_wm_tab_group); emit tabGroupChanged(); } bool Client::isCurrentTab() const { return !tab_group || tab_group->current() == this; } void Client::syncTabGroupFor(QString property, bool fromThisClient) { if (tab_group) tab_group->sync(property.toAscii().data(), fromThisClient ? this : tab_group->current()); } void Client::setClientShown(bool shown) { if (deleting) return; // Don't change shown status if this client is being deleted if (shown != hidden) return; // nothing to change hidden = !shown; if (options->isInactiveTabsSkipTaskbar()) setSkipTaskbar(hidden); // TODO: Causes reshuffle of the taskbar if (shown) { map(); takeFocus(); autoRaise(); FocusChain::self()->update(this, FocusChain::MakeFirst); } else { unmap(); // Don't move tabs to the end of the list when another tab get's activated if (isCurrentTab()) FocusChain::self()->update(this, FocusChain::MakeLast); addWorkspaceRepaint(visibleRect()); } } void Client::getMotifHints() { const bool wasClosable = m_motif.close(); const bool wasNoBorder = m_motif.noBorder(); if (m_managed) // only on property change, initial read is prefetched m_motif.fetch(); m_motif.read(); if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) { // If we just got a hint telling us to hide decorations, we do so. if (m_motif.noBorder()) noborder = rules()->checkNoBorder(true); // If the Motif hint is now telling us to show decorations, we only do so if the app didn't // instruct us to hide decorations in some other way, though. else if (!app_noborder) noborder = rules()->checkNoBorder(false); } // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too // mmaximize; - Ignore, bogus - Maximizing is basically just resizing const bool closabilityChanged = wasClosable != m_motif.close(); if (isManaged()) updateDecoration(true); // Check if noborder state has changed if (closabilityChanged) { emit closeableChanged(isCloseable()); } } void Client::getIcons() { // First read icons from the window itself const QString themedIconName = iconFromDesktopFile(); if (!themedIconName.isEmpty()) { setIcon(QIcon::fromTheme(themedIconName)); return; } QIcon icon; auto readIcon = [this, &icon](int size, bool scale = true) { const QPixmap pix = KWindowSystem::icon(window(), size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, info); if (!pix.isNull()) { icon.addPixmap(pix); } }; readIcon(16); readIcon(32); readIcon(48, false); readIcon(64, false); readIcon(128, false); if (icon.isNull()) { // Then try window group icon = group()->icon(); } if (icon.isNull() && isTransient()) { // Then mainclients auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd() && icon.isNull(); ++it) { if (!(*it)->icon().isNull()) { icon = (*it)->icon(); break; } } } if (icon.isNull()) { // And if nothing else, load icon from classhint or xapp icon icon.addPixmap(KWindowSystem::icon(window(), 32, 32, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 16, 16, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 64, 64, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); } setIcon(icon); } void Client::getSyncCounter() { // TODO: make sync working on XWayland static const bool isX11 = kwinApp()->operationMode() == Application::OperationModeX11; if (!Xcb::Extensions::self()->isSyncAvailable() || !isX11) return; Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1); const xcb_sync_counter_t counter = syncProp.value(XCB_NONE); if (counter != XCB_NONE) { syncRequest.counter = counter; syncRequest.value.hi = 0; syncRequest.value.lo = 0; auto *c = connection(); xcb_sync_set_counter(c, syncRequest.counter, syncRequest.value); if (syncRequest.alarm == XCB_NONE) { const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS; const uint32_t values[] = { syncRequest.counter, XCB_SYNC_VALUETYPE_RELATIVE, XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION, 1 }; syncRequest.alarm = xcb_generate_id(c); auto cookie = xcb_sync_create_alarm_checked(c, syncRequest.alarm, mask, values); ScopedCPointer error(xcb_request_check(c, cookie)); if (!error.isNull()) { syncRequest.alarm = XCB_NONE; } else { xcb_sync_change_alarm_value_list_t value; memset(&value, 0, sizeof(value)); value.value.hi = 0; value.value.lo = 1; value.delta.hi = 0; value.delta.lo = 1; xcb_sync_change_alarm_aux(c, syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value); } } } } /** * Send the client a _NET_SYNC_REQUEST */ void Client::sendSyncRequest() { if (syncRequest.counter == XCB_NONE || syncRequest.isPending) return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... if (!syncRequest.failsafeTimeout) { syncRequest.failsafeTimeout = new QTimer(this); connect(syncRequest.failsafeTimeout, &QTimer::timeout, this, [this]() { // client does not respond to XSYNC requests in reasonable time, remove support if (!ready_for_painting) { // failed on initial pre-show request setReadyForPainting(); setupWindowManagementInterface(); return; } // failed during resize syncRequest.isPending = false; syncRequest.counter = syncRequest.alarm = XCB_NONE; delete syncRequest.timeout; delete syncRequest.failsafeTimeout; syncRequest.timeout = syncRequest.failsafeTimeout = nullptr; syncRequest.lastTimestamp = XCB_CURRENT_TIME; } ); syncRequest.failsafeTimeout->setSingleShot(true); } // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client. // see events.cpp Client::syncEvent() syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000); // We increment before the notify so that after the notify // syncCounterSerial will equal the value we are expecting // in the acknowledgement const uint32_t oldLo = syncRequest.value.lo; syncRequest.value.lo++;; if (oldLo > syncRequest.value.lo) { syncRequest.value.hi++; } if (syncRequest.lastTimestamp >= xTime()) { updateXTime(); } // Send the message to client sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request, syncRequest.value.lo, syncRequest.value.hi); syncRequest.isPending = true; syncRequest.lastTimestamp = xTime(); } bool Client::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol)); } bool Client::acceptsFocus() const { return info->input(); } void Client::setBlockingCompositing(bool block) { const bool usedToBlock = blocks_compositing; blocks_compositing = rules()->checkBlockCompositing(block && options->windowsBlockCompositing()); if (usedToBlock != blocks_compositing) { emit blockingCompositingChanged(blocks_compositing ? this : 0); } } void Client::updateAllowedActions(bool force) { if (!isManaged() && !force) return; NET::Actions old_allowed_actions = NET::Actions(allowed_actions); allowed_actions = 0; if (isMovable()) allowed_actions |= NET::ActionMove; if (isResizable()) allowed_actions |= NET::ActionResize; if (isMinimizable()) allowed_actions |= NET::ActionMinimize; if (isShadeable()) allowed_actions |= NET::ActionShade; // Sticky state not supported if (isMaximizable()) allowed_actions |= NET::ActionMax; if (userCanSetFullScreen()) allowed_actions |= NET::ActionFullScreen; allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.) if (isCloseable()) allowed_actions |= NET::ActionClose; if (old_allowed_actions == allowed_actions) return; // TODO: This could be delayed and compressed - It's only for pagers etc. anyway info->setAllowedActions(allowed_actions); // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes) const NET::Actions relevant = ~(NET::ActionMove|NET::ActionResize); if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) { if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) { emit minimizeableChanged(allowed_actions & NET::ActionMinimize); } if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) { emit shadeableChanged(allowed_actions & NET::ActionShade); } if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) { emit maximizeableChanged(allowed_actions & NET::ActionMax); } } } void Client::debug(QDebug& stream) const { print(stream); } Xcb::StringProperty Client::fetchActivities() const { #ifdef KWIN_BUILD_ACTIVITIES return Xcb::StringProperty(window(), atoms->activities); #else return Xcb::StringProperty(); #endif } void Client::readActivities(Xcb::StringProperty &property) { #ifdef KWIN_BUILD_ACTIVITIES QStringList newActivitiesList; QString prop = QString::fromUtf8(property); activitiesDefined = !prop.isEmpty(); if (prop == Activities::nullUuid()) { //copied from setOnAllActivities to avoid a redundant XChangeProperty. if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } if (prop.isEmpty()) { //note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL' if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } newActivitiesList = prop.split(u','); if (newActivitiesList == activityList) return; //expected change, it's ok. //otherwise, somebody else changed it. we need to validate before reacting. //if the activities are not synced, and there are existing clients with //activities specified, somebody has restarted kwin. we can not validate //activities in this case. we need to trust the old values. if (Activities::self() && Activities::self()->serviceStatus() != KActivities::Consumer::Unknown) { QStringList allActivities = Activities::self()->all(); if (allActivities.isEmpty()) { qCDebug(KWIN_CORE) << "no activities!?!?"; //don't touch anything, there's probably something bad going on and we don't wanna make it worse return; } for (int i = 0; i < newActivitiesList.size(); ++i) { if (! allActivities.contains(newActivitiesList.at(i))) { qCDebug(KWIN_CORE) << "invalid:" << newActivitiesList.at(i); newActivitiesList.removeAt(i--); } } } setOnActivities(newActivitiesList); #else Q_UNUSED(property) #endif } void Client::checkActivities() { #ifdef KWIN_BUILD_ACTIVITIES Xcb::StringProperty property = fetchActivities(); readActivities(property); #endif } void Client::setSessionActivityOverride(bool needed) { sessionActivityOverride = needed; updateActivities(false); } QRect Client::decorationRect() const { return QRect(0, 0, width(), height()); } Xcb::Property Client::fetchFirstInTabBox() const { return Xcb::Property(false, m_client, atoms->kde_first_in_window_list, atoms->kde_first_in_window_list, 0, 1); } void Client::readFirstInTabBox(Xcb::Property &property) { setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list)); } void Client::updateFirstInTabBox() { // TODO: move into KWindowInfo Xcb::Property property = fetchFirstInTabBox(); readFirstInTabBox(property); } Xcb::StringProperty Client::fetchColorScheme() const { return Xcb::StringProperty(m_client, atoms->kde_color_sheme); } void Client::readColorScheme(Xcb::StringProperty &property) { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString::fromUtf8(property))); } void Client::updateColorScheme() { Xcb::StringProperty property = fetchColorScheme(); readColorScheme(property); } bool Client::isClient() const { return true; } NET::WindowType Client::windowType(bool direct, int supportedTypes) const { // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK? if (supportedTypes == 0) { supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK; } NET::WindowType wt = info->windowType(NET::WindowTypes(supportedTypes)); if (direct) { return wt; } NET::WindowType wt2 = rules()->checkType(wt); if (wt != wt2) { wt = wt2; info->setWindowType(wt); // force hint change } // hacks here if (wt == NET::Unknown) // this is more or less suggested in NETWM spec wt = isTransient() ? NET::Dialog : NET::Normal; return wt; } void Client::cancelFocusOutTimer() { if (m_focusOutTimer) { m_focusOutTimer->stop(); } } xcb_window_t Client::frameId() const { return m_frame; } Xcb::Property Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); } void Client::readShowOnScreenEdge(Xcb::Property &property) { //value comes in two parts, edge in the lower byte //then the type in the upper byte // 0 = autohide // 1 = raise in front on activate const uint32_t value = property.value(ElectricNone); ElectricBorder border = ElectricNone; switch (value & 0xFF) { case 0: border = ElectricTop; break; case 1: border = ElectricRight; break; case 2: border = ElectricBottom; break; case 3: border = ElectricLeft; break; } if (border != ElectricNone) { disconnect(m_edgeRemoveConnection); disconnect(m_edgeGeometryTrackingConnection); bool successfullyHidden = false; if (((value >> 8) & 0xFF) == 1) { setKeepBelow(true); successfullyHidden = keepBelow(); //request could have failed due to user kwin rules m_edgeRemoveConnection = connect(this, &AbstractClient::keepBelowChanged, this, [this](){ if (!keepBelow()) { ScreenEdges::self()->reserve(this, ElectricNone); } }); } else { hideClient(true); successfullyHidden = isHiddenInternal(); m_edgeGeometryTrackingConnection = connect(this, &Client::geometryChanged, this, [this, border](){ hideClient(true); ScreenEdges::self()->reserve(this, border); }); } if (successfullyHidden) { ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } else if (!property.isNull() && property->type != XCB_ATOM_NONE) { // property value is incorrect, delete the property // so that the client knows that it is not hidden xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } else { // restore // TODO: add proper unreserve //this will call showOnScreenEdge to reset the state disconnect(m_edgeGeometryTrackingConnection); ScreenEdges::self()->reserve(this, ElectricNone); } } void Client::updateShowOnScreenEdge() { Xcb::Property property = fetchShowOnScreenEdge(); readShowOnScreenEdge(property); } void Client::showOnScreenEdge() { disconnect(m_edgeRemoveConnection); hideClient(false); setKeepBelow(false); xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } void Client::addDamage(const QRegion &damage) { if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } repaints_region += damage; Toplevel::addDamage(damage); } -bool Client::belongsToSameApplication(const AbstractClient *other, bool active_hack) const +bool Client::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const { const Client *c2 = dynamic_cast(other); if (!c2) { return false; } - return Client::belongToSameApplication(this, c2, active_hack); + return Client::belongToSameApplication(this, c2, checks); } void Client::updateTabGroupStates(TabGroup::States states) { if (auto t = tabGroup()) { t->updateStates(this, states); } } QSize Client::resizeIncrements() const { return m_geometryHints.resizeIncrements(); } Xcb::StringProperty Client::fetchApplicationMenuServiceName() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name); } void Client::readApplicationMenuServiceName(Xcb::StringProperty &property) { updateApplicationMenuServiceName(QString::fromUtf8(property)); } void Client::checkApplicationMenuServiceName() { Xcb::StringProperty property = fetchApplicationMenuServiceName(); readApplicationMenuServiceName(property); } Xcb::StringProperty Client::fetchApplicationMenuObjectPath() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path); } void Client::readApplicationMenuObjectPath(Xcb::StringProperty &property) { updateApplicationMenuObjectPath(QString::fromUtf8(property)); } void Client::checkApplicationMenuObjectPath() { Xcb::StringProperty property = fetchApplicationMenuObjectPath(); readApplicationMenuObjectPath(property); } void Client::handleSync() { setReadyForPainting(); setupWindowManagementInterface(); syncRequest.isPending = false; if (syncRequest.failsafeTimeout) syncRequest.failsafeTimeout->stop(); if (isResize()) { if (syncRequest.timeout) syncRequest.timeout->stop(); performMoveResize(); } else // setReadyForPainting does as well, but there's a small chance for resize syncs after the resize ended addRepaintFull(); } } // namespace diff --git a/client.h b/client.h index 1af55b79f..cd13885d0 100644 --- a/client.h +++ b/client.h @@ -1,747 +1,744 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_CLIENT_H #define KWIN_CLIENT_H // kwin #include "options.h" #include "rules.h" #include "tabgroup.h" #include "abstract_client.h" #include "xcbutils.h" // Qt #include #include #include #include #include // X #include // TODO: Cleanup the order of things in this .h file class QTimer; class KStartupInfoData; class KStartupInfoId; namespace KWin { /** * @brief Defines Predicates on how to search for a Client. * * Used by Workspace::findClient. */ enum class Predicate { WindowMatch, WrapperIdMatch, FrameIdMatch, InputIdMatch }; class KWIN_EXPORT Client : public AbstractClient { Q_OBJECT /** * By how much the window wishes to grow/shrink at least. Usually QSize(1,1). * MAY BE DISOBEYED BY THE WM! It's only for information, do NOT rely on it at all. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(QSize basicUnit READ basicUnit) /** * The "Window Tabs" Group this Client belongs to. **/ Q_PROPERTY(KWin::TabGroup* tabGroup READ tabGroup NOTIFY tabGroupChanged SCRIPTABLE false) /** * A client can block compositing. That is while the Client is alive and the state is set, * Compositing is suspended and is resumed when there are no Clients blocking compositing any * more. * * This is actually set by a window property, unfortunately not used by the target application * group. For convenience it's exported as a property to the scripts. * * Use with care! **/ Q_PROPERTY(bool blocksCompositing READ isBlockingCompositing WRITE setBlockingCompositing NOTIFY blockingCompositingChanged) /** * Whether the Client uses client side window decorations. * Only GTK+ are detected. **/ Q_PROPERTY(bool clientSideDecorated READ isClientSideDecorated NOTIFY clientSideDecoratedChanged) public: explicit Client(); xcb_window_t wrapperId() const; xcb_window_t inputId() const { return m_decoInputExtent; } virtual xcb_window_t frameId() const override; bool isTransient() const override; bool groupTransient() const; bool wasOriginallyGroupTransient() const; QList mainClients() const override; // Call once before loop , is not indirect bool hasTransient(const AbstractClient* c, bool indirect) const override; void checkTransient(xcb_window_t w); AbstractClient* findModal(bool allow_itself = false) override; const Group* group() const; Group* group(); void checkGroup(Group* gr = NULL, bool force = false); void changeClientLeaderGroup(Group* gr); void updateWindowRules(Rules::Types selection) override; void updateFullscreenMonitors(NETFullscreenMonitors topology); bool hasNETSupport() const; QSize minSize() const override; QSize maxSize() const override; QSize basicUnit() const; virtual QSize clientSize() const; QPoint inputPos() const { return input_offset; } // Inside of geometry() bool windowEvent(xcb_generic_event_t *e); NET::WindowType windowType(bool direct = false, int supported_types = 0) const; bool manage(xcb_window_t w, bool isMapped); void releaseWindow(bool on_shutdown = false); void destroyClient(); virtual QStringList activities() const; void setOnActivity(const QString &activity, bool enable); void setOnAllActivities(bool set) override; void setOnActivities(QStringList newActivitiesList) override; void updateActivities(bool includeTransients); void blockActivityUpdates(bool b = true) override; /// Is not minimized and not hidden. I.e. normally visible on some virtual desktop. bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override; // For compositing ShadeMode shadeMode() const override; // Prefer isShade() void setShade(ShadeMode mode) override; bool isShadeable() const override; bool isMaximizable() const override; QRect geometryRestore() const override; MaximizeMode maximizeMode() const override; bool isMinimizable() const override; QRect iconGeometry() const override; void setFullScreen(bool set, bool user = true) override; bool isFullScreen() const override; - bool isFullScreenable() const override; - bool isFullScreenable(bool fullscreen_hack) const; bool userCanSetFullScreen() const override; QRect geometryFSRestore() const { return geom_fs_restore; // Only for session saving } int fullScreenMode() const { return fullscreen_mode; // only for session saving } bool noBorder() const override; void setNoBorder(bool set) override; bool userCanSetNoBorder() const override; - void checkNoBorder(); + void checkNoBorder() override; int sessionStackingOrder() const; // Auxiliary functions, depend on the windowType bool wantsInput() const override; bool isResizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isCloseable() const override; ///< May be closed by the user (May have a close button) void takeFocus() override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void updateShape(); using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; /// plainResize() simply resizes void plainResize(int w, int h, ForceGeometry_t force = NormalGeometrySet); void plainResize(const QSize& s, ForceGeometry_t force = NormalGeometrySet); /// resizeWithChecks() resizes according to gravity, and checks workarea position using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; void resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); void resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); QSize sizeForClientSize(const QSize&, Sizemode mode = SizemodeAny, bool noframe = false) const override; bool providesContextHelp() const override; Options::WindowOperation mouseButtonToWindowOperation(Qt::MouseButtons button); bool performMouseCommand(Options::MouseCommand, const QPoint& globalPos) override; QRect adjustedClientArea(const QRect& desktop, const QRect& area) const; xcb_colormap_t colormap() const; /// Updates visibility depending on being shaded, virtual desktop, etc. void updateVisibility(); /// Hides a client - Basically like minimize, but without effects, it's simply hidden void hideClient(bool hide) override; bool hiddenPreview() const; ///< Window is mapped in order to get a window pixmap virtual bool setupCompositing(); void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void setBlockingCompositing(bool block); inline bool isBlockingCompositing() { return blocks_compositing; } QString captionNormal() const override { return cap_normal; } QString captionSuffix() const override { return cap_suffix; } using AbstractClient::keyPressEvent; void keyPressEvent(uint key_code, xcb_timestamp_t time); // FRAME ?? void updateMouseGrab() override; xcb_window_t moveResizeGrabWindow() const; const QPoint calculateGravitation(bool invert, int gravity = 0) const; // FRAME public? void NETMoveResize(int x_root, int y_root, NET::Direction direction); void NETMoveResizeWindow(int flags, int x, int y, int width, int height); void restackWindow(xcb_window_t above, int detail, NET::RequestSource source, xcb_timestamp_t timestamp, bool send_event = false); void gotPing(xcb_timestamp_t timestamp); void updateUserTime(xcb_timestamp_t time = XCB_TIME_CURRENT_TIME); xcb_timestamp_t userTime() const override; bool hasUserTimeSupport() const; /// Does 'delete c;' static void deleteClient(Client* c); - static bool belongToSameApplication(const Client* c1, const Client* c2, bool active_hack = false); + static bool belongToSameApplication(const Client* c1, const Client* c2, SameApplicationChecks checks = SameApplicationChecks()); static bool sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack); void killWindow() override; void toggleShade(); void showContextHelp() override; void cancelShadeHoverTimer(); void checkActiveModal(); StrutRect strutRect(StrutArea area) const; StrutRects strutRects() const; bool hasStrut() const override; // Tabbing functions TabGroup* tabGroup() const override; // Returns a pointer to client_group Q_INVOKABLE inline bool tabBefore(Client *other, bool activate) { return tabTo(other, false, activate); } Q_INVOKABLE inline bool tabBehind(Client *other, bool activate) { return tabTo(other, true, activate); } /** * Syncs the *dynamic* @param property @param fromThisClient or the @link currentTab() to * all members of the @link tabGroup() (if there is one) * * eg. if you call: * client->setProperty("kwin_tiling_floats", true); * client->syncTabGroupFor("kwin_tiling_floats", true) * all clients in this tabGroup will have ::property("kwin_tiling_floats").toBool() == true * * WARNING: non dynamic properties are ignored - you're not supposed to alter/update such explicitly */ Q_INVOKABLE void syncTabGroupFor(QString property, bool fromThisClient = false); Q_INVOKABLE bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false) override; /** * Set tab group - this is to be invoked by TabGroup::add/remove(client) and NO ONE ELSE */ void setTabGroup(TabGroup* group); /* * If shown is true the client is mapped and raised, if false * the client is unmapped and hidden, this function is called * when the tabbing group of the client switches its visible * client. */ void setClientShown(bool shown); /* * When a click is done in the decoration and it calls the group * to change the visible client it starts to move-resize the new * client, this function stops it. */ bool isCurrentTab() const override; /** * Whether or not the window has a strut that expands through the invisible area of * an xinerama setup where the monitors are not the same resolution. */ bool hasOffscreenXineramaStrut() const; // Decorations <-> Effects QRect decorationRect() const; QRect transparentRect() const; bool isClientSideDecorated() const; bool wantsShadowToBeRendered() const override; void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const override; Xcb::Property fetchFirstInTabBox() const; void readFirstInTabBox(Xcb::Property &property); void updateFirstInTabBox(); Xcb::StringProperty fetchColorScheme() const; void readColorScheme(Xcb::StringProperty &property); void updateColorScheme() override; //sets whether the client should be faked as being on all activities (and be shown during session save) void setSessionActivityOverride(bool needed); virtual bool isClient() const; template void print(T &stream) const; void cancelFocusOutTimer(); /** * Restores the Client after it had been hidden due to show on screen edge functionality. * In addition the property gets deleted so that the Client knows that it is visible again. **/ void showOnScreenEdge() override; Xcb::StringProperty fetchApplicationMenuServiceName() const; void readApplicationMenuServiceName(Xcb::StringProperty &property); void checkApplicationMenuServiceName(); Xcb::StringProperty fetchApplicationMenuObjectPath() const; void readApplicationMenuObjectPath(Xcb::StringProperty &property); void checkApplicationMenuObjectPath(); struct SyncRequest { xcb_sync_counter_t counter; xcb_sync_int64_t value; xcb_sync_alarm_t alarm; xcb_timestamp_t lastTimestamp; QTimer *timeout, *failsafeTimeout; bool isPending; }; const SyncRequest &getSyncRequest() const { return syncRequest; } void handleSync(); static void cleanupX11(); public Q_SLOTS: void closeWindow() override; void updateCaption() override; private Q_SLOTS: void shadeHover(); void shadeUnhover(); private: // Use Workspace::createClient() virtual ~Client(); ///< Use destroyClient() or releaseWindow() // Handlers for X11 events bool mapRequestEvent(xcb_map_request_event_t *e); void unmapNotifyEvent(xcb_unmap_notify_event_t *e); void destroyNotifyEvent(xcb_destroy_notify_event_t *e); void configureRequestEvent(xcb_configure_request_event_t *e); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e) override; void clientMessageEvent(xcb_client_message_event_t *e) override; void enterNotifyEvent(xcb_enter_notify_event_t *e); void leaveNotifyEvent(xcb_leave_notify_event_t *e); void focusInEvent(xcb_focus_in_event_t *e); void focusOutEvent(xcb_focus_out_event_t *e); virtual void damageNotifyEvent(); bool buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time = XCB_CURRENT_TIME); bool buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root); bool motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root); Client* findAutogroupCandidate() const; protected: virtual void debug(QDebug& stream) const; void addDamage(const QRegion &damage) override; - bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const override; + bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const override; void doSetActive() override; void doSetKeepAbove() override; void doSetKeepBelow() override; void doSetDesktop(int desktop, int was_desk) override; void doMinimize() override; void doSetSkipPager() override; void doSetSkipTaskbar() override; bool belongsToDesktop() const override; - bool isActiveFullScreen() const override; void setGeometryRestore(const QRect &geo) override; void updateTabGroupStates(TabGroup::States states) override; void doMove(int x, int y) override; bool doStartMoveResize() override; void doPerformMoveResize() override; bool isWaitingForMoveResizeSync() const override; void doResizeSync() override; QSize resizeIncrements() const override; bool acceptsFocus() const override; //Signals for the scripting interface //Signals make an excellent way for communication //in between objects as compared to simple function //calls Q_SIGNALS: void clientManaging(KWin::Client*); void clientFullScreenSet(KWin::Client*, bool, bool); /** * Emitted whenever the Client's TabGroup changed. That is whenever the Client is moved to * another group, but not when a Client gets added or removed to the Client's ClientGroup. **/ void tabGroupChanged(); /** * Emitted whenever the Client want to show it menu */ void showRequest(); /** * Emitted whenever the Client's menu is closed */ void menuHidden(); /** * Emitted whenever the Client's menu is available **/ void appMenuAvailable(); /** * Emitted whenever the Client's menu is unavailable */ void appMenuUnavailable(); /** * Emitted whenever the Client's block compositing state changes. **/ void blockingCompositingChanged(KWin::Client *client); void clientSideDecoratedChanged(); private: void exportMappingState(int s); // ICCCM 4.1.3.1, 4.1.4, NETWM 2.5.1 bool isManaged() const; ///< Returns false if this client is not yet managed void updateAllowedActions(bool force = false); QRect fullscreenMonitorsArea(NETFullscreenMonitors topology) const; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; int checkFullScreenHack(const QRect& geom) const; // 0 - None, 1 - One xinerama screen, 2 - Full area void updateFullScreenHack(const QRect& geom); void getWmNormalHints(); void getMotifHints(); void getIcons(); void fetchName(); void fetchIconicName(); QString readName() const; void setCaption(const QString& s, bool force = false); bool hasTransientInternal(const Client* c, bool indirect, ConstClientList& set) const; void setShortcutInternal() override; void configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool); NETExtendedStrut strut() const; int checkShadeGeometry(int w, int h); void getSyncCounter(); void sendSyncRequest(); void leaveMoveResize() override; void positionGeometryTip() override; void grabButton(int mod); void ungrabButton(int mod); void resizeDecoration(); void createDecoration(const QRect &oldgeom); void pingWindow(); void killProcess(bool ask, xcb_timestamp_t timestamp = XCB_TIME_CURRENT_TIME); void updateUrgency(); static void sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1 = 0, uint32_t data2 = 0, uint32_t data3 = 0, xcb_timestamp_t timestamp = xTime()); void embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth); void detectNoBorder(); Xcb::Property fetchGtkFrameExtents() const; void readGtkFrameExtents(Xcb::Property &prop); void detectGtkFrameExtents(); void destroyDecoration() override; void updateFrameExtents(); void internalShow(); void internalHide(); void internalKeep(); void map(); void unmap(); void updateHiddenPreview(); void updateInputShape(); xcb_timestamp_t readUserTimeMapTimestamp(const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, bool session) const; xcb_timestamp_t readUserCreationTime() const; void startupIdChanged(); void updateInputWindow(); bool tabTo(Client *other, bool behind, bool activate); Xcb::Property fetchShowOnScreenEdge() const; void readShowOnScreenEdge(Xcb::Property &property); /** * Reads the property and creates/destroys the screen edge if required * and shows/hides the client. **/ void updateShowOnScreenEdge(); Xcb::Window m_client; Xcb::Window m_wrapper; Xcb::Window m_frame; QStringList activityList; int m_activityUpdatesBlocked; bool m_blockedActivityUpdatesRequireTransients; Xcb::Window m_moveResizeGrabWindow; bool move_resize_has_keyboard_grab; bool m_managed; Xcb::GeometryHints m_geometryHints; void sendSyntheticConfigureNotify(); enum MappingState { Withdrawn, ///< Not handled, as per ICCCM WithdrawnState Mapped, ///< The frame is mapped Unmapped, ///< The frame is not mapped Kept ///< The frame should be unmapped, but is kept (For compositing) }; MappingState mapping_state; Xcb::TransientFor fetchTransient() const; void readTransientProperty(Xcb::TransientFor &transientFor); void readTransient(); xcb_window_t verifyTransientFor(xcb_window_t transient_for, bool set); void addTransient(AbstractClient* cl) override; void removeTransient(AbstractClient* cl) override; void removeFromMainClients(); void cleanGrouping(); void checkGroupTransients(); void setTransient(xcb_window_t new_transient_for_id); xcb_window_t m_transientForId; xcb_window_t m_originalTransientForId; ShadeMode shade_mode; Client *shade_below; uint deleting : 1; ///< True when doing cleanup and destroying the client Xcb::MotifHints m_motif; uint hidden : 1; ///< Forcibly hidden by calling hide() uint noborder : 1; uint app_noborder : 1; ///< App requested no border via window type, shape extension, etc. uint ignore_focus_stealing : 1; ///< Don't apply focus stealing prevention to this client bool blocks_compositing; // DON'T reorder - Saved to config files !!! enum FullScreenMode { FullScreenNone, FullScreenNormal, FullScreenHack ///< Non-NETWM fullscreen (noborder and size of desktop) }; FullScreenMode fullscreen_mode; MaximizeMode max_mode; QRect geom_restore; QRect geom_fs_restore; QTimer* shadeHoverTimer; xcb_colormap_t m_colormap; QString cap_normal, cap_iconic, cap_suffix; Group* in_group; TabGroup* tab_group; QTimer* ping_timer; qint64 m_killHelperPID; xcb_timestamp_t m_pingTimestamp; xcb_timestamp_t m_userTime; NET::Actions allowed_actions; QSize client_size; bool shade_geometry_change; SyncRequest syncRequest; static bool check_active_modal; ///< \see Client::checkActiveModal() int sm_stacking_order; friend struct ResetupRulesProcedure; friend bool performTransiencyCheck(); Xcb::StringProperty fetchActivities() const; void readActivities(Xcb::StringProperty &property); void checkActivities(); bool activitiesDefined; //whether the x property was actually set bool sessionActivityOverride; bool needsXWindowMove; Xcb::Window m_decoInputExtent; QPoint input_offset; QTimer *m_focusOutTimer; QList m_connections; bool m_clientSideDecorated; QMetaObject::Connection m_edgeRemoveConnection; QMetaObject::Connection m_edgeGeometryTrackingConnection; }; inline xcb_window_t Client::wrapperId() const { return m_wrapper; } inline bool Client::isClientSideDecorated() const { return m_clientSideDecorated; } inline bool Client::groupTransient() const { return m_transientForId == rootWindow(); } // Needed because verifyTransientFor() may set transient_for_id to root window, // if the original value has a problem (window doesn't exist, etc.) inline bool Client::wasOriginallyGroupTransient() const { return m_originalTransientForId == rootWindow(); } inline bool Client::isTransient() const { return m_transientForId != XCB_WINDOW_NONE; } inline const Group* Client::group() const { return in_group; } inline Group* Client::group() { return in_group; } inline TabGroup* Client::tabGroup() const { return tab_group; } inline bool Client::isShown(bool shaded_is_shown) const { return !isMinimized() && (!isShade() || shaded_is_shown) && !hidden && (!tabGroup() || tabGroup()->current() == this); } inline bool Client::isHiddenInternal() const { return hidden; } inline ShadeMode Client::shadeMode() const { return shade_mode; } inline QRect Client::geometryRestore() const { return geom_restore; } inline void Client::setGeometryRestore(const QRect &geo) { geom_restore = geo; } inline MaximizeMode Client::maximizeMode() const { return max_mode; } inline bool Client::isFullScreen() const { return fullscreen_mode != FullScreenNone; } inline bool Client::hasNETSupport() const { return info->hasNETSupport(); } inline xcb_colormap_t Client::colormap() const { return m_colormap; } inline int Client::sessionStackingOrder() const { return sm_stacking_order; } inline bool Client::isManaged() const { return m_managed; } inline QSize Client::clientSize() const { return client_size; } inline void Client::plainResize(const QSize& s, ForceGeometry_t force) { plainResize(s.width(), s.height(), force); } inline void Client::resizeWithChecks(int w, int h, AbstractClient::ForceGeometry_t force) { resizeWithChecks(w, h, XCB_GRAVITY_BIT_FORGET, force); } inline void Client::resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), gravity, force); } inline bool Client::hasUserTimeSupport() const { return info->userTime() != -1U; } inline xcb_window_t Client::moveResizeGrabWindow() const { return m_moveResizeGrabWindow; } inline bool Client::hiddenPreview() const { return mapping_state == Kept; } template inline void Client::print(T &stream) const { stream << "\'ID:" << window() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } } // namespace Q_DECLARE_METATYPE(KWin::Client*) Q_DECLARE_METATYPE(QList) #endif diff --git a/cmake/modules/FindLibdrm.cmake b/cmake/modules/FindLibdrm.cmake index ebaa87dc9..9936e07ee 100644 --- a/cmake/modules/FindLibdrm.cmake +++ b/cmake/modules/FindLibdrm.cmake @@ -1,126 +1,126 @@ #.rst: # FindLibdrm # ------- # # Try to find libdrm on a Unix system. # # This will define the following variables: # # ``Libdrm_FOUND`` # True if (the requested version of) libdrm is available # ``Libdrm_VERSION`` # The version of libdrm # ``Libdrm_LIBRARIES`` # This can be passed to target_link_libraries() instead of the ``Libdrm::Libdrm`` # target # ``Libdrm_INCLUDE_DIRS`` # This should be passed to target_include_directories() if the target is not # used for linking # ``Libdrm_DEFINITIONS`` # This should be passed to target_compile_options() if the target is not # used for linking # # If ``Libdrm_FOUND`` is TRUE, it will also define the following imported target: # # ``Libdrm::Libdrm`` # The libdrm library # # In general we recommend using the imported target, as it is easier to use. # Bear in mind, however, that if the target is in the link interface of an # exported library, it must be made available by the package config file. #============================================================================= # Copyright 2014 Alex Merry # Copyright 2014 Martin Gräßlin # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #============================================================================= if(CMAKE_VERSION VERSION_LESS 2.8.12) message(FATAL_ERROR "CMake 2.8.12 is required by FindLibdrm.cmake") endif() if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use FindLibdrm.cmake") endif() if(NOT WIN32) # Use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls find_package(PkgConfig) pkg_check_modules(PKG_Libdrm QUIET libdrm) set(Libdrm_DEFINITIONS ${PKG_Libdrm_CFLAGS_OTHER}) set(Libdrm_VERSION ${PKG_Libdrm_VERSION}) find_path(Libdrm_INCLUDE_DIR NAMES xf86drm.h HINTS ${PKG_Libdrm_INCLUDE_DIRS} ) find_library(Libdrm_LIBRARY NAMES drm HINTS ${PKG_Libdrm_LIBRARY_DIRS} ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Libdrm FOUND_VAR Libdrm_FOUND REQUIRED_VARS Libdrm_LIBRARY Libdrm_INCLUDE_DIR VERSION_VAR Libdrm_VERSION ) if(Libdrm_FOUND AND NOT TARGET Libdrm::Libdrm) add_library(Libdrm::Libdrm UNKNOWN IMPORTED) set_target_properties(Libdrm::Libdrm PROPERTIES IMPORTED_LOCATION "${Libdrm_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${Libdrm_DEFINITIONS}" INTERFACE_INCLUDE_DIRECTORIES "${Libdrm_INCLUDE_DIR}" INTERFACE_INCLUDE_DIRECTORIES "${Libdrm_INCLUDE_DIR}/libdrm" ) endif() mark_as_advanced(Libdrm_LIBRARY Libdrm_INCLUDE_DIR) # compatibility variables set(Libdrm_LIBRARIES ${Libdrm_LIBRARY}) - set(Libdrm_INCLUDE_DIRS ${Libdrm_INCLUDE_DIR}) + set(Libdrm_INCLUDE_DIRS ${Libdrm_INCLUDE_DIR} "${Libdrm_INCLUDE_DIR}/libdrm") set(Libdrm_VERSION_STRING ${Libdrm_VERSION}) else() message(STATUS "FindLibdrm.cmake cannot find libdrm on Windows systems.") set(Libdrm_FOUND FALSE) endif() include(FeatureSummary) set_package_properties(Libdrm PROPERTIES URL "https://wiki.freedesktop.org/dri/" DESCRIPTION "Userspace interface to kernel DRM services." ) diff --git a/colorcorrection/colorcorrect_settings.kcfg b/colorcorrection/colorcorrect_settings.kcfg new file mode 100644 index 000000000..616d0627f --- /dev/null +++ b/colorcorrection/colorcorrect_settings.kcfg @@ -0,0 +1,56 @@ + + + + + + true + + + false + + + true + + + + + + + + NightColorMode::Automatic + + + true + + + 4500 + + + 0. + + + 0. + + + true + + + 0. + + + 0. + + + "0600" + + + "1800" + + + 30 + + + diff --git a/colorcorrection/colorcorrect_settings.kcfgc b/colorcorrection/colorcorrect_settings.kcfgc new file mode 100644 index 000000000..4c178d684 --- /dev/null +++ b/colorcorrection/colorcorrect_settings.kcfgc @@ -0,0 +1,8 @@ +File=colorcorrect_settings.kcfg +NameSpace=KWin::ColorCorrect +ClassName=Settings +Singleton=true +Mutators=true +# manager.h is needed for NightColorMode +IncludeFiles=colorcorrection/manager.h +UseEnumTypes=true diff --git a/plugins/platforms/drm/drm_buffer_gbm.h b/colorcorrection/colorcorrectdbusinterface.cpp similarity index 50% copy from plugins/platforms/drm/drm_buffer_gbm.h copy to colorcorrection/colorcorrectdbusinterface.cpp index bdd1e94f9..5376d9b22 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.h +++ b/colorcorrection/colorcorrectdbusinterface.cpp @@ -1,68 +1,54 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2017 Roman Gilg -Copyright 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ -#ifndef KWIN_DRM_BUFFER_GBM_H -#define KWIN_DRM_BUFFER_GBM_H -#include "drm_buffer.h" +#include "colorcorrectdbusinterface.h" +#include "colorcorrectadaptor.h" -#include +#include "manager.h" -struct gbm_bo; +namespace KWin { +namespace ColorCorrect { -namespace KWin +ColorCorrectDBusInterface::ColorCorrectDBusInterface(Manager *parent) + : QObject(parent) + , m_manager(parent) { + connect(m_manager, &Manager::configChange, this, &ColorCorrectDBusInterface::nightColorConfigChanged); + new ColorCorrectAdaptor(this); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/ColorCorrect"), this); +} -class DrmBackend; -class GbmSurface; - -class DrmSurfaceBuffer : public DrmBuffer +QHash ColorCorrectDBusInterface::nightColorInfo() { -public: - DrmSurfaceBuffer(DrmBackend *backend, const std::shared_ptr &surface); - ~DrmSurfaceBuffer(); - - bool needsModeChange(DrmBuffer *b) const override { - if (DrmSurfaceBuffer *sb = dynamic_cast(b)) { - return hasBo() != sb->hasBo(); - } else { - return true; - } - } - - bool hasBo() const { - return m_bo != nullptr; - } - - gbm_bo* getBo() const { - return m_bo; - } - - void releaseGbm() override; - -private: - std::shared_ptr m_surface; - gbm_bo *m_bo = nullptr; -}; + return m_manager->info(); +} +bool ColorCorrectDBusInterface::setNightColorConfig(QHash data) +{ + return m_manager->changeConfiguration(data); } -#endif +void ColorCorrectDBusInterface::nightColorAutoLocationUpdate(double latitude, double longitude) +{ + m_manager->autoLocationUpdate(latitude, longitude); +} +} +} diff --git a/colorcorrection/colorcorrectdbusinterface.h b/colorcorrection/colorcorrectdbusinterface.h new file mode 100644 index 000000000..5764b543a --- /dev/null +++ b/colorcorrection/colorcorrectdbusinterface.h @@ -0,0 +1,126 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#ifndef KWIN_NIGHTCOLOR_DBUS_INTERFACE_H +#define KWIN_NIGHTCOLOR_DBUS_INTERFACE_H + +#include +#include + +namespace KWin +{ + +namespace ColorCorrect +{ + +class Manager; + +class ColorCorrectDBusInterface : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.ColorCorrect") + +public: + explicit ColorCorrectDBusInterface(Manager *parent); + virtual ~ColorCorrectDBusInterface() = default; + +public Q_SLOTS: + /** + * @brief Gives information about the current state of Night Color. + * + * The returned variant hash has always the fields: + * - ActiveEnabled + * - Active + * - Mode + * - NightTemperatureEnabled + * - NightTemperature + * - Running + * - CurrentColorTemperature + * - LatitudeAuto + * - LongitudeAuto + * - LocationEnabled + * - LatitudeFixed + * - LongitudeFixed + * - TimingsEnabled + * - MorningBeginFixed + * - EveningBeginFixed + * - TransitionTime + * + * @return QHash + * @see nightColorConfigChange + * @see signalNightColorConfigChange + * @since 5.12 + **/ + QHash nightColorInfo(); + /** + * @brief Allows changing the Night Color configuration. + * + * The provided variant hash can have the following fields: + * - Active + * - Mode + * - NightTemperature + * - LatitudeAuto + * - LongitudeAuto + * - LatitudeFixed + * - LongitudeFixed + * - MorningBeginFixed + * - EveningBeginFixed + * - TransitionTime + * + * It returns true if the configuration change was succesful, otherwise false. + * A change request for the location or timings needs to provide all relevant fields at the same time + * to be succesful. Otherwise the whole change request will get ignored. A change request will be ignored + * as a whole as well, if one of the provided informations has been sent in a wrong format. + * + * @return bool + * @see nightColorInfo + * @see signalNightColorConfigChange + * @since 5.12 + **/ + bool setNightColorConfig(QHash data); + /** + * @brief For receiving auto location updates, primarily through the KDE Daemon + * @return void + * @since 5.12 + **/ + void nightColorAutoLocationUpdate(double latitude, double longitude); + +Q_SIGNALS: + /** + * @brief Emits that the Night Color configuration has been changed. + * + * The provided variant hash provides the same fields as @link nightColorInfo + * + * @return void + * @see nightColorInfo + * @see nightColorConfigChange + * @since 5.12 + **/ + void nightColorConfigChanged(QHash data); + +private: + Manager *m_manager; +}; + +} + +} + +#endif // KWIN_NIGHTCOLOR_DBUS_INTERFACE_H diff --git a/colorcorrection/constants.h b/colorcorrection/constants.h new file mode 100644 index 000000000..868079c91 --- /dev/null +++ b/colorcorrection/constants.h @@ -0,0 +1,288 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_NIGHTCOLOR_CONSTANTS_H +#define KWIN_NIGHTCOLOR_CONSTANTS_H + +namespace KWin +{ +namespace ColorCorrect +{ + +static const int MSC_DAY = 86400000; +static const int MIN_TEMPERATURE = 1000; +static const int NEUTRAL_TEMPERATURE = 6500; +static const int DEFAULT_NIGHT_TEMPERATURE = 4500; +static const int FALLBACK_SLOW_UPDATE_TIME = 1800000; /* 30 minutes */ + +/* Whitepoint values for temperatures at 100K intervals. + * These will be interpolated for the actual temperature. + * This table was provided by Ingo Thies, 2013. + * See the following file for more information: + * https://github.com/jonls/redshift/blob/master/README-colorramp + */ +static const float blackbodyColor[] = { + 1.00000000, 0.18172716, 0.00000000, /* 1000K */ + 1.00000000, 0.25503671, 0.00000000, /* 1100K */ + 1.00000000, 0.30942099, 0.00000000, /* 1200K */ + 1.00000000, 0.35357379, 0.00000000, /* ... */ + 1.00000000, 0.39091524, 0.00000000, + 1.00000000, 0.42322816, 0.00000000, + 1.00000000, 0.45159884, 0.00000000, + 1.00000000, 0.47675916, 0.00000000, + 1.00000000, 0.49923747, 0.00000000, + 1.00000000, 0.51943421, 0.00000000, + 1.00000000, 0.54360078, 0.08679949, /* 2000K */ + 1.00000000, 0.56618736, 0.14065513, + 1.00000000, 0.58734976, 0.18362641, + 1.00000000, 0.60724493, 0.22137978, + 1.00000000, 0.62600248, 0.25591950, + 1.00000000, 0.64373109, 0.28819679, + 1.00000000, 0.66052319, 0.31873863, + 1.00000000, 0.67645822, 0.34786758, + 1.00000000, 0.69160518, 0.37579588, + 1.00000000, 0.70602449, 0.40267128, + 1.00000000, 0.71976951, 0.42860152, /* 3000K */ + 1.00000000, 0.73288760, 0.45366838, + 1.00000000, 0.74542112, 0.47793608, + 1.00000000, 0.75740814, 0.50145662, + 1.00000000, 0.76888303, 0.52427322, + 1.00000000, 0.77987699, 0.54642268, + 1.00000000, 0.79041843, 0.56793692, + 1.00000000, 0.80053332, 0.58884417, + 1.00000000, 0.81024551, 0.60916971, + 1.00000000, 0.81957693, 0.62893653, + 1.00000000, 0.82854786, 0.64816570, /* 4000K */ + 1.00000000, 0.83717703, 0.66687674, + 1.00000000, 0.84548188, 0.68508786, + 1.00000000, 0.85347859, 0.70281616, + 1.00000000, 0.86118227, 0.72007777, + 1.00000000, 0.86860704, 0.73688797, /* 4500K */ + 1.00000000, 0.87576611, 0.75326132, + 1.00000000, 0.88267187, 0.76921169, + 1.00000000, 0.88933596, 0.78475236, + 1.00000000, 0.89576933, 0.79989606, + 1.00000000, 0.90198230, 0.81465502, /* 5000K */ + 1.00000000, 0.90963069, 0.82838210, + 1.00000000, 0.91710889, 0.84190889, + 1.00000000, 0.92441842, 0.85523742, + 1.00000000, 0.93156127, 0.86836903, + 1.00000000, 0.93853986, 0.88130458, + 1.00000000, 0.94535695, 0.89404470, + 1.00000000, 0.95201559, 0.90658983, + 1.00000000, 0.95851906, 0.91894041, + 1.00000000, 0.96487079, 0.93109690, + 1.00000000, 0.97107439, 0.94305985, /* 6000K */ + 1.00000000, 0.97713351, 0.95482993, + 1.00000000, 0.98305189, 0.96640795, + 1.00000000, 0.98883326, 0.97779486, + 1.00000000, 0.99448139, 0.98899179, + 1.00000000, 1.00000000, 1.00000000, /* 6500K */ + 0.98947904, 0.99348723, 1.00000000, + 0.97940448, 0.98722715, 1.00000000, + 0.96975025, 0.98120637, 1.00000000, + 0.96049223, 0.97541240, 1.00000000, + 0.95160805, 0.96983355, 1.00000000, /* 7000K */ + 0.94303638, 0.96443333, 1.00000000, + 0.93480451, 0.95923080, 1.00000000, + 0.92689056, 0.95421394, 1.00000000, + 0.91927697, 0.94937330, 1.00000000, + 0.91194747, 0.94470005, 1.00000000, + 0.90488690, 0.94018594, 1.00000000, + 0.89808115, 0.93582323, 1.00000000, + 0.89151710, 0.93160469, 1.00000000, + 0.88518247, 0.92752354, 1.00000000, + 0.87906581, 0.92357340, 1.00000000, /* 8000K */ + 0.87315640, 0.91974827, 1.00000000, + 0.86744421, 0.91604254, 1.00000000, + 0.86191983, 0.91245088, 1.00000000, + 0.85657444, 0.90896831, 1.00000000, + 0.85139976, 0.90559011, 1.00000000, + 0.84638799, 0.90231183, 1.00000000, + 0.84153180, 0.89912926, 1.00000000, + 0.83682430, 0.89603843, 1.00000000, + 0.83225897, 0.89303558, 1.00000000, + 0.82782969, 0.89011714, 1.00000000, /* 9000K */ + 0.82353066, 0.88727974, 1.00000000, + 0.81935641, 0.88452017, 1.00000000, + 0.81530175, 0.88183541, 1.00000000, + 0.81136180, 0.87922257, 1.00000000, + 0.80753191, 0.87667891, 1.00000000, + 0.80380769, 0.87420182, 1.00000000, + 0.80018497, 0.87178882, 1.00000000, + 0.79665980, 0.86943756, 1.00000000, + 0.79322843, 0.86714579, 1.00000000, + 0.78988728, 0.86491137, 1.00000000, /* 10000K */ + 0.78663296, 0.86273225, 1.00000000, + 0.78346225, 0.86060650, 1.00000000, + 0.78037207, 0.85853224, 1.00000000, + 0.77735950, 0.85650771, 1.00000000, + 0.77442176, 0.85453121, 1.00000000, + 0.77155617, 0.85260112, 1.00000000, + 0.76876022, 0.85071588, 1.00000000, + 0.76603147, 0.84887402, 1.00000000, + 0.76336762, 0.84707411, 1.00000000, + 0.76076645, 0.84531479, 1.00000000, /* 11000K */ + 0.75822586, 0.84359476, 1.00000000, + 0.75574383, 0.84191277, 1.00000000, + 0.75331843, 0.84026762, 1.00000000, + 0.75094780, 0.83865816, 1.00000000, + 0.74863017, 0.83708329, 1.00000000, + 0.74636386, 0.83554194, 1.00000000, + 0.74414722, 0.83403311, 1.00000000, + 0.74197871, 0.83255582, 1.00000000, + 0.73985682, 0.83110912, 1.00000000, + 0.73778012, 0.82969211, 1.00000000, /* 12000K */ + 0.73574723, 0.82830393, 1.00000000, + 0.73375683, 0.82694373, 1.00000000, + 0.73180765, 0.82561071, 1.00000000, + 0.72989845, 0.82430410, 1.00000000, + 0.72802807, 0.82302316, 1.00000000, + 0.72619537, 0.82176715, 1.00000000, + 0.72439927, 0.82053539, 1.00000000, + 0.72263872, 0.81932722, 1.00000000, + 0.72091270, 0.81814197, 1.00000000, + 0.71922025, 0.81697905, 1.00000000, /* 13000K */ + 0.71756043, 0.81583783, 1.00000000, + 0.71593234, 0.81471775, 1.00000000, + 0.71433510, 0.81361825, 1.00000000, + 0.71276788, 0.81253878, 1.00000000, + 0.71122987, 0.81147883, 1.00000000, + 0.70972029, 0.81043789, 1.00000000, + 0.70823838, 0.80941546, 1.00000000, + 0.70678342, 0.80841109, 1.00000000, + 0.70535469, 0.80742432, 1.00000000, + 0.70395153, 0.80645469, 1.00000000, /* 14000K */ + 0.70257327, 0.80550180, 1.00000000, + 0.70121928, 0.80456522, 1.00000000, + 0.69988894, 0.80364455, 1.00000000, + 0.69858167, 0.80273941, 1.00000000, + 0.69729688, 0.80184943, 1.00000000, + 0.69603402, 0.80097423, 1.00000000, + 0.69479255, 0.80011347, 1.00000000, + 0.69357196, 0.79926681, 1.00000000, + 0.69237173, 0.79843391, 1.00000000, + 0.69119138, 0.79761446, 1.00000000, /* 15000K */ + 0.69003044, 0.79680814, 1.00000000, + 0.68888844, 0.79601466, 1.00000000, + 0.68776494, 0.79523371, 1.00000000, + 0.68665951, 0.79446502, 1.00000000, + 0.68557173, 0.79370830, 1.00000000, + 0.68450119, 0.79296330, 1.00000000, + 0.68344751, 0.79222975, 1.00000000, + 0.68241029, 0.79150740, 1.00000000, + 0.68138918, 0.79079600, 1.00000000, + 0.68038380, 0.79009531, 1.00000000, /* 16000K */ + 0.67939381, 0.78940511, 1.00000000, + 0.67841888, 0.78872517, 1.00000000, + 0.67745866, 0.78805526, 1.00000000, + 0.67651284, 0.78739518, 1.00000000, + 0.67558112, 0.78674472, 1.00000000, + 0.67466317, 0.78610368, 1.00000000, + 0.67375872, 0.78547186, 1.00000000, + 0.67286748, 0.78484907, 1.00000000, + 0.67198916, 0.78423512, 1.00000000, + 0.67112350, 0.78362984, 1.00000000, /* 17000K */ + 0.67027024, 0.78303305, 1.00000000, + 0.66942911, 0.78244457, 1.00000000, + 0.66859988, 0.78186425, 1.00000000, + 0.66778228, 0.78129191, 1.00000000, + 0.66697610, 0.78072740, 1.00000000, + 0.66618110, 0.78017057, 1.00000000, + 0.66539706, 0.77962127, 1.00000000, + 0.66462376, 0.77907934, 1.00000000, + 0.66386098, 0.77854465, 1.00000000, + 0.66310852, 0.77801705, 1.00000000, /* 18000K */ + 0.66236618, 0.77749642, 1.00000000, + 0.66163375, 0.77698261, 1.00000000, + 0.66091106, 0.77647551, 1.00000000, + 0.66019791, 0.77597498, 1.00000000, + 0.65949412, 0.77548090, 1.00000000, + 0.65879952, 0.77499315, 1.00000000, + 0.65811392, 0.77451161, 1.00000000, + 0.65743716, 0.77403618, 1.00000000, + 0.65676908, 0.77356673, 1.00000000, + 0.65610952, 0.77310316, 1.00000000, /* 19000K */ + 0.65545831, 0.77264537, 1.00000000, + 0.65481530, 0.77219324, 1.00000000, + 0.65418036, 0.77174669, 1.00000000, + 0.65355332, 0.77130560, 1.00000000, + 0.65293404, 0.77086988, 1.00000000, + 0.65232240, 0.77043944, 1.00000000, + 0.65171824, 0.77001419, 1.00000000, + 0.65112144, 0.76959404, 1.00000000, + 0.65053187, 0.76917889, 1.00000000, + 0.64994941, 0.76876866, 1.00000000, /* 20000K */ + 0.64937392, 0.76836326, 1.00000000, + 0.64880528, 0.76796263, 1.00000000, + 0.64824339, 0.76756666, 1.00000000, + 0.64768812, 0.76717529, 1.00000000, + 0.64713935, 0.76678844, 1.00000000, + 0.64659699, 0.76640603, 1.00000000, + 0.64606092, 0.76602798, 1.00000000, + 0.64553103, 0.76565424, 1.00000000, + 0.64500722, 0.76528472, 1.00000000, + 0.64448939, 0.76491935, 1.00000000, /* 21000K */ + 0.64397745, 0.76455808, 1.00000000, + 0.64347129, 0.76420082, 1.00000000, + 0.64297081, 0.76384753, 1.00000000, + 0.64247594, 0.76349813, 1.00000000, + 0.64198657, 0.76315256, 1.00000000, + 0.64150261, 0.76281076, 1.00000000, + 0.64102399, 0.76247267, 1.00000000, + 0.64055061, 0.76213824, 1.00000000, + 0.64008239, 0.76180740, 1.00000000, + 0.63961926, 0.76148010, 1.00000000, /* 22000K */ + 0.63916112, 0.76115628, 1.00000000, + 0.63870790, 0.76083590, 1.00000000, + 0.63825953, 0.76051890, 1.00000000, + 0.63781592, 0.76020522, 1.00000000, + 0.63737701, 0.75989482, 1.00000000, + 0.63694273, 0.75958764, 1.00000000, + 0.63651299, 0.75928365, 1.00000000, + 0.63608774, 0.75898278, 1.00000000, + 0.63566691, 0.75868499, 1.00000000, + 0.63525042, 0.75839025, 1.00000000, /* 23000K */ + 0.63483822, 0.75809849, 1.00000000, + 0.63443023, 0.75780969, 1.00000000, + 0.63402641, 0.75752379, 1.00000000, + 0.63362667, 0.75724075, 1.00000000, + 0.63323097, 0.75696053, 1.00000000, + 0.63283925, 0.75668310, 1.00000000, + 0.63245144, 0.75640840, 1.00000000, + 0.63206749, 0.75613641, 1.00000000, + 0.63168735, 0.75586707, 1.00000000, + 0.63131096, 0.75560036, 1.00000000, /* 24000K */ + 0.63093826, 0.75533624, 1.00000000, + 0.63056920, 0.75507467, 1.00000000, + 0.63020374, 0.75481562, 1.00000000, + 0.62984181, 0.75455904, 1.00000000, + 0.62948337, 0.75430491, 1.00000000, + 0.62912838, 0.75405319, 1.00000000, + 0.62877678, 0.75380385, 1.00000000, + 0.62842852, 0.75355685, 1.00000000, + 0.62808356, 0.75331217, 1.00000000, + 0.62774186, 0.75306977, 1.00000000, /* 25000K */ + 0.62740336, 0.75282962, 1.00000000 +}; + +} +} + +#endif // KWIN_NIGHTCOLOR_CONSTANTS_H diff --git a/plugins/platforms/drm/drm_object_connector.h b/colorcorrection/gammaramp.h similarity index 61% copy from plugins/platforms/drm/drm_object_connector.h copy to colorcorrection/gammaramp.h index 0f6fcdbd4..53ef606b5 100644 --- a/plugins/platforms/drm/drm_object_connector.h +++ b/colorcorrection/gammaramp.h @@ -1,57 +1,50 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2016 Roman Gilg +Copyright 2017 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ -#ifndef KWIN_DRM_OBJECT_CONNECTOR_H -#define KWIN_DRM_OBJECT_CONNECTOR_H - -#include "drm_object.h" +#ifndef KWIN_GAMMARAMP_H +#define KWIN_GAMMARAMP_H namespace KWin { -class DrmConnector : public DrmObject +namespace ColorCorrect { -public: - DrmConnector(uint32_t connector_id, DrmBackend *backend); - - virtual ~DrmConnector(); - - bool atomicInit(); - - enum class PropertyIndex { - CrtcId = 0, - Count - }; - QVector encoders() { - return m_encoders; +struct GammaRamp { + GammaRamp(int _size) { + size = _size; + red = new uint16_t[3 * _size]; + green = red + _size; + blue = green + _size; + } + ~GammaRamp() { + delete[] red; + red = green = blue = nullptr; } - - bool initProps(); - bool isConnected(); - -private: - QVector m_encoders; + uint32_t size = 0; + uint16_t *red = nullptr; + uint16_t *green = nullptr; + uint16_t *blue = nullptr; }; +} } -#endif - +#endif // KWIN_GAMMARAMP_H diff --git a/colorcorrection/manager.cpp b/colorcorrection/manager.cpp new file mode 100644 index 000000000..6ef02aa11 --- /dev/null +++ b/colorcorrection/manager.cpp @@ -0,0 +1,776 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2017 Roman Gilg + +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 "manager.h" +#include "colorcorrectdbusinterface.h" +#include "suncalc.h" +#include "gammaramp.h" +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#ifdef Q_OS_LINUX +#include +#endif +#include +#include + +namespace KWin { +namespace ColorCorrect { + +static const int QUICK_ADJUST_DURATION = 2000; +static const int TEMPERATURE_STEP = 50; + +static bool checkLocation(double lat, double lng) +{ + return -90 <= lat && lat <= 90 && -180 <= lng && lng <= 180; +} + +Manager::Manager(QObject *parent) + : QObject(parent) +{ + m_iface = new ColorCorrectDBusInterface(this); + connect(kwinApp(), &Application::workspaceCreated, this, &Manager::init); +} + +void Manager::init() +{ + Settings::instance(kwinApp()->config()); + // we may always read in the current config + readConfig(); + + if (!kwinApp()->platform()->supportsGammaControl()) { + // at least update the sun timings to make the values accessible via dbus + updateSunTimings(true); + return; + } + + connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, + [this](bool active) { + if (active) { + hardReset(); + } else { + cancelAllTimers(); + } + } + ); + +#ifdef Q_OS_LINUX + // monitor for system clock changes - from the time dataengine + auto timeChangedFd = ::timerfd_create(CLOCK_REALTIME, O_CLOEXEC | O_NONBLOCK); + ::itimerspec timespec; + //set all timers to 0, which creates a timer that won't do anything + ::memset(×pec, 0, sizeof(timespec)); + + // Monitor for the time changing (flags == TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET). + // However these are not exposed in glibc so value is hardcoded: + ::timerfd_settime(timeChangedFd, 3, ×pec, 0); + + connect(this, &QObject::destroyed, [timeChangedFd]() { + ::close(timeChangedFd); + }); + + auto notifier = new QSocketNotifier(timeChangedFd, QSocketNotifier::Read, this); + connect(notifier, &QSocketNotifier::activated, this, [this](int fd) { + uint64_t c; + ::read(fd, &c, 8); + + // check if we're resuming from suspend - in this case do a hard reset + // Note: We're using the time clock to detect a suspend phase instead of connecting to the + // provided logind dbus signal, because this signal would be received way too late. + QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.DBus.Properties", + QStringLiteral("Get")); + message.setArguments(QVariantList({"org.freedesktop.login1.Manager", QStringLiteral("PreparingForSleep")})); + QDBusReply reply = QDBusConnection::systemBus().call(message); + bool comingFromSuspend; + if (reply.isValid()) { + comingFromSuspend = reply.value().toBool(); + } else { + qCDebug(KWIN_COLORCORRECTION) << "Failed to get PreparingForSleep Property of logind session:" << reply.error().message(); + // Always do a hard reset in case we have no further information. + comingFromSuspend = true; + } + + if (comingFromSuspend) { + hardReset(); + } else { + resetAllTimers(); + } + }); +#else + // TODO: Alternative method for BSD. +#endif + + hardReset(); +} + +void Manager::hardReset() +{ + cancelAllTimers(); + updateSunTimings(true); + if (kwinApp()->platform()->supportsGammaControl() && m_active) { + m_running = true; + commitGammaRamps(currentTargetTemp()); + } + resetAllTimers(); +} + +void Manager::reparseConfigAndReset() +{ + cancelAllTimers(); + readConfig(); + hardReset(); +} + +void Manager::readConfig() +{ + Settings *s = Settings::self(); + s->load(); + + m_active = s->active(); + + NightColorMode mode = s->mode(); + if (mode == NightColorMode::Location || mode == NightColorMode::Timings) { + m_mode = mode; + } else { + // also fallback for invalid setting values + m_mode = NightColorMode::Automatic; + } + + m_nightTargetTemp = qBound(MIN_TEMPERATURE, s->nightTemperature(), NEUTRAL_TEMPERATURE); + + double lat, lng; + auto correctReadin = [&lat, &lng]() { + if (!checkLocation(lat, lng)) { + // out of domain + lat = 0; + lng = 0; + } + }; + // automatic + lat = s->latitudeAuto(); + lng = s->longitudeAuto(); + correctReadin(); + m_latAuto = lat; + m_lngAuto = lng; + // fixed location + lat = s->latitudeFixed(); + lng = s->longitudeFixed(); + correctReadin(); + m_latFixed = lat; + m_lngFixed = lng; + + // fixed timings + QTime mrB = QTime::fromString(s->morningBeginFixed(), "hhmm"); + QTime evB = QTime::fromString(s->eveningBeginFixed(), "hhmm"); + + int diffME = mrB.msecsTo(evB); + if (diffME <= 0) { + // morning not strictly before evening - use defaults + mrB = QTime(6,0); + evB = QTime(18,0); + diffME = mrB.msecsTo(evB); + } + int diffMin = qMin(diffME, MSC_DAY - diffME); + + int trTime = s->transitionTime() * 1000 * 60; + if (trTime < 0 || diffMin <= trTime) { + // transition time too long - use defaults + mrB = QTime(6,0); + evB = QTime(18,0); + trTime = FALLBACK_SLOW_UPDATE_TIME; + } + m_morning = mrB; + m_evening = evB; + m_trTime = qMax(trTime / 1000 / 60, 1); +} + +void Manager::resetAllTimers() +{ + cancelAllTimers(); + if (kwinApp()->platform()->supportsGammaControl()) { + if (m_active) { + m_running = true; + } + // we do this also for active being false in order to reset the temperature back to the day value + resetQuickAdjustTimer(); + } else { + m_running = false; + } +} + +void Manager::cancelAllTimers() +{ + delete m_slowUpdateStartTimer; + delete m_slowUpdateTimer; + delete m_quickAdjustTimer; + + m_slowUpdateStartTimer = nullptr; + m_slowUpdateTimer = nullptr; + m_quickAdjustTimer = nullptr; +} + +void Manager::resetQuickAdjustTimer() +{ + updateSunTimings(false); + + int tempDiff = qAbs(currentTargetTemp() - m_currentTemp); + // allow tolerance of one TEMPERATURE_STEP to compensate if a slow update is coincidental + if (tempDiff > TEMPERATURE_STEP) { + cancelAllTimers(); + m_quickAdjustTimer = new QTimer(this); + m_quickAdjustTimer->setSingleShot(false); + connect(m_quickAdjustTimer, &QTimer::timeout, this, &Manager::quickAdjust); + + int interval = QUICK_ADJUST_DURATION / (tempDiff / TEMPERATURE_STEP); + if (interval == 0) { + interval = 1; + } + m_quickAdjustTimer->start(interval); + } else { + resetSlowUpdateStartTimer(); + } +} + +void Manager::quickAdjust() +{ + if (!m_quickAdjustTimer) { + return; + } + + int nextTemp; + int targetTemp = currentTargetTemp(); + + if (m_currentTemp < targetTemp) { + nextTemp = qMin(m_currentTemp + TEMPERATURE_STEP, targetTemp); + } else { + nextTemp = qMax(m_currentTemp - TEMPERATURE_STEP, targetTemp); + } + commitGammaRamps(nextTemp); + + if (nextTemp == targetTemp) { + // stop timer, we reached the target temp + delete m_quickAdjustTimer; + m_quickAdjustTimer = nullptr; + resetSlowUpdateStartTimer(); + } +} + +void Manager::resetSlowUpdateStartTimer() +{ + delete m_slowUpdateStartTimer; + m_slowUpdateStartTimer = nullptr; + + if (!m_running || m_quickAdjustTimer) { + // only reenable the slow update start timer when quick adjust is not active anymore + return; + } + + // set up the next slow update + m_slowUpdateStartTimer = new QTimer(this); + m_slowUpdateStartTimer->setSingleShot(true); + connect(m_slowUpdateStartTimer, &QTimer::timeout, this, &Manager::resetSlowUpdateStartTimer); + + updateSunTimings(false); + int diff; + if (m_mode == NightColorMode::Timings) { + // Timings mode is in local time + diff = QDateTime::currentDateTime().msecsTo(m_next.first); + } else { + diff = QDateTime::currentDateTimeUtc().msecsTo(m_next.first); + } + if (diff <= 0) { + qCCritical(KWIN_COLORCORRECTION) << "Error in time calculation. Deactivating Night Color."; + return; + } + m_slowUpdateStartTimer->start(diff); + + // start the current slow update + resetSlowUpdateTimer(); +} + +void Manager::resetSlowUpdateTimer() +{ + delete m_slowUpdateTimer; + m_slowUpdateTimer = nullptr; + + QDateTime now = QDateTime::currentDateTimeUtc(); + bool isDay = daylight(); + int targetTemp = isDay ? m_dayTargetTemp : m_nightTargetTemp; + + if (m_prev.first == m_prev.second) { + // transition time is zero + commitGammaRamps(isDay ? m_dayTargetTemp : m_nightTargetTemp); + return; + } + + if (m_prev.first <= now && now <= m_prev.second) { + int availTime = now.msecsTo(m_prev.second); + m_slowUpdateTimer = new QTimer(this); + m_slowUpdateTimer->setSingleShot(false); + if (isDay) { + connect(m_slowUpdateTimer, &QTimer::timeout, this, [this]() {slowUpdate(m_dayTargetTemp);}); + } else { + connect(m_slowUpdateTimer, &QTimer::timeout, this, [this]() {slowUpdate(m_nightTargetTemp);}); + } + + // calculate interval such as temperature is changed by TEMPERATURE_STEP K per timer timeout + int interval = availTime / (qAbs(targetTemp - m_currentTemp) / TEMPERATURE_STEP); + if (interval == 0) { + interval = 1; + } + m_slowUpdateTimer->start(interval); + } +} + +void Manager::slowUpdate(int targetTemp) +{ + if (!m_slowUpdateTimer) { + return; + } + int nextTemp; + if (m_currentTemp < targetTemp) { + nextTemp = qMin(m_currentTemp + TEMPERATURE_STEP, targetTemp); + } else { + nextTemp = qMax(m_currentTemp - TEMPERATURE_STEP, targetTemp); + } + commitGammaRamps(nextTemp); + if (nextTemp == targetTemp) { + // stop timer, we reached the target temp + delete m_slowUpdateTimer; + m_slowUpdateTimer = nullptr; + } +} + +void Manager::updateSunTimings(bool force) +{ + QDateTime todayNow = QDateTime::currentDateTimeUtc(); + + if (m_mode == NightColorMode::Timings) { + + QDateTime todayNowLocal = QDateTime::currentDateTime(); + + QDateTime morB = QDateTime(todayNowLocal.date(), m_morning); + QDateTime morE = morB.addSecs(m_trTime * 60); + QDateTime eveB = QDateTime(todayNowLocal.date(), m_evening); + QDateTime eveE = eveB.addSecs(m_trTime * 60); + + if (morB <= todayNowLocal && todayNowLocal < eveB) { + m_next = DateTimes(eveB, eveE); + m_prev = DateTimes(morB, morE); + } else if (todayNowLocal < morB) { + m_next = DateTimes(morB, morE); + m_prev = DateTimes(eveB.addDays(-1), eveE.addDays(-1)); + } else { + m_next = DateTimes(morB.addDays(1), morE.addDays(1)); + m_prev = DateTimes(eveB, eveE); + } + return; + } + + double lat, lng; + if (m_mode == NightColorMode::Automatic) { + lat = m_latAuto; + lng = m_lngAuto; + } else { + lat = m_latFixed; + lng = m_lngFixed; + } + + if (!force) { + // first try by only switching the timings + if (daylight()) { + // next is morning + m_prev = m_next; + m_next = getSunTimings(todayNow.date().addDays(1), lat, lng, true); + } else { + // next is evening + m_prev = m_next; + m_next = getSunTimings(todayNow.date(), lat, lng, false); + } + } + + if (force || !checkAutomaticSunTimings()) { + // in case this fails, reset them + DateTimes morning = getSunTimings(todayNow.date(), lat, lng, true); + if (todayNow < morning.first) { + m_prev = getSunTimings(todayNow.date().addDays(-1), lat, lng, false); + m_next = morning; + } else { + DateTimes evening = getSunTimings(todayNow.date(), lat, lng, false); + if (todayNow < evening.first) { + m_prev = morning; + m_next = evening; + } else { + m_prev = evening; + m_next = getSunTimings(todayNow.date().addDays(1), lat, lng, true); + } + } + } +} + +DateTimes Manager::getSunTimings(QDate date, double latitude, double longitude, bool morning) const +{ + Times times = calculateSunTimings(date, latitude, longitude, morning); + // At locations near the poles it is possible, that we can't + // calculate some or all sun timings (midnight sun). + // In this case try to fallback to sensible default values. + bool beginDefined = !times.first.isNull(); + bool endDefined = !times.second.isNull(); + if (!beginDefined || !endDefined) { + if (beginDefined) { + times.second = times.first.addMSecs( FALLBACK_SLOW_UPDATE_TIME ); + } else if (endDefined) { + times.first = times.second.addMSecs( - FALLBACK_SLOW_UPDATE_TIME); + } else { + // Just use default values for morning and evening, but the user + // will probably deactivate Night Color anyway if he is living + // in a region without clear sun rise and set. + times.first = morning ? QTime(6,0,0) : QTime(18,0,0); + times.second = times.first.addMSecs( FALLBACK_SLOW_UPDATE_TIME ); + } + } + return DateTimes(QDateTime(date, times.first, Qt::UTC), QDateTime(date, times.second, Qt::UTC)); +} + +bool Manager::checkAutomaticSunTimings() const +{ + if (m_prev.first.isValid() && m_prev.second.isValid() && + m_next.first.isValid() && m_next.second.isValid()) { + QDateTime todayNow = QDateTime::currentDateTimeUtc(); + return m_prev.first <= todayNow && todayNow < m_next.first && + m_prev.first.msecsTo(m_next.first) < MSC_DAY * 23./24; + } + return false; +} + +bool Manager::daylight() const +{ + return m_prev.first.date() == m_next.first.date(); +} + +int Manager::currentTargetTemp() const +{ + if (!m_active) { + return NEUTRAL_TEMPERATURE; + } + + QDateTime todayNow = QDateTime::currentDateTimeUtc(); + + auto f = [this, todayNow](int target1, int target2) { + if (todayNow <= m_prev.second) { + double residueQuota = todayNow.msecsTo(m_prev.second) / (double)m_prev.first.msecsTo(m_prev.second); + + double ret = (int)((1. - residueQuota) * (double)target2 + residueQuota * (double)target1); + // remove single digits + ret = ((int)(0.1 * ret)) * 10; + return (int)ret; + } else { + return target2; + } + }; + + if (daylight()) { + return f(m_nightTargetTemp, m_dayTargetTemp); + } else { + return f(m_dayTargetTemp, m_nightTargetTemp); + } +} + +void Manager::commitGammaRamps(int temperature) +{ + int nscreens = Screens::self()->count(); + + for (int screen = 0; screen < nscreens; screen++) { + int rampsize = kwinApp()->platform()->gammaRampSize(screen); + GammaRamp ramp(rampsize); + + /* + * The gamma calculation below is based on the Redshift app: + * https://github.com/jonls/redshift + */ + + // linear default state + for (int i = 0; i < rampsize; i++) { + uint16_t value = (double)i / rampsize * (UINT16_MAX + 1); + ramp.red[i] = value; + ramp.green[i] = value; + ramp.blue[i] = value; + } + + // approximate white point + float whitePoint[3]; + float alpha = (temperature % 100) / 100.; + int bbCIndex = ((temperature - 1000) / 100) * 3; + whitePoint[0] = (1. - alpha) * blackbodyColor[bbCIndex] + alpha * blackbodyColor[bbCIndex + 3]; + whitePoint[1] = (1. - alpha) * blackbodyColor[bbCIndex + 1] + alpha * blackbodyColor[bbCIndex + 4]; + whitePoint[2] = (1. - alpha) * blackbodyColor[bbCIndex + 2] + alpha * blackbodyColor[bbCIndex + 5]; + + for (int i = 0; i < rampsize; i++) { + ramp.red[i] = (double)ramp.red[i] / (UINT16_MAX+1) * whitePoint[0] * (UINT16_MAX+1); + ramp.green[i] = (double)ramp.green[i] / (UINT16_MAX+1) * whitePoint[1] * (UINT16_MAX+1); + ramp.blue[i] = (double)ramp.blue[i] / (UINT16_MAX+1) * whitePoint[2] * (UINT16_MAX+1); + } + + if (kwinApp()->platform()->setGammaRamp(screen, ramp)) { + m_currentTemp = temperature; + m_failedCommitAttempts = 0; + } else { + m_failedCommitAttempts++; + if (m_failedCommitAttempts < 10) { + qCWarning(KWIN_COLORCORRECTION).nospace() << "Committing Gamma Ramp failed for screen " << screen << + ". Trying " << (10 - m_failedCommitAttempts) << " times more."; + } else { + // TODO: On multi monitor setups we could try to rollback earlier changes for already commited outputs + qCWarning(KWIN_COLORCORRECTION) << "Gamma Ramp commit failed too often. Deactivating color correction for now."; + m_failedCommitAttempts = 0; // reset so we can try again later (i.e. after suspend phase or config change) + m_running = false; + cancelAllTimers(); + } + } + } +} + +QHash Manager::info() const +{ + return QHash { + { QStringLiteral("Available"), kwinApp()->platform()->supportsGammaControl() }, + + { QStringLiteral("ActiveEnabled"), true}, + { QStringLiteral("Active"), m_active}, + + { QStringLiteral("ModeEnabled"), true}, + { QStringLiteral("Mode"), (int)m_mode}, + + { QStringLiteral("NightTemperatureEnabled"), true}, + { QStringLiteral("NightTemperature"), m_nightTargetTemp}, + + { QStringLiteral("Running"), m_running}, + { QStringLiteral("CurrentColorTemperature"), m_currentTemp}, + + { QStringLiteral("LatitudeAuto"), m_latAuto}, + { QStringLiteral("LongitudeAuto"), m_lngAuto}, + + { QStringLiteral("LocationEnabled"), true}, + { QStringLiteral("LatitudeFixed"), m_latFixed}, + { QStringLiteral("LongitudeFixed"), m_lngFixed}, + + { QStringLiteral("TimingsEnabled"), true}, + { QStringLiteral("MorningBeginFixed"), m_morning.toString(Qt::ISODate)}, + { QStringLiteral("EveningBeginFixed"), m_evening.toString(Qt::ISODate)}, + { QStringLiteral("TransitionTime"), m_trTime}, + }; +} + +bool Manager::changeConfiguration(QHash data) +{ + bool activeUpdate, modeUpdate, tempUpdate, locUpdate, timeUpdate; + activeUpdate = modeUpdate = tempUpdate = locUpdate = timeUpdate = false; + + bool active = m_active; + NightColorMode mode = m_mode; + int nightT = m_nightTargetTemp; + + double lat = m_latFixed; + double lng = m_lngFixed; + + QTime mor = m_morning; + QTime eve = m_evening; + int trT = m_trTime; + + QHash::const_iterator iter1, iter2, iter3; + + iter1 = data.constFind("Active"); + if (iter1 != data.constEnd()) { + if (!iter1.value().canConvert()) { + return false; + } + bool act = iter1.value().toBool(); + activeUpdate = m_active != act; + active = act; + } + + iter1 = data.constFind("Mode"); + if (iter1 != data.constEnd()) { + if (!iter1.value().canConvert()) { + return false; + } + int mo = iter1.value().toInt(); + if (mo < 0 || 2 < mo) { + return false; + } + NightColorMode moM; + switch (mo) { + case 0: + moM = NightColorMode::Automatic; + break; + case 1: + moM = NightColorMode::Location; + break; + case 2: + moM = NightColorMode::Timings; + } + modeUpdate = m_mode != moM; + mode = moM; + } + + iter1 = data.constFind("NightTemperature"); + if (iter1 != data.constEnd()) { + if (!iter1.value().canConvert()) { + return false; + } + int nT = iter1.value().toInt(); + if (nT < MIN_TEMPERATURE || NEUTRAL_TEMPERATURE < nT) { + return false; + } + tempUpdate = m_nightTargetTemp != nT; + nightT = nT; + } + + iter1 = data.constFind("LatitudeFixed"); + iter2 = data.constFind("LongitudeFixed"); + if (iter1 != data.constEnd() && iter2 != data.constEnd()) { + if (!iter1.value().canConvert() || !iter2.value().canConvert()) { + return false; + } + double la = iter1.value().toDouble(); + double ln = iter2.value().toDouble(); + if (!checkLocation(la, ln)) { + return false; + } + locUpdate = m_latFixed != la || m_lngFixed != ln; + lat = la; + lng = ln; + } + + iter1 = data.constFind("MorningBeginFixed"); + iter2 = data.constFind("EveningBeginFixed"); + iter3 = data.constFind("TransitionTime"); + if (iter1 != data.constEnd() && iter2 != data.constEnd() && iter3 != data.constEnd()) { + if (!iter1.value().canConvert() || !iter2.value().canConvert() || !iter3.value().canConvert()) { + return false; + } + QTime mo = QTime::fromString(iter1.value().toString(), Qt::ISODate); + QTime ev = QTime::fromString(iter2.value().toString(), Qt::ISODate); + if (!mo.isValid() || !ev.isValid()) { + return false; + } + int tT = iter3.value().toInt(); + + int diffME = mo.msecsTo(ev); + if (diffME <= 0 || qMin(diffME, MSC_DAY - diffME) <= tT * 60 * 1000 || tT < 1) { + // morning not strictly before evening, transition time too long or transition time out of bounds + return false; + } + + timeUpdate = m_morning != mo || m_evening != ev || m_trTime != tT; + mor = mo; + eve = ev; + trT = tT; + } + + if (!(activeUpdate || modeUpdate || tempUpdate || locUpdate || timeUpdate)) { + return true; + } + + bool resetNeeded = activeUpdate || modeUpdate || tempUpdate || + (locUpdate && mode == NightColorMode::Location) || + (timeUpdate && mode == NightColorMode::Timings); + + if (resetNeeded) { + cancelAllTimers(); + } + + Settings *s = Settings::self(); + if (activeUpdate) { + m_active = active; + s->setActive(active); + } + + if (modeUpdate) { + m_mode = mode; + s->setMode(mode); + } + + if (tempUpdate) { + m_nightTargetTemp = nightT; + s->setNightTemperature(nightT); + } + + if (locUpdate) { + m_latFixed = lat; + m_lngFixed = lng; + s->setLatitudeFixed(lat); + s->setLongitudeFixed(lng); + } + + if (timeUpdate) { + m_morning = mor; + m_evening = eve; + m_trTime = trT; + s->setMorningBeginFixed(mor.toString("hhmm")); + s->setEveningBeginFixed(eve.toString("hhmm")); + s->setTransitionTime(trT); + } + s->save(); + + if (resetNeeded) { + resetAllTimers(); + } + emit configChange(info()); + return true; +} + +void Manager::autoLocationUpdate(double latitude, double longitude) +{ + if (!checkLocation(latitude, longitude)) { + return; + } + + // we tolerate small deviations with minimal impact on sun timings + if (qAbs(m_latAuto - latitude) < 2 && qAbs(m_lngAuto - longitude) < 1) { + return; + } + cancelAllTimers(); + m_latAuto = latitude; + m_lngAuto = longitude; + + Settings *s = Settings::self(); + s->setLatitudeAuto(latitude); + s->setLongitudeAuto(longitude); + s->save(); + + resetAllTimers(); + emit configChange(info()); +} + +} +} diff --git a/colorcorrection/manager.h b/colorcorrection/manager.h new file mode 100644 index 000000000..ae103aee7 --- /dev/null +++ b/colorcorrection/manager.h @@ -0,0 +1,147 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_COLORCORRECT_MANAGER_H +#define KWIN_COLORCORRECT_MANAGER_H + +#include "constants.h" +#include + +#include +#include +#include + +class QTimer; + +namespace KWin +{ + +class Platform; + +namespace ColorCorrect +{ + +typedef QPair DateTimes; +typedef QPair Times; + +class ColorCorrectDBusInterface; + + +enum NightColorMode { + // timings are based on provided location data + Automatic = 0, + // timings are based on fixed location data + Location, + // fixed timings + Timings +}; + +class KWIN_EXPORT Manager : public QObject +{ + Q_OBJECT + +public: + Manager(QObject *parent); + void init(); + + /** + * Get current configuration + * @see changeConfiguration + * @since 5.12 + **/ + QHash info() const; + /** + * Change configuration + * @see info + * @since 5.12 + **/ + bool changeConfiguration(QHash data); + void autoLocationUpdate(double latitude, double longitude); + + // for auto tests + void reparseConfigAndReset(); + +public Q_SLOTS: + void resetSlowUpdateStartTimer(); + void quickAdjust(); + +Q_SIGNALS: + void configChange(QHash data); + +private: + void readConfig(); + void hardReset(); + void slowUpdate(int targetTemp); + void resetAllTimers(); + int currentTargetTemp() const; + void cancelAllTimers(); + /** + * Quick shift on manual change to current target Temperature + **/ + void resetQuickAdjustTimer(); + /** + * Slow shift to daytime target Temperature + **/ + void resetSlowUpdateTimer(); + + void updateSunTimings(bool force); + DateTimes getSunTimings(QDate date, double latitude, double longitude, bool morning) const; + bool checkAutomaticSunTimings() const; + bool daylight() const; + + void commitGammaRamps(int temperature); + + ColorCorrectDBusInterface *m_iface; + + bool m_active; + bool m_running = false; + + NightColorMode m_mode = NightColorMode::Automatic; + + // the previous and next sunrise/sunset intervals - in UTC time + DateTimes m_prev = DateTimes(); + DateTimes m_next = DateTimes(); + + // manual times from config + QTime m_morning = QTime(6,0); + QTime m_evening = QTime(18,0); + int m_trTime = 30; // saved in minutes > 1 + + // auto location provided by work space + double m_latAuto; + double m_lngAuto; + // manual location from config + double m_latFixed; + double m_lngFixed; + + QTimer *m_slowUpdateStartTimer = nullptr; + QTimer *m_slowUpdateTimer = nullptr; + QTimer *m_quickAdjustTimer = nullptr; + + int m_currentTemp = NEUTRAL_TEMPERATURE; + int m_dayTargetTemp = NEUTRAL_TEMPERATURE; + int m_nightTargetTemp = DEFAULT_NIGHT_TEMPERATURE; + + int m_failedCommitAttempts = 0; +}; + +} +} + +#endif // KWIN_COLORCORRECT_MANAGER_H diff --git a/colorcorrection/suncalc.cpp b/colorcorrection/suncalc.cpp new file mode 100644 index 000000000..71bb2e517 --- /dev/null +++ b/colorcorrection/suncalc.cpp @@ -0,0 +1,163 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2017 Roman Gilg + +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 "suncalc.h" +#include "constants.h" + +#include +#include + +namespace KWin { +namespace ColorCorrect { + +#define TWILIGHT_NAUT -12.0 +#define TWILIGHT_CIVIL -6.0 +#define SUN_RISE_SET -0.833 +#define SUN_HIGH 2.0 + +QPair calculateSunTimings(QDate prompt, double latitude, double longitude, bool morning) +{ + // calculations based on http://aa.quae.nl/en/reken/zonpositie.html + // accuracy: +/- 5min + + // positioning + const double rad = M_PI / 180.; + const double earthObliquity = 23.4397; // epsilon + + const double lat = latitude; // phi + const double lng = -longitude; // lw + + // times + const double juPrompt = prompt.toJulianDay(); // J + const double ju2000 = 2451545.; // J2000 + + // geometry + auto mod360 = [](double number) -> double { + return std::fmod(number, 360.); + }; + + auto sin = [&rad](double angle) -> double { + return std::sin(angle * rad); + }; + auto cos = [&rad](double angle) -> double { + return std::cos(angle * rad); + }; + auto asin = [&rad](double val) -> double { + return std::asin(val) / rad; + }; + auto acos = [&rad](double val) -> double { + return std::acos(val) / rad; + }; + + auto anomaly = [&](const double date) -> double { // M + return mod360(357.5291 + 0.98560028 * (date - ju2000)); + }; + + auto center = [&sin](double anomaly) -> double { // C + return 1.9148 * sin(anomaly) + 0.02 * sin(2 * anomaly) + 0.0003 * sin(3 * anomaly); + }; + + auto ecliptLngMean = [](double anom) -> double { // Mean ecliptical longitude L_sun = Mean Anomaly + Perihelion + 180° + return anom + 282.9372; // anom + 102.9372 + 180° + }; + + auto ecliptLng = [&](double anom) -> double { // lambda = L_sun + C + return ecliptLngMean(anom) + center(anom); + }; + + auto declination = [&](const double date) -> double { // delta + const double anom = anomaly(date); + const double eclLng = ecliptLng(anom); + + return mod360(asin(sin(earthObliquity) * sin(eclLng))); + }; + + // sun hour angle at specific angle + auto hourAngle = [&](const double date, double angle) -> double { // H_t + const double decl = declination(date); + const double ret0 = (sin(angle) - sin(lat) * sin(decl)) / (cos(lat) * cos(decl)); + + double ret = mod360(acos( ret0 )); + if (180. < ret) { + ret = ret - 360.; + } + return ret; + }; + + /* + * Sun positions + */ + + // transit is at noon + auto getTransit = [&](const double date) -> double { // Jtransit + const double juMeanSolTime = juPrompt - ju2000 - 0.0009 - lng / 360.; // n_x = J - J_2000 - J_0 - l_w / 360° + const double juTrEstimate = date + qRound64(juMeanSolTime) - juMeanSolTime; // J_x = J + n - n_x + const double anom = anomaly(juTrEstimate); // M + const double eclLngM = ecliptLngMean(anom); // L_sun + + return juTrEstimate + 0.0053 * sin(anom) - 0.0068 * sin(2 * eclLngM); + }; + + auto getSunMorning = [&hourAngle](const double angle, const double transit) -> double { + return transit - hourAngle(transit, angle) / 360.; + }; + + auto getSunEvening = [&hourAngle](const double angle, const double transit) -> double { + return transit + hourAngle(transit, angle) / 360.; + }; + + /* + * Begin calculations + */ + + // noon - sun at the highest point + const double juNoon = getTransit(juPrompt); + + double begin, end; + if (morning) { + begin = getSunMorning(TWILIGHT_CIVIL, juNoon); + end = getSunMorning(SUN_HIGH, juNoon); + } else { + begin = getSunEvening(SUN_HIGH, juNoon); + end = getSunEvening(TWILIGHT_CIVIL, juNoon); + } + // transform to QDateTime + begin += 0.5; + end += 0.5; + + QTime timeBegin, timeEnd; + + if (std::isnan(begin)) { + timeBegin = QTime(); + } else { + double timePart = begin - (int)begin; + timeBegin = QTime::fromMSecsSinceStartOfDay((int)( timePart * MSC_DAY )); + } + if (std::isnan(end)) { + timeEnd = QTime(); + } else { + double timePart = end - (int)end; + timeEnd = QTime::fromMSecsSinceStartOfDay((int)( timePart * MSC_DAY )); + } + + return QPair (timeBegin, timeEnd); +} + +} +} diff --git a/plugins/platforms/drm/drm_object_connector.h b/colorcorrection/suncalc.h similarity index 60% copy from plugins/platforms/drm/drm_object_connector.h copy to colorcorrection/suncalc.h index 0f6fcdbd4..4cfcea055 100644 --- a/plugins/platforms/drm/drm_object_connector.h +++ b/colorcorrection/suncalc.h @@ -1,57 +1,47 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2016 Roman Gilg +Copyright 2017 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ -#ifndef KWIN_DRM_OBJECT_CONNECTOR_H -#define KWIN_DRM_OBJECT_CONNECTOR_H +#ifndef KWIN_SUNCALCULATOR_H +#define KWIN_SUNCALCULATOR_H -#include "drm_object.h" +#include +#include +#include namespace KWin { -class DrmConnector : public DrmObject +namespace ColorCorrect { -public: - DrmConnector(uint32_t connector_id, DrmBackend *backend); - virtual ~DrmConnector(); +/** + * Calculates for a given location and date two of the + * following sun timings in their temporal order: + * - Nautical dawn and sunrise for the morning + * - Sunset and nautical dusk for the evening + * @since 5.12 + **/ - bool atomicInit(); +QPair calculateSunTimings(QDate prompt, double latitude, double longitude, bool morning); - enum class PropertyIndex { - CrtcId = 0, - Count - }; - - QVector encoders() { - return m_encoders; - } - - bool initProps(); - bool isConnected(); - - -private: - QVector m_encoders; -}; +} } -#endif - +#endif // KWIN_SUNCALCULATOR_H diff --git a/composite.cpp b/composite.cpp index 4d71bb5a5..9cb2b389e 100644 --- a/composite.cpp +++ b/composite.cpp @@ -1,1201 +1,1208 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "composite.h" #include "dbusinterface.h" #include "utils.h" #include #include "workspace.h" #include "client.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "scene.h" #include "screens.h" #include "shadow.h" #include "useractions.h" #include "xcbutils.h" #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::Compositor::SuspendReason) namespace KWin { extern int currentRefreshRate(); CompositorSelectionOwner::CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection, connection(), rootWindow()), owning(false) { connect (this, SIGNAL(lostOwnership()), SLOT(looseOwnership())); } void CompositorSelectionOwner::looseOwnership() { owning = false; } KWIN_SINGLETON_FACTORY_VARIABLE(Compositor, s_compositor) static inline qint64 milliToNano(int milli) { return qint64(milli) * 1000 * 1000; } static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); } Compositor::Compositor(QObject* workspace) : QObject(workspace) , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend) , cm_selection(NULL) , vBlankInterval(0) , fpsInterval(0) , m_xrrRefreshRate(0) , m_finishing(false) , m_starting(false) , m_timeSinceLastVBlank(0) , m_scene(NULL) , m_bufferSwapPending(false) , m_composeAtSwapCompletion(false) { qRegisterMetaType("Compositor::SuspendReason"); connect(&compositeResetTimer, SIGNAL(timeout()), SLOT(restart())); connect(options, &Options::configChanged, this, &Compositor::slotConfigChanged); compositeResetTimer.setSingleShot(true); nextPaintReference.invalidate(); // Initialize the timer // 2 sec which should be enough to restart the compositor static const int compositorLostMessageDelay = 2000; m_releaseSelectionTimer.setSingleShot(true); m_releaseSelectionTimer.setInterval(compositorLostMessageDelay); connect(&m_releaseSelectionTimer, SIGNAL(timeout()), SLOT(releaseCompositorSelection())); m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay); m_unusedSupportPropertyTimer.setSingleShot(true); connect(&m_unusedSupportPropertyTimer, SIGNAL(timeout()), SLOT(deleteUnusedSupportProperties())); // delay the call to setup by one event cycle // The ctor of this class is invoked from the Workspace ctor, that means before // Workspace is completely constructed, so calling Workspace::self() would result // in undefined behavior. This is fixed by using a delayed invocation. if (kwinApp()->platform()->isReady()) { QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection); } connect(kwinApp()->platform(), &Platform::readyChanged, this, [this] (bool ready) { if (ready) { setup(); } else { finish(); } }, Qt::QueuedConnection ); connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, [this] { delete cm_selection; cm_selection = nullptr; } ); if (qEnvironmentVariableIsSet("KWIN_MAX_FRAMES_TESTED")) m_framesToTestForSafety = qEnvironmentVariableIntValue("KWIN_MAX_FRAMES_TESTED"); // register DBus new CompositorDBusInterface(this); } Compositor::~Compositor() { emit aboutToDestroy(); finish(); deleteUnusedSupportProperties(); delete cm_selection; s_compositor = NULL; } void Compositor::setup() { if (hasScene()) return; if (m_suspended) { QStringList reasons; if (m_suspended & UserSuspend) { reasons << QStringLiteral("Disabled by User"); } if (m_suspended & BlockRuleSuspend) { reasons << QStringLiteral("Disabled by Window"); } if (m_suspended & ScriptSuspend) { reasons << QStringLiteral("Disabled by Script"); } qCDebug(KWIN_CORE) << "Compositing is suspended, reason:" << reasons; return; } else if (!kwinApp()->platform()->compositingPossible()) { qCCritical(KWIN_CORE) << "Compositing is not possible"; return; } m_starting = true; if (!options->isCompositingInitialized()) { options->reloadCompositingSettings(true); slotCompositingOptionsInitialized(); } else { slotCompositingOptionsInitialized(); } } extern int screen_number; // main.cpp extern bool is_multihead; void Compositor::slotCompositingOptionsInitialized() { setupX11Support(); // There might still be a deleted around, needs to be cleared before creating the scene (BUG 333275) if (Workspace::self()) { while (!Workspace::self()->deletedList().isEmpty()) { Workspace::self()->deletedList().first()->discard(); } } auto supportedCompositors = kwinApp()->platform()->supportedCompositors(); const auto userConfigIt = std::find(supportedCompositors.begin(), supportedCompositors.end(), options->compositingMode()); if (userConfigIt != supportedCompositors.end()) { supportedCompositors.erase(userConfigIt); supportedCompositors.prepend(options->compositingMode()); } else { qCWarning(KWIN_CORE) << "Configured compositor not supported by Platform. Falling back to defaults"; } const auto availablePlugins = KPluginLoader::findPlugins(QStringLiteral("org.kde.kwin.scenes")); for (auto type : qAsConst(supportedCompositors)) { const auto pluginIt = std::find_if(availablePlugins.begin(), availablePlugins.end(), [type] (const auto &plugin) { const auto &metaData = plugin.rawData(); auto it = metaData.find(QStringLiteral("CompositingType")); if (it != metaData.end()) { if ((*it).toInt() == int{type}) { return true; } } return false; }); if (pluginIt != availablePlugins.end()) { std::unique_ptr factory{qobject_cast(pluginIt->instantiate())}; if (factory) { m_scene = factory->create(this); if (m_scene) { if (!m_scene->initFailed()) { qCDebug(KWIN_CORE) << "Instantiated compositing plugin:" << pluginIt->name(); break; } else { delete m_scene; m_scene = nullptr; } } } } } if (m_scene == NULL || m_scene->initFailed()) { qCCritical(KWIN_CORE) << "Failed to initialize compositing, compositing disabled"; delete m_scene; m_scene = NULL; m_starting = false; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } if (!supportedCompositors.contains(NoCompositing)) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return; } + + if (!Workspace::self() && m_scene && m_scene->compositingType() == QPainterCompositing) { + // Force Software QtQuick on first startup with QPainter + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); + } + connect(m_scene, &Scene::resetCompositing, this, &Compositor::restart); emit sceneCreated(); if (Workspace::self()) { startupWithWorkspace(); } else { connect(kwinApp(), &Application::workspaceCreated, this, &Compositor::startupWithWorkspace); } } void Compositor::claimCompositorSelection() { if (!cm_selection) { char selection_name[ 100 ]; sprintf(selection_name, "_NET_WM_CM_S%d", Application::x11ScreenNumber()); cm_selection = new CompositorSelectionOwner(selection_name); connect(cm_selection, SIGNAL(lostOwnership()), SLOT(finish())); } if (!cm_selection) // no X11 yet return; if (!cm_selection->owning) { cm_selection->claim(true); // force claiming cm_selection->owning = true; } } void Compositor::setupX11Support() { auto c = kwinApp()->x11Connection(); if (!c) { delete cm_selection; cm_selection = nullptr; return; } claimCompositorSelection(); xcb_composite_redirect_subwindows(c, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } void Compositor::startupWithWorkspace() { if (!m_starting) { return; } connect(kwinApp(), &Application::x11ConnectionChanged, this, &Compositor::setupX11Support, Qt::UniqueConnection); Workspace::self()->markXStackingOrderAsDirty(); Q_ASSERT(m_scene); connect(workspace(), &Workspace::destroyed, this, [this] { compositeTimer.stop(); }); setupX11Support(); m_xrrRefreshRate = KWin::currentRefreshRate(); fpsInterval = options->maxFpsInterval(); if (m_scene->syncsToVBlank()) { // if we do vsync, set the fps to the next multiple of the vblank rate vBlankInterval = milliToNano(1000) / m_xrrRefreshRate; fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); } else vBlankInterval = milliToNano(1); // no sync - DO NOT set "0", would cause div-by-zero segfaults. m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" - we don't have even a slight idea when the first vsync will occur scheduleRepaint(); kwinApp()->platform()->createEffectsHandler(this, m_scene); // sets also the 'effects' pointer connect(Workspace::self(), &Workspace::deletedRemoved, m_scene, &Scene::windowDeleted); connect(effects, SIGNAL(screenGeometryChanged(QSize)), SLOT(addRepaintFull())); addRepaintFull(); foreach (Client * c, Workspace::self()->clientList()) { c->setupCompositing(); c->getShadow(); } foreach (Client * c, Workspace::self()->desktopList()) c->setupCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) { c->setupCompositing(); c->getShadow(); } if (auto w = waylandServer()) { const auto clients = w->clients(); for (auto c : clients) { c->setupCompositing(); c->getShadow(); } const auto internalClients = w->internalClients(); for (auto c : internalClients) { c->setupCompositing(); c->getShadow(); } } emit compositingToggled(true); m_starting = false; if (m_releaseSelectionTimer.isActive()) { m_releaseSelectionTimer.stop(); } // render at least once performCompositing(); } void Compositor::scheduleRepaint() { if (!compositeTimer.isActive()) setCompositeTimer(); } void Compositor::finish() { if (!hasScene()) return; m_finishing = true; m_releaseSelectionTimer.start(); if (Workspace::self()) { foreach (Client * c, Workspace::self()->clientList()) m_scene->windowClosed(c, NULL); foreach (Client * c, Workspace::self()->desktopList()) m_scene->windowClosed(c, NULL); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) m_scene->windowClosed(c, NULL); foreach (Deleted * c, Workspace::self()->deletedList()) m_scene->windowDeleted(c); foreach (Client * c, Workspace::self()->clientList()) c->finishCompositing(); foreach (Client * c, Workspace::self()->desktopList()) c->finishCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) c->finishCompositing(); foreach (Deleted * c, Workspace::self()->deletedList()) c->finishCompositing(); if (auto c = kwinApp()->x11Connection()) { xcb_composite_unredirect_subwindows(c, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } } if (waylandServer()) { foreach (ShellClient *c, waylandServer()->clients()) { m_scene->windowClosed(c, nullptr); } foreach (ShellClient *c, waylandServer()->internalClients()) { m_scene->windowClosed(c, nullptr); } foreach (ShellClient *c, waylandServer()->clients()) { c->finishCompositing(); } foreach (ShellClient *c, waylandServer()->internalClients()) { c->finishCompositing(); } } delete effects; effects = NULL; delete m_scene; m_scene = NULL; compositeTimer.stop(); repaints_region = QRegion(); if (Workspace::self()) { for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { // forward all opacity values to the frame in case there'll be other CM running if ((*it)->opacity() != 1.0) { NETWinInfo i(connection(), (*it)->frameId(), rootWindow(), 0, 0); i.setOpacity(static_cast< unsigned long >((*it)->opacity() * 0xffffffff)); } } // discard all Deleted windows (#152914) while (!Workspace::self()->deletedList().isEmpty()) Workspace::self()->deletedList().first()->discard(); } m_finishing = false; emit compositingToggled(false); } void Compositor::releaseCompositorSelection() { if (hasScene() && !m_finishing) { // compositor is up and running again, no need to release the selection return; } if (m_starting) { // currently still starting the compositor, it might fail, so restart the timer to test again m_releaseSelectionTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow, so restart the timer to test again m_releaseSelectionTimer.start(); return; } qCDebug(KWIN_CORE) << "Releasing compositor selection"; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } } void Compositor::keepSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties.removeAll(atom); } void Compositor::removeSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties << atom; m_unusedSupportPropertyTimer.start(); } void Compositor::deleteUnusedSupportProperties() { if (m_starting) { // currently still starting the compositor m_unusedSupportPropertyTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow m_unusedSupportPropertyTimer.start(); return; } if (const auto c = kwinApp()->x11Connection()) { foreach (const xcb_atom_t &atom, m_unusedSupportProperties) { // remove property from root window xcb_delete_property(c, kwinApp()->x11RootWindow(), atom); } } } void Compositor::slotConfigChanged() { if (!m_suspended) { setup(); if (effects) // setupCompositing() may fail effects->reconfigure(); addRepaintFull(); } else finish(); } void Compositor::slotReinitialize() { // Reparse config. Config options will be reloaded by setup() kwinApp()->config()->reparseConfiguration(); // Restart compositing finish(); // resume compositing if suspended m_suspended = NoReasonSuspend; options->setCompositingInitialized(false); setup(); if (effects) { // setup() may fail effects->reconfigure(); } } // for the shortcut void Compositor::slotToggleCompositing() { if (kwinApp()->platform()->requiresCompositing()) { // we are not allowed to turn on/off compositing return; } if (m_suspended) { // direct user call; clear all bits resume(AllReasonSuspend); } else { // but only set the user one (sufficient to suspend) suspend(UserSuspend); } } void Compositor::updateCompositeBlocking() { updateCompositeBlocking(NULL); } void Compositor::updateCompositeBlocking(Client *c) { if (kwinApp()->platform()->requiresCompositing()) { return; } if (c) { // if c == 0 we just check if we can resume if (c->isBlockingCompositing()) { if (!(m_suspended & BlockRuleSuspend)) // do NOT attempt to call suspend(true); from within the eventchain! QMetaObject::invokeMethod(this, "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } else if (m_suspended & BlockRuleSuspend) { // lost a client and we're blocked - can we resume? bool resume = true; for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { if ((*it)->isBlockingCompositing()) { resume = false; break; } } if (resume) { // do NOT attempt to call suspend(false); from within the eventchain! QMetaObject::invokeMethod(this, "resume", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } } void Compositor::suspend(Compositor::SuspendReason reason) { if (kwinApp()->platform()->requiresCompositing()) { return; } Q_ASSERT(reason != NoReasonSuspend); m_suspended |= reason; if (reason & KWin::Compositor::ScriptSuspend) { // when disabled show a shortcut how the user can get back compositing const auto shortcuts = KGlobalAccel::self()->shortcut(workspace()->findChild(QStringLiteral("Suspend Compositing"))); if (!shortcuts.isEmpty()) { // display notification only if there is the shortcut const QString message = i18n("Desktop effects have been suspended by another application.
" "You can resume using the '%1' shortcut.", shortcuts.first().toString(QKeySequence::NativeText)); KNotification::event(QStringLiteral("compositingsuspendeddbus"), message); } } finish(); } void Compositor::resume(Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended &= ~reason; setup(); // signal "toggled" is eventually emitted from within setup } void Compositor::restart() { if (hasScene()) { finish(); QTimer::singleShot(0, this, SLOT(setup())); } } void Compositor::addRepaint(int x, int y, int w, int h) { if (!hasScene()) return; repaints_region += QRegion(x, y, w, h); scheduleRepaint(); } void Compositor::addRepaint(const QRect& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaint(const QRegion& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaintFull() { if (!hasScene()) return; const QSize &s = screens()->size(); repaints_region = QRegion(0, 0, s.width(), s.height()); scheduleRepaint(); } void Compositor::timerEvent(QTimerEvent *te) { if (te->timerId() == compositeTimer.timerId()) { performCompositing(); } else QObject::timerEvent(te); } void Compositor::aboutToSwapBuffers() { assert(!m_bufferSwapPending); m_bufferSwapPending = true; } void Compositor::bufferSwapComplete() { assert(m_bufferSwapPending); m_bufferSwapPending = false; if (m_composeAtSwapCompletion) { m_composeAtSwapCompletion = false; performCompositing(); } } void Compositor::performCompositing() { if (m_scene->usesOverlayWindow() && !isOverlayWindowVisible()) return; // nothing is visible anyway // If a buffer swap is still pending, we return to the event loop and // continue processing events until the swap has completed. if (m_bufferSwapPending) { m_composeAtSwapCompletion = true; compositeTimer.stop(); return; } // If outputs are disabled, we return to the event loop and // continue processing events until the outputs are enabled again if (!kwinApp()->platform()->areOutputsEnabled()) { compositeTimer.stop(); return; } // Create a list of all windows in the stacking order ToplevelList windows = Workspace::self()->xStackingOrder(); ToplevelList damaged; // Reset the damage state of each window and fetch the damage region // without waiting for a reply foreach (Toplevel *win, windows) { if (win->resetAndFetchDamage()) damaged << win; } if (damaged.count() > 0) { m_scene->triggerFence(); if (auto c = kwinApp()->x11Connection()) { xcb_flush(c); } } // Move elevated windows to the top of the stacking order foreach (EffectWindow *c, static_cast(effects)->elevatedWindows()) { Toplevel* t = static_cast< EffectWindowImpl* >(c)->window(); windows.removeAll(t); windows.append(t); } // Get the replies foreach (Toplevel *win, damaged) { // Discard the cached lanczos texture if (win->effectWindow()) { const QVariant texture = win->effectWindow()->data(LanczosCacheRole); if (texture.isValid()) { delete static_cast(texture.value()); win->effectWindow()->setData(LanczosCacheRole, QVariant()); } } win->getDamageRegionReply(); } if (repaints_region.isEmpty() && !windowRepaintsPending()) { m_scene->idle(); m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" m_timeSinceStart += m_timeSinceLastVBlank; // Note: It would seem here we should undo suspended unredirect, but when scenes need // it for some reason, e.g. transformations or translucency, the next pass that does not // need this anymore and paints normally will also reset the suspended unredirect. // Otherwise the window would not be painted normally anyway. compositeTimer.stop(); return; } // skip windows that are not yet ready for being painted and if screen is locked skip windows that are // neither lockscreen nor inputmethod windows // TODO ? // this cannot be used so carelessly - needs protections against broken clients, the window // should not get focus before it's displayed, handle unredirected windows properly and so on. foreach (Toplevel *t, windows) { if (!t->readyForPainting()) { windows.removeAll(t); } if (waylandServer() && waylandServer()->isScreenLocked()) { if(!t->isLockScreen() && !t->isInputMethod()) { windows.removeAll(t); } } } QRegion repaints = repaints_region; // clear all repaints, so that post-pass can add repaints for the next repaint repaints_region = QRegion(); if (m_framesToTestForSafety > 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreFrame); } m_timeSinceLastVBlank = m_scene->paint(repaints, windows); if (m_framesToTestForSafety > 0) { if (m_scene->compositingType() & OpenGLCompositing) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostFrame); } m_framesToTestForSafety--; if (m_framesToTestForSafety == 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostLastGuardedFrame); } } m_timeSinceStart += m_timeSinceLastVBlank; if (waylandServer()) { for (Toplevel *win : damaged) { if (auto surface = win->surface()) { surface->frameRendered(m_timeSinceStart); } } } compositeTimer.stop(); // stop here to ensure *we* cause the next repaint schedule - not some effect through m_scene->paint() // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() // is called the next time. If there would be nothing pending, it will not restart the timer and // scheduleRepaint() would restart it again somewhen later, called from functions that // would again add something pending. if (m_bufferSwapPending && m_scene->syncsToVBlank()) { m_composeAtSwapCompletion = true; } else { scheduleRepaint(); } } template static bool repaintsPending(const QList &windows) { return std::any_of(windows.begin(), windows.end(), [] (T *t) { return !t->repaints().isEmpty(); }); } bool Compositor::windowRepaintsPending() const { if (repaintsPending(Workspace::self()->clientList())) { return true; } if (repaintsPending(Workspace::self()->desktopList())) { return true; } if (repaintsPending(Workspace::self()->unmanagedList())) { return true; } if (repaintsPending(Workspace::self()->deletedList())) { return true; } if (auto w = waylandServer()) { const auto &clients = w->clients(); auto test = [] (ShellClient *c) { return c->readyForPainting() && !c->repaints().isEmpty(); }; if (std::any_of(clients.begin(), clients.end(), test)) { return true; } const auto &internalClients = w->internalClients(); auto internalTest = [] (ShellClient *c) { return c->isShown(true) && !c->repaints().isEmpty(); }; if (std::any_of(internalClients.begin(), internalClients.end(), internalTest)) { return true; } } return false; } void Compositor::setCompositeResetTimer(int msecs) { compositeResetTimer.start(msecs); } void Compositor::setCompositeTimer() { if (!hasScene()) // should not really happen, but there may be e.g. some damage events still pending return; if (m_starting || !Workspace::self()) { return; } // Don't start the timer if we're waiting for a swap event if (m_bufferSwapPending && m_composeAtSwapCompletion) return; // Don't start the timer if all outputs are disabled if (!kwinApp()->platform()->areOutputsEnabled()) { return; } uint waitTime = 1; if (m_scene->blocksForRetrace()) { // TODO: make vBlankTime dynamic?! // It's required because glXWaitVideoSync will *likely* block a full frame if one enters // a retrace pass which can last a variable amount of time, depending on the actual screen // Now, my ooold 19" CRT can do such retrace so that 2ms are entirely sufficient, // while another ooold 15" TFT requires about 6ms qint64 padding = m_timeSinceLastVBlank; if (padding > fpsInterval) { // we're at low repaints or spent more time in painting than the user wanted to wait for that frame padding = vBlankInterval - (padding%vBlankInterval); // -> align to next vblank } else { // -> align to the next maxFps tick padding = ((vBlankInterval - padding%vBlankInterval) + (fpsInterval/vBlankInterval-1)*vBlankInterval); // "remaining time of the first vsync" + "time for the other vsyncs of the frame" } if (padding < options->vBlankTime()) { // we'll likely miss this frame waitTime = nanoToMilli(padding + vBlankInterval - options->vBlankTime()); // so we add one } else { waitTime = nanoToMilli(padding - options->vBlankTime()); } } else { // w/o blocking vsync we just jump to the next demanded tick if (fpsInterval > m_timeSinceLastVBlank) { waitTime = nanoToMilli(fpsInterval - m_timeSinceLastVBlank); if (!waitTime) { waitTime = 1; // will ensure we don't block out the eventloop - the system's just not faster ... } }/* else if (m_scene->syncsToVBlank() && m_timeSinceLastVBlank - fpsInterval < (vBlankInterval<<1)) { // NOTICE - "for later" ------------------------------------------------------------------ // It can happen that we push two frames within one refresh cycle. // Swapping will then block even with triple buffering when the GPU does not discard but // queues frames // now here's the mean part: if we take that as "OMG, we're late - next frame ASAP", // there'll immediately be 2 frames in the pipe, swapping will block, we think we're // late ... ewww // so instead we pad to the clock again and add 2ms safety to ensure the pipe is really // free // NOTICE: obviously m_timeSinceLastVBlank can be too big because we're too slow as well // So if this code was enabled, we'd needlessly half the framerate once more (15 instead of 30) waitTime = nanoToMilli(vBlankInterval - (m_timeSinceLastVBlank - fpsInterval)%vBlankInterval) + 2; }*/ else { waitTime = 1; // ... "0" would be sufficient, but the compositor isn't the WMs only task } } compositeTimer.start(qMin(waitTime, 250u), this); // force 4fps minimum } bool Compositor::isActive() { return !m_finishing && hasScene(); } bool Compositor::checkForOverlayWindow(WId w) const { if (!hasScene()) { // no scene, so it cannot be the overlay window return false; } if (!m_scene->overlayWindow()) { // no overlay window, it cannot be the overlay return false; } // and compare the window ID's return w == m_scene->overlayWindow()->window(); } bool Compositor::isOverlayWindowVisible() const { if (!hasScene()) { return false; } if (!m_scene->overlayWindow()) { return false; } return m_scene->overlayWindow()->isVisible(); } /***************************************************** * Workspace ****************************************************/ bool Workspace::compositing() const { return m_compositor && m_compositor->hasScene(); } //**************************************** // Toplevel //**************************************** bool Toplevel::setupCompositing() { if (!compositing()) return false; if (damage_handle != XCB_NONE) return false; if (kwinApp()->operationMode() == Application::OperationModeX11 && !surface()) { damage_handle = xcb_generate_id(connection()); xcb_damage_create(connection(), damage_handle, frameId(), XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); } damage_region = QRegion(0, 0, width(), height()); effect_window = new EffectWindowImpl(this); Compositor::self()->scene()->windowAdded(this); // With unmanaged windows there is a race condition between the client painting the window // and us setting up damage tracking. If the client wins we won't get a damage event even // though the window has been painted. To avoid this we mark the whole window as damaged // and schedule a repaint immediately after creating the damage object. if (dynamic_cast(this)) addDamageFull(); return true; } void Toplevel::finishCompositing(ReleaseReason releaseReason) { if (kwinApp()->operationMode() == Application::OperationModeX11 && damage_handle == XCB_NONE) return; if (effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data discardWindowPixmap(); delete effect_window; } if (damage_handle != XCB_NONE && releaseReason != ReleaseReason::Destroyed) { xcb_damage_destroy(connection(), damage_handle); } damage_handle = XCB_NONE; damage_region = QRegion(); repaints_region = QRegion(); effect_window = NULL; } void Toplevel::discardWindowPixmap() { addDamageFull(); if (effectWindow() != NULL && effectWindow()->sceneWindow() != NULL) effectWindow()->sceneWindow()->pixmapDiscarded(); } void Toplevel::damageNotifyEvent() { m_isDamaged = true; // Note: The rect is supposed to specify the damage extents, // but we don't know it at this point. No one who connects // to this signal uses the rect however. emit damaged(this, QRect()); } bool Toplevel::compositing() const { if (!Workspace::self()) { return false; } return Workspace::self()->compositing(); } void Client::damageNotifyEvent() { if (syncRequest.isPending && isResize()) { emit damaged(this, QRect()); m_isDamaged = true; return; } if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } Toplevel::damageNotifyEvent(); } bool Toplevel::resetAndFetchDamage() { if (!m_isDamaged) return false; if (damage_handle == XCB_NONE) { m_isDamaged = false; return true; } xcb_connection_t *conn = connection(); // Create a new region and copy the damage region to it, // resetting the damaged state. xcb_xfixes_region_t region = xcb_generate_id(conn); xcb_xfixes_create_region(conn, region, 0, 0); xcb_damage_subtract(conn, damage_handle, 0, region); // Send a fetch-region request and destroy the region m_regionCookie = xcb_xfixes_fetch_region_unchecked(conn, region); xcb_xfixes_destroy_region(conn, region); m_isDamaged = false; m_damageReplyPending = true; return m_damageReplyPending; } void Toplevel::getDamageRegionReply() { if (!m_damageReplyPending) return; m_damageReplyPending = false; // Get the fetch-region reply xcb_xfixes_fetch_region_reply_t *reply = xcb_xfixes_fetch_region_reply(connection(), m_regionCookie, 0); if (!reply) return; // Convert the reply to a QRegion int count = xcb_xfixes_fetch_region_rectangles_length(reply); QRegion region; if (count > 1 && count < 16) { xcb_rectangle_t *rects = xcb_xfixes_fetch_region_rectangles(reply); QVector qrects; qrects.reserve(count); for (int i = 0; i < count; i++) qrects << QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height); region.setRects(qrects.constData(), count); } else region += QRect(reply->extents.x, reply->extents.y, reply->extents.width, reply->extents.height); damage_region += region; repaints_region += region; free(reply); } void Toplevel::addDamageFull() { if (!compositing()) return; damage_region = rect(); repaints_region |= rect(); emit damaged(this, rect()); } void Toplevel::resetDamage() { damage_region = QRegion(); } void Toplevel::addRepaint(const QRect& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addRepaint(r); } void Toplevel::addRepaint(const QRegion& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(const QRect& r) { if (!compositing()) { return; } layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addLayerRepaint(r); } void Toplevel::addLayerRepaint(const QRegion& r) { if (!compositing()) return; layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaintFull() { repaints_region = visibleRect().translated(-pos()); emit needsRepaint(); } void Toplevel::resetRepaints() { repaints_region = QRegion(); layer_repaints_region = QRegion(); } void Toplevel::addWorkspaceRepaint(int x, int y, int w, int h) { addWorkspaceRepaint(QRect(x, y, w, h)); } void Toplevel::addWorkspaceRepaint(const QRect& r2) { if (!compositing()) return; Compositor::self()->addRepaint(r2); } //**************************************** // Client //**************************************** bool Client::setupCompositing() { if (!Toplevel::setupCompositing()){ return false; } if (isDecorated()) { decoratedClient()->destroyRenderer(); } updateVisibility(); // for internalKeep() return true; } void Client::finishCompositing(ReleaseReason releaseReason) { Toplevel::finishCompositing(releaseReason); updateVisibility(); if (!deleting) { if (isDecorated()) { decoratedClient()->destroyRenderer(); } } // for safety in case KWin is just resizing the window resetHaveResizeEffect(); } } // namespace diff --git a/debug_console.cpp b/debug_console.cpp index bbc8c5a9c..44aed0f9f 100644 --- a/debug_console.cpp +++ b/debug_console.cpp @@ -1,1509 +1,1546 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "debug_console.h" #include "composite.h" #include "client.h" #include "input_event.h" #include "main.h" #include "scene.h" #include "shell_client.h" #include "unmanaged.h" #include "wayland_server.h" #include "workspace.h" #include "keyboard_input.h" #if HAVE_INPUT #include "libinput/connection.h" #include "libinput/device.h" #endif #include #include #include "ui_debug_console.h" // KWayland #include #include #include #include // frameworks #include #include // Qt #include #include #include // xkb #include #include namespace KWin { static QString tableHeaderRow(const QString &title) { return QStringLiteral("%1").arg(title); } template static QString tableRow(const QString &title, const T &argument) { return QStringLiteral("%1%2").arg(title).arg(argument); } static QString timestampRow(quint32 timestamp) { return tableRow(i18n("Timestamp"), timestamp); } static QString timestampRowUsec(quint64 timestamp) { return tableRow(i18n("Timestamp (µsec)"), timestamp); } static QString buttonToString(Qt::MouseButton button) { switch (button) { case Qt::LeftButton: return i18nc("A mouse button", "Left"); case Qt::RightButton: return i18nc("A mouse button", "Right"); case Qt::MiddleButton: return i18nc("A mouse button", "Middle"); case Qt::BackButton: return i18nc("A mouse button", "Back"); case Qt::ForwardButton: return i18nc("A mouse button", "Forward"); case Qt::TaskButton: return i18nc("A mouse button", "Task"); case Qt::ExtraButton4: return i18nc("A mouse button", "Extra Button 4"); case Qt::ExtraButton5: return i18nc("A mouse button", "Extra Button 5"); case Qt::ExtraButton6: return i18nc("A mouse button", "Extra Button 6"); case Qt::ExtraButton7: return i18nc("A mouse button", "Extra Button 7"); case Qt::ExtraButton8: return i18nc("A mouse button", "Extra Button 8"); case Qt::ExtraButton9: return i18nc("A mouse button", "Extra Button 9"); case Qt::ExtraButton10: return i18nc("A mouse button", "Extra Button 10"); case Qt::ExtraButton11: return i18nc("A mouse button", "Extra Button 11"); case Qt::ExtraButton12: return i18nc("A mouse button", "Extra Button 12"); case Qt::ExtraButton13: return i18nc("A mouse button", "Extra Button 13"); case Qt::ExtraButton14: return i18nc("A mouse button", "Extra Button 14"); case Qt::ExtraButton15: return i18nc("A mouse button", "Extra Button 15"); case Qt::ExtraButton16: return i18nc("A mouse button", "Extra Button 16"); case Qt::ExtraButton17: return i18nc("A mouse button", "Extra Button 17"); case Qt::ExtraButton18: return i18nc("A mouse button", "Extra Button 18"); case Qt::ExtraButton19: return i18nc("A mouse button", "Extra Button 19"); case Qt::ExtraButton20: return i18nc("A mouse button", "Extra Button 20"); case Qt::ExtraButton21: return i18nc("A mouse button", "Extra Button 21"); case Qt::ExtraButton22: return i18nc("A mouse button", "Extra Button 22"); case Qt::ExtraButton23: return i18nc("A mouse button", "Extra Button 23"); case Qt::ExtraButton24: return i18nc("A mouse button", "Extra Button 24"); default: return QString(); } } #if HAVE_INPUT static QString deviceRow(LibInput::Device *device) { if (!device) { return tableRow(i18n("Input Device"), i18nc("The input device of the event is not known", "Unknown")); } return tableRow(i18n("Input Device"), QStringLiteral("%1 (%2)").arg(device->name()).arg(device->sysName())); } #endif static QString buttonsToString(Qt::MouseButtons buttons) { QString ret; for (uint i = 1; i < Qt::ExtraButton24; i = i << 1) { if (buttons & i) { ret.append(buttonToString(Qt::MouseButton(uint(buttons) & i))); ret.append(QStringLiteral(" ")); } }; return ret.trimmed(); } static const QString s_hr = QStringLiteral("
"); static const QString s_tableStart = QStringLiteral(""); static const QString s_tableEnd = QStringLiteral("
"); DebugConsoleFilter::DebugConsoleFilter(QTextEdit *textEdit) : InputEventSpy() , m_textEdit(textEdit) { } DebugConsoleFilter::~DebugConsoleFilter() = default; void DebugConsoleFilter::pointerEvent(MouseEvent *event) { QString text = s_hr; const QString timestamp = timestampRow(event->timestamp()); text.append(s_tableStart); switch (event->type()) { case QEvent::MouseMove: { text.append(tableHeaderRow(i18nc("A mouse pointer motion event", "Pointer Motion"))); #if HAVE_INPUT text.append(deviceRow(event->device())); #endif text.append(timestamp); if (event->timestampMicroseconds() != 0) { text.append(timestampRowUsec(event->timestampMicroseconds())); } if (event->delta() != QSizeF()) { text.append(tableRow(i18nc("The relative mouse movement", "Delta"), QStringLiteral("%1/%2").arg(event->delta().width()).arg(event->delta().height()))); } if (event->deltaUnaccelerated() != QSizeF()) { text.append(tableRow(i18nc("The relative mouse movement", "Delta (not accelerated)"), QStringLiteral("%1/%2").arg(event->deltaUnaccelerated().width()).arg(event->deltaUnaccelerated().height()))); } text.append(tableRow(i18nc("The global mouse pointer position", "Global Position"), QStringLiteral("%1/%2").arg(event->pos().x()).arg(event->pos().y()))); break; } case QEvent::MouseButtonPress: text.append(tableHeaderRow(i18nc("A mouse pointer button press event", "Pointer Button Press"))); #if HAVE_INPUT text.append(deviceRow(event->device())); #endif text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), event->nativeButton())); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; case QEvent::MouseButtonRelease: text.append(tableHeaderRow(i18nc("A mouse pointer button release event", "Pointer Button Release"))); #if HAVE_INPUT text.append(deviceRow(event->device())); #endif text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), event->nativeButton())); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; default: break; } text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::wheelEvent(WheelEvent *event) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A mouse pointer axis (wheel) event", "Pointer Axis"))); #if HAVE_INPUT text.append(deviceRow(event->device())); #endif text.append(timestampRow(event->timestamp())); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; text.append(tableRow(i18nc("The orientation of a pointer axis event", "Orientation"), orientation == Qt::Horizontal ? i18nc("An orientation of a pointer axis event", "Horizontal") : i18nc("An orientation of a pointer axis event", "Vertical"))); text.append(tableRow(QStringLiteral("Delta"), orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::keyEvent(KeyEvent *event) { QString text = s_hr; text.append(s_tableStart); switch (event->type()) { case QEvent::KeyPress: text.append(tableHeaderRow(i18nc("A key press event", "Key Press"))); break; case QEvent::KeyRelease: text.append(tableHeaderRow(i18nc("A key release event", "Key Release"))); break; default: break; } #if HAVE_INPUT text.append(deviceRow(event->device())); #endif auto modifiersToString = [event] { QString ret; if (event->modifiers().testFlag(Qt::ShiftModifier)) { ret.append(i18nc("A keyboard modifier", "Shift")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::ControlModifier)) { ret.append(i18nc("A keyboard modifier", "Control")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::AltModifier)) { ret.append(i18nc("A keyboard modifier", "Alt")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::MetaModifier)) { ret.append(i18nc("A keyboard modifier", "Meta")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::KeypadModifier)) { ret.append(i18nc("A keyboard modifier", "Keypad")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::GroupSwitchModifier)) { ret.append(i18nc("A keyboard modifier", "Group-switch")); ret.append(QStringLiteral(" ")); } return ret; }; text.append(timestampRow(event->timestamp())); text.append(tableRow(i18nc("Whether the event is an automatic key repeat", "Repeat"), event->isAutoRepeat())); text.append(tableRow(i18nc("The code as read from the input device", "Scan code"), event->nativeScanCode())); text.append(tableRow(i18nc("The translated code to an Xkb symbol", "Xkb symbol"), event->nativeVirtualKey())); text.append(tableRow(i18nc("The translated code interpreted as text", "Utf8"), event->text())); text.append(tableRow(i18nc("The currently active modifiers", "Modifiers"), modifiersToString())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::touchDown(quint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch down event", "Touch down"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::touchMotion(quint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch motion event", "Touch Motion"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::touchUp(quint32 id, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch up event", "Touch Up"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureBegin(int fingerCount, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture is started", "Pinch start"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Number of fingers in this pinch gesture", "Finger count"), fingerCount)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture is updated", "Pinch update"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Current scale in pinch gesture", "Scale"), scale)); text.append(tableRow(i18nc("Current angle in pinch gesture", "Angle delta"), angleDelta)); text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta x"), delta.width())); text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta y"), delta.height())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureEnd(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture ended", "Pinch end"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureCancelled(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture got cancelled", "Pinch cancelled"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureBegin(int fingerCount, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture is started", "Swipe start"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Number of fingers in this swipe gesture", "Finger count"), fingerCount)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureUpdate(const QSizeF &delta, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture is updated", "Swipe update"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta x"), delta.width())); text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta y"), delta.height())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureEnd(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture ended", "Swipe end"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureCancelled(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture got cancelled", "Swipe cancelled"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } +void DebugConsoleFilter::switchEvent(SwitchEvent *event) +{ + QString text = s_hr; + text.append(s_tableStart); + text.append(tableHeaderRow(i18nc("A hardware switch (e.g. notebook lid) got toggled", "Switch toggled"))); + text.append(timestampRow(event->timestamp())); + if (event->timestampMicroseconds() != 0) { + text.append(timestampRowUsec(event->timestampMicroseconds())); + } +#if HAVE_INPUT + text.append(deviceRow(event->device())); + QString switchName; + if (event->device()->isLidSwitch()) { + switchName = i18nc("Name of a hardware switch", "Notebook lid"); + } else if (event->device()->isTabletModeSwitch()) { + switchName = i18nc("Name of a hardware switch", "Tablet mode"); + } + text.append(tableRow(i18nc("A hardware switch", "Switch"), switchName)); +#endif + QString switchState; + switch (event->state()) { + case SwitchEvent::State::Off: + switchState = i18nc("The hardware switch got turned off", "Off"); + break; + case SwitchEvent::State::On: + switchState = i18nc("The hardware switch got turned on", "On"); + break; + default: + Q_UNREACHABLE(); + } + text.append(tableRow(i18nc("State of a hardware switch (on/off)", "State"), switchState)); + text.append(s_tableEnd); + + m_textEdit->insertHtml(text); + m_textEdit->ensureCursorVisible(); +} + DebugConsole::DebugConsole() : QWidget() , m_ui(new Ui::DebugConsole) { setAttribute(Qt::WA_ShowWithoutActivating); m_ui->setupUi(this); m_ui->windowsView->setItemDelegate(new DebugConsoleDelegate(this)); m_ui->windowsView->setModel(new DebugConsoleModel(this)); m_ui->surfacesView->setModel(new SurfaceTreeModel(this)); #if HAVE_INPUT if (kwinApp()->usesLibinput()) { m_ui->inputDevicesView->setModel(new InputDeviceModel(this)); m_ui->inputDevicesView->setItemDelegate(new DebugConsoleDelegate(this)); } #endif m_ui->quitButton->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); m_ui->tabWidget->setTabIcon(0, QIcon::fromTheme(QStringLiteral("view-list-tree"))); m_ui->tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("view-list-tree"))); if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) { m_ui->tabWidget->setTabEnabled(1, false); m_ui->tabWidget->setTabEnabled(2, false); } if (!kwinApp()->usesLibinput()) { m_ui->tabWidget->setTabEnabled(3, false); } connect(m_ui->quitButton, &QAbstractButton::clicked, this, &DebugConsole::deleteLater); connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, [this] (int index) { // delay creation of input event filter until the tab is selected if (index == 2 && m_inputFilter.isNull()) { m_inputFilter.reset(new DebugConsoleFilter(m_ui->inputTextEdit)); input()->installInputEventSpy(m_inputFilter.data()); } if (index == 5) { updateKeyboardTab(); connect(input(), &InputRedirection::keyStateChanged, this, &DebugConsole::updateKeyboardTab); } } ); // for X11 setWindowFlags(Qt::X11BypassWindowManagerHint); initGLTab(); } DebugConsole::~DebugConsole() = default; void DebugConsole::initGLTab() { if (!effects || !effects->isOpenGLCompositing()) { m_ui->noOpenGLLabel->setVisible(true); m_ui->glInfoScrollArea->setVisible(false); return; } GLPlatform *gl = GLPlatform::instance(); m_ui->noOpenGLLabel->setVisible(false); m_ui->glInfoScrollArea->setVisible(true); m_ui->glVendorStringLabel->setText(QString::fromLocal8Bit(gl->glVendorString())); m_ui->glRendererStringLabel->setText(QString::fromLocal8Bit(gl->glRendererString())); m_ui->glVersionStringLabel->setText(QString::fromLocal8Bit(gl->glVersionString())); m_ui->glslVersionStringLabel->setText(QString::fromLocal8Bit(gl->glShadingLanguageVersionString())); m_ui->glDriverLabel->setText(GLPlatform::driverToString(gl->driver())); m_ui->glGPULabel->setText(GLPlatform::chipClassToString(gl->chipClass())); m_ui->glVersionLabel->setText(GLPlatform::versionToString(gl->glVersion())); m_ui->glslLabel->setText(GLPlatform::versionToString(gl->glslVersion())); auto extensionsString = [] (const auto &extensions) { QString text = QStringLiteral("
    "); for (auto extension : extensions) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(extension))); } text.append(QStringLiteral("
")); return text; }; m_ui->platformExtensionsLabel->setText(extensionsString(Compositor::self()->scene()->openGLPlatformInterfaceExtensions())); m_ui->openGLExtensionsLabel->setText(extensionsString(openGLExtensions())); } template QString keymapComponentToString(xkb_keymap *map, const T &count, std::function f) { QString text = QStringLiteral("
    "); for (T i = 0; i < count; i++) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(f(map, i)))); } text.append(QStringLiteral("
")); return text; } template QString stateActiveComponents(xkb_state *state, const T &count, std::function f, std::function name) { QString text = QStringLiteral("
    "); xkb_keymap *map = xkb_state_get_keymap(state); for (T i = 0; i < count; i++) { if (f(state, i) == 1) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(name(map, i)))); } } text.append(QStringLiteral("
")); return text; } void DebugConsole::updateKeyboardTab() { auto xkb = input()->keyboard()->xkb(); xkb_keymap *map = xkb->keymap(); xkb_state *state = xkb->state(); m_ui->layoutsLabel->setText(keymapComponentToString(map, xkb_keymap_num_layouts(map), &xkb_keymap_layout_get_name)); m_ui->currentLayoutLabel->setText(xkb_keymap_layout_get_name(map, xkb->currentLayout())); m_ui->modifiersLabel->setText(keymapComponentToString(map, xkb_keymap_num_mods(map), &xkb_keymap_mod_get_name)); m_ui->ledsLabel->setText(keymapComponentToString(map, xkb_keymap_num_leds(map), &xkb_keymap_led_get_name)); m_ui->activeLedsLabel->setText(stateActiveComponents(state, xkb_keymap_num_leds(map), &xkb_state_led_index_is_active, &xkb_keymap_led_get_name)); using namespace std::placeholders; auto modActive = std::bind(xkb_state_mod_index_is_active, _1, _2, XKB_STATE_MODS_EFFECTIVE); m_ui->activeModifiersLabel->setText(stateActiveComponents(state, xkb_keymap_num_mods(map), modActive, &xkb_keymap_mod_get_name)); } void DebugConsole::showEvent(QShowEvent *event) { QWidget::showEvent(event); // delay the connection to the show event as in ctor the windowHandle returns null connect(windowHandle(), &QWindow::visibleChanged, this, [this] (bool visible) { if (visible) { // ignore return; } deleteLater(); } ); } DebugConsoleDelegate::DebugConsoleDelegate(QObject *parent) : QStyledItemDelegate(parent) { } DebugConsoleDelegate::~DebugConsoleDelegate() = default; QString DebugConsoleDelegate::displayText(const QVariant &value, const QLocale &locale) const { switch (value.type()) { case QMetaType::QPoint: { const QPoint p = value.toPoint(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QPointF: { const QPointF p = value.toPointF(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QSize: { const QSize s = value.toSize(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QSizeF: { const QSizeF s = value.toSizeF(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QRect: { const QRect r = value.toRect(); return QStringLiteral("%1,%2 %3x%4").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()); } default: if (value.userType() == qMetaTypeId()) { if (auto s = value.value()) { return QStringLiteral("KWayland::Server::SurfaceInterface(0x%1)").arg(qulonglong(s), 0, 16); } else { return QStringLiteral("nullptr"); } } if (value.userType() == qMetaTypeId()) { const auto buttons = value.value(); if (buttons == Qt::NoButton) { return i18n("No Mouse Buttons"); } QStringList list; if (buttons.testFlag(Qt::LeftButton)) { list << i18nc("Mouse Button", "left"); } if (buttons.testFlag(Qt::RightButton)) { list << i18nc("Mouse Button", "right"); } if (buttons.testFlag(Qt::MiddleButton)) { list << i18nc("Mouse Button", "middle"); } if (buttons.testFlag(Qt::BackButton)) { list << i18nc("Mouse Button", "back"); } if (buttons.testFlag(Qt::ForwardButton)) { list << i18nc("Mouse Button", "forward"); } if (buttons.testFlag(Qt::ExtraButton1)) { list << i18nc("Mouse Button", "extra 1"); } if (buttons.testFlag(Qt::ExtraButton2)) { list << i18nc("Mouse Button", "extra 2"); } if (buttons.testFlag(Qt::ExtraButton3)) { list << i18nc("Mouse Button", "extra 3"); } if (buttons.testFlag(Qt::ExtraButton4)) { list << i18nc("Mouse Button", "extra 4"); } if (buttons.testFlag(Qt::ExtraButton5)) { list << i18nc("Mouse Button", "extra 5"); } if (buttons.testFlag(Qt::ExtraButton6)) { list << i18nc("Mouse Button", "extra 6"); } if (buttons.testFlag(Qt::ExtraButton7)) { list << i18nc("Mouse Button", "extra 7"); } if (buttons.testFlag(Qt::ExtraButton8)) { list << i18nc("Mouse Button", "extra 8"); } if (buttons.testFlag(Qt::ExtraButton9)) { list << i18nc("Mouse Button", "extra 9"); } if (buttons.testFlag(Qt::ExtraButton10)) { list << i18nc("Mouse Button", "extra 10"); } if (buttons.testFlag(Qt::ExtraButton11)) { list << i18nc("Mouse Button", "extra 11"); } if (buttons.testFlag(Qt::ExtraButton12)) { list << i18nc("Mouse Button", "extra 12"); } if (buttons.testFlag(Qt::ExtraButton13)) { list << i18nc("Mouse Button", "extra 13"); } if (buttons.testFlag(Qt::ExtraButton14)) { list << i18nc("Mouse Button", "extra 14"); } if (buttons.testFlag(Qt::ExtraButton15)) { list << i18nc("Mouse Button", "extra 15"); } if (buttons.testFlag(Qt::ExtraButton16)) { list << i18nc("Mouse Button", "extra 16"); } if (buttons.testFlag(Qt::ExtraButton17)) { list << i18nc("Mouse Button", "extra 17"); } if (buttons.testFlag(Qt::ExtraButton18)) { list << i18nc("Mouse Button", "extra 18"); } if (buttons.testFlag(Qt::ExtraButton19)) { list << i18nc("Mouse Button", "extra 19"); } if (buttons.testFlag(Qt::ExtraButton20)) { list << i18nc("Mouse Button", "extra 20"); } if (buttons.testFlag(Qt::ExtraButton21)) { list << i18nc("Mouse Button", "extra 21"); } if (buttons.testFlag(Qt::ExtraButton22)) { list << i18nc("Mouse Button", "extra 22"); } if (buttons.testFlag(Qt::ExtraButton23)) { list << i18nc("Mouse Button", "extra 23"); } if (buttons.testFlag(Qt::ExtraButton24)) { list << i18nc("Mouse Button", "extra 24"); } if (buttons.testFlag(Qt::TaskButton)) { list << i18nc("Mouse Button", "task"); } return list.join(QStringLiteral(", ")); } break; } return QStyledItemDelegate::displayText(value, locale); } static const int s_x11ClientId = 1; static const int s_x11UnmanagedId = 2; static const int s_waylandClientId = 3; static const int s_waylandInternalId = 4; static const quint32 s_propertyBitMask = 0xFFFF0000; static const quint32 s_clientBitMask = 0x0000FFFF; static const quint32 s_idDistance = 10000; template void DebugConsoleModel::add(int parentRow, QVector &clients, T *client) { beginInsertRows(index(parentRow, 0, QModelIndex()), clients.count(), clients.count()); clients.append(client); endInsertRows(); } template void DebugConsoleModel::remove(int parentRow, QVector &clients, T *client) { const int remove = clients.indexOf(client); if (remove == -1) { return; } beginRemoveRows(index(parentRow, 0, QModelIndex()), remove, remove); clients.removeAt(remove); endRemoveRows(); } DebugConsoleModel::DebugConsoleModel(QObject *parent) : QAbstractItemModel(parent) { if (waylandServer()) { const auto clients = waylandServer()->clients(); for (auto c : clients) { m_shellClients.append(c); } const auto internals = waylandServer()->internalClients(); for (auto c : internals) { m_internalClients.append(c); } // TODO: that only includes windows getting shown, not those which are only created connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this] (ShellClient *c) { if (c->isInternal()) { add(s_waylandInternalId -1, m_internalClients, c); } else { add(s_waylandClientId -1, m_shellClients, c); } } ); connect(waylandServer(), &WaylandServer::shellClientRemoved, this, [this] (ShellClient *c) { remove(s_waylandInternalId -1, m_internalClients, c); remove(s_waylandClientId -1, m_shellClients, c); } ); } const auto x11Clients = workspace()->clientList(); for (auto c : x11Clients) { m_x11Clients.append(c); } const auto x11DesktopClients = workspace()->desktopList(); for (auto c : x11DesktopClients) { m_x11Clients.append(c); } connect(workspace(), &Workspace::clientAdded, this, [this] (Client *c) { add(s_x11ClientId -1, m_x11Clients, c); } ); connect(workspace(), &Workspace::clientRemoved, this, [this] (AbstractClient *ac) { Client *c = qobject_cast(ac); if (!c) { return; } remove(s_x11ClientId -1, m_x11Clients, c); } ); const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { m_unmanageds.append(u); } connect(workspace(), &Workspace::unmanagedAdded, this, [this] (Unmanaged *u) { add(s_x11UnmanagedId -1, m_unmanageds, u); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, [this] (Unmanaged *u) { remove(s_x11UnmanagedId -1, m_unmanageds, u); } ); } DebugConsoleModel::~DebugConsoleModel() = default; int DebugConsoleModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } int DebugConsoleModel::topLevelRowCount() const { return kwinApp()->shouldUseWaylandForCompositing() ? 4 : 2; } template int DebugConsoleModel::propertyCount(const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { return t->metaObject()->propertyCount(); } return 0; } int DebugConsoleModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return topLevelRowCount(); } switch (parent.internalId()) { case s_x11ClientId: return m_x11Clients.count(); case s_x11UnmanagedId: return m_unmanageds.count(); case s_waylandClientId: return m_shellClients.count(); case s_waylandInternalId: return m_internalClients.count(); default: break; } if (parent.internalId() & s_propertyBitMask) { // properties do not have children return 0; } if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return propertyCount(parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::shellClient); } else if (parent.internalId() < s_idDistance * (s_waylandInternalId + 1)) { return propertyCount(parent, &DebugConsoleModel::internalClient); } return 0; } template QModelIndex DebugConsoleModel::indexForClient(int row, int column, const QVector &clients, int id) const { if (column != 0) { return QModelIndex(); } if (row >= clients.count()) { return QModelIndex(); } return createIndex(row, column, s_idDistance * id + row); } template QModelIndex DebugConsoleModel::indexForProperty(int row, int column, const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { if (row >= t->metaObject()->propertyCount()) { return QModelIndex(); } return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); } return QModelIndex(); } QModelIndex DebugConsoleModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { // index for a top level item if (column != 0 || row >= topLevelRowCount()) { return QModelIndex(); } return createIndex(row, column, row + 1); } if (column >= 2) { // max of 2 columns return QModelIndex(); } // index for a client (second level) switch (parent.internalId()) { case s_x11ClientId: return indexForClient(row, column, m_x11Clients, s_x11ClientId); case s_x11UnmanagedId: return indexForClient(row, column, m_unmanageds, s_x11UnmanagedId); case s_waylandClientId: return indexForClient(row, column, m_shellClients, s_waylandClientId); case s_waylandInternalId: return indexForClient(row, column, m_internalClients, s_waylandInternalId); default: break; } // index for a property (third level) if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::shellClient); } else if (parent.internalId() < s_idDistance * (s_waylandInternalId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::internalClient); } return QModelIndex(); } QModelIndex DebugConsoleModel::parent(const QModelIndex &child) const { if (child.internalId() <= s_waylandInternalId) { return QModelIndex(); } if (child.internalId() & s_propertyBitMask) { // a property const quint32 parentId = child.internalId() & s_clientBitMask; if (parentId < s_idDistance * (s_x11ClientId + 1)) { return createIndex(parentId - (s_idDistance * s_x11ClientId), 0, parentId); } else if (parentId < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(parentId - (s_idDistance * s_x11UnmanagedId), 0, parentId); } else if (parentId < s_idDistance * (s_waylandClientId + 1)) { return createIndex(parentId - (s_idDistance * s_waylandClientId), 0, parentId); } else if (parentId < s_idDistance * (s_waylandInternalId + 1)) { return createIndex(parentId - (s_idDistance * s_waylandInternalId), 0, parentId); } return QModelIndex(); } if (child.internalId() < s_idDistance * (s_x11ClientId + 1)) { return createIndex(s_x11ClientId -1, 0, s_x11ClientId); } else if (child.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(s_x11UnmanagedId -1, 0, s_x11UnmanagedId); } else if (child.internalId() < s_idDistance * (s_waylandClientId + 1)) { return createIndex(s_waylandClientId -1, 0, s_waylandClientId); } else if (child.internalId() < s_idDistance * (s_waylandInternalId + 1)) { return createIndex(s_waylandInternalId -1, 0, s_waylandInternalId); } return QModelIndex(); } QVariant DebugConsoleModel::propertyData(QObject *object, const QModelIndex &index, int role) const { Q_UNUSED(role) const auto property = object->metaObject()->property(index.row()); if (index.column() == 0) { return property.name(); } else { const QVariant value = property.read(object); if (qstrcmp(property.name(), "windowType") == 0) { switch (value.toInt()) { case NET::Normal: return QStringLiteral("NET::Normal"); case NET::Desktop: return QStringLiteral("NET::Desktop"); case NET::Dock: return QStringLiteral("NET::Dock"); case NET::Toolbar: return QStringLiteral("NET::Toolbar"); case NET::Menu: return QStringLiteral("NET::Menu"); case NET::Dialog: return QStringLiteral("NET::Dialog"); case NET::Override: return QStringLiteral("NET::Override"); case NET::TopMenu: return QStringLiteral("NET::TopMenu"); case NET::Utility: return QStringLiteral("NET::Utility"); case NET::Splash: return QStringLiteral("NET::Splash"); case NET::DropdownMenu: return QStringLiteral("NET::DropdownMenu"); case NET::PopupMenu: return QStringLiteral("NET::PopupMenu"); case NET::Tooltip: return QStringLiteral("NET::Tooltip"); case NET::Notification: return QStringLiteral("NET::Notification"); case NET::ComboBox: return QStringLiteral("NET::ComboBox"); case NET::DNDIcon: return QStringLiteral("NET::DNDIcon"); case NET::OnScreenDisplay: return QStringLiteral("NET::OnScreenDisplay"); case NET::Unknown: default: return QStringLiteral("NET::Unknown"); } } return value; } return QVariant(); } template QVariant DebugConsoleModel::clientData(const QModelIndex &index, int role, const QVector clients) const { if (index.row() >= clients.count()) { return QVariant(); } auto c = clients.at(index.row()); if (role == Qt::DisplayRole) { return QStringLiteral("%1: %2").arg(c->window()).arg(c->caption()); } else if (role == Qt::DecorationRole) { return c->icon(); } return QVariant(); } QVariant DebugConsoleModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (!index.parent().isValid()) { // one of the top levels if (index.column() != 0 || role != Qt::DisplayRole) { return QVariant(); } switch (index.internalId()) { case s_x11ClientId: return i18n("X11 Client Windows"); case s_x11UnmanagedId: return i18n("X11 Unmanaged Windows"); case s_waylandClientId: return i18n("Wayland Windows"); case s_waylandInternalId: return i18n("Internal Windows"); default: return QVariant(); } } if (index.internalId() & s_propertyBitMask) { if (index.column() >= 2 || role != Qt::DisplayRole) { return QVariant(); } if (ShellClient *c = shellClient(index)) { return propertyData(c, index, role); } else if (ShellClient *c = internalClient(index)) { return propertyData(c, index, role); } else if (Client *c = x11Client(index)) { return propertyData(c, index, role); } else if (Unmanaged *u = unmanaged(index)) { return propertyData(u, index, role); } } else { if (index.column() != 0) { return QVariant(); } switch (index.parent().internalId()) { case s_x11ClientId: return clientData(index, role, m_x11Clients); case s_x11UnmanagedId: { if (index.row() >= m_unmanageds.count()) { return QVariant(); } auto u = m_unmanageds.at(index.row()); if (role == Qt::DisplayRole) { return u->window(); } break; } case s_waylandClientId: return clientData(index, role, m_shellClients); case s_waylandInternalId: return clientData(index, role, m_internalClients); default: break; } } return QVariant(); } template static T *clientForIndex(const QModelIndex &index, const QVector &clients, int id) { const qint32 row = (index.internalId() & s_clientBitMask) - (s_idDistance * id); if (row < 0 || row >= clients.count()) { return nullptr; } return clients.at(row); } ShellClient *DebugConsoleModel::shellClient(const QModelIndex &index) const { return clientForIndex(index, m_shellClients, s_waylandClientId); } ShellClient *DebugConsoleModel::internalClient(const QModelIndex &index) const { return clientForIndex(index, m_internalClients, s_waylandInternalId); } Client *DebugConsoleModel::x11Client(const QModelIndex &index) const { return clientForIndex(index, m_x11Clients, s_x11ClientId); } Unmanaged *DebugConsoleModel::unmanaged(const QModelIndex &index) const { return clientForIndex(index, m_unmanageds, s_x11UnmanagedId); } /////////////////////////////////////// SurfaceTreeModel SurfaceTreeModel::SurfaceTreeModel(QObject *parent) : QAbstractItemModel(parent) { // TODO: it would be nice to not have to reset the model on each change auto reset = [this] { beginResetModel(); endResetModel(); }; using namespace KWayland::Server; const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { if (!u->surface()) { continue; } connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } for (auto c : workspace()->allClientList()) { if (!c->surface()) { continue; } connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } for (auto c : workspace()->desktopList()) { if (!c->surface()) { continue; } connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } if (waylandServer()) { for (auto c : waylandServer()->internalClients()) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this, reset] (ShellClient *c) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); reset(); } ); } connect(workspace(), &Workspace::clientAdded, this, [this, reset] (AbstractClient *c) { if (c->surface()) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::clientRemoved, this, reset); connect(workspace(), &Workspace::unmanagedAdded, this, [this, reset] (Unmanaged *u) { if (u->surface()) { connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, reset); } SurfaceTreeModel::~SurfaceTreeModel() = default; int SurfaceTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } int SurfaceTreeModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); return children.count(); } return 0; } const int internalClientsCount = waylandServer() ? waylandServer()->internalClients().count() : 0; // toplevel are all windows return workspace()->allClientList().count() + workspace()->desktopList().count() + workspace()->unmanagedList().count() + internalClientsCount; } QModelIndex SurfaceTreeModel::index(int row, int column, const QModelIndex &parent) const { if (column != 0) { // invalid column return QModelIndex(); } if (parent.isValid()) { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); if (row < children.count()) { return createIndex(row, column, children.at(row)->surface().data()); } } return QModelIndex(); } // a window const auto &allClients = workspace()->allClientList(); if (row < allClients.count()) { // references a client return createIndex(row, column, allClients.at(row)->surface()); } int reference = allClients.count(); const auto &desktopClients = workspace()->desktopList(); if (row < reference + desktopClients.count()) { return createIndex(row, column, desktopClients.at(row-reference)->surface()); } reference += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); if (row < reference + unmanaged.count()) { return createIndex(row, column, unmanaged.at(row-reference)->surface()); } reference += unmanaged.count(); if (waylandServer()) { const auto &internal = waylandServer()->internalClients(); if (row < reference + internal.count()) { return createIndex(row, column, internal.at(row-reference)->surface()); } } // not found return QModelIndex(); } QModelIndex SurfaceTreeModel::parent(const QModelIndex &child) const { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(child.internalPointer())) { const auto &subsurface = surface->subSurface(); if (subsurface.isNull()) { // doesn't reference a subsurface, this is a top-level window return QModelIndex(); } SurfaceInterface *parent = subsurface->parentSurface().data(); if (!parent) { // something is wrong return QModelIndex(); } // is the parent a subsurface itself? if (parent->subSurface()) { auto grandParent = parent->subSurface()->parentSurface(); if (grandParent.isNull()) { // something is wrong return QModelIndex(); } const auto &children = grandParent->childSubSurfaces(); for (int row = 0; row < children.count(); row++) { if (children.at(row).data() == parent->subSurface().data()) { return createIndex(row, 0, parent); } } return QModelIndex(); } // not a subsurface, thus it's a true window int row = 0; const auto &allClients = workspace()->allClientList(); for (; row < allClients.count(); row++) { if (allClients.at(row)->surface() == parent) { return createIndex(row, 0, parent); } } row = allClients.count(); const auto &desktopClients = workspace()->desktopList(); for (int i = 0; i < desktopClients.count(); i++) { if (desktopClients.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } row += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); for (int i = 0; i < unmanaged.count(); i++) { if (unmanaged.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } row += unmanaged.count(); if (waylandServer()) { const auto &internal = waylandServer()->internalClients(); for (int i = 0; i < internal.count(); i++) { if (internal.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } } } return QModelIndex(); } QVariant SurfaceTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(index.internalPointer())) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath()) .arg(surface->client()->processId()) .arg(surface->id()); } else if (role == Qt::DecorationRole) { if (auto buffer = surface->buffer()) { if (buffer->shmBuffer()) { return buffer->data().scaled(QSize(64, 64), Qt::KeepAspectRatio); } } } } return QVariant(); } #if HAVE_INPUT InputDeviceModel::InputDeviceModel(QObject *parent) : QAbstractItemModel(parent) , m_devices(LibInput::Connection::self()->devices()) { for (auto it = m_devices.constBegin(); it != m_devices.constEnd(); ++it) { setupDeviceConnections(*it); } auto c = LibInput::Connection::self(); connect(c, &LibInput::Connection::deviceAdded, this, [this] (LibInput::Device *d) { beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count()); m_devices << d; setupDeviceConnections(d); endInsertRows(); } ); connect(c, &LibInput::Connection::deviceRemoved, this, [this] (LibInput::Device *d) { const int index = m_devices.indexOf(d); if (index == -1) { return; } beginRemoveRows(QModelIndex(), index, index); m_devices.removeAt(index); endRemoveRows(); } ); } InputDeviceModel::~InputDeviceModel() = default; int InputDeviceModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } QVariant InputDeviceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (!index.parent().isValid() && index.column() == 0) { const auto devices = LibInput::Connection::self()->devices(); if (index.row() >= devices.count()) { return QVariant(); } if (role == Qt::DisplayRole) { return devices.at(index.row())->name(); } } if (index.parent().isValid()) { if (role == Qt::DisplayRole) { const auto device = LibInput::Connection::self()->devices().at(index.parent().row()); const auto property = device->metaObject()->property(index.row()); if (index.column() == 0) { return property.name(); } else if (index.column() == 1) { return device->property(property.name()); } } } return QVariant(); } QModelIndex InputDeviceModel::index(int row, int column, const QModelIndex &parent) const { if (column >= 2) { return QModelIndex(); } if (parent.isValid()) { if (parent.internalId() & s_propertyBitMask) { return QModelIndex(); } if (row >= LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount()) { return QModelIndex(); } return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); } if (row >= LibInput::Connection::self()->devices().count()) { return QModelIndex(); } return createIndex(row, column, row + 1); } int InputDeviceModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return LibInput::Connection::self()->devices().count(); } if (parent.internalId() & s_propertyBitMask) { return 0; } return LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount(); } QModelIndex InputDeviceModel::parent(const QModelIndex &child) const { if (child.internalId() & s_propertyBitMask) { const quintptr parentId = child.internalId() & s_clientBitMask; return createIndex(parentId - 1, 0, parentId); } return QModelIndex(); } void InputDeviceModel::setupDeviceConnections(LibInput::Device *device) { connect(device, &LibInput::Device::enabledChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("enabled"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); connect(device, &LibInput::Device::leftHandedChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("leftHanded"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); connect(device, &LibInput::Device::pointerAccelerationChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("pointerAcceleration"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); } #endif } diff --git a/debug_console.h b/debug_console.h index e5eeb09f1..27503d339 100644 --- a/debug_console.h +++ b/debug_console.h @@ -1,185 +1,187 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DEBUG_CONSOLE_H #define KWIN_DEBUG_CONSOLE_H #include #include #include "input.h" #include "input_event_spy.h" #include #include #include class QTextEdit; namespace Ui { class DebugConsole; } namespace KWin { class Client; class ShellClient; class Unmanaged; class DebugConsoleFilter; class KWIN_EXPORT DebugConsoleModel : public QAbstractItemModel { Q_OBJECT public: explicit DebugConsoleModel(QObject *parent = nullptr); virtual ~DebugConsoleModel(); int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex & parent) const override; int rowCount(const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; private: template QModelIndex indexForClient(int row, int column, const QVector &clients, int id) const; template QModelIndex indexForProperty(int row, int column, const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const; template int propertyCount(const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const; QVariant propertyData(QObject *object, const QModelIndex &index, int role) const; template QVariant clientData(const QModelIndex &index, int role, const QVector clients) const; template void add(int parentRow, QVector &clients, T *client); template void remove(int parentRow, QVector &clients, T *client); ShellClient *shellClient(const QModelIndex &index) const; ShellClient *internalClient(const QModelIndex &index) const; Client *x11Client(const QModelIndex &index) const; Unmanaged *unmanaged(const QModelIndex &index) const; int topLevelRowCount() const; QVector m_shellClients; QVector m_internalClients; QVector m_x11Clients; QVector m_unmanageds; }; class DebugConsoleDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit DebugConsoleDelegate(QObject *parent = nullptr); virtual ~DebugConsoleDelegate(); QString displayText(const QVariant &value, const QLocale &locale) const override; }; class KWIN_EXPORT DebugConsole : public QWidget { Q_OBJECT public: DebugConsole(); virtual ~DebugConsole(); protected: void showEvent(QShowEvent *event) override; private: void initGLTab(); void updateKeyboardTab(); QScopedPointer m_ui; QScopedPointer m_inputFilter; }; class SurfaceTreeModel : public QAbstractItemModel { Q_OBJECT public: explicit SurfaceTreeModel(QObject *parent = nullptr); virtual ~SurfaceTreeModel(); int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex & parent) const override; int rowCount(const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; }; class DebugConsoleFilter : public InputEventSpy { public: explicit DebugConsoleFilter(QTextEdit *textEdit); virtual ~DebugConsoleFilter(); void pointerEvent(MouseEvent *event) override; void wheelEvent(WheelEvent *event) override; void keyEvent(KeyEvent *event) override; void touchDown(quint32 id, const QPointF &pos, quint32 time) override; void touchMotion(quint32 id, const QPointF &pos, quint32 time) override; void touchUp(quint32 id, quint32 time) override; void pinchGestureBegin(int fingerCount, quint32 time) override; void pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override; void pinchGestureEnd(quint32 time) override; void pinchGestureCancelled(quint32 time) override; void swipeGestureBegin(int fingerCount, quint32 time) override; void swipeGestureUpdate(const QSizeF &delta, quint32 time) override; void swipeGestureEnd(quint32 time) override; void swipeGestureCancelled(quint32 time) override; + void switchEvent(SwitchEvent *event) override; + private: QTextEdit *m_textEdit; }; #if HAVE_INPUT namespace LibInput { class Device; } class InputDeviceModel : public QAbstractItemModel { Q_OBJECT public: explicit InputDeviceModel(QObject *parent = nullptr); virtual ~InputDeviceModel(); int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex & parent) const override; int rowCount(const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; private: void setupDeviceConnections(LibInput::Device *device); QVector m_devices; }; #endif } #endif diff --git a/decorations/decoratedclient.cpp b/decorations/decoratedclient.cpp index ff5ce64d4..09cc3d641 100644 --- a/decorations/decoratedclient.cpp +++ b/decorations/decoratedclient.cpp @@ -1,303 +1,315 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "decoratedclient.h" #include "decorationpalette.h" #include "decorationrenderer.h" #include "abstract_client.h" #include "composite.h" #include "cursor.h" #include "options.h" #include "platform.h" #include "workspace.h" #include #include #include +#include namespace KWin { namespace Decoration { DecoratedClientImpl::DecoratedClientImpl(AbstractClient *client, KDecoration2::DecoratedClient *decoratedClient, KDecoration2::Decoration *decoration) : QObject() , ApplicationMenuEnabledDecoratedClientPrivate(decoratedClient, decoration) , m_client(client) , m_clientSize(client->clientSize()) , m_renderer(nullptr) { createRenderer(); client->setDecoratedClient(QPointer(this)); connect(client, &AbstractClient::activeChanged, this, [decoratedClient, client]() { emit decoratedClient->activeChanged(client->isActive()); } ); connect(client, &AbstractClient::geometryChanged, this, [decoratedClient, this]() { if (m_client->clientSize() == m_clientSize) { return; } const auto oldSize = m_clientSize; m_clientSize = m_client->clientSize(); if (oldSize.width() != m_clientSize.width()) { emit decoratedClient->widthChanged(m_clientSize.width()); } if (oldSize.height() != m_clientSize.height()) { emit decoratedClient->heightChanged(m_clientSize.height()); } } ); connect(client, &AbstractClient::desktopChanged, this, [decoratedClient, client]() { emit decoratedClient->onAllDesktopsChanged(client->isOnAllDesktops()); } ); connect(client, &AbstractClient::captionChanged, this, [decoratedClient, client]() { emit decoratedClient->captionChanged(client->caption()); } ); connect(client, &AbstractClient::iconChanged, this, [decoratedClient, client]() { emit decoratedClient->iconChanged(client->icon()); } ); connect(client, &AbstractClient::shadeChanged, this, &Decoration::DecoratedClientImpl::signalShadeChange); connect(client, &AbstractClient::keepAboveChanged, decoratedClient, &KDecoration2::DecoratedClient::keepAboveChanged); connect(client, &AbstractClient::keepBelowChanged, decoratedClient, &KDecoration2::DecoratedClient::keepBelowChanged); m_compositorToggledConnection = connect(Compositor::self(), &Compositor::compositingToggled, this, [this, decoration]() { delete m_renderer; m_renderer = nullptr; createRenderer(); decoration->update(); } ); connect(Compositor::self(), &Compositor::aboutToDestroy, this, [this] { disconnect(m_compositorToggledConnection); m_compositorToggledConnection = QMetaObject::Connection(); } ); connect(client, &AbstractClient::quickTileModeChanged, decoratedClient, [this, decoratedClient]() { emit decoratedClient->adjacentScreenEdgesChanged(adjacentScreenEdges()); } ); connect(client, &AbstractClient::closeableChanged, decoratedClient, &KDecoration2::DecoratedClient::closeableChanged); connect(client, &AbstractClient::shadeableChanged, decoratedClient, &KDecoration2::DecoratedClient::shadeableChanged); connect(client, &AbstractClient::minimizeableChanged, decoratedClient, &KDecoration2::DecoratedClient::minimizeableChanged); connect(client, &AbstractClient::maximizeableChanged, decoratedClient, &KDecoration2::DecoratedClient::maximizeableChanged); connect(client, &AbstractClient::paletteChanged, decoratedClient, &KDecoration2::DecoratedClient::paletteChanged); connect(client, &AbstractClient::hasApplicationMenuChanged, decoratedClient, &KDecoration2::DecoratedClient::hasApplicationMenuChanged); connect(client, &AbstractClient::applicationMenuActiveChanged, decoratedClient, &KDecoration2::DecoratedClient::applicationMenuActiveChanged); } DecoratedClientImpl::~DecoratedClientImpl() = default; void DecoratedClientImpl::signalShadeChange() { emit decoratedClient()->shadedChanged(m_client->isShade()); } #define DELEGATE(type, name, clientName) \ type DecoratedClientImpl::name() const \ { \ return m_client->clientName(); \ } #define DELEGATE2(type, name) DELEGATE(type, name, name) DELEGATE2(QString, caption) DELEGATE2(bool, isActive) DELEGATE2(bool, isCloseable) DELEGATE(bool, isMaximizeable, isMaximizable) DELEGATE(bool, isMinimizeable, isMinimizable) DELEGATE2(bool, isModal) DELEGATE(bool, isMoveable, isMovable) DELEGATE(bool, isResizeable, isResizable) DELEGATE2(bool, isShadeable) DELEGATE2(bool, providesContextHelp) DELEGATE2(int, desktop) DELEGATE2(bool, isOnAllDesktops) DELEGATE2(QPalette, palette) DELEGATE2(QIcon, icon) #undef DELEGATE2 #undef DELEGATE #define DELEGATE(type, name, clientName) \ type DecoratedClientImpl::name() const \ { \ return m_client->clientName(); \ } DELEGATE(bool, isKeepAbove, keepAbove) DELEGATE(bool, isKeepBelow, keepBelow) DELEGATE(bool, isShaded, isShade) DELEGATE(WId, windowId, windowId) DELEGATE(WId, decorationId, frameId) #undef DELEGATE #define DELEGATE(name, op) \ void DecoratedClientImpl::name() \ { \ Workspace::self()->performWindowOperation(m_client, Options::op); \ } DELEGATE(requestToggleShade, ShadeOp) DELEGATE(requestToggleOnAllDesktops, OnAllDesktopsOp) DELEGATE(requestToggleKeepAbove, KeepAboveOp) DELEGATE(requestToggleKeepBelow, KeepBelowOp) #undef DELEGATE #define DELEGATE(name, clientName) \ void DecoratedClientImpl::name() \ { \ m_client->clientName(); \ } DELEGATE(requestContextHelp, showContextHelp) DELEGATE(requestMinimize, minimize) #undef DELEGATE void DecoratedClientImpl::requestClose() { QMetaObject::invokeMethod(m_client, "closeWindow", Qt::QueuedConnection); } QColor DecoratedClientImpl::color(KDecoration2::ColorGroup group, KDecoration2::ColorRole role) const { auto dp = m_client->decorationPalette(); if (dp) { return dp->color(group, role); } return QColor(); } +void DecoratedClientImpl::requestShowToolTip(const QString &text) +{ + QPoint pos = Cursor::pos(); + QToolTip::showText(pos, text); +} + +void DecoratedClientImpl::requestHideToolTip() +{ + QToolTip::hideText(); +} + void DecoratedClientImpl::requestShowWindowMenu() { // TODO: add rect to requestShowWindowMenu Workspace::self()->showWindowMenu(QRect(Cursor::pos(), Cursor::pos()), m_client); } void DecoratedClientImpl::requestShowApplicationMenu(const QRect &rect, int actionId) { Workspace::self()->showApplicationMenu(rect, m_client, actionId); } void DecoratedClientImpl::showApplicationMenu(int actionId) { decoration()->showApplicationMenu(actionId); } void DecoratedClientImpl::requestToggleMaximization(Qt::MouseButtons buttons) { QMetaObject::invokeMethod(this, "delayedRequestToggleMaximization", Qt::QueuedConnection, Q_ARG(Options::WindowOperation, options->operationMaxButtonClick(buttons))); } void DecoratedClientImpl::delayedRequestToggleMaximization(Options::WindowOperation operation) { Workspace::self()->performWindowOperation(m_client, operation); } int DecoratedClientImpl::width() const { return m_clientSize.width(); } int DecoratedClientImpl::height() const { return m_clientSize.height(); } bool DecoratedClientImpl::isMaximizedVertically() const { return m_client->maximizeMode() & MaximizeVertical; } bool DecoratedClientImpl::isMaximized() const { return isMaximizedHorizontally() && isMaximizedVertically(); } bool DecoratedClientImpl::isMaximizedHorizontally() const { return m_client->maximizeMode() & MaximizeHorizontal; } Qt::Edges DecoratedClientImpl::adjacentScreenEdges() const { Qt::Edges edges; const QuickTileMode mode = m_client->quickTileMode(); if (mode.testFlag(QuickTileFlag::Left)) { edges |= Qt::LeftEdge; if (!mode.testFlag(QuickTileFlag::Top) && !mode.testFlag(QuickTileFlag::Bottom)) { // using complete side edges |= Qt::TopEdge | Qt::BottomEdge; } } if (mode.testFlag(QuickTileFlag::Top)) { edges |= Qt::TopEdge; } if (mode.testFlag(QuickTileFlag::Right)) { edges |= Qt::RightEdge; if (!mode.testFlag(QuickTileFlag::Top) && !mode.testFlag(QuickTileFlag::Bottom)) { // using complete side edges |= Qt::TopEdge | Qt::BottomEdge; } } if (mode.testFlag(QuickTileFlag::Bottom)) { edges |= Qt::BottomEdge; } return edges; } bool DecoratedClientImpl::hasApplicationMenu() const { return m_client->hasApplicationMenu(); } bool DecoratedClientImpl::isApplicationMenuActive() const { return m_client->applicationMenuActive(); } void DecoratedClientImpl::createRenderer() { m_renderer = kwinApp()->platform()->createDecorationRenderer(this); } void DecoratedClientImpl::destroyRenderer() { delete m_renderer; m_renderer = nullptr; } } } diff --git a/decorations/decoratedclient.h b/decorations/decoratedclient.h index e01a2e6a0..5323a0041 100644 --- a/decorations/decoratedclient.h +++ b/decorations/decoratedclient.h @@ -1,115 +1,117 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DECORATED_CLIENT_H #define KWIN_DECORATED_CLIENT_H #include "options.h" #include #include namespace KWin { class AbstractClient; namespace Decoration { class Renderer; class DecoratedClientImpl : public QObject, public KDecoration2::ApplicationMenuEnabledDecoratedClientPrivate { Q_OBJECT public: explicit DecoratedClientImpl(AbstractClient *client, KDecoration2::DecoratedClient *decoratedClient, KDecoration2::Decoration *decoration); virtual ~DecoratedClientImpl(); QString caption() const override; WId decorationId() const override; int desktop() const override; int height() const override; QIcon icon() const override; bool isActive() const override; bool isCloseable() const override; bool isKeepAbove() const override; bool isKeepBelow() const override; bool isMaximizeable() const override; bool isMaximized() const override; bool isMaximizedHorizontally() const override; bool isMaximizedVertically() const override; bool isMinimizeable() const override; bool isModal() const override; bool isMoveable() const override; bool isOnAllDesktops() const override; bool isResizeable() const override; bool isShadeable() const override; bool isShaded() const override; QPalette palette() const override; QColor color(KDecoration2::ColorGroup group, KDecoration2::ColorRole role) const override; bool providesContextHelp() const override; int width() const override; WId windowId() const override; Qt::Edges adjacentScreenEdges() const override; bool hasApplicationMenu() const override; bool isApplicationMenuActive() const override; + void requestShowToolTip(const QString &text) override; + void requestHideToolTip() override; void requestClose() override; void requestContextHelp() override; void requestToggleMaximization(Qt::MouseButtons buttons) override; void requestMinimize() override; void requestShowWindowMenu() override; void requestShowApplicationMenu(const QRect &rect, int actionId) override; void requestToggleKeepAbove() override; void requestToggleKeepBelow() override; void requestToggleOnAllDesktops() override; void requestToggleShade() override; void showApplicationMenu(int actionId); AbstractClient *client() { return m_client; } Renderer *renderer() { return m_renderer; } void destroyRenderer(); KDecoration2::DecoratedClient *decoratedClient() { return KDecoration2::DecoratedClientPrivate::client(); } void signalShadeChange(); private Q_SLOTS: void delayedRequestToggleMaximization(Options::WindowOperation operation); private: void createRenderer(); AbstractClient *m_client; QSize m_clientSize; Renderer *m_renderer; QMetaObject::Connection m_compositorToggledConnection; }; } } #endif diff --git a/decorations/decorationrenderer.cpp b/decorations/decorationrenderer.cpp index 420c2476c..3efbceba3 100644 --- a/decorations/decorationrenderer.cpp +++ b/decorations/decorationrenderer.cpp @@ -1,81 +1,88 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "decorationrenderer.h" #include "decoratedclient.h" #include "deleted.h" +#include "abstract_client.h" +#include "screens.h" #include #include #include #include namespace KWin { namespace Decoration { Renderer::Renderer(DecoratedClientImpl *client) : QObject(client) , m_client(client) , m_imageSizesDirty(true) { - auto markImageSizesDirty = [this]{ m_imageSizesDirty = true; }; + auto markImageSizesDirty = [this]{ + m_imageSizesDirty = true; + }; + connect(client->client(), &AbstractClient::screenScaleChanged, this, markImageSizesDirty); connect(client->decoration(), &KDecoration2::Decoration::bordersChanged, this, markImageSizesDirty); connect(client->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, markImageSizesDirty); connect(client->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, markImageSizesDirty); } Renderer::~Renderer() = default; void Renderer::schedule(const QRect &rect) { m_scheduled = m_scheduled.united(rect); emit renderScheduled(rect); } QRegion Renderer::getScheduled() { QRegion region = m_scheduled; m_scheduled = QRegion(); return region; } QImage Renderer::renderToImage(const QRect &geo) { Q_ASSERT(m_client); - QImage image(geo.width(), geo.height(), QImage::Format_ARGB32_Premultiplied); + auto dpr = client()->client()->screenScale(); + QImage image(geo.width() * dpr, geo.height() * dpr, QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(dpr); image.fill(Qt::transparent); QPainter p(&image); p.setRenderHint(QPainter::Antialiasing); - p.setWindow(geo); + p.setWindow(QRect(geo.topLeft(), geo.size() * dpr)); p.setClipRect(geo); client()->decoration()->paint(&p, geo); return image; } void Renderer::reparent(Deleted *deleted) { setParent(deleted); m_client = nullptr; } } } diff --git a/decorations/settings.cpp b/decorations/settings.cpp index 92deb2519..8c5332378 100644 --- a/decorations/settings.cpp +++ b/decorations/settings.cpp @@ -1,190 +1,191 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "settings.h" // KWin #include "composite.h" #include "virtualdesktops.h" #include "workspace.h" +#include "appmenu.h" #include #include #include namespace KWin { namespace Decoration { SettingsImpl::SettingsImpl(KDecoration2::DecorationSettings *parent) : QObject() , DecorationSettingsPrivate(parent) , m_borderSize(KDecoration2::BorderSize::Normal) { readSettings(); auto c = connect(Compositor::self(), &Compositor::compositingToggled, parent, &KDecoration2::DecorationSettings::alphaChannelSupportedChanged); connect(VirtualDesktopManager::self(), &VirtualDesktopManager::countChanged, this, [parent](uint previous, uint current) { if (previous != 1 && current != 1) { return; } emit parent->onAllDesktopsAvailableChanged(current > 1); } ); // prevent changes in Decoration due to Compositor being destroyed connect(Compositor::self(), &Compositor::aboutToDestroy, this, [this, c] { disconnect(c); } ); connect(Workspace::self(), &Workspace::configChanged, this, &SettingsImpl::readSettings); } SettingsImpl::~SettingsImpl() = default; bool SettingsImpl::isAlphaChannelSupported() const { return Compositor::self()->compositing(); } bool SettingsImpl::isOnAllDesktopsAvailable() const { return VirtualDesktopManager::self()->count() > 1; } bool SettingsImpl::isCloseOnDoubleClickOnMenu() const { return m_closeDoubleClickMenu; } static QHash s_buttonNames; static void initButtons() { if (!s_buttonNames.isEmpty()) { return; } s_buttonNames[KDecoration2::DecorationButtonType::Menu] = QChar('M'); s_buttonNames[KDecoration2::DecorationButtonType::ApplicationMenu] = QChar('N'); s_buttonNames[KDecoration2::DecorationButtonType::OnAllDesktops] = QChar('S'); s_buttonNames[KDecoration2::DecorationButtonType::ContextHelp] = QChar('H'); s_buttonNames[KDecoration2::DecorationButtonType::Minimize] = QChar('I'); s_buttonNames[KDecoration2::DecorationButtonType::Maximize] = QChar('A'); s_buttonNames[KDecoration2::DecorationButtonType::Close] = QChar('X'); s_buttonNames[KDecoration2::DecorationButtonType::KeepAbove] = QChar('F'); s_buttonNames[KDecoration2::DecorationButtonType::KeepBelow] = QChar('B'); s_buttonNames[KDecoration2::DecorationButtonType::Shade] = QChar('L'); } static QString buttonsToString(const QVector &buttons) { auto buttonToString = [](KDecoration2::DecorationButtonType button) -> QChar { const auto it = s_buttonNames.constFind(button); if (it != s_buttonNames.constEnd()) { return it.value(); } return QChar(); }; QString ret; for (auto button : buttons) { ret.append(buttonToString(button)); } return ret; } QVector< KDecoration2::DecorationButtonType > SettingsImpl::readDecorationButtons(const KConfigGroup &config, const char *key, const QVector< KDecoration2::DecorationButtonType > &defaultValue) const { initButtons(); auto buttonsFromString = [](const QString &buttons) -> QVector { QVector ret; for (auto it = buttons.begin(); it != buttons.end(); ++it) { for (auto it2 = s_buttonNames.constBegin(); it2 != s_buttonNames.constEnd(); ++it2) { if (it2.value() == (*it)) { ret << it2.key(); } } } return ret; }; return buttonsFromString(config.readEntry(key, buttonsToString(defaultValue))); } static KDecoration2::BorderSize stringToSize(const QString &name) { static const QMap s_sizes = QMap({ {QStringLiteral("None"), KDecoration2::BorderSize::None}, {QStringLiteral("NoSides"), KDecoration2::BorderSize::NoSides}, {QStringLiteral("Tiny"), KDecoration2::BorderSize::Tiny}, {QStringLiteral("Normal"), KDecoration2::BorderSize::Normal}, {QStringLiteral("Large"), KDecoration2::BorderSize::Large}, {QStringLiteral("VeryLarge"), KDecoration2::BorderSize::VeryLarge}, {QStringLiteral("Huge"), KDecoration2::BorderSize::Huge}, {QStringLiteral("VeryHuge"), KDecoration2::BorderSize::VeryHuge}, {QStringLiteral("Oversized"), KDecoration2::BorderSize::Oversized} }); auto it = s_sizes.constFind(name); if (it == s_sizes.constEnd()) { // non sense values are interpreted just like normal return KDecoration2::BorderSize::Normal; } return it.value(); } void SettingsImpl::readSettings() { KConfigGroup config = kwinApp()->config()->group(QStringLiteral("org.kde.kdecoration2")); const auto &left = readDecorationButtons(config, "ButtonsOnLeft", QVector({ KDecoration2::DecorationButtonType::Menu, - KDecoration2::DecorationButtonType::ApplicationMenu, KDecoration2::DecorationButtonType::OnAllDesktops })); if (left != m_leftButtons) { m_leftButtons = left; emit decorationSettings()->decorationButtonsLeftChanged(m_leftButtons); } const auto &right = readDecorationButtons(config, "ButtonsOnRight", QVector({ KDecoration2::DecorationButtonType::ContextHelp, KDecoration2::DecorationButtonType::Minimize, KDecoration2::DecorationButtonType::Maximize, KDecoration2::DecorationButtonType::Close })); if (right != m_rightButtons) { m_rightButtons = right; emit decorationSettings()->decorationButtonsRightChanged(m_rightButtons); } + ApplicationMenu::self()->setViewEnabled(left.contains(KDecoration2::DecorationButtonType::ApplicationMenu) || right.contains(KDecoration2::DecorationButtonType::ApplicationMenu)); const bool close = config.readEntry("CloseOnDoubleClickOnMenu", false); if (close != m_closeDoubleClickMenu) { m_closeDoubleClickMenu = close; emit decorationSettings()->closeOnDoubleClickOnMenuChanged(m_closeDoubleClickMenu); } const auto size = stringToSize(config.readEntry("BorderSize", QStringLiteral("Normal"))); if (size != m_borderSize) { m_borderSize = size; emit decorationSettings()->borderSizeChanged(m_borderSize); } emit decorationSettings()->reconfigured(); } } } diff --git a/effects/CMakeLists.txt b/effects/CMakeLists.txt index 317d67e61..30b731352 100644 --- a/effects/CMakeLists.txt +++ b/effects/CMakeLists.txt @@ -1,190 +1,192 @@ # KI18N Translation Domain for this library add_definitions(-DTRANSLATION_DOMAIN=\"kwin_effects\" -DEFFECT_BUILTINS) include_directories(${KWIN_SOURCE_DIR}) # for xcbutils.h set(kwin_effect_OWN_LIBS kwineffects ) if( KWIN_HAVE_XRENDER_COMPOSITING ) set(kwin_effect_OWN_LIBS ${kwin_effect_OWN_LIBS} kwinxrenderutils) endif() set(kwin_effect_KDE_LIBS KF5::ConfigGui KF5::ConfigWidgets KF5::GlobalAccel KF5::I18n KF5::WindowSystem KF5::Plasma # screenedge effect KF5::IconThemes KF5::Service KF5::Notifications # screenshot effect ) set(kwin_effect_QT_LIBS Qt5::Concurrent Qt5::DBus Qt5::Quick Qt5::X11Extras ) set(kwin_effect_XLIB_LIBS ${X11_X11_LIB} ) set(kwin_effect_XCB_LIBS XCB::XCB XCB::IMAGE XCB::XFIXES ) if( KWIN_HAVE_XRENDER_COMPOSITING ) set(kwin_effect_XCB_LIBS ${kwin_effect_XCB_LIBS} XCB::RENDER) endif() set(kwin_effect_OWN_LIBS ${kwin_effect_OWN_LIBS} kwinglutils) macro( KWIN4_ADD_EFFECT_BACKEND name ) add_library( ${name} SHARED ${ARGN} ) target_link_libraries( ${name} PRIVATE ${kwin_effect_OWN_LIBS} ${kwin_effect_KDE_LIBS} ${kwin_effect_QT_LIBS} ${kwin_effect_XLIB_LIBS} ${kwin_effect_XCB_LIBS}) endmacro() # Adds effect plugin with given name. Sources are given after the name macro( KWIN4_ADD_EFFECT name ) kwin4_add_effect_backend(kwin4_effect_${name} ${ARGN}) set_target_properties(kwin4_effect_${name} PROPERTIES VERSION 1.0.0 SOVERSION 1 ) set_target_properties(kwin4_effect_${name} PROPERTIES OUTPUT_NAME ${KWIN_NAME}4_effect_${name}) install(TARGETS kwin4_effect_${name} ${INSTALL_TARGETS_DEFAULT_ARGS} ) endmacro() # Install the KWin/Effect service type install( FILES kwineffect.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) # Create initial variables set( kwin4_effect_include_directories ) set( kwin4_effect_builtins_sources logging.cpp effect_builtins.cpp blur/blur.cpp blur/blurshader.cpp colorpicker/colorpicker.cpp cube/cube.cpp cube/cube_proxy.cpp cube/cubeslide.cpp coverswitch/coverswitch.cpp desktopgrid/desktopgrid.cpp diminactive/diminactive.cpp flipswitch/flipswitch.cpp glide/glide.cpp invert/invert.cpp lookingglass/lookingglass.cpp magiclamp/magiclamp.cpp magnifier/magnifier.cpp mouseclick/mouseclick.cpp mousemark/mousemark.cpp presentwindows/presentwindows.cpp presentwindows/presentwindows_proxy.cpp resize/resize.cpp showfps/showfps.cpp + slide/slide.cpp thumbnailaside/thumbnailaside.cpp touchpoints/touchpoints.cpp trackmouse/trackmouse.cpp windowgeometry/windowgeometry.cpp wobblywindows/wobblywindows.cpp zoom/zoom.cpp ) qt5_add_resources( kwin4_effect_builtins_sources shaders.qrc ) kconfig_add_kcfg_files(kwin4_effect_builtins_sources blur/blurconfig.kcfgc cube/cubeslideconfig.kcfgc cube/cubeconfig.kcfgc coverswitch/coverswitchconfig.kcfgc desktopgrid/desktopgridconfig.kcfgc diminactive/diminactiveconfig.kcfgc fallapart/fallapartconfig.kcfgc flipswitch/flipswitchconfig.kcfgc glide/glideconfig.kcfgc lookingglass/lookingglassconfig.kcfgc magiclamp/magiclampconfig.kcfgc magnifier/magnifierconfig.kcfgc mouseclick/mouseclickconfig.kcfgc mousemark/mousemarkconfig.kcfgc presentwindows/presentwindowsconfig.kcfgc resize/resizeconfig.kcfgc showfps/showfpsconfig.kcfgc + slide/slideconfig.kcfgc slidingpopups/slidingpopupsconfig.kcfgc thumbnailaside/thumbnailasideconfig.kcfgc trackmouse/trackmouseconfig.kcfgc windowgeometry/windowgeometryconfig.kcfgc wobblywindows/wobblywindowsconfig.kcfgc zoom/zoomconfig.kcfgc ) # scripted effects add_subdirectory( dialogparent ) add_subdirectory( eyeonscreen ) add_subdirectory( fade ) add_subdirectory( fadedesktop ) add_subdirectory( frozenapp ) add_subdirectory( login ) add_subdirectory( logout ) add_subdirectory( maximize ) add_subdirectory( morphingpopups ) add_subdirectory( scalein ) add_subdirectory( translucency ) add_subdirectory( windowaperture ) ############################################################################### # Built-in effects go here # Common effects add_subdirectory( desktopgrid ) add_subdirectory( diminactive ) include( dimscreen/CMakeLists.txt ) include( fallapart/CMakeLists.txt ) include( highlightwindow/CMakeLists.txt ) include( kscreen/CMakeLists.txt ) add_subdirectory( magiclamp ) include( minimizeanimation/CMakeLists.txt ) add_subdirectory( presentwindows ) add_subdirectory( resize ) include( screenedge/CMakeLists.txt ) add_subdirectory( showfps ) include( showpaint/CMakeLists.txt ) -include( slide/CMakeLists.txt ) +add_subdirectory( slide ) include( slideback/CMakeLists.txt ) include( slidingpopups/CMakeLists.txt ) add_subdirectory( thumbnailaside ) add_subdirectory( windowgeometry ) add_subdirectory( zoom ) # OpenGL-specific effects add_subdirectory( blur ) include( backgroundcontrast/CMakeLists.txt ) add_subdirectory( coverswitch ) add_subdirectory( cube ) add_subdirectory( flipswitch ) add_subdirectory( glide ) add_subdirectory( invert ) add_subdirectory( lookingglass ) add_subdirectory( magnifier ) add_subdirectory( mouseclick ) add_subdirectory( mousemark ) include( screenshot/CMakeLists.txt ) include( sheet/CMakeLists.txt ) include( snaphelper/CMakeLists.txt ) include( startupfeedback/CMakeLists.txt ) add_subdirectory( trackmouse ) add_subdirectory( wobblywindows ) ############################################################################### # Add the builtins plugin kwin4_add_effect( builtins ${kwin4_effect_builtins_sources} ) diff --git a/effects/backgroundcontrast/contrast.cpp b/effects/backgroundcontrast/contrast.cpp index 3ef1cf1e0..f920fcd88 100644 --- a/effects/backgroundcontrast/contrast.cpp +++ b/effects/backgroundcontrast/contrast.cpp @@ -1,486 +1,486 @@ /* * Copyright © 2010 Fredrik Höglund * Copyright © 2011 Philipp Knechtges * Copyright 2014 Marco Martin * * 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; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "contrast.h" #include "contrastshader.h" // KConfigSkeleton #include #include #include #include #include namespace KWin { static const QByteArray s_contrastAtomName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION"); ContrastEffect::ContrastEffect() { shader = ContrastShader::create(); reconfigure(ReconfigureAll); // ### Hackish way to announce support. // Should be included in _NET_SUPPORTED instead. if (shader && shader->isValid()) { net_wm_contrast_region = effects->announceSupportProperty(s_contrastAtomName, this); KWayland::Server::Display *display = effects->waylandDisplay(); if (display) { m_contrastManager = display->createContrastManager(this); m_contrastManager->create(); } } else { net_wm_contrast_region = 0; } connect(effects, SIGNAL(windowAdded(KWin::EffectWindow*)), this, SLOT(slotWindowAdded(KWin::EffectWindow*))); connect(effects, SIGNAL(windowDeleted(KWin::EffectWindow*)), this, SLOT(slotWindowDeleted(KWin::EffectWindow*))); connect(effects, SIGNAL(propertyNotify(KWin::EffectWindow*,long)), this, SLOT(slotPropertyNotify(KWin::EffectWindow*,long))); connect(effects, SIGNAL(screenGeometryChanged(QSize)), this, SLOT(slotScreenGeometryChanged())); connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this] { if (shader && shader->isValid()) { net_wm_contrast_region = effects->announceSupportProperty(s_contrastAtomName, this); } } ); // Fetch the contrast regions for all windows for (EffectWindow *window: effects->stackingOrder()) { updateContrastRegion(window); } } ContrastEffect::~ContrastEffect() { delete shader; } void ContrastEffect::slotScreenGeometryChanged() { effects->makeOpenGLContextCurrent(); if (!supported()) { effects->reloadEffect(this); return; } for (EffectWindow *window: effects->stackingOrder()) { updateContrastRegion(window); } } void ContrastEffect::reconfigure(ReconfigureFlags flags) { Q_UNUSED(flags) if (shader) shader->init(); if (!shader || !shader->isValid()) { effects->removeSupportProperty(s_contrastAtomName, this); delete m_contrastManager; m_contrastManager = nullptr; } } void ContrastEffect::updateContrastRegion(EffectWindow *w) { QRegion region; float colorTransform[16]; QByteArray value; if (net_wm_contrast_region != XCB_ATOM_NONE) { value = w->readProperty(net_wm_contrast_region, net_wm_contrast_region, 32); if (value.size() > 0 && !((value.size() - (16 * sizeof(uint32_t))) % ((4 * sizeof(uint32_t))))) { const uint32_t *cardinals = reinterpret_cast(value.constData()); const float *floatCardinals = reinterpret_cast(value.constData()); unsigned int i = 0; for (; i < ((value.size() - (16 * sizeof(uint32_t)))) / sizeof(uint32_t);) { int x = cardinals[i++]; int y = cardinals[i++]; int w = cardinals[i++]; int h = cardinals[i++]; region += QRect(x, y, w, h); } for (unsigned int j = 0; j < 16; ++j) { colorTransform[j] = floatCardinals[i + j]; } QMatrix4x4 colorMatrix(colorTransform); m_colorMatrices[w] = colorMatrix; } } KWayland::Server::SurfaceInterface *surf = w->surface(); if (surf && surf->contrast()) { region = surf->contrast()->region(); m_colorMatrices[w] = colorMatrix(surf->contrast()->contrast(), surf->contrast()->intensity(), surf->contrast()->saturation()); } //!value.isNull() full window in X11 case, surf->contrast() //valid, full window in wayland case if (region.isEmpty() && (!value.isNull() || (surf && surf->contrast()))) { // Set the data to a dummy value. // This is needed to be able to distinguish between the value not // being set, and being set to an empty region. w->setData(WindowBackgroundContrastRole, 1); } else w->setData(WindowBackgroundContrastRole, region); } void ContrastEffect::slotWindowAdded(EffectWindow *w) { KWayland::Server::SurfaceInterface *surf = w->surface(); if (surf) { m_contrastChangedConnections[w] = connect(surf, &KWayland::Server::SurfaceInterface::contrastChanged, this, [this, w] () { if (w) { updateContrastRegion(w); } }); } updateContrastRegion(w); } void ContrastEffect::slotWindowDeleted(EffectWindow *w) { if (m_contrastChangedConnections.contains(w)) { disconnect(m_contrastChangedConnections[w]); m_contrastChangedConnections.remove(w); m_colorMatrices.remove(w); } } void ContrastEffect::slotPropertyNotify(EffectWindow *w, long atom) { if (w && atom == net_wm_contrast_region && net_wm_contrast_region != XCB_ATOM_NONE) { updateContrastRegion(w); } } QMatrix4x4 ContrastEffect::colorMatrix(qreal contrast, qreal intensity, qreal saturation) { QMatrix4x4 satMatrix; //saturation QMatrix4x4 intMatrix; //intensity QMatrix4x4 contMatrix; //contrast //Saturation matrix if (!qFuzzyCompare(saturation, 1.0)) { const qreal rval = (1.0 - saturation) * .2126; const qreal gval = (1.0 - saturation) * .7152; const qreal bval = (1.0 - saturation) * .0722; satMatrix = QMatrix4x4(rval + saturation, rval, rval, 0.0, gval, gval + saturation, gval, 0.0, bval, bval, bval + saturation, 0.0, 0, 0, 0, 1.0); } //IntensityMatrix if (!qFuzzyCompare(intensity, 1.0)) { intMatrix.scale(intensity, intensity, intensity); } //Contrast Matrix if (!qFuzzyCompare(contrast, 1.0)) { const float transl = (1.0 - contrast) / 2.0; contMatrix = QMatrix4x4(contrast, 0, 0, 0.0, 0, contrast, 0, 0.0, 0, 0, contrast, 0.0, transl, transl, transl, 1.0); } QMatrix4x4 colorMatrix = contMatrix * satMatrix * intMatrix; //colorMatrix = colorMatrix.transposed(); return colorMatrix; } bool ContrastEffect::enabledByDefault() { GLPlatform *gl = GLPlatform::instance(); if (gl->isIntel() && gl->chipClass() < SandyBridge) return false; if (gl->isSoftwareEmulation()) { return false; } return true; } bool ContrastEffect::supported() { bool supported = effects->isOpenGLCompositing() && GLRenderTarget::supported(); if (supported) { int maxTexSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); const QSize screenSize = effects->virtualScreenSize(); if (screenSize.width() > maxTexSize || screenSize.height() > maxTexSize) supported = false; } return supported; } QRegion ContrastEffect::contrastRegion(const EffectWindow *w) const { QRegion region; const QVariant value = w->data(WindowBackgroundContrastRole); if (value.isValid()) { const QRegion appRegion = qvariant_cast(value); if (!appRegion.isEmpty()) { region |= appRegion.translated(w->contentsRect().topLeft()) & w->decorationInnerRect(); } else { // An empty region means that the blur effect should be enabled // for the whole window. region = w->decorationInnerRect(); } } return region; } void ContrastEffect::uploadRegion(QVector2D *&map, const QRegion ®ion) { - foreach (const QRect &r, region.rects()) { + for (const QRect &r : region) { const QVector2D topLeft(r.x(), r.y()); const QVector2D topRight(r.x() + r.width(), r.y()); const QVector2D bottomLeft(r.x(), r.y() + r.height()); const QVector2D bottomRight(r.x() + r.width(), r.y() + r.height()); // First triangle *(map++) = topRight; *(map++) = topLeft; *(map++) = bottomLeft; // Second triangle *(map++) = bottomLeft; *(map++) = bottomRight; *(map++) = topRight; } } void ContrastEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion ®ion) { const int vertexCount = region.rectCount() * 6; if (!vertexCount) return; QVector2D *map = (QVector2D *) vbo->map(vertexCount * sizeof(QVector2D)); uploadRegion(map, region); vbo->unmap(); const GLVertexAttrib layout[] = { { VA_Position, 2, GL_FLOAT, 0 }, { VA_TexCoord, 2, GL_FLOAT, 0 } }; vbo->setAttribLayout(layout, 2, sizeof(QVector2D)); } void ContrastEffect::prePaintScreen(ScreenPrePaintData &data, int time) { m_paintedArea = QRegion(); m_currentContrast = QRegion(); effects->prePaintScreen(data, time); } void ContrastEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { // this effect relies on prePaintWindow being called in the bottom to top order effects->prePaintWindow(w, data, time); if (!w->isPaintingEnabled()) { return; } if (!shader || !shader->isValid()) { return; } const QRegion oldPaint = data.paint; // we don't have to blur a region we don't see m_currentContrast -= data.clip; // if we have to paint a non-opaque part of this window that intersects with the // currently blurred region (which is not cached) we have to redraw the whole region if ((data.paint-data.clip).intersects(m_currentContrast)) { data.paint |= m_currentContrast; } // in case this window has regions to be blurred const QRect screen = effects->virtualScreenGeometry(); const QRegion contrastArea = contrastRegion(w).translated(w->pos()) & screen; // we are not caching the window // if this window or an window underneath the modified area is painted again we have to // do everything if (m_paintedArea.intersects(contrastArea) || data.paint.intersects(contrastArea)) { data.paint |= contrastArea; // we have to check again whether we do not damage a blurred area // of a window we do not cache if (contrastArea.intersects(m_currentContrast)) { data.paint |= m_currentContrast; } } m_currentContrast |= contrastArea; // m_paintedArea keep track of all repainted areas m_paintedArea -= data.clip; m_paintedArea |= data.paint; } bool ContrastEffect::shouldContrast(const EffectWindow *w, int mask, const WindowPaintData &data) const { if (!shader || !shader->isValid()) return false; if (effects->activeFullScreenEffect() && !w->data(WindowForceBackgroundContrastRole).toBool()) return false; if (w->isDesktop()) return false; bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0); bool translated = data.xTranslation() || data.yTranslation(); if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBackgroundContrastRole).toBool()) return false; if (!w->hasAlpha()) return false; return true; } void ContrastEffect::drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { const QRect screen = GLRenderTarget::virtualScreenGeometry(); if (shouldContrast(w, mask, data)) { QRegion shape = region & contrastRegion(w).translated(w->pos()) & screen; // let's do the evil parts - someone wants to blur behind a transformed window const bool translated = data.xTranslation() || data.yTranslation(); const bool scaled = data.xScale() != 1 || data.yScale() != 1; if (scaled) { QPoint pt = shape.boundingRect().topLeft(); QVector shapeRects = shape.rects(); shape = QRegion(); // clear foreach (QRect r, shapeRects) { r.moveTo(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(), pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation()); r.setWidth(r.width() * data.xScale()); r.setHeight(r.height() * data.yScale()); shape |= r; } shape = shape & region; //Only translated, not scaled } else if (translated) { shape = shape.translated(data.xTranslation(), data.yTranslation()); shape = shape & region; } if (!shape.isEmpty()) { doContrast(w, shape, screen, data.opacity(), data.screenProjectionMatrix()); } } // Draw the window over the contrast area effects->drawWindow(w, mask, region, data); } void ContrastEffect::paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity) { //FIXME: this is a no-op for now, it should figure out the right contrast, intensity, saturation effects->paintEffectFrame(frame, region, opacity, frameOpacity); } void ContrastEffect::doContrast(EffectWindow *w, const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection) { const QRegion actualShape = shape & screen; const QRect r = actualShape.boundingRect(); qreal scale = GLRenderTarget::virtualScreenScale(); // Upload geometry for the horizontal and vertical passes GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); uploadGeometry(vbo, actualShape); vbo->bindArrays(); // Create a scratch texture and copy the area in the back buffer that we're // going to blur into it GLTexture scratch(GL_RGBA8, r.width() * scale, r.height() * scale); scratch.setFilter(GL_LINEAR); scratch.setWrapMode(GL_CLAMP_TO_EDGE); scratch.bind(); const QRect sg = GLRenderTarget::virtualScreenGeometry(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (r.x() - sg.x()) * scale, (sg.height() - sg.y() - r.y() - r.height()) * scale, scratch.width(), scratch.height()); // Draw the texture on the offscreen framebuffer object, while blurring it horizontally shader->setColorMatrix(m_colorMatrices.value(w)); shader->bind(); shader->setOpacity(opacity); // Set up the texture matrix to transform from screen coordinates // to texture coordinates. QMatrix4x4 textureMatrix; textureMatrix.scale(1.0 / r.width(), -1.0 / r.height(), 1); textureMatrix.translate(-r.x(), -r.height() - r.y(), 0); shader->setTextureMatrix(textureMatrix); shader->setModelViewProjectionMatrix(screenProjection); vbo->draw(GL_TRIANGLES, 0, actualShape.rectCount() * 6); scratch.unbind(); scratch.discard(); vbo->unbindArrays(); if (opacity < 1.0) { glDisable(GL_BLEND); } shader->unbind(); } } // namespace KWin diff --git a/effects/blur/blur.cpp b/effects/blur/blur.cpp index b4a4d0139..b4af21535 100644 --- a/effects/blur/blur.cpp +++ b/effects/blur/blur.cpp @@ -1,784 +1,769 @@ /* * Copyright © 2010 Fredrik Höglund * Copyright © 2011 Philipp Knechtges + * Copyright © 2018 Alex Nemeth * * 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; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "blur.h" #include "effects.h" #include "blurshader.h" // KConfigSkeleton #include "blurconfig.h" #include #include +#include // for ceil() #include #include #include #include namespace KWin { static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"); BlurEffect::BlurEffect() { initConfig(); - shader = BlurShader::create(); + m_shader = BlurShader::create(); m_simpleShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("logout-blur.frag")); + m_simpleTarget = new GLRenderTarget(); + if (!m_simpleShader->isValid()) { qCDebug(KWINEFFECTS) << "Simple blur shader failed to load"; } - updateTexture(); + initBlurStrengthValues(); reconfigure(ReconfigureAll); // ### Hackish way to announce support. // Should be included in _NET_SUPPORTED instead. - if (shader && shader->isValid() && target->valid()) { + if (m_shader && m_shader->isValid() && m_renderTargetsValid) { net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); KWayland::Server::Display *display = effects->waylandDisplay(); if (display) { m_blurManager = display->createBlurManager(this); m_blurManager->create(); } } else { net_wm_blur_region = 0; } connect(effects, SIGNAL(windowAdded(KWin::EffectWindow*)), this, SLOT(slotWindowAdded(KWin::EffectWindow*))); connect(effects, SIGNAL(windowDeleted(KWin::EffectWindow*)), this, SLOT(slotWindowDeleted(KWin::EffectWindow*))); connect(effects, SIGNAL(propertyNotify(KWin::EffectWindow*,long)), this, SLOT(slotPropertyNotify(KWin::EffectWindow*,long))); connect(effects, SIGNAL(screenGeometryChanged(QSize)), this, SLOT(slotScreenGeometryChanged())); connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this] { - if (shader && shader->isValid() && target->valid()) { + if (m_shader && m_shader->isValid() && m_renderTargetsValid) { net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); } } ); // Fetch the blur regions for all windows foreach (EffectWindow *window, effects->stackingOrder()) updateBlurRegion(window); } BlurEffect::~BlurEffect() { - windows.clear(); + deleteFBOs(); + + delete m_simpleTarget; + m_simpleTarget = nullptr; delete m_simpleShader; - delete shader; - delete target; + m_simpleShader = nullptr; + + delete m_shader; + m_shader = nullptr; } void BlurEffect::slotScreenGeometryChanged() { effects->makeOpenGLContextCurrent(); updateTexture(); + // Fetch the blur regions for all windows foreach (EffectWindow *window, effects->stackingOrder()) updateBlurRegion(window); effects->doneOpenGLContextCurrent(); } -void BlurEffect::updateTexture() { - delete target; - // Offscreen texture that's used as the target for the horizontal blur pass - // and the source for the vertical pass. - tex = GLTexture(GL_RGBA8, effects->virtualScreenSize()); - tex.setFilter(GL_LINEAR); - tex.setWrapMode(GL_CLAMP_TO_EDGE); +bool BlurEffect::renderTargetsValid() const +{ + return !m_renderTargets.isEmpty() && std::find_if(m_renderTargets.cbegin(), m_renderTargets.cend(), + [](const GLRenderTarget *target) { + return !target->valid(); + }) == m_renderTargets.cend(); +} + +void BlurEffect::deleteFBOs() +{ + qDeleteAll(m_renderTargets); + + m_renderTargets.clear(); + m_renderTextures.clear(); +} + +void BlurEffect::updateTexture() +{ + deleteFBOs(); + + /* Reserve memory for: + * - The original sized texture (1) + * - The downsized textures (m_downSampleIterations) + * - The helper texture (1) + */ + m_renderTargets.reserve(m_downSampleIterations + 2); + m_renderTextures.reserve(m_downSampleIterations + 2); + + for (int i = 0; i <= m_downSampleIterations; i++) { + m_renderTextures.append(GLTexture(GL_RGBA8, effects->virtualScreenSize() / (1 << i))); + m_renderTextures.last().setFilter(GL_LINEAR); + m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); + + m_renderTargets.append(new GLRenderTarget(m_renderTextures.last())); + } + + // This last set is used as a temporary helper texture + m_renderTextures.append(GLTexture(GL_RGBA8, effects->virtualScreenSize())); + m_renderTextures.last().setFilter(GL_LINEAR); + m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); + + m_renderTargets.append(new GLRenderTarget(m_renderTextures.last())); + + m_renderTargetsValid = renderTargetsValid(); + + // Prepare the stack for the rendering + m_renderTargetStack.clear(); + m_renderTargets.reserve(m_downSampleIterations * 2 - 1); + + // Upsample + for (int i = 1; i < m_downSampleIterations; i++) { + m_renderTargetStack.push(m_renderTargets[i]); + } + + // Downsample + for (int i = m_downSampleIterations; i > 0; i--) { + m_renderTargetStack.push(m_renderTargets[i]); + } + + // Copysample + m_renderTargetStack.push(m_renderTargets[0]); +} + +void BlurEffect::initBlurStrengthValues() +{ + // This function creates an array of blur strength values that are evenly distributed + + // The range of the slider on the blur settings UI + int numOfBlurSteps = 15; + int remainingSteps = numOfBlurSteps; + + /* + * Explanation for these numbers: + * + * The texture blur amount depends on the downsampling iterations and the offset value. + * By changing the offset we can alter the blur amount without relying on further downsampling. + * But there is a minimum and maximum value of offset per downsample iteration before we + * get artifacts. + * + * The minOffset variable is the minimum offset value for an iteration before we + * get blocky artifacts because of the downsampling. + * + * The maxOffset value is the maximum offset value for an iteration before we + * get diagonal line artifacts because of the nature of the dual kawase blur algorithm. + * + * The expandSize value is the minimum value for an iteration before we reach the end + * of a texture in the shader and sample outside of the area that was copied into the + * texture from the screen. + */ + + // {minOffset, maxOffset, expandSize} + blurOffsets.append({1.0, 2.0, 10}); // Down sample size / 2 + blurOffsets.append({2.0, 3.0, 20}); // Down sample size / 4 + blurOffsets.append({2.0, 5.0, 50}); // Down sample size / 8 + blurOffsets.append({3.0, 8.0, 150}); // Down sample size / 16 + //blurOffsets.append({5.0, 10.0, 400}); // Down sample size / 32 + //blurOffsets.append({7.0, ?.0}); // Down sample size / 64 + + float offsetSum = 0; + + for (int i = 0; i < blurOffsets.size(); i++) { + offsetSum += blurOffsets[i].maxOffset - blurOffsets[i].minOffset; + } + + for (int i = 0; i < blurOffsets.size(); i++) { + int iterationNumber = std::ceil((blurOffsets[i].maxOffset - blurOffsets[i].minOffset) / offsetSum * numOfBlurSteps); + remainingSteps -= iterationNumber; + + if (remainingSteps < 0) { + iterationNumber += remainingSteps; + } + + float offsetDifference = blurOffsets[i].maxOffset - blurOffsets[i].minOffset; - target = new GLRenderTarget(tex); + for (int j = 1; j <= iterationNumber; j++) { + // {iteration, offset} + blurStrengthValues.append({i + 1, blurOffsets[i].minOffset + (offsetDifference / iterationNumber) * j}); + } + } } void BlurEffect::reconfigure(ReconfigureFlags flags) { Q_UNUSED(flags) BlurConfig::self()->read(); - int radius = qBound(2, BlurConfig::blurRadius(), 14); - if (shader) - shader->setRadius(radius); - m_shouldCache = BlurConfig::cacheTexture(); + m_useSimpleBlur = BlurConfig::useSimpleBlur(); - windows.clear(); + int blurStrength = BlurConfig::blurStrength() - 1; + m_downSampleIterations = blurStrengthValues[blurStrength].iteration; + m_offset = blurStrengthValues[blurStrength].offset; + m_expandSize = blurOffsets[m_downSampleIterations - 1].expandSize; - if (!shader || !shader->isValid()) { + updateTexture(); + + if (!m_shader || !m_shader->isValid()) { effects->removeSupportProperty(s_blurAtomName, this); delete m_blurManager; m_blurManager = nullptr; } + + // Update all windows for the blur to take effect + effects->addRepaintFull(); } void BlurEffect::updateBlurRegion(EffectWindow *w) const { QRegion region; QByteArray value; if (net_wm_blur_region != XCB_ATOM_NONE) { value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32); if (value.size() > 0 && !(value.size() % (4 * sizeof(uint32_t)))) { const uint32_t *cardinals = reinterpret_cast(value.constData()); for (unsigned int i = 0; i < value.size() / sizeof(uint32_t);) { int x = cardinals[i++]; int y = cardinals[i++]; int w = cardinals[i++]; int h = cardinals[i++]; region += QRect(x, y, w, h); } } } KWayland::Server::SurfaceInterface *surf = w->surface(); if (surf && surf->blur()) { region = surf->blur()->region(); } //!value.isNull() full window in X11 case, surf->blur() //valid, full window in wayland case if (region.isEmpty() && (!value.isNull() || (surf && surf->blur()))) { // Set the data to a dummy value. // This is needed to be able to distinguish between the value not // being set, and being set to an empty region. w->setData(WindowBlurBehindRole, 1); } else w->setData(WindowBlurBehindRole, region); } void BlurEffect::slotWindowAdded(EffectWindow *w) { KWayland::Server::SurfaceInterface *surf = w->surface(); if (surf) { - windows[w].blurChangedConnection = connect(surf, &KWayland::Server::SurfaceInterface::blurChanged, this, [this, w] () { - + windowBlurChangedConnections[w] = connect(surf, &KWayland::Server::SurfaceInterface::blurChanged, this, [this, w] () { if (w) { updateBlurRegion(w); } }); } + updateBlurRegion(w); } void BlurEffect::slotWindowDeleted(EffectWindow *w) { - if (windows.contains(w)) { - disconnect(windows[w].blurChangedConnection); - windows.remove(w); + if (windowBlurChangedConnections.contains(w)) { + disconnect(windowBlurChangedConnections[w]); + windowBlurChangedConnections.remove(w); } } void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom) { if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) { updateBlurRegion(w); - CacheEntry it = windows.find(w); - if (it != windows.end()) { - const QRect screen = effects->virtualScreenGeometry(); - it->damagedRegion = expand(blurRegion(w).translated(w->pos())) & screen; - } } } bool BlurEffect::enabledByDefault() { GLPlatform *gl = GLPlatform::instance(); if (gl->isIntel() && gl->chipClass() < SandyBridge) return false; if (gl->isSoftwareEmulation()) { return false; } return true; } bool BlurEffect::supported() { bool supported = effects->isOpenGLCompositing() && GLRenderTarget::supported(); if (supported) { int maxTexSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); const QSize screenSize = effects->virtualScreenSize(); if (screenSize.width() > maxTexSize || screenSize.height() > maxTexSize) supported = false; } return supported; } QRect BlurEffect::expand(const QRect &rect) const { - const int radius = shader->radius(); - return rect.adjusted(-radius, -radius, radius, radius); + return rect.adjusted(-m_expandSize, -m_expandSize, m_expandSize, m_expandSize); } QRegion BlurEffect::expand(const QRegion ®ion) const { QRegion expanded; - foreach (const QRect & rect, region.rects()) { + for (const QRect &rect : region) { expanded += expand(rect); } return expanded; } QRegion BlurEffect::blurRegion(const EffectWindow *w) const { QRegion region; const QVariant value = w->data(WindowBlurBehindRole); if (value.isValid()) { const QRegion appRegion = qvariant_cast(value); if (!appRegion.isEmpty()) { if (w->decorationHasAlpha() && effects->decorationSupportsBlurBehind()) { region = w->shape(); region -= w->decorationInnerRect(); } region |= appRegion.translated(w->contentsRect().topLeft()) & w->decorationInnerRect(); } else { // An empty region means that the blur effect should be enabled // for the whole window. region = w->shape(); } } else if (w->decorationHasAlpha() && effects->decorationSupportsBlurBehind()) { // If the client hasn't specified a blur region, we'll only enable // the effect behind the decoration. region = w->shape(); region -= w->decorationInnerRect(); } return region; } -void BlurEffect::uploadRegion(QVector2D *&map, const QRegion ®ion) +void BlurEffect::uploadRegion(QVector2D *&map, const QRegion ®ion, const int downSampleIterations) { - foreach (const QRect &r, region.rects()) { - const QVector2D topLeft(r.x(), r.y()); - const QVector2D topRight(r.x() + r.width(), r.y()); - const QVector2D bottomLeft(r.x(), r.y() + r.height()); - const QVector2D bottomRight(r.x() + r.width(), r.y() + r.height()); - - // First triangle - *(map++) = topRight; - *(map++) = topLeft; - *(map++) = bottomLeft; - - // Second triangle - *(map++) = bottomLeft; - *(map++) = bottomRight; - *(map++) = topRight; + for (int i = 0; i <= downSampleIterations; i++) { + const int divisionRatio = (1 << i); + + for (const QRect &r : region) { + const QVector2D topLeft( r.x() / divisionRatio, r.y() / divisionRatio); + const QVector2D topRight( (r.x() + r.width()) / divisionRatio, r.y() / divisionRatio); + const QVector2D bottomLeft( r.x() / divisionRatio, (r.y() + r.height()) / divisionRatio); + const QVector2D bottomRight((r.x() + r.width()) / divisionRatio, (r.y() + r.height()) / divisionRatio); + + // First triangle + *(map++) = topRight; + *(map++) = topLeft; + *(map++) = bottomLeft; + + // Second triangle + *(map++) = bottomLeft; + *(map++) = bottomRight; + *(map++) = topRight; + } } } -void BlurEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion &horizontal, const QRegion &vertical) +void BlurEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion &blurRegion, const QRegion &windowRegion) { - const int vertexCount = (horizontal.rectCount() + vertical.rectCount()) * 6; + const int vertexCount = ((blurRegion.rectCount() * (m_downSampleIterations + 1)) + windowRegion.rectCount()) * 6; + if (!vertexCount) return; QVector2D *map = (QVector2D *) vbo->map(vertexCount * sizeof(QVector2D)); - uploadRegion(map, horizontal); - uploadRegion(map, vertical); + + uploadRegion(map, blurRegion, m_downSampleIterations); + uploadRegion(map, windowRegion, 0); + vbo->unmap(); const GLVertexAttrib layout[] = { { VA_Position, 2, GL_FLOAT, 0 }, { VA_TexCoord, 2, GL_FLOAT, 0 } }; vbo->setAttribLayout(layout, 2, sizeof(QVector2D)); } void BlurEffect::prePaintScreen(ScreenPrePaintData &data, int time) { m_damagedArea = QRegion(); m_paintedArea = QRegion(); m_currentBlur = QRegion(); effects->prePaintScreen(data, time); } void BlurEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { // this effect relies on prePaintWindow being called in the bottom to top order effects->prePaintWindow(w, data, time); if (!w->isPaintingEnabled()) { return; } - if (!shader || !shader->isValid()) { + if (!m_shader || !m_shader->isValid()) { return; } // to blur an area partially we have to shrink the opaque area of a window QRegion newClip; const QRegion oldClip = data.clip; - const int radius = shader->radius(); - foreach (const QRect& rect, data.clip.rects()) { - newClip |= rect.adjusted(radius,radius,-radius,-radius); + for (const QRect &rect : data.clip) { + newClip |= rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize); } data.clip = newClip; const QRegion oldPaint = data.paint; // we don't have to blur a region we don't see m_currentBlur -= newClip; // if we have to paint a non-opaque part of this window that intersects with the - // currently blurred region (which is not cached) we have to redraw the whole region - if ((data.paint-oldClip).intersects(m_currentBlur)) { + // currently blurred region we have to redraw the whole region + if ((data.paint - oldClip).intersects(m_currentBlur)) { data.paint |= m_currentBlur; } // in case this window has regions to be blurred const QRect screen = effects->virtualScreenGeometry(); const QRegion blurArea = blurRegion(w).translated(w->pos()) & screen; - const QRegion expandedBlur = expand(blurArea) & screen; - - if (m_shouldCache && !w->isDeleted()) { - // we are caching the horizontally blurred background texture - - // if a window underneath the blurred area is damaged we have to - // update the cached texture - QRegion damagedCache; - CacheEntry it = windows.find(w); - if (it != windows.end() && !it->dropCache && - it->windowPos == w->pos() && - it->blurredBackground.size() == expandedBlur.boundingRect().size()) { - damagedCache = (expand(expandedBlur & m_damagedArea) | - (it->damagedRegion & data.paint)) & expandedBlur; - } else { - damagedCache = expandedBlur; - } - if (!damagedCache.isEmpty()) { - // This is the area of the blurry window which really can change. - const QRegion damagedArea = damagedCache & blurArea; - // In order to be able to recalculate this area we have to make sure the - // background area is painted before. - data.paint |= expand(damagedArea); - if (it != windows.end()) { - // In case we already have a texture cache mark the dirty regions invalid. - it->damagedRegion &= expandedBlur; - it->damagedRegion |= damagedCache; - // The valid part of the cache can be considered as being opaque - // as long as we don't need to update a bordering part - data.clip |= blurArea - expand(it->damagedRegion); - it->dropCache = false; - } - // we keep track of the "damage propagation" - m_damagedArea |= damagedArea; - // we have to check again whether we do not damage a blurred area - // of a window we do not cache - if (expandedBlur.intersects(m_currentBlur)) { - data.paint |= m_currentBlur; - } - } - } else { - // we are not caching the window - - // if this window or an window underneath the blurred area is painted again we have to - // blur everything - if (m_paintedArea.intersects(expandedBlur) || data.paint.intersects(blurArea)) { - data.paint |= expandedBlur; - // we keep track of the "damage propagation" - m_damagedArea |= expand(expandedBlur & m_damagedArea) & blurArea; - // we have to check again whether we do not damage a blurred area - // of a window we do not cache - if (expandedBlur.intersects(m_currentBlur)) { - data.paint |= m_currentBlur; - } + const QRegion expandedBlur = (w->isDock() ? blurArea : expand(blurArea)) & screen; + + // if this window or a window underneath the blurred area is painted again we have to + // blur everything + if (m_paintedArea.intersects(expandedBlur) || data.paint.intersects(blurArea)) { + data.paint |= expandedBlur; + // we keep track of the "damage propagation" + m_damagedArea |= (w->isDock() ? (expandedBlur & m_damagedArea) : expand(expandedBlur & m_damagedArea)) & blurArea; + // we have to check again whether we do not damage a blurred area + // of a window + if (expandedBlur.intersects(m_currentBlur)) { + data.paint |= m_currentBlur; } - - m_currentBlur |= expandedBlur; } + m_currentBlur |= expandedBlur; + // we don't consider damaged areas which are occluded and are not // explicitly damaged by this window m_damagedArea -= data.clip; m_damagedArea |= oldPaint; // in contrast to m_damagedArea does m_paintedArea keep track of all repainted areas m_paintedArea -= data.clip; m_paintedArea |= data.paint; } bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const { - if (!target->valid() || !shader || !shader->isValid()) + if (!m_renderTargetsValid || !m_shader || !m_shader->isValid()) return false; if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool()) return false; if (w->isDesktop()) return false; bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0); bool translated = data.xTranslation() || data.yTranslation(); if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBlurRole).toBool()) return false; bool blurBehindDecos = effects->decorationsHaveAlpha() && effects->decorationSupportsBlurBehind(); if (!w->hasAlpha() && w->opacity() >= 1.0 && !(blurBehindDecos && w->hasDecoration())) return false; return true; } void BlurEffect::drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { const QRect screen = GLRenderTarget::virtualScreenGeometry(); if (shouldBlur(w, mask, data)) { QRegion shape = region & blurRegion(w).translated(w->pos()) & screen; // let's do the evil parts - someone wants to blur behind a transformed window const bool translated = data.xTranslation() || data.yTranslation(); const bool scaled = data.xScale() != 1 || data.yScale() != 1; if (scaled) { QPoint pt = shape.boundingRect().topLeft(); QVector shapeRects = shape.rects(); shape = QRegion(); // clear foreach (QRect r, shapeRects) { r.moveTo(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(), pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation()); r.setWidth(r.width() * data.xScale()); r.setHeight(r.height() * data.yScale()); shape |= r; } shape = shape & region; //Only translated, not scaled } else if (translated) { shape = shape.translated(data.xTranslation(), data.yTranslation()); shape = shape & region; } if (!shape.isEmpty()) { - if (w->isFullScreen() && GLRenderTarget::blitSupported() && m_simpleShader->isValid() - && !GLPlatform::instance()->supports(LimitedNPOT) && shape.boundingRect() == w->geometry()) { - doSimpleBlur(w, data.opacity(), data.screenProjectionMatrix()); - } else if (m_shouldCache && !translated && !w->isDeleted()) { - doCachedBlur(w, region, data.opacity(), data.screenProjectionMatrix()); + if (m_useSimpleBlur && + w->isFullScreen() && + GLRenderTarget::blitSupported() && + m_simpleShader->isValid() && + !GLPlatform::instance()->supports(LimitedNPOT) && + shape.boundingRect() == w->geometry()) { + doSimpleBlur(w, data.opacity(), data.screenProjectionMatrix()); } else { - doBlur(shape, screen, data.opacity(), data.screenProjectionMatrix()); + doBlur(shape, screen, data.opacity(), data.screenProjectionMatrix(), w->isDock()); } } } // Draw the window over the blurred area effects->drawWindow(w, mask, region, data); } void BlurEffect::paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity) { const QRect screen = effects->virtualScreenGeometry(); - bool valid = target->valid() && shader && shader->isValid(); - QRegion shape = frame->geometry().adjusted(-5, -5, 5, 5) & screen; + bool valid = m_renderTargetsValid && m_shader && m_shader->isValid(); + + QRegion shape = frame->geometry().adjusted(-borderSize, -borderSize, borderSize, borderSize) & screen; + if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) { - doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix()); + doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix(), false); } effects->paintEffectFrame(frame, region, opacity, frameOpacity); } void BlurEffect::doSimpleBlur(EffectWindow *w, const float opacity, const QMatrix4x4 &screenProjection) { // The fragment shader uses a LOD bias of 1.75, so we need 3 mipmap levels. GLTexture blurTexture = GLTexture(GL_RGBA8, w->size(), 3); blurTexture.setFilter(GL_LINEAR_MIPMAP_LINEAR); blurTexture.setWrapMode(GL_CLAMP_TO_EDGE); - target->attachTexture(blurTexture); - target->blitFromFramebuffer(w->geometry(), QRect(QPoint(0, 0), w->size())); + m_simpleTarget->attachTexture(blurTexture); + m_simpleTarget->blitFromFramebuffer(w->geometry(), QRect(QPoint(0, 0), w->size())); + m_simpleTarget->detachTexture(); // Unmodified base image ShaderBinder binder(m_simpleShader); QMatrix4x4 mvp = screenProjection; mvp.translate(w->x(), w->y()); m_simpleShader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_simpleShader->setUniform("u_alphaProgress", opacity); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); blurTexture.bind(); blurTexture.generateMipmaps(); blurTexture.render(infiniteRegion(), w->geometry()); blurTexture.unbind(); glDisable(GL_BLEND); } -void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection) +void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection, bool isDock) { - const QRegion expanded = expand(shape) & screen; - const QRect r = expanded.boundingRect(); + QRegion expandedBlurRegion = expand(shape) & expand(screen); - // Upload geometry for the horizontal and vertical passes + // Upload geometry for the down and upsample iterations GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); - uploadGeometry(vbo, expanded, shape); + uploadGeometry(vbo, expandedBlurRegion, shape); vbo->bindArrays(); - const qreal scale = GLRenderTarget::virtualScreenScale(); - - // Create a scratch texture and copy the area in the back buffer that we're - // going to blur into it - // for HIGH DPI scratch is captured in native resolution, it is then implicitly downsampled - // when rendering into tex - GLTexture scratch(GL_RGBA8, r.width() * scale, r.height() * scale); - scratch.setFilter(GL_LINEAR); - scratch.setWrapMode(GL_CLAMP_TO_EDGE); - scratch.bind(); - - const QRect sg = GLRenderTarget::virtualScreenGeometry(); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (r.x() - sg.x()) * scale, (sg.height() - sg.y() - r.y() - r.height()) * scale, - scratch.width(), scratch.height()); - - // Draw the texture on the offscreen framebuffer object, while blurring it horizontally - target->attachTexture(tex); - GLRenderTarget::pushRenderTarget(target); - - shader->bind(); - shader->setDirection(Qt::Horizontal); - shader->setPixelDistance(1.0 / r.width()); - - QMatrix4x4 modelViewProjectionMatrix; - modelViewProjectionMatrix.ortho(0, tex.width(), tex.height(), 0 , 0, 65535); - shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); - - // Set up the texture matrix to transform from screen coordinates - // to texture coordinates. - QMatrix4x4 textureMatrix; - textureMatrix.scale(1.0 / r.width(), -1.0 / r.height(), 1); - textureMatrix.translate(-r.x(), (-r.height() - r.y()), 0); - shader->setTextureMatrix(textureMatrix); - - vbo->draw(GL_TRIANGLES, 0, expanded.rectCount() * 6); + /* + * If the window is a dock or panel we avoid the "extended blur" effect. + * Extended blur is when windows that are not under the blurred area affect + * the final blur result. + * We want to avoid this on panels, because it looks really weird and ugly + * when maximized windows or windows near the panel affect the dock blur. + */ + isDock ? m_renderTextures.last().bind() : m_renderTextures[0].bind(); + + QRect copyRect = expandedBlurRegion.boundingRect() & screen; + glCopyTexSubImage2D( + GL_TEXTURE_2D, + 0, + copyRect.x(), + effects->virtualScreenSize().height() - copyRect.y() - copyRect.height(), + copyRect.x(), + effects->virtualScreenSize().height() - copyRect.y() - copyRect.height(), + copyRect.width(), + copyRect.height() + ); - GLRenderTarget::popRenderTarget(); - scratch.unbind(); - scratch.discard(); + GLRenderTarget::pushRenderTargets(m_renderTargetStack); + int blurRectCount = expandedBlurRegion.rectCount() * 6; - // Now draw the horizontally blurred area back to the backbuffer, while - // blurring it vertically and clipping it to the window shape. - tex.bind(); + if (isDock) { + copyScreenSampleTexture(vbo, blurRectCount, shape, screen.size(), screenProjection); + } else { + // Remove the m_renderTargets[0] from the top of the stack that we will not use + GLRenderTarget::popRenderTarget(); + } - shader->setDirection(Qt::Vertical); - shader->setPixelDistance(1.0 / tex.height()); + downSampleTexture(vbo, blurRectCount); + upSampleTexture(vbo, blurRectCount); // Modulate the blurred texture with the window opacity if the window isn't opaque if (opacity < 1.0) { glEnable(GL_BLEND); #if 1 // bow shape, always above y = x float o = 1.0f-opacity; o = 1.0f - o*o; #else // sigmoid shape, above y = x for x > 0.5, below y = x for x < 0.5 float o = 2.0f*opacity - 1.0f; o = 0.5f + o / (1.0f + qAbs(o)); #endif glBlendColor(0, 0, 0, o); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - // Set the up the texture matrix to transform from screen coordinates - // to texture coordinates. - textureMatrix.setToIdentity(); - textureMatrix.scale(1.0 / tex.width(), -1.0 / tex.height(), 1); - textureMatrix.translate(0, -tex.height(), 0); - shader->setTextureMatrix(textureMatrix); - shader->setModelViewProjectionMatrix(screenProjection); + //Final upscale to the screen + m_shader->bind(BlurShader::UpSampleType); + m_shader->setOffset(m_offset); - vbo->draw(GL_TRIANGLES, expanded.rectCount() * 6, shape.rectCount() * 6); - vbo->unbindArrays(); + m_shader->setModelViewProjectionMatrix(screenProjection); + m_shader->setTargetSize(m_renderTextures[0].size()); + + //Copy the image from this texture + m_renderTextures[1].bind(); + + //Render to the screen + vbo->draw(GL_TRIANGLES, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6); if (opacity < 1.0) { glDisable(GL_BLEND); } - tex.unbind(); - shader->unbind(); + vbo->unbindArrays(); + m_shader->unbind(); } -void BlurEffect::doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity, const QMatrix4x4 &screenProjection) +void BlurEffect::downSampleTexture(GLVertexBuffer *vbo, int blurRectCount) { - const QRect screen = effects->virtualScreenGeometry(); - const QRegion blurredRegion = blurRegion(w).translated(w->pos()) & screen; - const QRegion expanded = expand(blurredRegion) & screen; - const QRect r = expanded.boundingRect(); - - // The background texture we get is only partially valid. - - CacheEntry it = windows.find(w); - if (it == windows.end()) { - BlurWindowInfo bwi; - bwi.blurredBackground = GLTexture(GL_RGBA8, r.width(),r.height()); - bwi.damagedRegion = expanded; - bwi.dropCache = false; - bwi.windowPos = w->pos(); - it = windows.insert(w, bwi); - } else if (it->blurredBackground.size() != r.size()) { - it->blurredBackground = GLTexture(GL_RGBA8, r.width(),r.height()); - it->dropCache = false; - it->windowPos = w->pos(); - } else if (it->windowPos != w->pos()) { - it->dropCache = false; - it->windowPos = w->pos(); - } - - GLTexture targetTexture = it->blurredBackground; - targetTexture.setFilter(GL_LINEAR); - targetTexture.setWrapMode(GL_CLAMP_TO_EDGE); - shader->bind(); - QMatrix4x4 textureMatrix; QMatrix4x4 modelViewProjectionMatrix; - /** - * Which part of the background texture can be updated ? - * - * Well this is a rather difficult question. We kind of rely on the fact, that - * we need a bigger background region being painted before, more precisely if we want to - * blur region A we need the background region expand(A). This business logic is basically - * done in prePaintWindow: - * data.paint |= expand(damagedArea); - * - * Now "data.paint" gets clipped and becomes what we receive as the "region" variable - * in this function. In theory there is now only one function that does this clipping - * and this is paintSimpleScreen. The clipping has the effect that "damagedRegion" - * is no longer a subset of "region" and we cannot fully validate the cache within one - * rendering pass. If we would now update the "damageRegion & region" part of the cache - * we would wrongly update the part of the cache that is next to the "region" border and - * which lies within "damagedRegion", just because we cannot assume that the framebuffer - * outside of "region" is valid. Therefore the maximal damaged region of the cache that can - * be repainted is given by: - * validUpdate = damagedRegion - expand(damagedRegion - region); - * - * Now you may ask what is with the rest of "damagedRegion & region" that is not part - * of "validUpdate" but also might end up on the screen. Well under the assumption - * that only the occlusion culling can shrink "data.paint", we can control this by reducing - * the opaque area of every window by a margin of the blurring radius (c.f. prePaintWindow). - * This way we are sure that this area is overpainted by a higher opaque window. - * - * Apparently paintSimpleScreen is not the only function that can influence "region". - * In fact every effect's paintWindow that is called before Blur::paintWindow - * can do so (e.g. SlidingPopups). Hence we have to make the compromise that we update - * "damagedRegion & region" of the cache but only mark "validUpdate" as valid. - **/ - const QRegion damagedRegion = it->damagedRegion; - const QRegion updateBackground = damagedRegion & region; - const QRegion validUpdate = damagedRegion - expand(damagedRegion - region); + m_shader->bind(BlurShader::DownSampleType); + m_shader->setOffset(m_offset); - const QRegion horizontal = validUpdate.isEmpty() ? QRegion() : (updateBackground & screen); - const QRegion vertical = blurredRegion & region; + for (int i = 1; i <= m_downSampleIterations; i++) { + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); - const int horizontalOffset = 0; - const int horizontalCount = horizontal.rectCount() * 6; + m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + m_shader->setTargetSize(m_renderTextures[i].size()); - const int verticalOffset = horizontalCount; - const int verticalCount = vertical.rectCount() * 6; + //Copy the image from this texture + m_renderTextures[i - 1].bind(); - GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); - uploadGeometry(vbo, horizontal, vertical); - - vbo->bindArrays(); - - if (!validUpdate.isEmpty()) { - const QRect updateRect = (expand(updateBackground) & expanded).boundingRect(); - // First we have to copy the background from the frontbuffer - // into a scratch texture (in this case "tex"). - tex.bind(); + vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); + GLRenderTarget::popRenderTarget(); + } - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, updateRect.x(), effects->virtualScreenSize().height() - updateRect.y() - updateRect.height(), - updateRect.width(), updateRect.height()); + m_shader->unbind(); +} - // Draw the texture on the offscreen framebuffer object, while blurring it horizontally - target->attachTexture(targetTexture); - GLRenderTarget::pushRenderTarget(target); +void BlurEffect::upSampleTexture(GLVertexBuffer *vbo, int blurRectCount) +{ + QMatrix4x4 modelViewProjectionMatrix; - shader->setDirection(Qt::Horizontal); - shader->setPixelDistance(1.0 / tex.width()); + m_shader->bind(BlurShader::UpSampleType); + m_shader->setOffset(m_offset); - modelViewProjectionMatrix.ortho(0, r.width(), r.height(), 0 , 0, 65535); - modelViewProjectionMatrix.translate(-r.x(), -r.y(), 0); - shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + for (int i = m_downSampleIterations - 1; i > 0; i--) { + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); - // Set up the texture matrix to transform from screen coordinates - // to texture coordinates. - textureMatrix.scale(1.0 / tex.width(), -1.0 / tex.height(), 1); - textureMatrix.translate(-updateRect.x(), -updateRect.height() - updateRect.y(), 0); - shader->setTextureMatrix(textureMatrix); + m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + m_shader->setTargetSize(m_renderTextures[i].size()); - vbo->draw(GL_TRIANGLES, horizontalOffset, horizontalCount); + //Copy the image from this texture + m_renderTextures[i + 1].bind(); + vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); GLRenderTarget::popRenderTarget(); - tex.unbind(); - // mark the updated region as valid - it->damagedRegion -= validUpdate; } - // Now draw the horizontally blurred area back to the backbuffer, while - // blurring it vertically and clipping it to the window shape. - targetTexture.bind(); - - shader->setDirection(Qt::Vertical); - shader->setPixelDistance(1.0 / targetTexture.height()); - - // Modulate the blurred texture with the window opacity if the window isn't opaque - if (opacity < 1.0) { - glEnable(GL_BLEND); - glBlendColor(0, 0, 0, opacity); - glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); - } + m_shader->unbind(); +} - shader->setModelViewProjectionMatrix(screenProjection); +void BlurEffect::copyScreenSampleTexture(GLVertexBuffer *vbo, int blurRectCount, QRegion blurShape, QSize screenSize, QMatrix4x4 screenProjection) +{ + m_shader->bind(BlurShader::CopySampleType); - // Set the up the texture matrix to transform from screen coordinates - // to texture coordinates. - textureMatrix.setToIdentity(); - textureMatrix.scale(1.0 / targetTexture.width(), -1.0 / targetTexture.height(), 1); - textureMatrix.translate(-r.x(), -targetTexture.height() - r.y(), 0); - shader->setTextureMatrix(textureMatrix); + m_shader->setModelViewProjectionMatrix(screenProjection); + m_shader->setTargetSize(screenSize); - vbo->draw(GL_TRIANGLES, verticalOffset, verticalCount); - vbo->unbindArrays(); + /* + * This '1' sized adjustment is necessary do avoid windows affecting the blur that are + * right next to this window. + */ + m_shader->setBlurRect(blurShape.boundingRect().adjusted(1, 1, -1, -1), screenSize); - if (opacity < 1.0) { - glDisable(GL_BLEND); - } - - targetTexture.unbind(); - shader->unbind(); -} + vbo->draw(GL_TRIANGLES, 0, blurRectCount); + GLRenderTarget::popRenderTarget(); -int BlurEffect::blurRadius() const -{ - if (!shader) { - return 0; - } - return shader->radius(); + m_shader->unbind(); } } // namespace KWin diff --git a/effects/blur/blur.h b/effects/blur/blur.h index 85a7b4ae6..2c865187f 100644 --- a/effects/blur/blur.h +++ b/effects/blur/blur.h @@ -1,128 +1,146 @@ /* * Copyright © 2010 Fredrik Höglund + * Copyright © 2018 Alex Nemeth * * 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; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef BLUR_H #define BLUR_H #include #include #include #include #include +#include namespace KWayland { namespace Server { class BlurManagerInterface; } } namespace KWin { +static const int borderSize = 5; + class BlurShader; class BlurEffect : public KWin::Effect { Q_OBJECT - Q_PROPERTY(int blurRadius READ blurRadius) - Q_PROPERTY(bool cacheTexture READ isCacheTexture) + public: BlurEffect(); ~BlurEffect(); static bool supported(); static bool enabledByDefault(); void reconfigure(ReconfigureFlags flags); void prePaintScreen(ScreenPrePaintData &data, int time); void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); void drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data); void paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity); - // for dynamic setting extraction - int blurRadius() const; - bool isCacheTexture() const { - return m_shouldCache; - } virtual bool provides(Feature feature); int requestedEffectChainPosition() const override { return 75; } public Q_SLOTS: void slotWindowAdded(KWin::EffectWindow *w); void slotWindowDeleted(KWin::EffectWindow *w); void slotPropertyNotify(KWin::EffectWindow *w, long atom); void slotScreenGeometryChanged(); private: - void updateTexture(); QRect expand(const QRect &rect) const; QRegion expand(const QRegion ®ion) const; + bool renderTargetsValid() const; + void deleteFBOs(); + void initBlurStrengthValues(); + void updateTexture(); QRegion blurRegion(const EffectWindow *w) const; bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const; void updateBlurRegion(EffectWindow *w) const; void doSimpleBlur(EffectWindow *w, const float opacity, const QMatrix4x4 &screenProjection); - void doBlur(const QRegion &shape, const QRect &screen, const float opacity, const QMatrix4x4 &screenProjection); - void doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity, const QMatrix4x4 &screenProjection); - void uploadRegion(QVector2D *&map, const QRegion ®ion); - void uploadGeometry(GLVertexBuffer *vbo, const QRegion &horizontal, const QRegion &vertical); + void doBlur(const QRegion &shape, const QRect &screen, const float opacity, const QMatrix4x4 &screenProjection, bool isDock); + void uploadRegion(QVector2D *&map, const QRegion ®ion, const int downSampleIterations); + void uploadGeometry(GLVertexBuffer *vbo, const QRegion &blurRegion, const QRegion &windowRegion); + + void downSampleTexture(GLVertexBuffer *vbo, int blurRectCount); + void upSampleTexture(GLVertexBuffer *vbo, int blurRectCount); + void copyScreenSampleTexture(GLVertexBuffer *vbo, int blurRectCount, QRegion blurShape, QSize screenSize, QMatrix4x4 screenProjection); private: - BlurShader *shader; GLShader *m_simpleShader; - GLRenderTarget *target = nullptr; - GLTexture tex; + GLRenderTarget *m_simpleTarget; + + BlurShader *m_shader; + QVector m_renderTargets; + QVector m_renderTextures; + QStack m_renderTargetStack; + bool m_renderTargetsValid; long net_wm_blur_region; QRegion m_damagedArea; // keeps track of the area which has been damaged (from bottom to top) QRegion m_paintedArea; // actually painted area which is greater than m_damagedArea - QRegion m_currentBlur; // keeps track of the currently blured area of non-caching windows(from bottom to top) - bool m_shouldCache; - - struct BlurWindowInfo { - GLTexture blurredBackground; // keeps the horizontally blurred background - QRegion damagedRegion; - QPoint windowPos; - bool dropCache; - QMetaObject::Connection blurChangedConnection; + QRegion m_currentBlur; // keeps track of the currently blured area of the windows(from bottom to top) + bool m_useSimpleBlur; + + int m_downSampleIterations; // number of times the texture will be downsized to half size + int m_offset; + int m_expandSize; + + struct OffsetStruct { + float minOffset; + float maxOffset; + int expandSize; + }; + + QVector blurOffsets; + + struct BlurValuesStruct { + int iteration; + float offset; }; - QHash< const EffectWindow*, BlurWindowInfo > windows; - typedef QHash::iterator CacheEntry; + QVector blurStrengthValues; + + QMap windowBlurChangedConnections; KWayland::Server::BlurManagerInterface *m_blurManager = nullptr; }; inline bool BlurEffect::provides(Effect::Feature feature) { if (feature == Blur) { return true; } return KWin::Effect::provides(feature); } } // namespace KWin #endif diff --git a/effects/blur/blur.kcfg b/effects/blur/blur.kcfg index 6fd2e98c4..9fc659d3a 100644 --- a/effects/blur/blur.kcfg +++ b/effects/blur/blur.kcfg @@ -1,15 +1,15 @@ - - 12 + + 5 - - true + + false diff --git a/effects/blur/blur_config.ui b/effects/blur/blur_config.ui index 933d0d0a8..068637ca0 100644 --- a/effects/blur/blur_config.ui +++ b/effects/blur/blur_config.ui @@ -1,107 +1,107 @@ BlurEffectConfig 0 0 - 396 - 103 + 480 + 95 - + Strength of the effect: Qt::Horizontal QSizePolicy::Fixed 20 20 - + Light - + 1 - 14 + 15 - 2 + 1 - 2 + 1 - 12 + 5 Qt::Horizontal QSlider::TicksBelow - + Strong - - - - + - Save intermediate rendering results. + Use simple fullscreen blur + + + false Qt::Vertical 0 0 diff --git a/effects/blur/blurshader.cpp b/effects/blur/blurshader.cpp index 579e5cd3b..c6957a0d9 100644 --- a/effects/blur/blurshader.cpp +++ b/effects/blur/blurshader.cpp @@ -1,306 +1,384 @@ /* * Copyright © 2010 Fredrik Höglund + * Copyright © 2018 Alex Nemeth * * 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; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "blurshader.h" #include +#include "kwinglutils.h" #include #include #include #include #include #include using namespace KWin; BlurShader::BlurShader() - : mRadius(0), mValid(false) + : mValid(false) { } BlurShader::~BlurShader() { } BlurShader *BlurShader::create() { return new GLSLBlurShader(); } -void BlurShader::setRadius(int radius) -{ - const int r = qMax(radius, 2); +// ---------------------------------------------------------------------------- + - if (mRadius != r) { - mRadius = r; - reset(); - init(); - } -} -void BlurShader::setDirection(Qt::Orientation direction) +GLSLBlurShader::GLSLBlurShader() { - mDirection = direction; + init(); } -float BlurShader::gaussian(float x, float sigma) const +GLSLBlurShader::~GLSLBlurShader() { - return (1.0 / std::sqrt(2.0 * M_PI) * sigma) - * std::exp(-((x * x) / (2.0 * sigma * sigma))); + reset(); } -QList BlurShader::gaussianKernel() const +void GLSLBlurShader::reset() { - int size = qMin(mRadius | 1, maxKernelSize()); - if (!(size & 0x1)) - size -= 1; + delete m_shaderDownsample; + m_shaderDownsample = nullptr; - QList kernel; - const int center = size / 2; - const qreal sigma = (size - 1) / 2.5; + delete m_shaderUpsample; + m_shaderUpsample = nullptr; - kernel << KernelValue(0.0, gaussian(0.0, sigma)); - float total = kernel[0].g; + delete m_shaderCopysample; + m_shaderCopysample = nullptr; + + setIsValid(false); +} - for (int x = 1; x <= center; x++) { - const float fx = (x - 1) * 2 + 1.5; - const float g1 = gaussian(fx - 0.5, sigma); - const float g2 = gaussian(fx + 0.5, sigma); +void GLSLBlurShader::setModelViewProjectionMatrix(const QMatrix4x4 &matrix) +{ + if (!isValid()) + return; - // Offset taking the contribution of both pixels into account - const float offset = .5 - g1 / (g1 + g2); + switch (m_activeSampleType) { + case CopySampleType: + if (matrix == m_matrixCopysample) + return; - kernel << KernelValue(fx + offset, g1 + g2); - kernel << KernelValue(-(fx + offset), g1 + g2); + m_matrixCopysample = matrix; + m_shaderCopysample->setUniform(m_mvpMatrixLocationCopysample, matrix); + break; - total += (g1 + g2) * 2; - } + case UpSampleType: + if (matrix == m_matrixUpsample) + return; - qSort(kernel); + m_matrixUpsample = matrix; + m_shaderUpsample->setUniform(m_mvpMatrixLocationUpsample, matrix); + break; - // Normalize the kernel - for (int i = 0; i < kernel.count(); i++) - kernel[i].g /= total; + case DownSampleType: + if (matrix == m_matrixDownsample) + return; - return kernel; + m_matrixDownsample = matrix; + m_shaderDownsample->setUniform(m_mvpMatrixLocationDownsample, matrix); + break; + } } +void GLSLBlurShader::setOffset(float offset) +{ + if (!isValid()) + return; + switch (m_activeSampleType) { + case UpSampleType: + if (offset == m_offsetUpsample) + return; -// ---------------------------------------------------------------------------- - + m_offsetUpsample = offset; + m_shaderUpsample->setUniform(m_offsetLocationUpsample, offset); + break; + case DownSampleType: + if (offset == m_offsetDownsample) + return; -GLSLBlurShader::GLSLBlurShader() - : BlurShader(), shader(NULL) -{ + m_offsetDownsample = offset; + m_shaderDownsample->setUniform(m_offsetLocationDownsample, offset); + break; + } } -GLSLBlurShader::~GLSLBlurShader() +void GLSLBlurShader::setTargetSize(QSize renderTextureSize) { - reset(); -} + if (!isValid()) + return; -void GLSLBlurShader::reset() -{ - delete shader; - shader = NULL; + QVector2D texSize = QVector2D(renderTextureSize.width(), renderTextureSize.height()); - setIsValid(false); -} + switch (m_activeSampleType) { + case CopySampleType: + if (renderTextureSize == m_renderTextureSizeCopysample) + return; -void GLSLBlurShader::setPixelDistance(float val) -{ - if (!isValid()) - return; + m_renderTextureSizeCopysample = renderTextureSize; + m_shaderCopysample->setUniform(m_renderTextureSizeLocationCopysample, texSize); + break; - QVector2D pixelSize(0.0, 0.0); - if (direction() == Qt::Horizontal) - pixelSize.setX(val); - else - pixelSize.setY(val); + case UpSampleType: + if (renderTextureSize == m_renderTextureSizeUpsample) + return; - shader->setUniform(pixelSizeLocation, pixelSize); -} + m_renderTextureSizeUpsample = renderTextureSize; + m_shaderUpsample->setUniform(m_renderTextureSizeLocationUpsample, texSize); + m_shaderUpsample->setUniform(m_halfpixelLocationUpsample, QVector2D(0.5 / texSize.x(), 0.5 / texSize.y())); + break; -void GLSLBlurShader::setTextureMatrix(const QMatrix4x4 &matrix) -{ - if (!isValid()) - return; + case DownSampleType: + if (renderTextureSize == m_renderTextureSizeDownsample) + return; - shader->setUniform(textureMatrixLocation, matrix); + m_renderTextureSizeDownsample = renderTextureSize; + m_shaderDownsample->setUniform(m_renderTextureSizeLocationDownsample, texSize); + m_shaderDownsample->setUniform(m_halfpixelLocationDownsample, QVector2D(0.5 / texSize.x(), 0.5 / texSize.y())); + break; + } } -void GLSLBlurShader::setModelViewProjectionMatrix(const QMatrix4x4 &matrix) +void GLSLBlurShader::setBlurRect(QRect blurRect, QSize screenSize) { - if (!isValid()) + if (!isValid() || blurRect == m_blurRectCopysample) return; - shader->setUniform(mvpMatrixLocation, matrix); + m_blurRectCopysample = blurRect; + + QVector4D rect = QVector4D( + blurRect.bottomLeft().x() / float(screenSize.width()), + 1.0 - blurRect.bottomLeft().y() / float(screenSize.height()), + blurRect.topRight().x() / float(screenSize.width()), + 1.0 - blurRect.topRight().y() / float(screenSize.height()) + ); + + m_shaderCopysample->setUniform(m_blurRectLocationCopysample, rect); } -void GLSLBlurShader::bind() +void GLSLBlurShader::bind(SampleType sampleType) { if (!isValid()) return; - ShaderManager::instance()->pushShader(shader); + switch (sampleType) { + case CopySampleType: + ShaderManager::instance()->pushShader(m_shaderCopysample); + break; + + case UpSampleType: + ShaderManager::instance()->pushShader(m_shaderUpsample); + break; + + case DownSampleType: + ShaderManager::instance()->pushShader(m_shaderDownsample); + break; + } + + m_activeSampleType = sampleType; } void GLSLBlurShader::unbind() { ShaderManager::instance()->popShader(); } -int GLSLBlurShader::maxKernelSize() const -{ - if (GLPlatform::instance()->isGLES()) { - // GL_MAX_VARYING_FLOATS not available in GLES - // querying for GL_MAX_VARYING_VECTORS crashes on nouveau - // using the minimum value of 8 - return 8 * 2; - } else { - int value; - glGetIntegerv(GL_MAX_VARYING_FLOATS, &value); - // Maximum number of vec4 varyings * 2 - // The code generator will pack two vec2's into each vec4. - return value / 2; - } -} - void GLSLBlurShader::init() { - QList kernel = gaussianKernel(); - const int size = kernel.size(); - const int center = size / 2; - - QList offsets; - for (int i = 0; i < kernel.size(); i += 2) { - QVector4D vec4(0, 0, 0, 0); - - vec4.setX(kernel[i].x); - vec4.setY(kernel[i].x); - - if (i < kernel.size() - 1) { - vec4.setZ(kernel[i + 1].x); - vec4.setW(kernel[i + 1].x); - } - - offsets << vec4; - } - const bool gles = GLPlatform::instance()->isGLES(); const bool glsl_140 = !gles && GLPlatform::instance()->glslVersion() >= kVersionNumber(1, 40); const bool core = glsl_140 || (gles && GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0)); QByteArray vertexSource; - QByteArray fragmentSource; + QByteArray fragmentDownSource; + QByteArray fragmentUpSource; + QByteArray fragmentCopySource; - const QByteArray attribute = core ? "in" : "attribute"; - const QByteArray varying_in = core ? (gles ? "in" : "noperspective in") : "varying"; - const QByteArray varying_out = core ? (gles ? "out" : "noperspective out") : "varying"; - const QByteArray texture2D = core ? "texture" : "texture2D"; - const QByteArray fragColor = core ? "fragColor" : "gl_FragColor"; + const QByteArray attribute = core ? "in" : "attribute"; + const QByteArray texture2D = core ? "texture" : "texture2D"; + const QByteArray fragColor = core ? "fragColor" : "gl_FragColor"; - // Vertex shader - // =================================================================== - QTextStream stream(&vertexSource); + QString glHeaderString; if (gles) { if (core) { - stream << "#version 300 es\n\n"; + glHeaderString += "#version 300 es\n\n"; } - stream << "precision highp float;\n"; + + glHeaderString += "precision highp float;\n"; } else if (glsl_140) { - stream << "#version 140\n\n"; + glHeaderString += "#version 140\n\n"; } - stream << "uniform mat4 modelViewProjectionMatrix;\n"; - stream << "uniform mat4 textureMatrix;\n"; - stream << "uniform vec2 pixelSize;\n\n"; - stream << attribute << " vec4 vertex;\n\n"; - stream << varying_out << " vec4 samplePos[" << std::ceil(size / 2.0) << "];\n"; - stream << "\n"; - stream << "void main(void)\n"; - stream << "{\n"; - stream << " vec4 center = vec4(textureMatrix * vertex).stst;\n"; - stream << " vec4 ps = pixelSize.stst;\n\n"; - for (int i = 0; i < offsets.size(); i++) { - stream << " samplePos[" << i << "] = center + ps * vec4(" - << offsets[i].x() << ", " << offsets[i].y() << ", " - << offsets[i].z() << ", " << offsets[i].w() << ");\n"; + QString glUniformString = "uniform sampler2D texUnit;\n" + "uniform float offset;\n" + "uniform vec2 renderTextureSize;\n" + "uniform vec2 halfpixel;\n"; + + if (core) { + glUniformString += "out vec4 fragColor;\n\n"; } - stream << "\n"; - stream << " gl_Position = modelViewProjectionMatrix * vertex;\n"; - stream << "}\n"; - stream.flush(); - // Fragment shader + + // Vertex shader // =================================================================== - QTextStream stream2(&fragmentSource); + QTextStream streamVert(&vertexSource); - if (gles) { - if (core) { - stream2 << "#version 300 es\n\n"; - } - stream2 << "precision highp float;\n"; - } else if (glsl_140) { - stream2 << "#version 140\n\n"; - } + streamVert << glHeaderString; + + streamVert << "uniform mat4 modelViewProjectionMatrix;\n"; + streamVert << attribute << " vec4 vertex;\n\n"; + streamVert << "\n"; + streamVert << "void main(void)\n"; + streamVert << "{\n"; + streamVert << " gl_Position = modelViewProjectionMatrix * vertex;\n"; + streamVert << "}\n"; + + streamVert.flush(); + + // Fragment shader (Dual Kawase Blur) - Downsample + // =================================================================== + QTextStream streamFragDown(&fragmentDownSource); + + streamFragDown << glHeaderString << glUniformString; + + streamFragDown << "void main(void)\n"; + streamFragDown << "{\n"; + streamFragDown << " vec2 uv = vec2(gl_FragCoord.xy / renderTextureSize);\n"; + streamFragDown << " \n"; + streamFragDown << " vec4 sum = " << texture2D << "(texUnit, uv) * 4.0;\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv - halfpixel.xy * offset);\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv + halfpixel.xy * offset);\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset);\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv - vec2(halfpixel.x, -halfpixel.y) * offset);\n"; + streamFragDown << " \n"; + streamFragDown << " " << fragColor << " = sum / 8.0;\n"; + streamFragDown << "}\n"; + + streamFragDown.flush(); + + // Fragment shader (Dual Kawase Blur) - Upsample + // =================================================================== + QTextStream streamFragUp(&fragmentUpSource); + + streamFragUp << glHeaderString << glUniformString; + + streamFragUp << "void main(void)\n"; + streamFragUp << "{\n"; + streamFragUp << " vec2 uv = vec2(gl_FragCoord.xy / renderTextureSize);\n"; + streamFragUp << " \n"; + streamFragUp << " vec4 sum = " << texture2D << "(texUnit, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(0.0, halfpixel.y * 2.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x * 2.0, 0.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(0.0, -halfpixel.y * 2.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " \n"; + streamFragUp << " " << fragColor << " = sum / 12.0;\n"; + streamFragUp << "}\n"; + + streamFragUp.flush(); + + // Fragment shader - Copy texture + // =================================================================== + QTextStream streamFragCopy(&fragmentCopySource); - stream2 << "uniform sampler2D texUnit;\n"; - stream2 << varying_in << " vec4 samplePos[" << std::ceil(size / 2.0) << "];\n\n"; + streamFragCopy << glHeaderString; - for (int i = 0; i <= center; i++) - stream2 << "const float kernel" << i << " = " << kernel[i].g << ";\n"; - stream2 << "\n"; + streamFragCopy << "uniform sampler2D texUnit;\n"; + streamFragCopy << "uniform vec2 renderTextureSize;\n"; + streamFragCopy << "uniform vec4 blurRect;\n"; if (core) - stream2 << "out vec4 fragColor;\n\n"; - - stream2 << "void main(void)\n"; - stream2 << "{\n"; - stream2 << " vec4 sum = " << texture2D << "(texUnit, samplePos[0].st) * kernel0;\n"; - for (int i = 1, j = -center + 1; i < size; i++, j++) - stream2 << " sum = sum + " << texture2D << "(texUnit, samplePos[" << i / 2 - << ((i % 2) ? "].pq)" : "].st)") << " * kernel" << center - qAbs(j) << ";\n"; - stream2 << " " << fragColor << " = sum;\n"; - stream2 << "}\n"; - stream2.flush(); - - shader = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentSource); - if (shader->isValid()) { - pixelSizeLocation = shader->uniformLocation("pixelSize"); - textureMatrixLocation = shader->uniformLocation("textureMatrix"); - mvpMatrixLocation = shader->uniformLocation("modelViewProjectionMatrix"); + streamFragCopy << "out vec4 fragColor;\n\n"; + + streamFragCopy << "void main(void)\n"; + streamFragCopy << "{\n"; + streamFragCopy << " vec2 uv = vec2(gl_FragCoord.xy / renderTextureSize);\n"; + streamFragCopy << " " << fragColor << " = " << texture2D << "(texUnit, clamp(uv, blurRect.xy, blurRect.zw));\n"; + streamFragCopy << "}\n"; + + streamFragCopy.flush(); + + + m_shaderDownsample = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentDownSource); + m_shaderUpsample = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentUpSource); + m_shaderCopysample = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentCopySource); + + bool areShadersValid = m_shaderDownsample->isValid() && m_shaderUpsample->isValid() && m_shaderCopysample->isValid(); + setIsValid(areShadersValid); + + if (areShadersValid) { + m_mvpMatrixLocationDownsample = m_shaderDownsample->uniformLocation("modelViewProjectionMatrix"); + m_offsetLocationDownsample = m_shaderDownsample->uniformLocation("offset"); + m_renderTextureSizeLocationDownsample = m_shaderDownsample->uniformLocation("renderTextureSize"); + m_halfpixelLocationDownsample = m_shaderDownsample->uniformLocation("halfpixel"); + + m_mvpMatrixLocationUpsample = m_shaderUpsample->uniformLocation("modelViewProjectionMatrix"); + m_offsetLocationUpsample = m_shaderUpsample->uniformLocation("offset"); + m_renderTextureSizeLocationUpsample = m_shaderUpsample->uniformLocation("renderTextureSize"); + m_halfpixelLocationUpsample = m_shaderUpsample->uniformLocation("halfpixel"); + + m_mvpMatrixLocationCopysample = m_shaderCopysample->uniformLocation("modelViewProjectionMatrix"); + m_renderTextureSizeLocationCopysample = m_shaderCopysample->uniformLocation("renderTextureSize"); + m_blurRectLocationCopysample = m_shaderCopysample->uniformLocation("blurRect"); QMatrix4x4 modelViewProjection; const QSize screenSize = effects->virtualScreenSize(); modelViewProjection.ortho(0, screenSize.width(), screenSize.height(), 0, 0, 65535); - ShaderManager::instance()->pushShader(shader); - shader->setUniform(textureMatrixLocation, QMatrix4x4()); - shader->setUniform(mvpMatrixLocation, modelViewProjection); + + //Add default values to the uniforms of the shaders + ShaderManager::instance()->pushShader(m_shaderDownsample); + m_shaderDownsample->setUniform(m_mvpMatrixLocationDownsample, modelViewProjection); + m_shaderDownsample->setUniform(m_offsetLocationDownsample, float(1.0)); + m_shaderDownsample->setUniform(m_renderTextureSizeLocationDownsample, QVector2D(1.0, 1.0)); + m_shaderDownsample->setUniform(m_halfpixelLocationDownsample, QVector2D(1.0, 1.0)); ShaderManager::instance()->popShader(); - } - setIsValid(shader->isValid()); + ShaderManager::instance()->pushShader(m_shaderUpsample); + m_shaderUpsample->setUniform(m_mvpMatrixLocationUpsample, modelViewProjection); + m_shaderUpsample->setUniform(m_offsetLocationUpsample, float(1.0)); + m_shaderUpsample->setUniform(m_renderTextureSizeLocationUpsample, QVector2D(1.0, 1.0)); + m_shaderUpsample->setUniform(m_halfpixelLocationUpsample, QVector2D(1.0, 1.0)); + ShaderManager::instance()->popShader(); + + ShaderManager::instance()->pushShader(m_shaderCopysample); + m_shaderCopysample->setUniform(m_mvpMatrixLocationCopysample, modelViewProjection); + m_shaderCopysample->setUniform(m_renderTextureSizeLocationCopysample, QVector2D(1.0, 1.0)); + m_shaderCopysample->setUniform(m_blurRectLocationCopysample, QVector4D(1.0, 1.0, 1.0, 1.0)); + ShaderManager::instance()->popShader(); + + m_activeSampleType = -1; + } } diff --git a/effects/blur/blurshader.h b/effects/blur/blurshader.h index 49e9c0b86..a69811a1f 100644 --- a/effects/blur/blurshader.h +++ b/effects/blur/blurshader.h @@ -1,120 +1,132 @@ /* * Copyright © 2010 Fredrik Höglund + * Copyright © 2018 Alex Nemeth * * 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; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef BLURSHADER_H #define BLURSHADER_H #include +#include +#include + class QMatrix4x4; namespace KWin { -struct KernelValue -{ - KernelValue() {} - KernelValue(float x, float g) : x(x), g(g) {} - bool operator < (const KernelValue &other) const { return x < other.x; } - - float x; - float g; -}; - class BlurShader { public: BlurShader(); virtual ~BlurShader(); static BlurShader *create(); bool isValid() const { return mValid; } - // Sets the radius in pixels - void setRadius(int radius); - int radius() const { - return mRadius; - } - - // Sets the blur direction - void setDirection(Qt::Orientation direction); - Qt::Orientation direction() const { - return mDirection; - } - - // Sets the distance between two pixels - virtual void setPixelDistance(float val) = 0; - virtual void setTextureMatrix(const QMatrix4x4 &matrix) = 0; virtual void setModelViewProjectionMatrix(const QMatrix4x4 &matrix) = 0; + virtual void setOffset(float offset) = 0; + virtual void setTargetSize(QSize renderTextureSize) = 0; + virtual void setBlurRect(QRect blurRect, QSize screenSize) = 0; + + enum SampleType { + DownSampleType, + UpSampleType, + CopySampleType + }; - virtual void bind() = 0; + virtual void bind(SampleType sampleType) = 0; virtual void unbind() = 0; protected: - float gaussian(float x, float sigma) const; - QList gaussianKernel() const; void setIsValid(bool value) { mValid = value; } virtual void init() = 0; virtual void reset() = 0; - virtual int maxKernelSize() const = 0; private: - int mRadius; - Qt::Orientation mDirection; bool mValid; }; // ---------------------------------------------------------------------------- class GLSLBlurShader : public BlurShader { public: GLSLBlurShader(); ~GLSLBlurShader(); - void setPixelDistance(float val); - void bind(); - void unbind(); - void setTextureMatrix(const QMatrix4x4 &matrix); - void setModelViewProjectionMatrix(const QMatrix4x4 &matrix); + void bind(SampleType sampleType) override final; + void unbind() override final; + void setModelViewProjectionMatrix(const QMatrix4x4 &matrix) override final; + void setOffset(float offset) override final; + void setTargetSize(QSize renderTextureSize) override final; + void setBlurRect(QRect blurRect, QSize screenSize) override final; protected: - void init(); - void reset(); - int maxKernelSize() const; + void init() override final; + void reset() override final; private: - GLShader *shader; - int mvpMatrixLocation; - int textureMatrixLocation; - int pixelSizeLocation; + GLShader *m_shaderDownsample = nullptr; + GLShader *m_shaderUpsample = nullptr; + GLShader *m_shaderCopysample = nullptr; + + int m_mvpMatrixLocationDownsample; + int m_offsetLocationDownsample; + int m_renderTextureSizeLocationDownsample; + int m_halfpixelLocationDownsample; + + int m_mvpMatrixLocationUpsample; + int m_offsetLocationUpsample; + int m_renderTextureSizeLocationUpsample; + int m_halfpixelLocationUpsample; + + int m_mvpMatrixLocationCopysample; + int m_renderTextureSizeLocationCopysample; + int m_blurRectLocationCopysample; + + + //Caching uniform values to aviod unnecessary setUniform calls + int m_activeSampleType; + + float m_offsetDownsample; + QMatrix4x4 m_matrixDownsample; + QSize m_renderTextureSizeDownsample; + + float m_offsetUpsample; + QMatrix4x4 m_matrixUpsample; + QSize m_renderTextureSizeUpsample; + + QMatrix4x4 m_matrixCopysample; + QSize m_renderTextureSizeCopysample; + QRect m_blurRectCopysample; + }; } // namespace KWin #endif - diff --git a/effects/coverswitch/coverswitch.cpp b/effects/coverswitch/coverswitch.cpp index b2c26c41f..303782c1b 100644 --- a/effects/coverswitch/coverswitch.cpp +++ b/effects/coverswitch/coverswitch.cpp @@ -1,1000 +1,1001 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 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 "coverswitch.h" // KConfigSkeleton #include "coverswitchconfig.h" #include #include #include #include #include #include +#include #include #include #include #include #include namespace KWin { CoverSwitchEffect::CoverSwitchEffect() : mActivated(0) , angle(60.0) , animation(false) , start(false) , stop(false) , stopRequested(false) , startRequested(false) , zPosition(900.0) , scaleFactor(0.0) , direction(Left) , selected_window(0) , captionFrame(NULL) , primaryTabBox(false) , secondaryTabBox(false) { initConfig(); reconfigure(ReconfigureAll); // Caption frame captionFont.setBold(true); captionFont.setPointSize(captionFont.pointSize() * 2); if (effects->compositingType() == OpenGL2Compositing) { m_reflectionShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("coverswitch-reflection.glsl")); } else { m_reflectionShader = NULL; } connect(effects, SIGNAL(windowClosed(KWin::EffectWindow*)), this, SLOT(slotWindowClosed(KWin::EffectWindow*))); connect(effects, SIGNAL(tabBoxAdded(int)), this, SLOT(slotTabBoxAdded(int))); connect(effects, SIGNAL(tabBoxClosed()), this, SLOT(slotTabBoxClosed())); connect(effects, SIGNAL(tabBoxUpdated()), this, SLOT(slotTabBoxUpdated())); connect(effects, SIGNAL(tabBoxKeyEvent(QKeyEvent*)), this, SLOT(slotTabBoxKeyEvent(QKeyEvent*))); } CoverSwitchEffect::~CoverSwitchEffect() { delete captionFrame; delete m_reflectionShader; } bool CoverSwitchEffect::supported() { return effects->isOpenGLCompositing() && effects->animationsSupported(); } void CoverSwitchEffect::reconfigure(ReconfigureFlags) { CoverSwitchConfig::self()->read(); animationDuration = animationTime(200); animateSwitch = CoverSwitchConfig::animateSwitch(); animateStart = CoverSwitchConfig::animateStart(); animateStop = CoverSwitchConfig::animateStop(); reflection = CoverSwitchConfig::reflection(); windowTitle = CoverSwitchConfig::windowTitle(); zPosition = CoverSwitchConfig::zPosition(); timeLine.setCurveShape(QTimeLine::EaseInOutCurve); timeLine.setDuration(animationDuration); // Defined outside the ui primaryTabBox = CoverSwitchConfig::tabBox(); secondaryTabBox = CoverSwitchConfig::tabBoxAlternative(); QColor tmp = CoverSwitchConfig::mirrorFrontColor(); mirrorColor[0][0] = tmp.redF(); mirrorColor[0][1] = tmp.greenF(); mirrorColor[0][2] = tmp.blueF(); mirrorColor[0][3] = 1.0; tmp = CoverSwitchConfig::mirrorRearColor(); mirrorColor[1][0] = tmp.redF(); mirrorColor[1][1] = tmp.greenF(); mirrorColor[1][2] = tmp.blueF(); mirrorColor[1][3] = -1.0; } void CoverSwitchEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (mActivated || stop || stopRequested) { data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; if (animation || start || stop) { timeLine.setCurrentTime(timeLine.currentTime() + time); } if (selected_window == NULL) abort(); } effects->prePaintScreen(data, time); } void CoverSwitchEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); if (mActivated || stop || stopRequested) { QList< EffectWindow* > tempList = currentWindowList; int index = tempList.indexOf(selected_window); if (animation || start || stop) { if (!start && !stop) { if (direction == Right) index++; else index--; if (index < 0) index = tempList.count() + index; if (index >= tempList.count()) index = index % tempList.count(); } foreach (Direction direction, scheduled_directions) { if (direction == Right) index++; else index--; if (index < 0) index = tempList.count() + index; if (index >= tempList.count()) index = index % tempList.count(); } } int leftIndex = index - 1; if (leftIndex < 0) leftIndex = tempList.count() - 1; int rightIndex = index + 1; if (rightIndex == tempList.count()) rightIndex = 0; EffectWindow* frontWindow = tempList[ index ]; leftWindows.clear(); rightWindows.clear(); bool evenWindows = (tempList.count() % 2 == 0) ? true : false; int leftWindowCount = 0; if (evenWindows) leftWindowCount = tempList.count() / 2 - 1; else leftWindowCount = (tempList.count() - 1) / 2; for (int i = 0; i < leftWindowCount; i++) { int tempIndex = (leftIndex - i); if (tempIndex < 0) tempIndex = tempList.count() + tempIndex; leftWindows.prepend(tempList[ tempIndex ]); } int rightWindowCount = 0; if (evenWindows) rightWindowCount = tempList.count() / 2; else rightWindowCount = (tempList.count() - 1) / 2; for (int i = 0; i < rightWindowCount; i++) { int tempIndex = (rightIndex + i) % tempList.count(); rightWindows.prepend(tempList[ tempIndex ]); } if (reflection) { // no reflections during start and stop animation // except when using a shader if ((!start && !stop) || effects->compositingType() == OpenGL2Compositing) paintScene(frontWindow, leftWindows, rightWindows, true); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // we can use a huge scale factor (needed to calculate the rearground vertices) // as we restrict with a PaintClipper painting on the current screen float reflectionScaleFactor = 100000 * tan(60.0 * M_PI / 360.0f) / area.width(); const float width = area.width(); const float height = area.height(); float vertices[] = { -width * 0.5f, height, 0.0, width * 0.5f, height, 0.0, width*reflectionScaleFactor, height, -5000, -width*reflectionScaleFactor, height, -5000 }; // foreground if (start) { mirrorColor[0][3] = timeLine.currentValue(); } else if (stop) { mirrorColor[0][3] = 1.0 - timeLine.currentValue(); } else { mirrorColor[0][3] = 1.0; } int y = 0; // have to adjust the y values to fit OpenGL // in OpenGL y==0 is at bottom, in Qt at top if (effects->numScreens() > 1) { QRect fullArea = effects->clientArea(FullArea, 0, 1); if (fullArea.height() != area.height()) { if (area.y() == 0) y = fullArea.height() - area.height(); else y = fullArea.height() - area.y() - area.height(); } } // use scissor to restrict painting of the reflection plane to current screen glScissor(area.x(), y, area.width(), area.height()); glEnable(GL_SCISSOR_TEST); if (m_reflectionShader && m_reflectionShader->isValid()) { ShaderManager::instance()->pushShader(m_reflectionShader); QMatrix4x4 windowTransformation = data.projectionMatrix(); windowTransformation.translate(area.x() + area.width() * 0.5f, 0.0, 0.0); m_reflectionShader->setUniform(GLShader::ModelViewProjectionMatrix, windowTransformation); m_reflectionShader->setUniform("u_frontColor", QVector4D(mirrorColor[0][0], mirrorColor[0][1], mirrorColor[0][2], mirrorColor[0][3])); m_reflectionShader->setUniform("u_backColor", QVector4D(mirrorColor[1][0], mirrorColor[1][1], mirrorColor[1][2], mirrorColor[1][3])); // TODO: make this one properly QVector verts; QVector texcoords; verts.reserve(18); texcoords.reserve(12); texcoords << 1.0 << 0.0; verts << vertices[6] << vertices[7] << vertices[8]; texcoords << 1.0 << 0.0; verts << vertices[9] << vertices[10] << vertices[11]; texcoords << 0.0 << 0.0; verts << vertices[0] << vertices[1] << vertices[2]; texcoords << 0.0 << 0.0; verts << vertices[0] << vertices[1] << vertices[2]; texcoords << 0.0 << 0.0; verts << vertices[3] << vertices[4] << vertices[5]; texcoords << 1.0 << 0.0; verts << vertices[6] << vertices[7] << vertices[8]; GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setData(6, 3, verts.data(), texcoords.data()); vbo->render(GL_TRIANGLES); ShaderManager::instance()->popShader(); } glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); } paintScene(frontWindow, leftWindows, rightWindows); // Render the caption frame if (windowTitle) { double opacity = 1.0; if (start) opacity = timeLine.currentValue(); else if (stop) opacity = 1.0 - timeLine.currentValue(); if (animation) captionFrame->setCrossFadeProgress(timeLine.currentValue()); captionFrame->render(region, opacity); } } } void CoverSwitchEffect::postPaintScreen() { if ((mActivated && (animation || start)) || stop || stopRequested) { if (timeLine.currentValue() == 1.0) { timeLine.setCurrentTime(0); if (stop) { stop = false; effects->setActiveFullScreenEffect(0); foreach (EffectWindow * window, referrencedWindows) { window->unrefWindow(); } referrencedWindows.clear(); currentWindowList.clear(); if (startRequested) { startRequested = false; mActivated = true; effects->refTabBox(); currentWindowList = effects->currentTabBoxWindowList(); if (animateStart) { start = true; } } } else if (!scheduled_directions.isEmpty()) { direction = scheduled_directions.dequeue(); if (start) { animation = true; start = false; } } else { animation = false; start = false; if (stopRequested) { stopRequested = false; stop = true; } } } effects->addRepaintFull(); } effects->postPaintScreen(); } void CoverSwitchEffect::paintScene(EffectWindow* frontWindow, const EffectWindowList& leftWindows, const EffectWindowList& rightWindows, bool reflectedWindows) { // LAYOUT // one window in the front. Other windows left and right rotated // for odd number of windows: left: (n-1)/2; front: 1; right: (n-1)/2 // for even number of windows: left: n/2; front: 1; right: n/2 -1 // // ANIMATION // forward (alt+tab) // all left windows are moved to next position // top most left window is rotated and moved to front window position // front window is rotated and moved to next right window position // right windows are moved to next position // last right window becomes totally transparent in half the time // appears transparent on left side and becomes totally opaque again // backward (alt+shift+tab) same as forward but opposite direction int width = area.width(); int leftWindowCount = leftWindows.count(); int rightWindowCount = rightWindows.count(); // Problem during animation: a window which is painted after another window // appears in front of the other // so during animation the painting order has to be rearreanged // paint sequence no animation: left, right, front // paint sequence forward animation: right, front, left if (!animation) { paintWindows(leftWindows, true, reflectedWindows); paintWindows(rightWindows, false, reflectedWindows); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); } else { if (direction == Right) { if (timeLine.currentValue() < 0.5) { // paint in normal way paintWindows(leftWindows, true, reflectedWindows); paintWindows(rightWindows, false, reflectedWindows); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); } else { paintWindows(rightWindows, false, reflectedWindows); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); paintWindows(leftWindows, true, reflectedWindows, rightWindows.at(0)); } } else { paintWindows(leftWindows, true, reflectedWindows); if (timeLine.currentValue() < 0.5) { paintWindows(rightWindows, false, reflectedWindows); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); } else { EffectWindow* leftWindow; if (leftWindowCount > 0) { leftWindow = leftWindows.at(0); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); } else leftWindow = frontWindow; paintWindows(rightWindows, false, reflectedWindows, leftWindow); } } } } void CoverSwitchEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (mActivated || stop || stopRequested) { if (!(mask & PAINT_WINDOW_TRANSFORMED) && !w->isDesktop()) { if ((start || stop) && w->isDock()) { data.setOpacity(1.0 - timeLine.currentValue()); if (stop) data.setOpacity(timeLine.currentValue()); } else return; } } if ((start || stop) && (!w->isOnCurrentDesktop() || w->isMinimized())) { if (stop) // Fade out windows not on the current desktop data.setOpacity((1.0 - timeLine.currentValue())); else // Fade in Windows from other desktops when animation is started data.setOpacity(timeLine.currentValue()); } effects->paintWindow(w, mask, region, data); } void CoverSwitchEffect::slotTabBoxAdded(int mode) { if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; if (!mActivated) { effects->setShowingDesktop(false); // only for windows mode if (((mode == TabBoxWindowsMode && primaryTabBox) || (mode == TabBoxWindowsAlternativeMode && secondaryTabBox) || (mode == TabBoxCurrentAppWindowsMode && primaryTabBox) || (mode == TabBoxCurrentAppWindowsAlternativeMode && secondaryTabBox)) && effects->currentTabBoxWindowList().count() > 0) { effects->startMouseInterception(this, Qt::ArrowCursor); activeScreen = effects->activeScreen(); if (!stop && !stopRequested) { effects->refTabBox(); effects->setActiveFullScreenEffect(this); scheduled_directions.clear(); selected_window = effects->currentTabBoxWindow(); currentWindowList = effects->currentTabBoxWindowList(); direction = Left; mActivated = true; if (animateStart) { start = true; } // Calculation of correct area area = effects->clientArea(FullScreenArea, activeScreen, effects->currentDesktop()); const QSize screenSize = effects->virtualScreenSize(); scaleFactor = (zPosition + 1100) * 2.0 * tan(60.0 * M_PI / 360.0f) / screenSize.width(); if (screenSize.width() - area.width() != 0) { // one of the screens is smaller than the other (horizontal) if (area.width() < screenSize.width() - area.width()) scaleFactor *= (float)area.width() / (float)(screenSize.width() - area.width()); else if (area.width() != screenSize.width() - area.width()) { // vertical layout with different width // but we don't want to catch screens with same width and different height if (screenSize.height() != area.height()) scaleFactor *= (float)area.width() / (float)(screenSize.width()); } } if (effects->numScreens() > 1) { // unfortunatelly we have to change the projection matrix in dual screen mode // code is adapted from SceneOpenGL2::createProjectionMatrix() QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); float fovy = 60.0f; float aspect = 1.0f; float zNear = 0.1f; float zFar = 100.0f; float ymax = zNear * std::tan(fovy * M_PI / 360.0f); float ymin = -ymax; float xmin = ymin * aspect; float xmax = ymax * aspect; if (area.width() != fullRect.width()) { if (area.x() == 0) { // horizontal layout: left screen xmin *= (float)area.width() / (float)fullRect.width(); xmax *= (fullRect.width() - 0.5f * area.width()) / (0.5f * fullRect.width()); } else { // horizontal layout: right screen xmin *= (fullRect.width() - 0.5f * area.width()) / (0.5f * fullRect.width()); xmax *= (float)area.width() / (float)fullRect.width(); } } if (area.height() != fullRect.height()) { if (area.y() == 0) { // vertical layout: top screen ymin *= (fullRect.height() - 0.5f * area.height()) / (0.5f * fullRect.height()); ymax *= (float)area.height() / (float)fullRect.height(); } else { // vertical layout: bottom screen ymin *= (float)area.height() / (float)fullRect.height(); ymax *= (fullRect.height() - 0.5f * area.height()) / (0.5f * fullRect.height()); } } m_projectionMatrix = QMatrix4x4(); m_projectionMatrix.frustum(xmin, xmax, ymin, ymax, zNear, zFar); const float scaleFactor = 1.1f / zNear; // Create a second matrix that transforms screen coordinates // to world coordinates. QMatrix4x4 matrix; matrix.translate(xmin * scaleFactor, ymax * scaleFactor, -1.1); matrix.scale( (xmax - xmin) * scaleFactor / fullRect.width(), -(ymax - ymin) * scaleFactor / fullRect.height(), 0.001); // Combine the matrices m_projectionMatrix *= matrix; m_modelviewMatrix = QMatrix4x4(); m_modelviewMatrix.translate(area.x(), area.y(), 0.0); } // Setup caption frame geometry if (windowTitle) { QRect frameRect = QRect(area.width() * 0.25f + area.x(), area.height() * 0.9f + area.y(), area.width() * 0.5f, QFontMetrics(captionFont).height()); if (!captionFrame) { captionFrame = effects->effectFrame(EffectFrameStyled); captionFrame->setFont(captionFont); captionFrame->enableCrossFade(true); } captionFrame->setGeometry(frameRect); captionFrame->setIconSize(QSize(frameRect.height(), frameRect.height())); // And initial contents updateCaption(); } effects->addRepaintFull(); } else { startRequested = true; } } } } void CoverSwitchEffect::slotTabBoxClosed() { if (mActivated) { if (animateStop) { if (!animation && !start) { stop = true; } else if (start && scheduled_directions.isEmpty()) { start = false; stop = true; timeLine.setCurrentTime(timeLine.duration() - timeLine.currentValue()); } else { stopRequested = true; } } else effects->setActiveFullScreenEffect(0); mActivated = false; effects->unrefTabBox(); effects->stopMouseInterception(this); effects->addRepaintFull(); } } void CoverSwitchEffect::slotTabBoxUpdated() { if (mActivated) { if (animateSwitch && currentWindowList.count() > 1) { // determine the switch direction if (selected_window != effects->currentTabBoxWindow()) { if (selected_window != NULL) { int old_index = currentWindowList.indexOf(selected_window); int new_index = effects->currentTabBoxWindowList().indexOf(effects->currentTabBoxWindow()); Direction new_direction; int distance = new_index - old_index; if (distance > 0) new_direction = Left; if (distance < 0) new_direction = Right; if (effects->currentTabBoxWindowList().count() == 2) { new_direction = Left; distance = 1; } if (distance != 0) { distance = abs(distance); int tempDistance = effects->currentTabBoxWindowList().count() - distance; if (tempDistance < abs(distance)) { distance = tempDistance; if (new_direction == Left) new_direction = Right; else new_direction = Left; } if (!animation && !start) { animation = true; direction = new_direction; distance--; } for (int i = 0; i < distance; i++) { if (!scheduled_directions.isEmpty() && scheduled_directions.last() != new_direction) scheduled_directions.pop_back(); else scheduled_directions.enqueue(new_direction); if (scheduled_directions.count() == effects->currentTabBoxWindowList().count()) scheduled_directions.clear(); } } } selected_window = effects->currentTabBoxWindow(); currentWindowList = effects->currentTabBoxWindowList(); updateCaption(); } } effects->addRepaintFull(); } } void CoverSwitchEffect::paintWindowCover(EffectWindow* w, bool reflectedWindow, WindowPaintData& data) { QRect windowRect = w->geometry(); data.setYTranslation(area.height() - windowRect.y() - windowRect.height()); data.setZTranslation(-zPosition); if (start) { if (w->isMinimized()) { data.multiplyOpacity(timeLine.currentValue()); } else { const QVector3D translation = data.translation() * timeLine.currentValue(); data.setXTranslation(translation.x()); data.setYTranslation(translation.y()); data.setZTranslation(translation.z()); if (effects->numScreens() > 1) { QRect clientRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop()); QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); if (w->screen() == activeScreen) { if (clientRect.width() != fullRect.width() && clientRect.x() != fullRect.x()) { data.translate(- clientRect.x() * (1.0f - timeLine.currentValue())); } if (clientRect.height() != fullRect.height() && clientRect.y() != fullRect.y()) { data.translate(0.0, - clientRect.y() * (1.0f - timeLine.currentValue())); } } else { if (clientRect.width() != fullRect.width() && clientRect.x() < area.x()) { data.translate(- clientRect.width() * (1.0f - timeLine.currentValue())); } if (clientRect.height() != fullRect.height() && clientRect.y() < area.y()) { data.translate(0.0, - clientRect.height() * (1.0f - timeLine.currentValue())); } } } data.setRotationAngle(data.rotationAngle() * timeLine.currentValue()); } } if (stop) { if (w->isMinimized() && w != effects->activeWindow()) { data.multiplyOpacity((1.0 - timeLine.currentValue())); } else { const QVector3D translation = data.translation() * (1.0 - timeLine.currentValue()); data.setXTranslation(translation.x()); data.setYTranslation(translation.y()); data.setZTranslation(translation.z()); if (effects->numScreens() > 1) { QRect clientRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop()); QRect rect = effects->clientArea(FullScreenArea, activeScreen, effects->currentDesktop()); QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); if (w->screen() == activeScreen) { if (clientRect.width() != fullRect.width() && clientRect.x() != fullRect.x()) { data.translate(- clientRect.x() * timeLine.currentValue()); } if (clientRect.height() != fullRect.height() && clientRect.y() != fullRect.y()) { data.translate(0.0, - clientRect.y() * timeLine.currentValue()); } } else { if (clientRect.width() != fullRect.width() && clientRect.x() < rect.x()) { data.translate(- clientRect.width() * timeLine.currentValue()); } if (clientRect.height() != fullRect.height() && clientRect.y() < area.y()) { data.translate(0.0, - clientRect.height() * timeLine.currentValue()); } } } data.setRotationAngle(data.rotationAngle() * (1.0 - timeLine.currentValue())); } } if (reflectedWindow) { QMatrix4x4 reflectionMatrix; reflectionMatrix.scale(1.0, -1.0, 1.0); data.setProjectionMatrix(data.screenProjectionMatrix()); data.setModelViewMatrix(reflectionMatrix); data.setYTranslation(- area.height() - windowRect.y() - windowRect.height()); if (start) { data.multiplyOpacity(timeLine.currentValue()); } else if (stop) { data.multiplyOpacity(1.0 - timeLine.currentValue()); } effects->drawWindow(w, PAINT_WINDOW_TRANSFORMED, infiniteRegion(), data); } else { effects->paintWindow(w, PAINT_WINDOW_TRANSFORMED, infiniteRegion(), data); } } void CoverSwitchEffect::paintFrontWindow(EffectWindow* frontWindow, int width, int leftWindows, int rightWindows, bool reflectedWindow) { if (frontWindow == NULL) return; bool specialHandlingForward = false; WindowPaintData data(frontWindow); if (effects->numScreens() > 1) { data.setProjectionMatrix(m_projectionMatrix); data.setModelViewMatrix(m_modelviewMatrix); } data.setXTranslation(area.width() * 0.5 - frontWindow->geometry().x() - frontWindow->geometry().width() * 0.5); if (leftWindows == 0) { leftWindows = 1; if (!start && !stop) specialHandlingForward = true; } if (rightWindows == 0) { rightWindows = 1; } if (animation) { float distance = 0.0; const QSize screenSize = effects->virtualScreenSize(); if (direction == Right) { // move to right distance = -frontWindow->geometry().width() * 0.5f + area.width() * 0.5f + (((float)screenSize.width() * 0.5 * scaleFactor) - (float)area.width() * 0.5f) / rightWindows; data.translate(distance * timeLine.currentValue()); data.setRotationAxis(Qt::YAxis); data.setRotationAngle(-angle * timeLine.currentValue()); data.setRotationOrigin(QVector3D(frontWindow->geometry().width(), 0.0, 0.0)); } else { // move to left distance = frontWindow->geometry().width() * 0.5f - area.width() * 0.5f + ((float)width * 0.5f - ((float)screenSize.width() * 0.5 * scaleFactor)) / leftWindows; float factor = 1.0; if (specialHandlingForward) factor = 2.0; data.translate(distance * timeLine.currentValue() * factor); data.setRotationAxis(Qt::YAxis); data.setRotationAngle(angle * timeLine.currentValue()); } } if (specialHandlingForward) { data.multiplyOpacity((1.0 - timeLine.currentValue() * 2.0)); paintWindowCover(frontWindow, reflectedWindow, data); } else paintWindowCover(frontWindow, reflectedWindow, data); } void CoverSwitchEffect::paintWindows(const EffectWindowList& windows, bool left, bool reflectedWindows, EffectWindow* additionalWindow) { int width = area.width(); int windowCount = windows.count(); EffectWindow* window; int rotateFactor = 1; if (!left) { rotateFactor = -1; } const QSize screenSize = effects->virtualScreenSize(); float xTranslate = -((float)(width) * 0.5f - ((float)screenSize.width() * 0.5 * scaleFactor)); if (!left) xTranslate = ((float)screenSize.width() * 0.5 * scaleFactor) - (float)width * 0.5f; // handling for additional window from other side // has to appear on this side after half of the time if (animation && timeLine.currentValue() >= 0.5 && additionalWindow != NULL) { WindowPaintData data(additionalWindow); if (effects->numScreens() > 1) { data.setProjectionMatrix(m_projectionMatrix); data.setModelViewMatrix(m_modelviewMatrix); } data.setRotationAxis(Qt::YAxis); data.setRotationAngle(angle * rotateFactor); if (left) { data.translate(-xTranslate - additionalWindow->geometry().x()); } else { data.translate(xTranslate + area.width() - additionalWindow->geometry().x() - additionalWindow->geometry().width()); data.setRotationOrigin(QVector3D(additionalWindow->geometry().width(), 0.0, 0.0)); } data.multiplyOpacity((timeLine.currentValue() - 0.5) * 2.0); paintWindowCover(additionalWindow, reflectedWindows, data); } // normal behaviour for (int i = 0; i < windows.count(); i++) { window = windows.at(i); if (window == NULL || window->isDeleted()) { continue; } WindowPaintData data(window); if (effects->numScreens() > 1) { data.setProjectionMatrix(m_projectionMatrix); data.setModelViewMatrix(m_modelviewMatrix); } data.setRotationAxis(Qt::YAxis); data.setRotationAngle(angle); if (left) data.translate(-xTranslate + xTranslate * i / windowCount - window->geometry().x()); else data.translate(xTranslate + width - xTranslate * i / windowCount - window->geometry().x() - window->geometry().width()); if (animation) { if (direction == Right) { if ((i == windowCount - 1) && left) { // right most window on left side -> move to front // have to move one window distance plus half the difference between the window and the desktop size data.translate((xTranslate / windowCount + (width - window->geometry().width()) * 0.5f) * timeLine.currentValue()); data.setRotationAngle(angle - angle * timeLine.currentValue()); } // right most window does not have to be moved else if (!left && (i == 0)); // do nothing else { // all other windows - move to next position data.translate(xTranslate / windowCount * timeLine.currentValue()); } } else { if ((i == windowCount - 1) && !left) { // left most window on right side -> move to front data.translate(- (xTranslate / windowCount + (width - window->geometry().width()) * 0.5f) * timeLine.currentValue()); data.setRotationAngle(angle - angle * timeLine.currentValue()); } // left most window does not have to be moved else if (i == 0 && left); // do nothing else { // all other windows - move to next position data.translate(- xTranslate / windowCount * timeLine.currentValue()); } } } if (!left) data.setRotationOrigin(QVector3D(window->geometry().width(), 0.0, 0.0)); data.setRotationAngle(data.rotationAngle() * rotateFactor); // make window most to edge transparent if animation if (animation && i == 0 && ((direction == Left && left) || (direction == Right && !left))) { // only for the first half of the animation if (timeLine.currentValue() < 0.5) { data.multiplyOpacity((1.0 - timeLine.currentValue() * 2.0)); paintWindowCover(window, reflectedWindows, data); } } else { paintWindowCover(window, reflectedWindows, data); } } } void CoverSwitchEffect::windowInputMouseEvent(QEvent* e) { if (e->type() != QEvent::MouseButtonPress) return; // we don't want click events during animations if (animation) return; QMouseEvent* event = static_cast< QMouseEvent* >(e); switch (event->button()) { case Qt::XButton1: // wheel up selectPreviousWindow(); break; case Qt::XButton2: // wheel down selectNextWindow(); break; case Qt::LeftButton: case Qt::RightButton: case Qt::MidButton: default: QPoint pos = event->pos(); // determine if a window has been clicked // not interested in events above a fullscreen window (ignoring panel size) if (pos.y() < (area.height()*scaleFactor - area.height()) * 0.5f *(1.0f / scaleFactor)) return; // if there is no selected window (that is no window at all) we cannot click it if (!selected_window) return; if (pos.x() < (area.width()*scaleFactor - selected_window->width()) * 0.5f *(1.0f / scaleFactor)) { float availableSize = (area.width() * scaleFactor - area.width()) * 0.5f * (1.0f / scaleFactor); for (int i = 0; i < leftWindows.count(); i++) { int windowPos = availableSize / leftWindows.count() * i; if (pos.x() < windowPos) continue; if (i + 1 < leftWindows.count()) { if (pos.x() > availableSize / leftWindows.count()*(i + 1)) continue; } effects->setTabBoxWindow(leftWindows[i]); return; } } if (pos.x() > area.width() - (area.width()*scaleFactor - selected_window->width()) * 0.5f *(1.0f / scaleFactor)) { float availableSize = (area.width() * scaleFactor - area.width()) * 0.5f * (1.0f / scaleFactor); for (int i = 0; i < rightWindows.count(); i++) { int windowPos = area.width() - availableSize / rightWindows.count() * i; if (pos.x() > windowPos) continue; if (i + 1 < rightWindows.count()) { if (pos.x() < area.width() - availableSize / rightWindows.count()*(i + 1)) continue; } effects->setTabBoxWindow(rightWindows[i]); return; } } break; } } void CoverSwitchEffect::abort() { // it's possible that abort is called after tabbox has been closed // in this case the cleanup is already done (see bug 207554) if (mActivated) { effects->unrefTabBox(); effects->stopMouseInterception(this); } effects->setActiveFullScreenEffect(0); mActivated = false; stop = false; stopRequested = false; effects->addRepaintFull(); captionFrame->free(); } void CoverSwitchEffect::slotWindowClosed(EffectWindow* c) { if (c == selected_window) selected_window = 0; // if the list is not empty, the effect is active if (!currentWindowList.isEmpty()) { c->refWindow(); referrencedWindows.append(c); currentWindowList.removeAll(c); leftWindows.removeAll(c); rightWindows.removeAll(c); } } bool CoverSwitchEffect::isActive() const { return (mActivated || stop || stopRequested) && !effects->isScreenLocked(); } void CoverSwitchEffect::updateCaption() { if (!selected_window || !windowTitle) { return; } if (selected_window->isDesktop()) { captionFrame->setText(i18nc("Special entry in alt+tab list for minimizing all windows", "Show Desktop")); static QPixmap pix = QIcon::fromTheme(QStringLiteral("user-desktop")).pixmap(captionFrame->iconSize()); captionFrame->setIcon(pix); } else { captionFrame->setText(selected_window->caption()); captionFrame->setIcon(selected_window->icon()); } } void CoverSwitchEffect::slotTabBoxKeyEvent(QKeyEvent *event) { if (event->type() == QEvent::KeyPress) { switch (event->key()) { case Qt::Key_Left: selectPreviousWindow(); break; case Qt::Key_Right: selectNextWindow(); break; default: // nothing break; } } } void CoverSwitchEffect::selectNextOrPreviousWindow(bool forward) { if (!mActivated || !selected_window) { return; } const int index = effects->currentTabBoxWindowList().indexOf(selected_window); int newIndex = index; if (forward) { ++newIndex; } else { --newIndex; } if (newIndex == effects->currentTabBoxWindowList().size()) { newIndex = 0; } else if (newIndex < 0) { newIndex = effects->currentTabBoxWindowList().size() -1; } if (index == newIndex) { return; } effects->setTabBoxWindow(effects->currentTabBoxWindowList().at(newIndex)); } } // namespace diff --git a/effects/coverswitch/coverswitch.h b/effects/coverswitch/coverswitch.h index b058db4d4..320c0521d 100644 --- a/effects/coverswitch/coverswitch.h +++ b/effects/coverswitch/coverswitch.h @@ -1,166 +1,167 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_COVERSWITCH_H #define KWIN_COVERSWITCH_H #include #include #include #include #include +#include #include #include #include namespace KWin { class CoverSwitchEffect : public Effect { Q_OBJECT Q_PROPERTY(int animationDuration READ configuredAnimationDuration) Q_PROPERTY(bool animateSwitch READ isAnimateSwitch) Q_PROPERTY(bool animateStart READ isAnimateStart) Q_PROPERTY(bool animateStop READ isAnimateStop) Q_PROPERTY(bool reflection READ isReflection) Q_PROPERTY(bool windowTitle READ isWindowTitle) Q_PROPERTY(qreal zPosition READ windowZPosition) Q_PROPERTY(bool primaryTabBox READ isPrimaryTabBox) Q_PROPERTY(bool secondaryTabBox READ isSecondaryTabBox) // TODO: mirror colors public: CoverSwitchEffect(); ~CoverSwitchEffect(); virtual void reconfigure(ReconfigureFlags); virtual void prePaintScreen(ScreenPrePaintData& data, int time); virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); virtual void postPaintScreen(); virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); virtual void windowInputMouseEvent(QEvent* e); virtual bool isActive() const; static bool supported(); // for properties int configuredAnimationDuration() const { return animationDuration; } bool isAnimateSwitch() const { return animateSwitch; } bool isAnimateStart() const { return animateStart; } bool isAnimateStop() const { return animateStop; } bool isReflection() const { return reflection; } bool isWindowTitle() const { return windowTitle; } qreal windowZPosition() const { return zPosition; } bool isPrimaryTabBox() const { return primaryTabBox; } bool isSecondaryTabBox() const { return secondaryTabBox; } int requestedEffectChainPosition() const override { return 50; } public Q_SLOTS: void slotWindowClosed(KWin::EffectWindow *c); void slotTabBoxAdded(int mode); void slotTabBoxClosed(); void slotTabBoxUpdated(); void slotTabBoxKeyEvent(QKeyEvent* event); private: void paintScene(EffectWindow* frontWindow, const EffectWindowList& leftWindows, const EffectWindowList& rightWindows, bool reflectedWindows = false); void paintWindowCover(EffectWindow* w, bool reflectedWindow, WindowPaintData& data); void paintFrontWindow(EffectWindow* frontWindow, int width, int leftWindows, int rightWindows, bool reflectedWindow); void paintWindows(const EffectWindowList& windows, bool left, bool reflectedWindows, EffectWindow* additionalWindow = NULL); void selectNextOrPreviousWindow(bool forward); inline void selectNextWindow() { selectNextOrPreviousWindow(true); } inline void selectPreviousWindow() { selectNextOrPreviousWindow(false); } void abort(); /** * Updates the caption of the caption frame. * Taking care of rewording the desktop client. * As well sets the icon for the caption frame. **/ void updateCaption(); bool mActivated; float angle; bool animateSwitch; bool animateStart; bool animateStop; bool animation; bool start; bool stop; bool reflection; float mirrorColor[2][4]; bool windowTitle; int animationDuration; bool stopRequested; bool startRequested; QTimeLine timeLine; QRect area; float zPosition; float scaleFactor; enum Direction { Left, Right }; Direction direction; QQueue scheduled_directions; EffectWindow* selected_window; int activeScreen; QList< EffectWindow* > leftWindows; QList< EffectWindow* > rightWindows; EffectWindowList currentWindowList; EffectWindowList referrencedWindows; EffectFrame* captionFrame; QFont captionFont; bool primaryTabBox; bool secondaryTabBox; GLShader *m_reflectionShader; QMatrix4x4 m_projectionMatrix; QMatrix4x4 m_modelviewMatrix; }; } // namespace #endif diff --git a/effects/cube/cube.h b/effects/cube/cube.h index 974526678..0de8ad53e 100644 --- a/effects/cube/cube.h +++ b/effects/cube/cube.h @@ -1,256 +1,257 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_CUBE_H #define KWIN_CUBE_H #include #include #include #include #include #include +#include #include "cube_inside.h" #include "cube_proxy.h" namespace KWin { class CubeEffect : public Effect { Q_OBJECT Q_PROPERTY(qreal cubeOpacity READ configuredCubeOpacity) Q_PROPERTY(bool opacityDesktopOnly READ isOpacityDesktopOnly) Q_PROPERTY(bool displayDesktopName READ isDisplayDesktopName) Q_PROPERTY(bool reflection READ isReflection) Q_PROPERTY(int rotationDuration READ configuredRotationDuration) Q_PROPERTY(QColor backgroundColor READ configuredBackgroundColor) Q_PROPERTY(QColor capColor READ configuredCapColor) Q_PROPERTY(bool paintCaps READ isPaintCaps) Q_PROPERTY(bool closeOnMouseRelease READ isCloseOnMouseRelease) Q_PROPERTY(qreal zPosition READ configuredZPosition) Q_PROPERTY(bool useForTabBox READ isUseForTabBox) Q_PROPERTY(bool invertKeys READ isInvertKeys) Q_PROPERTY(bool invertMouse READ isInvertMouse) Q_PROPERTY(qreal capDeformationFactor READ configuredCapDeformationFactor) Q_PROPERTY(bool useZOrdering READ isUseZOrdering) Q_PROPERTY(bool texturedCaps READ isTexturedCaps) // TODO: electric borders: not a registered type public: CubeEffect(); ~CubeEffect(); virtual void reconfigure(ReconfigureFlags); virtual void prePaintScreen(ScreenPrePaintData& data, int time); virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); virtual void postPaintScreen(); virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); virtual bool borderActivated(ElectricBorder border); virtual void grabbedKeyboardEvent(QKeyEvent* e); virtual void windowInputMouseEvent(QEvent* e); virtual bool isActive() const; int requestedEffectChainPosition() const override { return 50; } // proxy functions virtual void* proxy(); void registerCubeInsideEffect(CubeInsideEffect* effect); void unregisterCubeInsideEffect(CubeInsideEffect* effect); static bool supported(); // for properties qreal configuredCubeOpacity() const { return cubeOpacity; } bool isOpacityDesktopOnly() const { return opacityDesktopOnly; } bool isDisplayDesktopName() const { return displayDesktopName; } bool isReflection() const { return reflection; } int configuredRotationDuration() const { return rotationDuration; } QColor configuredBackgroundColor() const { return backgroundColor; } QColor configuredCapColor() const { return capColor; } bool isPaintCaps() const { return paintCaps; } bool isCloseOnMouseRelease() const { return closeOnMouseRelease; } qreal configuredZPosition() const { return zPosition; } bool isUseForTabBox() const { return useForTabBox; } bool isInvertKeys() const { return invertKeys; } bool isInvertMouse() const { return invertMouse; } qreal configuredCapDeformationFactor() const { return capDeformationFactor; } bool isUseZOrdering() const { return useZOrdering; } bool isTexturedCaps() const { return texturedCaps; } private Q_SLOTS: void toggleCube(); void toggleCylinder(); void toggleSphere(); // slots for global shortcut changed // needed to toggle the effect void globalShortcutChanged(QAction *action, const QKeySequence &seq); void slotTabBoxAdded(int mode); void slotTabBoxUpdated(); void slotTabBoxClosed(); void slotCubeCapLoaded(); void slotWallPaperLoaded(); private: enum RotationDirection { Left, Right, Upwards, Downwards }; enum VerticalRotationPosition { Up, Normal, Down }; enum CubeMode { Cube, Cylinder, Sphere }; void toggle(CubeMode newMode = Cube); void paintCube(int mask, QRegion region, ScreenPaintData& data); void paintCap(bool frontFirst, float zOffset, const QMatrix4x4 &projection); void paintCubeCap(); void paintCylinderCap(); void paintSphereCap(); bool loadShader(); void rotateCube(); void rotateToDesktop(int desktop); void setActive(bool active); QImage loadCubeCap(const QString &capPath); QImage loadWallPaper(const QString &file); bool activated; bool cube_painting; bool keyboard_grab; bool schedule_close; QList borderActivate; QList borderActivateCylinder; QList borderActivateSphere; int painting_desktop; int frontDesktop; float cubeOpacity; bool opacityDesktopOnly; bool displayDesktopName; EffectFrame* desktopNameFrame; QFont desktopNameFont; bool reflection; bool rotating; bool verticalRotating; bool desktopChangedWhileRotating; bool paintCaps; QTimeLine timeLine; QTimeLine verticalTimeLine; RotationDirection rotationDirection; RotationDirection verticalRotationDirection; VerticalRotationPosition verticalPosition; QQueue rotations; QQueue verticalRotations; QColor backgroundColor; QColor capColor; GLTexture* wallpaper; bool texturedCaps; GLTexture* capTexture; float manualAngle; float manualVerticalAngle; QTimeLine::CurveShape currentShape; bool start; bool stop; bool reflectionPainting; int rotationDuration; int activeScreen; bool bottomCap; bool closeOnMouseRelease; float zoom; float zPosition; bool useForTabBox; bool invertKeys; bool invertMouse; bool tabBoxMode; bool shortcutsRegistered; CubeMode mode; bool useShaders; GLShader* cylinderShader; GLShader* sphereShader; GLShader* m_reflectionShader; GLShader* m_capShader; float capDeformationFactor; bool useZOrdering; float zOrderingFactor; bool useList; // needed for reflection float mAddedHeightCoeff1; float mAddedHeightCoeff2; QMatrix4x4 m_rotationMatrix; QMatrix4x4 m_reflectionMatrix; QMatrix4x4 m_textureMirrorMatrix; QMatrix4x4 m_currentFaceMatrix; GLVertexBuffer *m_cubeCapBuffer; // Shortcuts - needed to toggle the effect QList cubeShortcut; QList cylinderShortcut; QList sphereShortcut; // proxy CubeEffectProxy m_proxy; QList< CubeInsideEffect* > m_cubeInsideEffects; QAction *m_cubeAction; QAction *m_cylinderAction; QAction *m_sphereAction; }; } // namespace #endif diff --git a/effects/effect_builtins.cpp b/effects/effect_builtins.cpp index 2783cf586..7198d6cf0 100644 --- a/effects/effect_builtins.cpp +++ b/effects/effect_builtins.cpp @@ -1,788 +1,788 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "effect_builtins.h" #ifdef EFFECT_BUILTINS // common effects #include "backgroundcontrast/contrast.h" #include "blur/blur.h" #include "colorpicker/colorpicker.h" #include "kscreen/kscreen.h" #include "presentwindows/presentwindows.h" #include "screenedge/screenedgeeffect.h" #include "screenshot/screenshot.h" #include "slidingpopups/slidingpopups.h" // Common effects only relevant to desktop #include "desktopgrid/desktopgrid.h" #include "diminactive/diminactive.h" #include "dimscreen/dimscreen.h" #include "fallapart/fallapart.h" #include "highlightwindow/highlightwindow.h" #include "magiclamp/magiclamp.h" #include "minimizeanimation/minimizeanimation.h" #include "resize/resize.h" #include "showfps/showfps.h" #include "showpaint/showpaint.h" #include "slide/slide.h" #include "slideback/slideback.h" #include "thumbnailaside/thumbnailaside.h" #include "touchpoints/touchpoints.h" #include "windowgeometry/windowgeometry.h" #include "zoom/zoom.h" // OpenGL-specific effects for desktop #include "coverswitch/coverswitch.h" #include "cube/cube.h" #include "cube/cubeslide.h" #include "flipswitch/flipswitch.h" #include "glide/glide.h" #include "invert/invert.h" #include "lookingglass/lookingglass.h" #include "magnifier/magnifier.h" #include "mouseclick/mouseclick.h" #include "mousemark/mousemark.h" #include "sheet/sheet.h" #include "snaphelper/snaphelper.h" #include "startupfeedback/startupfeedback.h" #include "trackmouse/trackmouse.h" #include "wobblywindows/wobblywindows.h" #endif #include #include #ifndef EFFECT_BUILTINS #define EFFECT_FALLBACK nullptr, nullptr, nullptr #else #define EFFECT_FALLBACK #endif namespace KWin { namespace BuiltInEffects { template inline Effect *createHelper() { return new T(); } static const QVector s_effectData = { { QString(), QString(), QString(), QString(), QString(), QUrl(), false, false, nullptr, nullptr, nullptr }, { QStringLiteral("blur"), i18ndc("kwin_effects", "Name of a KWin Effect", "Blur"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Blurs the background behind semi-transparent windows"), QStringLiteral("Appearance"), QString(), QUrl(), true, false, #ifdef EFFECT_BUILTINS &createHelper, &BlurEffect::supported, &BlurEffect::enabledByDefault #endif EFFECT_FALLBACK }, { QStringLiteral("colorpicker"), i18ndc("kwin_effects", "Name of a KWin Effect", "Color Picker"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Supports picking a color"), QStringLiteral("Accessibility"), QString(), QUrl(), true, true, #ifdef EFFECT_BUILTINS &createHelper, &ColorPickerEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("contrast"), i18ndc("kwin_effects", "Name of a KWin Effect", "Background contrast"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Improve contrast and readability behind semi-transparent windows"), QStringLiteral("Appearance"), QString(), QUrl(), true, false, #ifdef EFFECT_BUILTINS &createHelper, &ContrastEffect::supported, &ContrastEffect::enabledByDefault #endif EFFECT_FALLBACK }, { QStringLiteral("coverswitch"), i18ndc("kwin_effects", "Name of a KWin Effect", "Cover Switch"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Display a Cover Flow effect for the alt+tab window switcher"), QStringLiteral("Window Management"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/cover_switch.mp4")), false, true, #ifdef EFFECT_BUILTINS &createHelper, &CoverSwitchEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("cube"), i18ndc("kwin_effects", "Name of a KWin Effect", "Desktop Cube"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Display each virtual desktop on a side of a cube"), QStringLiteral("Window Management"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/desktop_cube.ogv")), false, false, #ifdef EFFECT_BUILTINS &createHelper, &CubeEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("cubeslide"), i18ndc("kwin_effects", "Name of a KWin Effect", "Desktop Cube Animation"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Animate desktop switching with a cube"), QStringLiteral("Virtual Desktop Switching Animation"), QStringLiteral("desktop-animations"), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/desktop_cube_animation.ogv")), false, false, #ifdef EFFECT_BUILTINS &createHelper, &CubeSlideEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("desktopgrid"), i18ndc("kwin_effects", "Name of a KWin Effect", "Desktop Grid"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Zoom out so all desktops are displayed side-by-side in a grid"), QStringLiteral("Window Management"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/desktop_grid.mp4")), true, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("diminactive"), i18ndc("kwin_effects", "Name of a KWin Effect", "Dim Inactive"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Darken inactive windows"), QStringLiteral("Focus"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/dim_inactive.mp4")), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("dimscreen"), i18ndc("kwin_effects", "Name of a KWin Effect", "Dim Screen for Administrator Mode"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Darkens the entire screen when requesting root privileges"), QStringLiteral("Focus"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/dim_administration.mp4")), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("fallapart"), i18ndc("kwin_effects", "Name of a KWin Effect", "Fall Apart"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Closed windows fall into pieces"), QStringLiteral("Appearance"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, &FallApartEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("flipswitch"), i18ndc("kwin_effects", "Name of a KWin Effect", "Flip Switch"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Flip through windows that are in a stack for the alt+tab window switcher"), QStringLiteral("Window Management"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/flip_switch.mp4")), false, false, #ifdef EFFECT_BUILTINS &createHelper, &FlipSwitchEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("glide"), i18ndc("kwin_effects", "Name of a KWin Effect", "Glide"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Windows Glide Effect as they are open and closed"), QStringLiteral("Appearance"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, &GlideEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("highlightwindow"), i18ndc("kwin_effects", "Name of a KWin Effect", "Highlight Window"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Highlight the appropriate window when hovering over taskbar entries"), QStringLiteral("Appearance"), QString(), QUrl(), true, true, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("invert"), i18ndc("kwin_effects", "Name of a KWin Effect", "Invert"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Inverts the color of the desktop and windows"), QStringLiteral("Accessibility"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/invert.mp4")), false, false, #ifdef EFFECT_BUILTINS &createHelper, &InvertEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("kscreen"), i18ndc("kwin_effects", "Name of a KWin Effect", "Kscreen"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Helper Effect for KScreen"), QStringLiteral("Appearance"), QString(), QUrl(), true, true, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("lookingglass"), i18ndc("kwin_effects", "Name of a KWin Effect", "Looking Glass"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "A screen magnifier that looks like a fisheye lens"), QStringLiteral("Accessibility"), QStringLiteral("magnifiers"), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/looking_glass.ogv")), false, false, #ifdef EFFECT_BUILTINS &createHelper, &LookingGlassEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("magiclamp"), i18ndc("kwin_effects", "Name of a KWin Effect", "Magic Lamp"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Simulate a magic lamp when minimizing windows"), QStringLiteral("Appearance"), QStringLiteral("minimize"), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/magic_lamp.ogv")), false, false, #ifdef EFFECT_BUILTINS &createHelper, &MagicLampEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("magnifier"), i18ndc("kwin_effects", "Name of a KWin Effect", "Magnifier"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Magnify the section of the screen that is near the mouse cursor"), QStringLiteral("Accessibility"), QStringLiteral("magnifiers"), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/magnifier.ogv")), false, false, #ifdef EFFECT_BUILTINS &createHelper, &MagnifierEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("minimizeanimation"), i18ndc("kwin_effects", "Name of a KWin Effect", "Minimize Animation"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Animate the minimizing of windows"), QStringLiteral("Appearance"), QStringLiteral("minimize"), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/minimize.ogv")), true, false, #ifdef EFFECT_BUILTINS &createHelper, - nullptr, + &MinimizeAnimationEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("mouseclick"), i18ndc("kwin_effects", "Name of a KWin Effect", "Mouse Click Animation"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Creates an animation whenever a mouse button is clicked. This is useful for screenrecordings/presentations"), QStringLiteral("Accessibility"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/mouse_click.mp4")), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("mousemark"), i18ndc("kwin_effects", "Name of a KWin Effect", "Mouse Mark"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Allows you to draw lines on the desktop"), QStringLiteral("Appearance"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("presentwindows"), i18ndc("kwin_effects", "Name of a KWin Effect", "Present Windows"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Zoom out until all opened windows can be displayed side-by-side"), QStringLiteral("Window Management"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/present_windows.mp4")), true, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("resize"), i18ndc("kwin_effects", "Name of a KWin Effect", "Resize Window"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Resizes windows with a fast texture scale instead of updating contents"), QStringLiteral("Window Management"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("screenedge"), i18ndc("kwin_effects", "Name of a KWin Effect", "Screen Edge"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Highlights a screen edge when approaching"), QStringLiteral("Appearance"), QString(), QUrl(), true, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("screenshot"), i18ndc("kwin_effects", "Name of a KWin Effect", "Screenshot"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Helper effect for KSnapshot"), QStringLiteral("Appearance"), QString(), QUrl(), true, true, #ifdef EFFECT_BUILTINS &createHelper, &ScreenShotEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("sheet"), i18ndc("kwin_effects", "Name of a KWin Effect", "Sheet"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Make modal dialogs smoothly fly in and out when they are shown or hidden"), QStringLiteral("Appearance"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, &SheetEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("showfps"), i18ndc("kwin_effects", "Name of a KWin Effect", "Show FPS"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Display KWin's performance in the corner of the screen"), QStringLiteral("Tools"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("showpaint"), i18ndc("kwin_effects", "Name of a KWin Effect", "Show Paint"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Highlight areas of the desktop that have been recently updated"), QStringLiteral("Tools"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("slide"), i18ndc("kwin_effects", "Name of a KWin Effect", "Slide"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Slide windows across the screen when switching virtual desktops"), QStringLiteral("Virtual Desktop Switching Animation"), QStringLiteral("desktop-animations"), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/slide.ogv")), true, false, #ifdef EFFECT_BUILTINS &createHelper, - nullptr, + &SlideEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("slideback"), i18ndc("kwin_effects", "Name of a KWin Effect", "Slide Back"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Slide back windows when another window is raised"), QStringLiteral("Focus"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("slidingpopups"), i18ndc("kwin_effects", "Name of a KWin Effect", "Sliding popups"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Sliding animation for Plasma popups"), QStringLiteral("Appearance"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/sliding_popups.mp4")), true, false, #ifdef EFFECT_BUILTINS &createHelper, - nullptr, + &SlidingPopupsEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("snaphelper"), i18ndc("kwin_effects", "Name of a KWin Effect", "Snap Helper"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Help you locate the center of the screen when moving a window"), QStringLiteral("Accessibility"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/snap_helper.mp4")), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("startupfeedback"), i18ndc("kwin_effects", "Name of a KWin Effect", "Startup Feedback"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Helper effect for startup feedback"), QStringLiteral("Candy"), QString(), QUrl(), true, true, #ifdef EFFECT_BUILTINS &createHelper, &StartupFeedbackEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("thumbnailaside"), i18ndc("kwin_effects", "Name of a KWin Effect", "Thumbnail Aside"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Display window thumbnails on the edge of the screen"), QStringLiteral("Appearance"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("touchpoints"), i18ndc("kwin_effects", "Name of a KWin Effect", "Touch Points"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Visualize touch points"), QStringLiteral("Appearance"), QString(), QUrl(), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("trackmouse"), i18ndc("kwin_effects", "Name of a KWin Effect", "Track Mouse"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Display a mouse cursor locating effect when activated"), QStringLiteral("Accessibility"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/track_mouse.mp4")), false, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("windowgeometry"), i18ndc("kwin_effects", "Name of a KWin Effect", "Window Geometry"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Display window geometries on move/resize"), QStringLiteral("Appearance"), QString(), QUrl(), false, true, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("wobblywindows"), i18ndc("kwin_effects", "Name of a KWin Effect", "Wobbly Windows"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Deform windows while they are moving"), QStringLiteral("Appearance"), QString(), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/wobbly_windows.ogv")), false, false, #ifdef EFFECT_BUILTINS &createHelper, &WobblyWindowsEffect::supported, nullptr #endif EFFECT_FALLBACK }, { QStringLiteral("zoom"), i18ndc("kwin_effects", "Name of a KWin Effect", "Zoom"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Magnify the entire desktop"), QStringLiteral("Accessibility"), QStringLiteral("magnifiers"), QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/zoom.ogv")), true, false, #ifdef EFFECT_BUILTINS &createHelper, nullptr, nullptr #endif EFFECT_FALLBACK } }; static inline int index(BuiltInEffect effect) { return static_cast(effect); } Effect *create(BuiltInEffect effect) { const EffectData &effectData = s_effectData.at(index(effect)); if (effectData.createFunction == nullptr) { return nullptr; } return effectData.createFunction(); } bool available(const QString &name) { auto it = std::find_if(s_effectData.begin(), s_effectData.end(), [name](const EffectData &data) { return data.name == name; } ); return it != s_effectData.end(); } bool supported(BuiltInEffect effect) { if (effect == BuiltInEffect::Invalid) { return false; } const EffectData &effectData = s_effectData.at(index(effect)); if (effectData.supportedFunction == nullptr) { return true; } return effectData.supportedFunction(); } bool checkEnabledByDefault(BuiltInEffect effect) { if (effect == BuiltInEffect::Invalid) { return false; } const EffectData &effectData = s_effectData.at(index(effect)); if (effectData.enabledFunction == nullptr) { return true; } return effectData.enabledFunction(); } bool enabledByDefault(BuiltInEffect effect) { const EffectData &effectData = s_effectData.at(index(effect)); return effectData.enabled; } QStringList availableEffectNames() { QStringList result; for (const EffectData &data : s_effectData) { if (data.name.isEmpty()) { continue; } result << data.name; } return result; } QList< BuiltInEffect > availableEffects() { QList result; for (int i = index(BuiltInEffect::Invalid) + 1; i <= index(BuiltInEffect::Zoom); ++i) { result << BuiltInEffect(i); } return result; } BuiltInEffect builtInForName(const QString &name) { auto it = std::find_if(s_effectData.begin(), s_effectData.end(), [name](const EffectData &data) { return data.name == name; } ); if (it == s_effectData.end()) { return BuiltInEffect::Invalid; } return BuiltInEffect(std::distance(s_effectData.begin(), it)); } QString nameForEffect(BuiltInEffect effect) { return s_effectData.at(index(effect)).name; } const EffectData &effectData(BuiltInEffect effect) { return s_effectData.at(index(effect)); } } // BuiltInEffects } // namespace diff --git a/effects/flipswitch/flipswitch.h b/effects/flipswitch/flipswitch.h index 859f30c65..add05b4ff 100644 --- a/effects/flipswitch/flipswitch.h +++ b/effects/flipswitch/flipswitch.h @@ -1,164 +1,165 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008, 2009 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_FLIPSWITCH_H #define KWIN_FLIPSWITCH_H #include #include #include #include +#include namespace KWin { class FlipSwitchEffect : public Effect { Q_OBJECT Q_PROPERTY(bool tabBox READ isTabBox) Q_PROPERTY(bool tabBoxAlternative READ isTabBoxAlternative) Q_PROPERTY(int duration READ duration) Q_PROPERTY(int angle READ angle) Q_PROPERTY(qreal xPosition READ xPosition) Q_PROPERTY(qreal yPosition READ yPosition) Q_PROPERTY(bool windowTitle READ isWindowTitle) public: FlipSwitchEffect(); ~FlipSwitchEffect(); virtual void reconfigure(ReconfigureFlags); virtual void prePaintScreen(ScreenPrePaintData& data, int time); virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); virtual void postPaintScreen(); virtual void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, int time); virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); virtual void grabbedKeyboardEvent(QKeyEvent* e); virtual void windowInputMouseEvent(QEvent* e); virtual bool isActive() const; int requestedEffectChainPosition() const override { return 50; } static bool supported(); // for properties bool isTabBox() const { return m_tabbox; } bool isTabBoxAlternative() const { return m_tabboxAlternative; } int duration() const { return m_timeLine.duration(); } int angle() const { return m_angle; } qreal xPosition() const { return m_xPosition; } qreal yPosition() const { return m_yPosition; } bool isWindowTitle() const { return m_windowTitle; } private Q_SLOTS: void toggleActiveCurrent(); void toggleActiveAllDesktops(); void globalShortcutChanged(QAction *action, QKeySequence shortcut); void slotWindowAdded(KWin::EffectWindow* w); void slotWindowClosed(KWin::EffectWindow *w); void slotTabBoxAdded(int mode); void slotTabBoxClosed(); void slotTabBoxUpdated(); void slotTabBoxKeyEvent(QKeyEvent* event); private: class ItemInfo; enum SwitchingDirection { DirectionForward, DirectionBackward }; enum FlipSwitchMode { TabboxMode, CurrentDesktopMode, AllDesktopsMode }; void setActive(bool activate, FlipSwitchMode mode); bool isSelectableWindow(EffectWindow *w) const; void scheduleAnimation(const SwitchingDirection& direction, int distance = 1); void adjustWindowMultiScreen(const EffectWindow *w, WindowPaintData& data); void selectNextOrPreviousWindow(bool forward); inline void selectNextWindow() { selectNextOrPreviousWindow(true); } inline void selectPreviousWindow() { selectNextOrPreviousWindow(false); } /** * Updates the caption of the caption frame. * Taking care of rewording the desktop client. * As well sets the icon for the caption frame. **/ void updateCaption(); QQueue< SwitchingDirection> m_scheduledDirections; EffectWindow* m_selectedWindow; QTimeLine m_timeLine; QTimeLine m_startStopTimeLine; QTimeLine::CurveShape m_currentAnimationShape; QRect m_screenArea; int m_activeScreen; bool m_active; bool m_start; bool m_stop; bool m_animation; bool m_hasKeyboardGrab; FlipSwitchMode m_mode; EffectFrame* m_captionFrame; QFont m_captionFont; EffectWindowList m_flipOrderedWindows; QHash< const EffectWindow*, ItemInfo* > m_windows; QMatrix4x4 m_projectionMatrix; QMatrix4x4 m_modelviewMatrix; // options bool m_tabbox; bool m_tabboxAlternative; float m_angle; float m_xPosition; float m_yPosition; bool m_windowTitle; // Shortcuts QList m_shortcutCurrent; QList m_shortcutAll; }; class FlipSwitchEffect::ItemInfo { public: ItemInfo(); ~ItemInfo(); bool deleted; double opacity; double brightness; double saturation; }; } // namespace #endif diff --git a/effects/frozenapp/package/metadata.desktop b/effects/frozenapp/package/metadata.desktop index 9cdae4554..8d6ba89ad 100644 --- a/effects/frozenapp/package/metadata.desktop +++ b/effects/frozenapp/package/metadata.desktop @@ -1,80 +1,86 @@ [Desktop Entry] Name=Desaturate Unresponsive Applications Name[ca]=Dessatura les aplicacions que no responen Name[ca@valencia]=Dessatura les aplicacions que no responen +Name[da]=Dæmp programmer som ikke svarer Name[el]=Αποκορεσμός χρωμάτων μη αποκρινόμενων εφαρμογών Name[en_GB]=Desaturate Unresponsive Applications Name[es]=Desaturar las aplicaciones que no responden Name[eu]=Desasetu erantzuten ez duten aplikazioak Name[fi]=Vähennä värikylläisyyttä sovelluksilta, jotka eivät vastaa Name[fr]=Désature les applications qui ne répondent pas Name[gl]=Reducir a saturación dos aplicativos que non responden Name[he]=מחשיך יישומים שאינם מגיבים +Name[hu]=Nem válaszoló alkalmazások színtelenítése Name[it]=Desatura le applicazioni che non rispondono +Name[ko]=응답 없는 프로그램을 무채색으로 전환 Name[nl]=Verzadiging van niet responsieve toepassingen verminderen Name[nn]=Fjern fargemetting på ikkje-responsive program Name[pl]=Odbarw nieodpowiadające aplikacje Name[pt]=Reduzir a Saturação das Aplicações Bloqueadas Name[pt_BR]=Reduzir saturação de aplicativos que não respondem Name[ru]=Обесцвечивание зависших приложений Name[sk]=Desaturovať neodpovedajúce aplikácie Name[sl]=Zmanjšaj nasičenost neodzivnih programov Name[sr]=Посивљавање програма без одзива Name[sr@ijekavian]=Посивљавање програма без одзива Name[sr@ijekavianlatin]=Posivljavanje programa bez odziva Name[sr@latin]=Posivljavanje programa bez odziva Name[sv]=Avmätta oemottagliga program Name[tr]=Yanıt Vermeyen Uygulamaların Yoğunluğunu Kaldır Name[uk]=Зненасичення вікон, які не відповідають на запити Name[x-test]=xxDesaturate Unresponsive Applicationsxx Name[zh_CN]=对未响应应用程序降低饱和度 Name[zh_TW]=淡化無回應的應用程式 Icon=preferences-system-windows-effect-frozenapp Comment=Desaturate windows of unresponsive (frozen) applications Comment[ca]=Dessatura les finestres de les aplicacions que no responen (congelades) Comment[ca@valencia]=Dessatura les finestres de les aplicacions que no responen (congelades) +Comment[da]=Dæmp vinduerne for programmer der ikke svarer (er frosset) Comment[el]=Αποκορεσμός χρωμάτων παραθύρων μη αποκρινόμενων (κολλημένων) εφαρμογών Comment[en_GB]=Desaturate windows of unresponsive (frozen) applications Comment[es]=Desaturar las ventanas de las aplicaciones que no responden (congeladas) Comment[eu]=Desasetu erantzuten ez duten aplikazioen leihoak (izoztuak) Comment[fi]=Vähennä värikylläisyyttä sovelluksilta, jotka eivät vastaa (ovat jumittuneet) Comment[fr]=Désature les fenêtres des applications qui ne répondent pas (gelées) Comment[gl]=Reducir a saturación das xanelas de aplicativos que non responden (que queradon conxelados). Comment[he]=מחשיך חלונות של יישומים שאינם מגיבים (תקועים) +Comment[hu]=Színteleníti a nem válaszoló, lefagyott alkalmazások ablakait Comment[it]=Desatura le finestre delle applicazione che non rispondono (bloccate) +Comment[ko]=응답 없는 프로그램 창을 무채색으로 전환 Comment[nl]=Verzadiging van vensters van niet responsieve (bevroren) toepassingen verminderen Comment[nn]=Fjern fargemetting på vindauge på program som ikkje lenger reagerer Comment[pl]=Odbarw okna nieodpowiadających (zawieszonych) aplikacji Comment[pt]=Reduzir a saturação das janelas das aplicações sem resposta (bloqueadas) Comment[pt_BR]=Reduzir saturação de janelas de aplicativos que não respondem (travados) Comment[ru]=Обесцвечивание окон приложений, не отвечающих на запросы Comment[sk]=Desaturovať okná neodpovedajúcich aplikácií Comment[sl]=Zmanjšaj nasičenost oken neodzivnih (zamrznjenih) programov Comment[sr]=Прозори програма који се не одазивају (смрзнутих) бивају посивљени Comment[sr@ijekavian]=Прозори програма који се не одазивају (смрзнутих) бивају посивљени Comment[sr@ijekavianlatin]=Prozori programa koji se ne odazivaju (smrznutih) bivaju posivljeni Comment[sr@latin]=Prozori programa koji se ne odazivaju (smrznutih) bivaju posivljeni Comment[sv]=Avmätta fönster för oemottagliga (frysta) program Comment[tr]=Yanıt vermeyen (donmuş) uygulamaların pencerelerini soldur Comment[uk]=Зменшення насиченості кольорів вікон програм, які не відповідають на запити (повисли) Comment[x-test]=xxDesaturate windows of unresponsive (frozen) applicationsxx Comment[zh_CN]=将未响应 (冻结) 的应用程序窗口的饱和度降低 Comment[zh_TW]=淡化無回應(凍結)的應用程式視窗 Type=Service X-KDE-ServiceTypes=KWin/Effect,KCModule X-KDE-PluginInfo-Author=Kai Uwe Broulik X-KDE-PluginInfo-Email=kde@privat.broulik.de X-KDE-PluginInfo-Name=kwin4_effect_frozenapp X-KDE-PluginInfo-Version=1.0 X-KDE-PluginInfo-Category=Appearance X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-EnabledByDefault=true X-KDE-Ordering=60 X-Plasma-API=javascript X-Plasma-MainScript=code/main.js X-KDE-PluginKeyword=kwin4_effect_frozenapp X-KDE-Library=kcm_kwin4_genericscripted X-KDE-ParentComponents=kwin4_effect_frozen X-KWin-Config-TranslationDomain=kwin_effects diff --git a/effects/logout/package/metadata.desktop b/effects/logout/package/metadata.desktop index c2e5a6a2d..c4141f00b 100644 --- a/effects/logout/package/metadata.desktop +++ b/effects/logout/package/metadata.desktop @@ -1,120 +1,122 @@ [Desktop Entry] Name=logout Name[ca]=sortida Name[ca@valencia]=eixida Name[cs]=odhlášení +Name[da]=log ud Name[de]=Abmeldung Name[el]=αποσύνδεση Name[en_GB]=logout Name[es]=cerrar la sesión Name[eu]=itxi saioa Name[fi]=kirjaudu ulos Name[fr]=Déconnexion Name[gl]=saír Name[he]=התנתקות Name[hu]=Kijelentkezés Name[ia]=Clausura de session Name[it]=uscita +Name[ko]=로그아웃 Name[nl]=afmelden Name[nn]=Utlogging Name[pl]=wylogowanie Name[pt]=encerramento Name[pt_BR]=sair Name[ru]=Завершение сеанса Name[sk]=odhlásiť Name[sl]=odjavi Name[sr]=Одјава Name[sr@ijekavian]=Одјава Name[sr@ijekavianlatin]=Odjava Name[sr@latin]=Odjava Name[sv]=utloggning Name[tr]=çıkış yap -Name[uk]=вихід +Name[uk]=Вихід Name[x-test]=xxlogoutxx Name[zh_CN]=注销 Name[zh_TW]=登出 Icon=preferences-system-windows-effect-logout Comment=Smoothly fade to the desktop when logging in Comment[ar]=اظهار سطح المكتب بنعومة عند الولوج Comment[bg]=Плавен преход към работния плот при влизане Comment[bs]=Glatko pretapa na površ pri prijavljivanju Comment[ca]=Transició suau a l'escriptori en connectar-se Comment[ca@valencia]=Transició suau a l'escriptori en connectar-se Comment[cs]=Plynule zobrazit plochu po přihlášení Comment[da]=Toner blidt til skrivebordet når der logges ind Comment[de]=Blendet die Arbeitsfläche nach der Anmeldung langsam ein. Comment[el]=Ομαλή εμφάνιση της επιφάνειας εργασίας κατά τη σύνδεση Comment[en_GB]=Smoothly fade to the desktop when logging in Comment[es]=Desvanece el escritorio cuando se inicia la sesión Comment[et]=Töölaua sujuv ilmumine sisselogimisel Comment[eu]=Mahaigaina pixkanaka desagertzen da saioa hastean Comment[fi]=Häivytä pehmeästi työpöydälle kirjauduttaessa sisään Comment[fr]=Effectue un dégradé progressif vers le bureau lors de la connexion Comment[fy]=Lit it opstartskerm ferdizenje nei it buroblêd ûnder it oanmelden Comment[ga]=Céimnigh go dtí an deasc go réidh ag am logála isteach Comment[gl]=Suaviza a entrada no escritorio cun efecto de esvaecemento Comment[gu]=જ્યારે પ્રવેશ કરવામાં આવે ત્યારે ડેસ્કટોપને સરળ રીતે ઝાંખું કરે છે Comment[he]=עמעום המסך בהדרגה בעת הכניסה למערכת Comment[hne]=जब लागइन होथे तब डेस्कटाप मं धीरे से फेड होथे Comment[hr]=Lagano pojavljivanje radne površine prilikom prijave na sustav Comment[hu]=Folyamatos átmenet az asztalra bejelentkezéskor Comment[ia]=Dulcemente pallidi le scriptorio quando tu accede in identification Comment[id]=Memudar dengan halus ke desktop ketika log masuk Comment[is]=Láta skjáborð koma mjúklega í ljós við innstimplun Comment[it]=Dissolvenza graduale del desktop all'accesso Comment[ja]=ログイン時に滑らかにデスクトップを表示します Comment[kk]=Кіргенде үстелге біртіндеп ауысу Comment[km]=លិច​​បន្តិចម្ដងៗ​ទៅ​ផ្ទៃតុ​នៅពេល​ចូល Comment[kn]=ಪ್ರವೇಶಿಸುವಾಗ (ಲಾಗಿಂಗ್ ಇನ್), ನಾಜೂಕಾಗಿ ಗಣಕತೆರೆಗೆ ಮಸುಕುಗೊಳಿಸು Comment[ko]=로그인할 때 부드럽게 데스크톱을 보여줍니다 Comment[lt]=Sklandžiai išryškina darbalaukį prisijungiant Comment[lv]=Gludeni parādīt darbvirsmu, piesakoties Comment[ml]=അകത്തേക്ക് കടക്കുംബോള്‍ പണിയിടത്തിലേക്ക് തനിയെ സ്മൂത് ആയി കടക്കുന്നു Comment[mr]=प्रवेश करताना डेस्कटॉप फीका करा Comment[nb]=Ton jevnt inn til skrivebordet når det logges inn Comment[nds]=Bi't Anmellen week na den Schriefdisch överblennen Comment[nl]=Laat het opstartscherm vervagen naar het opkomende bureaublad tijdens het aanmelden Comment[nn]=Ton inn skrivebordet ved innlogging Comment[pa]=ਜਦੋਂ ਲਾਗਇਨ ਕਰਨਾ ਹੋਵੇ ਤਾਂ ਹੌਲੀ ਹੌਲੀ ਡੈਸਕਟਾਪ ਨੂੰ ਫਿੱਕਾ ਕਰੋ Comment[pl]=Płynne rozjaśnianie do pulpitu podczas logowania Comment[pt]=Desvanecer suavemente para o ecrã ao ligar-se Comment[pt_BR]=Suaviza o desaparecimento da área de trabalho ao fazer a autenticação Comment[ro]=Estompează lin biroul la autentificare Comment[ru]=Плавное проявление рабочего стола при входе в систему Comment[si]=පිවිසීමේදී වැඩතලයට සුමුදු අවපැහැකිරීමක් ලබාදෙන්න Comment[sk]=Plynule zobrazí plochu pri prihlásení Comment[sl]=Pri prijavi se namizje prikaže postopoma Comment[sr]=Глатко претапа на површ при пријављивању Comment[sr@ijekavian]=Глатко претапа на површ при пријављивању Comment[sr@ijekavianlatin]=Glatko pretapa na površ pri prijavljivanju Comment[sr@latin]=Glatko pretapa na površ pri prijavljivanju Comment[sv]=Tona mjukt till skrivbordet vid inloggning Comment[ta]=Smoothly fade to the desktop when logging in Comment[te]=లాగిన్ అవుతున్నప్పుడు రంగస్థలమునకు సున్నితంగా ఫేడ్ చేయుము Comment[th]=ค่อย ๆ ปรับภาพพื้นที่ทำงานให้ชัดขึ้นอย่างนุ่มนวลเมื่อทำการล็อกอิน Comment[tr]=Giriş yapılırken masaüstünü pürüzsüzce belirginleştir Comment[ug]=تىزىمغا كىرگەندە ئۈستەلئۈستىگە تەكشى سۇسلاشتۇر Comment[uk]=Плавна поява стільниці під час входу Comment[vi]=Làm mờ dần màn hình khi đăng nhập Comment[wa]=Dous fondou viè l' sicribanne a l' elodjaedje Comment[x-test]=xxSmoothly fade to the desktop when logging inxx Comment[zh_CN]=登录时平滑淡入到桌面 Comment[zh_TW]=登入時平順地淡入桌面 Type=Service X-KDE-ServiceTypes=KWin/Effect,KCModule X-KDE-PluginInfo-Author=Lubos Lunak, Kai Uwe Broulik, Martin Gräßlin, Marco MArtin X-KDE-PluginInfo-Email=l.lunak@kde.org, kde@privat.broulik.de, mgraesslin@kde.org, mart@kde.org X-KDE-PluginInfo-Name=kwin4_effect_logout X-KDE-PluginInfo-Version=0.2.0 X-KDE-PluginInfo-Category=Appearance X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-EnabledByDefault=true X-KDE-Ordering=40 X-Plasma-API=javascript X-Plasma-MainScript=code/main.js X-KDE-PluginKeyword=kwin4_effect_logout X-KDE-Library=kcm_kwin4_genericscripted X-KDE-ParentComponents=kwin4_effect_logout X-KWin-Config-TranslationDomain=kwin_effects diff --git a/effects/minimizeanimation/minimizeanimation.cpp b/effects/minimizeanimation/minimizeanimation.cpp index fdbb3da28..3d00d3235 100644 --- a/effects/minimizeanimation/minimizeanimation.cpp +++ b/effects/minimizeanimation/minimizeanimation.cpp @@ -1,156 +1,160 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks 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 "minimizeanimation.h" #include #include namespace KWin { MinimizeAnimationEffect::MinimizeAnimationEffect() { mActiveAnimations = 0; connect(effects, SIGNAL(windowDeleted(KWin::EffectWindow*)), this, SLOT(slotWindowDeleted(KWin::EffectWindow*))); connect(effects, SIGNAL(windowMinimized(KWin::EffectWindow*)), this, SLOT(slotWindowMinimized(KWin::EffectWindow*))); connect(effects, SIGNAL(windowUnminimized(KWin::EffectWindow*)), this, SLOT(slotWindowUnminimized(KWin::EffectWindow*))); } +bool MinimizeAnimationEffect::supported() +{ + return effects->animationsSupported(); +} void MinimizeAnimationEffect::prePaintScreen(ScreenPrePaintData& data, int time) { QHash< EffectWindow*, QTimeLine* >::iterator entry = mTimeLineWindows.begin(); bool erase = false; while (entry != mTimeLineWindows.end()) { QTimeLine *timeline = entry.value(); if (entry.key()->isMinimized()) { timeline->setCurrentTime(timeline->currentTime() + time); erase = (timeline->currentValue() >= 1.0f); } else { timeline->setCurrentTime(timeline->currentTime() - time); erase = (timeline->currentValue() <= 0.0f); } if (erase) { delete timeline; entry = mTimeLineWindows.erase(entry); } else ++entry; } mActiveAnimations = mTimeLineWindows.count(); if (mActiveAnimations > 0) // We need to mark the screen windows as transformed. Otherwise the // whole screen won't be repainted, resulting in artefacts data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; effects->prePaintScreen(data, time); } void MinimizeAnimationEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { // Schedule window for transformation if the animation is still in // progress if (mTimeLineWindows.contains(w)) { // We'll transform this window data.setTransformed(); w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE); } effects->prePaintWindow(w, data, time); } void MinimizeAnimationEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { QHash< EffectWindow*, QTimeLine* >::const_iterator entry = mTimeLineWindows.constFind(w); if (entry != mTimeLineWindows.constEnd()) { // 0 = not minimized, 1 = fully minimized double progress = entry.value()->currentValue(); QRect geo = w->geometry(); QRect icon = w->iconGeometry(); // If there's no icon geometry, minimize to the center of the screen if (!icon.isValid()) icon = QRect(effects->virtualScreenGeometry().center(), QSize(0, 0)); data *= QVector2D(interpolate(1.0, icon.width() / (double)geo.width(), progress), interpolate(1.0, icon.height() / (double)geo.height(), progress)); data.setXTranslation((int)interpolate(data.xTranslation(), icon.x() - geo.x(), progress)); data.setYTranslation((int)interpolate(data.yTranslation(), icon.y() - geo.y(), progress)); data.multiplyOpacity(0.1 + (1 - progress) * 0.9); } // Call the next effect. effects->paintWindow(w, mask, region, data); } void MinimizeAnimationEffect::postPaintScreen() { if (mActiveAnimations > 0) // Repaint the workspace so that everything would be repainted next time effects->addRepaintFull(); mActiveAnimations = mTimeLineWindows.count(); // Call the next effect. effects->postPaintScreen(); } void MinimizeAnimationEffect::slotWindowDeleted(EffectWindow* w) { delete mTimeLineWindows.take(w); } void MinimizeAnimationEffect::slotWindowMinimized(EffectWindow* w) { if (effects->activeFullScreenEffect()) return; QTimeLine *timeline; if (mTimeLineWindows.contains(w)) { timeline = mTimeLineWindows[w]; } else { timeline = new QTimeLine(animationTime(250), this); mTimeLineWindows.insert(w, timeline); } timeline->setCurveShape(QTimeLine::EaseInCurve); timeline->setCurrentTime(0.0); } void MinimizeAnimationEffect::slotWindowUnminimized(EffectWindow* w) { if (effects->activeFullScreenEffect()) return; QTimeLine *timeline; if (mTimeLineWindows.contains(w)) { timeline = mTimeLineWindows[w]; } else { timeline = new QTimeLine(animationTime(250), this); mTimeLineWindows.insert(w, timeline); } timeline->setCurveShape(QTimeLine::EaseInOutCurve); timeline->setCurrentTime(timeline->duration()); } bool MinimizeAnimationEffect::isActive() const { return !mTimeLineWindows.isEmpty(); } } // namespace diff --git a/effects/minimizeanimation/minimizeanimation.h b/effects/minimizeanimation/minimizeanimation.h index f6fd13d2e..cbdfec962 100644 --- a/effects/minimizeanimation/minimizeanimation.h +++ b/effects/minimizeanimation/minimizeanimation.h @@ -1,64 +1,66 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_MINIMIZEANIMATION_H #define KWIN_MINIMIZEANIMATION_H // Include with base class for effects. #include class QTimeLine; namespace KWin { /** * Animates minimize/unminimize **/ class MinimizeAnimationEffect : public Effect { Q_OBJECT public: MinimizeAnimationEffect(); virtual void prePaintScreen(ScreenPrePaintData& data, int time); virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); virtual void postPaintScreen(); virtual bool isActive() const; int requestedEffectChainPosition() const override { return 50; } + static bool supported(); + public Q_SLOTS: void slotWindowDeleted(KWin::EffectWindow *w); void slotWindowMinimized(KWin::EffectWindow *w); void slotWindowUnminimized(KWin::EffectWindow *w); private: QHash< EffectWindow*, QTimeLine* > mTimeLineWindows; int mActiveAnimations; }; } // namespace #endif diff --git a/effects/mouseclick/mouseclick.h b/effects/mouseclick/mouseclick.h index 40cddeb4c..afcd170bc 100644 --- a/effects/mouseclick/mouseclick.h +++ b/effects/mouseclick/mouseclick.h @@ -1,184 +1,185 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 Filip Wieladek This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_MOUSECLICK_H #define KWIN_MOUSECLICK_H #include #include #include #include +#include namespace KWin { #define BUTTON_COUNT 3 class MouseEvent { public: int m_button; QPoint m_pos; int m_time; EffectFrame* m_frame; bool m_press; public: MouseEvent(int button, QPoint point, int time, EffectFrame* frame, bool press) : m_button(button), m_pos(point), m_time(time), m_frame(frame), m_press(press) {}; ~MouseEvent() { delete m_frame; } }; class MouseButton { public: QString m_labelUp; QString m_labelDown; Qt::MouseButtons m_button; bool m_isPressed; int m_time; public: MouseButton(QString label, Qt::MouseButtons button) : m_labelUp(label), m_labelDown(label), m_button(button), m_isPressed(false), m_time(0) { m_labelDown.append(i18n("↓")); m_labelUp.append(i18n("↑")); }; inline void setPressed(bool pressed) { if (m_isPressed != pressed) { m_isPressed = pressed; if (pressed) m_time = 0; } } }; class MouseClickEffect : public Effect { Q_OBJECT Q_PROPERTY(QColor color1 READ color1) Q_PROPERTY(QColor color2 READ color2) Q_PROPERTY(QColor color3 READ color3) Q_PROPERTY(qreal lineWidth READ lineWidth) Q_PROPERTY(int ringLife READ ringLife) Q_PROPERTY(int ringSize READ ringSize) Q_PROPERTY(int ringCount READ ringCount) Q_PROPERTY(bool showText READ isShowText) Q_PROPERTY(QFont font READ font) Q_PROPERTY(bool enabled READ isEnabled) public: MouseClickEffect(); ~MouseClickEffect(); virtual void reconfigure(ReconfigureFlags); virtual void prePaintScreen(ScreenPrePaintData& data, int time); virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); virtual void postPaintScreen(); virtual bool isActive() const; // for properties QColor color1() const { return m_colors[0]; } QColor color2() const { return m_colors[1]; } QColor color3() const { return m_colors[2]; } qreal lineWidth() const { return m_lineWidth; } int ringLife() const { return m_ringLife; } int ringSize() const { return m_ringMaxSize; } int ringCount() const { return m_ringCount; } bool isShowText() const { return m_showText; } QFont font() const { return m_font; } bool isEnabled() const { return m_enabled; } private Q_SLOTS: void toggleEnabled(); void slotMouseChanged(const QPoint& pos, const QPoint& old, Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers); private: EffectFrame* createEffectFrame(const QPoint& pos, const QString& text); inline void drawCircle(const QColor& color, float cx, float cy, float r); inline void paintScreenSetup(int mask, QRegion region, ScreenPaintData& data); inline void paintScreenFinish(int mask, QRegion region, ScreenPaintData& data); inline bool isReleased(Qt::MouseButtons button, Qt::MouseButtons buttons, Qt::MouseButtons oldButtons); inline bool isPressed(Qt::MouseButtons button, Qt::MouseButtons buttons, Qt::MouseButtons oldButtons); inline float computeRadius(const MouseEvent* click, int ring); inline float computeAlpha(const MouseEvent* click, int ring); void repaint(); void drawCircleGl(const QColor& color, float cx, float cy, float r); void drawCircleXr(const QColor& color, float cx, float cy, float r); void drawCircleQPainter(const QColor& color, float cx, float cy, float r); void paintScreenSetupGl(int mask, QRegion region, ScreenPaintData& data); void paintScreenFinishGl(int mask, QRegion region, ScreenPaintData& data); QColor m_colors[BUTTON_COUNT]; int m_ringCount; float m_lineWidth; float m_ringLife; float m_ringMaxSize; bool m_showText; QFont m_font; QList m_clicks; MouseButton* m_buttons[BUTTON_COUNT]; bool m_enabled; }; } // namespace #endif diff --git a/effects/mouseclick/mouseclick_config.desktop b/effects/mouseclick/mouseclick_config.desktop index 4cfd06267..a8756fa0a 100644 --- a/effects/mouseclick/mouseclick_config.desktop +++ b/effects/mouseclick/mouseclick_config.desktop @@ -1,55 +1,55 @@ [Desktop Entry] Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kwin_mouseclick_config X-KDE-ParentComponents=mouseclick Name=Mouse Click Animation Name[bs]=Animacija klika mišem Name[ca]=Animació de clic de ratolí Name[ca@valencia]=Animació de clic de ratolí Name[cs]=Animace kliknutí myši Name[da]=Animation af museklik Name[de]=Animation für Mausklicks Name[el]=Εφέ κίνησης με κλικ του ποντικιού Name[en_GB]=Mouse Click Animation Name[es]=Animación del clic de ratón Name[et]=Hiireklõpsu animeerimine Name[eu]=Sagu-klikaren animazioa Name[fi]=Hiiren napsautuksen animointi Name[fr]=Animation du clic de la souris Name[gl]=Animación ao premer o rato Name[he]=הנפשה של לחיצה עם העכבר Name[hu]=Egérkattintás animáció Name[ia]=Animation de click de mus -Name[id]=Animasi Klik Tetikus +Name[id]=Animasi Klik Mouse Name[is]=Hreyfingar við músarsmell Name[it]=Animazione del clic del mouse Name[ja]=マウスクリックアニメーション Name[kk]=Тышқанды түрту анимациясы Name[ko]=마우스 클릭 애니메이션 Name[lt]=Spragtelėjimo pele animacija Name[mr]=माऊस क्लिक ऍनीमेशन Name[nb]=Animer ved museklikk Name[nds]=Muusklick-Animeren Name[nl]=Animatie van muisklik Name[nn]=Museklikkanimasjon Name[pa]=ਮਾਊਸ ਕਲਿੱਕ ਐਨੀਮੇਸ਼ਨ Name[pl]=Animacja kliknięcia myszą Name[pt]=Animação do Botão do Rato Name[pt_BR]=Animação de clique do mouse Name[ro]=Animație la clic de maus Name[ru]=Анимация щелчка мышью Name[sk]=Animácia kliknutia myšou Name[sl]=Animacija klika z miško Name[sr]=Анимација на клик мишем Name[sr@ijekavian]=Анимација на клик мишем Name[sr@ijekavianlatin]=Animacija na klik mišem Name[sr@latin]=Animacija na klik mišem Name[sv]=Animering av musklick Name[tr]=Fare Tıklama Animasyonu Name[uk]=Анімація за клацанням миші Name[x-test]=xxMouse Click Animationxx Name[zh_CN]=鼠标点击动画 Name[zh_TW]=滑鼠點擊動畫 diff --git a/effects/mousemark/mousemark_config.desktop b/effects/mousemark/mousemark_config.desktop index b03e3c9d7..dc8b9813f 100644 --- a/effects/mousemark/mousemark_config.desktop +++ b/effects/mousemark/mousemark_config.desktop @@ -1,85 +1,85 @@ [Desktop Entry] Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kwin_mousemark_config X-KDE-ParentComponents=mousemark Name=Mouse Mark Name[af]=Muismerk Name[ar]=علامة الفأرة Name[be]=Адметка мышы Name[be@latin]=Malavańnie myššu Name[bg]=Чертане с мишката Name[bs]=Otisci miša Name[ca]=Marca amb el ratolí Name[ca@valencia]=Marca amb el ratolí Name[cs]=Značkovač Name[csb]=Merk mëszë Name[da]=Muse-tusch Name[de]=Mausspur Name[el]=Σήμανση ποντικιού Name[en_GB]=Mouse Mark Name[eo]=Musmarko Name[es]=Marcación con el ratón Name[et]=Hiirejälg Name[eu]=Sagu-marka Name[fa]=نشان موشی Name[fi]=Hiiren jäljet Name[fr]=Tracé à la souris Name[fy]=Mûs markearing Name[ga]=Mouse Mark Name[gl]=Marca do rato Name[gu]=માઉસ નિશાની Name[he]=סימון בעזרת העכבר Name[hi]=माउस मार्क Name[hne]=मुसुवा चिनहा Name[hr]=Oznaka miša Name[hu]=Egérnyom Name[ia]=Marca de mus -Name[id]=Tanda Tetikus +Name[id]=Tanda Mouse Name[is]=Músarspor Name[it]=Pennarello Name[ja]=マウスマーク Name[kk]=Тышқан белгісі Name[km]=សម្គាល់​កណ្តុរ​ Name[kn]=ಮೂಷಕ (ಮೌಸ್) ಮುದ್ರೆ Name[ko]=마우스 자취 Name[lt]=Piešimas pele Name[lv]=Peles zīmēšana Name[mai]=माउस मार्क Name[mk]=Цртање со глушец Name[ml]=മൌസിന്റെ അടയാളം Name[mr]=माऊस मार्क Name[nb]=Musemerke Name[nds]=Muus-Mark Name[ne]=माउस मार्क Name[nl]=Muismarkering Name[nn]=Muselinjer Name[pa]=ਮਾਊਸ ਨਿਸ਼ਾਨ Name[pl]=Znacznik myszy Name[pt]=Marcação com o Rato Name[pt_BR]=Anotar com o mouse Name[ro]=Urme de maus Name[ru]=Рисование мышью Name[se]=Sáhpánmearka Name[si]=මවුස ලකුණ Name[sk]=Stopa myši Name[sl]=Risanje Name[sr]=Отисци миша Name[sr@ijekavian]=Отисци миша Name[sr@ijekavianlatin]=Otisci miša Name[sr@latin]=Otisci miša Name[sv]=Markera med musen Name[ta]=எலியச் சுட்டி Name[te]=మౌస్ గుర్తు Name[tg]=Рисование отметок на экране Name[th]=วาดด้วยเมาส์ Name[tr]=Fare İzi Name[ug]=چاشقىنەك بەلگىسى Name[uk]=Позначки мишкою Name[vi]=Dấu chuột Name[wa]=Marke di sori Name[x-test]=xxMouse Markxx Name[zh_CN]=鼠标标记 Name[zh_TW]=滑鼠標記 diff --git a/effects/presentwindows/presentwindows.h b/effects/presentwindows/presentwindows.h index 4d7f2b166..6e95bdada 100644 --- a/effects/presentwindows/presentwindows.h +++ b/effects/presentwindows/presentwindows.h @@ -1,350 +1,351 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2008 Lucas Murray This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_PRESENTWINDOWS_H #define KWIN_PRESENTWINDOWS_H #include "presentwindows_proxy.h" #include +class QMouseEvent; class QElapsedTimer; class QQuickView; namespace KWin { class CloseWindowView : public QObject { Q_OBJECT public: explicit CloseWindowView(QObject *parent = 0); void windowInputMouseEvent(QMouseEvent* e); void disarm(); void show(); void hide(); bool isVisible() const; // delegate to QWindow int width() const; int height() const; QSize size() const; QRect geometry() const; WId winId() const; void setGeometry(const QRect &geometry); QPoint mapFromGlobal(const QPoint &pos) const; Q_SIGNALS: void requestClose(); private: QScopedPointer m_armTimer; QScopedPointer m_window; bool m_visible; QPoint m_pos; bool m_posIsValid; }; /** * Expose-like effect which shows all windows on current desktop side-by-side, * letting the user select active window. **/ class PresentWindowsEffect : public Effect { Q_OBJECT Q_PROPERTY(int layoutMode READ layoutMode) Q_PROPERTY(bool showCaptions READ isShowCaptions) Q_PROPERTY(bool showIcons READ isShowIcons) Q_PROPERTY(bool doNotCloseWindows READ isDoNotCloseWindows) Q_PROPERTY(bool ignoreMinimized READ isIgnoreMinimized) Q_PROPERTY(int accuracy READ accuracy) Q_PROPERTY(bool fillGaps READ isFillGaps) Q_PROPERTY(int fadeDuration READ fadeDuration) Q_PROPERTY(bool showPanel READ isShowPanel) Q_PROPERTY(int leftButtonWindow READ leftButtonWindow) Q_PROPERTY(int rightButtonWindow READ rightButtonWindow) Q_PROPERTY(int middleButtonWindow READ middleButtonWindow) Q_PROPERTY(int leftButtonDesktop READ leftButtonDesktop) Q_PROPERTY(int middleButtonDesktop READ middleButtonDesktop) Q_PROPERTY(int rightButtonDesktop READ rightButtonDesktop) // TODO: electric borders private: // Structures struct WindowData { bool visible; bool deleted; bool referenced; double opacity; double highlight; EffectFrame* textFrame; EffectFrame* iconFrame; }; typedef QHash DataHash; struct GridSize { int columns; int rows; }; public: PresentWindowsEffect(); virtual ~PresentWindowsEffect(); virtual void reconfigure(ReconfigureFlags); virtual void* proxy(); // Screen painting virtual void prePaintScreen(ScreenPrePaintData &data, int time); virtual void paintScreen(int mask, QRegion region, ScreenPaintData &data); virtual void postPaintScreen(); // Window painting virtual void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, int time); virtual void paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data); // User interaction virtual bool borderActivated(ElectricBorder border); virtual void windowInputMouseEvent(QEvent *e); virtual void grabbedKeyboardEvent(QKeyEvent *e); virtual bool isActive() const; bool touchDown(quint32 id, const QPointF &pos, quint32 time) override; bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override; bool touchUp(quint32 id, quint32 time) override; int requestedEffectChainPosition() const override { return 70; } enum { LayoutNatural, LayoutRegularGrid, LayoutFlexibleGrid }; // Layout modes enum PresentWindowsMode { ModeAllDesktops, // Shows windows of all desktops ModeCurrentDesktop, // Shows windows on current desktop ModeSelectedDesktop, // Shows windows of selected desktop via property (m_desktop) ModeWindowGroup, // Shows windows selected via property ModeWindowClass // Shows all windows of same class as selected class }; enum WindowMouseAction { WindowNoAction = 0, // Nothing WindowActivateAction = 1, // Activates the window and deactivates the effect WindowExitAction = 2, // Deactivates the effect without activating new window WindowToCurrentDesktopAction = 3, // Brings window to current desktop WindowToAllDesktopsAction = 4, // Brings window to all desktops WindowMinimizeAction = 5 // Minimize the window }; enum DesktopMouseAction { DesktopNoAction = 0, // nothing DesktopActivateAction = 1, // Activates the window and deactivates the effect DesktopExitAction = 2, // Deactivates the effect without activating new window DesktopShowDesktopAction = 3 // Minimizes all windows }; // for properties int layoutMode() const { return m_layoutMode; } bool isShowCaptions() const { return m_showCaptions; } bool isShowIcons() const { return m_showIcons; } bool isDoNotCloseWindows() const { return m_doNotCloseWindows; } bool isIgnoreMinimized() const { return m_ignoreMinimized; } int accuracy() const { return m_accuracy; } bool isFillGaps() const { return m_fillGaps; } int fadeDuration() const { return m_fadeDuration; } bool isShowPanel() const { return m_showPanel; } int leftButtonWindow() const { return m_leftButtonWindow; } int rightButtonWindow() const { return m_rightButtonWindow; } int middleButtonWindow() const { return m_middleButtonWindow; } int leftButtonDesktop() const { return m_leftButtonDesktop; } int middleButtonDesktop() const { return m_middleButtonDesktop; } int rightButtonDesktop() const { return m_rightButtonDesktop; } public Q_SLOTS: void setActive(bool active); void toggleActive() { m_mode = ModeCurrentDesktop; setActive(!m_activated); } void toggleActiveAllDesktops() { m_mode = ModeAllDesktops; setActive(!m_activated); } void toggleActiveClass(); // slots for global shortcut changed // needed to toggle the effect void globalShortcutChanged(QAction *action, const QKeySequence &seq); // EffectsHandler void slotWindowAdded(KWin::EffectWindow *w); void slotWindowClosed(KWin::EffectWindow *w); void slotWindowDeleted(KWin::EffectWindow *w); void slotWindowGeometryShapeChanged(KWin::EffectWindow *w, const QRect &old); // atoms void slotPropertyNotify(KWin::EffectWindow* w, long atom); private Q_SLOTS: void closeWindow(); void elevateCloseWindow(); protected: // Window rearranging void rearrangeWindows(); void reCreateGrids(); void calculateWindowTransformations(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager, bool external = false); void calculateWindowTransformationsClosest(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager); void calculateWindowTransformationsKompose(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager); void calculateWindowTransformationsNatural(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager); // Helper functions for window rearranging inline double aspectRatio(EffectWindow *w) { return w->width() / double(w->height()); } inline int widthForHeight(EffectWindow *w, int height) { return int((height / double(w->height())) * w->width()); } inline int heightForWidth(EffectWindow *w, int width) { return int((width / double(w->width())) * w->height()); } bool isOverlappingAny(EffectWindow *w, const QHash &targets, const QRegion &border); // Filter box void updateFilterFrame(); // Helper functions bool isSelectableWindow(EffectWindow *w); bool isVisibleWindow(EffectWindow *w); void setHighlightedWindow(EffectWindow *w); EffectWindow* relativeWindow(EffectWindow *w, int xdiff, int ydiff, bool wrap) const; EffectWindow* findFirstWindow() const; void updateCloseWindow(); // Helper functions for mouse actions void mouseActionWindow(WindowMouseAction& action); void mouseActionDesktop(DesktopMouseAction& action); void inputEventUpdate(const QPoint &pos, QEvent::Type type = QEvent::None, Qt::MouseButton button = Qt::NoButton); private: PresentWindowsEffectProxy m_proxy; friend class PresentWindowsEffectProxy; // User configuration settings QList m_borderActivate; QList m_borderActivateAll; QList m_borderActivateClass; int m_layoutMode; bool m_showCaptions; bool m_showIcons; bool m_doNotCloseWindows; int m_accuracy; bool m_fillGaps; double m_fadeDuration; bool m_showPanel; // Activation bool m_activated; bool m_ignoreMinimized; double m_decalOpacity; bool m_hasKeyboardGrab; PresentWindowsMode m_mode; int m_desktop; EffectWindowList m_selectedWindows; EffectWindow *m_managerWindow; QString m_class; bool m_needInitialSelection; // Window data WindowMotionManager m_motionManager; DataHash m_windowData; EffectWindow *m_highlightedWindow; // Grid layout info QList m_gridSizes; // Filter box EffectFrame* m_filterFrame; QString m_windowFilter; // Shortcut - needed to toggle the effect QList shortcut; QList shortcutAll; QList shortcutClass; // Atoms // Present windows for all windows of given desktop // -1 for all desktops long m_atomDesktop; // Present windows for group of window ids long m_atomWindows; // Mouse Actions WindowMouseAction m_leftButtonWindow; WindowMouseAction m_middleButtonWindow; WindowMouseAction m_rightButtonWindow; DesktopMouseAction m_leftButtonDesktop; DesktopMouseAction m_middleButtonDesktop; DesktopMouseAction m_rightButtonDesktop; CloseWindowView* m_closeView; EffectWindow* m_closeWindow; Qt::Corner m_closeButtonCorner; struct { quint32 id = 0; bool active = false; } m_touch; QAction *m_exposeAction; QAction *m_exposeAllAction; QAction *m_exposeClassAction; }; } // namespace #endif diff --git a/effects/showfps/showfps.cpp b/effects/showfps/showfps.cpp index f95fb5e9d..b17aa2342 100644 --- a/effects/showfps/showfps.cpp +++ b/effects/showfps/showfps.cpp @@ -1,546 +1,547 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "showfps.h" // KConfigSkeleton #include "showfpsconfig.h" #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #include #endif #include #include #include #include +#include namespace KWin { const int FPS_WIDTH = 10; const int MAX_TIME = 100; ShowFpsEffect::ShowFpsEffect() : paints_pos(0) , frames_pos(0) , m_noBenchmark(effects->effectFrame(EffectFrameUnstyled, false)) { initConfig(); for (int i = 0; i < NUM_PAINTS; ++i) { paints[ i ] = 0; paint_size[ i ] = 0; } for (int i = 0; i < MAX_FPS; ++i) frames[ i ] = 0; m_noBenchmark->setAlignment(Qt::AlignTop | Qt::AlignRight); m_noBenchmark->setText(i18n("This effect is not a benchmark")); reconfigure(ReconfigureAll); } void ShowFpsEffect::reconfigure(ReconfigureFlags) { ShowFpsConfig::self()->read(); alpha = ShowFpsConfig::alpha(); x = ShowFpsConfig::x(); y = ShowFpsConfig::y(); const QSize screenSize = effects->virtualScreenSize(); if (x == -10000) // there's no -0 :( x = screenSize.width() - 2 * NUM_PAINTS - FPS_WIDTH; else if (x < 0) x = screenSize.width() - 2 * NUM_PAINTS - FPS_WIDTH - x; if (y == -10000) y = screenSize.height() - MAX_TIME; else if (y < 0) y = screenSize.height() - MAX_TIME - y; fps_rect = QRect(x, y, FPS_WIDTH + 2 * NUM_PAINTS, MAX_TIME); m_noBenchmark->setPosition(fps_rect.bottomRight() + QPoint(-6, 6)); int textPosition = ShowFpsConfig::textPosition(); textFont = ShowFpsConfig::textFont(); textColor = ShowFpsConfig::textColor(); double textAlpha = ShowFpsConfig::textAlpha(); if (!textColor.isValid()) textColor = QPalette().color(QPalette::Active, QPalette::WindowText); textColor.setAlphaF(textAlpha); switch(textPosition) { case TOP_LEFT: fpsTextRect = QRect(0, 0, 100, 100); textAlign = Qt::AlignTop | Qt::AlignLeft; break; case TOP_RIGHT: fpsTextRect = QRect(screenSize.width() - 100, 0, 100, 100); textAlign = Qt::AlignTop | Qt::AlignRight; break; case BOTTOM_LEFT: fpsTextRect = QRect(0, screenSize.height() - 100, 100, 100); textAlign = Qt::AlignBottom | Qt::AlignLeft; break; case BOTTOM_RIGHT: fpsTextRect = QRect(screenSize.width() - 100, screenSize.height() - 100, 100, 100); textAlign = Qt::AlignBottom | Qt::AlignRight; break; case NOWHERE: fpsTextRect = QRect(); break; case INSIDE_GRAPH: default: fpsTextRect = QRect(x, y, FPS_WIDTH + NUM_PAINTS, MAX_TIME); textAlign = Qt::AlignTop | Qt::AlignRight; break; } } void ShowFpsEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (time == 0) { // TODO optimized away } t.start(); frames[ frames_pos ] = t.minute() * 60000 + t.second() * 1000 + t.msec(); if (++frames_pos == MAX_FPS) frames_pos = 0; effects->prePaintScreen(data, time); data.paint += fps_rect; paint_size[ paints_pos ] = 0; } void ShowFpsEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->paintWindow(w, mask, region, data); // Take intersection of region and actual window's rect, minus the fps area // (since we keep repainting it) and count the pixels. QRegion r2 = region & QRect(w->x(), w->y(), w->width(), w->height()); r2 -= fps_rect; int winsize = 0; foreach (const QRect & r, r2.rects()) winsize += r.width() * r.height(); paint_size[ paints_pos ] += winsize; } void ShowFpsEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); int fps = 0; for (int i = 0; i < MAX_FPS; ++i) if (abs(t.minute() * 60000 + t.second() * 1000 + t.msec() - frames[ i ]) < 1000) ++fps; // count all frames in the last second if (fps > MAX_TIME) fps = MAX_TIME; // keep it the same height if (effects->isOpenGLCompositing()) { paintGL(fps, data.projectionMatrix()); glFinish(); // make sure all rendering is done } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) { paintXrender(fps); xcb_flush(xcbConnection()); // make sure all rendering is done } #endif if (effects->compositingType() == QPainterCompositing) { paintQPainter(fps); } m_noBenchmark->render(infiniteRegion(), 1.0, alpha); } void ShowFpsEffect::paintGL(int fps, const QMatrix4x4 &projectionMatrix) { int x = this->x; int y = this->y; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // TODO painting first the background white and then the contents // means that the contents also blend with the background, I guess ShaderBinder binder(ShaderTrait::UniformColor); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, projectionMatrix); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); QColor color(255, 255, 255); color.setAlphaF(alpha); vbo->setColor(color); QVector verts; verts.reserve(12); verts << x + 2 * NUM_PAINTS + FPS_WIDTH << y; verts << x << y; verts << x << y + MAX_TIME; verts << x << y + MAX_TIME; verts << x + 2 * NUM_PAINTS + FPS_WIDTH << y + MAX_TIME; verts << x + 2 * NUM_PAINTS + FPS_WIDTH << y; vbo->setData(6, 2, verts.constData(), NULL); vbo->render(GL_TRIANGLES); y += MAX_TIME; // paint up from the bottom color.setRed(0); color.setGreen(0); vbo->setColor(color); verts.clear(); verts << x + FPS_WIDTH << y - fps; verts << x << y - fps; verts << x << y; verts << x << y; verts << x + FPS_WIDTH << y; verts << x + FPS_WIDTH << y - fps; vbo->setData(6, 2, verts.constData(), NULL); vbo->render(GL_TRIANGLES); color.setBlue(0); vbo->setColor(color); QVector vertices; for (int i = 10; i < MAX_TIME; i += 10) { vertices << x << y - i; vertices << x + FPS_WIDTH << y - i; } vbo->setData(vertices.size() / 2, 2, vertices.constData(), NULL); vbo->render(GL_LINES); x += FPS_WIDTH; // Paint FPS graph paintFPSGraph(x, y); x += NUM_PAINTS; // Paint amount of rendered pixels graph paintDrawSizeGraph(x, y); // Paint FPS numerical value if (fpsTextRect.isValid()) { fpsText.reset(new GLTexture(fpsTextImage(fps))); fpsText->bind(); ShaderBinder binder(ShaderTrait::MapTexture); QMatrix4x4 mvp = projectionMatrix; mvp.translate(fpsTextRect.x(), fpsTextRect.y()); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp); fpsText->render(QRegion(fpsTextRect), fpsTextRect); fpsText->unbind(); effects->addRepaint(fpsTextRect); } // Paint paint sizes glDisable(GL_BLEND); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING /* Differences between OpenGL and XRender: - differently specified rectangles (X: width/height, O: x2,y2) - XRender uses pre-multiplied alpha */ void ShowFpsEffect::paintXrender(int fps) { xcb_pixmap_t pixmap = xcb_generate_id(xcbConnection()); xcb_create_pixmap(xcbConnection(), 32, pixmap, x11RootWindow(), FPS_WIDTH, MAX_TIME); XRenderPicture p(pixmap, 32); xcb_free_pixmap(xcbConnection(), pixmap); xcb_render_color_t col; col.alpha = int(alpha * 0xffff); col.red = int(alpha * 0xffff); // white col.green = int(alpha * 0xffff); col.blue = int(alpha * 0xffff); xcb_rectangle_t rect = {0, 0, FPS_WIDTH, MAX_TIME}; xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, 1, &rect); col.red = 0; // blue col.green = 0; col.blue = int(alpha * 0xffff); rect.y = MAX_TIME - fps; rect.width = FPS_WIDTH; rect.height = fps; xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, 1, &rect); col.red = 0; // black col.green = 0; col.blue = 0; QVector rects; for (int i = 10; i < MAX_TIME; i += 10) { xcb_rectangle_t rect = {0, int16_t(MAX_TIME - i), uint16_t(FPS_WIDTH), 1}; rects << rect; } xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, rects.count(), rects.constData()); xcb_render_composite(xcbConnection(), alpha != 1.0 ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC, p, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, x, y, FPS_WIDTH, MAX_TIME); // Paint FPS graph paintFPSGraph(x + FPS_WIDTH, y); // Paint amount of rendered pixels graph paintDrawSizeGraph(x + FPS_WIDTH + MAX_TIME, y); // Paint FPS numerical value if (fpsTextRect.isValid()) { QImage textImg(fpsTextImage(fps)); XRenderPicture textPic(textImg); xcb_render_composite(xcbConnection(), XCB_RENDER_PICT_OP_OVER, textPic, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, fpsTextRect.x(), fpsTextRect.y(), textImg.width(), textImg.height()); effects->addRepaint(fpsTextRect); } } #endif void ShowFpsEffect::paintQPainter(int fps) { QPainter *painter = effects->scenePainter(); painter->save(); QColor color(255, 255, 255); color.setAlphaF(alpha); painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->fillRect(x, y, 2 * NUM_PAINTS + FPS_WIDTH, MAX_TIME, color); color.setRed(0); color.setGreen(0); painter->fillRect(x, y + MAX_TIME - fps, FPS_WIDTH, fps, color); color.setBlue(0); for (int i = 10; i < MAX_TIME; i += 10) { painter->setPen(color); painter->drawLine(x, y + MAX_TIME - i, x + FPS_WIDTH, y + MAX_TIME - i); } // Paint FPS graph paintFPSGraph(x + FPS_WIDTH, y + MAX_TIME - 1); // Paint amount of rendered pixels graph paintDrawSizeGraph(x + FPS_WIDTH + NUM_PAINTS, y + MAX_TIME - 1); // Paint FPS numerical value painter->setPen(Qt::black); painter->drawText(fpsTextRect, textAlign, QString::number(fps)); painter->restore(); } void ShowFpsEffect::paintFPSGraph(int x, int y) { // Paint FPS graph QList lines; lines << 10 << 20 << 50; QList values; for (int i = 0; i < NUM_PAINTS; ++i) { values.append(paints[(i + paints_pos) % NUM_PAINTS ]); } paintGraph(x, y, values, lines, true); } void ShowFpsEffect::paintDrawSizeGraph(int x, int y) { int max_drawsize = 0; for (int i = 0; i < NUM_PAINTS; i++) max_drawsize = qMax(max_drawsize, paint_size[ i ]); // Log of min/max values shown on graph const float max_pixels_log = 7.2f; const float min_pixels_log = 2.0f; const int minh = 5; // Minimum height of the bar when value > 0 float drawscale = (MAX_TIME - minh) / (max_pixels_log - min_pixels_log); QList drawlines; for (int logh = (int)min_pixels_log; logh <= max_pixels_log; logh++) drawlines.append((int)((logh - min_pixels_log) * drawscale) + minh); QList drawvalues; for (int i = 0; i < NUM_PAINTS; ++i) { int value = paint_size[(i + paints_pos) % NUM_PAINTS ]; int h = 0; if (value > 0) { h = (int)((log10((double)value) - min_pixels_log) * drawscale); h = qMin(qMax(0, h) + minh, MAX_TIME); } drawvalues.append(h); } paintGraph(x, y, drawvalues, drawlines, false); } void ShowFpsEffect::paintGraph(int x, int y, QList values, QList lines, bool colorize) { if (effects->isOpenGLCompositing()) { QColor color(0, 0, 0); color.setAlphaF(alpha); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setColor(color); QVector verts; // First draw the lines foreach (int h, lines) { verts << x << y - h; verts << x + values.count() << y - h; } vbo->setData(verts.size() / 2, 2, verts.constData(), NULL); vbo->render(GL_LINES); // Then the graph values int lastValue = 0; verts.clear(); for (int i = 0; i < values.count(); i++) { int value = values[ i ]; if (colorize && value != lastValue) { if (!verts.isEmpty()) { vbo->setData(verts.size() / 2, 2, verts.constData(), NULL); vbo->render(GL_LINES); } verts.clear(); if (value <= 10) { color = QColor(0, 255, 0); } else if (value <= 20) { color = QColor(255, 255, 0); } else if (value <= 50) { color = QColor(255, 0, 0); } else { color = QColor(0, 0, 0); } vbo->setColor(color); } verts << x + values.count() - i << y; verts << x + values.count() - i << y - value; lastValue = value; } if (!verts.isEmpty()) { vbo->setData(verts.size() / 2, 2, verts.constData(), NULL); vbo->render(GL_LINES); } } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) { xcb_pixmap_t pixmap = xcb_generate_id(xcbConnection()); xcb_create_pixmap(xcbConnection(), 32, pixmap, x11RootWindow(), values.count(), MAX_TIME); XRenderPicture p(pixmap, 32); xcb_free_pixmap(xcbConnection(), pixmap); xcb_render_color_t col; col.alpha = int(alpha * 0xffff); // Draw background col.red = col.green = col.blue = int(alpha * 0xffff); // white xcb_rectangle_t rect = {0, 0, uint16_t(values.count()), uint16_t(MAX_TIME)}; xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, 1, &rect); // Then the values col.red = col.green = col.blue = int(alpha * 0x8000); // grey for (int i = 0; i < values.count(); i++) { int value = values[ i ]; if (colorize) { if (value <= 10) { // green col.red = 0; col.green = int(alpha * 0xffff); col.blue = 0; } else if (value <= 20) { // yellow col.red = int(alpha * 0xffff); col.green = int(alpha * 0xffff); col.blue = 0; } else if (value <= 50) { // red col.red = int(alpha * 0xffff); col.green = 0; col.blue = 0; } else { // black col.red = 0; col.green = 0; col.blue = 0; } } xcb_rectangle_t rect = {int16_t(values.count() - i), int16_t(MAX_TIME - value), 1, uint16_t(value)}; xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, 1, &rect); } // Then the lines col.red = col.green = col.blue = 0; // black QVector rects; foreach (int h, lines) { xcb_rectangle_t rect = {0, int16_t(MAX_TIME - h), uint16_t(values.count()), 1}; rects << rect; } xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, rects.count(), rects.constData()); // Finally render the pixmap onto screen xcb_render_composite(xcbConnection(), alpha != 1.0 ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC, p, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, x, y, values.count(), MAX_TIME); } #endif if (effects->compositingType() == QPainterCompositing) { QPainter *painter = effects->scenePainter(); painter->setPen(Qt::black); // First draw the lines foreach (int h, lines) { painter->drawLine(x, y - h, x + values.count(), y - h); } QColor color(0, 0, 0); color.setAlphaF(alpha); for (int i = 0; i < values.count(); i++) { int value = values[ i ]; if (colorize) { if (value <= 10) { color = QColor(0, 255, 0); } else if (value <= 20) { color = QColor(255, 255, 0); } else if (value <= 50) { color = QColor(255, 0, 0); } else { color = QColor(0, 0, 0); } } painter->setPen(color); painter->drawLine(x + values.count() - i, y, x + values.count() - i, y - value); } } } void ShowFpsEffect::postPaintScreen() { effects->postPaintScreen(); paints[ paints_pos ] = t.elapsed(); if (++paints_pos == NUM_PAINTS) paints_pos = 0; effects->addRepaint(fps_rect); } QImage ShowFpsEffect::fpsTextImage(int fps) { QImage im(100, 100, QImage::Format_ARGB32); im.fill(Qt::transparent); QPainter painter(&im); painter.setFont(textFont); painter.setPen(textColor); painter.drawText(QRect(0, 0, 100, 100), textAlign, QString::number(fps)); painter.end(); return im; } } // namespace diff --git a/effects/showfps/showfps.h b/effects/showfps/showfps.h index 3f8909a05..40c6a86dd 100644 --- a/effects/showfps/showfps.h +++ b/effects/showfps/showfps.h @@ -1,108 +1,109 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_SHOWFPS_H #define KWIN_SHOWFPS_H #include +#include #include namespace KWin { class GLTexture; class ShowFpsEffect : public Effect { Q_OBJECT Q_PROPERTY(qreal alpha READ configuredAlpha) Q_PROPERTY(int x READ configuredX) Q_PROPERTY(int y READ configuredY) Q_PROPERTY(QRect fpsTextRect READ configuredFpsTextRect) Q_PROPERTY(int textAlign READ configuredTextAlign) Q_PROPERTY(QFont textFont READ configuredTextFont) Q_PROPERTY(QColor textColor READ configuredTextColor) public: ShowFpsEffect(); virtual void reconfigure(ReconfigureFlags); virtual void prePaintScreen(ScreenPrePaintData& data, int time); virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); virtual void postPaintScreen(); enum { INSIDE_GRAPH, NOWHERE, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; // fps text position // for properties qreal configuredAlpha() const { return alpha; } int configuredX() const { return x; } int configuredY() const { return y; } QRect configuredFpsTextRect() const { return fpsTextRect; } int configuredTextAlign() const { return textAlign; } QFont configuredTextFont() const { return textFont; } QColor configuredTextColor() const { return textColor; } private: void paintGL(int fps, const QMatrix4x4 &projectionMatrix); #ifdef KWIN_HAVE_XRENDER_COMPOSITING void paintXrender(int fps); #endif void paintQPainter(int fps); void paintFPSGraph(int x, int y); void paintDrawSizeGraph(int x, int y); void paintGraph(int x, int y, QList values, QList lines, bool colorize); QImage fpsTextImage(int fps); QTime t; enum { NUM_PAINTS = 100 }; // remember time needed to paint this many paints int paints[ NUM_PAINTS ]; // time needed to paint int paint_size[ NUM_PAINTS ]; // number of pixels painted int paints_pos; // position in the queue enum { MAX_FPS = 200 }; int frames[ MAX_FPS ]; // (sec*1000+msec) of the time the frame was done int frames_pos; // position in the queue double alpha; int x; int y; QRect fps_rect; QScopedPointer fpsText; int textPosition; QFont textFont; QColor textColor; QRect fpsTextRect; int textAlign; QScopedPointer m_noBenchmark; }; } // namespace #endif diff --git a/effects/slide/CMakeLists.txt b/effects/slide/CMakeLists.txt index 71b806f01..fa35bef2a 100644 --- a/effects/slide/CMakeLists.txt +++ b/effects/slide/CMakeLists.txt @@ -1,7 +1,24 @@ ####################################### -# Effect +# Config +set(kwin_slide_config_SRCS slide_config.cpp) +ki18n_wrap_ui(kwin_slide_config_SRCS slide_config.ui) +qt5_add_dbus_interface(kwin_slide_config_SRCS ${kwin_effects_dbus_xml} kwineffects_interface) +kconfig_add_kcfg_files(kwin_slide_config_SRCS slideconfig.kcfgc) -# Source files -set( kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} - slide/slide.cpp - ) +add_library(kwin_slide_config MODULE ${kwin_slide_config_SRCS}) + +target_link_libraries(kwin_slide_config + Qt5::DBus + KF5::ConfigWidgets + KF5::I18n + KF5::Service +) + +kcoreaddons_desktop_to_json(kwin_slide_config slide_config.desktop SERVICE_TYPES kcmodule.desktop) + +install( + TARGETS + kwin_slide_config + DESTINATION + ${PLUGIN_INSTALL_DIR}/kwin/effects/configs +) diff --git a/effects/slide/slide.cpp b/effects/slide/slide.cpp index c6badc155..375721ed2 100644 --- a/effects/slide/slide.cpp +++ b/effects/slide/slide.cpp @@ -1,292 +1,314 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Copyright (C) 2008 Lucas Murray 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 "slide.h" +// KConfigSkeleton +#include "slideconfig.h" #include namespace KWin { SlideEffect::SlideEffect() : slide(false) { - connect(effects, SIGNAL(desktopChanged(int,int)), this, SLOT(slotDesktopChanged(int,int))); + initConfig(); + connect(effects, SIGNAL(desktopChanged(int,int,KWin::EffectWindow*)), + this, SLOT(slotDesktopChanged(int,int,KWin::EffectWindow*))); connect(effects, &EffectsHandler::windowAdded, this, &SlideEffect::windowAdded); - connect(effects, &EffectsHandler::windowDeleted, this, [this](EffectWindow *w) { - m_backgroundContrastForcedBefore.removeAll(w); - }); + connect(effects, &EffectsHandler::windowDeleted, this, &SlideEffect::windowDeleted); mTimeLine.setCurveShape(QTimeLine::EaseInOutCurve); reconfigure(ReconfigureAll); } +bool SlideEffect::supported() +{ + return effects->animationsSupported(); +} + void SlideEffect::reconfigure(ReconfigureFlags) { - mTimeLine.setDuration(animationTime(250)); + SlideConfig::self()->read(); + + const auto d = animationTime( + SlideConfig::duration() != 0 ? SlideConfig::duration() : 250); + mTimeLine.setDuration(d); } void SlideEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (slide) { mTimeLine.setCurrentTime(mTimeLine.currentTime() + time); // PAINT_SCREEN_BACKGROUND_FIRST is needed because screen will be actually painted more than once, // so with normal screen painting second screen paint would erase parts of the first paint if (mTimeLine.currentValue() != 1) data.mask |= PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; else { foreach (EffectWindow * w, effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant(false)); if (m_backgroundContrastForcedBefore.contains(w)) { w->setData(WindowForceBackgroundContrastRole, QVariant()); } } m_backgroundContrastForcedBefore.clear(); + m_movingWindow = nullptr; slide = false; mTimeLine.setCurrentTime(0); effects->setActiveFullScreenEffect(NULL); } } effects->prePaintScreen(data, time); } void SlideEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { - if (slide) { + if (slide && w != m_movingWindow) { if (w->isOnAllDesktops()) { bool keep_above = w->keepAbove() || w->isDock(); if ((!slide_painting_sticky || keep_above) && (!keep_above || !slide_painting_keep_above)) { w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } } else if (w->isOnDesktop(painting_desktop)) { data.setTransformed(); w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } else { w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } } effects->prePaintWindow(w, data, time); } void SlideEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { if (mTimeLine.currentValue() == 0) { effects->paintScreen(mask, region, data); return; } /* Transformations are done by remembering starting position of the change and the progress of it, the destination is computed from the current desktop. Positions of desktops are done using their topleft corner. */ QPoint destPos = desktopRect(effects->currentDesktop()).topLeft(); QPoint diffPos = destPos - slide_start_pos; int w = 0; int h = 0; if (effects->optionRollOverDesktops()) { w = effects->workspaceWidth(); h = effects->workspaceHeight(); // wrap around if shorter if (diffPos.x() > 0 && diffPos.x() > w / 2) diffPos.setX(diffPos.x() - w); if (diffPos.x() < 0 && abs(diffPos.x()) > w / 2) diffPos.setX(diffPos.x() + w); if (diffPos.y() > 0 && diffPos.y() > h / 2) diffPos.setY(diffPos.y() - h); if (diffPos.y() < 0 && abs(diffPos.y()) > h / 2) diffPos.setY(diffPos.y() + h); } QPoint currentPos = slide_start_pos + mTimeLine.currentValue() * diffPos; QRegion currentRegion = QRect(currentPos, effects->virtualScreenSize()); if (effects->optionRollOverDesktops()) { currentRegion |= (currentRegion & QRect(-w, 0, w, h)).translated(w, 0); currentRegion |= (currentRegion & QRect(0, -h, w, h)).translated(0, h); currentRegion |= (currentRegion & QRect(w, 0, w, h)).translated(-w, 0); currentRegion |= (currentRegion & QRect(0, h, w, h)).translated(0, -h); } bool do_sticky = true; // Assure that the windows that are on all desktops and always on top // are painted with the last screen (e.g. plasma's tooltips). All other windows // that are on all desktops (e.g. the background window) are painted together // with the first screen. int last_desktop = 0; QList desktop_rects; for (int desktop = 1; desktop <= effects->numberOfDesktops(); ++desktop) { QRect rect = desktopRect(desktop); desktop_rects << rect; if (currentRegion.contains(rect)) { last_desktop = desktop; } } for (int desktop = 1; desktop <= effects->numberOfDesktops(); ++desktop) { QRect rect = desktop_rects[desktop-1]; if (currentRegion.contains(rect)) { // part of the desktop needs painting painting_desktop = desktop; slide_painting_sticky = do_sticky; slide_painting_keep_above = (last_desktop == desktop); slide_painting_diff = rect.topLeft() - currentPos; const QSize screenSize = effects->virtualScreenSize(); if (effects->optionRollOverDesktops()) { if (slide_painting_diff.x() > screenSize.width()) slide_painting_diff.setX(slide_painting_diff.x() - w); if (slide_painting_diff.x() < -screenSize.width()) slide_painting_diff.setX(slide_painting_diff.x() + w); if (slide_painting_diff.y() > screenSize.height()) slide_painting_diff.setY(slide_painting_diff.y() - h); if (slide_painting_diff.y() < -screenSize.height()) slide_painting_diff.setY(slide_painting_diff.y() + h); } do_sticky = false; // paint on-all-desktop windows only once // TODO mask parts that are not visible? effects->paintScreen(mask, region, data); } } } void SlideEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (slide) { - // don't move windows on all desktops (compensate screen transformation) - if (!w->isOnAllDesktops()) { // TODO also fix 'Workspace::movingClient' + // Do not move a window if it is on all desktops or being moved to another desktop. + if (!w->isOnAllDesktops() && w != m_movingWindow) { data += slide_painting_diff; } } effects->paintWindow(w, mask, region, data); } void SlideEffect::postPaintScreen() { if (slide) effects->addRepaintFull(); effects->postPaintScreen(); } // Gives a position of the given desktop when all desktops are arranged in a grid QRect SlideEffect::desktopRect(int desktop) const { QRect rect = effects->virtualScreenGeometry(); rect.translate(effects->desktopCoords(desktop)); return rect; } -void SlideEffect::slotDesktopChanged(int old, int current) +void SlideEffect::slotDesktopChanged(int old, int current, EffectWindow* with) { if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; if (slide) { // old slide still in progress QPoint diffPos = desktopRect(old).topLeft() - slide_start_pos; int w = 0; int h = 0; if (effects->optionRollOverDesktops()) { w = effects->workspaceWidth(); h = effects->workspaceHeight(); // wrap around if shorter if (diffPos.x() > 0 && diffPos.x() > w / 2) diffPos.setX(diffPos.x() - w); if (diffPos.x() < 0 && abs(diffPos.x()) > w / 2) diffPos.setX(diffPos.x() + w); if (diffPos.y() > 0 && diffPos.y() > h / 2) diffPos.setY(diffPos.y() - h); if (diffPos.y() < 0 && abs(diffPos.y()) > h / 2) diffPos.setY(diffPos.y() + h); } QPoint currentPos = slide_start_pos + mTimeLine.currentValue() * diffPos; const QSize screenSize = effects->virtualScreenSize(); QRegion currentRegion = QRect(currentPos, screenSize); if (effects->optionRollOverDesktops()) { currentRegion |= (currentRegion & QRect(-w, 0, w, h)).translated(w, 0); currentRegion |= (currentRegion & QRect(0, -h, w, h)).translated(0, h); currentRegion |= (currentRegion & QRect(w, 0, w, h)).translated(-w, 0); currentRegion |= (currentRegion & QRect(0, h, w, h)).translated(0, -h); } QRect rect = desktopRect(current); if (currentRegion.contains(rect)) { // current position is in new current desktop (e.g. quickly changing back), // don't do full progress if (abs(currentPos.x() - rect.x()) > abs(currentPos.y() - rect.y())) mTimeLine.setCurrentTime((1.0 - abs(currentPos.x() - rect.x()) / double(screenSize.width()))*(qreal)mTimeLine.currentValue()); else mTimeLine.setCurrentTime((1.0 - abs(currentPos.y() - rect.y()) / double(screenSize.height()))*(qreal)mTimeLine.currentValue()); } else // current position is not on current desktop, do full progress mTimeLine.setCurrentTime(0); diffPos = rect.topLeft() - currentPos; if (mTimeLine.currentValue() <= 0) { // Compute starting point for this new move (given current and end positions) slide_start_pos = rect.topLeft() - diffPos * 1 / (1 - mTimeLine.currentValue()); } else { // at the end, stop foreach (EffectWindow * w, m_backgroundContrastForcedBefore) { w->setData(WindowForceBackgroundContrastRole, QVariant()); } m_backgroundContrastForcedBefore.clear(); slide = false; mTimeLine.setCurrentTime(0); effects->setActiveFullScreenEffect(NULL); } } else { if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; mTimeLine.setCurrentTime(0); slide_start_pos = desktopRect(old).topLeft(); slide = true; foreach (EffectWindow * w, effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant(true)); if (shouldForceBackgroundContrast(w)) { m_backgroundContrastForcedBefore.append(w); w->setData(WindowForceBackgroundContrastRole, QVariant(true)); } } effects->setActiveFullScreenEffect(this); } + + m_movingWindow = with; + effects->addRepaintFull(); } void SlideEffect::windowAdded(EffectWindow *w) { if (slide && shouldForceBackgroundContrast(w)) { m_backgroundContrastForcedBefore.append(w); w->setData(WindowForceBackgroundContrastRole, QVariant(true)); } } +void SlideEffect::windowDeleted(EffectWindow *w) +{ + m_backgroundContrastForcedBefore.removeAll(w); + if (w == m_movingWindow) + m_movingWindow = nullptr; +} + bool SlideEffect::shouldForceBackgroundContrast(const EffectWindow *w) const { // Windows that are docks, kept above (such as panel popups), and do not // have the background contrast explicitely disabled should be forced on // during the slide animation const bool bgWindow = (w->hasAlpha() && w->isOnAllDesktops() && (w->isDock() || w->keepAbove())); return bgWindow && (!w->data(WindowForceBackgroundContrastRole).isValid()); } bool SlideEffect::isActive() const { return slide; } } // namespace diff --git a/effects/slide/slide.h b/effects/slide/slide.h index 0bfe5ab36..7e972b99e 100644 --- a/effects/slide/slide.h +++ b/effects/slide/slide.h @@ -1,70 +1,74 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Copyright (C) 2008 Lucas Murray This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_SLIDE_H #define KWIN_SLIDE_H #include #include #include namespace KWin { class SlideEffect : public Effect { Q_OBJECT public: SlideEffect(); virtual void reconfigure(ReconfigureFlags); virtual void prePaintScreen(ScreenPrePaintData& data, int time); virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); virtual void postPaintScreen(); virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); virtual bool isActive() const; int requestedEffectChainPosition() const override { return 50; } + static bool supported(); + private Q_SLOTS: - void slotDesktopChanged(int old, int current); + void slotDesktopChanged(int old, int current, KWin::EffectWindow* with); private: void windowAdded(EffectWindow* w); + void windowDeleted(EffectWindow* w); bool shouldForceBackgroundContrast(const EffectWindow* w) const; QList< EffectWindow* > m_backgroundContrastForcedBefore; QRect desktopRect(int desktop) const; QTimeLine mTimeLine; int painting_desktop; bool slide; QPoint slide_start_pos; bool slide_painting_sticky; bool slide_painting_keep_above; QPoint slide_painting_diff; + EffectWindow* m_movingWindow = nullptr; }; } // namespace #endif diff --git a/effects/blur/blur.kcfg b/effects/slide/slide.kcfg similarity index 60% copy from effects/blur/blur.kcfg copy to effects/slide/slide.kcfg index 6fd2e98c4..a2230a515 100644 --- a/effects/blur/blur.kcfg +++ b/effects/slide/slide.kcfg @@ -1,15 +1,13 @@ + - - - 12 - - - true + + + 0 diff --git a/effects/slide/slide_config.cpp b/effects/slide/slide_config.cpp new file mode 100644 index 000000000..fffb1b7e8 --- /dev/null +++ b/effects/slide/slide_config.cpp @@ -0,0 +1,64 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Vlad Zagorodniy + +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 "slide_config.h" +// KConfigSkeleton +#include "slideconfig.h" +#include + +#include + +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(SlideEffectConfigFactory, + "slide_config.json", + registerPlugin();) + +namespace KWin +{ + +SlideEffectConfig::SlideEffectConfig(QWidget *parent, const QVariantList &args) + : KCModule(KAboutData::pluginData(QStringLiteral("slide")), parent, args) +{ + mUi.setupUi(this); + SlideConfig::instance(KWIN_CONFIG); + addConfig(SlideConfig::self(), this); + + load(); +} + +SlideEffectConfig::~SlideEffectConfig() +{ +} + +void SlideEffectConfig::save() +{ + KCModule::save(); + + OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), + QStringLiteral("/Effects"), + QDBusConnection::sessionBus()); + interface.reconfigureEffect(QStringLiteral("slide")); +} + +} // namespace KWin + +#include "slide_config.moc" diff --git a/effects/slide/slide_config.desktop b/effects/slide/slide_config.desktop new file mode 100644 index 000000000..cb884e504 --- /dev/null +++ b/effects/slide/slide_config.desktop @@ -0,0 +1,68 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kwin_slide_config +X-KDE-ParentComponents=slide + +Name=Slide +Name[ar]=أزْلِق +Name[bg]=Приплъзване +Name[ca]=Diapositiva +Name[ca@valencia]=Diapositiva +Name[cs]=Sklouznutí +Name[csb]=Pùrganié +Name[da]=Glid +Name[de]=Gleiten +Name[el]=Κύλιση +Name[en_GB]=Slide +Name[eo]=Lumbildo +Name[es]=Deslizar +Name[et]=Liuglemine +Name[eu]=Irristatu +Name[fr]=Glisser +Name[fy]=Glydzje +Name[ga]=Sleamhnaigh +Name[gl]=Deslizar +Name[gu]=સ્લાઇડ +Name[hi]=स्लाइड +Name[hne]=स्लाइड +Name[hr]=Pomak +Name[hu]=Csúsztatott váltás +Name[ia]=Glissa +Name[is]=Renna til +Name[it]=Scivola +Name[kk]=Сырғанату +Name[km]=ស្លាយ +Name[kn]=ಜಾರು +Name[ku]=Xîş Bike +Name[lv]=Slīdēt +Name[mai]=स्लाइड +Name[ml]=തെന്നിമാറുക +Name[mr]=सरकणे +Name[nds]=Glieden +Name[nl]=Schuiven +Name[nn]=Skliding +Name[pa]=ਸਲਾਈਡ +Name[pl]=Slajd +Name[pt]=Deslizar +Name[ro]=Alunecă +Name[ru]=Прокрутка +Name[si]=ලිස්සන්න +Name[sk]=Posúvať +Name[sl]=Drsenje +Name[sr]=Клизање +Name[sr@ijekavian]=Клизање +Name[sr@ijekavianlatin]=Klizanje +Name[sr@latin]=Klizanje +Name[sv]=Skjut +Name[ta]=நழுவு +Name[tg]=Слайд +Name[th]=เลื่อนหน้าต่าง +Name[tr]=Kaydır +Name[ug]=تام تەسۋىر +Name[uk]=Ковзання +Name[wa]=Rider +Name[x-test]=xxSlidexx +Name[zh_CN]=滑行 +Name[zh_TW]=滑動 diff --git a/plugins/platforms/drm/drm_object_connector.h b/effects/slide/slide_config.h similarity index 62% copy from plugins/platforms/drm/drm_object_connector.h copy to effects/slide/slide_config.h index 0f6fcdbd4..7022fe52b 100644 --- a/plugins/platforms/drm/drm_object_connector.h +++ b/effects/slide/slide_config.h @@ -1,57 +1,47 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2016 Roman Gilg +Copyright (C) 2017 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ -#ifndef KWIN_DRM_OBJECT_CONNECTOR_H -#define KWIN_DRM_OBJECT_CONNECTOR_H -#include "drm_object.h" + +#ifndef SLIDE_CONFIG_H +#define SLIDE_CONFIG_H + +#include +#include "ui_slide_config.h" namespace KWin { -class DrmConnector : public DrmObject +class SlideEffectConfig : public KCModule { -public: - DrmConnector(uint32_t connector_id, DrmBackend *backend); - - virtual ~DrmConnector(); + Q_OBJECT - bool atomicInit(); - - enum class PropertyIndex { - CrtcId = 0, - Count - }; - - QVector encoders() { - return m_encoders; - } - - bool initProps(); - bool isConnected(); +public: + explicit SlideEffectConfig(QWidget *parent = 0, const QVariantList& args = QVariantList()); + ~SlideEffectConfig(); + void save(); private: - QVector m_encoders; + ::Ui::SlideEffectConfig mUi; }; -} +} // namespace KWin #endif - diff --git a/effects/slide/slide_config.ui b/effects/slide/slide_config.ui new file mode 100644 index 000000000..3ffd35693 --- /dev/null +++ b/effects/slide/slide_config.ui @@ -0,0 +1,53 @@ + + + SlideEffectConfig + + + + 0 + 0 + 400 + 300 + + + + + + + Duration: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + kcfg_Duration + + + + + + + + 0 + 0 + + + + Default + + + milliseconds + + + 9999 + + + 10 + + + + + + + + diff --git a/effects/slide/slideconfig.kcfgc b/effects/slide/slideconfig.kcfgc new file mode 100644 index 000000000..680f393cb --- /dev/null +++ b/effects/slide/slideconfig.kcfgc @@ -0,0 +1,5 @@ +File=slide.kcfg +ClassName=SlideConfig +NameSpace=KWin +Singleton=true +Mutators=true diff --git a/effects/slidingpopups/slidingpopups.cpp b/effects/slidingpopups/slidingpopups.cpp index 5408c10bf..c8bb89297 100644 --- a/effects/slidingpopups/slidingpopups.cpp +++ b/effects/slidingpopups/slidingpopups.cpp @@ -1,492 +1,498 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2009 Marco Martin notmart@gmail.com 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 "slidingpopups.h" #include "slidingpopupsconfig.h" #include #include +#include #include #include #include namespace KWin { SlidingPopupsEffect::SlidingPopupsEffect() { initConfig(); KWayland::Server::Display *display = effects->waylandDisplay(); if (display) { display->createSlideManager(this)->create(); } mSlideLength = QFontMetrics(qApp->font()).height() * 8; mAtom = effects->announceSupportProperty("_KDE_SLIDE", this); connect(effects, SIGNAL(windowAdded(KWin::EffectWindow*)), this, SLOT(slotWindowAdded(KWin::EffectWindow*))); connect(effects, SIGNAL(windowClosed(KWin::EffectWindow*)), this, SLOT(slotWindowClosed(KWin::EffectWindow*))); connect(effects, SIGNAL(windowDeleted(KWin::EffectWindow*)), this, SLOT(slotWindowDeleted(KWin::EffectWindow*))); connect(effects, SIGNAL(propertyNotify(KWin::EffectWindow*,long)), this, SLOT(slotPropertyNotify(KWin::EffectWindow*,long))); connect(effects, &EffectsHandler::windowShown, this, &SlidingPopupsEffect::startForShow); connect(effects, &EffectsHandler::windowHidden, this, &SlidingPopupsEffect::slotWindowClosed); connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this] { mAtom = effects->announceSupportProperty(QByteArrayLiteral("_KDE_SLIDE"), this); } ); reconfigure(ReconfigureAll); } SlidingPopupsEffect::~SlidingPopupsEffect() { } +bool SlidingPopupsEffect::supported() +{ + return effects->animationsSupported(); +} + void SlidingPopupsEffect::reconfigure(ReconfigureFlags flags) { Q_UNUSED(flags) SlidingPopupsConfig::self()->read(); mFadeInTime = animationTime(SlidingPopupsConfig::slideInTime() != 0 ? SlidingPopupsConfig::slideInTime() : 150); mFadeOutTime = animationTime(SlidingPopupsConfig::slideOutTime() != 0 ? SlidingPopupsConfig::slideOutTime() : 250); QHash< const EffectWindow*, QTimeLine* >::iterator it = mAppearingWindows.begin(); while (it != mAppearingWindows.end()) { it.value()->setDuration(animationTime(mFadeInTime)); ++it; } it = mDisappearingWindows.begin(); while (it != mDisappearingWindows.end()) { it.value()->setDuration(animationTime(mFadeOutTime)); ++it; } QHash< const EffectWindow*, Data >::iterator wIt = mWindowsData.begin(); while (wIt != mWindowsData.end()) { wIt.value().fadeInDuration = mFadeInTime; wIt.value().fadeOutDuration = mFadeOutTime; ++wIt; } } void SlidingPopupsEffect::prePaintScreen(ScreenPrePaintData& data, int time) { effects->prePaintScreen(data, time); } void SlidingPopupsEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { qreal progress = 1.0; bool appearing = false; if (mAppearingWindows.contains(w)) { mAppearingWindows[ w ]->setCurrentTime(mAppearingWindows[ w ]->currentTime() + time); if (mAppearingWindows[ w ]->currentValue() < 1) { data.setTransformed(); progress = mAppearingWindows[ w ]->currentValue(); appearing = true; } else { delete mAppearingWindows.take(w); w->setData(WindowForceBlurRole, false); if (m_backgroundContrastForced.contains(w) && w->hasAlpha() && w->data(WindowForceBackgroundContrastRole).toBool()) { w->setData(WindowForceBackgroundContrastRole, QVariant()); m_backgroundContrastForced.removeAll(w); } } } else if (mDisappearingWindows.contains(w)) { mDisappearingWindows[ w ]->setCurrentTime(mDisappearingWindows[ w ]->currentTime() + time); progress = mDisappearingWindows[ w ]->currentValue(); if (progress != 1.0) { data.setTransformed(); w->enablePainting(EffectWindow::PAINT_DISABLED | EffectWindow::PAINT_DISABLED_BY_DELETE); } else { delete mDisappearingWindows.take(w); w->addRepaintFull(); if (w->isDeleted()) { w->unrefWindow(); } } } if (progress != 1.0) { const int start = mWindowsData[ w ].start; if (start != 0) { const QRect screenRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop()); const QRect geo = w->expandedGeometry(); // filter out window quads, but only if the window does not start from the edge int slideLength; if (mWindowsData[ w ].slideLength > 0) { slideLength = mWindowsData[ w ].slideLength; } else { slideLength = mSlideLength; } switch(mWindowsData[ w ].from) { case West: { const double splitPoint = geo.width() - (geo.x() + geo.width() - screenRect.x() - start) + qMin(geo.width(), slideLength) * (appearing ? 1.0 - progress : progress); data.quads = data.quads.splitAtX(splitPoint); WindowQuadList filtered; foreach (const WindowQuad &quad, data.quads) { if (quad.left() >= splitPoint) { filtered << quad; } } data.quads = filtered; break; } case North: { const double splitPoint = geo.height() - (geo.y() + geo.height() - screenRect.y() - start) + qMin(geo.height(), slideLength) * (appearing ? 1.0 - progress : progress); data.quads = data.quads.splitAtY(splitPoint); WindowQuadList filtered; foreach (const WindowQuad &quad, data.quads) { if (quad.top() >= splitPoint) { filtered << quad; } } data.quads = filtered; break; } case East: { const double splitPoint = screenRect.x() + screenRect.width() - geo.x() - start - qMin(geo.width(), slideLength) * (appearing ? 1.0 - progress : progress); data.quads = data.quads.splitAtX(splitPoint); WindowQuadList filtered; foreach (const WindowQuad &quad, data.quads) { if (quad.right() <= splitPoint) { filtered << quad; } } data.quads = filtered; break; } case South: default: { const double splitPoint = screenRect.y() + screenRect.height() - geo.y() - start - qMin(geo.height(), slideLength) * (appearing ? 1.0 - progress : progress); data.quads = data.quads.splitAtY(splitPoint); WindowQuadList filtered; foreach (const WindowQuad &quad, data.quads) { if (quad.bottom() <= splitPoint) { filtered << quad; } } data.quads = filtered; break; } } } } effects->prePaintWindow(w, data, time); } void SlidingPopupsEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { bool animating = false; bool appearing = false; if (mAppearingWindows.contains(w)) { appearing = true; animating = true; } else if (mDisappearingWindows.contains(w)) { appearing = false; animating = true; } if (animating) { qreal progress; if (appearing) progress = 1.0 - mAppearingWindows[ w ]->currentValue(); else { if (mDisappearingWindows.contains(w)) progress = mDisappearingWindows[ w ]->currentValue(); else progress = 1.0; } const int start = mWindowsData[ w ].start; int slideLength; if (mWindowsData[ w ].slideLength > 0) { slideLength = mWindowsData[ w ].slideLength; } else { slideLength = mSlideLength; } const QRect screenRect = effects->clientArea(FullScreenArea, w->screen(), w->desktop()); int splitPoint = 0; const QRect geo = w->expandedGeometry(); switch(mWindowsData[ w ].from) { case West: if (slideLength < geo.width()) { data.multiplyOpacity(1 - progress); } data.translate(- qMin(geo.width(), slideLength) * progress); splitPoint = geo.width() - (geo.x() + geo.width() - screenRect.x() - start); region = QRegion(geo.x() + splitPoint, geo.y(), geo.width() - splitPoint, geo.height()); break; case North: if (slideLength < geo.height()) { data.multiplyOpacity(1 - progress); } data.translate(0.0, - qMin(geo.height(), slideLength) * progress); splitPoint = geo.height() - (geo.y() + geo.height() - screenRect.y() - start); region = QRegion(geo.x(), geo.y() + splitPoint, geo.width(), geo.height() - splitPoint); break; case East: if (slideLength < geo.width()) { data.multiplyOpacity(1 - progress); } data.translate(qMin(geo.width(), slideLength) * progress); splitPoint = screenRect.x() + screenRect.width() - geo.x() - start; region = QRegion(geo.x(), geo.y(), splitPoint, geo.height()); break; case South: default: if (slideLength < geo.height()) { data.multiplyOpacity(1 - progress); } data.translate(0.0, qMin(geo.height(), slideLength) * progress); splitPoint = screenRect.y() + screenRect.height() - geo.y() - start; region = QRegion(geo.x(), geo.y(), geo.width(), splitPoint); } } effects->paintWindow(w, mask, region, data); } void SlidingPopupsEffect::postPaintWindow(EffectWindow* w) { if (mAppearingWindows.contains(w) || mDisappearingWindows.contains(w)) { w->addRepaintFull(); // trigger next animation repaint } effects->postPaintWindow(w); } void SlidingPopupsEffect::slotWindowAdded(EffectWindow *w) { //X11 if (mAtom != XCB_ATOM_NONE) { slotPropertyNotify(w, mAtom); } //Wayland if (auto surf = w->surface()) { slotWaylandSlideOnShowChanged(w); connect(surf, &KWayland::Server::SurfaceInterface::slideOnShowHideChanged, this, [this, surf] { slotWaylandSlideOnShowChanged(effects->findWindow(surf)); }); } startForShow(w); } void SlidingPopupsEffect::startForShow(EffectWindow *w) { if (w->isOnCurrentDesktop() && mWindowsData.contains(w)) { if (!w->data(WindowForceBackgroundContrastRole).isValid() && w->hasAlpha()) { w->setData(WindowForceBackgroundContrastRole, QVariant(true)); m_backgroundContrastForced.append(w); } auto it = mDisappearingWindows.find(w); if (it != mDisappearingWindows.end()) { delete it.value(); mDisappearingWindows.erase(it); } it = mAppearingWindows.find(w); if (it != mAppearingWindows.end()) { delete it.value(); mAppearingWindows.erase(it); } mAppearingWindows.insert(w, new QTimeLine(mWindowsData[ w ].fadeInDuration, this)); mAppearingWindows[ w ]->setCurveShape(QTimeLine::EaseInOutCurve); w->setData(WindowAddedGrabRole, QVariant::fromValue(static_cast(this))); w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast(this))); w->setData(WindowForceBlurRole, true); w->addRepaintFull(); } } void SlidingPopupsEffect::slotWindowClosed(EffectWindow* w) { if (w->isOnCurrentDesktop() && !w->isMinimized() && mWindowsData.contains(w)) { if (w->isDeleted()) { w->refWindow(); } auto it = mAppearingWindows.find(w); if (it != mAppearingWindows.end()) { delete it.value(); mAppearingWindows.erase(it); } // could be already running, better check if (mDisappearingWindows.contains(w)) { return; } mDisappearingWindows.insert(w, new QTimeLine(mWindowsData[ w ].fadeOutDuration, this)); mDisappearingWindows[ w ]->setCurveShape(QTimeLine::EaseInOutCurve); // Tell other windowClosed() effects to ignore this window w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast(this))); w->setData(WindowForceBlurRole, true); if (!w->data(WindowForceBackgroundContrastRole).isValid() && w->hasAlpha()) { w->setData(WindowForceBackgroundContrastRole, QVariant(true)); } w->addRepaintFull(); } m_backgroundContrastForced.removeAll(w); } void SlidingPopupsEffect::slotWindowDeleted(EffectWindow* w) { delete mAppearingWindows.take(w); delete mDisappearingWindows.take(w); mWindowsData.remove(w); effects->addRepaint(w->expandedGeometry()); } void SlidingPopupsEffect::slotPropertyNotify(EffectWindow* w, long a) { if (!w || a != mAtom || mAtom == XCB_ATOM_NONE) return; QByteArray data = w->readProperty(mAtom, mAtom, 32); if (data.length() < 1) { // Property was removed, thus also remove the effect for window if (w->data(WindowClosedGrabRole).value() == this) { w->setData(WindowClosedGrabRole, QVariant()); } delete mAppearingWindows.take(w); delete mDisappearingWindows.take(w); mWindowsData.remove(w); return; } auto* d = reinterpret_cast< uint32_t* >(data.data()); Data animData; animData.start = d[ 0 ]; animData.from = (Position)d[ 1 ]; //custom duration animData.slideLength = 0; if (data.length() >= (int)(sizeof(uint32_t) * 3)) { animData.fadeInDuration = d[2]; if (data.length() >= (int)(sizeof(uint32_t) * 4)) //custom fadein animData.fadeOutDuration = d[3]; else //custom fadeout animData.fadeOutDuration = d[2]; //do we want an actual slide? if (data.length() >= (int)(sizeof(uint32_t) * 5)) animData.slideLength = d[5]; } else { animData.fadeInDuration = animationTime(mFadeInTime); animData.fadeOutDuration = animationTime(mFadeOutTime); } mWindowsData[ w ] = animData; setupAnimData(w); } void SlidingPopupsEffect::setupAnimData(EffectWindow *w) { const QRect screenRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop()); if (mWindowsData[w].start == -1) { switch (mWindowsData[w].from) { case West: mWindowsData[w].start = qMax(w->x() - screenRect.x(), 0); break; case North: mWindowsData[w].start = qMax(w->y() - screenRect.y(), 0); break; case East: mWindowsData[w].start = qMax(screenRect.x() + screenRect.width() - (w->x() + w->width()), 0); break; case South: default: mWindowsData[w].start = qMax(screenRect.y() + screenRect.height() - (w->y() + w->height()), 0); break; } } // sanitize int difference = 0; switch (mWindowsData[w].from) { case West: difference = w->x() - screenRect.x(); break; case North: difference = w->y() - screenRect.y(); break; case East: difference = w->x() + w->width() - (screenRect.x() + screenRect.width()); break; case South: default: difference = w->y() + w->height() - (screenRect.y() + screenRect.height()); break; } mWindowsData[w].start = qMax(mWindowsData[w].start, difference); // Grab the window, so other windowClosed effects will ignore it w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast(this))); } void SlidingPopupsEffect::slotWaylandSlideOnShowChanged(EffectWindow* w) { if (!w) { return; } KWayland::Server::SurfaceInterface *surf = w->surface(); if (!surf) { return; } if (surf->slideOnShowHide()) { Data animData; animData.start = surf->slideOnShowHide()->offset(); switch (surf->slideOnShowHide()->location()) { case KWayland::Server::SlideInterface::Location::Top: animData.from = North; break; case KWayland::Server::SlideInterface::Location::Left: animData.from = West; break; case KWayland::Server::SlideInterface::Location::Right: animData.from = East; break; case KWayland::Server::SlideInterface::Location::Bottom: default: animData.from = South; break; } animData.slideLength = 0; animData.fadeInDuration = animationTime(mFadeInTime); animData.fadeOutDuration = animationTime(mFadeOutTime); mWindowsData[ w ] = animData; setupAnimData(w); } } bool SlidingPopupsEffect::isActive() const { return !mAppearingWindows.isEmpty() || !mDisappearingWindows.isEmpty(); } } // namespace diff --git a/effects/slidingpopups/slidingpopups.h b/effects/slidingpopups/slidingpopups.h index ffa362253..3f6bf7f83 100644 --- a/effects/slidingpopups/slidingpopups.h +++ b/effects/slidingpopups/slidingpopups.h @@ -1,100 +1,103 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2009 Marco Martin notmart@gmail.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_SLIDINGPOPUPS_H #define KWIN_SLIDINGPOPUPS_H // Include with base class for effects. #include class QTimeLine; namespace KWin { class SlidingPopupsEffect : public Effect { Q_OBJECT Q_PROPERTY(int fadeInTime READ fadeInTime) Q_PROPERTY(int fadeOutTime READ fadeOutTime) public: SlidingPopupsEffect(); ~SlidingPopupsEffect(); virtual void prePaintScreen(ScreenPrePaintData& data, int time); virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); virtual void postPaintWindow(EffectWindow* w); virtual void reconfigure(ReconfigureFlags flags); virtual bool isActive() const; int requestedEffectChainPosition() const override { return 40; } + + static bool supported(); + // TODO react also on virtual desktop changes // for properties int fadeInTime() const { return mFadeInTime; } int fadeOutTime() const { return mFadeOutTime; } public Q_SLOTS: void slotWindowAdded(KWin::EffectWindow *c); void slotWindowClosed(KWin::EffectWindow *c); void slotWindowDeleted(KWin::EffectWindow *w); void slotPropertyNotify(KWin::EffectWindow *w, long a); void slotWaylandSlideOnShowChanged(EffectWindow* w); private: void setupAnimData(EffectWindow *w); void startForShow(EffectWindow *w); enum Position { West = 0, North = 1, East = 2, South = 3 }; struct Data { int start; //point in screen coordinates where the window starts //to animate, from decides if this point is an x or an y Position from; int fadeInDuration; int fadeOutDuration; int slideLength; }; long mAtom; // This list is only for appearing windows: we remember that we've enabled the // WindowBackgroundContrastForcedRole flag, so we can remove it later. // It doesn't matter for disappearing windows, they'll be deleted anyway. QList< const EffectWindow* > m_backgroundContrastForced; QHash< const EffectWindow*, QTimeLine* > mAppearingWindows; QHash< const EffectWindow*, QTimeLine* > mDisappearingWindows; QHash< const EffectWindow*, Data > mWindowsData; int mSlideLength; int mFadeInTime; int mFadeOutTime; }; } // namespace #endif diff --git a/effects/trackmouse/trackmouse_config.desktop b/effects/trackmouse/trackmouse_config.desktop index 462fa476b..9c12df3db 100644 --- a/effects/trackmouse/trackmouse_config.desktop +++ b/effects/trackmouse/trackmouse_config.desktop @@ -1,86 +1,86 @@ [Desktop Entry] Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kwin_trackmouse_config X-KDE-ParentComponents=trackmouse Name=Track Mouse Name[af]=Volg die Muis Name[ar]=تتبع الفارة Name[be]=Адследжваць мыш Name[be@latin]=Vyjaŭleńnie kursora Name[bg]=Проследяване на мишката Name[bn]=মাউস ট্র্যাক করো Name[bs]=Praćenje miša Name[ca]=Seguiment del ratolí Name[ca@valencia]=Seguiment del ratolí Name[cs]=Sledování myši Name[csb]=Nalezénié pòłożenia mëszë Name[da]=Sporing af mus Name[de]=Maus-Position finden Name[el]=Ανίχνευση ποντικιού Name[en_GB]=Track Mouse Name[eo]=Spuri muson Name[es]=Seguir el ratón Name[et]=Hiire jälgimine Name[eu]=Jarraitu saguari Name[fa]=ردگیری موشی Name[fi]=Hiiren jäljitys Name[fr]=Repérer la souris Name[fy]=Track Mûs Name[ga]=Lorg an Luch Name[gl]=Seguir o rato Name[gu]=માઉસની ખબર રાખો Name[he]=מעקב אחרי העכבר Name[hi]=माउस ट्रैक करें Name[hne]=मुसुवा ट्रैक करव Name[hr]=Praćenje miša Name[hu]=Egérkövetés Name[ia]=Tracia mus -Name[id]=Lacak Tetikus +Name[id]=Lacak Mouse Name[is]=Elta mús Name[it]=Trova il mouse Name[ja]=マウス追跡 Name[kk]=Тышқандың ізі Name[km]=ដាន​កណ្តុរ​ Name[kn]=ಮೂಷಕ ಹಿಂಬಾಲಕ Name[ko]=마우스 추적 Name[lt]=Pelės sekimas Name[lv]=Sekot pelei Name[mai]=माउस ट्रैक करू Name[mk]=Следење на глушецот Name[ml]=മൌസിനെ നിരീക്ഷിക്കുക Name[mr]=माऊसचा मागोवा घ्या Name[nb]=Spor mus Name[nds]=Muusspoor Name[ne]=ट्रयाक माउस Name[nl]=Muis volgen Name[nn]=Følg mus Name[pa]=ਮਾਊਸ ਟਰੈਕ Name[pl]=Śledzenie myszy Name[pt]=Seguir o Rato Name[pt_BR]=Seguir o mouse Name[ro]=Urmărește mausul Name[ru]=Поиск курсора мыши на экране Name[se]=Čuovo sáhpána Name[si]=මවුසය සොයන්න Name[sk]=Sledovať myš Name[sl]=Sledenje miški Name[sr]=Праћење миша Name[sr@ijekavian]=Праћење миша Name[sr@ijekavianlatin]=Praćenje miša Name[sr@latin]=Praćenje miša Name[sv]=Följ musen Name[ta]=எலியத்தை கவனி Name[te]=ట్రాక్ మౌస్ Name[tg]=Отслеживание мыши Name[th]=ติดตามเมาส์ Name[tr]=Fareyi İzle Name[ug]=چاشقىنەكنى ئىزلا Name[uk]=Сліди мишки Name[vi]=Vết chuột Name[wa]=Shut l' sori Name[x-test]=xxTrack Mousexx Name[zh_CN]=跟踪鼠标 Name[zh_TW]=追蹤滑鼠 diff --git a/effects/windowgeometry/windowgeometry.cpp b/effects/windowgeometry/windowgeometry.cpp index d9c623fe8..8d66eadc6 100644 --- a/effects/windowgeometry/windowgeometry.cpp +++ b/effects/windowgeometry/windowgeometry.cpp @@ -1,228 +1,237 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Thomas Lübking 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 "windowgeometry.h" // KConfigSkeleton #include "windowgeometryconfig.h" #include #include #include #include #include #include #include using namespace KWin; WindowGeometry::WindowGeometry() { initConfig(); iAmActivated = true; iAmActive = false; myResizeWindow = 0L; #define myResizeString "Window geometry display, %1 and %2 are the new size," \ " %3 and %4 are pixel increments - avoid reformatting or suffixes like 'px'", \ "Width: %1 (%3)\nHeight: %2 (%4)" #define myCoordString_0 "Window geometry display, %1 and %2 are the cartesian x and y coordinates" \ " - avoid reformatting or suffixes like 'px'", \ "X: %1\nY: %2" #define myCoordString_1 "Window geometry display, %1 and %2 are the cartesian x and y coordinates," \ " %3 and %4 are the resp. increments - avoid reformatting or suffixes like 'px'", \ "X: %1 (%3)\nY: %2 (%4)" reconfigure(ReconfigureAll); - QFont fnt; fnt.setBold(true); fnt.setPointSize(12); - for (int i = 0; i < 3; ++i) { - myMeasure[i] = effects->effectFrame(EffectFrameUnstyled, false); - myMeasure[i]->setFont(fnt); - } - myMeasure[0]->setAlignment(Qt::AlignLeft | Qt::AlignTop); - myMeasure[1]->setAlignment(Qt::AlignCenter); - myMeasure[2]->setAlignment(Qt::AlignRight | Qt::AlignBottom); - QAction* a = new QAction(this); a->setObjectName(QStringLiteral("WindowGeometry")); a->setText(i18n("Toggle window geometry display (effect only)")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::CTRL + Qt::SHIFT + Qt::Key_F11); KGlobalAccel::self()->setShortcut(a, QList() << Qt::CTRL + Qt::SHIFT + Qt::Key_F11); effects->registerGlobalShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_F11, a); connect(a, SIGNAL(triggered(bool)), this, SLOT(toggle())); connect(effects, SIGNAL(windowStartUserMovedResized(KWin::EffectWindow*)), this, SLOT(slotWindowStartUserMovedResized(KWin::EffectWindow*))); connect(effects, SIGNAL(windowFinishUserMovedResized(KWin::EffectWindow*)), this, SLOT(slotWindowFinishUserMovedResized(KWin::EffectWindow*))); connect(effects, SIGNAL(windowStepUserMovedResized(KWin::EffectWindow*,QRect)), this, SLOT(slotWindowStepUserMovedResized(KWin::EffectWindow*,QRect))); } WindowGeometry::~WindowGeometry() { for (int i = 0; i < 3; ++i) delete myMeasure[i]; } +void WindowGeometry::createFrames() +{ + if (myMeasure[0]) { + return; + } + QFont fnt; fnt.setBold(true); fnt.setPointSize(12); + for (int i = 0; i < 3; ++i) { + myMeasure[i] = effects->effectFrame(EffectFrameUnstyled, false); + myMeasure[i]->setFont(fnt); + } + myMeasure[0]->setAlignment(Qt::AlignLeft | Qt::AlignTop); + myMeasure[1]->setAlignment(Qt::AlignCenter); + myMeasure[2]->setAlignment(Qt::AlignRight | Qt::AlignBottom); + +} + void WindowGeometry::reconfigure(ReconfigureFlags) { WindowGeometryConfiguration::self()->read(); iHandleMoves = WindowGeometryConfiguration::move(); iHandleResizes = WindowGeometryConfiguration::resize(); } void WindowGeometry::paintScreen(int mask, QRegion region, ScreenPaintData &data) { effects->paintScreen(mask, region, data); if (iAmActivated && iAmActive) { for (int i = 0; i < 3; ++i) myMeasure[i]->render(infiniteRegion(), 1.0, .66); } } void WindowGeometry::toggle() { iAmActivated = !iAmActivated; + createFrames(); } void WindowGeometry::slotWindowStartUserMovedResized(EffectWindow *w) { if (!iAmActivated) return; if (w->isUserResize() && !iHandleResizes) return; if (w->isUserMove() && !iHandleMoves) return; + createFrames(); iAmActive = true; myResizeWindow = w; myOriginalGeometry = w->geometry(); myCurrentGeometry = w->geometry(); slotWindowStepUserMovedResized(w, w->geometry()); } void WindowGeometry::slotWindowFinishUserMovedResized(EffectWindow *w) { if (iAmActive && w == myResizeWindow) { iAmActive = false; myResizeWindow = 0L; w->addRepaintFull(); if (myExtraDirtyArea.isValid()) w->addLayerRepaint(myExtraDirtyArea); myExtraDirtyArea = QRect(); } } static inline QString number(int n) { QLocale locale; QString sign; if (n >= 0) { sign = locale.positiveSign(); if (sign.isEmpty()) sign = QStringLiteral("+"); } else { n = -n; sign = locale.negativeSign(); if (sign.isEmpty()) sign = QStringLiteral("-"); } return sign + QString::number(n); } void WindowGeometry::slotWindowStepUserMovedResized(EffectWindow *w, const QRect &geometry) { if (iAmActivated && iAmActive && w == myResizeWindow) { if (myExtraDirtyArea.isValid()) effects->addRepaint(myExtraDirtyArea); myExtraDirtyArea = QRect(); myCurrentGeometry = geometry; QPoint center = geometry.center(); const QRect &r = geometry; const QRect &r2 = myOriginalGeometry; const QRect screen = effects->clientArea(ScreenArea, center, w->desktop()); QRect expandedGeometry = w->expandedGeometry(); expandedGeometry = geometry.adjusted(expandedGeometry.x() - w->x(), expandedGeometry.y() - w->y(), expandedGeometry.right() - w->geometry().right(), expandedGeometry.bottom() - w->geometry().bottom()); // sufficient for moves, resizes calculated otherwise int dx = r.x() - r2.x(); int dy = r.y() - r2.y(); // upper left ---------------------- if (w->isUserResize()) myMeasure[0]->setText( i18nc(myCoordString_1, r.x(), r.y(), number(dx), number(dy) ) ); else myMeasure[0]->setText( i18nc(myCoordString_0, r.x(), r.y() ) ); QPoint pos = expandedGeometry.topLeft(); pos = QPoint(qMax(pos.x(), screen.x()), qMax(pos.y(), screen.y())); myMeasure[0]->setPosition(pos + QPoint(6,6)); // "6" is magic number because the unstyled effectframe has 5px padding // center ---------------------- if (w->isUserResize()) { // calc width for center element, otherwise the current dx/dx remains right dx = r.width() - r2.width(); dy = r.height() - r2.height(); const QSize baseInc = w->basicUnit(); if (baseInc != QSize(1,1)) { Q_ASSERT(baseInc.width() && baseInc.height()); const QSize csz = w->contentsRect().size(); myMeasure[1]->setText( i18nc(myResizeString, csz.width()/baseInc.width(), csz.height()/baseInc.height(), number(dx/baseInc.width()), number(dy/baseInc.height()) ) ); } else myMeasure[1]->setText( i18nc(myResizeString, r.width(), r.height(), number(dx), number(dy) ) ); // calc width for bottomright element, superfluous otherwise dx = r.right() - r2.right(); dy = r.bottom() - r2.bottom(); } else myMeasure[1]->setText( i18nc(myCoordString_0, number(dx), number(dy) ) ); const int cdx = myMeasure[1]->geometry().width() / 2 + 3; // "3" = 6/2 is magic number because const int cdy = myMeasure[1]->geometry().height() / 2 + 3; // the unstyled effectframe has 5px padding center = QPoint(qMax(center.x(), screen.x() + cdx), qMax(center.y(), screen.y() + cdy)); center = QPoint(qMin(center.x(), screen.right() - cdx), qMin(center.y(), screen.bottom() - cdy)); myMeasure[1]->setPosition(center); // lower right ---------------------- if (w->isUserResize()) myMeasure[2]->setText( i18nc(myCoordString_1, r.right(), r.bottom(), number(dx), number(dy) ) ); else myMeasure[2]->setText( i18nc(myCoordString_0, r.right(), r.bottom() ) ); pos = expandedGeometry.bottomRight(); pos = QPoint(qMin(pos.x(), screen.right()), qMin(pos.y(), screen.bottom())); myMeasure[2]->setPosition(pos - QPoint(6,6)); // "6" is magic number because the unstyled effectframe has 5px padding myExtraDirtyArea |= myMeasure[0]->geometry(); myExtraDirtyArea |= myMeasure[1]->geometry(); myExtraDirtyArea |= myMeasure[2]->geometry(); myExtraDirtyArea.adjust(-6,-6,6,6); if (myExtraDirtyArea.isValid()) effects->addRepaint(myExtraDirtyArea); } } bool WindowGeometry::isActive() const { return iAmActive; } diff --git a/effects/windowgeometry/windowgeometry.h b/effects/windowgeometry/windowgeometry.h index 276eaf1d2..6c05550bb 100644 --- a/effects/windowgeometry/windowgeometry.h +++ b/effects/windowgeometry/windowgeometry.h @@ -1,72 +1,73 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Thomas Lübking This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef WINDOWGEOMETRY_H #define WINDOWGEOMETRY_H #include namespace KWin { class WindowGeometry : public Effect { Q_OBJECT Q_PROPERTY(bool handlesMoves READ isHandlesMoves) Q_PROPERTY(bool handlesResizes READ isHandlesResizes) public: WindowGeometry(); ~WindowGeometry(); inline bool provides(Effect::Feature ef) { return ef == Effect::GeometryTip; } void reconfigure(ReconfigureFlags); void paintScreen(int mask, QRegion region, ScreenPaintData &data); virtual bool isActive() const; int requestedEffectChainPosition() const override { return 90; } // for properties bool isHandlesMoves() const { return iHandleMoves; } bool isHandlesResizes() const { return iHandleResizes; } private Q_SLOTS: void toggle(); void slotWindowStartUserMovedResized(KWin::EffectWindow *w); void slotWindowFinishUserMovedResized(KWin::EffectWindow *w); void slotWindowStepUserMovedResized(KWin::EffectWindow *w, const QRect &geometry); private: + void createFrames(); EffectWindow *myResizeWindow; - EffectFrame *myMeasure[3]; + EffectFrame *myMeasure[3] = {nullptr, nullptr, nullptr}; QRect myOriginalGeometry, myCurrentGeometry; QRect myExtraDirtyArea; bool iAmActive, iAmActivated, iHandleMoves, iHandleResizes; QString myCoordString[2], myResizeString; }; } // namespace #endif diff --git a/effects/zoom/zoom_config.desktop b/effects/zoom/zoom_config.desktop index 6cded0944..82ff74049 100644 --- a/effects/zoom/zoom_config.desktop +++ b/effects/zoom/zoom_config.desktop @@ -1,92 +1,92 @@ [Desktop Entry] Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kwin_zoom_config X-KDE-ParentComponents=zoom Name=Zoom Name[af]=Zoem Name[ar]=تكبير Name[as]=ডাঙৰকৈ প্ৰদৰ্শন Name[be]=Маштабаванне Name[be@latin]=Maštab Name[bg]=Мащабиране Name[bn_IN]=বড় করে প্রদর্শন Name[br]=Zoom Name[bs]=Uveličanje Name[ca]=Zoom Name[ca@valencia]=Zoom Name[cs]=Zvětšení Name[csb]=Zwikasznié Name[da]=Zoom Name[de]=Arbeitsflächen-Vergrößerung Name[el]=Εστίαση Name[en_GB]=Zoom Name[eo]=Zomi Name[es]=Ampliación Name[et]=Suurendus Name[eu]=Handiagotu Name[fa]=بزرگ‌نمایی Name[fi]=Zoomaus Name[fr]=Zoom Name[fy]=Zoome Name[ga]=Súmáil Name[gl]=Ampliación Name[gu]=મોટું કરો Name[he]=מגדיל Name[hi]=ज़ूम Name[hne]=जूम Name[hr]=Povećanje Name[hu]=Kinagyítás Name[ia]=Zoom -Name[id]=Pembesaran +Name[id]=Perbesaran Name[is]=Aðdráttur Name[it]=Ingrandimento Name[ja]=ズーム Name[kk]=Ұлғайту Name[km]=ពង្រីក Name[kn]=ಹಿಗ್ಗಿಸು Name[ko]=확대/축소 Name[ku]=Mezinkirin Name[lt]=Išdidinimas Name[lv]=Palielināšana Name[mai]=जूम Name[mk]=Зум Name[ml]=വലുതാക്കുക Name[mr]=झूम Name[nb]=Skalering Name[nds]=Ansichtgrött Name[ne]=जुम गर्नुहोस् Name[nl]=Zoomen Name[nn]=Forstørr skrivebordet Name[oc]=Zoom Name[pa]=ਜ਼ੂਮ Name[pl]=Powiększanie Name[pt]=Ampliação Name[pt_BR]=Zoom Name[ro]=Scalare Name[ru]=Масштаб Name[se]=Stuorrudit Name[si]=විශාලණය Name[sk]=Lupa Name[sl]=Približanje Name[sr]=Увеличање Name[sr@ijekavian]=Увеличање Name[sr@ijekavianlatin]=Uveličanje Name[sr@latin]=Uveličanje Name[sv]=Zoom Name[ta]=Zoom Name[te]=జూమ్ Name[tg]=Калонкунӣ Name[th]=ดูย่อ/ดูขยาย Name[tr]=Büyüt Name[ug]=كېڭەيت-تارايت Name[uk]=Масштабування Name[uz]=Kattalashtirish Name[uz@cyrillic]=Катталаштириш Name[vi]=Thu/Phóng Name[wa]=Zoum Name[x-test]=xxZoomxx Name[zh_CN]=缩放 Name[zh_TW]=縮放 diff --git a/geometry.cpp b/geometry.cpp index 78d0e0b58..7885b6b36 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1,3535 +1,3538 @@ /******************************************************************** 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) 2009 Lucas Murray This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to geometry, i.e. workspace size, window positions and window sizes. */ #include "client.h" #include "composite.h" #include "cursor.h" #include "netinfo.h" #include "workspace.h" #include "placement.h" #include "geometrytip.h" #include "rules.h" #include "screens.h" #include "effects.h" #include "screenedge.h" #include #include #include #include "outline.h" #include "shell_client.h" #include "wayland_server.h" #include #include namespace KWin { static inline int sign(int v) { return (v > 0) - (v < 0); } //******************************************** // Workspace //******************************************** extern int screen_number; extern bool is_multihead; /*! Resizes the workspace after an XRANDR screen size change */ void Workspace::desktopResized() { QRect geom = screens()->geometry(); if (rootInfo()) { NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo()->setDesktopGeometry(desktop_geometry); } updateClientArea(); saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one // TODO: emit a signal instead and remove the deep function calls into edges and effects ScreenEdges::self()->recreateEdges(); if (effects) { static_cast(effects)->desktopResized(geom.size()); } } void Workspace::saveOldScreenSizes() { olddisplaysize = screens()->displaySize(); oldscreensizes.clear(); for( int i = 0; i < screens()->count(); ++i ) oldscreensizes.append( screens()->geometry( i )); } /*! Updates the current client areas according to the current clients. If the area changes or force is true, the new areas are propagated to the world. The client area is the area that is available for clients (that which is not taken by windows like panels, the top-of-screen menu etc). \sa clientArea() */ void Workspace::updateClientArea(bool force) { const Screens *s = Screens::self(); int nscreens = s->count(); const int numberOfDesktops = VirtualDesktopManager::self()->count(); QVector< QRect > new_wareas(numberOfDesktops + 1); QVector< StrutRects > new_rmoveareas(numberOfDesktops + 1); QVector< QVector< QRect > > new_sareas(numberOfDesktops + 1); QVector< QRect > screens(nscreens); QRect desktopArea; for (int i = 0; i < nscreens; i++) { desktopArea |= s->geometry(i); } for (int iS = 0; iS < nscreens; iS ++) { screens [iS] = s->geometry(iS); } for (int i = 1; i <= numberOfDesktops; ++i) { new_wareas[ i ] = desktopArea; new_sareas[ i ].resize(nscreens); for (int iS = 0; iS < nscreens; iS ++) new_sareas[ i ][ iS ] = screens[ iS ]; } for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->hasStrut()) continue; QRect r = (*it)->adjustedClientArea(desktopArea, desktopArea); // sanity check that a strut doesn't exclude a complete screen geometry // this is a violation to EWMH, as KWin just ignores the strut for (int i = 0; i < Screens::self()->count(); i++) { if (!r.intersects(Screens::self()->geometry(i))) { qCDebug(KWIN_CORE) << "Adjusted client area would exclude a complete screen, ignore"; r = desktopArea; break; } } StrutRects strutRegion = (*it)->strutRects(); const QRect clientsScreenRect = KWin::screens()->geometry((*it)->screen()); for (auto strut = strutRegion.begin(); strut != strutRegion.end(); strut++) { *strut = StrutRect((*strut).intersected(clientsScreenRect), (*strut).area()); } // Ignore offscreen xinerama struts. These interfere with the larger monitors on the setup // and should be ignored so that applications that use the work area to work out where // windows can go can use the entire visible area of the larger monitors. // This goes against the EWMH description of the work area but it is a toss up between // having unusable sections of the screen (Which can be quite large with newer monitors) // or having some content appear offscreen (Relatively rare compared to other). bool hasOffscreenXineramaStrut = (*it)->hasOffscreenXineramaStrut(); if ((*it)->isOnAllDesktops()) { for (int i = 1; i <= numberOfDesktops; ++i) { if (!hasOffscreenXineramaStrut) new_wareas[ i ] = new_wareas[ i ].intersected(r); new_rmoveareas[ i ] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { const auto geo = new_sareas[ i ][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); // ignore the geometry if it results in the screen getting removed completly if (!geo.isEmpty()) { new_sareas[ i ][ iS ] = geo; } } } } else { if (!hasOffscreenXineramaStrut) new_wareas[(*it)->desktop()] = new_wareas[(*it)->desktop()].intersected(r); new_rmoveareas[(*it)->desktop()] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { // qDebug() << "adjusting new_sarea: " << screens[ iS ]; const auto geo = new_sareas[(*it)->desktop()][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); // ignore the geometry if it results in the screen getting removed completly if (!geo.isEmpty()) { new_sareas[(*it)->desktop()][ iS ] = geo; } } } } if (waylandServer()) { auto updateStrutsForWaylandClient = [&] (ShellClient *c) { // assuming that only docks have "struts" and that all docks have a strut if (!c->hasStrut()) { return; } auto margins = [c] (const QRect &geometry) { QMargins margins; if (!geometry.intersects(c->geometry())) { return margins; } // figure out which areas of the overall screen setup it borders const bool left = c->geometry().left() == geometry.left(); const bool right = c->geometry().right() == geometry.right(); const bool top = c->geometry().top() == geometry.top(); const bool bottom = c->geometry().bottom() == geometry.bottom(); const bool horizontal = c->geometry().width() >= c->geometry().height(); if (left && ((!top && !bottom) || !horizontal)) { margins.setLeft(c->geometry().width()); } if (right && ((!top && !bottom) || !horizontal)) { margins.setRight(c->geometry().width()); } if (top && ((!left && !right) || horizontal)) { margins.setTop(c->geometry().height()); } if (bottom && ((!left && !right) || horizontal)) { margins.setBottom(c->geometry().height()); } return margins; }; auto marginsToStrutArea = [] (const QMargins &margins) { if (margins.left() != 0) { return StrutAreaLeft; } if (margins.right() != 0) { return StrutAreaRight; } if (margins.top() != 0) { return StrutAreaTop; } if (margins.bottom() != 0) { return StrutAreaBottom; } return StrutAreaInvalid; }; const auto strut = margins(KWin::screens()->geometry(c->screen())); const StrutRects strutRegion = StrutRects{StrutRect(c->geometry(), marginsToStrutArea(strut))}; QRect r = desktopArea - margins(KWin::screens()->geometry()); if (c->isOnAllDesktops()) { for (int i = 1; i <= numberOfDesktops; ++i) { new_wareas[ i ] = new_wareas[ i ].intersected(r); for (int iS = 0; iS < nscreens; ++iS) { new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected(screens[iS] - margins(screens[iS])); } new_rmoveareas[ i ] += strutRegion; } } else { new_wareas[c->desktop()] = new_wareas[c->desktop()].intersected(r); for (int iS = 0; iS < nscreens; iS++) { new_sareas[c->desktop()][ iS ] = new_sareas[c->desktop()][ iS ].intersected(screens[iS] - margins(screens[iS])); } new_rmoveareas[ c->desktop() ] += strutRegion; } }; const auto clients = waylandServer()->clients(); for (auto c : clients) { updateStrutsForWaylandClient(c); } const auto internalClients = waylandServer()->internalClients(); for (auto c : internalClients) { updateStrutsForWaylandClient(c); } } #if 0 for (int i = 1; i <= numberOfDesktops(); ++i) { for (int iS = 0; iS < nscreens; iS ++) qCDebug(KWIN_CORE) << "new_sarea: " << new_sareas[ i ][ iS ]; } #endif bool changed = force; if (screenarea.isEmpty()) changed = true; for (int i = 1; !changed && i <= numberOfDesktops; ++i) { if (workarea[ i ] != new_wareas[ i ]) changed = true; if (restrictedmovearea[ i ] != new_rmoveareas[ i ]) changed = true; if (screenarea[ i ].size() != new_sareas[ i ].size()) changed = true; for (int iS = 0; !changed && iS < nscreens; iS ++) if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) changed = true; } if (changed) { workarea = new_wareas; oldrestrictedmovearea = restrictedmovearea; restrictedmovearea = new_rmoveareas; screenarea = new_sareas; if (rootInfo()) { NETRect r; for (int i = 1; i <= numberOfDesktops; i++) { r.pos.x = workarea[ i ].x(); r.pos.y = workarea[ i ].y(); r.size.width = workarea[ i ].width(); r.size.height = workarea[ i ].height(); rootInfo()->setWorkArea(i, r); } } for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) (*it)->checkWorkspacePosition(); for (ClientList::ConstIterator it = desktops.constBegin(); it != desktops.constEnd(); ++it) (*it)->checkWorkspacePosition(); oldrestrictedmovearea.clear(); // reset, no longer valid or needed } } void Workspace::updateClientArea() { updateClientArea(false); } /*! returns the area available for clients. This is the desktop geometry minus windows on the dock. Placement algorithms should refer to this rather than geometry(). \sa geometry() */ QRect Workspace::clientArea(clientAreaOption opt, int screen, int desktop) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = VirtualDesktopManager::self()->current(); if (screen == -1) screen = screens()->current(); const QSize displaySize = screens()->displaySize(); QRect sarea, warea; if (is_multihead) { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen_number ] : screens()->geometry(screen_number); warea = workarea[ desktop ].isNull() ? screens()->geometry(screen_number) : workarea[ desktop ]; } else { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen ] : screens()->geometry(screen); warea = workarea[ desktop ].isNull() ? QRect(0, 0, displaySize.width(), displaySize.height()) : workarea[ desktop ]; } switch(opt) { case MaximizeArea: case PlacementArea: return sarea; case MaximizeFullArea: case FullScreenArea: case MovementArea: case ScreenArea: if (is_multihead) return screens()->geometry(screen_number); else return screens()->geometry(screen); case WorkArea: if (is_multihead) return sarea; else return warea; case FullArea: return QRect(0, 0, displaySize.width(), displaySize.height()); } abort(); } QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const { return clientArea(opt, screens()->number(p), desktop); } QRect Workspace::clientArea(clientAreaOption opt, const AbstractClient* c) const { return clientArea(opt, c->geometry().center(), c->desktop()); } QRegion Workspace::restrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = VirtualDesktopManager::self()->current(); QRegion region; foreach (const StrutRect & rect, restrictedmovearea[desktop]) if (areas & rect.area()) region += rect; return region; } bool Workspace::inUpdateClientArea() const { return !oldrestrictedmovearea.isEmpty(); } QRegion Workspace::previousRestrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = VirtualDesktopManager::self()->current(); QRegion region; foreach (const StrutRect & rect, oldrestrictedmovearea.at(desktop)) if (areas & rect.area()) region += rect; return region; } QVector< QRect > Workspace::previousScreenSizes() const { return oldscreensizes; } int Workspace::oldDisplayWidth() const { return olddisplaysize.width(); } int Workspace::oldDisplayHeight() const { return olddisplaysize.height(); } /*! Client \a c is moved around to position \a pos. This gives the workspace the opportunity to interveniate and to implement snap-to-windows functionality. The parameter \a snapAdjust is a multiplier used to calculate the effective snap zones. When 1.0, it means that the snap zones will be used without change. */ QPoint Workspace::adjustClientPosition(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust) { QSize borderSnapZone(options->borderSnapZone(), options->borderSnapZone()); QRect maxRect; int guideMaximized = MaximizeRestore; if (c->maximizeMode() != MaximizeRestore) { maxRect = clientArea(MaximizeArea, pos + c->rect().center(), c->desktop()); QRect geo = c->geometry(); if (c->maximizeMode() & MaximizeHorizontal && (geo.x() == maxRect.left() || geo.right() == maxRect.right())) { guideMaximized |= MaximizeHorizontal; borderSnapZone.setWidth(qMax(borderSnapZone.width() + 2, maxRect.width() / 16)); } if (c->maximizeMode() & MaximizeVertical && (geo.y() == maxRect.top() || geo.bottom() == maxRect.bottom())) { guideMaximized |= MaximizeVertical; borderSnapZone.setHeight(qMax(borderSnapZone.height() + 2, maxRect.height() / 16)); } } if (options->windowSnapZone() || !borderSnapZone.isNull() || options->centerSnapZone()) { const bool sOWO = options->isSnapOnlyWhenOverlapping(); const int screen = screens()->number(pos + c->rect().center()); if (maxRect.isNull()) maxRect = clientArea(MovementArea, screen, c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right() + 1; //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom() + 1; const int cx(pos.x()); const int cy(pos.y()); const int cw(c->width()); const int ch(c->height()); const int rx(cx + cw); const int ry(cy + ch); //these don't change int nx(cx), ny(cy); //buffers int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap const int snapX = borderSnapZone.width() * snapAdjust; //snap trigger const int snapY = borderSnapZone.height() * snapAdjust; if (snapX || snapY) { QRect geo = c->geometry(); const QPoint cp = c->clientPos(); const QSize cs = geo.size() - c->clientSize(); int padding[4] = { cp.x(), cs.width() - cp.x(), cp.y(), cs.height() - cp.y() }; // snap to titlebar / snap to window borders on inner screen edges Client::Position titlePos = c->titlebarPosition(); if (padding[0] && (titlePos == Client::PositionLeft || (c->maximizeMode() & MaximizeHorizontal) || screens()->intersecting(geo.translated(maxRect.x() - (padding[0] + geo.x()), 0)) > 1)) padding[0] = 0; if (padding[1] && (titlePos == Client::PositionRight || (c->maximizeMode() & MaximizeHorizontal) || screens()->intersecting(geo.translated(maxRect.right() + padding[1] - geo.right(), 0)) > 1)) padding[1] = 0; if (padding[2] && (titlePos == Client::PositionTop || (c->maximizeMode() & MaximizeVertical) || screens()->intersecting(geo.translated(0, maxRect.y() - (padding[2] + geo.y()))) > 1)) padding[2] = 0; if (padding[3] && (titlePos == Client::PositionBottom || (c->maximizeMode() & MaximizeVertical) || screens()->intersecting(geo.translated(0, maxRect.bottom() + padding[3] - geo.bottom())) > 1)) padding[3] = 0; if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) { deltaX = xmin - cx; nx = xmin - padding[0]; } if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) { deltaX = rx - xmax; nx = xmax - cw + padding[1]; } if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) { deltaY = ymin - cy; ny = ymin - padding[2]; } if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) { deltaY = ry - ymax; ny = ymax - ch + padding[3]; } } // windows snap int snap = options->windowSnapZone() * snapAdjust; if (snap) { for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) { if ((*l) == c) continue; if ((*l)->isMinimized()) continue; // is minimized if (!(*l)->isShown(false)) continue; if ((*l)->tabGroup() && (*l) != (*l)->tabGroup()->current()) continue; // is not active tab if (!((*l)->isOnDesktop(c->desktop()) || c->isOnDesktop((*l)->desktop()))) continue; // wrong virtual desktop if (!(*l)->isOnCurrentActivity()) continue; // wrong activity if ((*l)->isDesktop() || (*l)->isSplash()) continue; lx = (*l)->x(); ly = (*l)->y(); lrx = lx + (*l)->width(); lry = ly + (*l)->height(); if (!(guideMaximized & MaximizeHorizontal) && (((cy <= lry) && (cy >= ly)) || ((ry >= ly) && (ry <= lry)) || ((cy <= ly) && (ry >= lry)))) { if ((sOWO ? (cx < lrx) : true) && (qAbs(lrx - cx) < snap) && (qAbs(lrx - cx) < deltaX)) { deltaX = qAbs(lrx - cx); nx = lrx; } if ((sOWO ? (rx > lx) : true) && (qAbs(rx - lx) < snap) && (qAbs(rx - lx) < deltaX)) { deltaX = qAbs(rx - lx); nx = lx - cw; } } if (!(guideMaximized & MaximizeVertical) && (((cx <= lrx) && (cx >= lx)) || ((rx >= lx) && (rx <= lrx)) || ((cx <= lx) && (rx >= lrx)))) { if ((sOWO ? (cy < lry) : true) && (qAbs(lry - cy) < snap) && (qAbs(lry - cy) < deltaY)) { deltaY = qAbs(lry - cy); ny = lry; } //if ( (qAbs( ry-ly ) < snap) && (qAbs( ry - ly ) < deltaY )) if ((sOWO ? (ry > ly) : true) && (qAbs(ry - ly) < snap) && (qAbs(ry - ly) < deltaY)) { deltaY = qAbs(ry - ly); ny = ly - ch; } } // Corner snapping if (!(guideMaximized & MaximizeVertical) && (nx == lrx || nx + cw == lx)) { if ((sOWO ? (ry > lry) : true) && (qAbs(lry - ry) < snap) && (qAbs(lry - ry) < deltaY)) { deltaY = qAbs(lry - ry); ny = lry - ch; } if ((sOWO ? (cy < ly) : true) && (qAbs(cy - ly) < snap) && (qAbs(cy - ly) < deltaY)) { deltaY = qAbs(cy - ly); ny = ly; } } if (!(guideMaximized & MaximizeHorizontal) && (ny == lry || ny + ch == ly)) { if ((sOWO ? (rx > lrx) : true) && (qAbs(lrx - rx) < snap) && (qAbs(lrx - rx) < deltaX)) { deltaX = qAbs(lrx - rx); nx = lrx - cw; } if ((sOWO ? (cx < lx) : true) && (qAbs(cx - lx) < snap) && (qAbs(cx - lx) < deltaX)) { deltaX = qAbs(cx - lx); nx = lx; } } } } // center snap snap = options->centerSnapZone() * snapAdjust; //snap trigger if (snap) { int diffX = qAbs((xmin + xmax) / 2 - (cx + cw / 2)); int diffY = qAbs((ymin + ymax) / 2 - (cy + ch / 2)); if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) { // Snap to center of screen nx = (xmin + xmax) / 2 - cw / 2; ny = (ymin + ymax) / 2 - ch / 2; } else if (options->borderSnapZone()) { // Enhance border snap if ((nx == xmin || nx == xmax - cw) && diffY < snap && diffY < deltaY) { // Snap to vertical center on screen edge ny = (ymin + ymax) / 2 - ch / 2; } else if (((unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch) && diffX < snap && diffX < deltaX) { // Snap to horizontal center on screen edge nx = (xmin + xmax) / 2 - cw / 2; } } } pos = QPoint(nx, ny); } return pos; } QRect Workspace::adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode) { //adapted from adjustClientPosition on 29May2004 //this function is called when resizing a window and will modify //the new dimensions to snap to other windows/borders if appropriate if (options->windowSnapZone() || options->borderSnapZone()) { // || options->centerSnapZone ) const bool sOWO = options->isSnapOnlyWhenOverlapping(); const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right(); //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom(); const int cx(moveResizeGeom.left()); const int cy(moveResizeGeom.top()); const int rx(moveResizeGeom.right()); const int ry(moveResizeGeom.bottom()); int newcx(cx), newcy(cy); //buffers int newrx(rx), newry(ry); int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone(); //snap trigger if (snap) { deltaX = int(snap); deltaY = int(snap); #define SNAP_BORDER_TOP \ if ((sOWO?(newcyymax):true) && (qAbs(ymax-newry)xmax):true) && (qAbs(xmax-newrx)windowSnapZone(); if (snap) { deltaX = int(snap); deltaY = int(snap); for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) { if ((*l)->isOnDesktop(VirtualDesktopManager::self()->current()) && !(*l)->isMinimized() && (*l) != c) { lx = (*l)->x() - 1; ly = (*l)->y() - 1; lrx = (*l)->x() + (*l)->width(); lry = (*l)->y() + (*l)->height(); #define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \ (( newry >= ly ) && ( newry <= lry )) || \ (( newcy <= ly ) && ( newry >= lry )) ) #define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \ (( rx >= lx ) && ( rx <= lrx )) || \ (( cx <= lx ) && ( rx >= lrx )) ) #define SNAP_WINDOW_TOP if ( (sOWO?(newcyly):true) \ && WITHIN_WIDTH \ && (qAbs( ly - newry ) < deltaY) ) { \ deltaY = qAbs( ly - newry ); \ newry=ly; \ } #define SNAP_WINDOW_LEFT if ( (sOWO?(newcxlx):true) \ && WITHIN_HEIGHT \ && (qAbs( lx - newrx ) < deltaX)) \ { \ deltaX = qAbs( lx - newrx ); \ newrx=lx; \ } #define SNAP_WINDOW_C_TOP if ( (sOWO?(newcylry):true) \ && (newcx == lrx || newrx == lx) \ && qAbs(lry-newry) < deltaY ) { \ deltaY = qAbs( lry - newry - 1 ); \ newry = lry - 1; \ } #define SNAP_WINDOW_C_LEFT if ( (sOWO?(newcxlrx):true) \ && (newcy == lry || newry == ly) \ && qAbs(lrx-newrx) < deltaX ) { \ deltaX = qAbs( lrx - newrx - 1 ); \ newrx = lrx - 1; \ } switch(mode) { case Client::PositionBottomRight: SNAP_WINDOW_BOTTOM SNAP_WINDOW_RIGHT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_RIGHT break; case Client::PositionRight: SNAP_WINDOW_RIGHT SNAP_WINDOW_C_RIGHT break; case Client::PositionBottom: SNAP_WINDOW_BOTTOM SNAP_WINDOW_C_BOTTOM break; case Client::PositionTopLeft: SNAP_WINDOW_TOP SNAP_WINDOW_LEFT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_LEFT break; case Client::PositionLeft: SNAP_WINDOW_LEFT SNAP_WINDOW_C_LEFT break; case Client::PositionTop: SNAP_WINDOW_TOP SNAP_WINDOW_C_TOP break; case Client::PositionTopRight: SNAP_WINDOW_TOP SNAP_WINDOW_RIGHT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_RIGHT break; case Client::PositionBottomLeft: SNAP_WINDOW_BOTTOM SNAP_WINDOW_LEFT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_LEFT break; default: abort(); break; } } } } // center snap //snap = options->centerSnapZone; //if (snap) // { // // Don't resize snap to center as it interferes too much // // There are two ways of implementing this if wanted: // // 1) Snap only to the same points that the move snap does, and // // 2) Snap to the horizontal and vertical center lines of the screen // } moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry)); } return moveResizeGeom; } /*! Marks the client as being moved around by the user. */ void Workspace::setClientIsMoving(AbstractClient *c) { Q_ASSERT(!c || !movingClient); // Catch attempts to move a second // window while still moving the first one. movingClient = c; if (movingClient) ++block_focus; else --block_focus; } // When kwin crashes, windows will not be gravitated back to their original position // and will remain offset by the size of the decoration. So when restarting, fix this // (the property with the size of the frame remains on the window after the crash). void Workspace::fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geometry) { NETWinInfo i(connection(), w, rootWindow(), NET::WMFrameExtents, 0); NETStrut frame = i.frameExtents(); if (frame.left != 0 || frame.top != 0) { // left and top needed due to narrowing conversations restrictions in C++11 const uint32_t left = frame.left; const uint32_t top = frame.top; const uint32_t values[] = { geometry->x - left, geometry->y - top }; xcb_configure_window(connection(), w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); } } //******************************************** // Client //******************************************** /*! Returns \a area with the client's strut taken into account. Used from Workspace in updateClientArea. */ // TODO move to Workspace? QRect Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) const { QRect r = area; NETExtendedStrut str = strut(); QRect stareaL = QRect( 0, str . left_start, str . left_width, str . left_end - str . left_start + 1); QRect stareaR = QRect( desktopArea . right() - str . right_width + 1, str . right_start, str . right_width, str . right_end - str . right_start + 1); QRect stareaT = QRect( str . top_start, 0, str . top_end - str . top_start + 1, str . top_width); QRect stareaB = QRect( str . bottom_start, desktopArea . bottom() - str . bottom_width + 1, str . bottom_end - str . bottom_start + 1, str . bottom_width); QRect screenarea = workspace()->clientArea(ScreenArea, this); // HACK: workarea handling is not xinerama aware, so if this strut // reserves place at a xinerama edge that's inside the virtual screen, // ignore the strut for workspace setting. if (area == QRect(QPoint(0, 0), screens()->displaySize())) { if (stareaL.left() < screenarea.left()) stareaL = QRect(); if (stareaR.right() > screenarea.right()) stareaR = QRect(); if (stareaT.top() < screenarea.top()) stareaT = QRect(); if (stareaB.bottom() < screenarea.bottom()) stareaB = QRect(); } // Handle struts at xinerama edges that are inside the virtual screen. // They're given in virtual screen coordinates, make them affect only // their xinerama screen. stareaL.setLeft(qMax(stareaL.left(), screenarea.left())); stareaR.setRight(qMin(stareaR.right(), screenarea.right())); stareaT.setTop(qMax(stareaT.top(), screenarea.top())); stareaB.setBottom(qMin(stareaB.bottom(), screenarea.bottom())); if (stareaL . intersects(area)) { // qDebug() << "Moving left of: " << r << " to " << stareaL.right() + 1; r . setLeft(stareaL . right() + 1); } if (stareaR . intersects(area)) { // qDebug() << "Moving right of: " << r << " to " << stareaR.left() - 1; r . setRight(stareaR . left() - 1); } if (stareaT . intersects(area)) { // qDebug() << "Moving top of: " << r << " to " << stareaT.bottom() + 1; r . setTop(stareaT . bottom() + 1); } if (stareaB . intersects(area)) { // qDebug() << "Moving bottom of: " << r << " to " << stareaB.top() - 1; r . setBottom(stareaB . top() - 1); } return r; } NETExtendedStrut Client::strut() const { NETExtendedStrut ext = info->extendedStrut(); NETStrut str = info->strut(); const QSize displaySize = screens()->displaySize(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) { // build extended from simple if (str.left != 0) { ext.left_width = str.left; ext.left_start = 0; ext.left_end = displaySize.height(); } if (str.right != 0) { ext.right_width = str.right; ext.right_start = 0; ext.right_end = displaySize.height(); } if (str.top != 0) { ext.top_width = str.top; ext.top_start = 0; ext.top_end = displaySize.width(); } if (str.bottom != 0) { ext.bottom_width = str.bottom; ext.bottom_start = 0; ext.bottom_end = displaySize.width(); } } return ext; } StrutRect Client::strutRect(StrutArea area) const { assert(area != StrutAreaAll); // Not valid const QSize displaySize = screens()->displaySize(); NETExtendedStrut strutArea = strut(); switch(area) { case StrutAreaTop: if (strutArea.top_width != 0) return StrutRect(QRect( strutArea.top_start, 0, strutArea.top_end - strutArea.top_start, strutArea.top_width ), StrutAreaTop); break; case StrutAreaRight: if (strutArea.right_width != 0) return StrutRect(QRect( displaySize.width() - strutArea.right_width, strutArea.right_start, strutArea.right_width, strutArea.right_end - strutArea.right_start ), StrutAreaRight); break; case StrutAreaBottom: if (strutArea.bottom_width != 0) return StrutRect(QRect( strutArea.bottom_start, displaySize.height() - strutArea.bottom_width, strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width ), StrutAreaBottom); break; case StrutAreaLeft: if (strutArea.left_width != 0) return StrutRect(QRect( 0, strutArea.left_start, strutArea.left_width, strutArea.left_end - strutArea.left_start ), StrutAreaLeft); break; default: abort(); // Not valid } return StrutRect(); // Null rect } StrutRects Client::strutRects() const { StrutRects region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); return region; } bool Client::hasStrut() const { NETExtendedStrut ext = strut(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0) return false; return true; } bool Client::hasOffscreenXineramaStrut() const { // Get strut as a QRegion QRegion region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); // Remove all visible areas so that only the invisible remain for (int i = 0; i < screens()->count(); i ++) region -= screens()->geometry(i); // If there's anything left then we have an offscreen strut return !region.isEmpty(); } void AbstractClient::checkWorkspacePosition(QRect oldGeometry, int oldDesktop, QRect oldClientGeometry) { enum { Left = 0, Top, Right, Bottom }; const int border[4] = { borderLeft(), borderTop(), borderRight(), borderBottom() }; if( !oldGeometry.isValid()) oldGeometry = geometry(); if( oldDesktop == -2 ) oldDesktop = desktop(); if (!oldClientGeometry.isValid()) oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]); if (isDesktop()) return; if (isFullScreen()) { QRect area = workspace()->clientArea(FullScreenArea, this); if (geometry() != area) setGeometry(area); return; } if (isDock()) return; if (maximizeMode() != MaximizeRestore) { // TODO update geom_restore? changeMaximize(false, false, true); // adjust size const QRect screenArea = workspace()->clientArea(ScreenArea, this); QRect geom = geometry(); checkOffscreenPosition(&geom, screenArea); setGeometry(geom); return; } if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { setGeometry(electricBorderMaximizeGeometry(geometry().center(), desktop())); return; } // this can be true only if this window was mapped before KWin // was started - in such case, don't adjust position to workarea, // because the window already had its position, and if a window // with a strut altering the workarea would be managed in initialization // after this one, this window would be moved if (!workspace() || workspace()->initializing()) return; // If the window was touching an edge before but not now move it so it is again. // Old and new maximums have different starting values so windows on the screen // edge will move when a new strut is placed on the edge. QRect oldScreenArea; QRect oldGeomTall; QRect oldGeomWide; const auto displaySize = screens()->displaySize(); if( workspace()->inUpdateClientArea()) { // we need to find the screen area as it was before the change oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight()); oldGeomTall = QRect(oldGeometry.x(), 0, oldGeometry.width(), workspace()->oldDisplayHeight()); // Full screen height oldGeomWide = QRect(0, oldGeometry.y(), workspace()->oldDisplayWidth(), oldGeometry.height()); // Full screen width int distance = INT_MAX; foreach(const QRect &r, workspace()->previousScreenSizes()) { int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength(); if( d < distance ) { distance = d; oldScreenArea = r; } } } else { oldScreenArea = workspace()->clientArea(ScreenArea, oldGeometry.center(), oldDesktop); oldGeomTall = QRect(oldGeometry.x(), 0, oldGeometry.width(), displaySize.height()); // Full screen height oldGeomWide = QRect(0, oldGeometry.y(), displaySize.width(), oldGeometry.height()); // Full screen width } int oldTopMax = oldScreenArea.y(); int oldRightMax = oldScreenArea.x() + oldScreenArea.width(); int oldBottomMax = oldScreenArea.y() + oldScreenArea.height(); int oldLeftMax = oldScreenArea.x(); const QRect screenArea = workspace()->clientArea(ScreenArea, geometryRestore().center(), desktop()); int topMax = screenArea.y(); int rightMax = screenArea.x() + screenArea.width(); int bottomMax = screenArea.y() + screenArea.height(); int leftMax = screenArea.x(); QRect newGeom = geometryRestore(); // geometry(); QRect newClientGeom = newGeom.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]); const QRect newGeomTall = QRect(newGeom.x(), 0, newGeom.width(), displaySize.height()); // Full screen height const QRect newGeomWide = QRect(0, newGeom.y(), displaySize.width(), newGeom.height()); // Full screen width // Get the max strut point for each side where the window is (E.g. Highest point for // the bottom struts bounded by the window's left and right sides). // These 4 compute old bounds ... auto moveAreaFunc = workspace()->inUpdateClientArea() ? &Workspace::previousRestrictedMoveArea : //... the restricted areas changed &Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); } foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldRightMax = qMin(oldRightMax, rect.x()); } foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldBottomMax = qMin(oldBottomMax, rect.y()); } foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); } // These 4 compute new bounds foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaTop).rects()) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) topMax = qMax(topMax, rect.y() + rect.height()); } foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaRight).rects()) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) rightMax = qMin(rightMax, rect.x()); } foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaBottom).rects()) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) bottomMax = qMin(bottomMax, rect.y()); } foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaLeft).rects()) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) leftMax = qMax(leftMax, rect.x() + rect.width()); } // Check if the sides were inside or touching but are no longer bool keep[4] = {false, false, false, false}; bool save[4] = {false, false, false, false}; int padding[4] = {0, 0, 0, 0}; if (oldGeometry.x() >= oldLeftMax) save[Left] = newGeom.x() < leftMax; if (oldGeometry.x() == oldLeftMax) keep[Left] = newGeom.x() != leftMax; else if (oldClientGeometry.x() == oldLeftMax && newClientGeom.x() != leftMax) { padding[0] = border[Left]; keep[Left] = true; } if (oldGeometry.y() >= oldTopMax) save[Top] = newGeom.y() < topMax; if (oldGeometry.y() == oldTopMax) keep[Top] = newGeom.y() != topMax; else if (oldClientGeometry.y() == oldTopMax && newClientGeom.y() != topMax) { padding[1] = border[Left]; keep[Top] = true; } if (oldGeometry.right() <= oldRightMax - 1) save[Right] = newGeom.right() > rightMax - 1; if (oldGeometry.right() == oldRightMax - 1) keep[Right] = newGeom.right() != rightMax - 1; else if (oldClientGeometry.right() == oldRightMax - 1 && newClientGeom.right() != rightMax - 1) { padding[2] = border[Right]; keep[Right] = true; } if (oldGeometry.bottom() <= oldBottomMax - 1) save[Bottom] = newGeom.bottom() > bottomMax - 1; if (oldGeometry.bottom() == oldBottomMax - 1) keep[Bottom] = newGeom.bottom() != bottomMax - 1; else if (oldClientGeometry.bottom() == oldBottomMax - 1 && newClientGeom.bottom() != bottomMax - 1) { padding[3] = border[Bottom]; keep[Bottom] = true; } // if randomly touches opposing edges, do not favor either if (keep[Left] && keep[Right]) { keep[Left] = keep[Right] = false; padding[0] = padding[2] = 0; } if (keep[Top] && keep[Bottom]) { keep[Top] = keep[Bottom] = false; padding[1] = padding[3] = 0; } if (save[Left] || keep[Left]) newGeom.moveLeft(qMax(leftMax, screenArea.x()) - padding[0]); if (padding[0] && screens()->intersecting(newGeom) > 1) newGeom.moveLeft(newGeom.left() + padding[0]); if (save[Top] || keep[Top]) newGeom.moveTop(qMax(topMax, screenArea.y()) - padding[1]); if (padding[1] && screens()->intersecting(newGeom) > 1) newGeom.moveTop(newGeom.top() + padding[1]); if (save[Right] || keep[Right]) newGeom.moveRight(qMin(rightMax - 1, screenArea.right()) + padding[2]); if (padding[2] && screens()->intersecting(newGeom) > 1) newGeom.moveRight(newGeom.right() - padding[2]); if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) newGeom.setLeft(qMax(leftMax, screenArea.x())); else if (oldClientGeometry.x() >= oldLeftMax && newGeom.x() + border[Left] < leftMax) { newGeom.setLeft(qMax(leftMax, screenArea.x()) - border[Left]); if (screens()->intersecting(newGeom) > 1) newGeom.setLeft(newGeom.left() + border[Left]); } if (save[Bottom] || keep[Bottom]) newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom()) + padding[3]); if (padding[3] && screens()->intersecting(newGeom) > 1) newGeom.moveBottom(newGeom.bottom() - padding[3]); if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) newGeom.setTop(qMax(topMax, screenArea.y())); else if (oldClientGeometry.y() >= oldTopMax && newGeom.y() + border[Top] < topMax) { newGeom.setTop(qMax(topMax, screenArea.y()) - border[Top]); if (screens()->intersecting(newGeom) > 1) newGeom.setTop(newGeom.top() + border[Top]); } checkOffscreenPosition(&newGeom, screenArea); // Obey size hints. TODO: We really should make sure it stays in the right place if (!isShade()) newGeom.setSize(adjustedSize(newGeom.size())); if (newGeom != geometry()) setGeometry(newGeom); } void AbstractClient::checkOffscreenPosition(QRect* geom, const QRect& screenArea) { if (geom->left() > screenArea.right()) { geom->moveLeft(screenArea.right() - screenArea.width()/4); } else if (geom->right() < screenArea.left()) { geom->moveRight(screenArea.left() + screenArea.width()/4); } if (geom->top() > screenArea.bottom()) { geom->moveTop(screenArea.bottom() - screenArea.height()/4); } else if (geom->bottom() < screenArea.top()) { geom->moveBottom(screenArea.top() + screenArea.width()/4); } } /*! Adjust the frame size \a frame according to he window's size hints. */ QSize AbstractClient::adjustedSize(const QSize& frame, Sizemode mode) const { // first, get the window size for the given frame size s QSize wsize(frame.width() - (borderLeft() + borderRight()), frame.height() - (borderTop() + borderBottom())); if (wsize.isEmpty()) wsize = QSize(qMax(wsize.width(), 1), qMax(wsize.height(), 1)); return sizeForClientSize(wsize, mode, false); } // this helper returns proper size even if the window is shaded // see also the comment in Client::setGeometry() QSize AbstractClient::adjustedSize() const { return sizeForClientSize(clientSize()); } /*! Calculate the appropriate frame size for the given client size \a wsize. \a wsize is adapted according to the window's size hints (minimum, maximum and incremental size changes). */ QSize Client::sizeForClientSize(const QSize& wsize, Sizemode mode, bool noframe) const { int w = wsize.width(); int h = wsize.height(); if (w < 1 || h < 1) { qCWarning(KWIN_CORE) << "sizeForClientSize() with empty size!" ; } if (w < 1) w = 1; if (h < 1) h = 1; // basesize, minsize, maxsize, paspect and resizeinc have all values defined, // even if they're not set in flags - see getWmNormalHints() QSize min_size = tabGroup() ? tabGroup()->minSize() : minSize(); QSize max_size = tabGroup() ? tabGroup()->maxSize() : maxSize(); if (isDecorated()) { QSize decominsize(0, 0); QSize border_size(borderLeft() + borderRight(), borderTop() + borderBottom()); if (border_size.width() > decominsize.width()) // just in case decominsize.setWidth(border_size.width()); if (border_size.height() > decominsize.height()) decominsize.setHeight(border_size.height()); if (decominsize.width() > min_size.width()) min_size.setWidth(decominsize.width()); if (decominsize.height() > min_size.height()) min_size.setHeight(decominsize.height()); } w = qMin(max_size.width(), w); h = qMin(max_size.height(), h); w = qMax(min_size.width(), w); h = qMax(min_size.height(), h); int w1 = w; int h1 = h; int width_inc = m_geometryHints.resizeIncrements().width(); int height_inc = m_geometryHints.resizeIncrements().height(); int basew_inc = m_geometryHints.baseSize().width(); int baseh_inc = m_geometryHints.baseSize().height(); if (!m_geometryHints.hasBaseSize()) { basew_inc = m_geometryHints.minSize().width(); baseh_inc = m_geometryHints.minSize().height(); } w = int((w - basew_inc) / width_inc) * width_inc + basew_inc; h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc; // code for aspect ratios based on code from FVWM /* * The math looks like this: * * minAspectX dwidth maxAspectX * ---------- <= ------- <= ---------- * minAspectY dheight maxAspectY * * If that is multiplied out, then the width and height are * invalid in the following situations: * * minAspectX * dheight > minAspectY * dwidth * maxAspectX * dheight < maxAspectY * dwidth * */ if (m_geometryHints.hasAspect()) { double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise double max_aspect_w = m_geometryHints.maxAspect().width(); double max_aspect_h = m_geometryHints.maxAspect().height(); // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, // and I have no idea how it works, let's hope nobody relies on that. const QSize baseSize = m_geometryHints.baseSize(); w -= baseSize.width(); h -= baseSize.height(); int max_width = max_size.width() - baseSize.width(); int min_width = min_size.width() - baseSize.width(); int max_height = max_size.height() - baseSize.height(); int min_height = min_size.height() - baseSize.height(); #define ASPECT_CHECK_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } #define ASPECT_CHECK_SHRINK_H_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \ if ( h - delta >= min_height ) \ h -= delta; \ else \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } \ } #define ASPECT_CHECK_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } #define ASPECT_CHECK_SHRINK_W_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \ if ( w - delta >= min_width ) \ w -= delta; \ else \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } \ } switch(mode) { case SizemodeAny: #if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width, // so that changing aspect ratio to a different value and back keeps the same size (#87298) { ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_H ASPECT_CHECK_GROW_W break; } #endif case SizemodeFixedW: { // the checks are order so that attempts to modify height are first ASPECT_CHECK_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W break; } case SizemodeFixedH: { ASPECT_CHECK_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_GROW_H break; } case SizemodeMax: { // first checks that try to shrink ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W ASPECT_CHECK_GROW_H break; } } #undef ASPECT_CHECK_SHRINK_H_GROW_W #undef ASPECT_CHECK_SHRINK_W_GROW_H #undef ASPECT_CHECK_GROW_W #undef ASPECT_CHECK_GROW_H w += baseSize.width(); h += baseSize.height(); } if (!rules()->checkStrictGeometry(!isFullScreen())) { // disobey increments and aspect by explicit rule w = w1; h = h1; } if (!noframe) { w += borderLeft() + borderRight(); h += borderTop() + borderBottom(); } return rules()->checkSize(QSize(w, h)); } /*! Gets the client's normal WM hints and reconfigures itself respectively. */ void Client::getWmNormalHints() { const bool hadFixedAspect = m_geometryHints.hasAspect(); // roundtrip to X server m_geometryHints.fetch(); m_geometryHints.read(); if (!hadFixedAspect && m_geometryHints.hasAspect()) { // align to eventual new contraints maximize(max_mode); } // Update min/max size of this group if (tabGroup()) tabGroup()->updateMinMaxSize(); if (isManaged()) { // update to match restrictions QSize new_size = adjustedSize(); if (new_size != size() && !isFullScreen()) { QRect origClientGeometry(pos() + clientPos(), clientSize()); resizeWithChecks(new_size); if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(origClientGeometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(origClientGeometry)) keepInArea(area); } } } updateAllowedActions(); // affects isResizeable() } QSize Client::minSize() const { return rules()->checkMinSize(m_geometryHints.minSize()); } QSize Client::maxSize() const { return rules()->checkMaxSize(m_geometryHints.maxSize()); } QSize Client::basicUnit() const { return m_geometryHints.resizeIncrements(); } /*! Auxiliary function to inform the client about the current window configuration. */ void Client::sendSyntheticConfigureNotify() { xcb_configure_notify_event_t c; memset(&c, 0, sizeof(c)); c.response_type = XCB_CONFIGURE_NOTIFY; c.event = window(); c.window = window(); c.x = x() + clientPos().x(); c.y = y() + clientPos().y(); c.width = clientSize().width(); c.height = clientSize().height(); c.border_width = 0; c.above_sibling = XCB_WINDOW_NONE; c.override_redirect = 0; xcb_send_event(connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(&c)); xcb_flush(connection()); } const QPoint Client::calculateGravitation(bool invert, int gravity) const { int dx, dy; dx = dy = 0; if (gravity == 0) // default (nonsense) value for the argument gravity = m_geometryHints.windowGravity(); // dx, dy specify how the client window moves to make space for the frame switch(gravity) { case NorthWestGravity: // move down right default: dx = borderLeft(); dy = borderTop(); break; case NorthGravity: // move right dx = 0; dy = borderTop(); break; case NorthEastGravity: // move down left dx = -borderRight(); dy = borderTop(); break; case WestGravity: // move right dx = borderLeft(); dy = 0; break; case CenterGravity: break; // will be handled specially case StaticGravity: // don't move dx = 0; dy = 0; break; case EastGravity: // move left dx = -borderRight(); dy = 0; break; case SouthWestGravity: // move up right dx = borderLeft() ; dy = -borderBottom(); break; case SouthGravity: // move up dx = 0; dy = -borderBottom(); break; case SouthEastGravity: // move up left dx = -borderRight(); dy = -borderBottom(); break; } if (gravity != CenterGravity) { // translate from client movement to frame movement dx -= borderLeft(); dy -= borderTop(); } else { // center of the frame will be at the same position client center without frame would be dx = - (borderLeft() + borderRight()) / 2; dy = - (borderTop() + borderBottom()) / 2; } if (!invert) return QPoint(x() + dx, y() + dy); else return QPoint(x() - dx, y() - dy); } void Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool) { // "maximized" is a user setting -> we do not allow the client to resize itself // away from this & against the users explicit wish qCDebug(KWIN_CORE) << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)) << bool(maximizeMode() & MaximizeVertical) << bool(maximizeMode() & MaximizeHorizontal); // we want to (partially) ignore the request when the window is somehow maximized or quicktiled bool ignore = !app_noborder && (quickTileMode() != QuickTileMode(QuickTileFlag::None) || maximizeMode() != MaximizeRestore); // however, the user shall be able to force obedience despite and also disobedience in general ignore = rules()->checkIgnoreGeometry(ignore); if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it. updateQuickTileMode(QuickTileFlag::None); max_mode = MaximizeRestore; emit quickTileModeChanged(); } else if (!app_noborder && quickTileMode() == QuickTileMode(QuickTileFlag::None) && (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) { // ignoring can be, because either we do, or the user does explicitly not want it. // for partially maximized windows we want to allow configures in the other dimension. // so we've to ask the user again - to know whether we just ignored for the partial maximization. // the problem here is, that the user can explicitly permit configure requests - even for maximized windows! // we cannot distinguish that from passing "false" for partially maximized windows. ignore = rules()->checkIgnoreGeometry(false); if (!ignore) { // the user is not interested, so we fix up dimensions if (maximizeMode() == MaximizeVertical) value_mask &= ~(CWY|CWHeight); if (maximizeMode() == MaximizeHorizontal) value_mask &= ~(CWX|CWWidth); if (!(value_mask & (CWX|CWWidth|CWY|CWHeight))) { ignore = true; // the modification turned the request void } } } if (ignore) { qCDebug(KWIN_CORE) << "DENIED"; return; // nothing to (left) to do for use - bugs #158974, #252314, #321491 } qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)); if (gravity == 0) // default (nonsense) value for the argument gravity = m_geometryHints.windowGravity(); if (value_mask & (CWX | CWY)) { QPoint new_pos = calculateGravitation(true, gravity); // undo gravitation if (value_mask & CWX) new_pos.setX(rx); if (value_mask & CWY) new_pos.setY(ry); // clever(?) workaround for applications like xv that want to set // the location to the current location but miscalculate the // frame size due to kwin being a double-reparenting window // manager if (new_pos.x() == x() + clientPos().x() && new_pos.y() == y() + clientPos().y() && gravity == NorthWestGravity && !from_tool) { new_pos.setX(x()); new_pos.setY(y()); } int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & CWWidth) nw = rw; if (value_mask & CWHeight) nh = rh; QSize ns = sizeForClientSize(QSize(nw, nh)); // enforces size if needed new_pos = rules()->checkPosition(new_pos); int newScreen = screens()->number(QRect(new_pos, ns).center()); if (newScreen != rules()->checkScreen(newScreen)) return; // not allowed by rule QRect origClientGeometry(pos() + clientPos(), clientSize()); GeometryUpdatesBlocker blocker(this); move(new_pos); plainResize(ns); setGeometry(QRect(calculateGravitation(false, gravity), size())); updateFullScreenHack(QRect(new_pos, QSize(nw, nh))); QRect area = workspace()->clientArea(WorkArea, this); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen() && area.contains(origClientGeometry)) keepInArea(area); // this is part of the kicker-xinerama-hack... it should be // safe to remove when kicker gets proper ExtendedStrut support; // see Workspace::updateClientArea() and // Client::adjustedClientArea() if (hasStrut()) workspace() -> updateClientArea(); } if (value_mask & (CWWidth | CWHeight) && !(value_mask & (CWX | CWY))) { // pure resize int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & CWWidth) nw = rw; if (value_mask & CWHeight) nh = rh; QSize ns = sizeForClientSize(QSize(nw, nh)); if (ns != size()) { // don't restore if some app sets its own size again QRect origClientGeometry(pos() + clientPos(), clientSize()); GeometryUpdatesBlocker blocker(this); resizeWithChecks(ns, xcb_gravity_t(gravity)); updateFullScreenHack(QRect(calculateGravitation(true, m_geometryHints.windowGravity()), QSize(nw, nh))); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(origClientGeometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(origClientGeometry)) keepInArea(area); } } } geom_restore = geometry(); // No need to send synthetic configure notify event here, either it's sent together // with geometry change, or there's no need to send it. // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. } void Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force) { assert(!shade_geometry_change); if (isShade()) { if (h == borderTop() + borderBottom()) { qCWarning(KWIN_CORE) << "Shaded geometry passed for size:" ; } } int newx = x(); int newy = y(); QRect area = workspace()->clientArea(WorkArea, this); // don't allow growing larger than workarea if (w > area.width()) w = area.width(); if (h > area.height()) h = area.height(); QSize tmp = adjustedSize(QSize(w, h)); // checks size constraints, including min/max size w = tmp.width(); h = tmp.height(); if (gravity == 0) { gravity = m_geometryHints.windowGravity(); } switch(gravity) { case NorthWestGravity: // top left corner doesn't move default: break; case NorthGravity: // middle of top border doesn't move newx = (newx + width() / 2) - (w / 2); break; case NorthEastGravity: // top right corner doesn't move newx = newx + width() - w; break; case WestGravity: // middle of left border doesn't move newy = (newy + height() / 2) - (h / 2); break; case CenterGravity: // middle point doesn't move newx = (newx + width() / 2) - (w / 2); newy = (newy + height() / 2) - (h / 2); break; case StaticGravity: // top left corner of _client_ window doesn't move // since decoration doesn't change, equal to NorthWestGravity break; case EastGravity: // // middle of right border doesn't move newx = newx + width() - w; newy = (newy + height() / 2) - (h / 2); break; case SouthWestGravity: // bottom left corner doesn't move newy = newy + height() - h; break; case SouthGravity: // middle of bottom border doesn't move newx = (newx + width() / 2) - (w / 2); newy = newy + height() - h; break; case SouthEastGravity: // bottom right corner doesn't move newx = newx + width() - w; newy = newy + height() - h; break; } setGeometry(newx, newy, w, h, force); } // _NET_MOVERESIZE_WINDOW void Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height) { int gravity = flags & 0xff; int value_mask = 0; if (flags & (1 << 8)) value_mask |= CWX; if (flags & (1 << 9)) value_mask |= CWY; if (flags & (1 << 10)) value_mask |= CWWidth; if (flags & (1 << 11)) value_mask |= CWHeight; configureRequest(value_mask, x, y, width, height, gravity, true); } /*! Returns whether the window is moveable or has a fixed position. */ bool Client::isMovable() const { if (!hasNETSupport() && !m_motif.move()) { return false; } if (isFullScreen()) return false; if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } /*! Returns whether the window is moveable across Xinerama screens */ bool Client::isMovableAcrossScreens() const { if (!hasNETSupport() && !m_motif.move()) { return false; } if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } /*! Returns whether the window is resizable or has a fixed size. */ bool Client::isResizable() const { if (!hasNETSupport() && !m_motif.resize()) { return false; } if (isFullScreen()) return false; if (isSpecialWindow() || isSplash() || isToolbar()) return false; if (rules()->checkSize(QSize()).isValid()) // forced size return false; const Position mode = moveResizePointerMode(); if ((mode == PositionTop || mode == PositionTopLeft || mode == PositionTopRight || mode == PositionLeft || mode == PositionBottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint) return false; QSize min = tabGroup() ? tabGroup()->minSize() : minSize(); QSize max = tabGroup() ? tabGroup()->maxSize() : maxSize(); return min.width() < max.width() || min.height() < max.height(); } /* Returns whether the window is maximizable or not */ bool Client::isMaximizable() const { if (!isResizable() || isToolbar()) // SELI isToolbar() ? return false; if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore) return true; return false; } /*! Reimplemented to inform the client about the new window position. */ void Client::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { // this code is also duplicated in Client::plainResize() // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, // simply because there are too many places dealing with geometry. Those places // ignore shaded state and use normal geometry, which they usually should get // from adjustedSize(). Such geometry comes here, and if the window is shaded, // the geometry is used only for client_size, since that one is not used when // shading. Then the frame geometry is adjusted for the shaded geometry. // This gets more complicated in the case the code does only something like // setGeometry( geometry()) - geometry() will return the shaded frame geometry. // Such code is wrong and should be changed to handle the case when the window is shaded, // for example using Client::clientSize() if (shade_geometry_change) ; // nothing else if (isShade()) { if (h == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); h = borderTop() + borderBottom(); } } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); } QRect g(x, y, w, h); if (!areGeometryUpdatesBlocked() && g != rules()->checkGeometry(g)) { qCDebug(KWIN_CORE) << "forced geometry fail:" << g << ":" << rules()->checkGeometry(g); } if (force == NormalGeometrySet && geom == g && pendingGeometryUpdate() == PendingGeometryNone) return; geom = g; if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } QSize oldClientSize = m_frame.geometry().size(); bool resized = (geometryBeforeUpdateBlocking().size() != geom.size() || pendingGeometryUpdate() == PendingGeometryForced); if (resized) { resizeDecoration(); m_frame.setGeometry(x, y, w, h); if (!isShade()) { QSize cs = clientSize(); m_wrapper.setGeometry(QRect(clientPos(), cs)); if (!isResize() || syncRequest.counter == XCB_NONE) m_client.setGeometry(0, 0, cs.width(), cs.height()); // SELI - won't this be too expensive? // THOMAS - yes, but gtk+ clients will not resize without ... sendSyntheticConfigureNotify(); } updateShape(); } else { if (isMoveResize()) { if (compositing()) // Defer the X update until we leave this mode needsXWindowMove = true; else m_frame.move(x, y); // sendSyntheticConfigureNotify() on finish shall be sufficient } else { m_frame.move(x, y); sendSyntheticConfigureNotify(); } // Unconditionally move the input window: it won't affect rendering m_decoInputExtent.move(QPoint(x, y) + inputPos()); } updateWindowRules(Rules::Position|Rules::Size); // keep track of old maximize mode // to detect changes screens()->setCurrent(this); workspace()->updateStackingOrder(); // need to regenerate decoration pixmaps when either // - size is changed // - maximize mode is changed to MaximizeRestore, when size unchanged // which can happen when untabbing maximized windows if (resized) { if (oldClientSize != QSize(w,h)) discardWindowPixmap(); } emit geometryShapeChanged(this, geometryBeforeUpdateBlocking()); addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Geometry); // TODO: this signal is emitted too often emit geometryChanged(); } void Client::plainResize(int w, int h, ForceGeometry_t force) { // this code is also duplicated in Client::setGeometry(), and it's also commented there if (shade_geometry_change) ; // nothing else if (isShade()) { if (h == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); h = borderTop() + borderBottom(); } } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); } QSize s(w, h); if (!areGeometryUpdatesBlocked() && s != rules()->checkSize(s)) { qCDebug(KWIN_CORE) << "forced size fail:" << s << ":" << rules()->checkSize(s); } // resuming geometry updates is handled only in setGeometry() assert(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); if (force == NormalGeometrySet && geom.size() == s) return; geom.setSize(s); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } QSize oldClientSize = m_frame.geometry().size(); resizeDecoration(); m_frame.resize(w, h); // resizeDecoration( s ); if (!isShade()) { QSize cs = clientSize(); m_wrapper.setGeometry(QRect(clientPos(), cs)); m_client.setGeometry(0, 0, cs.width(), cs.height()); } updateShape(); sendSyntheticConfigureNotify(); updateWindowRules(Rules::Position|Rules::Size); screens()->setCurrent(this); workspace()->updateStackingOrder(); if (oldClientSize != QSize(w,h)) discardWindowPixmap(); emit geometryShapeChanged(this, geometryBeforeUpdateBlocking()); addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Geometry); // TODO: this signal is emitted too often emit geometryChanged(); } /*! Reimplemented to inform the client about the new window position. */ void AbstractClient::move(int x, int y, ForceGeometry_t force) { // resuming geometry updates is handled only in setGeometry() assert(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); QPoint p(x, y); if (!areGeometryUpdatesBlocked() && p != rules()->checkPosition(p)) { qCDebug(KWIN_CORE) << "forced position fail:" << p << ":" << rules()->checkPosition(p); } if (force == NormalGeometrySet && geom.topLeft() == p) return; geom.moveTopLeft(p); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } doMove(x, y); updateWindowRules(Rules::Position); screens()->setCurrent(this); workspace()->updateStackingOrder(); // client itself is not damaged addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); // Update states of all other windows in this group updateTabGroupStates(TabGroup::Geometry); emit geometryChanged(); } void Client::doMove(int x, int y) { m_frame.move(x, y); sendSyntheticConfigureNotify(); } void AbstractClient::blockGeometryUpdates(bool block) { if (block) { if (m_blockGeometryUpdates == 0) m_pendingGeometryUpdate = PendingGeometryNone; ++m_blockGeometryUpdates; } else { if (--m_blockGeometryUpdates == 0) { if (m_pendingGeometryUpdate != PendingGeometryNone) { if (isShade()) setGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet); else setGeometry(geometry(), NormalGeometrySet); m_pendingGeometryUpdate = PendingGeometryNone; } } } } void AbstractClient::maximize(MaximizeMode m) { if (m == maximizeMode()) { return; } setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); } /*! Sets the maximization according to \a vertically and \a horizontally */ void AbstractClient::setMaximize(bool vertically, bool horizontally) { // changeMaximize() flips the state, so change from set->flip const MaximizeMode oldMode = maximizeMode(); changeMaximize( oldMode & MaximizeVertical ? !vertically : vertically, oldMode & MaximizeHorizontal ? !horizontally : horizontally, false); const MaximizeMode newMode = maximizeMode(); if (oldMode != newMode) { emit clientMaximizedStateChanged(this, newMode); emit clientMaximizedStateChanged(this, vertically, horizontally); } } // Update states of all other windows in this group class TabSynchronizer { public: TabSynchronizer(AbstractClient *client, TabGroup::States syncStates) : m_client(client) , m_states(syncStates) { if (client->tabGroup()) client->tabGroup()->blockStateUpdates(true); } ~TabSynchronizer() { syncNow(); } void syncNow() { if (m_client && m_client->tabGroup()) { m_client->tabGroup()->blockStateUpdates(false); m_client->tabGroup()->updateStates(dynamic_cast(m_client), m_states); } m_client = 0; } private: AbstractClient *m_client; TabGroup::States m_states; }; static bool changeMaximizeRecursion = false; void Client::changeMaximize(bool vertical, bool horizontal, bool adjust) { if (changeMaximizeRecursion) return; if (!isResizable() || isToolbar()) // SELI isToolbar() ? return; QRect clientArea; if (isElectricBorderMaximizing()) clientArea = workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()); else clientArea = workspace()->clientArea(MaximizeArea, this); MaximizeMode old_mode = max_mode; // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) max_mode = MaximizeMode(max_mode ^ MaximizeVertical); if (horizontal) max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal); } // if the client insist on a fix aspect ratio, we check whether the maximizing will get us // out of screen bounds and take that as a "full maximization with aspect check" then if (m_geometryHints.hasAspect() && // fixed aspect (max_mode == MaximizeVertical || max_mode == MaximizeHorizontal) && // ondimensional maximization rules()->checkStrictGeometry(true)) { // obey aspect const QSize minAspect = m_geometryHints.minAspect(); const QSize maxAspect = m_geometryHints.maxAspect(); if (max_mode == MaximizeVertical || (old_mode & MaximizeVertical)) { const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT if (fx*clientArea.height()/fy > clientArea.width()) // too big max_mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull; } else { // max_mode == MaximizeHorizontal const double fx = maxAspect.width(); const double fy = minAspect.height(); if (fy*clientArea.width()/fx > clientArea.height()) // too big max_mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull; } } max_mode = rules()->checkMaximize(max_mode); if (!adjust && max_mode == old_mode) return; GeometryUpdatesBlocker blocker(this); // QT synchronizing required because we eventually change from QT to Maximized TabSynchronizer syncer(this, TabGroup::Maximized|TabGroup::QuickTile); // maximing one way and unmaximizing the other way shouldn't happen, // so restore first and then maximize the other way if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal) || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) { changeMaximize(false, false, false); // restore } // save sizes for restoring, if maximalizing QSize sz; if (isShade()) sz = sizeForClientSize(clientSize()); else sz = size(); if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { if (!adjust && !(old_mode & MaximizeVertical)) { geom_restore.setTop(y()); geom_restore.setHeight(sz.height()); } if (!adjust && !(old_mode & MaximizeHorizontal)) { geom_restore.setLeft(x()); geom_restore.setWidth(sz.width()); } } // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(max_mode & MaximizeVertical); } if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal); } if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) { emit c->maximizedChanged(max_mode & MaximizeFull); } changeMaximizeRecursion = false; } if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry changeMaximizeRecursion = true; setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull)); changeMaximizeRecursion = false; } const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; // Conditional quick tiling exit points if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { if (old_mode == MaximizeFull && !clientArea.contains(geom_restore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) || (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry } } switch(max_mode) { case MaximizeVertical: { if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull if (geom_restore.width() == 0 || !clientArea.contains(geom_restore.center())) { // needs placement plainResize(adjustedSize(QSize(width() * 2 / 3, clientArea.height()), SizemodeFixedH), geom_mode); Placement::self()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(geom_restore.x(), clientArea.top()), adjustedSize(QSize(geom_restore.width(), clientArea.height()), SizemodeFixedH)), geom_mode); } } else { QRect r(x(), clientArea.top(), width(), clientArea.height()); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizemodeFixedH)); setGeometry(r, geom_mode); } info->setState(NET::MaxVert, NET::Max); break; } case MaximizeHorizontal: { if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull if (geom_restore.height() == 0 || !clientArea.contains(geom_restore.center())) { // needs placement plainResize(adjustedSize(QSize(clientArea.width(), height() * 2 / 3), SizemodeFixedW), geom_mode); Placement::self()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(clientArea.left(), geom_restore.y()), adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW)), geom_mode); } } else { QRect r(clientArea.left(), y(), clientArea.width(), height()); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizemodeFixedW)); setGeometry(r, geom_mode); } info->setState(NET::MaxHoriz, NET::Max); break; } case MaximizeRestore: { QRect restore = geometry(); // when only partially maximized, geom_restore may not have the other dimension remembered if (old_mode & MaximizeVertical) { restore.setTop(geom_restore.top()); restore.setBottom(geom_restore.bottom()); } if (old_mode & MaximizeHorizontal) { restore.setLeft(geom_restore.left()); restore.setRight(geom_restore.right()); } if (!restore.isValid()) { QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3); if (geom_restore.width() > 0) s.setWidth(geom_restore.width()); if (geom_restore.height() > 0) s.setHeight(geom_restore.height()); plainResize(adjustedSize(s)); Placement::self()->placeSmart(this, clientArea); restore = geometry(); if (geom_restore.width() > 0) restore.moveLeft(geom_restore.x()); if (geom_restore.height() > 0) restore.moveTop(geom_restore.y()); geom_restore = restore; // relevant for mouse pos calculation, bug #298646 } if (m_geometryHints.hasAspect()) { restore.setSize(adjustedSize(restore.size(), SizemodeAny)); } setGeometry(restore, geom_mode); if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen Placement::self()->place(this, clientArea); info->setState(0, NET::Max); updateQuickTileMode(QuickTileFlag::None); break; } case MaximizeFull: { QRect r(clientArea); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizemodeMax)); if (r.size() != clientArea.size()) { // to avoid off-by-one errors... if (isElectricBorderMaximizing() && r.width() < clientArea.width()) { r.moveLeft(qMax(clientArea.left(), Cursor::pos().x() - r.width()/2)); r.moveRight(qMin(clientArea.right(), r.right())); } else { r.moveCenter(clientArea.center()); const bool closeHeight = r.height() > 97*clientArea.height()/100; const bool closeWidth = r.width() > 97*clientArea.width() /100; const bool overHeight = r.height() > clientArea.height(); const bool overWidth = r.width() > clientArea.width(); if (closeWidth || closeHeight) { Position titlePos = titlebarPosition(); const QRect screenArea = workspace()->clientArea(ScreenArea, clientArea.center(), desktop()); if (closeHeight) { bool tryBottom = titlePos == PositionBottom; if ((overHeight && titlePos == PositionTop) || screenArea.top() == clientArea.top()) r.setTop(clientArea.top()); else tryBottom = true; if (tryBottom && (overHeight || screenArea.bottom() == clientArea.bottom())) r.setBottom(clientArea.bottom()); } if (closeWidth) { bool tryLeft = titlePos == PositionLeft; if ((overWidth && titlePos == PositionRight) || screenArea.right() == clientArea.right()) r.setRight(clientArea.right()); else tryLeft = true; if (tryLeft && (overWidth || screenArea.left() == clientArea.left())) r.setLeft(clientArea.left()); } } } r.moveTopLeft(rules()->checkPosition(r.topLeft())); } setGeometry(r, geom_mode); if (options->electricBorderMaximize() && r.top() == clientArea.top()) updateQuickTileMode(QuickTileFlag::Maximize); else updateQuickTileMode(QuickTileFlag::None); info->setState(NET::Max, NET::Max); break; } default: break; } syncer.syncNow(); // important because of window rule updates! updateAllowedActions(); updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size); emit quickTileModeChanged(); } -bool Client::isFullScreenable() const +bool AbstractClient::isFullScreenable() const { return isFullScreenable(false); } -bool Client::isFullScreenable(bool fullscreen_hack) const +bool AbstractClient::isFullScreenable(bool fullscreen_hack) const { if (!rules()->checkFullScreen(true)) return false; if (fullscreen_hack) return isNormalWindow(); if (rules()->checkStrictGeometry(true)) { // allow rule to ignore geometry constraints QRect fsarea = workspace()->clientArea(FullScreenArea, this); if (sizeForClientSize(fsarea.size(), SizemodeAny, true) != fsarea.size()) return false; // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements } // don't check size constrains - some apps request fullscreen despite requesting fixed size return !isSpecialWindow(); // also better disallow only weird types to go fullscreen } bool Client::userCanSetFullScreen() const { if (fullscreen_mode == FullScreenHack) return false; if (!isFullScreenable(false)) return false; return isNormalWindow() || isDialog(); } void Client::setFullScreen(bool set, bool user) { if (!isFullScreen() && !set) return; if (fullscreen_mode == FullScreenHack) return; if (user && !userCanSetFullScreen()) return; set = rules()->checkFullScreen(set && !isSpecialWindow()); setShade(ShadeNone); bool was_fs = isFullScreen(); if (was_fs) workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event else geom_fs_restore = geometry(); fullscreen_mode = set ? FullScreenNormal : FullScreenNone; if (was_fs == isFullScreen()) return; if (set) { untab(); workspace()->raiseClient(this); } StackingUpdatesBlocker blocker1(workspace()); GeometryUpdatesBlocker blocker2(this); workspace()->updateClientLayer(this); // active fullscreens get different layer info->setState(isFullScreen() ? NET::FullScreen : NET::States(0), NET::FullScreen); updateDecoration(false, false); if (isFullScreen()) { if (info->fullscreenMonitors().isSet()) setGeometry(fullscreenMonitorsArea(info->fullscreenMonitors())); else setGeometry(workspace()->clientArea(FullScreenArea, this)); } else { if (!geom_fs_restore.isNull()) { int currentScreen = screen(); setGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size()))); if( currentScreen != screen()) workspace()->sendClientToScreen( this, currentScreen ); // TODO isShaded() ? } else { // does this ever happen? setGeometry(workspace()->clientArea(MaximizeArea, this)); } } updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); if (was_fs != isFullScreen()) { emit clientFullScreenSet(this, set, user); emit fullScreenChanged(); } } void Client::updateFullscreenMonitors(NETFullscreenMonitors topology) { int nscreens = screens()->count(); // qDebug() << "incoming request with top: " << topology.top << " bottom: " << topology.bottom // << " left: " << topology.left << " right: " << topology.right // << ", we have: " << nscreens << " screens."; if (topology.top >= nscreens || topology.bottom >= nscreens || topology.left >= nscreens || topology.right >= nscreens) { qCWarning(KWIN_CORE) << "fullscreenMonitors update failed. request higher than number of screens."; return; } info->setFullscreenMonitors(topology); if (isFullScreen()) setGeometry(fullscreenMonitorsArea(topology)); } /*! Calculates the bounding rectangle defined by the 4 monitor indices indicating the top, bottom, left, and right edges of the window when the fullscreen state is enabled. */ QRect Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const { QRect top, bottom, left, right, total; top = screens()->geometry(requestedTopology.top); bottom = screens()->geometry(requestedTopology.bottom); left = screens()->geometry(requestedTopology.left); right = screens()->geometry(requestedTopology.right); total = top.united(bottom.united(left.united(right))); // qDebug() << "top: " << top << " bottom: " << bottom // << " left: " << left << " right: " << right; // qDebug() << "returning rect: " << total; return total; } int Client::checkFullScreenHack(const QRect& geom) const { if (!options->isLegacyFullscreenSupport()) return 0; // if it's noborder window, and has size of one screen or the whole desktop geometry, it's fullscreen hack if (noBorder() && app_noborder && isFullScreenable(true)) { if (geom.size() == workspace()->clientArea(FullArea, geom.center(), desktop()).size()) return 2; // full area fullscreen hack if (geom.size() == workspace()->clientArea(ScreenArea, geom.center(), desktop()).size()) return 1; // xinerama-aware fullscreen hack } return 0; } void Client::updateFullScreenHack(const QRect& geom) { int type = checkFullScreenHack(geom); if (fullscreen_mode == FullScreenNone && type != 0) { fullscreen_mode = FullScreenHack; updateDecoration(false, false); QRect geom; if (rules()->checkStrictGeometry(false)) { geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area ? workspace()->clientArea(FullArea, geom.center(), desktop()) : workspace()->clientArea(ScreenArea, geom.center(), desktop()); } else geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop()); setGeometry(geom); emit fullScreenChanged(); } else if (fullscreen_mode == FullScreenHack && type == 0) { fullscreen_mode = FullScreenNone; updateDecoration(false, false); // whoever called this must setup correct geometry emit fullScreenChanged(); } StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active fullscreens get different layer } static GeometryTip* geometryTip = 0; void Client::positionGeometryTip() { assert(isMove() || isResize()); // Position and Size display if (effects && static_cast(effects)->provides(Effect::GeometryTip)) return; // some effect paints this for us if (options->showGeometryTip()) { if (!geometryTip) { geometryTip = new GeometryTip(&m_geometryHints); } QRect wgeom(moveResizeGeometry()); // position of the frame, size of the window itself wgeom.setWidth(wgeom.width() - (width() - clientSize().width())); wgeom.setHeight(wgeom.height() - (height() - clientSize().height())); if (isShade()) wgeom.setHeight(0); geometryTip->setGeometry(wgeom); if (!geometryTip->isVisible()) geometryTip->show(); geometryTip->raise(); } } bool AbstractClient::startMoveResize() { assert(!isMoveResize()); assert(QWidget::keyboardGrabber() == NULL); assert(QWidget::mouseGrabber() == NULL); stopDelayedMoveResize(); if (QApplication::activePopupWidget() != NULL) return false; // popups have grab if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens())) return false; if (!doStartMoveResize()) { return false; } invalidateDecorationDoubleClickTimer(); setMoveResize(true); workspace()->setClientIsMoving(this); const Position mode = moveResizePointerMode(); if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize setGeometryRestore(geometry()); // "restore" to current geometry setMaximize(false, false); } } if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && mode != PositionCenter) { // Cannot use isResize() yet // Exit quick tile mode when the user attempts to resize a tiled window updateQuickTileMode(QuickTileFlag::None); // Do so without restoring original geometry setGeometryRestore(geometry()); emit quickTileModeChanged(); } updateHaveResizeEffect(); updateInitialMoveResizeGeometry(); checkUnrestrictedMoveResize(); emit clientStartUserMovedResized(this); if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal); return true; } bool Client::doStartMoveResize() { bool has_grab = false; // This reportedly improves smoothness of the moveresize operation, // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* // (http://lists.kde.org/?t=107302193400001&r=1&w=2) QRect r = workspace()->clientArea(FullArea, this); m_moveResizeGrabWindow.create(r, XCB_WINDOW_CLASS_INPUT_ONLY, 0, NULL, rootWindow()); m_moveResizeGrabWindow.map(); m_moveResizeGrabWindow.raise(); updateXTime(); const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(connection(), false, m_moveResizeGrabWindow, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursor::x11Cursor(cursor()), xTime()); ScopedCPointer pointerGrab(xcb_grab_pointer_reply(connection(), cookie, NULL)); if (!pointerGrab.isNull() && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) { has_grab = true; } if (!has_grab && grabXKeyboard(frameId())) has_grab = move_resize_has_keyboard_grab = true; if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize m_moveResizeGrabWindow.reset(); return false; } return true; } void AbstractClient::finishMoveResize(bool cancel) { const bool wasResize = isResize(); // store across leaveMoveResize leaveMoveResize(); if (cancel) setGeometry(initialMoveResizeGeometry()); else { const QRect &moveResizeGeom = moveResizeGeometry(); if (wasResize) { const bool restoreH = maximizeMode() == MaximizeHorizontal && moveResizeGeom.width() != initialMoveResizeGeometry().width(); const bool restoreV = maximizeMode() == MaximizeVertical && moveResizeGeom.height() != initialMoveResizeGeometry().height(); if (restoreH || restoreV) { changeMaximize(restoreV, restoreH, false); } } setGeometry(moveResizeGeom); } checkScreen(); // needs to be done because clientFinishUserMovedResized has not yet re-activated online alignment if (screen() != moveResizeStartScreen()) { workspace()->sendClientToScreen(this, screen()); // checks rule validity if (maximizeMode() != MaximizeRestore) checkWorkspacePosition(); } if (isElectricBorderMaximizing()) { setQuickTileMode(electricBorderMode()); setElectricBorderMaximizing(false); } else if (!cancel) { QRect geom_restore = geometryRestore(); if (!(maximizeMode() & MaximizeHorizontal)) { geom_restore.setX(geometry().x()); geom_restore.setWidth(geometry().width()); } if (!(maximizeMode() & MaximizeVertical)) { geom_restore.setY(geometry().y()); geom_restore.setHeight(geometry().height()); } setGeometryRestore(geom_restore); } // FRAME update(); emit clientFinishUserMovedResized(this); } void Client::leaveMoveResize() { if (needsXWindowMove) { // Do the deferred move m_frame.move(geom.topLeft()); needsXWindowMove = false; } if (!isResize()) sendSyntheticConfigureNotify(); // tell the client about it's new final position if (geometryTip) { geometryTip->hide(); delete geometryTip; geometryTip = NULL; } if (move_resize_has_keyboard_grab) ungrabXKeyboard(); move_resize_has_keyboard_grab = false; xcb_ungrab_pointer(connection(), xTime()); m_moveResizeGrabWindow.reset(); if (syncRequest.counter == XCB_NONE) // don't forget to sanitize since the timeout will no more fire syncRequest.isPending = false; delete syncRequest.timeout; syncRequest.timeout = NULL; AbstractClient::leaveMoveResize(); } // This function checks if it actually makes sense to perform a restricted move/resize. // If e.g. the titlebar is already outside of the workarea, there's no point in performing // a restricted move resize, because then e.g. resize would also move the window (#74555). // NOTE: Most of it is duplicated from handleMoveResize(). void AbstractClient::checkUnrestrictedMoveResize() { if (isUnrestrictedMoveResize()) return; const QRect &moveResizeGeom = moveResizeGeometry(); QRect desktopArea = workspace()->clientArea(WorkArea, moveResizeGeom.center(), desktop()); int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; // restricted move/resize - keep at least part of the titlebar always visible // how much must remain visible when moved away in that direction left_marge = qMin(100 + borderRight(), moveResizeGeom.width()); right_marge = qMin(100 + borderLeft(), moveResizeGeom.width()); // width/height change with opaque resizing, use the initial ones titlebar_marge = initialMoveResizeGeometry().height(); top_marge = borderBottom(); bottom_marge = borderTop(); if (isResize()) { if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.right() < desktopArea.left() + left_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.left() > desktopArea.right() - right_marge) setUnrestrictedMoveResize(true); if (!isUnrestrictedMoveResize() && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out setUnrestrictedMoveResize(true); } if (isMove()) { if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1) setUnrestrictedMoveResize(true); // no need to check top_marge, titlebar_marge already handles it if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out setUnrestrictedMoveResize(true); if (moveResizeGeom.right() < desktopArea.left() + left_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.left() > desktopArea.right() - right_marge) setUnrestrictedMoveResize(true); } } // When the user pressed mouse on the titlebar, don't activate move immediatelly, // since it may be just a click. Activate instead after a delay. Move used to be // activated only after moving by several pixels, but that looks bad. void AbstractClient::startDelayedMoveResize() { Q_ASSERT(!m_moveResize.delayedTimer); m_moveResize.delayedTimer = new QTimer(this); m_moveResize.delayedTimer->setSingleShot(true); connect(m_moveResize.delayedTimer, &QTimer::timeout, this, [this]() { assert(isMoveResizePointerButtonDown()); if (!startMoveResize()) { setMoveResizePointerButtonDown(false); } updateCursor(); stopDelayedMoveResize(); } ); m_moveResize.delayedTimer->start(QApplication::startDragTime()); } void AbstractClient::stopDelayedMoveResize() { delete m_moveResize.delayedTimer; m_moveResize.delayedTimer = nullptr; } void AbstractClient::handleMoveResize(const QPoint &local, const QPoint &global) { const QRect oldGeo = geometry(); handleMoveResize(local.x(), local.y(), global.x(), global.y()); if (!isFullScreen() && isMove()) { if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != geometry()) { GeometryUpdatesBlocker blocker(this); setQuickTileMode(QuickTileFlag::None); const QRect &geom_restore = geometryRestore(); setMoveOffset(QPoint(double(moveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()), double(moveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height()))); if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore) setMoveResizeGeometry(geom_restore); handleMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) { checkQuickTilingMaximizationZones(global.x(), global.y()); } } } bool Client::isWaitingForMoveResizeSync() const { return syncRequest.isPending && isResize(); } void AbstractClient::handleMoveResize(int x, int y, int x_root, int y_root) { if (isWaitingForMoveResizeSync()) return; // we're still waiting for the client or the timeout const Position mode = moveResizePointerMode(); if ((mode == PositionCenter && !isMovableAcrossScreens()) || (mode != PositionCenter && (isShade() || !isResizable()))) return; if (!isMoveResize()) { QPoint p(QPoint(x/* - padding_left*/, y/* - padding_top*/) - moveOffset()); if (p.manhattanLength() >= QApplication::startDragDistance()) { if (!startMoveResize()) { setMoveResizePointerButtonDown(false); updateCursor(); return; } updateCursor(); } else return; } // ShadeHover or ShadeActive, ShadeNormal was already avoided above if (mode != PositionCenter && shadeMode() != ShadeNone) setShade(ShadeNone); QPoint globalPos(x_root, y_root); // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) QPoint topleft = globalPos - moveOffset(); QPoint bottomright = globalPos + invertedMoveOffset(); QRect previousMoveResizeGeom = moveResizeGeometry(); // TODO move whole group when moving its leader or when the leader is not mapped? auto titleBarRect = [this](bool &transposed, int &requiredPixels) -> QRect { const QRect &moveResizeGeom = moveResizeGeometry(); QRect r(moveResizeGeom); r.moveTopLeft(QPoint(0,0)); switch (titlebarPosition()) { default: case PositionTop: r.setHeight(borderTop()); break; case PositionLeft: r.setWidth(borderLeft()); transposed = true; break; case PositionBottom: r.setTop(r.bottom() - borderBottom()); break; case PositionRight: r.setLeft(r.right() - borderRight()); transposed = true; break; } // When doing a restricted move we must always keep 100px of the titlebar // visible to allow the user to be able to move it again. requiredPixels = qMin(100 * (transposed ? r.width() : r.height()), moveResizeGeom.width() * moveResizeGeom.height()); return r; }; bool update = false; if (isResize()) { QRect orig = initialMoveResizeGeometry(); Sizemode sizemode = SizemodeAny; auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizemode, &mode]() { switch(mode) { case PositionTopLeft: setMoveResizeGeometry(QRect(topleft, orig.bottomRight())); break; case PositionBottomRight: setMoveResizeGeometry(QRect(orig.topLeft(), bottomright)); break; case PositionBottomLeft: setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y()))); break; case PositionTopRight: setMoveResizeGeometry(QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom()))); break; case PositionTop: setMoveResizeGeometry(QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight())); sizemode = SizemodeFixedH; // try not to affect height break; case PositionBottom: setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y()))); sizemode = SizemodeFixedH; break; case PositionLeft: setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight())); sizemode = SizemodeFixedW; break; case PositionRight: setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom()))); sizemode = SizemodeFixedW; break; case PositionCenter: default: abort(); break; } }; // first resize (without checking constrains), then snap, then check bounds, then check constrains calculateMoveResizeGeom(); // adjust new size to snap to other windows/borders setMoveResizeGeometry(workspace()->adjustClientSize(this, moveResizeGeometry(), mode)); if (!isUnrestrictedMoveResize()) { // Make sure the titlebar isn't behind a restricted area. We don't need to restrict // the other directions. If not visible enough, move the window to the closest valid // point. We bruteforce this by slowly moving the window back to its previous position QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen availableArea -= workspace()->restrictedMoveArea(desktop()); // Strut areas bool transposed = false; int requiredPixels; QRect bTitleRect = titleBarRect(transposed, requiredPixels); int lastVisiblePixels = -1; QRect lastTry = moveResizeGeometry(); bool titleFailed = false; for (;;) { const QRect titleRect(bTitleRect.translated(moveResizeGeometry().topLeft())); int visiblePixels = 0; int realVisiblePixels = 0; foreach (const QRect &rect, availableArea.rects()) { const QRect r = rect & titleRect; realVisiblePixels += r.width() * r.height(); if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas visiblePixels += r.width() * r.height(); } if (visiblePixels >= requiredPixels) break; // We have reached a valid position if (realVisiblePixels <= lastVisiblePixels) { if (titleFailed && realVisiblePixels < lastVisiblePixels) break; // we won't become better else { if (!titleFailed) setMoveResizeGeometry(lastTry); titleFailed = true; } } lastVisiblePixels = realVisiblePixels; QRect moveResizeGeom = moveResizeGeometry(); lastTry = moveResizeGeom; // Not visible enough, move the window to the closest valid point. We bruteforce // this by slowly moving the window back to its previous position. // The geometry changes at up to two edges, the one with the title (if) shall take // precedence. The opposing edge has no impact on visiblePixels and only one of // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges // if the title edge altered bool leftChanged = previousMoveResizeGeom.left() != moveResizeGeom.left(); bool rightChanged = previousMoveResizeGeom.right() != moveResizeGeom.right(); bool topChanged = previousMoveResizeGeom.top() != moveResizeGeom.top(); bool btmChanged = previousMoveResizeGeom.bottom() != moveResizeGeom.bottom(); auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) { counter = false; if (titleFailed) major = false; if (major) ad1 = ad2 = false; }; switch (titlebarPosition()) { default: case PositionTop: fixChangedState(topChanged, btmChanged, leftChanged, rightChanged); break; case PositionLeft: fixChangedState(leftChanged, rightChanged, topChanged, btmChanged); break; case PositionBottom: fixChangedState(btmChanged, topChanged, leftChanged, rightChanged); break; case PositionRight: fixChangedState(rightChanged, leftChanged, topChanged, btmChanged); break; } if (topChanged) moveResizeGeom.setTop(moveResizeGeom.y() + sign(previousMoveResizeGeom.y() - moveResizeGeom.y())); else if (leftChanged) moveResizeGeom.setLeft(moveResizeGeom.x() + sign(previousMoveResizeGeom.x() - moveResizeGeom.x())); else if (btmChanged) moveResizeGeom.setBottom(moveResizeGeom.bottom() + sign(previousMoveResizeGeom.bottom() - moveResizeGeom.bottom())); else if (rightChanged) moveResizeGeom.setRight(moveResizeGeom.right() + sign(previousMoveResizeGeom.right() - moveResizeGeom.right())); else break; // no position changed - that's certainly not good setMoveResizeGeometry(moveResizeGeom); } } // Always obey size hints, even when in "unrestricted" mode QSize size = adjustedSize(moveResizeGeometry().size(), sizemode); // the new topleft and bottomright corners (after checking size constrains), if they'll be needed topleft = QPoint(moveResizeGeometry().right() - size.width() + 1, moveResizeGeometry().bottom() - size.height() + 1); bottomright = QPoint(moveResizeGeometry().left() + size.width() - 1, moveResizeGeometry().top() + size.height() - 1); orig = moveResizeGeometry(); // if aspect ratios are specified, both dimensions may change. // Therefore grow to the right/bottom if needed. // TODO it should probably obey gravity rather than always using right/bottom ? if (sizemode == SizemodeFixedH) orig.setRight(bottomright.x()); else if (sizemode == SizemodeFixedW) orig.setBottom(bottomright.y()); calculateMoveResizeGeom(); if (moveResizeGeometry().size() != previousMoveResizeGeom.size()) update = true; } else if (isMove()) { assert(mode == PositionCenter); if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here // Special moving of maximized windows on Xinerama screens int screen = screens()->number(globalPos); if (isFullScreen()) setMoveResizeGeometry(workspace()->clientArea(FullScreenArea, screen, 0)); else { QRect moveResizeGeom = workspace()->clientArea(MaximizeArea, screen, 0); QSize adjSize = adjustedSize(moveResizeGeom.size(), SizemodeMax); if (adjSize != moveResizeGeom.size()) { QRect r(moveResizeGeom); moveResizeGeom.setSize(adjSize); moveResizeGeom.moveCenter(r.center()); } setMoveResizeGeometry(moveResizeGeom); } } else { // first move, then snap, then check bounds QRect moveResizeGeom = moveResizeGeometry(); moveResizeGeom.moveTopLeft(topleft); moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(), isUnrestrictedMoveResize())); setMoveResizeGeometry(moveResizeGeom); if (!isUnrestrictedMoveResize()) { const QRegion strut = workspace()->restrictedMoveArea(desktop()); // Strut areas QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen availableArea -= strut; // Strut areas bool transposed = false; int requiredPixels; QRect bTitleRect = titleBarRect(transposed, requiredPixels); for (;;) { QRect moveResizeGeom = moveResizeGeometry(); const QRect titleRect(bTitleRect.translated(moveResizeGeom.topLeft())); int visiblePixels = 0; foreach (const QRect &rect, availableArea.rects()) { const QRect r = rect & titleRect; if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas visiblePixels += r.width() * r.height(); } if (visiblePixels >= requiredPixels) break; // We have reached a valid position // (esp.) if there're more screens with different struts (panels) it the titlebar // will be movable outside the movearea (covering one of the panels) until it // crosses the panel "too much" (not enough visiblePixels) and then stucks because // it's usually only pushed by 1px to either direction // so we first check whether we intersect suc strut and move the window below it // immediately (it's still possible to hit the visiblePixels >= titlebarArea break // by moving the window slightly downwards, but it won't stuck) // see bug #274466 // and bug #301805 for why we can't just match the titlearea against the screen if (screens()->count() > 1) { // optimization // TODO: could be useful on partial screen struts (half-width panels etc.) int newTitleTop = -1; foreach (const QRect &r, strut.rects()) { if (r.top() == 0 && r.width() > r.height() && // "top panel" r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) { newTitleTop = r.bottom() + 1; break; } } if (newTitleTop > -1) { moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change setMoveResizeGeometry(moveResizeGeom); break; } } int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()), dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y()); if (visiblePixels && dx) // means there's no full width cap -> favor horizontally dy = 0; else if (dy) dx = 0; // Move it back moveResizeGeom.translate(dx, dy); setMoveResizeGeometry(moveResizeGeom); if (moveResizeGeom == previousMoveResizeGeom) { break; // Prevent lockup } } } } if (moveResizeGeometry().topLeft() != previousMoveResizeGeom.topLeft()) update = true; } else abort(); if (!update) return; if (isResize() && !haveResizeEffect()) { doResizeSync(); } else performMoveResize(); if (isMove()) { ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime())); } } void Client::doResizeSync() { if (!syncRequest.timeout) { syncRequest.timeout = new QTimer(this); connect(syncRequest.timeout, &QTimer::timeout, this, &Client::performMoveResize); syncRequest.timeout->setSingleShot(true); } if (syncRequest.counter != XCB_NONE) { syncRequest.timeout->start(250); sendSyncRequest(); } else { // for clients not supporting the XSYNC protocol, we syncRequest.isPending = true; // limit the resizes to 30Hz to take pointless load from X11 syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed } // and no human can control faster resizes anyway const QRect &moveResizeGeom = moveResizeGeometry(); m_client.setGeometry(0, 0, moveResizeGeom.width() - (borderLeft() + borderRight()), moveResizeGeom.height() - (borderTop() + borderBottom())); } void AbstractClient::performMoveResize() { const QRect &moveResizeGeom = moveResizeGeometry(); if (isMove() || (isResize() && !haveResizeEffect())) { setGeometry(moveResizeGeom); } doPerformMoveResize(); if (isResize()) addRepaintFull(); positionGeometryTip(); emit clientStepUserMovedResized(this, moveResizeGeom); } void Client::doPerformMoveResize() { if (syncRequest.counter == XCB_NONE) // client w/o XSYNC support. allow the next resize event syncRequest.isPending = false; // NEVER do this for clients with a valid counter // (leads to sync request races in some clients) } void AbstractClient::setElectricBorderMode(QuickTileMode mode) { if (mode != QuickTileMode(QuickTileFlag::Maximize)) { // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) mode &= ~QuickTileMode(QuickTileFlag::Horizontal); if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) mode &= ~QuickTileMode(QuickTileFlag::Vertical); } m_electricMode = mode; } void AbstractClient::setElectricBorderMaximizing(bool maximizing) { m_electricMaximizing = maximizing; if (maximizing) outline()->show(electricBorderMaximizeGeometry(Cursor::pos(), desktop()), moveResizeGeometry()); else outline()->hide(); elevate(maximizing); } QRect AbstractClient::electricBorderMaximizeGeometry(QPoint pos, int desktop) { if (electricBorderMode() == QuickTileMode(QuickTileFlag::Maximize)) { if (maximizeMode() == MaximizeFull) return geometryRestore(); else return workspace()->clientArea(MaximizeArea, pos, desktop); } QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop); if (electricBorderMode() & QuickTileFlag::Left) ret.setRight(ret.left()+ret.width()/2 - 1); else if (electricBorderMode() & QuickTileFlag::Right) ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1); if (electricBorderMode() & QuickTileFlag::Top) ret.setBottom(ret.top()+ret.height()/2 - 1); else if (electricBorderMode() & QuickTileFlag::Bottom) ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1); return ret; } void AbstractClient::setQuickTileMode(QuickTileMode mode, bool keyboard) { // Only allow quick tile on a regular or maximized window if (!isResizable() && maximizeMode() != MaximizeFull) return; workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event GeometryUpdatesBlocker blocker(this); if (mode == QuickTileMode(QuickTileFlag::Maximize)) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry|TabGroup::Maximized); m_quickTileMode = int(QuickTileFlag::None); if (maximizeMode() == MaximizeFull) { setMaximize(false, false); } else { QRect prev_geom_restore = geometryRestore(); // setMaximize() would set moveResizeGeom as geom_restore m_quickTileMode = int(QuickTileFlag::Maximize); setMaximize(true, true); QRect clientArea = workspace()->clientArea(MaximizeArea, this); if (geometry().top() != clientArea.top()) { QRect r(geometry()); r.moveTop(clientArea.top()); setGeometry(r); } setGeometryRestore(prev_geom_restore); } emit quickTileModeChanged(); return; } // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) mode &= ~QuickTileMode(QuickTileFlag::Horizontal); if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) mode &= ~QuickTileMode(QuickTileFlag::Vertical); setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging if (maximizeMode() != MaximizeRestore) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry|TabGroup::Maximized); if (mode != QuickTileMode(QuickTileFlag::None)) { - m_quickTileMode = mode; // decorations may turn off some borders when tiled const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused + + setMaximize(false, false); + setGeometry(electricBorderMaximizeGeometry(keyboard ? geometry().center() : Cursor::pos(), desktop()), geom_mode); + // Store the mode change + m_quickTileMode = mode; + } else { + m_quickTileMode = mode; + setMaximize(false, false); } - // Store the mode change - m_quickTileMode = mode; - - setMaximize(false, false); emit quickTileModeChanged(); return; } if (mode != QuickTileMode(QuickTileFlag::None)) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry); QPoint whichScreen = keyboard ? geometry().center() : Cursor::pos(); // If trying to tile to the side that the window is already tiled to move the window to the next // screen if it exists, otherwise toggle the mode (set QuickTileFlag::None) if (quickTileMode() == mode) { const int numScreens = screens()->count(); const int curScreen = screen(); int nextScreen = curScreen; QVarLengthArray screens(numScreens); for (int i = 0; i < numScreens; ++i) // Cache screens[i] = Screens::self()->geometry(i); for (int i = 0; i < numScreens; ++i) { if (i == curScreen) continue; if (screens[i].bottom() <= screens[curScreen].top() || screens[i].top() >= screens[curScreen].bottom()) continue; // not in horizontal line const int x = screens[i].center().x(); if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) { if (x >= screens[curScreen].center().x() || (curScreen != nextScreen && x <= screens[nextScreen].center().x())) continue; // not left of current or more left then found next } else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) { if (x <= screens[curScreen].center().x() || (curScreen != nextScreen && x >= screens[nextScreen].center().x())) continue; // not right of current or more right then found next } nextScreen = i; } if (nextScreen == curScreen) { mode = QuickTileFlag::None; // No other screens, toggle tiling } else { // Move to other screen setGeometry(geometryRestore().translated(screens[nextScreen].topLeft() - screens[curScreen].topLeft())); whichScreen = screens[nextScreen].center(); // Swap sides if (mode & QuickTileFlag::Horizontal) { mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical); } } setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile. // Store geometry first, so we can go out of this tile later. setGeometryRestore(geometry()); } if (mode != QuickTileMode(QuickTileFlag::None)) { m_quickTileMode = mode; // decorations may turn off some borders when tiled const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; // Temporary, so the maximize code doesn't get all confused m_quickTileMode = int(QuickTileFlag::None); setGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode); } // Store the mode change m_quickTileMode = mode; } if (mode == QuickTileMode(QuickTileFlag::None)) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry); m_quickTileMode = int(QuickTileFlag::None); // Untiling, so just restore geometry, and we're done. if (!geometryRestore().isValid()) // invalid if we started maximized and wait for placement setGeometryRestore(geometry()); // decorations may turn off some borders when tiled const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; setGeometry(geometryRestore(), geom_mode); checkWorkspacePosition(); // Just in case it's a different screen } emit quickTileModeChanged(); } void AbstractClient::sendToScreen(int newScreen) { newScreen = rules()->checkScreen(newScreen); if (isActive()) { screens()->setCurrent(newScreen); // might impact the layer of a fullscreen window foreach (AbstractClient *cc, workspace()->allClientList()) { if (cc->isFullScreen() && cc->screen() == newScreen) { cc->updateLayer(); } } } if (screen() == newScreen) // Don't use isOnScreen(), that's true even when only partially return; GeometryUpdatesBlocker blocker(this); // operating on the maximized / quicktiled window would leave the old geom_restore behind, // so we clear the state first MaximizeMode maxMode = maximizeMode(); QuickTileMode qtMode = quickTileMode(); if (maxMode != MaximizeRestore) maximize(MaximizeRestore); if (qtMode != QuickTileMode(QuickTileFlag::None)) setQuickTileMode(QuickTileFlag::None, true); QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this); QRect screenArea = workspace()->clientArea(MaximizeArea, newScreen, desktop()); // the window can have its center so that the position correction moves the new center onto // the old screen, what will tile it where it is. Ie. the screen is not changed // this happens esp. with electric border quicktiling if (qtMode != QuickTileMode(QuickTileFlag::None)) keepInArea(oldScreenArea); QRect oldGeom = geometry(); QRect newGeom = oldGeom; // move the window to have the same relative position to the center of the screen // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) QPoint center = newGeom.center() - oldScreenArea.center(); center.setX(center.x() * screenArea.width() / oldScreenArea.width()); center.setY(center.y() * screenArea.height() / oldScreenArea.height()); center += screenArea.center(); newGeom.moveCenter(center); setGeometry(newGeom); // If the window was inside the old screen area, explicitly make sure its inside also the new screen area. // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could // be big enough to overlap outside of the new screen area, making struts from other screens come into effect, // which could alter the resulting geometry. if (oldScreenArea.contains(oldGeom)) { keepInArea(screenArea); } // align geom_restore - checkWorkspacePosition operates on it setGeometryRestore(geometry()); checkWorkspacePosition(oldGeom); // re-align geom_restore to constrained geometry setGeometryRestore(geometry()); // finally reset special states // NOTICE that MaximizeRestore/QuickTileFlag::None checks are required. // eg. setting QuickTileFlag::None would break maximization if (maxMode != MaximizeRestore) maximize(maxMode); if (qtMode != QuickTileMode(QuickTileFlag::None) && qtMode != quickTileMode()) setQuickTileMode(qtMode, true); auto tso = workspace()->ensureStackingOrder(transients()); for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it) (*it)->sendToScreen(newScreen); } } // namespace diff --git a/group.cpp b/group.cpp index 7f04331a2..a4c4b0a9f 100644 --- a/group.cpp +++ b/group.cpp @@ -1,921 +1,923 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to window grouping. */ //#define QT_CLEAN_NAMESPACE #include "group.h" #include #include "workspace.h" #include "client.h" #include "effects.h" #include #include #include #include /* TODO Rename as many uses of 'transient' as possible (hasTransient->hasSubwindow,etc.), or I'll get it backwards in half of the cases again. */ namespace KWin { //******************************************** // Group //******************************************** Group::Group(Window leader_P) : leader_client(NULL), leader_wid(leader_P), leader_info(NULL), user_time(-1U), refcount(0) { if (leader_P != None) { leader_client = workspace()->findClient(Predicate::WindowMatch, leader_P); leader_info = new NETWinInfo(connection(), leader_P, rootWindow(), 0, NET::WM2StartupId); } effect_group = new EffectWindowGroupImpl(this); workspace()->addGroup(this); } Group::~Group() { delete leader_info; delete effect_group; } QIcon Group::icon() const { if (leader_client != NULL) return leader_client->icon(); else if (leader_wid != None) { QIcon ic; NETWinInfo info(connection(), leader_wid, rootWindow(), NET::WMIcon, NET::WM2IconPixmap); auto readIcon = [&ic, &info, this](int size, bool scale = true) { const QPixmap pix = KWindowSystem::icon(leader_wid, size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, &info); if (!pix.isNull()) { ic.addPixmap(pix); } }; readIcon(16); readIcon(32); readIcon(48, false); readIcon(64, false); readIcon(128, false); return ic; } return QIcon(); } void Group::addMember(Client* member_P) { _members.append(member_P); // qDebug() << "GROUPADD:" << this << ":" << member_P; // qDebug() << kBacktrace(); } void Group::removeMember(Client* member_P) { // qDebug() << "GROUPREMOVE:" << this << ":" << member_P; // qDebug() << kBacktrace(); Q_ASSERT(_members.contains(member_P)); _members.removeAll(member_P); // there are cases when automatic deleting of groups must be delayed, // e.g. when removing a member and doing some operation on the possibly // other members of the group (which would be however deleted already // if there were no other members) if (refcount == 0 && _members.isEmpty()) { workspace()->removeGroup(this); delete this; } } void Group::ref() { ++refcount; } void Group::deref() { if (--refcount == 0 && _members.isEmpty()) { workspace()->removeGroup(this); delete this; } } void Group::gotLeader(Client* leader_P) { assert(leader_P->window() == leader_wid); leader_client = leader_P; } void Group::lostLeader() { assert(!_members.contains(leader_client)); leader_client = NULL; if (_members.isEmpty()) { workspace()->removeGroup(this); delete this; } } //*************************************** // Workspace //*************************************** Group* Workspace::findGroup(xcb_window_t leader) const { assert(leader != None); for (GroupList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) if ((*it)->leader() == leader) return *it; return NULL; } // Client is group transient, but has no group set. Try to find // group with windows with the same client leader. Group* Workspace::findClientLeaderGroup(const Client* c) const { Group* ret = NULL; for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (*it == c) continue; if ((*it)->wmClientLeader() == c->wmClientLeader()) { if (ret == NULL || ret == (*it)->group()) ret = (*it)->group(); else { // There are already two groups with the same client leader. // This most probably means the app uses group transients without // setting group for its windows. Merging the two groups is a bad // hack, but there's no really good solution for this case. ClientList old_group = (*it)->group()->members(); // old_group autodeletes when being empty for (int pos = 0; pos < old_group.count(); ++pos) { Client* tmp = old_group[ pos ]; if (tmp != c) tmp->changeClientLeaderGroup(ret); } } } } return ret; } void Workspace::updateMinimizedOfTransients(AbstractClient* c) { // if mainwindow is minimized or shaded, minimize transients too if (c->isMinimized()) { for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) { if ((*it)->isModal()) continue; // there's no reason to hide modal dialogs with the main client // but to keep them to eg. watch progress or whatever if (!(*it)->isMinimized()) { (*it)->minimize(); updateMinimizedOfTransients((*it)); } } if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too foreach (AbstractClient * c2, c->mainClients()) c2->minimize(); } } else { // else unmiminize the transients for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) { if ((*it)->isMinimized()) { (*it)->unminimize(); updateMinimizedOfTransients((*it)); } } if (c->isModal()) { foreach (AbstractClient * c2, c->mainClients()) c2->unminimize(); } } } /*! Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops. */ void Workspace::updateOnAllDesktopsOfTransients(AbstractClient* c) { for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) { if ((*it)->isOnAllDesktops() != c->isOnAllDesktops()) (*it)->setOnAllDesktops(c->isOnAllDesktops()); } } // A new window has been mapped. Check if it's not a mainwindow for some already existing transient window. void Workspace::checkTransients(xcb_window_t w) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->checkTransient(w); } //**************************************** // Toplevel //**************************************** // hacks for broken apps here // all resource classes are forced to be lowercase bool Toplevel::resourceMatch(const Toplevel* c1, const Toplevel* c2) { return c1->resourceClass() == c2->resourceClass(); } //**************************************** // Client //**************************************** -bool Client::belongToSameApplication(const Client* c1, const Client* c2, bool active_hack) +bool Client::belongToSameApplication(const Client* c1, const Client* c2, SameApplicationChecks checks) { bool same_app = false; // tests that definitely mean they belong together if (c1 == c2) same_app = true; else if (c1->isTransient() && c2->hasTransient(c1, true)) same_app = true; // c1 has c2 as mainwindow else if (c2->isTransient() && c1->hasTransient(c2, true)) same_app = true; // c2 has c1 as mainwindow else if (c1->group() == c2->group()) same_app = true; // same group else if (c1->wmClientLeader() == c2->wmClientLeader() && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), && c2->wmClientLeader() != c2->window()) // don't use in this test then same_app = true; // same client leader // tests that mean they most probably don't belong together - else if (c1->pid() != c2->pid() + else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) || c1->wmClientMachine(false) != c2->wmClientMachine(false)) ; // different processes else if (c1->wmClientLeader() != c2->wmClientLeader() && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), - && c2->wmClientLeader() != c2->window()) // don't use in this test then + && c2->wmClientLeader() != c2->window() // don't use in this test then + && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) ; // different client leader else if (!resourceMatch(c1, c2)) ; // different apps - else if (!sameAppWindowRoleMatch(c1, c2, active_hack)) + else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive)) + && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) ; // "different" apps else if (c1->pid() == 0 || c2->pid() == 0) ; // old apps that don't have _NET_WM_PID, consider them different // if they weren't found to match above else same_app = true; // looks like it's the same app return same_app; } // Non-transient windows with window role containing '#' are always // considered belonging to different applications (unless // the window role is exactly the same). KMainWindow sets // window role this way by default, and different KMainWindow // usually "are" different application from user's point of view. // This help with no-focus-stealing for e.g. konqy reusing. // On the other hand, if one of the windows is active, they are // considered belonging to the same application. This is for // the cases when opening new mainwindow directly from the application, // e.g. 'Open New Window' in konqy ( active_hack == true ). bool Client::sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack) { if (c1->isTransient()) { while (const Client *t = dynamic_cast(c1->transientFor())) c1 = t; if (c1->groupTransient()) return c1->group() == c2->group(); #if 0 // if a group transient is in its own group, it didn't possibly have a group, // and therefore should be considered belonging to the same app like // all other windows from the same app || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; #endif } if (c2->isTransient()) { while (const Client *t = dynamic_cast(c2->transientFor())) c2 = t; if (c2->groupTransient()) return c1->group() == c2->group(); #if 0 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; #endif } int pos1 = c1->windowRole().indexOf('#'); int pos2 = c2->windowRole().indexOf('#'); if ((pos1 >= 0 && pos2 >= 0)) { if (!active_hack) // without the active hack for focus stealing prevention, return c1 == c2; // different mainwindows are always different apps if (!c1->isActive() && !c2->isActive()) return c1 == c2; else return true; } return true; } /* Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 WM_TRANSIENT_FOR is basically means "this is my mainwindow". For NET::Unknown windows, transient windows are considered to be NET::Dialog windows, for compatibility with non-NETWM clients. KWin may adjust the value of this property in some cases (window pointing to itself or creating a loop, keeping NET::Splash windows above other windows from the same app, etc.). Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after possibly being adjusted by KWin. Client::transient_for points to the Client this Client is transient for, or is NULL. If Client::transient_for_id is poiting to the root window, the window is considered to be transient for the whole window group, as suggested in NETWM 7.3. In the case of group transient window, Client::transient_for is NULL, and Client::groupTransient() returns true. Such window is treated as if it were transient for every window in its window group that has been mapped _before_ it (or, to be exact, was added to the same group before it). Otherwise two group transients can create loops, which can lead very very nasty things (bug #67914 and all its dupes). Client::original_transient_for_id is the value of the property, which may be different if Client::transient_for_id if e.g. forcing NET::Splash to be kept on top of its window group, or when the mainwindow is not mapped yet, in which case the window is temporarily made group transient, and when the mainwindow is mapped, transiency is re-evaluated. This can get a bit complicated with with e.g. two Konqueror windows created by the same process. They should ideally appear like two independent applications to the user. This should be accomplished by all windows in the same process having the same window group (needs to be changed in Qt at the moment), and using non-group transients poiting to their relevant mainwindow for toolwindows etc. KWin should handle both group and non-group transient dialogs well. In other words: - non-transient windows : isTransient() == false - normal transients : transientFor() != NULL - group transients : groupTransient() == true - list of mainwindows : mainClients() (call once and loop over the result) - list of transients : transients() - every window in the group : group()->members() */ Xcb::TransientFor Client::fetchTransient() const { return Xcb::TransientFor(window()); } void Client::readTransientProperty(Xcb::TransientFor &transientFor) { xcb_window_t new_transient_for_id = XCB_WINDOW_NONE; if (transientFor.getTransientFor(&new_transient_for_id)) { m_originalTransientForId = new_transient_for_id; new_transient_for_id = verifyTransientFor(new_transient_for_id, true); } else { m_originalTransientForId = XCB_WINDOW_NONE; new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false); } setTransient(new_transient_for_id); } void Client::readTransient() { Xcb::TransientFor transientFor = fetchTransient(); readTransientProperty(transientFor); } void Client::setTransient(xcb_window_t new_transient_for_id) { if (new_transient_for_id != m_transientForId) { removeFromMainClients(); Client *transient_for = nullptr; m_transientForId = new_transient_for_id; if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) { transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId); assert(transient_for != NULL); // verifyTransient() had to check this transient_for->addTransient(this); } // checkGroup() will check 'check_active_modal' setTransientFor(transient_for); checkGroup(NULL, true); // force, because transiency has changed workspace()->updateClientLayer(this); workspace()->resetUpdateToolWindowsTimer(); emit transientChanged(); } } void Client::removeFromMainClients() { if (transientFor()) transientFor()->removeTransient(this); if (groupTransient()) { for (ClientList::ConstIterator it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) (*it)->removeTransient(this); } } // *sigh* this transiency handling is madness :( // This one is called when destroying/releasing a window. // It makes sure this client is removed from all grouping // related lists. void Client::cleanGrouping() { // qDebug() << "CLEANGROUPING:" << this; // for ( ClientList::ConstIterator it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL:" << *it; // ClientList mains; // mains = mainClients(); // for ( ClientList::ConstIterator it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN:" << *it; removeFromMainClients(); // qDebug() << "CLEANGROUPING2:" << this; // for ( ClientList::ConstIterator it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL2:" << *it; // mains = mainClients(); // for ( ClientList::ConstIterator it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN2:" << *it; for (auto it = transients().constBegin(); it != transients().constEnd(); ) { if ((*it)->transientFor() == this) { removeTransient(*it); it = transients().constBegin(); // restart, just in case something more has changed with the list } else ++it; } // qDebug() << "CLEANGROUPING3:" << this; // for ( ClientList::ConstIterator it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL3:" << *it; // mains = mainClients(); // for ( ClientList::ConstIterator it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN3:" << *it; // HACK // removeFromMainClients() did remove 'this' from transient // lists of all group members, but then made windows that // were transient for 'this' group transient, which again // added 'this' to those transient lists :( ClientList group_members = group()->members(); group()->removeMember(this); in_group = NULL; for (ClientList::ConstIterator it = group_members.constBegin(); it != group_members.constEnd(); ++it) (*it)->removeTransient(this); // qDebug() << "CLEANGROUPING4:" << this; // for ( ClientList::ConstIterator it = group_members.begin(); // it != group_members.end(); // ++it ) // qDebug() << "CL4:" << *it; m_transientForId = XCB_WINDOW_NONE; } // Make sure that no group transient is considered transient // for a window that is (directly or indirectly) transient for it // (including another group transients). // Non-group transients not causing loops are checked in verifyTransientFor(). void Client::checkGroupTransients() { for (ClientList::ConstIterator it1 = group()->members().constBegin(); it1 != group()->members().constEnd(); ++it1) { if (!(*it1)->groupTransient()) // check all group transients in the group continue; // TODO optimize to check only the changed ones? for (ClientList::ConstIterator it2 = group()->members().constBegin(); it2 != group()->members().constEnd(); ++it2) { // group transients can be transient only for others in the group, // so don't make them transient for the ones that are transient for it if (*it1 == *it2) continue; for (AbstractClient* cl = (*it2)->transientFor(); cl != NULL; cl = cl->transientFor()) { if (cl == *it1) { // don't use removeTransient(), that would modify *it2 too (*it2)->removeTransientFromList(*it1); continue; } } // if *it1 and *it2 are both group transients, and are transient for each other, // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later, // and should be therefore on top of *it1 // TODO This could possibly be optimized, it also requires hasTransient() to check for loops. if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true)) (*it2)->removeTransientFromList(*it1); // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3 // is added, make it transient only for W2, not for W1, because it's already indirectly // transient for it - the indirect transiency actually shouldn't break anything, // but it can lead to exponentially expensive operations (#95231) // TODO this is pretty slow as well for (ClientList::ConstIterator it3 = group()->members().constBegin(); it3 != group()->members().constEnd(); ++it3) { if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3) continue; if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) { if ((*it2)->hasTransient(*it3, true)) (*it2)->removeTransientFromList(*it1); if ((*it3)->hasTransient(*it2, true)) (*it3)->removeTransientFromList(*it1); } } } } } /*! Check that the window is not transient for itself, and similar nonsense. */ xcb_window_t Client::verifyTransientFor(xcb_window_t new_transient_for, bool set) { xcb_window_t new_property_value = new_transient_for; // make sure splashscreens are shown above all their app's windows, even though // they're in Normal layer if (isSplash() && new_transient_for == XCB_WINDOW_NONE) new_transient_for = rootWindow(); if (new_transient_for == XCB_WINDOW_NONE) { if (set) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window new_property_value = new_transient_for = rootWindow(); else return XCB_WINDOW_NONE; } if (new_transient_for == window()) { // pointing to self // also fix the property itself qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ; new_property_value = new_transient_for = rootWindow(); } // The transient_for window may be embedded in another application, // so kwin cannot see it. Try to find the managed client for the // window and fix the transient_for property if possible. xcb_window_t before_search = new_transient_for; while (new_transient_for != XCB_WINDOW_NONE && new_transient_for != rootWindow() && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { Xcb::Tree tree(new_transient_for); if (tree.isNull()) { break; } new_transient_for = tree->parent; } if (Client* new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { if (new_transient_for != before_search) { qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window " << before_search << ", child of " << new_transient_for_client << ", adjusting."; new_property_value = new_transient_for; // also fix the property } } else new_transient_for = before_search; // nice try // loop detection // group transients cannot cause loops, because they're considered transient only for non-transient // windows in the group int count = 20; xcb_window_t loop_pos = new_transient_for; while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) { Client* pos = workspace()->findClient(Predicate::WindowMatch, loop_pos); if (pos == NULL) break; loop_pos = pos->m_transientForId; if (--count == 0 || pos == this) { qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ; new_transient_for = rootWindow(); } } if (new_transient_for != rootWindow() && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == NULL) { // it's transient for a specific window, but that window is not mapped new_transient_for = rootWindow(); } if (new_property_value != m_originalTransientForId) Xcb::setTransientFor(window(), new_property_value); return new_transient_for; } void Client::addTransient(AbstractClient* cl) { AbstractClient::addTransient(cl); if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal()) check_active_modal = true; // qDebug() << "ADDTRANS:" << this << ":" << cl; // qDebug() << kBacktrace(); // for ( ClientList::ConstIterator it = transients_list.begin(); // it != transients_list.end(); // ++it ) // qDebug() << "AT:" << (*it); } void Client::removeTransient(AbstractClient* cl) { // qDebug() << "REMOVETRANS:" << this << ":" << cl; // qDebug() << kBacktrace(); // cl is transient for this, but this is going away // make cl group transient AbstractClient::removeTransient(cl); if (cl->transientFor() == this) { if (Client *c = dynamic_cast(cl)) { c->m_transientForId = XCB_WINDOW_NONE; c->setTransientFor(nullptr); // SELI // SELI cl->setTransient( rootWindow()); c->setTransient(XCB_WINDOW_NONE); } } } // A new window has been mapped. Check if it's not a mainwindow for this already existing window. void Client::checkTransient(xcb_window_t w) { if (m_originalTransientForId != w) return; w = verifyTransientFor(w, true); setTransient(w); } // returns true if cl is the transient_for window for this client, // or recursively the transient_for window bool Client::hasTransient(const AbstractClient* cl, bool indirect) const { if (const Client *c = dynamic_cast(cl)) { // checkGroupTransients() uses this to break loops, so hasTransient() must detect them ConstClientList set; return hasTransientInternal(c, indirect, set); } return false; } bool Client::hasTransientInternal(const Client* cl, bool indirect, ConstClientList& set) const { if (const Client *t = dynamic_cast(cl->transientFor())) { if (t == this) return true; if (!indirect) return false; if (set.contains(cl)) return false; set.append(cl); return hasTransientInternal(t, indirect, set); } if (!cl->isTransient()) return false; if (group() != cl->group()) return false; // cl is group transient, search from top if (transients().contains(const_cast< Client* >(cl))) return true; if (!indirect) return false; if (set.contains(this)) return false; set.append(this); for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) { - Client *c = dynamic_cast(*it); + const Client *c = qobject_cast(*it); if (!c) { continue; } if (c->hasTransientInternal(cl, indirect, set)) return true; } return false; } QList Client::mainClients() const { if (!isTransient()) return QList(); if (const AbstractClient *t = transientFor()) return QList{const_cast< AbstractClient* >(t)}; QList result; Q_ASSERT(group()); for (ClientList::ConstIterator it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) if ((*it)->hasTransient(this, false)) result.append(*it); return result; } AbstractClient* Client::findModal(bool allow_itself) { for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) if (AbstractClient* ret = (*it)->findModal(true)) return ret; if (isModal() && allow_itself) return this; return NULL; } // Client::window_group only holds the contents of the hint, // but it should be used only to find the group, not for anything else // Argument is only when some specific group needs to be set. void Client::checkGroup(Group* set_group, bool force) { Group* old_group = in_group; if (old_group != NULL) old_group->ref(); // turn off automatic deleting if (set_group != NULL) { if (set_group != in_group) { if (in_group != NULL) in_group->removeMember(this); in_group = set_group; in_group->addMember(this); } } else if (info->groupLeader() != XCB_WINDOW_NONE) { Group* new_group = workspace()->findGroup(info->groupLeader()); Client *t = qobject_cast(transientFor()); if (t != NULL && t->group() != new_group) { // move the window to the right group (e.g. a dialog provided // by different app, but transient for this one, so make it part of that group) new_group = t->group(); } if (new_group == NULL) // doesn't exist yet new_group = new Group(info->groupLeader()); if (new_group != in_group) { if (in_group != NULL) in_group->removeMember(this); in_group = new_group; in_group->addMember(this); } } else { if (Client *t = qobject_cast(transientFor())) { // doesn't have window group set, but is transient for something // so make it part of that group Group* new_group = t->group(); if (new_group != in_group) { if (in_group != NULL) in_group->removeMember(this); in_group = t->group(); in_group->addMember(this); } } else if (groupTransient()) { // group transient which actually doesn't have a group :( // try creating group with other windows with the same client leader Group* new_group = workspace()->findClientLeaderGroup(this); if (new_group == NULL) new_group = new Group(None); if (new_group != in_group) { if (in_group != NULL) in_group->removeMember(this); in_group = new_group; in_group->addMember(this); } } else { // Not transient without a group, put it in its client leader group. // This might be stupid if grouping was used for e.g. taskbar grouping // or minimizing together the whole group, but as long as it is used // only for dialogs it's better to keep windows from one app in one group. Group* new_group = workspace()->findClientLeaderGroup(this); if (in_group != NULL && in_group != new_group) { in_group->removeMember(this); in_group = NULL; } if (new_group == NULL) new_group = new Group(None); if (in_group != new_group) { in_group = new_group; in_group->addMember(this); } } } if (in_group != old_group || force) { for (auto it = transients().constBegin(); it != transients().constEnd(); ) { Client *c = dynamic_cast(*it); if (!c) { ++it; continue; } // group transients in the old group are no longer transient for it if (c->groupTransient() && c->group() != group()) { removeTransientFromList(c); it = transients().constBegin(); // restart, just in case something more has changed with the list } else ++it; } if (groupTransient()) { // no longer transient for ones in the old group if (old_group != NULL) { for (ClientList::ConstIterator it = old_group->members().constBegin(); it != old_group->members().constEnd(); ++it) (*it)->removeTransient(this); } // and make transient for all in the new group for (ClientList::ConstIterator it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { if (*it == this) break; // this means the window is only transient for windows mapped before it (*it)->addTransient(this); } } // group transient splashscreens should be transient even for windows // in group mapped later for (ClientList::ConstIterator it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { if (!(*it)->isSplash()) continue; if (!(*it)->groupTransient()) continue; if (*it == this || hasTransient(*it, true)) // TODO indirect? continue; addTransient(*it); } } if (old_group != NULL) old_group->deref(); // can be now deleted if empty checkGroupTransients(); checkActiveModal(); workspace()->updateClientLayer(this); } // used by Workspace::findClientLeaderGroup() void Client::changeClientLeaderGroup(Group* gr) { // transientFor() != NULL are in the group of their mainwindow, so keep them there if (transientFor() != NULL) return; // also don't change the group for window which have group set if (info->groupLeader()) return; checkGroup(gr); // change group } bool Client::check_active_modal = false; void Client::checkActiveModal() { // if the active window got new modal transient, activate it. // cannot be done in AddTransient(), because there may temporarily // exist loops, breaking findModal Client* check_modal = dynamic_cast(workspace()->mostRecentlyActivatedClient()); if (check_modal != NULL && check_modal->check_active_modal) { Client* new_modal = dynamic_cast(check_modal->findModal()); if (new_modal != NULL && new_modal != check_modal) { if (!new_modal->isManaged()) return; // postpone check until end of manage() workspace()->activateClient(new_modal); } check_modal->check_active_modal = false; } } } // namespace diff --git a/idle_inhibition.cpp b/idle_inhibition.cpp new file mode 100644 index 000000000..6e78b4012 --- /dev/null +++ b/idle_inhibition.cpp @@ -0,0 +1,89 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 "idle_inhibition.h" +#include "deleted.h" +#include "shell_client.h" + +#include +#include + +#include + +using KWayland::Server::SurfaceInterface; + +namespace KWin +{ + +IdleInhibition::IdleInhibition(IdleInterface *idle) + : QObject(idle) + , m_idle(idle) +{ +} + +IdleInhibition::~IdleInhibition() = default; + +void IdleInhibition::registerShellClient(ShellClient *client) +{ + auto surface = client->surface(); + m_connections.insert(client, connect(surface, &SurfaceInterface::inhibitsIdleChanged, this, + [this, client] { + // TODO: only inhibit if the ShellClient is visible + if (client->surface()->inhibitsIdle()) { + inhibit(client); + } else { + uninhibit(client); + } + } + )); + connect(client, &ShellClient::windowClosed, this, + [this, client] { + uninhibit(client); + auto it = m_connections.find(client); + if (it != m_connections.end()) { + disconnect(it.value()); + m_connections.erase(it); + } + } + ); +} + +void IdleInhibition::inhibit(ShellClient *client) +{ + if (isInhibited(client)) { + // already inhibited + return; + } + m_idleInhibitors << client; + m_idle->inhibit(); + // TODO: notify powerdevil? +} + +void IdleInhibition::uninhibit(ShellClient *client) +{ + auto it = std::find_if(m_idleInhibitors.begin(), m_idleInhibitors.end(), [client] (auto c) { return c == client; }); + if (it == m_idleInhibitors.end()) { + // not inhibited + return; + } + m_idleInhibitors.erase(it); + m_idle->uninhibit(); +} + +} diff --git a/idle_inhibition.h b/idle_inhibition.h new file mode 100644 index 000000000..1c90119e3 --- /dev/null +++ b/idle_inhibition.h @@ -0,0 +1,66 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 . +*********************************************************************/ +#pragma once + +#include +#include +#include + +#include + +namespace KWayland +{ +namespace Server +{ +class IdleInterface; +} +} + +using KWayland::Server::IdleInterface; + +namespace KWin +{ +class ShellClient; + +class IdleInhibition : public QObject +{ + Q_OBJECT +public: + explicit IdleInhibition(IdleInterface *idle); + ~IdleInhibition(); + + void registerShellClient(ShellClient *client); + + bool isInhibited() const { + return !m_idleInhibitors.isEmpty(); + } + bool isInhibited(ShellClient *client) const { + return std::any_of(m_idleInhibitors.begin(), m_idleInhibitors.end(), [client] (auto c) { return c == client; }); + } + +private: + void inhibit(ShellClient *client); + void uninhibit(ShellClient *client); + + IdleInterface *m_idle; + QVector m_idleInhibitors; + QMap m_connections; +}; +} diff --git a/input.cpp b/input.cpp index 2199acd6a..976611538 100644 --- a/input.cpp +++ b/input.cpp @@ -1,2141 +1,2231 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "input.h" #include "input_event.h" #include "input_event_spy.h" #include "keyboard_input.h" #include "pointer_input.h" #include "touch_input.h" #include "client.h" #include "effects.h" #include "gestures.h" #include "globalshortcuts.h" #include "logind.h" #include "main.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox/tabbox.h" #endif #include "unmanaged.h" #include "screenedge.h" #include "screens.h" #include "workspace.h" #if HAVE_INPUT #include "libinput/connection.h" #include "libinput/device.h" #endif #include "platform.h" #include "popup_input_filter.h" #include "shell_client.h" #include "wayland_server.h" #include #include #include #include #include #include #include //screenlocker #include // Qt #include #include namespace KWin { InputEventFilter::InputEventFilter() = default; InputEventFilter::~InputEventFilter() { if (input()) { input()->uninstallInputEventFilter(this); } } bool InputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { Q_UNUSED(event) Q_UNUSED(nativeButton) return false; } bool InputEventFilter::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::keyEvent(QKeyEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::touchDown(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchMotion(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchUp(quint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureUpdate(const QSizeF &delta, quint32 time) { Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } +bool InputEventFilter::switchEvent(SwitchEvent *event) +{ + Q_UNUSED(event) + return false; +} + void InputEventFilter::passToWaylandServer(QKeyEvent *event) { Q_ASSERT(waylandServer()); if (event->isAutoRepeat()) { return; } switch (event->type()) { case QEvent::KeyPress: waylandServer()->seat()->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: waylandServer()->seat()->keyReleased(event->nativeScanCode()); break; default: break; } } #if HAVE_INPUT class VirtualTerminalFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { // really on press and not on release? X11 switches on press. if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { const xkb_keysym_t keysym = event->nativeVirtualKey(); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { LogindIntegration::self()->switchVirtualTerminal(keysym - XKB_KEY_XF86Switch_VT_1 + 1); return true; } } return false; } }; #endif class TerminateServerFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { if (event->nativeVirtualKey() == XKB_KEY_Terminate_Server) { qCWarning(KWIN_CORE) << "Request to terminate server"; QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection); return true; } } return false; } }; class LockScreenFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (event->type() == QEvent::MouseMove) { input()->pointer()->update(); if (pointerSurfaceAllowed()) { seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton); } } return true; } bool wheelEvent(QWheelEvent *event) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); if (pointerSurfaceAllowed()) { seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); } return true; } bool keyEvent(QKeyEvent * event) override { if (!waylandServer()->isScreenLocked()) { return false; } if (event->isAutoRepeat()) { // wayland client takes care of it return true; } // send event to KSldApp for global accel // if event is set to accepted it means a whitelisted shortcut was triggered // in that case we filter it out and don't process it further event->setAccepted(false); QCoreApplication::sendEvent(ScreenLocker::KSldApp::self(), event); if (event->isAccepted()) { return true; } // continue normal processing input()->keyboard()->update(); auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (!keyboardSurfaceAllowed()) { // don't pass event to seat return true; } switch (event->type()) { case QEvent::KeyPress: seat->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: seat->keyReleased(event->nativeScanCode()); break; default: break; } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } if (touchSurfaceAllowed()) { input()->touch()->insertId(id, seat->touchDown(pos)); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } } return true; } bool touchUp(quint32 id, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } } return true; } bool pinchGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(fingerCount) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureEnd(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureCancelled(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(fingerCount) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { Q_UNUSED(delta) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureEnd(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureCancelled(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } private: bool surfaceAllowed(KWayland::Server::SurfaceInterface *(KWayland::Server::SeatInterface::*method)() const) const { if (KWayland::Server::SurfaceInterface *s = (waylandServer()->seat()->*method)()) { if (Toplevel *t = waylandServer()->findClient(s)) { return t->isLockScreen() || t->isInputMethod(); } return false; } return true; } bool pointerSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedPointerSurface); } bool keyboardSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedKeyboardSurface); } bool touchSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedTouchSurface); } }; class PointerConstraintsFilter : public InputEventFilter { public: explicit PointerConstraintsFilter() : InputEventFilter() , m_timer(new QTimer) { QObject::connect(m_timer.data(), &QTimer::timeout, [this] { if (waylandServer()) { // break keyboard focus, this cancels the pressed ESC waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); } input()->pointer()->breakPointerConstraints(); input()->pointer()->blockPointerConstraints(); // TODO: show notification waylandServer()->seat()->keyReleased(m_keyCode); cancel(); } ); } bool keyEvent(QKeyEvent *event) override { if (isActive()) { if (event->type() == QEvent::KeyPress) { // is that another key that gets pressed? if (!event->isAutoRepeat() && event->key() != Qt::Key_Escape) { cancel(); return false; } if (event->isAutoRepeat() && event->key() == Qt::Key_Escape) { // filter out return true; } } else { cancel(); return false; } } else if (input()->pointer()->isConstrained()) { if (event->type() == QEvent::KeyPress && event->key() == Qt::Key_Escape && static_cast(event)->modifiersRelevantForGlobalShortcuts() == Qt::KeyboardModifiers()) { // TODO: don't hard code m_timer->start(3000); m_keyCode = event->nativeScanCode(); return false; } } return false; } void cancel() { if (!isActive()) { return; } m_timer->stop(); input()->keyboard()->update(); } bool isActive() const { return m_timer->isActive(); } private: QScopedPointer m_timer; int m_keyCode = 0; }; class EffectsFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!effects) { return false; } return static_cast(effects)->checkInputWindowEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!effects || !static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { return false; } waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(event); return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchDown(id, pos, time); } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchMotion(id, pos, time); } bool touchUp(quint32 id, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchUp(id, time); } }; class MoveResizeFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } switch (event->type()) { case QEvent::MouseMove: c->updateMoveResize(event->screenPos().toPoint()); break; case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { c->endMoveResize(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while moving a window return workspace()->getMovingClient() != nullptr; } bool keyEvent(QKeyEvent *event) override { AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } if (event->type() == QEvent::KeyPress) { c->keyPressEvent(event->key() | event->modifiers()); if (c->isMove() || c->isResize()) { // only update if mode didn't end c->updateMoveResize(input()->globalPointer()); } } return true; } }; class WindowSelectorFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!m_active) { return false; } switch (event->type()) { case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { if (event->button() == Qt::RightButton) { cancel(); } else { accept(event->globalPos()); } } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while selecting a window return m_active; } bool keyEvent(QKeyEvent *event) override { Q_UNUSED(event) if (!m_active) { return false; } waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { // x11 variant does this on key press, so do the same if (event->key() == Qt::Key_Escape) { cancel(); } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Space) { accept(input()->globalPointer()); } if (input()->supportsPointerWarping()) { int mx = 0; int my = 0; if (event->key() == Qt::Key_Left) { mx = -10; } if (event->key() == Qt::Key_Right) { mx = 10; } if (event->key() == Qt::Key_Up) { my = -10; } if (event->key() == Qt::Key_Down) { my = 10; } if (event->modifiers() & Qt::ControlModifier) { mx /= 10; my /= 10; } input()->warpPointer(input()->globalPointer() + QPointF(mx, my)); } } // filter out while selecting a window return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } m_touchPoints.insert(id, pos); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } auto it = m_touchPoints.find(id); if (it != m_touchPoints.end()) { *it = pos; } return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } auto it = m_touchPoints.find(id); if (it != m_touchPoints.end()) { const auto pos = it.value(); m_touchPoints.erase(it); if (m_touchPoints.isEmpty()) { accept(pos); } } return true; } bool isActive() const { return m_active; } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_callback = callback; input()->keyboard()->update(); input()->cancelTouch(); } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_pointSelectionFallback = callback; input()->keyboard()->update(); input()->cancelTouch(); } private: void deactivate() { m_active = false; m_callback = std::function(); m_pointSelectionFallback = std::function(); input()->pointer()->removeWindowSelectionCursor(); input()->keyboard()->update(); m_touchPoints.clear(); } void cancel() { if (m_callback) { m_callback(nullptr); } if (m_pointSelectionFallback) { m_pointSelectionFallback(QPoint(-1, -1)); } deactivate(); } void accept(const QPoint &pos) { if (m_callback) { // TODO: this ignores shaped windows m_callback(input()->findToplevel(pos)); } if (m_pointSelectionFallback) { m_pointSelectionFallback(pos); } deactivate(); } void accept(const QPointF &pos) { accept(pos.toPoint()); } bool m_active = false; std::function m_callback; std::function m_pointSelectionFallback; QMap m_touchPoints; }; class GlobalShortcutFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton); if (event->type() == QEvent::MouseButtonPress) { if (input()->shortcuts()->processPointerPressed(event->modifiers(), event->buttons())) { return true; } } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->modifiers() == Qt::NoModifier) { return false; } PointerAxisDirection direction = PointerAxisUp; if (event->angleDelta().x() < 0) { direction = PointerAxisRight; } else if (event->angleDelta().x() > 0) { direction = PointerAxisLeft; } else if (event->angleDelta().y() < 0) { direction = PointerAxisDown; } else if (event->angleDelta().y() > 0) { direction = PointerAxisUp; } return input()->shortcuts()->processAxis(event->modifiers(), direction); } bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress) { return input()->shortcuts()->processKey(static_cast(event)->modifiersRelevantForGlobalShortcuts(), event->key()); } return false; } bool swipeGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeStart(fingerCount); return false; } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeUpdate(delta); return false; } bool swipeGestureCancelled(quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeCancel(); return false; } bool swipeGestureEnd(quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeEnd(); return false; } }; class InternalWindowEventFilter : public InputEventFilter { bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } if (!internal) { return false; } QMouseEvent e(event->type(), event->pos() - internal->position(), event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool wheelEvent(QWheelEvent *event) override { auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } const QPointF localPos = event->globalPosF() - QPointF(internal->x(), internal->y()); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta() * -1, delta * -1, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool keyEvent(QKeyEvent *event) override { const auto &internalClients = waylandServer()->internalClients(); if (internalClients.isEmpty()) { return false; } QWindow *found = nullptr; auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if (!screens()->geometry().contains(w->geometry())) { continue; } if (w->property("_q_showWithoutActivating").toBool()) { continue; } + if (w->property("outputOnly").toBool()) { + continue; + } found = w; break; } } while (it != internalClients.begin()); if (!found) { return false; } auto xkb = input()->keyboard()->xkb(); Qt::Key key = xkb->toQtKey(xkb->toKeysym(event->nativeScanCode())); if (key == Qt::Key_Super_L || key == Qt::Key_Super_R) { // workaround for QTBUG-62102 key = Qt::Key_Meta; } QKeyEvent internalEvent(event->type(), key, event->modifiers(), event->nativeScanCode(), event->nativeVirtualKey(), event->nativeModifiers(), event->text()); internalEvent.setAccepted(false); if (QCoreApplication::sendEvent(found, &internalEvent)) { waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); return true; } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { // something else is getting the events return false; } auto touch = input()->touch(); if (touch->internalPressId() != -1) { // already on a decoration, ignore further touch points, but filter out return true; } // a new touch point seat->setTimestamp(time); touch->update(pos); auto internal = touch->internalWindow(); if (!internal) { return false; } touch->setInternalPressId(id); // Qt's touch event API is rather complex, let's do fake mouse events instead m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); QCoreApplication::instance()->sendEvent(internal.data(), &e); return true; } bool touchUp(quint32 id, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setInternalPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; +namespace { + +enum class MouseAction { + ModifierOnly, + ModifierAndWindow +}; +std::pair performClientMouseAction(QMouseEvent *event, AbstractClient *client, MouseAction action = MouseAction::ModifierOnly) +{ + Options::MouseCommand command = Options::MouseNothing; + bool wasAction = false; + if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { + wasAction = true; + switch (event->button()) { + case Qt::LeftButton: + command = options->commandAll1(); + break; + case Qt::MiddleButton: + command = options->commandAll2(); + break; + case Qt::RightButton: + command = options->commandAll3(); + break; + default: + // nothing + break; + } + } else { + if (action == MouseAction::ModifierAndWindow) { + command = client->getMouseCommand(event->button(), &wasAction); + } + } + if (wasAction) { + return std::make_pair(wasAction, !client->performMouseCommand(command, event->globalPos())); + } + return std::make_pair(wasAction, false); +} + +std::pair performClientWheelAction(QWheelEvent *event, AbstractClient *c, MouseAction action = MouseAction::ModifierOnly) +{ + bool wasAction = false; + Options::MouseCommand command = Options::MouseNothing; + if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { + wasAction = true; + command = options->operationWindowMouseWheel(-1 * event->angleDelta().y()); + } else { + if (action == MouseAction::ModifierAndWindow) { + command = c->getWheelCommand(Qt::Vertical, &wasAction); + } + } + if (wasAction) { + return std::make_pair(wasAction, !c->performMouseCommand(command, event->globalPos())); + } + return std::make_pair(wasAction, false); +} + +} + class DecorationEventFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF p = event->globalPos() - decoration->client()->pos(); switch (event->type()) { case QEvent::MouseMove: { if (event->buttons() == Qt::NoButton) { return false; } QHoverEvent e(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); return true; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { + const auto actionResult = performClientMouseAction(event, decoration->client()); + if (actionResult.first) { + return actionResult.second; + } QMouseEvent e(event->type(), p, event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted() && event->type() == QEvent::MouseButtonPress) { decoration->client()->processDecorationButtonPress(&e); } if (event->type() == QEvent::MouseButtonRelease) { decoration->client()->processDecorationButtonRelease(&e); } return true; } default: break; } return false; } bool wheelEvent(QWheelEvent *event) override { auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } + if (event->angleDelta().y() != 0) { + // client window action only on vertical scrolling + const auto actionResult = performClientWheelAction(event, decoration->client()); + if (actionResult.first) { + return actionResult.second; + } + } const QPointF localPos = event->globalPosF() - decoration->client()->pos(); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta(), delta, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration.data(), &e); if (e.isAccepted()) { return true; } if ((orientation == Qt::Vertical) && decoration->client()->titlebarPositionUnderMouse()) { decoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1), event->globalPosF().toPoint()); } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } if (input()->touch()->decorationPressId() != -1) { // already on a decoration, ignore further touch points, but filter out return true; } seat->setTimestamp(time); input()->touch()->update(pos); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } input()->touch()->setDecorationPressId(id); m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted()) { decoration->client()->processDecorationButtonPress(&e); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint()); return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationButtonRelease(&e); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setDecorationPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; #ifdef KWIN_BUILD_TABBOX class TabBoxInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 button) override { Q_UNUSED(button) if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleMouseEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } auto seat = waylandServer()->seat(); seat->setFocusedKeyboardSurface(nullptr); // pass the key event to the seat, so that it has a proper model of the currently hold keys // this is important for combinations like alt+shift to ensure that shift is not considered pressed passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { TabBox::TabBox::self()->keyPress(event->modifiers() | event->key()); } else if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == Qt::NoModifier) { TabBox::TabBox::self()->modifiersReleased(); } return true; } bool wheelEvent(QWheelEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleWheelEvent(event); } }; #endif class ScreenEdgeInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) ScreenEdges::self()->isEntered(event); // always forward return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) // TODO: better check whether a touch sequence is in progess if (m_touchInProgress || waylandServer()->seat()->isTouchSequence()) { // cancel existing touch ScreenEdges::self()->gestureRecognizer()->cancelSwipeGesture(); m_touchInProgress = false; m_id = 0; return false; } if (ScreenEdges::self()->gestureRecognizer()->startSwipeGesture(pos) > 0) { m_touchInProgress = true; m_id = id; m_lastPos = pos; return true; } return false; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (m_touchInProgress && m_id == id) { ScreenEdges::self()->gestureRecognizer()->updateSwipeGesture(QSizeF(pos.x() - m_lastPos.x(), pos.y() - m_lastPos.y())); m_lastPos = pos; return true; } return false; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time) if (m_touchInProgress && m_id == id) { ScreenEdges::self()->gestureRecognizer()->endSwipeGesture(); m_touchInProgress = false; return true; } return false; } private: bool m_touchInProgress = false; quint32 m_id = 0; QPointF m_lastPos; }; /** * This filter implements window actions. If the event should not be passed to the * current pointer window it will filter out the event **/ class WindowActionInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (event->type() != QEvent::MouseButtonPress) { return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } - bool wasAction = false; - Options::MouseCommand command = Options::MouseNothing; - if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { - wasAction = true; - switch (event->button()) { - case Qt::LeftButton: - command = options->commandAll1(); - break; - case Qt::MiddleButton: - command = options->commandAll2(); - break; - case Qt::RightButton: - command = options->commandAll3(); - break; - default: - // nothing - break; - } - } else { - command = c->getMouseCommand(event->button(), &wasAction); - } - if (wasAction) { - return !c->performMouseCommand(command, event->globalPos()); + const auto actionResult = performClientMouseAction(event, c, MouseAction::ModifierAndWindow); + if (actionResult.first) { + return actionResult.second; } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->angleDelta().y() == 0) { // only actions on vertical scroll return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } - bool wasAction = false; - Options::MouseCommand command = Options::MouseNothing; - if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { - wasAction = true; - command = options->operationWindowMouseWheel(-1 * event->angleDelta().y()); - } else { - command = c->getWheelCommand(Qt::Vertical, &wasAction); - } - if (wasAction) { - return !c->performMouseCommand(command, event->globalPos()); + const auto actionResult = performClientWheelAction(event, c, MouseAction::ModifierAndWindow); + if (actionResult.first) { + return actionResult.second; } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(time) auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } input()->touch()->update(pos); AbstractClient *c = dynamic_cast(input()->touch()->window().data()); if (!c) { return false; } bool wasAction = false; const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction); if (wasAction) { return !c->performMouseCommand(command, pos.toPoint()); } return false; } }; /** * The remaining default input filter which forwards events to other windows **/ class ForwardInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); input()->pointer()->enablePointerConstraints(); } seat->setPointerPos(event->globalPos()); MouseEvent *e = static_cast(event); if (e->delta() != QSizeF()) { seat->relativePointerMotion(e->delta(), e->deltaUnaccelerated(), e->timestampMicroseconds()); } break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); if (event->buttons() == Qt::NoButton) { input()->pointer()->update(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); return true; } bool keyEvent(QKeyEvent *event) override { if (!workspace()) { return false; } if (event->isAutoRepeat()) { // handled by Wayland client return false; } auto seat = waylandServer()->seat(); input()->keyboard()->update(); seat->setTimestamp(event->timestamp()); passToWaylandServer(event); return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } input()->touch()->insertId(id, seat->touchDown(pos)); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } return true; } bool touchUp(quint32 id, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } return true; } bool pinchGestureBegin(int fingerCount, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->startPointerPinchGesture(fingerCount); return true; } bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->updatePointerPinchGesture(delta, scale, angleDelta); return true; } bool pinchGestureEnd(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->endPointerPinchGesture(); return true; } bool pinchGestureCancelled(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->cancelPointerPinchGesture(); return true; } bool swipeGestureBegin(int fingerCount, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->startPointerSwipeGesture(fingerCount); return true; } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->updatePointerSwipeGesture(delta); return true; } bool swipeGestureEnd(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->endPointerSwipeGesture(); return true; } bool swipeGestureCancelled(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->cancelPointerSwipeGesture(); return true; } }; class DragAndDropInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); if (!seat->isDragPointer()) { return false; } seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { if (Toplevel *t = input()->findToplevel(event->globalPos())) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->raiseClient(c); } seat->setPointerPos(event->globalPos()); seat->setDragTarget(t->surface(), event->globalPos(), t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } seat->setPointerPos(event->globalPos()); break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); break; default: break; } // TODO: should we pass through effects? return true; } }; KWIN_SINGLETON_FACTORY(InputRedirection) static const QString s_touchpadComponent = QStringLiteral("kcm_touchpad"); InputRedirection::InputRedirection(QObject *parent) : QObject(parent) , m_keyboard(new KeyboardInputRedirection(this)) , m_pointer(new PointerInputRedirection(this)) , m_touch(new TouchInputRedirection(this)) , m_shortcuts(new GlobalShortcutsManager(this)) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); #if HAVE_INPUT if (Application::usesLibinput()) { if (LogindIntegration::self()->hasSessionControl()) { setupLibInput(); } else { + LibInput::Connection::createThread(); if (LogindIntegration::self()->isConnected()) { LogindIntegration::self()->takeControl(); } else { connect(LogindIntegration::self(), &LogindIntegration::connectedChanged, LogindIntegration::self(), &LogindIntegration::takeControl); } connect(LogindIntegration::self(), &LogindIntegration::hasSessionControlChanged, this, [this] (bool sessionControl) { if (sessionControl) { setupLibInput(); } } ); } } #endif connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace); reconfigure(); } InputRedirection::~InputRedirection() { s_self = NULL; qDeleteAll(m_filters); qDeleteAll(m_spies); } void InputRedirection::installInputEventFilter(InputEventFilter *filter) { Q_ASSERT(!m_filters.contains(filter)); m_filters << filter; } void InputRedirection::prependInputEventFilter(InputEventFilter *filter) { Q_ASSERT(!m_filters.contains(filter)); m_filters.prepend(filter); } void InputRedirection::uninstallInputEventFilter(InputEventFilter *filter) { m_filters.removeOne(filter); } void InputRedirection::installInputEventSpy(InputEventSpy *spy) { m_spies << spy; } void InputRedirection::uninstallInputEventSpy(InputEventSpy *spy) { m_spies.removeOne(spy); } void InputRedirection::init() { m_shortcuts->init(); } void InputRedirection::setupWorkspace() { if (waylandServer()) { using namespace KWayland::Server; FakeInputInterface *fakeInput = waylandServer()->display()->createFakeInput(this); fakeInput->create(); connect(fakeInput, &FakeInputInterface::deviceCreated, this, [this] (FakeInputDevice *device) { connect(device, &FakeInputDevice::authenticationRequested, this, [this, device] (const QString &application, const QString &reason) { Q_UNUSED(application) Q_UNUSED(reason) // TODO: make secure device->setAuthentication(true); } ); connect(device, &FakeInputDevice::pointerMotionRequested, this, [this] (const QSizeF &delta) { // TODO: Fix time m_pointer->processMotion(globalPointer() + QPointF(delta.width(), delta.height()), 0); + waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonPressed, 0); + waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonReleased, 0); + waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerAxisRequested, this, [this] (Qt::Orientation orientation, qreal delta) { // TODO: Fix time InputRedirection::PointerAxis axis; switch (orientation) { case Qt::Horizontal: axis = InputRedirection::PointerAxisHorizontal; break; case Qt::Vertical: axis = InputRedirection::PointerAxisVertical; break; default: Q_UNREACHABLE(); break; } // TODO: Fix time m_pointer->processAxis(axis, delta, 0); + waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchDownRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processDown(id, pos, 0); + waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchMotionRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processMotion(id, pos, 0); + waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchUpRequested, this, [this] (quint32 id) { // TODO: Fix time m_touch->processUp(id, 0); + waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchCancelRequested, this, [this] () { m_touch->cancel(); } ); connect(device, &FakeInputDevice::touchFrameRequested, this, [this] () { m_touch->frame(); } ); } ); connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure); m_keyboard->init(); m_pointer->init(); m_touch->init(); } setupInputFilters(); } void InputRedirection::setupInputFilters() { #if HAVE_INPUT if (LogindIntegration::self()->hasSessionControl()) { installInputEventFilter(new VirtualTerminalFilter); } #endif if (waylandServer()) { installInputEventFilter(new TerminateServerFilter); installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); installInputEventFilter(new PopupInputFilter); m_pointerConstraintsFilter = new PointerConstraintsFilter; installInputEventFilter(m_pointerConstraintsFilter); m_windowSelector = new WindowSelectorFilter; installInputEventFilter(m_windowSelector); } installInputEventFilter(new ScreenEdgeInputFilter); installInputEventFilter(new EffectsFilter); installInputEventFilter(new MoveResizeFilter); #ifdef KWIN_BUILD_TABBOX installInputEventFilter(new TabBoxInputFilter); #endif installInputEventFilter(new GlobalShortcutFilter); installInputEventFilter(new InternalWindowEventFilter); installInputEventFilter(new DecorationEventFilter); if (waylandServer()) { installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new ForwardInputFilter); } } void InputRedirection::reconfigure() { #if HAVE_INPUT if (Application::usesLibinput()) { auto inputConfig = kwinApp()->inputConfig(); inputConfig->reparseConfiguration(); const auto config = inputConfig->group(QStringLiteral("keyboard")); const int delay = config.readEntry("RepeatDelay", 660); const int rate = config.readEntry("RepeatRate", 25); const bool enabled = config.readEntry("KeyboardRepeating", 0) == 0; waylandServer()->seat()->setKeyRepeatInfo(enabled ? rate : 0, delay); } #endif } static KWayland::Server::SeatInterface *findSeat() { auto server = waylandServer(); if (!server) { return nullptr; } return server->seat(); } void InputRedirection::setupLibInput() { #if HAVE_INPUT if (!Application::usesLibinput()) { return; } if (m_libInput) { return; } LibInput::Connection *conn = LibInput::Connection::create(this); m_libInput = conn; if (conn) { if (waylandServer()) { // create relative pointer manager waylandServer()->display()->createRelativePointerManager(KWayland::Server::RelativePointerInterfaceVersion::UnstableV1, waylandServer()->display())->create(); } conn->setInputConfig(kwinApp()->inputConfig()); conn->updateLEDs(m_keyboard->xkb()->leds()); - conn->setup(); connect(m_keyboard, &KeyboardInputRedirection::ledsChanged, conn, &LibInput::Connection::updateLEDs); connect(conn, &LibInput::Connection::eventsRead, this, [this] { m_libInput->processEvents(); }, Qt::QueuedConnection ); + conn->setup(); connect(conn, &LibInput::Connection::pointerButtonChanged, m_pointer, &PointerInputRedirection::processButton); connect(conn, &LibInput::Connection::pointerAxisChanged, m_pointer, &PointerInputRedirection::processAxis); connect(conn, &LibInput::Connection::pinchGestureBegin, m_pointer, &PointerInputRedirection::processPinchGestureBegin); connect(conn, &LibInput::Connection::pinchGestureUpdate, m_pointer, &PointerInputRedirection::processPinchGestureUpdate); connect(conn, &LibInput::Connection::pinchGestureEnd, m_pointer, &PointerInputRedirection::processPinchGestureEnd); connect(conn, &LibInput::Connection::pinchGestureCancelled, m_pointer, &PointerInputRedirection::processPinchGestureCancelled); connect(conn, &LibInput::Connection::swipeGestureBegin, m_pointer, &PointerInputRedirection::processSwipeGestureBegin); connect(conn, &LibInput::Connection::swipeGestureUpdate, m_pointer, &PointerInputRedirection::processSwipeGestureUpdate); connect(conn, &LibInput::Connection::swipeGestureEnd, m_pointer, &PointerInputRedirection::processSwipeGestureEnd); connect(conn, &LibInput::Connection::swipeGestureCancelled, m_pointer, &PointerInputRedirection::processSwipeGestureCancelled); connect(conn, &LibInput::Connection::keyChanged, m_keyboard, &KeyboardInputRedirection::processKey); connect(conn, &LibInput::Connection::pointerMotion, this, [this] (const QSizeF &delta, const QSizeF &deltaNonAccel, uint32_t time, quint64 timeMicroseconds, LibInput::Device *device) { m_pointer->processMotion(m_pointer->pos() + QPointF(delta.width(), delta.height()), delta, deltaNonAccel, time, timeMicroseconds, device); } ); connect(conn, &LibInput::Connection::pointerMotionAbsolute, this, [this] (QPointF orig, QPointF screen, uint32_t time, LibInput::Device *device) { Q_UNUSED(orig) m_pointer->processMotion(screen, time, device); } ); connect(conn, &LibInput::Connection::touchDown, m_touch, &TouchInputRedirection::processDown); connect(conn, &LibInput::Connection::touchUp, m_touch, &TouchInputRedirection::processUp); connect(conn, &LibInput::Connection::touchMotion, m_touch, &TouchInputRedirection::processMotion); connect(conn, &LibInput::Connection::touchCanceled, m_touch, &TouchInputRedirection::cancel); connect(conn, &LibInput::Connection::touchFrame, m_touch, &TouchInputRedirection::frame); + auto handleSwitchEvent = [this] (SwitchEvent::State state, quint32 time, quint64 timeMicroseconds, LibInput::Device *device) { + SwitchEvent event(state, time, timeMicroseconds, device); + processSpies(std::bind(&InputEventSpy::switchEvent, std::placeholders::_1, &event)); + processFilters(std::bind(&InputEventFilter::switchEvent, std::placeholders::_1, &event)); + }; + connect(conn, &LibInput::Connection::switchToggledOn, this, + std::bind(handleSwitchEvent, SwitchEvent::State::On, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + connect(conn, &LibInput::Connection::switchToggledOff, this, + std::bind(handleSwitchEvent, SwitchEvent::State::Off, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); if (screens()) { setupLibInputWithScreens(); } else { connect(kwinApp(), &Application::screensCreated, this, &InputRedirection::setupLibInputWithScreens); } if (auto s = findSeat()) { // Workaround for QTBUG-54371: if there is no real keyboard Qt doesn't request virtual keyboard s->setHasKeyboard(true); s->setHasPointer(conn->hasPointer()); s->setHasTouch(conn->hasTouch()); connect(conn, &LibInput::Connection::hasAlphaNumericKeyboardChanged, this, [this] (bool set) { if (m_libInput->isSuspended()) { return; } // TODO: this should update the seat, only workaround for QTBUG-54371 emit hasAlphaNumericKeyboardChanged(set); } ); + connect(conn, &LibInput::Connection::hasTabletModeSwitchChanged, this, + [this] (bool set) { + if (m_libInput->isSuspended()) { + return; + } + emit hasTabletModeSwitchChanged(set); + } + ); connect(conn, &LibInput::Connection::hasPointerChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasPointer(set); } ); connect(conn, &LibInput::Connection::hasTouchChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasTouch(set); } ); } connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, m_libInput, [this] (bool active) { if (!active) { m_libInput->deactivate(); } } ); } setupTouchpadShortcuts(); #endif } void InputRedirection::setupTouchpadShortcuts() { if (!m_libInput) { return; } #if HAVE_INPUT QAction *touchpadToggleAction = new QAction(this); QAction *touchpadOnAction = new QAction(this); QAction *touchpadOffAction = new QAction(this); touchpadToggleAction->setObjectName(QStringLiteral("Toggle Touchpad")); touchpadToggleAction->setProperty("componentName", s_touchpadComponent); touchpadOnAction->setObjectName(QStringLiteral("Enable Touchpad")); touchpadOnAction->setProperty("componentName", s_touchpadComponent); touchpadOffAction->setObjectName(QStringLiteral("Disable Touchpad")); touchpadOffAction->setProperty("componentName", s_touchpadComponent); KGlobalAccel::self()->setDefaultShortcut(touchpadToggleAction, QList{Qt::Key_TouchpadToggle}); KGlobalAccel::self()->setShortcut(touchpadToggleAction, QList{Qt::Key_TouchpadToggle}); KGlobalAccel::self()->setDefaultShortcut(touchpadOnAction, QList{Qt::Key_TouchpadOn}); KGlobalAccel::self()->setShortcut(touchpadOnAction, QList{Qt::Key_TouchpadOn}); KGlobalAccel::self()->setDefaultShortcut(touchpadOffAction, QList{Qt::Key_TouchpadOff}); KGlobalAccel::self()->setShortcut(touchpadOffAction, QList{Qt::Key_TouchpadOff}); #ifndef KWIN_BUILD_TESTING registerShortcut(Qt::Key_TouchpadToggle, touchpadToggleAction); registerShortcut(Qt::Key_TouchpadOn, touchpadOnAction); registerShortcut(Qt::Key_TouchpadOff, touchpadOffAction); #endif connect(touchpadToggleAction, &QAction::triggered, m_libInput, &LibInput::Connection::toggleTouchpads); connect(touchpadOnAction, &QAction::triggered, m_libInput, &LibInput::Connection::enableTouchpads); connect(touchpadOffAction, &QAction::triggered, m_libInput, &LibInput::Connection::disableTouchpads); #endif } bool InputRedirection::hasAlphaNumericKeyboard() { #if HAVE_INPUT if (m_libInput) { return m_libInput->hasAlphaNumericKeyboard(); } #endif return true; } +bool InputRedirection::hasTabletModeSwitch() +{ +#if HAVE_INPUT + if (m_libInput) { + return m_libInput->hasTabletModeSwitch(); + } +#endif + return false; +} + void InputRedirection::setupLibInputWithScreens() { #if HAVE_INPUT if (!screens() || !m_libInput) { return; } m_libInput->setScreenSize(screens()->size()); + m_libInput->updateScreens(); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); + connect(screens(), &Screens::changed, m_libInput, &LibInput::Connection::updateScreens); #endif } void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time) { m_pointer->processMotion(pos, time); } void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { m_pointer->processButton(button, state, time); } void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { m_pointer->processAxis(axis, delta, time); } void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { m_keyboard->processKey(key, state, time); } void InputRedirection::processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { m_keyboard->processModifiers(modsDepressed, modsLatched, modsLocked, group); } void InputRedirection::processKeymapChange(int fd, uint32_t size) { m_keyboard->processKeymapChange(fd, size); } void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time) { m_touch->processDown(id, pos, time); } void InputRedirection::processTouchUp(qint32 id, quint32 time) { m_touch->processUp(id, time); } void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time) { m_touch->processMotion(id, pos, time); } void InputRedirection::cancelTouch() { m_touch->cancel(); } void InputRedirection::touchFrame() { m_touch->frame(); } Qt::MouseButtons InputRedirection::qtButtonStates() const { return m_pointer->buttons(); } static bool acceptsInput(Toplevel *t, const QPoint &pos) { const QRegion input = t->inputShape(); if (input.isEmpty()) { return true; } return input.translated(t->pos()).contains(pos); } Toplevel *InputRedirection::findToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); // TODO: check whether the unmanaged wants input events at all if (!isScreenLocked) { // if an effect overrides the cursor we don't have a window to focus if (effects && static_cast(effects)->isMouseInterception()) { return nullptr; } const UnmanagedList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { if (u->geometry().contains(pos) && acceptsInput(u, pos)) { return u; } } } const ToplevelList &stacking = Workspace::self()->stackingOrder(); if (stacking.isEmpty()) { return NULL; } auto it = stacking.end(); do { --it; Toplevel *t = (*it); if (t->isDeleted()) { // a deleted window doesn't get mouse events continue; } if (AbstractClient *c = dynamic_cast(t)) { if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || !c->isCurrentTab() || c->isHiddenInternal()) { continue; } } if (!t->readyForPainting()) { continue; } if (isScreenLocked) { if (!t->isLockScreen() && !t->isInputMethod()) { continue; } } if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); return NULL; } Qt::KeyboardModifiers InputRedirection::keyboardModifiers() const { return m_keyboard->modifiers(); } Qt::KeyboardModifiers InputRedirection::modifiersRelevantForGlobalShortcuts() const { return m_keyboard->modifiersRelevantForGlobalShortcuts(); } void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { Q_UNUSED(shortcut) kwinApp()->platform()->setupActionForGlobalAccel(action); } void InputRedirection::registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) { m_shortcuts->registerPointerShortcut(action, modifiers, pointerButtons); } void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { m_shortcuts->registerAxisShortcut(action, modifiers, axis); } void InputRedirection::registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action) { m_shortcuts->registerTouchpadSwipe(action, direction); } void InputRedirection::registerGlobalAccel(KGlobalAccelInterface *interface) { m_shortcuts->setKGlobalAccelInterface(interface); } void InputRedirection::warpPointer(const QPointF &pos) { m_pointer->warp(pos); } bool InputRedirection::supportsPointerWarping() const { return m_pointer->supportsWarping(); } QPointF InputRedirection::globalPointer() const { return m_pointer->pos(); } void InputRedirection::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (!m_windowSelector || m_windowSelector->isActive()) { callback(nullptr); return; } m_windowSelector->start(callback); m_pointer->setWindowSelectionCursor(cursorName); } void InputRedirection::startInteractivePositionSelection(std::function callback) { if (!m_windowSelector || m_windowSelector->isActive()) { callback(QPoint(-1, -1)); return; } m_windowSelector->start(callback); m_pointer->setWindowSelectionCursor(QByteArray()); } bool InputRedirection::isSelectingWindow() const { return m_windowSelector ? m_windowSelector->isActive() : false; } bool InputRedirection::isBreakingPointerConstraints() const { return m_pointerConstraintsFilter ? m_pointerConstraintsFilter->isActive() : false; } InputDeviceHandler::InputDeviceHandler(InputRedirection *input) : QObject(input) , m_input(input) { } InputDeviceHandler::~InputDeviceHandler() = default; void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos) { const auto oldDeco = m_decoration; bool needsReset = waylandServer()->isScreenLocked(); if (AbstractClient *c = dynamic_cast(t)) { // check whether it's on a Decoration if (c->decoratedClient()) { const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos()); if (!clientRect.contains(pos.toPoint())) { m_decoration = c->decoratedClient(); } else { needsReset = true; } } else { needsReset = true; } } else { needsReset = true; } if (needsReset) { m_decoration.clear(); } bool leftSend = false; auto oldWindow = qobject_cast(m_window.data()); if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { leftSend = true; oldWindow->leaveEvent(); } if (oldDeco && oldDeco != m_decoration) { if (oldDeco->client() != t && !leftSend) { leftSend = true; oldDeco->client()->leaveEvent(); } // send leave QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); } if (m_decoration) { if (m_decoration->client() != oldWindow) { m_decoration->client()->enterEvent(pos.toPoint()); workspace()->updateFocusMousePosition(pos.toPoint()); } const QPointF p = pos - t->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); m_decoration->client()->processDecorationMove(p.toPoint(), pos.toPoint()); } } void InputDeviceHandler::updateInternalWindow(const QPointF &pos) { const auto oldInternalWindow = m_internalWindow; bool found = false; // TODO: screen locked check without going through wayland server bool needsReset = waylandServer()->isScreenLocked(); const auto &internalClients = waylandServer()->internalClients(); const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible()); if (!internalClients.isEmpty() && change) { auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if ((*it)->geometry().contains(pos.toPoint())) { // check input mask const QRegion mask = w->mask().translated(w->geometry().topLeft()); if (!mask.isEmpty() && !mask.contains(pos.toPoint())) { continue; } + if (w->property("outputOnly").toBool()) { + continue; + } m_internalWindow = QPointer(w); found = true; break; } } } while (it != internalClients.begin()); if (!found) { needsReset = true; } } if (needsReset) { m_internalWindow.clear(); } if (oldInternalWindow != m_internalWindow) { // changed if (oldInternalWindow) { QEvent event(QEvent::Leave); QCoreApplication::sendEvent(oldInternalWindow.data(), &event); } if (m_internalWindow) { QEnterEvent event(pos - m_internalWindow->position(), pos - m_internalWindow->position(), pos); QCoreApplication::sendEvent(m_internalWindow.data(), &event); } emit internalWindowChanged(); } } } // namespace diff --git a/input.h b/input.h index 9aa804721..49e1dece1 100644 --- a/input.h +++ b/input.h @@ -1,422 +1,427 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_INPUT_H #define KWIN_INPUT_H #include #include #include #include #include #include #include #include class KGlobalAccelInterface; class QKeySequence; class QMouseEvent; class QKeyEvent; class QWheelEvent; namespace KWin { class GlobalShortcutsManager; class Toplevel; class InputEventFilter; class InputEventSpy; class KeyboardInputRedirection; class PointerConstraintsFilter; class PointerInputRedirection; class TouchInputRedirection; class WindowSelectorFilter; +class SwitchEvent; namespace Decoration { class DecoratedClientImpl; } namespace LibInput { class Connection; } /** * @brief This class is responsible for redirecting incoming input to the surface which currently * has input or send enter/leave events. * * In addition input is intercepted before passed to the surfaces to have KWin internal areas * getting input first (e.g. screen edges) and filter the input event out if we currently have * a full input grab. * */ class KWIN_EXPORT InputRedirection : public QObject { Q_OBJECT public: enum PointerButtonState { PointerButtonReleased, PointerButtonPressed }; enum PointerAxis { PointerAxisVertical, PointerAxisHorizontal }; enum KeyboardKeyState { KeyboardKeyReleased, KeyboardKeyPressed, KeyboardKeyAutoRepeat }; virtual ~InputRedirection(); void init(); /** * @return const QPointF& The current global pointer position */ QPointF globalPointer() const; Qt::MouseButtons qtButtonStates() const; Qt::KeyboardModifiers keyboardModifiers() const; Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const; void registerShortcut(const QKeySequence &shortcut, QAction *action); /** * @overload * * Like registerShortcut, but also connects QAction::triggered to the @p slot on @p receiver. * It's recommended to use this method as it ensures that the X11 timestamp is updated prior * to the @p slot being invoked. If not using this overload it's required to ensure that * registerShortcut is called before connecting to QAction's triggered signal. **/ template void registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, Slot slot); void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action); void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action); void registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action); void registerGlobalAccel(KGlobalAccelInterface *interface); /** * @internal */ void processPointerMotion(const QPointF &pos, uint32_t time); /** * @internal */ void processPointerButton(uint32_t button, PointerButtonState state, uint32_t time); /** * @internal */ void processPointerAxis(PointerAxis axis, qreal delta, uint32_t time); /** * @internal */ void processKeyboardKey(uint32_t key, KeyboardKeyState state, uint32_t time); /** * @internal */ void processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); /** * @internal **/ void processKeymapChange(int fd, uint32_t size); void processTouchDown(qint32 id, const QPointF &pos, quint32 time); void processTouchUp(qint32 id, quint32 time); void processTouchMotion(qint32 id, const QPointF &pos, quint32 time); void cancelTouch(); void touchFrame(); bool supportsPointerWarping() const; void warpPointer(const QPointF &pos); /** * Adds the @p filter to the list of event filters and makes it the first * event filter in processing. * * Note: the event filter will get events before the lock screen can get them, thus * this is a security relevant method. **/ void prependInputEventFilter(InputEventFilter *filter); void uninstallInputEventFilter(InputEventFilter *filter); /** * Installs the @p spy for spying on events. **/ void installInputEventSpy(InputEventSpy *spy); /** * Uninstalls the @p spy. This happens automatically when deleting an InputEventSpy. **/ void uninstallInputEventSpy(InputEventSpy *spy); Toplevel *findToplevel(const QPoint &pos); GlobalShortcutsManager *shortcuts() const { return m_shortcuts; } /** * Sends an event through all InputFilters. * The method @p function is invoked on each input filter. Processing is stopped if * a filter returns @c true for @p function. * * The UnaryPredicate is defined like the UnaryPredicate of std::any_of. * The signature of the function should be equivalent to the following: * @code * bool function(const InputEventFilter *spy); * @endcode * * The intended usage is to std::bind the method to invoke on the filter with all arguments * bind. **/ template void processFilters(UnaryPredicate function) { std::any_of(m_filters.constBegin(), m_filters.constEnd(), function); } /** * Sends an event through all input event spies. * The @p function is invoked on each InputEventSpy. * * The UnaryFunction is defined like the UnaryFunction of std::for_each. * The signature of the function should be equivalent to the following: * @code * void function(const InputEventSpy *spy); * @endcode * * The intended usage is to std::bind the method to invoke on the spies with all arguments * bind. **/ template void processSpies(UnaryFunction function) { std::for_each(m_spies.constBegin(), m_spies.constEnd(), function); } KeyboardInputRedirection *keyboard() const { return m_keyboard; } PointerInputRedirection *pointer() const { return m_pointer; } TouchInputRedirection *touch() const { return m_touch; } bool hasAlphaNumericKeyboard(); + bool hasTabletModeSwitch(); void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName); void startInteractivePositionSelection(std::function callback); bool isSelectingWindow() const; bool isBreakingPointerConstraints() const; Q_SIGNALS: /** * @brief Emitted when the global pointer position changed * * @param pos The new global pointer position. */ void globalPointerChanged(const QPointF &pos); /** * @brief Emitted when the state of a pointer button changed. * * @param button The button which changed * @param state The new button state */ void pointerButtonStateChanged(uint32_t button, InputRedirection::PointerButtonState state); /** * @brief Emitted when a pointer axis changed * * @param axis The axis on which the even occurred * @param delta The delta of the event. */ void pointerAxisChanged(InputRedirection::PointerAxis axis, qreal delta); /** * @brief Emitted when the modifiers changes. * * Only emitted for the mask which is provided by Qt::KeyboardModifiers, if other modifiers * change signal is not emitted * * @param newMods The new modifiers state * @param oldMods The previous modifiers state */ void keyboardModifiersChanged(Qt::KeyboardModifiers newMods, Qt::KeyboardModifiers oldMods); /** * @brief Emitted when the state of a key changed. * * @param keyCode The keycode of the key which changed * @param oldMods The new key state */ void keyStateChanged(quint32 keyCode, InputRedirection::KeyboardKeyState state); void hasAlphaNumericKeyboardChanged(bool set); + void hasTabletModeSwitchChanged(bool set); private: void setupLibInput(); void setupTouchpadShortcuts(); void setupLibInputWithScreens(); void setupWorkspace(); void reconfigure(); void setupInputFilters(); void installInputEventFilter(InputEventFilter *filter); KeyboardInputRedirection *m_keyboard; PointerInputRedirection *m_pointer; TouchInputRedirection *m_touch; GlobalShortcutsManager *m_shortcuts; LibInput::Connection *m_libInput = nullptr; WindowSelectorFilter *m_windowSelector = nullptr; PointerConstraintsFilter *m_pointerConstraintsFilter = nullptr; QVector m_filters; QVector m_spies; KWIN_SINGLETON(InputRedirection) friend InputRedirection *input(); friend class DecorationEventFilter; friend class InternalWindowEventFilter; friend class ForwardInputFilter; }; /** * Base class for filtering input events inside InputRedirection. * * The idea behind the InputEventFilter is to have task oriented * filters. E.g. there is one filter taking care of a locked screen, * one to take care of interacting with window decorations, etc. * * A concrete subclass can reimplement the virtual methods and decide * whether an event should be filtered out or not by returning either * @c true or @c false. E.g. the lock screen filter can easily ensure * that all events are filtered out. * * As soon as a filter returns @c true the processing is stopped. If * a filter returns @c false the next one is invoked. This means a filter * installed early gets to see more events than a filter installed later on. * * Deleting an instance of InputEventFilter automatically uninstalls it from * InputRedirection. **/ class KWIN_EXPORT InputEventFilter { public: InputEventFilter(); virtual ~InputEventFilter(); /** * Event filter for pointer events which can be described by a QMouseEvent. * * Please note that the button translation in QMouseEvent cannot cover all * possible buttons. Because of that also the @p nativeButton code is passed * through the filter. For internal areas it's fine to use @p event, but for * passing to client windows the @p nativeButton should be used. * * @param event The event information about the move or button press/release * @param nativeButton The native key code of the button, for move events 0 * @return @c true to stop further event processing, @c false to pass to next filter **/ virtual bool pointerEvent(QMouseEvent *event, quint32 nativeButton); /** * Event filter for pointer axis events. * * @param event The event information about the axis event * @return @c true to stop further event processing, @c false to pass to next filter **/ virtual bool wheelEvent(QWheelEvent *event); /** * Event filter for keyboard events. * * @param event The event information about the key event * @return @c tru to stop further event processing, @c false to pass to next filter. **/ virtual bool keyEvent(QKeyEvent *event); virtual bool touchDown(quint32 id, const QPointF &pos, quint32 time); virtual bool touchMotion(quint32 id, const QPointF &pos, quint32 time); virtual bool touchUp(quint32 id, quint32 time); virtual bool pinchGestureBegin(int fingerCount, quint32 time); virtual bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time); virtual bool pinchGestureEnd(quint32 time); virtual bool pinchGestureCancelled(quint32 time); virtual bool swipeGestureBegin(int fingerCount, quint32 time); virtual bool swipeGestureUpdate(const QSizeF &delta, quint32 time); virtual bool swipeGestureEnd(quint32 time); virtual bool swipeGestureCancelled(quint32 time); + virtual bool switchEvent(SwitchEvent *event); + protected: void passToWaylandServer(QKeyEvent *event); }; class InputDeviceHandler : public QObject { Q_OBJECT public: virtual ~InputDeviceHandler(); QPointer window() const { return m_window; } QPointer decoration() const { return m_decoration; } QPointer internalWindow() const { return m_internalWindow; } Q_SIGNALS: void decorationChanged(); void internalWindowChanged(); protected: explicit InputDeviceHandler(InputRedirection *parent); void updateDecoration(Toplevel *t, const QPointF &pos); void updateInternalWindow(const QPointF &pos); InputRedirection *m_input; /** * @brief The Toplevel which currently receives events */ QPointer m_window; /** * @brief The Decoration which currently receives events. **/ QPointer m_decoration; QPointer m_internalWindow; }; inline InputRedirection *input() { return InputRedirection::s_self; } template inline void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, Slot slot) { registerShortcut(shortcut, action); connect(action, &QAction::triggered, receiver, slot); } } // namespace KWin Q_DECLARE_METATYPE(KWin::InputRedirection::KeyboardKeyState) Q_DECLARE_METATYPE(KWin::InputRedirection::PointerButtonState) Q_DECLARE_METATYPE(KWin::InputRedirection::PointerAxis) #endif // KWIN_INPUT_H diff --git a/input_event.cpp b/input_event.cpp index 8de428459..b8a2d7c97 100644 --- a/input_event.cpp +++ b/input_event.cpp @@ -1,54 +1,63 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "input_event.h" namespace KWin { MouseEvent::MouseEvent(QEvent::Type type, const QPointF &pos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, quint32 timestamp, const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestampMicroseconds, LibInput::Device *device) : QMouseEvent(type, pos, pos, button, buttons, modifiers) , m_delta(delta) , m_deltaUnccelerated(deltaNonAccelerated) , m_timestampMicroseconds(timestampMicroseconds) , m_device(device) { setTimestamp(timestamp); } WheelEvent::WheelEvent(const QPointF &pos, qreal delta, Qt::Orientation orientation, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, quint32 timestamp, LibInput::Device *device) : QWheelEvent(pos, pos, QPoint(), (orientation == Qt::Horizontal) ? QPoint(delta, 0) : QPoint(0, delta), delta, orientation, buttons, modifiers) , m_device(device) { setTimestamp(timestamp); } KeyEvent::KeyEvent(QEvent::Type type, Qt::Key key, Qt::KeyboardModifiers modifiers, quint32 code, quint32 keysym, const QString &text, bool autorepeat, quint32 timestamp, LibInput::Device *device) : QKeyEvent(type, key, modifiers, code, keysym, 0, text, autorepeat) , m_device(device) { setTimestamp(timestamp); } +SwitchEvent::SwitchEvent(State state, quint32 timestamp, quint64 timestampMicroseconds, LibInput::Device* device) + : QInputEvent(QEvent::User) + , m_state(state) + , m_timestampMicroseconds(timestampMicroseconds) + , m_device(device) +{ + setTimestamp(timestamp); +} + } diff --git a/input_event.h b/input_event.h index 548fcfd7f..7854a1088 100644 --- a/input_event.h +++ b/input_event.h @@ -1,129 +1,156 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_INPUT_EVENT_H #define KWIN_INPUT_EVENT_H #include namespace KWin { namespace LibInput { class Device; } class MouseEvent : public QMouseEvent { public: explicit MouseEvent(QEvent::Type type, const QPointF &pos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, quint32 timestamp, const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestampMicroseconds, LibInput::Device *device); QSizeF delta() const { return m_delta; } QSizeF deltaUnaccelerated() const { return m_deltaUnccelerated; } quint64 timestampMicroseconds() const { return m_timestampMicroseconds; } LibInput::Device *device() const { return m_device; } Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const { return m_modifiersRelevantForShortcuts; } void setModifiersRelevantForGlobalShortcuts(const Qt::KeyboardModifiers &mods) { m_modifiersRelevantForShortcuts = mods; } quint32 nativeButton() const { return m_nativeButton; } void setNativeButton(quint32 button) { m_nativeButton = button; } private: QSizeF m_delta; QSizeF m_deltaUnccelerated; quint64 m_timestampMicroseconds; LibInput::Device *m_device; Qt::KeyboardModifiers m_modifiersRelevantForShortcuts = Qt::KeyboardModifiers(); quint32 m_nativeButton = 0; }; class WheelEvent : public QWheelEvent { public: explicit WheelEvent(const QPointF &pos, qreal delta, Qt::Orientation orientation, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, quint32 timestamp, LibInput::Device *device); LibInput::Device *device() const { return m_device; } Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const { return m_modifiersRelevantForShortcuts; } void setModifiersRelevantForGlobalShortcuts(const Qt::KeyboardModifiers &mods) { m_modifiersRelevantForShortcuts = mods; } private: LibInput::Device *m_device; Qt::KeyboardModifiers m_modifiersRelevantForShortcuts = Qt::KeyboardModifiers(); }; class KeyEvent : public QKeyEvent { public: explicit KeyEvent(QEvent::Type type, Qt::Key key, Qt::KeyboardModifiers modifiers, quint32 code, quint32 keysym, const QString &text, bool autorepeat, quint32 timestamp, LibInput::Device *device); LibInput::Device *device() const { return m_device; } Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const { return m_modifiersRelevantForShortcuts; } void setModifiersRelevantForGlobalShortcuts(const Qt::KeyboardModifiers &mods) { m_modifiersRelevantForShortcuts = mods; } private: LibInput::Device *m_device; Qt::KeyboardModifiers m_modifiersRelevantForShortcuts = Qt::KeyboardModifiers(); }; +class SwitchEvent : public QInputEvent +{ +public: + enum class State { + Off, + On + }; + explicit SwitchEvent(State state, quint32 timestamp, quint64 timestampMicroseconds, LibInput::Device *device); + + State state() const { + return m_state; + } + + quint64 timestampMicroseconds() const { + return m_timestampMicroseconds; + } + + LibInput::Device *device() const { + return m_device; + } + +private: + State m_state; + quint64 m_timestampMicroseconds; + LibInput::Device *m_device; +}; + } #endif diff --git a/input_event_spy.cpp b/input_event_spy.cpp index a7538ab3b..bb139c057 100644 --- a/input_event_spy.cpp +++ b/input_event_spy.cpp @@ -1,119 +1,124 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "input_event_spy.h" #include "input.h" #include #include namespace KWin { InputEventSpy::InputEventSpy() = default; InputEventSpy::~InputEventSpy() { if (input()) { input()->uninstallInputEventSpy(this); } } void InputEventSpy::pointerEvent(MouseEvent *event) { Q_UNUSED(event) } void InputEventSpy::wheelEvent(WheelEvent *event) { Q_UNUSED(event) } void InputEventSpy::keyEvent(KeyEvent *event) { Q_UNUSED(event) } void InputEventSpy::touchDown(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) } void InputEventSpy::touchMotion(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) } void InputEventSpy::touchUp(quint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) } void InputEventSpy::pinchGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) } void InputEventSpy::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) } void InputEventSpy::pinchGestureEnd(quint32 time) { Q_UNUSED(time) } void InputEventSpy::pinchGestureCancelled(quint32 time) { Q_UNUSED(time) } void InputEventSpy::swipeGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) } void InputEventSpy::swipeGestureUpdate(const QSizeF &delta, quint32 time) { Q_UNUSED(delta) Q_UNUSED(time) } void InputEventSpy::swipeGestureEnd(quint32 time) { Q_UNUSED(time) } void InputEventSpy::swipeGestureCancelled(quint32 time) { Q_UNUSED(time) } +void InputEventSpy::switchEvent(SwitchEvent *event) +{ + Q_UNUSED(event) +} + } diff --git a/input_event_spy.h b/input_event_spy.h index 843e148ff..32ba36ba3 100644 --- a/input_event_spy.h +++ b/input_event_spy.h @@ -1,89 +1,92 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_INPUT_EVENT_SPY_H #define KWIN_INPUT_EVENT_SPY_H #include #include class QPointF; class QSizeF; namespace KWin { class KeyEvent; class MouseEvent; class WheelEvent; +class SwitchEvent; /** * Base class for spying on input events inside InputRedirection. * * This class is quite similar to InputEventFilter, except that it does not * support event filtering. Each InputEventSpy gets to see all input events, * the processing happens prior to sending events through the InputEventFilters. * * Deleting an instance of InputEventSpy automatically uninstalls it from * InputRedirection. **/ class KWIN_EXPORT InputEventSpy { public: InputEventSpy(); virtual ~InputEventSpy(); /** * Event spy for pointer events which can be described by a MouseEvent. * * @param event The event information about the move or button press/release **/ virtual void pointerEvent(MouseEvent *event); /** * Event spy for pointer axis events. * * @param event The event information about the axis event **/ virtual void wheelEvent(WheelEvent *event); /** * Event spy for keyboard events. * * @param event The event information about the key event **/ virtual void keyEvent(KeyEvent *event); virtual void touchDown(quint32 id, const QPointF &pos, quint32 time); virtual void touchMotion(quint32 id, const QPointF &pos, quint32 time); virtual void touchUp(quint32 id, quint32 time); virtual void pinchGestureBegin(int fingerCount, quint32 time); virtual void pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time); virtual void pinchGestureEnd(quint32 time); virtual void pinchGestureCancelled(quint32 time); virtual void swipeGestureBegin(int fingerCount, quint32 time); virtual void swipeGestureUpdate(const QSizeF &delta, quint32 time); virtual void swipeGestureEnd(quint32 time); virtual void swipeGestureCancelled(quint32 time); + virtual void switchEvent(SwitchEvent *event); + }; } // namespace KWin #endif diff --git a/kcmkwin/kwincompositing/.reviewboardrc b/kcmkwin/kwincompositing/.reviewboardrc deleted file mode 100644 index 6b1c452e1..000000000 --- a/kcmkwin/kwincompositing/.reviewboardrc +++ /dev/null @@ -1,2 +0,0 @@ -REVIEWBOARD_URL = "https://git.reviewboard.kde.org" -REPOSITORY = 'git://anongit.kde.org/kwin-compositing-kcm' diff --git a/kcmkwin/kwincompositing/kwincompositing.desktop b/kcmkwin/kwincompositing/kwincompositing.desktop index 2e346a265..bfc42fa6a 100644 --- a/kcmkwin/kwincompositing/kwincompositing.desktop +++ b/kcmkwin/kwincompositing/kwincompositing.desktop @@ -1,136 +1,136 @@ [Desktop Entry] Exec=kcmshell5 kwincompositing Icon=preferences-desktop Type=Service X-KDE-ServiceTypes=KCModule X-DocPath=https://userbase.kde.org/Desktop_Effects_Performance#Advanced_Desktop_Effects_Settings X-KDE-Library=kwincompositing X-KDE-PluginKeyword=compositing X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=display X-KDE-Weight=60 Name=Compositor Name[bs]=Compositor Name[ca]=Compositor Name[ca@valencia]=Compositor Name[cs]=Kompozitor Name[da]=Compositor Name[de]=Compositor Name[el]=Συνθέτης Name[en_GB]=Compositor Name[es]=Compositor Name[et]=Komposiitor Name[eu]=Konposatzailea Name[fi]=Koostin Name[fr]=Compositeur Name[gl]=Compositor Name[hu]=Összeállító Name[ia]=Compositor Name[id]=Kompositor Name[it]=Compositore Name[ko]=컴포지터 Name[lt]=Kompozitorius Name[nb]=Sammensetter Name[nds]=Tosamensetten Name[nl]=Compositor Name[nn]=Samansetjar Name[pa]=ਕੰਪੋਜੀਟਰ Name[pl]=Kompozytor Name[pt]=Compositor Name[pt_BR]=Compositor Name[ro]=Compozitor Name[ru]=Обеспечение эффектов Name[sk]=Kompozítor Name[sl]=Upravljalnik skladnje Name[sr]=Слагач Name[sr@ijekavian]=Слагач Name[sr@ijekavianlatin]=Slagač Name[sr@latin]=Slagač Name[sv]=Sammansättning -Name[tr]=Birleştirici +Name[tr]=Dizgici Name[uk]=Засіб композиції Name[vi]=Compositor Name[x-test]=xxCompositorxx Name[zh_CN]=混成器 Name[zh_TW]=組合器 Comment=Compositor Settings for Desktop Effects Comment[bs]=Postavke Compositor-a za Desktop Efekte Comment[ca]=Arranjament del compositor pels efectes d'escriptori Comment[ca@valencia]=Arranjament del compositor pels efectes d'escriptori Comment[cs]=Nastavení kompozitoru pro efekty pracovní plochy Comment[da]=Compositor-indstillinger til skrivebordseffekter Comment[de]=Compositor-Einstellungen für Arbeitsflächen-Effekte Comment[el]=Ρυθμίσεις συνθέτη για τα εφέ επιφάνειας εργασίας Comment[en_GB]=Compositor Settings for Desktop Effects Comment[es]=Configurar las preferencias del compositor para los efectos del escritorio Comment[et]=Komposiitori seadistused töölauaefektide tarbeks Comment[eu]=Konposatzailearen ezarpenak mahaigaineko efektuetarako Comment[fi]=Koostimen asetukset työpöytätehosteita varten Comment[fr]=Paramétrage du compositeur pour les effets de bureau Comment[gl]=Configuración do compositor para os efectos de escritorio Comment[hu]=A kompozitor beállításai az asztali effektusokhoz Comment[ia]=Preferentias de compositor pro le effectos de scriptorio -Comment[id]=Pengaturan Kompositor untuk Efek Jendela +Comment[id]=Setelan Kompositor untuk Efek Jendela Comment[it]=Impostazioni del compositore per gli effetti del desktop Comment[ko]=데스크톱 효과에 사용되는 컴포지터 설정 Comment[lt]=Kompozitoriaus nustatymai skirti darbalaukio efektams Comment[nb]=Sammensetter-innstillinger for skrivebordseffekter Comment[nds]=Tosamensettoptschonen för de Schriefdischeffekten instellen Comment[nl]=Instellingen van compositor configureren voor bureaubladeffecten Comment[nn]=Samansetjarinnstillingar for skrivebordseffekter Comment[pa]=ਡੈਸਕਟਾਪ ਪਰਭਾਵ ਲਈ ਕੰਪੋਜੀਟਰ ਸੈਟਿੰਗਾਂ Comment[pl]=Ustawienia kompozytora dla efektów pulpitu Comment[pt]=Configuração do Compositor para os Efeitos do Ecrã Comment[pt_BR]=Definições do Compositor para os efeitos da área de trabalho Comment[ru]=Настройка движка эффектов рабочего стола Comment[sk]=Nastavenia kompozítora pre efekty plochy Comment[sl]=Nastavitve upravljalnika skladnje za učinke namizja Comment[sr]=Поставке слагача за ефекте површи Comment[sr@ijekavian]=Поставке слагача за ефекте површи Comment[sr@ijekavianlatin]=Postavke slagača za efekte površi Comment[sr@latin]=Postavke slagača za efekte površi Comment[sv]=Sammansättningsinställningar för skrivbordseffekter -Comment[tr]=Masaüstü Efektleri için Birleştirici Ayarları +Comment[tr]=Masaüstü Efektleri için Dizgici Ayarları Comment[uk]=Параметри засобу композиції для ефектів стільниці Comment[vi]=Thiết lập Compositor cho hiệu ứng màn hình Comment[x-test]=xxCompositor Settings for Desktop Effectsxx Comment[zh_CN]=桌面特效混成器设置 Comment[zh_TW]=桌面效果使用的組合器設定 X-KDE-Keywords=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed X-KDE-Keywords[ca]=kwin,finestra,gestor,composició,efecte,efectes 3D,efectes 2D,OpenGL,XRender,arranjament de vídeo,efectes gràfics,efectes d'escriptori,velocitat de les animacions X-KDE-Keywords[ca@valencia]=kwin,finestra,gestor,composició,efecte,efectes 3D,efectes 2D,OpenGL,XRender,arranjament de vídeo,efectes gràfics,efectes d'escriptori,velocitat de les animacions X-KDE-Keywords[da]=kwin,vindue,håndtering,window,manager,compositing,effekt,3D effekter,2D effekter,OpenGL,XRender,video-indstillinger,grafiske effekter,skrivebordseffekter,desktop effects,animationshastighed X-KDE-Keywords[de]=kwin,Fenstermanager,Fensterverwaltung,Effekt,Fenster,Compositing,3D-Effekte,2D-Effekte,OpenGL,XRender,Video-Einstellungen,Grafikeffekte,Arbeitsflächeneffekte,Animationsgeschwindigkeit X-KDE-Keywords[el]=kwin,παράθυρο,διαχειριστής,σύνθεση,εφέ,3D εφέ,2D εφέ,OpenGL,XRender,ρυθμίσεις βίντεο,γραφικά εφέ,εφέ επιφάνειας εργασίας,ταχύτητα κίνησης X-KDE-Keywords[en_GB]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed X-KDE-Keywords[es]=kwin,ventana,gestor,composición,efecto,efectos 3D,efectos 2D,OpenGL,XRender,preferencias de vídeo,efectos gráficos,efectos del escritorio,velocidad de animación X-KDE-Keywords[et]=kwin,aken,haldur,komposiit,komposiitor,efekt,3D efektid,ruumilised efektid,2D efektid,OpenGL,XRender,videoseadistused,graafilised efektid,töölauaefektid,animatsiooni kiirus X-KDE-Keywords[eu]=kwin,leiho,kudeatzaile,konposatzaile,efektu,3D efektuak,2D efektuak,OpenGL,XRender,bideo ezarpenak,efektu grafikoak,mahiagaineko efektuak,animazioaren abiadura X-KDE-Keywords[fi]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,ikkunointiohjelma,ikkunaohjelma,ikkunanhallinta,tehoste,3D-tehosteet,2D-tehosteet,videoasetukset,graafiset tehosteet,työpöytätehosteet,animointinopeus,animaationopeus X-KDE-Keywords[fr]=kwin, fenêtre, gestionnaire, compositeur, effet, effets 3D, effets 2D, OpenGL, XRender, paramètres vidéo, effets graphiques, effets de bureau, vitesse d'animation X-KDE-Keywords[gl]=kwin,xanela,xestor,composición,efecto,3D efectos,2D efectos,OpenGL,XRender,configuración da imaxe,efectos gráficos,efectos do escritorio,animation speed,velocidade das animacións X-KDE-Keywords[hu]=kwin,ablak,kezelő,összeállítás,hatás,3D hatások,2D hatások,OpenGL,XRender,videobeállítások,grafikus hatások,asztali hatások,animációsebesség X-KDE-Keywords[it]=kwin,finestra,gestore,composizione,effetto,effetti 3D,effetti 2D,OpenGL,XRender,impostazioni video,effetti grafici,effetti del desktop,velocità delle animazioni X-KDE-Keywords[ko]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed,3D 효과,2D 효과,비디오 설정,그래픽 설정,그래픽 효과,데스크톱 효과,애니메이션 속도 X-KDE-Keywords[nl]=kwin,window,manager,beheerder,compositing,effecten,3D-effecten,2D-effecten,OpenGL,XRender,video-instellingen,grafische effecten,bureaubladeffecten,animatiesneleheid X-KDE-Keywords[nn]=kwin,vindauge,vindaugshandsamar,samansetjing,effekt,3D-effektar,2D-effektar,OpenGL,XRender,videoinnstillingar,grafiske effektar,skrivebordseffekter,animasjonsfart X-KDE-Keywords[pl]=kwin,okno,menadżer,menedżer,zarządca,kompozytor,efekt,efekt 3D,efekt 2D,OpenGL,XRender,ustawienia wideo,efekty graficzne,efekty pulpitu,szybkość animacji X-KDE-Keywords[pt]=kwin,janela,gestor,composição,efeito,efeitos 3D,efeitos 2D,OpenGL,XRender,configuração do vídeo,efeitos gráficos,efeitos do ecrã,velocidade da animação X-KDE-Keywords[pt_BR]=kwin,janela,gerenciador,composição,efeito,efeitos 3D,efeitos 2D,OpenGL,XRender,configurações de vídeo,efeitos gráficos,efeitos da área de trabalho,velocidade da animação X-KDE-Keywords[ru]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,композитинг,композитный диспетчер окон,эффекты рабочего стола,графические эффекты,рендеринг,параметры видео,настройка видео,скорость анимации X-KDE-Keywords[sk]=kwin,okno,správca,kompozícia,efekt,3D efekty,2D efekty,OpenGL,XRender,nastavenia videa,grafické efekty,efekty plochy X-KDE-Keywords[sl]=kwin,okna,upravljalnik,skladnja,učinek,3D učinki,2D učinki,OpenGL,XRender,nastavitve videa,video,grafični učinki,učinki namizja,hitrost animacije X-KDE-Keywords[sr]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed,К‑вин,прозор,менаџер,слагање,ефекти,3Д ефекти,2Д ефекти,опенГЛ,Икс‑рендер,видео поставке,графички ефекти,ефекти површи,брзина анимације X-KDE-Keywords[sr@ijekavian]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed,К‑вин,прозор,менаџер,слагање,ефекти,3Д ефекти,2Д ефекти,опенГЛ,Икс‑рендер,видео поставке,графички ефекти,ефекти површи,брзина анимације X-KDE-Keywords[sr@ijekavianlatin]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed,KWin,prozor,menadžer,slaganje,efekti,3D efekti,2D efekti,OpenGL,XRender,video postavke,grafički efekti,efekti površi,brzina animacije X-KDE-Keywords[sr@latin]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed,KWin,prozor,menadžer,slaganje,efekti,3D efekti,2D efekti,OpenGL,XRender,video postavke,grafički efekti,efekti površi,brzina animacije X-KDE-Keywords[sv]=kwin,fönster,hantering,sammansättning,effekt,3D effekter,2D effekter,OpenGL,XRender,videoinställningar,grafiska effekter,skrivbordseffekter,animeringshastighet X-KDE-Keywords[tr]=kwin,pencere,yönetici,birleştirme,efekt,3D efektler,2D efektler,OpenGL,XRender,görüntü ayarları,grafiksel efektler,masaüstü efektleri,animasyon hızı X-KDE-Keywords[uk]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed,вікно,керування,композитне,композиція,ефект,просторовий,ефекти,плоскі,параметри відео,графічні ефекти,ефекти стільниці,швидкість анімації X-KDE-Keywords[x-test]=xxkwinxx,xxwindowxx,xxmanagerxx,xxcompositingxx,xxeffectxx,xx3D effectsxx,xx2D effectsxx,xxOpenGLxx,xxXRenderxx,xxvideo settingsxx,xxgraphical effectsxx,xxdesktop effectsxx,xxanimation speedxx X-KDE-Keywords[zh_CN]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed,窗口,管理器,混成,特效,3D 特效,2D 特效,视频设置,图形特效,桌面特效,动画速度 X-KDE-Keywords[zh_TW]=kwin,window,manager,compositing,effect,3D effects,2D effects,OpenGL,XRender,video settings,graphical effects,desktop effects,animation speed diff --git a/kcmkwin/kwincompositing/kwineffect.knsrc b/kcmkwin/kwincompositing/kwineffect.knsrc index 15402281d..4d80e93e7 100644 --- a/kcmkwin/kwincompositing/kwineffect.knsrc +++ b/kcmkwin/kwincompositing/kwineffect.knsrc @@ -1,44 +1,45 @@ [KNewStuff3] Name=Window Manager Effects Name[ca]=Efectes del gestor de finestres Name[ca@valencia]=Efectes del gestor de finestres Name[cs]=Efekty správce oken Name[da]=Vindueshåndteringseffekter Name[de]=Fensterverwaltungs-Effekte Name[el]=Εφέ διαχειριστή παραθύρων Name[en_GB]=Window Manager Effects Name[es]=Efectos del gestor de ventanas Name[eu]=Leiho kudeatzailearen efektua Name[fi]=Ikkunaohjelman tehosteet Name[fr]=Effets du gestionnaire de fenêtres Name[gl]=Efectos do xestor de xanelas Name[he]=אפקטי מנהל חלונות Name[hu]=Ablakkezelő-effektusok Name[ia]=Gerente de effectos de fenestra Name[it]=Effetti del gestore delle finestre Name[ko]=창 관리자 효과 Name[lt]=Langų tvarkyklės efektai Name[nl]=Effecten van vensterbeheerder Name[nn]=Effektar for vindaugshandsamar Name[pa]=ਵਿੰਡੋ ਮੈਨੇਜਰ ਪਰਭਾਵ Name[pl]=Efekty zarządzania oknami Name[pt]=Efeitos do Gestor de Janelas Name[pt_BR]=Efeitos do gerenciador de janelas +Name[ru]=Эффекты диспетчера окон KWin Name[sk]=Efekty správcu okien Name[sl]=Učinki upravljalnika oken Name[sr]=Ефекти менаџера прозора Name[sr@ijekavian]=Ефекти менаџера прозора Name[sr@ijekavianlatin]=Efekti menadžera prozora Name[sr@latin]=Efekti menadžera prozora Name[sv]=Fönsterhanteringseffekter Name[tr]=Pencere Yöneticisi Efektleri Name[uk]=Ефекти засобу керування вікнами Name[x-test]=xxWindow Manager Effectsxx Name[zh_CN]=窗口管理器效果 Name[zh_TW]=視窗管理員效果 ProvidersUrl=https://download.kde.org/ocs/providers.xml Categories=KWin Effects StandardResource=tmp InstallationCommand=plasmapkg2 -t kwineffect -i %f UninstallCommand=plasmapkg2 -t kwineffect -r %f diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp b/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp index 7d7c413c2..c5856ebd5 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp +++ b/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp @@ -1,479 +1,465 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "previewclient.h" #include #include -#include -#include - #include #include #include #include namespace KDecoration2 { namespace Preview { PreviewClient::PreviewClient(DecoratedClient *c, Decoration *decoration) : QObject(decoration) , ApplicationMenuEnabledDecoratedClientPrivate(c, decoration) - , m_colorSchemeManager(new KColorSchemeManager(this)) - , m_colorSchemeIndex(0) , m_icon(QIcon::fromTheme(QStringLiteral("start-here-kde"))) , m_iconName(m_icon.name()) , m_palette(QStringLiteral("kdeglobals")) , m_active(true) , m_closeable(true) , m_keepBelow(false) , m_keepAbove(false) , m_maximizable(true) , m_maximizedHorizontally(false) , m_maximizedVertically(false) , m_minimizable(true) , m_modal(false) , m_movable(true) , m_resizable(true) , m_shadeable(true) , m_shaded(false) , m_providesContextHelp(false) , m_desktop(1) , m_width(0) , m_height(0) , m_bordersTopEdge(false) , m_bordersLeftEdge(false) , m_bordersRightEdge(false) , m_bordersBottomEdge(false) { connect(this, &PreviewClient::captionChanged, c, &DecoratedClient::captionChanged); connect(this, &PreviewClient::activeChanged, c, &DecoratedClient::activeChanged); connect(this, &PreviewClient::closeableChanged, c, &DecoratedClient::closeableChanged); connect(this, &PreviewClient::keepAboveChanged, c, &DecoratedClient::keepAboveChanged); connect(this, &PreviewClient::keepBelowChanged, c, &DecoratedClient::keepBelowChanged); connect(this, &PreviewClient::maximizableChanged, c, &DecoratedClient::maximizeableChanged); connect(this, &PreviewClient::maximizedChanged, c, &DecoratedClient::maximizedChanged); connect(this, &PreviewClient::maximizedVerticallyChanged, c, &DecoratedClient::maximizedVerticallyChanged); connect(this, &PreviewClient::maximizedHorizontallyChanged, c, &DecoratedClient::maximizedHorizontallyChanged); connect(this, &PreviewClient::minimizableChanged, c, &DecoratedClient::minimizeableChanged); // connect(this, &PreviewClient::modalChanged, c, &DecoratedClient::modalChanged); connect(this, &PreviewClient::movableChanged, c, &DecoratedClient::moveableChanged); connect(this, &PreviewClient::onAllDesktopsChanged, c, &DecoratedClient::onAllDesktopsChanged); connect(this, &PreviewClient::resizableChanged, c, &DecoratedClient::resizeableChanged); connect(this, &PreviewClient::shadeableChanged, c, &DecoratedClient::shadeableChanged); connect(this, &PreviewClient::shadedChanged, c, &DecoratedClient::shadedChanged); connect(this, &PreviewClient::providesContextHelpChanged, c, &DecoratedClient::providesContextHelpChanged); connect(this, &PreviewClient::onAllDesktopsChanged, c, &DecoratedClient::onAllDesktopsChanged); connect(this, &PreviewClient::widthChanged, c, &DecoratedClient::widthChanged); connect(this, &PreviewClient::heightChanged, c, &DecoratedClient::heightChanged); connect(this, &PreviewClient::iconChanged, c, &DecoratedClient::iconChanged); connect(this, &PreviewClient::paletteChanged, c, &DecoratedClient::paletteChanged); // connect(this, &PreviewClient::, c, &DecoratedClient::); connect(this, &PreviewClient::maximizedVerticallyChanged, this, [this]() { emit maximizedChanged(isMaximized()); } ); connect(this, &PreviewClient::maximizedHorizontallyChanged, this, [this]() { emit maximizedChanged(isMaximized()); } ); connect(this, &PreviewClient::iconNameChanged, this, [this]() { m_icon = QIcon::fromTheme(m_iconName); emit iconChanged(m_icon); } ); connect(this, &PreviewClient::desktopChanged, this, [this]() { emit onAllDesktopsChanged(isOnAllDesktops()); } ); connect(&m_palette, &KWin::Decoration::DecorationPalette::changed, [this]() { emit paletteChanged(m_palette.palette()); }); auto emitEdgesChanged = [this, c]() { c->adjacentScreenEdgesChanged(adjacentScreenEdges()); }; connect(this, &PreviewClient::bordersTopEdgeChanged, this, emitEdgesChanged); connect(this, &PreviewClient::bordersLeftEdgeChanged, this, emitEdgesChanged); connect(this, &PreviewClient::bordersRightEdgeChanged, this, emitEdgesChanged); connect(this, &PreviewClient::bordersBottomEdgeChanged, this, emitEdgesChanged); qApp->installEventFilter(this); } PreviewClient::~PreviewClient() = default; void PreviewClient::setIcon(const QIcon &pixmap) { m_icon = pixmap; emit iconChanged(m_icon); } int PreviewClient::width() const { return m_width; } int PreviewClient::height() const { return m_height; } QString PreviewClient::caption() const { return m_caption; } WId PreviewClient::decorationId() const { return 0; } int PreviewClient::desktop() const { return m_desktop; } void PreviewClient::setDesktop(int desktop) { if (desktop == 0) { desktop = 1; } if (m_desktop == desktop) { return; } m_desktop = desktop; emit desktopChanged(m_desktop); } QIcon PreviewClient::icon() const { return m_icon; } QString PreviewClient::iconName() const { return m_iconName; } bool PreviewClient::isActive() const { return m_active; } bool PreviewClient::isCloseable() const { return m_closeable; } bool PreviewClient::isKeepAbove() const { return m_keepAbove; } bool PreviewClient::isKeepBelow() const { return m_keepBelow; } bool PreviewClient::isMaximizeable() const { return m_maximizable; } bool PreviewClient::isMaximized() const { return isMaximizedHorizontally() && isMaximizedVertically(); } bool PreviewClient::isMaximizedHorizontally() const { return m_maximizedHorizontally; } bool PreviewClient::isMaximizedVertically() const { return m_maximizedVertically; } bool PreviewClient::isMinimizeable() const { return m_minimizable; } bool PreviewClient::isModal() const { return m_modal; } bool PreviewClient::isMoveable() const { return m_movable; } bool PreviewClient::isOnAllDesktops() const { return desktop() == -1; } bool PreviewClient::isResizeable() const { return m_resizable; } bool PreviewClient::isShadeable() const { return m_shadeable; } bool PreviewClient::isShaded() const { return m_shaded; } bool PreviewClient::providesContextHelp() const { return m_providesContextHelp; } WId PreviewClient::windowId() const { return 0; } QPalette PreviewClient::palette() const { return m_palette.palette(); } QColor PreviewClient::color(ColorGroup group, ColorRole role) const { return m_palette.color(group, role); } -QAbstractItemModel *PreviewClient::colorSchemeModel() const -{ - return m_colorSchemeManager->model(); -} - -int PreviewClient::colorSchemeIndex() const -{ - return m_colorSchemeIndex; -} - -void PreviewClient::setColorSchemeIndex(int index) -{ - if (m_colorSchemeIndex == index) { - return; - } - m_colorSchemeIndex = index; - emit colorSchemeIndexChanged(m_colorSchemeIndex); -} - Qt::Edges PreviewClient::adjacentScreenEdges() const { Qt::Edges edges; if (m_bordersBottomEdge) { edges |= Qt::BottomEdge; } if (m_bordersLeftEdge) { edges |= Qt::LeftEdge; } if (m_bordersRightEdge) { edges |= Qt::RightEdge; } if (m_bordersTopEdge) { edges |= Qt::TopEdge; } return edges; } bool PreviewClient::hasApplicationMenu() const { return true; } bool PreviewClient::isApplicationMenuActive() const { return false; } bool PreviewClient::bordersBottomEdge() const { return m_bordersBottomEdge; } bool PreviewClient::bordersLeftEdge() const { return m_bordersLeftEdge; } bool PreviewClient::bordersRightEdge() const { return m_bordersRightEdge; } bool PreviewClient::bordersTopEdge() const { return m_bordersTopEdge; } void PreviewClient::setBordersBottomEdge(bool enabled) { if (m_bordersBottomEdge == enabled) { return; } m_bordersBottomEdge = enabled; emit bordersBottomEdgeChanged(enabled); } void PreviewClient::setBordersLeftEdge(bool enabled) { if (m_bordersLeftEdge == enabled) { return; } m_bordersLeftEdge = enabled; emit bordersLeftEdgeChanged(enabled); } void PreviewClient::setBordersRightEdge(bool enabled) { if (m_bordersRightEdge == enabled) { return; } m_bordersRightEdge = enabled; emit bordersRightEdgeChanged(enabled); } void PreviewClient::setBordersTopEdge(bool enabled) { if (m_bordersTopEdge == enabled) { return; } m_bordersTopEdge = enabled; emit bordersTopEdgeChanged(enabled); } +void PreviewClient::requestShowToolTip(const QString &text) +{ + qDebug() << "tooltip show requested with text:" << text; +} + +void PreviewClient::requestHideToolTip() +{ + qDebug() << "tooltip hide requested"; +} + void PreviewClient::requestClose() { emit closeRequested(); } void PreviewClient::requestContextHelp() { qDebug() << "context help requested"; } void PreviewClient::requestToggleMaximization(Qt::MouseButtons buttons) { if (buttons.testFlag(Qt::LeftButton)) { const bool set = !isMaximized(); setMaximizedHorizontally(set); setMaximizedVertically(set); } else if (buttons.testFlag(Qt::RightButton)) { setMaximizedHorizontally(!isMaximizedHorizontally()); } else if (buttons.testFlag(Qt::MiddleButton)) { setMaximizedVertically(!isMaximizedVertically()); } } void PreviewClient::requestMinimize() { emit minimizeRequested(); } void PreviewClient::requestToggleKeepAbove() { setKeepAbove(!isKeepAbove()); } void PreviewClient::requestToggleKeepBelow() { setKeepBelow(!isKeepBelow()); } void PreviewClient::requestShowWindowMenu() { emit showWindowMenuRequested(); } void PreviewClient::requestShowApplicationMenu(const QRect &rect, int actionId) { Q_UNUSED(rect); Q_UNUSED(actionId); } void PreviewClient::showApplicationMenu(int actionId) { Q_UNUSED(actionId) } void PreviewClient::requestToggleOnAllDesktops() { setDesktop(isOnAllDesktops() ? 1 : -1); } void PreviewClient::requestToggleShade() { setShaded(!isShaded()); } #define SETTER(type, name, variable) \ void PreviewClient::name(type variable) \ { \ if (m_##variable == variable) { \ return; \ } \ qDebug() << "Setting " << #variable << ":" << variable;\ m_##variable = variable; \ emit variable##Changed(m_##variable); \ } #define SETTER2(name, variable) SETTER(bool, name, variable) SETTER(const QString &, setCaption, caption) SETTER(const QString &, setIconName, iconName) SETTER(int, setWidth, width) SETTER(int, setHeight, height) SETTER2(setActive, active) SETTER2(setCloseable, closeable) SETTER2(setMaximizable, maximizable) SETTER2(setKeepBelow, keepBelow) SETTER2(setKeepAbove, keepAbove) SETTER2(setMaximizedHorizontally, maximizedHorizontally) SETTER2(setMaximizedVertically, maximizedVertically) SETTER2(setMinimizable, minimizable) SETTER2(setModal, modal) SETTER2(setMovable, movable) SETTER2(setResizable, resizable) SETTER2(setShadeable, shadeable) SETTER2(setShaded, shaded) SETTER2(setProvidesContextHelp, providesContextHelp) #undef SETTER2 #undef SETTER } // namespace Preview } // namespace KDecoration2 diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewclient.h b/kcmkwin/kwindecoration/declarative-plugin/previewclient.h index 47ff2c415..df3608393 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/previewclient.h +++ b/kcmkwin/kwindecoration/declarative-plugin/previewclient.h @@ -1,222 +1,214 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KDECOARTIONS_PREVIEW_CLIENT_H #define KDECOARTIONS_PREVIEW_CLIENT_H #include "../../../decorations/decorationpalette.h" #include #include #include -class KColorSchemeManager; class QAbstractItemModel; namespace KDecoration2 { namespace Preview { class PreviewClient : public QObject, public ApplicationMenuEnabledDecoratedClientPrivate { Q_OBJECT Q_PROPERTY(KDecoration2::Decoration *decoration READ decoration CONSTANT) Q_PROPERTY(QString caption READ caption WRITE setCaption NOTIFY captionChanged) Q_PROPERTY(QIcon icon READ icon WRITE setIcon NOTIFY iconChanged) Q_PROPERTY(QString iconName READ iconName WRITE setIconName NOTIFY iconNameChanged) - Q_PROPERTY(QAbstractItemModel *colorSchemeModel READ colorSchemeModel CONSTANT) - Q_PROPERTY(int colorSchemeIndex READ colorSchemeIndex WRITE setColorSchemeIndex NOTIFY colorSchemeIndexChanged) Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) Q_PROPERTY(bool closeable READ isCloseable WRITE setCloseable NOTIFY closeableChanged) Q_PROPERTY(bool keepAbove READ isKeepAbove WRITE setKeepAbove NOTIFY keepAboveChanged) Q_PROPERTY(bool keepBelow READ isKeepBelow WRITE setKeepBelow NOTIFY keepBelowChanged) Q_PROPERTY(bool maximizable READ isMaximizeable WRITE setMaximizable NOTIFY maximizableChanged) Q_PROPERTY(bool maximized READ isMaximized NOTIFY maximizedChanged) Q_PROPERTY(bool maximizedVertically READ isMaximizedVertically WRITE setMaximizedVertically NOTIFY maximizedVerticallyChanged) Q_PROPERTY(bool maximizedHorizontally READ isMaximizedHorizontally WRITE setMaximizedHorizontally NOTIFY maximizedHorizontallyChanged) Q_PROPERTY(bool minimizable READ isMinimizeable WRITE setMinimizable NOTIFY minimizableChanged) Q_PROPERTY(bool modal READ isModal WRITE setModal NOTIFY modalChanged) Q_PROPERTY(bool movable READ isMoveable WRITE setMovable NOTIFY movableChanged) Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops NOTIFY onAllDesktopsChanged) Q_PROPERTY(bool resizable READ isResizeable WRITE setResizable NOTIFY resizableChanged) Q_PROPERTY(bool shadeable READ isShadeable WRITE setShadeable NOTIFY shadeableChanged) Q_PROPERTY(bool shaded READ isShaded WRITE setShaded NOTIFY shadedChanged) Q_PROPERTY(bool providesContextHelp READ providesContextHelp WRITE setProvidesContextHelp NOTIFY providesContextHelpChanged) Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged) Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged) Q_PROPERTY(bool bordersTopEdge READ bordersTopEdge WRITE setBordersTopEdge NOTIFY bordersTopEdgeChanged) Q_PROPERTY(bool bordersLeftEdge READ bordersLeftEdge WRITE setBordersLeftEdge NOTIFY bordersLeftEdgeChanged) Q_PROPERTY(bool bordersRightEdge READ bordersRightEdge WRITE setBordersRightEdge NOTIFY bordersRightEdgeChanged) Q_PROPERTY(bool bordersBottomEdge READ bordersBottomEdge WRITE setBordersBottomEdge NOTIFY bordersBottomEdgeChanged) public: explicit PreviewClient(DecoratedClient *client, Decoration *decoration); virtual ~PreviewClient(); QString caption() const override; WId decorationId() const override; WId windowId() const override; int desktop() const override; QIcon icon() const override; bool isActive() const override; bool isCloseable() const override; bool isKeepAbove() const override; bool isKeepBelow() const override; bool isMaximizeable() const override; bool isMaximized() const override; bool isMaximizedVertically() const override; bool isMaximizedHorizontally() const override; bool isMinimizeable() const override; bool isModal() const override; bool isMoveable() const override; bool isOnAllDesktops() const override; bool isResizeable() const override; bool isShadeable() const override; bool isShaded() const override; bool providesContextHelp() const override; int width() const override; int height() const override; QPalette palette() const override; QColor color(ColorGroup group, ColorRole role) const override; Qt::Edges adjacentScreenEdges() const override; bool hasApplicationMenu() const override; bool isApplicationMenuActive() const override; + void requestShowToolTip(const QString &text) override; + void requestHideToolTip() override; void requestClose() override; void requestContextHelp() override; void requestToggleMaximization(Qt::MouseButtons buttons) override; void requestMinimize() override; void requestToggleKeepAbove() override; void requestToggleKeepBelow() override; void requestToggleShade() override; void requestShowWindowMenu() override; void requestShowApplicationMenu(const QRect &rect, int actionId) override; void requestToggleOnAllDesktops() override; void showApplicationMenu(int actionId); void setCaption(const QString &caption); void setActive(bool active); void setCloseable(bool closeable); void setMaximizable(bool maximizable); void setKeepBelow(bool keepBelow); void setKeepAbove(bool keepAbove); void setMaximizedHorizontally(bool maximized); void setMaximizedVertically(bool maximized); void setMinimizable(bool minimizable); void setModal(bool modal); void setMovable(bool movable); void setResizable(bool resizable); void setShadeable(bool shadeable); void setShaded(bool shaded); void setProvidesContextHelp(bool contextHelp); void setDesktop(int desktop); void setWidth(int width); void setHeight(int height); QString iconName() const; void setIconName(const QString &icon); void setIcon(const QIcon &icon); - QAbstractItemModel *colorSchemeModel() const; - int colorSchemeIndex() const; - void setColorSchemeIndex(int index); - bool bordersTopEdge() const; bool bordersLeftEdge() const; bool bordersRightEdge() const; bool bordersBottomEdge() const; void setBordersTopEdge(bool enabled); void setBordersLeftEdge(bool enabled); void setBordersRightEdge(bool enabled); void setBordersBottomEdge(bool enabled); Q_SIGNALS: void captionChanged(const QString &); void iconChanged(const QIcon &); void iconNameChanged(const QString &); void activeChanged(bool); void closeableChanged(bool); void keepAboveChanged(bool); void keepBelowChanged(bool); void maximizableChanged(bool); void maximizedChanged(bool); void maximizedVerticallyChanged(bool); void maximizedHorizontallyChanged(bool); void minimizableChanged(bool); void modalChanged(bool); void movableChanged(bool); void onAllDesktopsChanged(bool); void resizableChanged(bool); void shadeableChanged(bool); void shadedChanged(bool); void providesContextHelpChanged(bool); void desktopChanged(int); void widthChanged(int); void heightChanged(int); - void colorSchemeIndexChanged(int); void paletteChanged(const QPalette&); void bordersTopEdgeChanged(bool); void bordersLeftEdgeChanged(bool); void bordersRightEdgeChanged(bool); void bordersBottomEdgeChanged(bool); void showWindowMenuRequested(); void showApplicationMenuRequested(); void minimizeRequested(); void closeRequested(); private: - KColorSchemeManager *m_colorSchemeManager; - int m_colorSchemeIndex; QString m_caption; QIcon m_icon; QString m_iconName; KWin::Decoration::DecorationPalette m_palette; bool m_active; bool m_closeable; bool m_keepBelow; bool m_keepAbove; bool m_maximizable; bool m_maximizedHorizontally; bool m_maximizedVertically; bool m_minimizable; bool m_modal; bool m_movable; bool m_resizable; bool m_shadeable; bool m_shaded; bool m_providesContextHelp; int m_desktop; int m_width; int m_height; bool m_bordersTopEdge; bool m_bordersLeftEdge; bool m_bordersRightEdge; bool m_bordersBottomEdge; }; } // namespace Preview } // namespace KDecoration2 #endif diff --git a/kcmkwin/kwindecoration/kwindecoration.desktop b/kcmkwin/kwindecoration/kwindecoration.desktop index 10cbf7054..f74463838 100644 --- a/kcmkwin/kwindecoration/kwindecoration.desktop +++ b/kcmkwin/kwindecoration/kwindecoration.desktop @@ -1,165 +1,165 @@ [Desktop Entry] Exec=kcmshell5 kwindecoration Icon=preferences-system-windows-action Type=Service X-KDE-ServiceTypes=KCModule X-DocPath=kcontrol/kwindecoration/index.html X-KDE-Library=kcm_kwindecoration X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=applicationstyle X-KDE-Weight=40 Name=Window Decorations Name[ar]=زخارف النوافذ Name[bg]=Декорации на прозорците Name[bs]=Dekoracije prozora Name[ca]=Decoració de les finestres Name[ca@valencia]=Decoració de les finestres Name[cs]=Dekorace oken Name[da]=Vinduesdekorationer Name[de]=Fensterdekoration Name[el]=Διακοσμήσεις παραθύρου Name[en_GB]=Window Decorations Name[es]=Decoración de ventanas Name[et]=Akna dekoratsioonid Name[eu]=Leiho-apaindurak Name[fi]=Ikkunakehykset Name[fr]=Décorations de fenêtres Name[ga]=Maisiúcháin Fhuinneog Name[gl]=Decoración da xanela Name[he]=מסגרת חלון Name[hi]=विंडो सजावट Name[hr]=Ukrasi prozora Name[hu]=Ablakdekorációk Name[ia]=Decorationes de fenestra Name[id]=Dekorasi Jendela Name[is]=Gluggaskreytingar Name[it]=Decorazioni delle finestre Name[ja]=ウィンドウの飾り Name[kk]=Терезенің безендірулері Name[km]=ការ​តុបតែង​បង្អួច Name[kn]=ವಿಂಡೋ ಅಲಂಕಾರಗಳು Name[ko]=창 장식 Name[lt]=Lango dekoracijos Name[lv]=Logu dekorācijas Name[mr]=चौकट सजावट Name[nb]=Vinduspynt Name[nds]=Finstern opfladusen Name[nl]=Vensterdecoraties Name[nn]=Vindaugspynt Name[pa]=ਵਿੰਡੋ ਸਜਾਵਟ Name[pl]=Wygląd okien Name[pt]=Decorações das Janelas Name[pt_BR]=Decorações da janela Name[ro]=Decorații fereastră Name[ru]=Оформление окон Name[si]=කවුළු සැරසිලි Name[sk]=Dekorácie okien Name[sl]=Okraski oken Name[sr]=Декорације прозора Name[sr@ijekavian]=Декорације прозора Name[sr@ijekavianlatin]=Dekoracije prozora Name[sr@latin]=Dekoracije prozora Name[sv]=Fönsterdekorationer Name[th]=ส่วนตกแต่งหน้าต่าง Name[tr]=Pencere Dekorasyonları Name[ug]=كۆزنەك بېزەكلىرى Name[uk]=Обрамлення вікон Name[wa]=Gåyotaedjes des fniesses Name[x-test]=xxWindow Decorationsxx Name[zh_CN]=窗口装饰 Name[zh_TW]=視窗裝飾 Comment=Look and Feel of Window Titles Comment[bs]=Izgled i osjećaj naslova prozora Comment[ca]=Aspecte i comportament dels títols de les finestres Comment[ca@valencia]=Aspecte i comportament dels títols de les finestres Comment[cs]=Vzhled a dekorace titulků oken Comment[da]=Udseendet af vinduestitler Comment[de]=Erscheinungsbild von Fenstertiteln Comment[el]=Διαμόρφωση της εμφάνισης και αίσθησης του τίτλου των παραθύρων Comment[en_GB]=Look and Feel of Window Titles Comment[es]=Aspecto visual de los títulos de las ventanas Comment[et]=Akna tiitliribade välimus ja tunnetus Comment[eu]=Leiho-tituluen itxura eta izaera Comment[fi]=Ikkunoiden kehysten ulkoasu Comment[fr]=Apparence des titres de fenêtre Comment[gl]=Aparencia e o comportamento dos títulos das xanelas Comment[he]=הגדרת המראה והתחושה של מסגרות החלונות Comment[hu]=Az ablakok címsorának megjelenése Comment[ia]=Semblantia de titulos de fenestra Comment[id]=Tampilan dan Rasa dari Judul Jendela Comment[it]=Aspetto dei titoli delle finestre Comment[ja]=ウィンドウタイトルの外観を設定 Comment[ko]=창 제목 표시줄의 모습과 느낌 설정 Comment[lt]=Langų antraščių išvaizda ir elgsena Comment[nb]=Utseende og virkemåte for vindustitlene Comment[nds]=Dat Utsehn vun de Finstertiteln instellen Comment[nl]=Uiterlijk en gedrag van venstertitels Comment[nn]=Utsjånad og åtferd for vindaugstitlar Comment[pa]=ਵਿੰਡੋ ਟਾਇਟਲਾਂ ਦੇ ਰੰਗ-ਰੂਪ ਦੀ ਸੰਰਚਨਾ Comment[pl]=Tytuły okien - wrażenia wzrokowe i dotykowe Comment[pt]=Aparência e Comportamento dos Títulos das Janelas Comment[pt_BR]=Aparência do título das janelas Comment[ru]=Настройка внешнего вида заголовков окон Comment[sk]=Nastavenie vzhľadu titulkov okien Comment[sl]=Videz in občutek naslovnih vrstic okna Comment[sr]=Изглед и осећај за наслове прозора Comment[sr@ijekavian]=Изглед и осјећај за наслове прозора Comment[sr@ijekavianlatin]=Izgled i osjećaj za naslove prozora Comment[sr@latin]=Izgled i osećaj za naslove prozora Comment[sv]=Namnlisternas utseende och känsla Comment[tr]=Pencere Başlıklarının Görünüm ve Davranışlarını Yapılandır Comment[uk]=Вигляд і поведінка смужок заголовків вікон Comment[x-test]=xxLook and Feel of Window Titlesxx Comment[zh_CN]=窗口标题的观感 Comment[zh_TW]=視窗標題列的外觀與感覺 X-KDE-Keywords=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration X-KDE-Keywords[bs]=kwin,prozor,upravitelj,granica,stil,tema,izgled,osjećati,izgled,dugme,držati,ivica,kwm,dekoracija X-KDE-Keywords[ca]=kwin,finestra,gestor,vora,estil,tema,aspecte,aparença,disposició,botó,gestió,vora,kwm,decoració X-KDE-Keywords[ca@valencia]=kwin,finestra,gestor,vora,estil,tema,aspecte,aparença,disposició,botó,gestió,vora,kwm,decoració X-KDE-Keywords[cs]=kwin,okna,správce,okraj,styl,motiv,vzhled,pocit,rozvržení,tlačítko,madlo,okraj,kwm,dekorace X-KDE-Keywords[da]=kwin,vindueshåndtering,window,manager,kant,stil,tema,udseende,layout,knap,håndtag,kant,kwm,dekoration X-KDE-Keywords[de]=KWin,Kwm,Fenster,Manager,Rahmen,Design,Stile,Themes,Optik,Erscheinungsbild,Layout,Knöpfe,Ränder,Dekorationen X-KDE-Keywords[el]=kwin,παράθυρο,διαχειριστής,περίγραμμα,στιλ,θέμα,εμφάνιση,αίσθηση,διάταξη,κουμπί,χειρισμός,άκρη,kwm,διακόσμηση X-KDE-Keywords[en_GB]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration X-KDE-Keywords[es]=kwin,ventana,gestor,borde,estilo,tema,aspecto,sensación,disposición,botón,asa,borde,kwm,decoración X-KDE-Keywords[et]=kwin,aken,haldur,piire,stiil,teema,välimus,paigutus,nupp,pide,serv,kwm,dekoratsioon X-KDE-Keywords[eu]=kwin,leiho,kudeatzaile,ertz,estilo,gai, itxura,izaera,diseinu,botoi,helduleku,kwm,dekorazio,apaindura,apainketa X-KDE-Keywords[fi]=kwin,ikkuna,hallinta,ikkunointiohjelma,kehys,reunus,tyyli,teema,ulkoasu,toiminta,asettelu,painike,kahva,kulma,reuna,kwm,koriste X-KDE-Keywords[fr]=kwin, fenêtre, gestionnaire, composition, bordure, style, thème, apparence, comportement, disposition, bouton, prise en main, bord, kwm, décoration X-KDE-Keywords[ga]=kwin,fuinneog,bainisteoir,imlíne,stíl,téama,cuma,brath,leagan amach,cnaipe,hanla,ciumhais,kwm,maisiúchán X-KDE-Keywords[gl]=kwin,xanela,xestor,estilo,tema,aparencia,comportamento,aspecto,disposición, botón,asa,bordo,kwm,decoración X-KDE-Keywords[hu]=kwin,ablak,kezelő,szegély,stílus,téma,kinézet,megjelenés,elrendezés,gomb,kezel,szél,kwm,dekoráció X-KDE-Keywords[ia]=kwin,fenestra,gerente,margine,stilo,thema,aspecto,sentir,disposition,button,maneator,bordo,kwm,decoration -X-KDE-Keywords[id]=kwin,jendela,manajer,batas,gaya,tema,tampilan,rasa,tata letak,tombol,pegangan,tepi,kwm,dekorasi +X-KDE-Keywords[id]=kwin,jendela,pengelola,batas,gaya,tema,tampilan,rasa,tata letak,tombol,pegangan,tepi,kwm,dekorasi X-KDE-Keywords[it]=kwin,gestore,finestre,bordo,stile,tema,aspetto,disposizione,pulsante,gestore,kwm,decorazione X-KDE-Keywords[kk]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration X-KDE-Keywords[km]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration X-KDE-Keywords[ko]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration,창,관리자,테두리,스타일,테마,단추,핸들,경계 X-KDE-Keywords[nb]=kwin,vindu,behandler,ramme,stil,tema,lås,utforming,knapp,håndtak,kant,kwm X-KDE-Keywords[nds]=KWin,Finster,Pleger,Rahmen,Stil,Muster,Utsehn,Bedenen,Knoop,Greep,Kant,kwm,Dekoratschoon X-KDE-Keywords[nl]=kwin,venster,beheerder,grens,stijl,thema,look,feel,indeling,knop,handel,rand,kwm,decoratie X-KDE-Keywords[nn]=kwin,vindauge,handsamar,ramme,kantlinje,stil,tema,lås,utforming,knapp,handtak,kant,kwm,dekorasjon,pynt X-KDE-Keywords[pl]=kwin,okno,menadżer,obramowanie,styl,motyw,wygląd,odczucie,układ,przycisk, uchwyt,krawędź,kwm,dekoracja X-KDE-Keywords[pt]=kwin,gestor,janela,contorno,estilo,tema,aparência,comportamento,disposição,botão,pega,extremo,kwm,decoração X-KDE-Keywords[pt_BR]=kwin,gerenciador,janela,borda,estilo,tema,aparência,comportamento,layout,botão,canto,extremo,kwm,decoração X-KDE-Keywords[ru]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration,окно,диспетчер,граница,стиль,тема,внешний вид,оформление,разметка,шаблон,кнопка,управление,край X-KDE-Keywords[sk]=kwin,okno,správca,rám,štýl,téma,vzhľad,cítenie,rozloženie,tlačidlo,spracovanie,okraj,kwm,dekorácia X-KDE-Keywords[sl]=kwin,okna,okenski upravljalnik,upravljalnik oken,rob,obroba,slog,tema,videz,obnašanje,občutek,razpored,gumbi,ročica,okraski,kwm X-KDE-Keywords[sr]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration,К‑вин,прозор,менаџер,ивица,стила,тема,изглед,осећај,распоред,дугме,ручка,КВМ,декорација X-KDE-Keywords[sr@ijekavian]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration,К‑вин,прозор,менаџер,ивица,стила,тема,изглед,осећај,распоред,дугме,ручка,КВМ,декорација X-KDE-Keywords[sr@ijekavianlatin]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration,KWin,prozor,menadžer,ivica,stila,tema,izgled,osećaj,raspored,dugme,ručka,KWM,dekoracija X-KDE-Keywords[sr@latin]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration,KWin,prozor,menadžer,ivica,stila,tema,izgled,osećaj,raspored,dugme,ručka,KWM,dekoracija X-KDE-Keywords[sv]=kwin,fönster,hantering,kant,stil,tema,utseende,känsla,layout,knapp,grepp,kant,kwm,dekoration X-KDE-Keywords[tr]=kwin,pencere,yönetici,kenarlık,biçim,tema,görünüm,şekil,düzen,düğme,kullanım,kenar,kwm,dekorasyon X-KDE-Keywords[uk]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration,вікно,вікна,керування,менеджер,рамка,межа,стиль,тема,вигляд,поведінка,компонування,кнопка,елемент,край,декорації,обрамлення X-KDE-Keywords[x-test]=xxkwinxx,xxwindowxx,xxmanagerxx,xxborderxx,xxstylexx,xxthemexx,xxlookxx,xxfeelxx,xxlayoutxx,xxbuttonxx,xxhandlexx,xxedgexx,xxkwmxx,xxdecorationxx X-KDE-Keywords[zh_CN]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration,窗口,管理,边框,样式,主题,外怪,布局,按钮,边界,装饰 X-KDE-Keywords[zh_TW]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration Categories=Qt;KDE;X-KDE-settings-looknfeel; diff --git a/kcmkwin/kwindesktop/desktop.desktop b/kcmkwin/kwindesktop/desktop.desktop index c78e4bb6b..13e4b0955 100644 --- a/kcmkwin/kwindesktop/desktop.desktop +++ b/kcmkwin/kwindesktop/desktop.desktop @@ -1,161 +1,161 @@ [Desktop Entry] Type=Service X-KDE-ServiceTypes=KCModule X-DocPath=kcontrol/desktop/index.html Icon=preferences-desktop Exec=kcmshell5 desktop X-KDE-Library=kcm_kwindesktop X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=desktopbehavior X-KDE-Weight=60 Name=Virtual Desktops Name[ar]=أسطح المكتب الافتراضية Name[bg]=Виртуални работни плотове Name[bs]=Virtuelne površi Name[ca]=Escriptoris virtuals Name[ca@valencia]=Escriptoris virtuals Name[cs]=Virtuální plochy Name[da]=Virtuelle skriveborde Name[de]=Virtuelle Arbeitsflächen Name[el]=Εικονικές επιφάνειες εργασίες Name[en_GB]=Virtual Desktops Name[es]=Escritorios virtuales Name[et]=Virtuaalsed töölauad Name[eu]=Alegiazko mahaigaina Name[fi]=Virtuaalityöpöydät Name[fr]=Bureaux virtuels Name[ga]=Deasca Fíorúla Name[gl]=Escritorios virtuais Name[gu]=વર્ચ્યુઅલ ડેસ્કટોપો Name[he]=שולחנות עבודה וירטואליים Name[hi]=आभासी डेस्कटॉप Name[hr]=Virtualne radne površine Name[hu]=Virtuális asztalok Name[ia]=Scriptorios virtual Name[id]=Desktop Virtual Name[is]=Sýndarskjáborð Name[it]=Desktop virtuali Name[ja]=仮想デスクトップ Name[kk]=Виртуалды Үстелдер Name[km]=ផ្ទៃតុ​និម្មិត Name[kn]=ವಾಸ್ತವಪ್ರಾಯ ಗಣಕತೆರೆಗಳು Name[ko]=가상 데스크톱 Name[lt]=Virtualūs darbalaukiai Name[lv]=Virtuālās darbvirsmas Name[mr]=आभासी डेस्कटॉप Name[nb]=Virtuelle skrivebord Name[nds]=Mehr Schriefdischen Name[nl]=Virtuele bureaubladen Name[nn]=Virtuelle skrivebord Name[pa]=ਵਰਚੁਅਲ ਡੈਸਕਟਾਪ Name[pl]=Pulpity wirtualne Name[pt]=Ecrãs Virtuais Name[pt_BR]=Áreas de trabalho virtuais Name[ro]=Birouri virtuale Name[ru]=Рабочие столы Name[si]=අත්ථ්‍ය වැඩතල Name[sk]=Virtuálne pracovné plochy Name[sl]=Navidezna namizja Name[sr]=Виртуелне површи Name[sr@ijekavian]=Виртуелне површи Name[sr@ijekavianlatin]=Virtuelne površi Name[sr@latin]=Virtuelne površi Name[sv]=Virtuella skrivbord Name[tg]=Мизҳои кории виртуалӣ Name[th]=พื้นที่ทำงานเสมือน Name[tr]=Sanal Masaüstleri Name[ug]=مەۋھۇم ئۈستەلئۈستى Name[uk]=Віртуальні стільниці Name[wa]=Forveyous scribannes Name[x-test]=xxVirtual Desktopsxx Name[zh_CN]=虚拟桌面 Name[zh_TW]=虛擬桌面 Comment=Navigation, Number and Layout of Virtual Desktops Comment[bs]=Navigacija, broj i izgled virtualnih desktopa Comment[ca]=Navegació, nombre i disposició dels escriptoris virtuals Comment[ca@valencia]=Navegació, nombre i disposició dels escriptoris virtuals Comment[cs]=Navigace, počet a rozvržení virtuálních ploch Comment[da]=Navigation, antal og layout af virtuelle skriveborde Comment[de]=Navigation, Anzahl und Layout virtueller Arbeitsflächen Comment[el]=Περιήγηση, αριθμός και διάταξη εικονικών επιφανειών εργασίας Comment[en_GB]=Navigation, Number and Layout of Virtual Desktops Comment[es]=Navegación, número y disposición de los escritorios virtuales Comment[et]=Virtuaalsete töölaudade vahel liikumine, nende arv ja paigutus Comment[eu]=Nabigazioa, alegiazko mahaigainen kopurua eta antolamendua Comment[fi]=Virtuaalityöpöytien vaihtaminen, määrä ja asettelu Comment[fr]=Navigation, nombre et disposition des bureaux virtuels Comment[gl]=Navegación, cantidade e disposición dos escritorios virtuais Comment[he]=ניווט, פריסה ומספר שולחנות עבודה וירטואלים Comment[hu]=Navigáció, a virtuális asztalok száma és elrendezése Comment[id]=Navigasi, Jumlah dan Tata Letak Desktop Virtual Comment[it]=Navigazione, numero e disposizione dei desktop virtuali Comment[ko]=가상 데스크톱 탐색, 개수, 레이아웃 Comment[lt]=Naršymas, Skaičius ir išdėstymas virtualių darbalaukių Comment[nb]=Navigering, antall og utlegg av virtuelle skrivebord Comment[nds]=Tall, Anornen un dat Anstüern vun de virtuellen Schriefdischen fastleggen Comment[nl]=Navigatie door, aantal en indeling van virtuele bureaubladen Comment[nn]=Navigering, nummer og vising av virtuelle skrivebord Comment[pa]=ਵਰਚੁਅਲ ਡੈਸਕਟਾਪਾਂ ਲਈ ਨੇਵੀਗੇਸ਼ਨ, ਗਿਣਤੀ ਅਤੇ ਢਾਂਚਾ Comment[pl]=Poruszanie się, liczba i układ wirtualnych pulpitów Comment[pt]=Navegação, Número e Disposição dos Ecrãs Virtuais Comment[pt_BR]=Navegação, quantidade e layout das áreas de trabalho virtuais Comment[ru]=Число, расположение и способ переключения рабочих столов Comment[sk]=Navigácia, počet a rozloženie virtuálnych plôch Comment[sl]=Krmarjenje med, število in razporeditev navideznih namizij Comment[sr]=Кретање, број и распоред виртуелних површи Comment[sr@ijekavian]=Кретање, број и распоред виртуелних површи Comment[sr@ijekavianlatin]=Kretanje, broj i raspored virtuelnih površi Comment[sr@latin]=Kretanje, broj i raspored virtuelnih površi Comment[sv]=Navigering, antal och layout av virtuella skrivbord Comment[tr]=Gezinti, Sanal Masaüstlerinin Sayısı ve Yerleşimi Comment[uk]=Навігація, кількість та компонування віртуальних стільниць Comment[vi]=Số lượng, bố trí và điều hướng của màn hình ảo Comment[x-test]=xxNavigation, Number and Layout of Virtual Desktopsxx Comment[zh_CN]=虚拟桌面的切换,数量和布局 Comment[zh_TW]=虛擬桌面的導覽、數字與佈局 X-KDE-Keywords=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings X-KDE-Keywords[bs]=pozadina,pozadine,broj,virtuelna pozadina,višestruka pozadina,pejdžer,dodatak pejdžeru,aplet pejdžer,pejdžer postavke X-KDE-Keywords[ca]=escriptori,escriptoris,nombre,escriptori virtual,escriptoris múltiples,paginador,estri paginador,miniaplicació de paginació,arranjament de paginador X-KDE-Keywords[ca@valencia]=escriptori,escriptoris,nombre,escriptori virtual,escriptoris múltiples,paginador,estri paginador,miniaplicació de paginació,arranjament de paginador X-KDE-Keywords[da]=skrivebord,skriveborde,desktop,desktops,virtuelt skrivebord,flere skriveborde,spaces,pager,skrivebordsvælger,pager widget,pager applet X-KDE-Keywords[de]=Arbeitsfläche,Arbeitsflächen,Desktop,Anzahl,Virtuelle Arbeitsfläche,Mehrere Arbeitsflächen,Arbeitsflächenumschalter,Arbeitsflächenumschalter-Bedienelement,Arbeitsflächenumschalter-Miniprogramm,Arbeitsflächenumschalter-Einstellungen X-KDE-Keywords[el]=επιφάνεια εργασίας,επιφάνειες εργασίας,αριθμός,εικονική επιφάνεια εργασίας,πολλαπλές επιφάνειες εργασίας,χαρτί,γραφικό συστατικό χαρτιού,μικροεφαρμογή χαρτιού,ρυθμίσεις χαρτιού X-KDE-Keywords[en_GB]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings X-KDE-Keywords[es]=escritorio,escritorios,número,escritorio virtual,múltiples escritorios,paginador,control de paginación,miniaplicación del paginador,preferencias del paginador X-KDE-Keywords[et]=töölaud,töölauad,arv,virtuaalne töölaud,mitu töölauda,töölauavahetaja,töölaudade vahetaja,töölauavahetaja aplett,töölauavahetaja vidin,töölauavahetaja seadistused X-KDE-Keywords[eu]=mahaigain,mahaigainak,kopuru,mahaigain birtuala,alegiazko mahaigaina,hainbat mahaigain,bilagailu,bilagailuaren trepeta,bilagailuaren miniaplikazioa,bilagailuaren ezarpenak X-KDE-Keywords[fi]=työpöytä,työpöydät,lukumäärä,virtuaalityöpöytä,monta työpöytää,sivutin,sivutinsovelma,sivuttimen asetukset X-KDE-Keywords[fr]=bureau, bureaux, numéro, bureau virtuel, bureaux multiples, gestionnaire de bureau, composant graphique du gestionnaire de bureau, paramètres du gestionnaire de bureaux X-KDE-Keywords[gl]=escritorio,escritorios,número,escritorio virtual,escritorios múltiplos,paxinador, trebello paxinador, miniaplicativo paxinador,configuración do paxinador X-KDE-Keywords[hu]=asztal,asztalok,szám,virtuális asztal,több asztal,papír,papír felületi elem,papír kisalkalmazás,papírbeállítások X-KDE-Keywords[ia]=scriptorio,scriptorios,numero,scriptorio virtual,scriptorio multiple,pager, widget de pager, applet de pager, preferentias de pager -X-KDE-Keywords[id]=desktop,desktop,jumlah,desktop virtual,banyak desktop,halaman,widget halaman,applet halaman,pengaturan halaman +X-KDE-Keywords[id]=desktop,desktop,jumlah,desktop virtual,banyak desktop,halaman,widget halaman,applet halaman,setelan halaman X-KDE-Keywords[it]=desktop,numero,desktop virtuali,desktop multipli,cambiadesktop,oggetto cambiadesktop,applet cambiadesktop,impostazioni del cambiadesktop X-KDE-Keywords[kk]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings X-KDE-Keywords[km]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings X-KDE-Keywords[ko]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,데스크톱,가상 데스크톱,다중 데스크톱 X-KDE-Keywords[nb]=skrivebord,antall,virtuelt skrivebord,flere skrivebord,veksler,vekslerelement,veksler-miniprogram,vekslerinnstillinger X-KDE-Keywords[nds]=Schriefdisch,Schriefdischen,virtuell,mehr,Schriefdisch-Översicht,instellen X-KDE-Keywords[nl]=bureaublad,bureaubladen,aantal,virtueel bureaublad,meervoudige bureaubladen,pager,pager-widget,pager-applet,pagerinstellingen X-KDE-Keywords[nn]=skrivebord,mengd,tal,virtuelt skrivebord,fleire skrivebord,vekslar,vekslarelement,vekslarelement,vekslerinnstillinger,vekslaroppsett X-KDE-Keywords[pa]=ਡੈਸਕਟਾਪ,ਗਿਣਤੀ,ਨੰਬਰ,ਅੰਕ,ਵਰਚੁਅਲ ਡੈਸਕਟਾਪ,ਕਈ ਡੈਸਕਟਾਪ,ਪੇਜ਼ਰ,ਪੇਜ਼ਰ ਵਿਜੈਟ,ਪੇਜ਼ਰ ਐਪਲਿਟ,ਪੇਜ਼ਰ ਸੈਟਿੰਗਾਂ X-KDE-Keywords[pl]=pulpit,pulpity,liczba,pulpity wirtualne,wiele pulpitów X-KDE-Keywords[pt]=ecrã,ecrãs,número,ecrã virtual,múltiplos ecrãs,paginador,elemento paginador,'applet' do paginador,configuração do paginador X-KDE-Keywords[pt_BR]=área de trabalho,áreas de trabalho,desktop,desktops,número,área de trabalho virtual,múltiplas áreas de trabalho,paginador,elemento paginador,miniaplicativo do paginador,configurações do paginador X-KDE-Keywords[ru]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,рабочий стол,рабочие столы,число,виртуальный рабочий стол,несколько рабочих столов,переключатель,переключение,виджет переключения,аплет переключения,параметры переключения,настройки переключения X-KDE-Keywords[sk]=plocha,plochy,číslo,virtuálna plocha,viac plôch,pager,widget pagera,applet pagera,nastavenia pagera X-KDE-Keywords[sl]=namizje,namizja,število namizij,navidezna namizja,več namizij,pozivnik X-KDE-Keywords[sr]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,површ,број,виртуелна површ,више површи,листач,виџет листача,аплет листача,поставке листача X-KDE-Keywords[sr@ijekavian]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,површ,број,виртуелна површ,више површи,листач,виџет листача,аплет листача,поставке листача X-KDE-Keywords[sr@ijekavianlatin]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,površ,broj,virtuelna površ,više površi,listač,vidžet listača,aplet listača,postavke listača X-KDE-Keywords[sr@latin]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,površ,broj,virtuelna površ,više površi,listač,vidžet listača,aplet listača,postavke listača X-KDE-Keywords[sv]=skrivbord,antal,virtuellt skrivbord,flera skrivbord,skrivbordsvisning,visningskomponent,visningsminiprogram,visningsinställningar X-KDE-Keywords[tr]=masaüstü,masaüstleri,sayı,sanal masaüstü,çoklu masaüstü,sayfalayıcı,sayfalayıcı gereci,sayfalayıcı gereci,sayfalayıcı ayarları X-KDE-Keywords[uk]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,стільниця,стільниці,кількість,віртуальна стільниця,перемикач,пейджер,віджет перемикача,віджет пейджера,аплет перемикання,аплет перемикача,параметри перемикання,параметри перемикача X-KDE-Keywords[x-test]=xxdesktopxx,xxdesktopsxx,xxnumberxx,xxvirtual desktopxx,xxmultiple desktopsxx,xxpagerxx,xxpager widgetxx,xxpager appletxx,xxpager settingsxx X-KDE-Keywords[zh_CN]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,桌面,虚拟桌面,多桌面,分页,分页器,分页器组件,分页器设置 X-KDE-Keywords[zh_TW]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings diff --git a/kcmkwin/kwindesktop/main.ui b/kcmkwin/kwindesktop/main.ui index b84fda028..8997fdb2f 100644 --- a/kcmkwin/kwindesktop/main.ui +++ b/kcmkwin/kwindesktop/main.ui @@ -1,320 +1,326 @@ KWinDesktopConfigForm 0 0 572 310 0 0 0 0 0 Desktops Layout QFormLayout::AllNonFixedFieldsGrow 0 Here you can set how many virtual desktops you want on your KDE desktop. &Number of desktops: numberSpinBox Here you can set how many virtual desktops you want on your KDE desktop. 1 20 4 true N&umber of rows: rowsSpinBox true + + 1 + + + 20 + Desktop Names Qt::Vertical 20 40 Switching Enable this option if you want keyboard or active desktop border navigation beyond the edge of a desktop to take you to the opposite edge of the new desktop. Desktop navigation wraps around Desktop Effect Animation QFormLayout::ExpandingFieldsGrow 0 Animation: effectComboBox 0 0 0 0 Desktop Switch On-Screen Display true false QFormLayout::AllNonFixedFieldsGrow 0 Duration: popupHideSpinBox msec 5000 50 Enabling this option will show a small preview of the desktop layout indicating the selected desktop. Show desktop layout indicators Shortcuts QFrame::NoFrame QFrame::Plain 0 Show shortcuts for all possible desktops KWin::DesktopNamesWidget QWidget
desktopnameswidget.h
1 numberChanged(int)
numberSpinBox valueChanged(int) desktopNames numberChanged(int) 327 144 326 209
diff --git a/kcmkwin/kwinoptions/kwinactions.desktop b/kcmkwin/kwinoptions/kwinactions.desktop index 6eba0788a..69c67b311 100644 --- a/kcmkwin/kwinoptions/kwinactions.desktop +++ b/kcmkwin/kwinoptions/kwinactions.desktop @@ -1,188 +1,188 @@ [Desktop Entry] Icon=preferences-system-windows-action Type=Service X-KDE-ServiceTypes=KCModule Exec=kcmshell5 kwinactions X-DocPath=kcontrol/windowbehaviour/index.html#titlebar-actions Icon=preferences-system-windows-actions X-KDE-Library=kcm_kwinoptions X-KDE-PluginKeyword=kwinactions Name=Actions Name[af]=Aksies Name[ar]=إجراءات Name[be]=Дзеянні Name[be@latin]=Aperacyi Name[bg]=Действия Name[bn]=কাজ Name[bn_IN]=কর্ম Name[br]=Oberoù Name[bs]=Radnje Name[ca]=Accions Name[ca@valencia]=Accions Name[cs]=Činnosti Name[csb]=Dzejania Name[cy]=Gweithredoedd Name[da]=Handlinger Name[de]=Aktionen Name[el]=Ενέργειες Name[en_GB]=Actions Name[eo]=Agoj Name[es]=Acciones Name[et]=Tegevused Name[eu]=Ekintzak Name[fa]=کنشها Name[fi]=Toiminnot Name[fr]=Actions Name[fy]=Aksjes Name[ga]=Gníomhartha Name[gl]=Accións Name[gu]=ક્રિયાઓ Name[he]=פעולות Name[hi]=क्रियाएं Name[hne]=काम Name[hr]=Aktivnosti Name[hu]=Műveletek Name[ia]=Actiones Name[id]=Aksi Name[is]=Aðgerðir Name[it]=Azioni Name[ja]=動作 Name[ka]=ქცევა Name[kk]=Амалдар Name[km]=អំពើ Name[kn]=ಕ್ರಿಯೆಗಳು Name[ko]=동작 Name[ku]=Çalakî Name[lt]=Veiksmai Name[lv]=Darbības Name[mai]=क्रियासभ Name[mk]=Акции Name[ml]=പ്രവര്‍ത്തനങ്ങള്‍ Name[mr]=क्रिया Name[ms]=Tindakan Name[nb]=Handlinger Name[nds]=Akschonen Name[ne]=कार्य Name[nl]=Acties Name[nn]=Handlingar Name[oc]=Accions Name[pa]=ਕਾਰਵਾਈਆਂ Name[pl]=Działania Name[pt]=Acções Name[pt_BR]=Ações Name[ro]=Acțiuni Name[ru]=Действия Name[se]=Doaimmat Name[si]=ක්‍රියා Name[sk]=Akcie Name[sl]=Dejanja Name[sr]=Радње Name[sr@ijekavian]=Радње Name[sr@ijekavianlatin]=Radnje Name[sr@latin]=Radnje Name[sv]=Åtgärder Name[ta]=செயல்கள் Name[te]=చర్యలు Name[tg]=Амалҳо Name[th]=การกระทำ Name[tr]=Eylemler Name[ug]=مەشغۇلاتلار Name[uk]=Дії Name[uz]=Amallar Name[uz@cyrillic]=Амаллар Name[vi]=Hành động Name[wa]=Accions Name[xh]=Iintshukumo Name[x-test]=xxActionsxx Name[zh_CN]=动作 Name[zh_TW]=動作 Comment=Mouse Actions on Windows Comment[bs]=Akcioje miša na prozorima Comment[ca]=Accions del ratolí en les finestres Comment[ca@valencia]=Accions del ratolí en les finestres Comment[cs]=Činnosti myši na oknech Comment[da]=Musehandlinger på vinduer Comment[de]=Maus-Aktionen für Fenster Comment[el]=Ενέργειες ποντικιού στα παράθυρα Comment[en_GB]=Mouse Actions on Windows Comment[es]=Acciones del ratón sobre las ventanas Comment[et]=Hiiretoimingud akendes Comment[eu]=Sagu-ekintzak leihoetan Comment[fi]=Ikkunoiden hiiritoiminnot Comment[fr]=Actions de souris sur les fenêtres Comment[gl]=Accións do rato nas xanelas Comment[he]=הגדרות פעולות עכבר Comment[hu]=Egérműveletek az ablakokon Comment[ia]=Actiones de mus sur fenestras -Comment[id]=Aksi Tetikus di Jendela +Comment[id]=Aksi Mouse di Jendela Comment[it]=Azioni del mouse sulle finestre Comment[ja]=ウインドウ上でのマウスアクション Comment[ko]=창 마우스 동작 설정 Comment[lt]=Pelės veiksmai ant langų Comment[nb]=Musehandlinger på vinduer Comment[nds]=Muusakschonen för Finstern fastleggen Comment[nl]=Muisacties op vensters Comment[nn]=Musehandlingar på vindauge Comment[pa]=ਵਿੰਡੋਜ਼ ਉੱਤੇ ਮਾਊਸ ਐਕਸ਼ਨ Comment[pl]=Działania myszy na oknach Comment[pt]=Acções do Rato nas Janelas Comment[pt_BR]=Ações do mouse nas janelas Comment[ru]=Настройка действий мыши для окон Comment[sk]=Akcie myši na oknách Comment[sl]=Dejanja miške na oknih Comment[sr]=Радње мишем над прозорима Comment[sr@ijekavian]=Радње мишем над прозорима Comment[sr@ijekavianlatin]=Radnje mišem nad prozorima Comment[sr@latin]=Radnje mišem nad prozorima Comment[sv]=Musåtgärder för fönster Comment[tr]=Pencerelerde Fare Eylemleri Comment[uk]=Дії над вікнами за допомогою миші Comment[x-test]=xxMouse Actions on Windowsxx Comment[zh_CN]=窗口的鼠标动作 Comment[zh_TW]=視窗上的滑鼠動作 X-KDE-Keywords=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize X-KDE-Keywords[bs]=sjena,povećali,povećala,smanjiti,umanjiti,sniziti,izbornik operacija,naslovnica,promjena veličine X-KDE-Keywords[ca]=ombra,maximitza,maximitza,minimitza,minimitza,abaixa,menú d'operacions,barra de títol,redimensiona X-KDE-Keywords[ca@valencia]=ombra,maximitza,maximitza,minimitza,minimitza,abaixa,menú d'operacions,barra de títol,redimensiona X-KDE-Keywords[da]=skyg,maksimer,minimer,nedre,operationsmenu,titellinje,ændr størrelse X-KDE-Keywords[de]=Fenstermenü,Fensterheber,Maximieren,Minimieren,Nach oben/unten,Titelleiste,Größe ändern X-KDE-Keywords[el]=σκιά,μεγιστοποίηση,μεγιστοποίηση,ελαχιστοποίηση,ελαχιστοποίηση, χαμηλότερα,μενού λειτουργιών,γραμμή τίτλου,αλλαγή μεγέθους X-KDE-Keywords[en_GB]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize X-KDE-Keywords[es]=sombra,maximizar,maximizar,minimizar,minimizar,inferior,menú de operaciones,barra de título,cambio de tamaño X-KDE-Keywords[et]=varjamine,peitmine,maksimeerimine,minimeerimine,allakerimine,üleskerimine,menüü,tiitliriba,suuruse muutmine X-KDE-Keywords[eu]=bildu,maximizatu,ikonotu,jaitsi,eragiketa-menua,titulu-barra,tamainaz aldatu X-KDE-Keywords[fi]=varjosta,rullaa,suurenna,pienennä,laske,toimintovalikko,otsikkopalkki,muuta kokoa X-KDE-Keywords[fr]=ombre, maximiser, maximise, minimiser, minimise, menu des opérations, barre de titre, redimensionner X-KDE-Keywords[ga]=scáth,scáthaigh,uasmhéadaigh,íosmhéadaigh,íoslaghdaigh,laghdaigh,roghchlár oibríochta,barra teidil,athraigh méid X-KDE-Keywords[gl]=sombra,sombrear,maximizar,minimizar,recoller,menú de operacións, barra de título, redimensionar X-KDE-Keywords[hu]=árnyék,maximalizálás,maximalizálás,minimalizálás,minimalizálás,alacsonyabb,műveletek menü,címsáv,átméretezés X-KDE-Keywords[ia]=tinta,maximisa,maximisa,minimisa,minimisa,plus basse,menu de operationes,barra de titulo, redimensionar -X-KDE-Keywords[id]=bayangan,maksimalkan,maksimalkan,minimalkan,minimalkan,ke bawah,menu operasi,batang judul,ubah ukuran +X-KDE-Keywords[id]=bayangan,maksimalkan,maksimalkan,minimalkan,minimalkan,ke bawah,menu operasi,bilah judul,ubah ukuran X-KDE-Keywords[it]=ombra,massimizza,minimizza,abbassa,menu operazioni,barra del titolo,ridimensiona X-KDE-Keywords[kk]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize X-KDE-Keywords[km]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize X-KDE-Keywords[ko]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize,최대화,최소화,제목 표시줄,크기 조정 X-KDE-Keywords[nb]=rull,maksimer,minimer,senk,handlinger,meny,tittellinje,endre størrelse X-KDE-Keywords[nds]=Inrullen,maximeren,minimeren,na achtern,Akschonenmenü,Titelbalken,Finsternmenü,Grött ännern X-KDE-Keywords[nl]=verdonkeren,maximaliseren,minimaliseren,naar onderen,bedieningsmenu,titelbalk,grootte wijzigen X-KDE-Keywords[nn]=rull,fald saman,fald ut,samanfalding,maksimer,minimer,senk,handlingar,meny,tittellinje,storleiksendring X-KDE-Keywords[pl]=zwiń,maksymalizuj,minimalizuj,obniż,operacje na menu,pasek tytułu,zmień rozmiar X-KDE-Keywords[pt]=enrolar,maximizar,minimizar,baixar,menu de operações,barra de título,dimensionar X-KDE-Keywords[pt_BR]=enrolar,maximizar,minimizar,baixar,menu de operações,barra de título,redimensionar X-KDE-Keywords[ru]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize,свернуть,распахнуть,убрать вниз,меню операций,меню действий,заголовок окна,заголовок,изменить размер X-KDE-Keywords[sk]=tieň,maximalizácia,maximalizovanie,minimalizácia,minimalizovanie,nižsí,ponuka operácií,titulkový pruh,zmeniť veľkosť X-KDE-Keywords[sl]=zvij,povečaj,razpni,pomanjšaj,skrči,dvigni,spusti,naslovna vrstica,spremeni velikost,okenski meni,meni okna X-KDE-Keywords[sr]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize,сенка,максимизуј,минимизуј,спусти,мени радњи,насловна трака,промени величину X-KDE-Keywords[sr@ijekavian]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize,сенка,максимизуј,минимизуј,спусти,мени радњи,насловна трака,промени величину X-KDE-Keywords[sr@ijekavianlatin]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize,senka,maksimizuj,minimizuj,spusti,meni radnji,naslovna traka,promeni veličinu X-KDE-Keywords[sr@latin]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize,senka,maksimizuj,minimizuj,spusti,meni radnji,naslovna traka,promeni veličinu X-KDE-Keywords[sv]=skugga,maximera,minimera,åtgärdsmeny,namnlist,ändra storlek X-KDE-Keywords[tr]=geri yükle, gölgele,büyüt,küçült,aşağı al,işlemler menüsü,başlık çubuğu,yeniden boyutlandır X-KDE-Keywords[uk]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize,тінь,максимізувати,розгорнути,згорнути,нижче,меню дій,заголовок,смужка заголовка,розмір,розміри,зміна розмірів X-KDE-Keywords[x-test]=xxshadexx,xxmaximisexx,xxmaximizexx,xxminimizexx,xxminimisexx,xxlowerxx,xxoperations menuxx,xxtitlebarxx,xxresizexx X-KDE-Keywords[zh_CN]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize,阴影,最大化,最小化,降低,动作,菜单,标题栏,更改大小 X-KDE-Keywords[zh_TW]=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize diff --git a/kcmkwin/kwinoptions/kwinadvanced.desktop b/kcmkwin/kwinoptions/kwinadvanced.desktop index 728fe77cb..37230c440 100644 --- a/kcmkwin/kwinoptions/kwinadvanced.desktop +++ b/kcmkwin/kwinoptions/kwinadvanced.desktop @@ -1,186 +1,186 @@ [Desktop Entry] Icon=preferences-system-windows-actions Type=Service X-KDE-ServiceTypes=KCModule Exec=kcmshell5 kwinadvanced X-DocPath=kcontrol/windowbehaviour/index.html#action-advanced X-KDE-Library=kcm_kwinoptions X-KDE-PluginKeyword=kwinadvanced Name=Advanced Name[af]=Gevorderd Name[ar]=متقدم Name[be]=Асаблівы Name[be@latin]=Asablivaje Name[bg]=Допълнителни Name[bn]=অগ্রসর Name[bn_IN]=উন্নত বৈশিষ্ট্য Name[br]=Barek Name[bs]=Napredno Name[ca]=Avançat Name[ca@valencia]=Avançat Name[cs]=Pokročilé Name[csb]=Awansowóné Name[cy]=Uwch Name[da]=Avanceret Name[de]=Erweitert Name[el]=Για προχωρημένους Name[en_GB]=Advanced Name[eo]=Pliaj Name[es]=Avanzado Name[et]=Muu Name[eu]=Aurreratua Name[fa]=پیشرفته Name[fi]=Lisäasetukset Name[fr]=Avancé Name[fy]=Avansearre Name[ga]=Casta Name[gl]=Avanzado Name[gu]=ઉચ્ચ Name[he]=הגדרות מתקדמות Name[hi]=विस्तृत Name[hne]=विस्तृत Name[hr]=Napredno Name[hu]=Speciális Name[ia]=Avantiate Name[id]=Lanjutan Name[is]=Ítarlegt Name[it]=Avanzate Name[ja]=詳細 Name[ka]=დამატებით Name[kk]=Жетелеу Name[km]=កម្រិត​ខ្ពស់ Name[kn]=ಪ್ರೌಢ Name[ko]=고급 Name[ku]=Pêşketî Name[lt]=Sudėtingesni Name[lv]=Paplašināti Name[mai]=उन्नत Name[mk]=Напредни Name[ml]=സങ്കീര്‍ണ്ണമായ Name[mr]=प्रगत Name[ms]=Lanjutan Name[nb]=Avansert Name[nds]=Verwiedert Name[ne]=उन्नत Name[nl]=Geavanceerd Name[nn]=Avansert Name[oc]=A_vançat Name[pa]=ਤਕਨੀਕੀ Name[pl]=Zaawansowane Name[pt]=Avançado Name[pt_BR]=Avançado Name[ro]=Avansat Name[ru]=Дополнительно Name[se]=Viiddiduvvon Name[si]=උසස් Name[sk]=Pokročilé Name[sl]=Napredno Name[sr]=Напредно Name[sr@ijekavian]=Напредно Name[sr@ijekavianlatin]=Napredno Name[sr@latin]=Napredno Name[sv]=Avancerat Name[ta]=உயர்நிலை Name[te]=ఆధునాతన Name[tg]=Иловагӣ Name[th]=ขั้นสูง Name[tr]=Gelişmiş Name[ug]=ئالىي Name[uk]=Додатково Name[uz]=Qoʻshimcha Name[uz@cyrillic]=Қўшимча Name[vi]=Nâng cao Name[wa]=Sipepieus Name[xh]=Ebhekisa phambili Name[x-test]=xxAdvancedxx Name[zh_CN]=高级 Name[zh_TW]=進階 Comment=Advanced Window Management Features Comment[bs]=Napredne mogućnosti upravljanja prozoeima Comment[ca]=Característiques avançades per a la gestió de les finestres Comment[ca@valencia]=Característiques avançades per a la gestió de les finestres Comment[cs]=Pokročilé vlastností správy oken Comment[da]=Avancerede vindueshåndteringsegenskaber Comment[de]=Erweiterte Fensterverwaltung Comment[el]=Διαμόρφωση προχωρημένων χαρακτηριστικών της διαχείρισης παραθύρων Comment[en_GB]=Advanced Window Management Features Comment[es]=Funciones avanzadas del gestor de ventanas Comment[et]=Muud aknahalduse omadused Comment[eu]=Leiho kudeaketaren ezaugarri aurreratuak Comment[fi]=Ikkunoinnin lisäominaisuudet Comment[fr]=Fonctionnalités de gestion avancée des fenêtres Comment[gl]=Funcionalidades avanzadas da xestión de xanelas Comment[he]=תכונות ניהול חלונות מתקדמים Comment[hu]=Speciális ablakkezelési szolgáltatások Comment[ia]=Characteristicas avantiate de gestion de fenestra -Comment[id]=Fitur Manajemen Jendela Lanjutan +Comment[id]=Fitur Pengelolaan Jendela Lanjutan Comment[it]=Funzionalità avanzate della gestione delle finestre Comment[ja]=高度なウインドウ管理機能 Comment[ko]=고급 창 관리자 기능 설정 Comment[lt]=Išsamesnės langų tvarkymo savybės Comment[nb]=Funksjoner for avansert vindusbehandling Comment[nds]=Verwiedert Finsterinstellen Comment[nl]=Geavanceerde vensterbeheermogelijkheden Comment[nn]=Avanserte vindaugshandsamarfunksjonar Comment[pa]=ਤਕਨੀਕੀ ਵਿੰਡੋ ਮੈਨਜੇਮੈਂਟ ਫੀਚਰ Comment[pl]=Zaawansowane ustawienia zarządzania oknami Comment[pt]=Funcionalidades de Gestão de Janelas Avançadas Comment[pt_BR]=Recursos avançados de gerenciamento de janelas Comment[ru]=Настройка дополнительных возможностей управления окнами Comment[sk]=Pokročilé možnosti správy okien Comment[sl]=Napredne zmožnosti upravljanja oken Comment[sr]=Напредне могућности управљања прозорима Comment[sr@ijekavian]=Напредне могућности управљања прозорима Comment[sr@ijekavianlatin]=Napredne mogućnosti upravljanja prozorima Comment[sr@latin]=Napredne mogućnosti upravljanja prozorima Comment[sv]=Avancerade fönsterhanteringsfunktioner Comment[tr]=Gelişmiş Pencere Yönetim Özellikleri Comment[uk]=Додаткові можливості з керування вікнами Comment[x-test]=xxAdvanced Window Management Featuresxx Comment[zh_CN]=高级窗口管理特性 Comment[zh_TW]=進階視窗管理功能 X-KDE-Keywords=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior X-KDE-Keywords[bs]=sjenčanje, granične, lebdjenje, aktivne granice, popločavanje, Kartice, tabovanje, prozorno tabovanje, grupiranje prozora, pločica, prozorna pločica, plasman przora, plasman prozorâ, napredo ponašanje prozora X-KDE-Keywords[ca]=ombra,vora,passar per sobre,vores actives,mosaic,pestanyes,pestanyes de finestra,agrupació de les finestres,mosaic de les finestres,col·locació de les finestres,comportament avançat de les finestres X-KDE-Keywords[ca@valencia]=ombra,vora,passar per sobre,vores actives,mosaic,pestanyes,pestanyes de finestra,agrupació de les finestres,mosaic de les finestres,col·locació de les finestres,comportament avançat de les finestres X-KDE-Keywords[da]=kant,hover,aktive kanter,tiling,faneblade,vinduesfaneblade,gruppering af vinduer,vinduesplacering,placering af vinduer,avanceret vinduesopførsel X-KDE-Keywords[de]=Fensterheber,Rand,Überfahren,Aktive Ränder,Kacheln,Unterfenster,Fenstergruppierung,Fensterkachelung,Fensteranordnung,Erweitertes Fensterverhalten X-KDE-Keywords[el]=σκίαση,περίγραμμα,αιώρηση,ενεργά περιγράμματα,παράθεση,στηλοθέτες,στηλοθέτηση,στηλοθέτηση παραθύρων,ομαδοποίηση παραθύρων,παράθεση παραθύρων,τοποθέτηση παραθύρων,τοποθέτηση παραθύρων,προχωρημένη συμπεριφορά παραθύρων X-KDE-Keywords[en_GB]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behaviour X-KDE-Keywords[es]=sombra,borde,pasada,bordes activos,mosaico,pestañas,páginas en pestañas,pestañas de páginas,agrupación de ventanas,ventanas en mosaico,posicionamiento de ventanas,comportamiento avanzado de las ventanas X-KDE-Keywords[et]=varjamine,piire,kohalviibimine,aktiivsed piirded,paanimine,kaardid,aknad kaartidena,akende rühmitamine,akende paanimine,akende paigutus, akende täpne käitumine X-KDE-Keywords[eu]=biltzea,ertza,gainetik pasatzea,ertz aktiboak,lauza, fitxak, leihoen fitxak,leihoak lauza moduan,leihoen kokalekua,leihoen portaera aurreratua X-KDE-Keywords[fi]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior,varjostus,raja,kohdistus,aktiiviset reunat,välilehdet,ikkunoiden ryhmittely,ikkunoiden sijoittelu,ikkunoiden lisäasetukset X-KDE-Keywords[fr]=ombres, bord, survol, bords actifs, mosaïque, onglets, tabulation, changement d'onglet, groupement de fenêtres, mosaïque de fenêtres, placement de fenêtres, comportement avancé des fenêtres X-KDE-Keywords[gl]=sombra,bordo,beira,pasar,bordos activos,beiras activas,lapelas,agrupar xanelas, situación das xanelas, posicionamento das xanelas,comportamento avanzado das xanelas X-KDE-Keywords[hu]=árnyékolás,szegély,lebegés,aktív szegélyek,csempézés,bejárás,ablakbejárás,ablakcsoportosítás,ablakcsempézés,ablakelhelyezés,ablakok elhelyezése,ablak speciális viselkedése X-KDE-Keywords[ia]=umbrar,margine,planante,margines active,con tegulas,schedas,tabbing,tabbing de fenestra,gruppante fenestra,fenestra con tegulas,placiamento de fenestra,placiamento de fenestras, comportamento avantiate de fenestra X-KDE-Keywords[id]=bayangan,batas,melayang,batas aktif,ubin,tab,tab,tab jendela,grup jendela,ubin jendela,penempatan jendela,penempatan jendela,perilaku lanjutan jendela X-KDE-Keywords[it]=ombreggiatura,bordo,sovrapponi,bordi attivi,affiancatura,schede,navigazione schede,finestre a schede,regruppamento finestre,affaincatura finestre,posizionamento finestre,posizionamento delle finestre,comportamento avanzato delle finestre X-KDE-Keywords[kk]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior X-KDE-Keywords[km]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior X-KDE-Keywords[ko]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior,그림자,경계선,호버,지나다니기,타일,탭,창 탭,창 그룹,창 타일,창 위치 X-KDE-Keywords[nb]=-gardinrulling,kant,sveve,aktive kanter,flislegging,faner,vindusfaner,vindusgruppering,vindus-flislegging,vindusplassering,plassering av vinduer,avansert vindusoppførsel X-KDE-Keywords[nds]=Inrullen,Rahmen,sweven,aktive Kanten,kacheln,Paneels,wesseln,Finster,Finsterkoppel,utrichten,verwiedert,Platzeren X-KDE-Keywords[nl]=verduisteren,rand,overzweven,actieve randen,schuin achter elkaar,tabbladen,met tabbladen werken,vensterwisseling,verstergroepering,vensters schuin achter elkaar,vensterplaatsing,plaatsing van vensters,geavanceerd gedrag van vensters X-KDE-Keywords[nn]=opprulling,kant,sveva,aktive kantar,flislegging,faner,vindaugsfaner,vindaugsgruppering,vindaugsflislegging,vindaugsplassering,plassering av vindauge,avansert vindaugsåtferd X-KDE-Keywords[pl]=zwijanie,obramowanie,unoszenie,aktywne obramowania,kafelkowanie,karty,tworzenie kart, umieszczanie okien w kartach,grupowanie okien,kafelkowanie okien,umieszczanie okien, zaawansowane zachowania okien X-KDE-Keywords[pt]=sombra,contorno,passagem,contornos activos,lado-a-lado,páginas,páginas da janela,agrupamento de janelas,janelas lado-a-lado,colocação das janelas,comportamento avançado das janelas X-KDE-Keywords[pt_BR]=sombra,contorno,passagem,contornos ativos,lado a lado,páginas,páginas da janela,agrupamento de janelas,janelas lado a lado,colocação das janelas,comportamento avançado das janelas X-KDE-Keywords[ru]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior,затенение,граница,наведение,активные границы,мозаика,вкладки,окна во вкладках,группировка окон,мозаичный режим,окон,расположение окон,расширенное поведение окон X-KDE-Keywords[sk]=tieňovanie,okraj,prechod,aktívne okraje,dlaždicovanie,karty,kartovanie okien, zoskupovanie okien,dlaždicovanie okien,umiestnenie okna,poloha okien,pokročilé správanie okien X-KDE-Keywords[sl]=zvijanje,rob,obroba,robovi,obrobe,prehod,lebdenje,tlakovanje,zavihki,združevanje oken,tlakovanje oken,postavljanje oken,postavitev oken,napredno obnašanje oken X-KDE-Keywords[sr]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior,сенка,ивица,лебдење,активне ивице,поплочавање,језичци,прозори под језичцима,груписање прозора,поплочавање прозора,постављење прозора,напредног понашање прозора X-KDE-Keywords[sr@ijekavian]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior,сенка,ивица,лебдење,активне ивице,поплочавање,језичци,прозори под језичцима,груписање прозора,поплочавање прозора,постављење прозора,напредног понашање прозора X-KDE-Keywords[sr@ijekavianlatin]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior,senka,ivica,lebdenje,aktivne ivice,popločavanje,jezičci,prozori pod jezičcima,grupisanje prozora,popločavanje prozora,postavljenje prozora,naprednog ponašanje prozora X-KDE-Keywords[sr@latin]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior,senka,ivica,lebdenje,aktivne ivice,popločavanje,jezičci,prozori pod jezičcima,grupisanje prozora,popločavanje prozora,postavljenje prozora,naprednog ponašanje prozora X-KDE-Keywords[sv]=skuggning,kanter,hålla musen över,aktiva kanter,sida vid sida,flikar,fönsterflikar,fönstergruppering,fönster sida vid sida,fönsterplacering,placering av fönster,avancerat fönsterbeteende X-KDE-Keywords[tr]=gölgeleme,geri yükleme,kenarlık,üzerine gelme,etkin kenarlık,döşeme,sekmeler,sekmeleme,pencere sekmeleme,pencere gruplama,pencere döşeme,pencere konumlandırma,pencere yerleşimi,gelişmiş pencere davranışları X-KDE-Keywords[uk]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior,тіні,границі,межі,краї,активні краї,плитка,тайлінґ,вкладки,мозаїка,вікно з вкладками,групування вікон,розташування вікон, додаткові ефекти поведінки X-KDE-Keywords[x-test]=xxshadingxx,xxborderxx,xxhoverxx,xxactive bordersxx,xxtilingxx,xxtabsxx,xxtabbingxx,xxwindow tabbingxx,xxwindow groupingxx,xxwindow tilingxx,xxwindow placementxx,xxplacement of windowsxx,xxwindow advanced behaviorxx X-KDE-Keywords[zh_CN]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior,阴影,边框,悬停,激活边界,平铺,标签,窗口标签,窗口分组,平铺窗口,窗口位置,窗口高级行为 X-KDE-Keywords[zh_TW]=shading,border,hover,active borders,tiling,tabs,tabbing,window tabbing,window grouping,window tiling,window placement,placement of windows,window advanced behavior diff --git a/kcmkwin/kwinoptions/kwinfocus.desktop b/kcmkwin/kwinoptions/kwinfocus.desktop index 3761af8e0..947abf6a3 100644 --- a/kcmkwin/kwinoptions/kwinfocus.desktop +++ b/kcmkwin/kwinoptions/kwinfocus.desktop @@ -1,182 +1,182 @@ [Desktop Entry] Icon=preferences-system-windows Type=Service X-KDE-ServiceTypes=KCModule Exec=kcmshell5 kwinfocus X-DocPath=kcontrol/windowbehaviour/index.html#action-focus X-KDE-Library=kcm_kwinoptions X-KDE-PluginKeyword=kwinfocus Name=Focus Name[af]=Fokus Name[ar]=التركيز Name[be]=Фокус Name[be@latin]=Fokus Name[bg]=Фокус Name[bn]=ফোকাস Name[br]=Fokus Name[bs]=Fokus Name[ca]=Focus Name[ca@valencia]=Focus Name[cs]=Zaměření Name[csb]=Zrëszanié Name[cy]=Canolbwynt Name[da]=Fokus Name[de]=Aktivierung Name[el]=Εστίαση Name[en_GB]=Focus Name[eo]=Fokuso Name[es]=Foco Name[et]=Fookus Name[eu]=Fokua Name[fa]=کانون Name[fi]=Kohdistus Name[fr]=Focus Name[fy]=Focus Name[ga]=Fócas Name[gl]=Foco Name[gu]=ધ્યાન Name[he]=התמקדות Name[hi]=फ़ोकस Name[hne]=फोकस Name[hr]=Fokus Name[hu]=Fókuszálás Name[ia]=Foco Name[id]=Fokus Name[is]=Virkni Name[it]=Attivazione Name[ja]=フォーカス Name[ka]=ფოკუსი Name[kk]=Назар Name[km]=ផ្ដោត Name[kn]=ನಾಭೀಕರಿಸು (ಫೋಕಸ್) Name[ko]=초점 Name[ku]=Nîvend Bike Name[lt]=Fokusas Name[lv]=Fokuss Name[mai]=फोकस Name[mk]=Фокусирање Name[ml]=ഫോക്കസ് Name[mr]=केंद्र Name[ms]=Fokus Name[nb]=Fokus Name[nds]=Fokus Name[ne]=फोकस Name[nl]=Focus Name[nn]=Fokus Name[pa]=ਫੋਕਸ Name[pl]=Uaktywnianie Name[pt]=Foco Name[pt_BR]=Foco Name[ro]=Focalizare Name[ru]=Фокус Name[se]=Fohkus Name[si]=නාඹිගත කරන්න Name[sk]=Zameranie Name[sl]=Žarišče Name[sr]=Фокус Name[sr@ijekavian]=Фокус Name[sr@ijekavianlatin]=Fokus Name[sr@latin]=Fokus Name[sv]=Fokus Name[ta]=முனைப்படுத்து Name[te]=దృష్టి Name[tg]=Фокус Name[th]=การโฟกัส Name[tr]=Odaklama Name[ug]=فوكۇس Name[uk]=Фокус Name[uz]=Fokus Name[uz@cyrillic]=Фокус Name[vi]=Tập trung Name[wa]=Focus Name[xh]=Focus Name[x-test]=xxFocusxx Name[zh_CN]=焦点 Name[zh_TW]=焦點 Comment=Active Window Policy Comment[bs]=Pravila aktivnih prozora Comment[ca]=Política de la finestra activa Comment[ca@valencia]=Política de la finestra activa Comment[cs]=Chování aktivního okna Comment[da]=Politik for aktivt vindue Comment[de]=Richtlinie für aktives Fenster Comment[el]=Πολιτική ενεργού παραθύρου Comment[en_GB]=Active Window Policy Comment[es]=Política de la ventana activa Comment[et]=Aktiivse akna reegel Comment[eu]=Leiho aktiboentzako politika Comment[fi]=Aktiivisen ikkunan valintatapa Comment[fr]=Politique pour les fenêtres actives Comment[gl]=Política da xanela activa Comment[he]=מדיניות חלון פעיל Comment[hu]=Aktív ablakok szabályai Comment[id]=Kebijakan Jendela Aktif Comment[it]=Politica finestra attiva Comment[ko]=활성 창 정책 Comment[lt]=Aktyvus lango taisyklės Comment[nb]=ActiveWindow-styring Comment[nds]=Regel för't aktive Finster Comment[nl]=Beleid voor actief venster Comment[nn]=Aktiv vindaugsstyring Comment[pa]=ਸਰਗਰਮ ਵਿੰਡੋ ਪਾਲਸੀ Comment[pl]=Zasady aktywowania okna Comment[pt]=Política da Janela Activa Comment[pt_BR]=Política da janela ativa Comment[ru]=Правила смены активного окна Comment[sk]=Politika aktívneho okna Comment[sl]=Pravilnik dejavnih oken Comment[sr]=Смерница активирања прозора Comment[sr@ijekavian]=Смерница активирања прозора Comment[sr@ijekavianlatin]=Smernica aktiviranja prozora Comment[sr@latin]=Smernica aktiviranja prozora Comment[sv]=Aktiv fönsterprincip Comment[tr]=Etkin Pencere Politikası Comment[uk]=Правила для задіяння вікон Comment[x-test]=xxActive Window Policyxx Comment[zh_CN]=活动窗口策略 Comment[zh_TW]=作用中視窗政策 X-KDE-Keywords=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior X-KDE-Keywords[bs]=fokus, smještaj, automatski rast, rast, kliknite na rast, tastatura, CDE, Alt-Tab, cijeli desktop, fokus slijedi miša, fokus prevenciju, usredotoči krade, fokus politike, fokus prozora ponašanje, prozor zaslon ponašanje X-KDE-Keywords[ca]=focus,col·locació,elevació automàtica,elevació,elevació en clic,teclat,CDE,alt-tab,tots els escriptoris,focus segueix el ratolí,prevenció de focus,robatori de focus,política de focus,comportament del focus de la finestra,comportament en pantalla de la finestra X-KDE-Keywords[ca@valencia]=focus,col·locació,elevació automàtica,elevació,elevació en clic,teclat,CDE,alt-tab,tots els escriptoris,focus segueix el ratolí,prevenció de focus,robatori de focus,política de focus,comportament del focus de la finestra,comportament en pantalla de la finestra X-KDE-Keywords[da]=fokus,placering,autohæv,hæv,klikhæv,tastatur,CDE,alt-tab,alle skriveborde,fokus følger mus,fokusforhindring,stjæler fokus,fokuspolitik,opførsel for vinduesfokus X-KDE-Keywords[de]=Fokus,Aktivierung,Anordnung,Platzierung,Automatisch nach vorne,Auf Klick nach vorne,Tastatur,CDE,Alt-Tab,Alle Arbeitsflächen,Aktivierung bei Mauskontakt,Vorbeugung gegen unerwünschte Aktivierung,Aktivierungsregel,Aktivierungsverhalten des Fensters,Fensterverhalten X-KDE-Keywords[el]=εστίαση,τοποθέτηση,αυτόματη αύξηση,αύξηση,αύξηση κλικ,πληκτρολόγιο,CDE,alt-tab,όλες οι επιφάνειες εργασίας,εστίαση ακολουθεί ποντίκι,πρόληψη εστίασης,κλοπή εστίασης,πολιτική εστίασης,συμπεριφορά εστίασης παραθύρων,συμπεριφορά οθόνης παραθύρων X-KDE-Keywords[en_GB]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behaviour,window screen behaviour X-KDE-Keywords[es]=foco,posicionamiento,auto levantar,levantar,clic para levantar,teclado,CDE,alt-tab,todo el escritorio,el foco sigue al ratón,prevención de foco,robo del foco,política del foco,comportamiento del foco de las ventanas,comportamiento de la pantalla de ventanas X-KDE-Keywords[et]=fookus,asetus,paigutus,automaatne esiletoomine,klõpsuga esiletoomine,klaviatuur,CDE,alt-tab,kõik töölauad,fookus järgib hiirt,fookuse vältimine,fookuse röövimine,fookuse reegel,akna fookuse käitumine X-KDE-Keywords[eu]=fokua,kokaleku,automatikoki igo,igo,egin klik igotzeko,teklatu,DE,alt-tab,mahaigain guztiak,fokuak saguari jarraitzen dio,foku-prebentzioa,foku-lapurreta,fokuaren gidalerro,leihoen fokuaren portaera,leihoen pantailen portaera X-KDE-Keywords[fi]=kohdistus,sijoitus,automaattinen nosto,automaattinen nostaminen,nosta,nosta napsauttamalla,näppäimistö,alt-sarkain,kaikki työpöydät,kohdistus seuraa hiirtä,kohdistuksen esto,kohdistuksen varastaminen,kohdistustapa,ikkunoiden kohdistuksen toiminta,ikkunoiden näyttötoiminta,focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior X-KDE-Keywords[fr]=focus, placement, agrandissement automatique, agrandissement, clic d'agrandissement, clavier, CDE, alt-tab, tous les bureaux, focus suivi par la souris, prise de focus, politique de focus, comportement du focus des fenêtres, comportement des fenêtres X-KDE-Keywords[gl]=foco,posicionamento,erguer automaticamente,erguer,erguer ao premer,teclado,CDE,alt-tab,todo o escritorio,foco que segue o rato,prevención do foco,roubar o foco,política de foco,comportamento de foco de xanela,comportamento de pantalla de xanela X-KDE-Keywords[hu]=fókusz,elhelyezés,automatikus felemelés,felemelés,kattintásra felemelés,billentyűzet,CDE,alt-tab,összes asztal,egérkövető fókusz,fókuszmegelőzés,fókuszlopás,fókusz házirend,ablakfókusz működése,ablakképernyő működése X-KDE-Keywords[ia]=focus,placiamento,auto raise,raise,click raise,clavierp,CDE,alt-tab,all desktop,focus seque mus,prevention de focus,focus stealing,politica de focus,comportamento de foco de fenestra,comportamento de schermo de fenestra -X-KDE-Keywords[id]=fokus,penempatan,naikkan otomatis,naikkan,klik naikkan,papan ketik,CDE,alt-tab,semua desktop,fokus mengikuti tetikus,pencegahan fokus,pencurian fokus,kebijakan fokus,perilaku fokus jendela,perilaku layar jendela +X-KDE-Keywords[id]=fokus,penempatan,naikkan otomatis,naikkan,klik naikkan,papan ketik,CDE,alt-tab,semua desktop,fokus mengikuti mouse,pencegahan fokus,pencurian fokus,kebijakan fokus,perilaku fokus jendela,perilaku layar jendela X-KDE-Keywords[it]=fuoco,posizionamento,avanzamento automatico,avanzamento,avanzamento con clic,tastiera,CDE,alt-tab,tutti i desktop,il fuco segue il mouse,impedisci il fuoco,mantieni il fuoco,regole fuoco,regole fuoco finestra,comportamento finestra X-KDE-Keywords[kk]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior X-KDE-Keywords[km]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior X-KDE-Keywords[ko]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior,초점,위치,키보드,모든 데스크톱,초점,초점 훔치기,초점 훔치기 방지,초점 정책,창 초점 행동,창 화면 행동 X-KDE-Keywords[nb]=fokus,plassering,autohev,hev,klikk-hev,tastatur,CDE,alt-tab,alle skrivebord,fokus følger mus,fokushindring,fokus-stjeling,fokuspraksis,fokusoppførsel for vinduer,vindusoppførsel på skjerm X-KDE-Keywords[nds]=Fokus,Platzeren,automaatsch,na vörn,op Klick,Tastatuur,CDE,Alt-Tab,all Schriefdischen,Muusfokus,verhöden,verleren,Fokusregel,Schirm,bedregen X-KDE-Keywords[nl]=focus,plaatsing,automatisch omhoog komen,omhoog komen,omhoog komen bij klikken,toetsenbord,CDE,alt-tab,alle bureaubladen,focus volgt muis,voorkomen van focus,focus stelen,focusbeleid,focusgedrag in venster,gedrag van vensterscherm X-KDE-Keywords[nn]=fokus,plassering,autohev,hev,klikk-og-hev,tastatur,CDE,alt-tab,alle skrivebord,fokus følgjer mus,fokushindring,fokussteling,fokuspraksis,fokusåtferd for vindauge,vindaugsåtferd på skjerm X-KDE-Keywords[pl]=uaktywnienie,umieszczenie,auto wznoszenie,wznoszenie,wznoszenie na kliknięcie,klawiatura,CDE,alt-tab,wszystkie pulpity X-KDE-Keywords[pt]=foco,colocação,elevação automática,elevar,elevar ao carregar,teclado,CDE,alt-tab,todos os ecrãs,foco segue o rato,prevenção do foco,roubo do foco,política de foco,comportamento do foco da janela,comportamento da janela no ecrã X-KDE-Keywords[pt_BR]=foco,colocação,elevação automática,elevar,elevar ao clicar,teclado,CDE,alt-tab,todas as áreas de trabalho,foco segue o mouse,prevenção do foco,captura do foco,política de foco,comportamento do foco da janela,comportamento da janela na tela X-KDE-Keywords[ru]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior,фокус,размещение,автоматически,поднятие,поднимать,клавиатура,весь рабочий стол,фокус следует за мышью,фокус под мышью,похищение фокуса,предотвращение перехвата фокуса,поведение фокуса,окон,поведение экрана X-KDE-Keywords[sk]=zameranie,umiestnenie,automatické zdvihnutie,zdvihnutie,klik na zdvihnutie,klávesnica,CDE,alt-tab, všetky plochy,zameranie nasleduje mys,predchádzanie zameraniu,kradnutie zamerania,politika zamerania, správania zamerania okien,správania okien obrazovky X-KDE-Keywords[sl]=fokus,žarišče,postavitev,postavljanje,samodejni dvig,samodejno dvigovanje,dvig,dvigovanje,dvig na klik,tipkovnica,cde,celotno namizje,fokus sledi miški,žarišče sledi miškipreprečevanje fokusa,preprečevanje žarišča,kraja fokusa,kraja žarišča,pravila fokusiranja,pravila za žarišče,obnašanje pri fokusiranju oken,obnašanje pri postavljanju oken v žarišče,obnašanje oken X-KDE-Keywords[sr]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior,фокус,постављење,аутоматско дизање,дизање,дизање кликом,тастатура,ЦДЕ,Alt-Tab,све површи,фокус прати миш,спречавање фокуса,крађа фокуса,смерница фокуса,понашање фокусирања прозора X-KDE-Keywords[sr@ijekavian]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior,фокус,постављење,аутоматско дизање,дизање,дизање кликом,тастатура,ЦДЕ,Alt-Tab,све површи,фокус прати миш,спречавање фокуса,крађа фокуса,смерница фокуса,понашање фокусирања прозора X-KDE-Keywords[sr@ijekavianlatin]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior,fokus,postavljenje,automatsko dizanje,dizanje,dizanje klikom,tastatura,CDE,Alt-Tab,sve površi,fokus prati miš,sprečavanje fokusa,krađa fokusa,smernica fokusa,ponašanje fokusiranja prozora X-KDE-Keywords[sr@latin]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior,fokus,postavljenje,automatsko dizanje,dizanje,dizanje klikom,tastatura,CDE,Alt-Tab,sve površi,fokus prati miš,sprečavanje fokusa,krađa fokusa,smernica fokusa,ponašanje fokusiranja prozora X-KDE-Keywords[sv]=fokus,placering,fönsterbeteende,animering,höj,höj automatiskt,höj med klick,CDE,alt-tab,alla skrivbord,fokus följer musen,förhindra fokus,stjäla fokus,fokusprincip,fönsterfokusbeteende,fönsterskärmbeteende X-KDE-Keywords[tr]=odakla,odak,yerleşim,otomatik yükselt,yükselt,tıkla yükselt,klavye,CDE,alt-tab,tüm masaüstleri,odak fareyi takip etsin,odaklama engelleme,odak çalma,odaklama politikası,pencere odaklama davranışı,pencere ekran davranışı X-KDE-Keywords[uk]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior,фокус,фокусування,розташування,автопідняття,підняття,клацання,клавіатура,альт-таб,всі стільниці,фокус за мишею,запобігання,перехід фокуса,правила фокусування,поведінка вікон X-KDE-Keywords[x-test]=xxfocusxx,xxplacementxx,xxauto raisexx,xxraisexx,xxclick raisexx,xxkeyboardxx,xxCDExx,xxalt-tabxx,xxall desktopxx,xxfocus follows mousexx,xxfocus preventionxx,xxfocus stealingxx,xxfocus policyxx,xxwindow focus behaviorxx,xxwindow screen behaviorxx X-KDE-Keywords[zh_CN]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior,焦点,位置,自动升起,升起,点击升起,键盘,全部桌面,焦点跟随鼠标,偷取焦点,焦点策略,窗口焦点行为 X-KDE-Keywords[zh_TW]=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop,focus follows mouse,focus prevention,focus stealing,focus policy,window focus behavior,window screen behavior diff --git a/kcmkwin/kwinoptions/kwinoptions.desktop b/kcmkwin/kwinoptions/kwinoptions.desktop index 4113e6a9d..09cf35f3a 100644 --- a/kcmkwin/kwinoptions/kwinoptions.desktop +++ b/kcmkwin/kwinoptions/kwinoptions.desktop @@ -1,189 +1,189 @@ [Desktop Entry] Exec=kcmshell5 kwinoptions Icon=preferences-system-windows-actions Type=Service X-KDE-ServiceTypes=KCModule X-DocPath=kcontrol/windowbehaviour/index.html X-KDE-Library=kcm_kwinoptions X-KDE-PluginKeyword=kwinoptions X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=windowmanagement X-KDE-Weight=40 Name=Window Behavior Name[af]=Venstergedrag Name[ar]=سلوك النوافذ Name[be]=Паводзіны вокнаў Name[be@latin]=Pavodziny akna Name[bg]=Поведение на прозорците Name[bn]=উইণ্ডো আচরণ Name[bn_IN]=উইন্ডোর আচরণ Name[br]=Emzalc'h ar prenester Name[bs]=Ponašanje prozora Name[ca]=Comportament de les finestres Name[ca@valencia]=Comportament de les finestres Name[cs]=Chování oken Name[csb]=Ùchòwanié òkna Name[cy]=Ymddygiad Ffenestri Name[da]=Vinduesopførsel Name[de]=Fensterverhalten Name[el]=Συμπεριφορά παραθύρων Name[en_GB]=Window Behaviour Name[eo]=Fenestrokonduto Name[es]=Comportamiento de la ventana Name[et]=Akende käitumine Name[eu]=Leihoaren portaera Name[fa]=رفتار پنجره Name[fi]=Ikkunoiden toiminta Name[fr]=Comportement des fenêtres Name[fy]=Finstergedrach Name[ga]=Oibriú na bhFuinneog Name[gl]=Comportamento das xanelas Name[gu]=વિન્ડો વર્તણૂક Name[he]=התנהגות חלונות Name[hi]=विंडो व्यवहार Name[hne]=विंडो व्यवहार Name[hr]=Ponašanje prozora Name[hu]=Ablakműveletek Name[ia]=Comportamento de fenestra Name[id]=Perilaku Jendela Name[is]=Hegðun glugga Name[it]=Comportamento delle finestre Name[ja]=ウィンドウの挙動 Name[ka]=ფანჯრის ქცევა Name[kk]=Терезе қасиеттері Name[km]=ឥរិយាបថ​បង្អួច Name[kn]=ಕಿಟಕಿ ವರ್ತನೆ Name[ko]=창 동작 Name[ku]=Helwesta Paceyan Name[lt]=Langų elgsena Name[lv]=Logu izturēšanās Name[mai]=विंडो व्यवहार Name[mk]=Однесување на прозорци Name[ml]=ജാലകത്തിന്റെ വിശേഷത Name[mr]=चौकट वर्तन Name[nb]=Vindusoppførsel Name[nds]=Finsterbedregen Name[ne]=सञ्झ्याल व्यवहार Name[nl]=Venstergedrag Name[nn]=Vindaugs­åtferd Name[pa]=ਵਿੰਡੋ ਰਵੱਈਆ Name[pl]=Zachowania okien Name[pt]=Comportamento das Janelas Name[pt_BR]=Comportamento das janelas Name[ro]=Comportament fereastră Name[ru]=Поведение окон Name[se]=Láseláhtten Name[si]=කවුළු හැසිරීම Name[sk]=Správanie okien Name[sl]=Obnašanje oken Name[sr]=Понашање прозора Name[sr@ijekavian]=Понашање прозора Name[sr@ijekavianlatin]=Ponašanje prozora Name[sr@latin]=Ponašanje prozora Name[sv]=Fönsterbeteende Name[ta]=சாளர நடத்தை Name[te]=విండో ప్రవర్తన Name[tg]=Холати тиреза Name[th]=พฤติกรรมของหน้าต่าง Name[tr]=Pencere Davranışı Name[ug]=كۆزنەكنىڭ ئىش-ھەرىكەتلىرى Name[uk]=Поведінка вікон Name[uz]=Oynaning xususiyatlari Name[uz@cyrillic]=Ойнанинг хусусиятлари Name[vi]=Ứng xử của Cửa sổ Name[wa]=Dujhance des fniesses Name[xh]=Ukuziphatha kwe Window Name[x-test]=xxWindow Behaviorxx Name[zh_CN]=窗口行为 Name[zh_TW]=視窗行為 Comment=Window Actions and Behavior Comment[bs]=Akcije i ponašanje prozora Comment[ca]=Accions i comportament de les finestres Comment[ca@valencia]=Accions i comportament de les finestres Comment[cs]=Činnosti a chování oken Comment[da]=Vindueshandlinger og -opførsel Comment[de]=Fenster-Aktionen und -verhalten Comment[el]=Ενέργειες και συμπεριφορά παραθύρου Comment[en_GB]=Window Actions and Behaviour Comment[es]=Acciones y comportamiento de las ventanas Comment[et]=Akende toimingud ja käitumine Comment[eu]=Leihoen ekintzak eta portaera Comment[fi]=Ikkunoiden toiminnot ja toiminta Comment[fr]=Actions et comportement des fenêtres Comment[gl]=Comportamento e accións das xanelas Comment[he]=התנהגויות ופעולות של חלונות Comment[hu]=Ablakműveletek és működés Comment[ia]=Comportamento e actiones de fenestra Comment[id]=Aksi dan Perilaku Jendela Comment[it]=Azioni e comportamento delle finestre Comment[ja]=ウィンドウのアクションと挙動 Comment[ko]=창 동작과 행동 Comment[lt]=Lango veiksmai ir elgsena Comment[nb]=Vindusoppførsel og handlinger Comment[nds]=Finsterakschonen und -bedregen Comment[nl]=Vensteracties en gedrag Comment[nn]=Handlingar og åtferd for vindauge Comment[pa]=ਵਿੰਡੋ ਕਾਰੀਆਂ ਅਤੇ ਰਵੱਈਆ Comment[pl]=Działania i zachowania okien Comment[pt]=Acções e Comportamento das Janelas Comment[pt_BR]=Ações e comportamento das janelas Comment[ru]=Настройка поведения окон Comment[sk]=Akcie a správanie okien Comment[sl]=Dejanja in obnašanje oken Comment[sr]=Понашање прозора и радње над њима Comment[sr@ijekavian]=Понашање прозора и радње над њима Comment[sr@ijekavianlatin]=Ponašanje prozora i radnje nad njima Comment[sr@latin]=Ponašanje prozora i radnje nad njima Comment[sv]=Fönsteråtgärder och beteende Comment[tr]=Pencere Eylem ve Davranışları Comment[uk]=Реакція і поведінка вікон Comment[x-test]=xxWindow Actions and Behaviorxx Comment[zh_CN]=窗口行为 Comment[zh_TW]=視窗動作與行為 X-KDE-Keywords=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick X-KDE-Keywords[bs]=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick,fokus,pozicioniranje,ponašanje prozora,akcije prozora,animacija,podizanje,okvir,naslovna traka X-KDE-Keywords[ca]=focus,emplaçament,comportament de la finestra,accions de la finestra,animació,elevació,elevació automàtica,finestres,marc,barra de títol,clic doble X-KDE-Keywords[ca@valencia]=focus,emplaçament,comportament de la finestra,accions de la finestra,animació,elevació,elevació automàtica,finestres,marc,barra de títol,clic doble X-KDE-Keywords[da]=fokus,placering,vinduesopførsel,vindueshandlinger,animation,hæv,autohæv,vinduesramme,titelbjælke,dobbeltklik X-KDE-Keywords[de]=Aktivierung,Platzierung,Fensterverhalten,Fensteraktionen,Animation,Nach vorn/hinten, Fenster,Rahmen,Umrandung,Titelleiste,Doppelklick X-KDE-Keywords[el]=εστίαση,τοποθέτηση,συμπεριφορά παραθύρου,κίνηση εικόνας,αύξηση,αυτόματη αύξηση,παράθυρα,πλαίσιο,γραμμή τίτλου,διπλό κλικ X-KDE-Keywords[en_GB]=focus,placement,window behaviour,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick X-KDE-Keywords[es]=foco,posicionamiento,comportamiento de las ventanas,acciones de las ventanas,animación,elevación,autoelevación,ventanas,marco,barra de título,doble clic X-KDE-Keywords[et]=fookus,asetus,paigutus,akende käitumine,aknatoimingud,animeerimine,animatsioon,esiletoomine,automaatne esiletoomine,aknad,raam,tiitliriba,topeltklõps X-KDE-Keywords[eu]=foku,kokaleku,leihoen portaera,leiho-ekintzak,animazio,igo,automatikoki igo,leihoak,marko,titulu-barra,klik bikoitz X-KDE-Keywords[fi]=kohdistus,sijoittelu,sijoitus,ikkunoiden toiminta,ikkunoiden toiminnot,animaatio,nosta,automaattinen nosto,ikkunat,kehys,otsikkopalkki,kaksoisnapsautus,tuplanapsautus,kaksoisklikkaus,tuplaklikkaus X-KDE-Keywords[fr]=focus, placement, comportement de la fenêtre, actions sur les fenêtres, animation, agrandissement, agrandissement automatique, fenêtres, cadre, barre de titre, double clic X-KDE-Keywords[gl]=foco,posicionamento,comportamento das xanelas,accións das xanelas, animación,xanelas,moldura,barra de título,marco X-KDE-Keywords[hu]=fókusz,elhelyezés,ablakműködés,ablakműveletek,animáció,felemelés,automatikus felemelés,ablakok,keret,címsor,dupla kattintás X-KDE-Keywords[ia]=focus,placiamento,comportamento de fenestra,actiones de fenestra,animation,altiar,auto altiar,fenestras,quadro,barra de titulo,duple click -X-KDE-Keywords[id]=fokus,penempatan,perilaku jendela,aksi jendela,animasi,naikkan,naikkan otomatis,jendela,bingkai,batang judul,klik ganda +X-KDE-Keywords[id]=fokus,penempatan,perilaku jendela,aksi jendela,animasi,naikkan,naikkan otomatis,jendela,bingkai,bilah judul,klik ganda X-KDE-Keywords[it]=fuoco,posizionamento,comportamento della finestra,azioni delle finestre,animazione,sollevamento,sollevamento automatico,finestre,riquadro,barra del titolo,doppio clic X-KDE-Keywords[ko]=focus,placement,window behavior,animation,raise,auto raise,windows,frame,titlebar,doubleclick,초점,위치,창 행동,애니메이션,올리기,창,프레임,제목 표시줄 X-KDE-Keywords[nb]=fokus,plassering,vindusoppførsel,vindushandlinger,animering,hev,autohev,vinduer,ramme,tittellinje,dobbeltklikk X-KDE-Keywords[nds]=Fokus,Platzeren,Finsterbedregen,Finsterakschonen,Animeren,na vörn,automaatsch,Finstern,Rahmen,Titelbalken,Dubbelklick X-KDE-Keywords[nl]=focus,plaatsing,venstegedrag,vensteracties,animatie,omhoog,automatisch omhoog,vensters,frame,titelbalk,dubbelklik X-KDE-Keywords[nn]=fokus,plassering,vindaugsåtferd,vindaugshandlingar,animering,hev,autohev,vindauge,ramme,tittellinje,dobbeltklikk X-KDE-Keywords[pl]=uaktywnienie,umieszczenie,zachowanie okna,działania okien,animacja,wzniesienie,auto-wzniesienie, okna,ramka,pasek tytułu,podwójne kliknięcie X-KDE-Keywords[pt]=foco,colocação,comportamento da janela,acções das janelas,animação,elevar,elevar automaticamente,janelas,contorno,barra de título,duplo-click X-KDE-Keywords[pt_BR]=foco,colocação,comportamento da janela,ações da janela,animação,elevar,elevar automaticamente,janelas,contorno,barra de título,clique duplo X-KDE-Keywords[ru]=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick,фокус,местоположение,поведение окон,анимация,увеличение,автоувеличение,окна,рамка,заголовок,двойной щелчок,действия над окнами X-KDE-Keywords[sk]=zameranie,umiestnenie,správanie okien,animácia,zdvihnúť,automaticky zdvihnúť,okná,rám,titulkový pruh,dvojklik X-KDE-Keywords[sl]=fokus,žarišče,postavitev,postavljanje,obnašanje oken,dejanja oken,animacija,dvig,samodejni dvig,okna,okvir,naslovna vrstica,dvojni klik,dvoklik X-KDE-Keywords[sr]=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick,фокус,постављење,понашање прозора,радње над прозорима,анимација,подигни,аутоматско подизање,прозор,оквир,насловна трака,двоклик X-KDE-Keywords[sr@ijekavian]=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick,фокус,постављење,понашање прозора,радње над прозорима,анимација,подигни,аутоматско подизање,прозор,оквир,насловна трака,двоклик X-KDE-Keywords[sr@ijekavianlatin]=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick,fokus,postavljenje,ponašanje prozora,radnje nad prozorima,animacija,podigni,automatsko podizanje,prozor,okvir,naslovna traka,dvoklik X-KDE-Keywords[sr@latin]=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick,fokus,postavljenje,ponašanje prozora,radnje nad prozorima,animacija,podigni,automatsko podizanje,prozor,okvir,naslovna traka,dvoklik X-KDE-Keywords[sv]=fokus,placering,fönsterbeteende,animering,höj,höj automatiskt,fönster,ram,namnlist,dubbelklick X-KDE-Keywords[tr]=odak,yerleşim,pencere davranışı,pencere eylemleri,canlandırma,yükselt,otomatik yükselt,pencereler,çerçeve,başlık çubuğu,çift tıklama X-KDE-Keywords[uk]=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick,фокус,розташування,місце,вікно,поведінка,поведінка вікон,дії,реакція,дії з вікнами,реакція вікон,анімація,підняти,підняття,автоматична,автоматично,рамка,заголовок,смужка заголовка,клацання,подвійне X-KDE-Keywords[x-test]=xxfocusxx,xxplacementxx,xxwindow behaviorxx,xxwindow actionsxx,xxanimationxx,xxraisexx,xxauto raisexx,xxwindowsxx,xxframexx,xxtitlebarxx,xxdoubleclickxx X-KDE-Keywords[zh_CN]=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick,焦点,位置,窗口行为,窗口动作,动画,升起,自动升起,窗口,边框,标题栏,双击 X-KDE-Keywords[zh_TW]=focus,placement,window behavior,window actions,animation,raise,auto raise,windows,frame,titlebar,doubleclick Categories=Qt;KDE;X-KDE-settings-looknfeel; diff --git a/kcmkwin/kwinscreenedges/kwinscreenedges.desktop b/kcmkwin/kwinscreenedges/kwinscreenedges.desktop index 21e6c43ba..7496d3995 100644 --- a/kcmkwin/kwinscreenedges/kwinscreenedges.desktop +++ b/kcmkwin/kwinscreenedges/kwinscreenedges.desktop @@ -1,165 +1,165 @@ [Desktop Entry] Exec=kcmshell5 kwinscreenedges Icon=preferences-desktop Type=Service X-KDE-ServiceTypes=KCModule X-DocPath=kcontrol/kwinscreenedges/index.html X-KDE-Library=kcm_kwinscreenedges X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=desktopbehavior X-KDE-Weight=50 Name=Screen Edges Name[ar]=حواف الشاشة Name[ast]=Berbesos de pantalla Name[bg]=Краища на екрана Name[bs]=Ivice ekrana Name[ca]=Vores de la pantalla Name[ca@valencia]=Vores de la pantalla Name[cs]=Hrany obrazovky Name[csb]=Nórtë ekranu Name[da]=Skærmkanter Name[de]=Bildschirmränder Name[el]=Άκρα οθόνης Name[en_GB]=Screen Edges Name[eo]=Ekrananguloj Name[es]=Bordes de pantalla Name[et]=Ekraani servad Name[eu]=Pantailaren ertzak Name[fi]=Näytön reunat Name[fr]=Bords de l'écran Name[fy]=Skerm rânen Name[ga]=Ciumhaiseanna Scáileáin Name[gl]=Bordos da pantalla Name[gu]=સ્ક્રિનનાં ખૂણાઓ Name[he]=קצוות מסך Name[hi]=स्क्रीन सीमाएँ Name[hr]=Rubovi ekrana Name[hu]=Képernyőszélek Name[ia]=Margines de schermo Name[id]=Tepi Layar Name[is]=Skjájaðrar Name[it]=Lati dello schermo Name[ja]=スクリーンエッジ Name[kk]=Экран жиектері Name[km]=គែម​អេក្រង់​ Name[kn]=ತೆರೆ ಅಂಚುಗಳು Name[ko]=화면 경계 Name[lt]=Ekrano kraštai Name[lv]=Ekrāna malas Name[mai]=स्क्रीन किनार Name[mk]=Рабови на екранот Name[ml]=സ്ക്രീന്‍ അതിരുകള്‍ Name[mr]=स्क्रीनच्या कडा Name[nb]=Skjermkanter Name[nds]=Schirmkanten Name[nl]=Schermranden Name[nn]=Skjerm­kantar Name[pa]=ਸਕਰੀਨ ਬਾਹੀਆਂ Name[pl]=Krawędzie ekranu Name[pt]=Extremos do Ecrã Name[pt_BR]=Contornos da tela Name[ro]=Muchiile ecranului Name[ru]=Края экрана Name[si]=තිර මුළු Name[sk]=Okraje obrazovky Name[sl]=Robovi zaslona Name[sr]=Ивице екрана Name[sr@ijekavian]=Ивице екрана Name[sr@ijekavianlatin]=Ivice ekrana Name[sr@latin]=Ivice ekrana Name[sv]=Skärmkanter Name[tg]=Пардаи экран Name[th]=ขอบจอ Name[tr]=Ekran Kenarları Name[ug]=ئېكران گىرۋەكلىرى Name[uk]=Краї екрана Name[wa]=Boirds del waitroûle Name[x-test]=xxScreen Edgesxx Name[zh_CN]=屏幕边缘 Name[zh_TW]=螢幕邊緣 Comment=Active Screen Corners and Edges Comment[bs]=Ivice i uglovi aktivnog ekrana Comment[ca]=Cantonades i vores actives de la pantalla Comment[ca@valencia]=Cantonades i vores actives de la pantalla Comment[cs]=Aktivní rohy a hrany obrazovky Comment[da]=Aktive skærmhjørner og -kanter Comment[de]=Aktive Bildschirmränder Comment[el]=Ενεργές γωνίες και άκρα οθόνης Comment[en_GB]=Active Screen Corners and Edges Comment[es]=Esquinas y bordes de la pantalla activa Comment[et]=Aktiivsed ekraani nurgad ja servad Comment[eu]=Pantaila aktiboaren izkinak eta ertzak Comment[fi]=Näytön kulmien ja reunojen toiminnot Comment[fr]=Bords et coins actifs de l'écran Comment[gl]=Bordos e esquinas activos da pantalla Comment[he]=פינות וקצוות של המסך Comment[hu]=Aktív képernyősarkok és szélek Comment[id]=Sudut dan Tepi Layar Aktif Comment[it]=Angoli e bordi attivi dello schermo Comment[ko]=활성 화면 경계와 꼭지점 Comment[lt]=Aktyvaus ekrano kampai ir kraštinės Comment[nb]=Aktive skjemkanter og hjørner Comment[nds]=Aktive Schirmkanten un -hörns Comment[nl]=Hoeken en randen van het actieve scherm Comment[nn]=Aktive skjermhjørne og skjermkantar Comment[pa]=ਸਰਗਰਮ ਸਕਰੀਨ ਕੋਨੇ ਅਤੇ ਬਾਹੀਆਂ Comment[pl]=Narożniki i krawędzie aktywnego ekranu Comment[pt]=Cantos e Extremos do Ecrã Activo Comment[pt_BR]=Cantos e bordas da tela ativa Comment[ru]=Настройка действий для краёв экрана Comment[sk]=Rohy a okraje aktívneho okna Comment[sl]=Dejavni robovi in koti zaslona Comment[sr]=Активни углови и ивице екрана Comment[sr@ijekavian]=Активни углови и ивице екрана Comment[sr@ijekavianlatin]=Aktivni uglovi i ivice ekrana Comment[sr@latin]=Aktivni uglovi i ivice ekrana Comment[sv]=Aktiva skärmhörn och kanter Comment[tr]=Etkin Ekran Kenar ve Köşeleri Comment[uk]=Активні кути та краї екрана Comment[x-test]=xxActive Screen Corners and Edgesxx Comment[zh_CN]=活动屏幕边缘 Comment[zh_TW]=作用中螢幕角落與邊緣 X-KDE-Keywords=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners X-KDE-Keywords[bs]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners, prozor, menadćer, efekt, uigao, ivica, akcija, prebacivanje, desktop ivice, ivice ekrana, maksimitiranje prozora, ponašanje ekrana, virtualni desktop -X-KDE-Keywords[ca]=kwin,finestra,gestor,efecte,cantonada,vora,borde,acció,canvi,escriptori,vores de pantalla del kwin,vores d'escriptori,vores de pantalla,maximitza finestres,mosaic de les finestres,costat de pantalla,comportament de pantalla,canvi d'escriptori,escriptori virtual,cantonades de la pantalla -X-KDE-Keywords[ca@valencia]=kwin,finestra,gestor,efecte,cantonada,vora,borde,acció,canvi,escriptori,vores de pantalla del kwin,vores d'escriptori,vores de pantalla,maximitza finestres,mosaic de les finestres,costat de pantalla,comportament de pantalla,canvi d'escriptori,escriptori virtual,cantonades de la pantalla +X-KDE-Keywords[ca]=kwin,finestra,gestor,efecte,cantonada,vora,acció,canvi,escriptori,vores de pantalla del kwin,vores d'escriptori,vores de pantalla,maximitza finestres,mosaic de les finestres,costat de pantalla,comportament de pantalla,canvi d'escriptori,escriptori virtual,cantonades de la pantalla +X-KDE-Keywords[ca@valencia]=kwin,finestra,gestor,efecte,cantonada,vora,acció,canvi,escriptori,vores de pantalla del kwin,vores d'escriptori,vores de pantalla,maximitza finestres,mosaic de les finestres,costat de pantalla,comportament de pantalla,canvi d'escriptori,escriptori virtual,cantonades de la pantalla X-KDE-Keywords[da]=kwin,vindue,håndtering,manager,effekt,hjørne,kant,handling,skift,skrivebord,kwin skærmkanter,skrivebordskanter,skærmkanter,maksimer vinduer,tile windows,fliser,felter,siden af skærmen,skærmens opførsel,skift skrivebord,virtuelle skriveborde,skærmhjørner,hjørner X-KDE-Keywords[de]=KWin,Fenster,Verwaltung,Effekt,Kante,Rand,Aktion,Wechseln,Desktop,Arbeitsfläche,KWin Bildschirmkanten,Desktopkanten,Bildschirmkanten,Fenster maximieren,Fenster kacheln,Bildschirmseite,Bildschirmverhalten,Desktop wechseln,Arbeitsfläche wechseln,Virtueller Desktop,Virtuelle Arbeitsfläche,Bildschirmecken X-KDE-Keywords[el]=kwin,παράθυρο,διαχειριστής,εφέ,άκρη,περίγραμμα,ενέργεια,εναλλαγή,επιφάνεια εργασίας,άκρες οθόνης kwin,άκρες επιφάνειας εργασίας,άκρες οθόνης,μεγιστοποίηση παραθύρων,παράθεση παραθύρων,πλευρά οθόνης,συμπεριφορά οθόνης,εναλλαγή επιφάνειας εργασίας,εικονική επιφάνεια εργασίας,γωνίες οθόνης X-KDE-Keywords[en_GB]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximise windows,tile windows,side of screen,screen behaviour,switch desktop,virtual desktop,screen corners X-KDE-Keywords[es]=kwin,ventana,gestor,efecto,esquina,borde,acción,cambiar,escritorio,bordes de pantalla de kwin,bordes del escritorio,bordes de la pantalla,maximizar ventanas,ventanas en mosaico,lado de la pantalla,comportamiento de la pantalla,cambiar escritorio,escritorio virtual,esquinas de la pantalla X-KDE-Keywords[et]=kwin,aken,haldur,efekt.nurk,serv,piire,toiming,lülitamine,töölaud,kwini ekraani servad,töölaua servad,ekraani servad,akende maksimeerimine,akende paanimine,ekraani äär,ekraani käitumine,töölaua lülitamine,virtuaalne töölaud,ekraani nurgad X-KDE-Keywords[eu]=kwin,leiho,kudeatzaile,efektu,izkin,ertz,ekintza,aldatu,mahaigain,kwin pantailaren ertzak,mahaigainaren ertzak,pantailen ertzak,maximizatu leihoak,lauzatu leihoak,leihoaren alboa,pantailaren portaera,aldatu mahaigaina,mahaigain birtuala,alegiazko mahaigaina,pantailaren izkinak X-KDE-Keywords[fi]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,ikkunamanageri,ikkunointiohjelma,tehoste,kulma,reuna,vaihda,vaihto,työpöytä,näytön reunat,työpöydän reunat,suurenna ikkuna,kasaa ikkunat,näytön toiminta,vaihda työpöytää,virtuaalityöpöytä,näytön reunat X-KDE-Keywords[fr]=kwin, fenêtre, gestionnaire, effet, bord, coin, bordure, action, bascule, bureau, bords de l'écran kwin, bords du bureau, bords de l'écran, maximisation des fenêtres, mosaïque de fenêtres, cotés de l'écran, comportement de l'écran, changement de bureau, bureaux virtuels, coins de l'écran X-KDE-Keywords[gl]=kwin,xanela,xestor,efecto,esquina,beira,bordo,bordo,acción,trocar,escritorio,bordo do escritorio,maximizar xanelas,escritorio virtual,esquinas da pantalla X-KDE-Keywords[hu]=kwin,ablak,kezelő,effektus,sarok,szél,szegély,művelet,váltás,asztal,kwin képernyőszél,asztalszél,képernyőszél,ablakok maximalizálása,ablakcím,képernyőoldal,képernyő működése,asztalváltás,virtuális asztal,képernyősarkok X-KDE-Keywords[ia]=kwin,fenestra,gerente,effecto,bordo,margine,action,commuta,scriptorio,bordos de schermo de kwin,bordos de scriptorio,bordos de scriptorio,maximisa fenestras,tegula fenestras,parte de schermo, comportamento de schermo,commuta scriptorio,scriptorio virtual,angulos de schermo -X-KDE-Keywords[id]=kwin,jendela,manajer,efek,sudut,tepi,batas,aksi,ganti,desktop,tepi layar kwin,tepi desktop,tepi layar,maksimalkan jendela,jendela ubin,tepi layar,perilaku layar,ganti desktop,desktop virtual,sudut layar +X-KDE-Keywords[id]=kwin,jendela,pengelola,efek,sudut,tepi,batas,aksi,ganti,desktop,tepi layar kwin,tepi desktop,tepi layar,maksimalkan jendela,jendela ubin,tepi layar,perilaku layar,ganti desktop,desktop virtual,sudut layar X-KDE-Keywords[it]=kwin,finestra,gestore,effetto,angolo,bordo,azione,scambiatore,desktop,bordi schermo kwin,bordi desktop,bordi schermo,massimizza finestre,affianca finestre,lato dello schermo,comportamento schermo,scambia desktop,desktop virtuale,angoli dello schermo X-KDE-Keywords[ko]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,창,관리자,효과,경계,경계선,동작,액션,전환,kwin 화면 경계,화면 경계,창 최대화,최대화,바둑판식 배열,화면 행동,데스크톱 전환,가상 데스크톱,화면 모서리,꼭지점 X-KDE-Keywords[nb]=kwin,vindu,behandler,effekt,kant,hjørne,ramme,handling,bytte,skrivebord,kwin skjermkanter,skrivebordkanter,skjermkanter,maksimere vinduer,flislegge vinduer,skjermside,skjermoppførsel,bytte skrivebord,virtuelt skrivebord,skjermhjørner X-KDE-Keywords[nds]=KWin,Finster,Pleger,Effekt,Hörn,Kant,Rahmen,Akschoon,wesseln,Schriefdisch,maximeren,kacheln,Schirmkant,Schirmbedregen,Schirmhuuk X-KDE-Keywords[nl]=kwin,venster,beheerder,effect,hoek,kant,rand,actie,omschakelen,bureaublad,schermranden van kwin,bureaubladkanten,schermkanten,vensters maximaliseren,venster schuin achter elkaar,zijkant van het scherm,schermgedrag,bureaublad omschakelen,virtueel bureaublad,schermhoeken X-KDE-Keywords[nn]=kwin,vindauge,handsamar,effekt,kant,hjørne,ramme,handling,byte,skrivebord,kwin skjermkantar,skrivebordskantar,skjermkantar,maksimera vindauge,flisleggja vindauge,skjermside,skjermåtferd,byte skrivebord,virtuelt skrivebord,skjermhjørne X-KDE-Keywords[pl]=kwin,okno,menadżer,efekt,narożnik,krawędź,obramowanie,działanie,przełącz,pulpit, krawędzie ekranu kwin,krawędzie pulpitu,krawędzie ekranu,maksymalizacja okien, kafelkowanie okien,strona ekranu,zachowanie ekranu,przełączanie pulpitu,wirtualny pulpit, krawędzie ekranu X-KDE-Keywords[pt]=kwin,janela,gestor,efeito,extremo,contorno,acção,mudar,ecrã,extremos do ecrã no kwin,extremos do ecrã,maximizar as janelas,janelas lado-a-lado,lado do ecrã,comportamento do ecrã,mudar de ecrã,ecrã virtual,cantos do ecrã X-KDE-Keywords[pt_BR]=kwin,janela,gerenciador,efeito,canto,contorno,borda,ação,mudar,área de trabalho,cantos da área de trabalho no kwin,cantos da área de trabalho,maximizar as janelas,janelas lado a lado,lado da tela,comportamento da tela,mudar de área de trabalho virtual,desktop,cantos da tela X-KDE-Keywords[ru]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,окно,окон,диспетчер,эффект,край,граница,действие,переключить,рабочий стол,края экрана kwin,края экрана,края рабочего стола,распахнуть окна,мозаика окон,край экрана,поведение экрана,переключить рабочий стол,виртуальный рабочий стол,углы рабочего стола,углы экрана X-KDE-Keywords[sk]=kwin,okno, správca,efekt,okraj,hrana,akcia,prepnúť,plocha,okraje obrazovky kwin, efekty plochy,okraje obrazovky,maximalizovať okná,dlaždicové okná,strana obrazovky, správanie obrazovky,prepnúť plochu,virtuálna plocha,okraje obrazovky X-KDE-Keywords[sl]=kwin,okno,upravljalnik oken,upravljanje oken,učinki,kot,rob,obroba,dejanje,preklopi,preklapljanje,robovi zaslona,robovi namizja,razpni okna,povečaj okna,tlakuj okna,rob zaslona,obnašanje zaslona,preklopi namizje,preklapljanje namizij,navidezno namizje,koti zaslona X-KDE-Keywords[sr]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,К‑вин,прозор,менаџер,ефекат,угао,ивица,радња,пребаци,површ,ивице екрана,ивице површи,максимизовање прозора,поплочавање прозора,странице прозора,понашање прозора,мењање површи,виртуелна површ,углови екрана X-KDE-Keywords[sr@ijekavian]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,К‑вин,прозор,менаџер,ефекат,угао,ивица,радња,пребаци,површ,ивице екрана,ивице површи,максимизовање прозора,поплочавање прозора,странице прозора,понашање прозора,мењање површи,виртуелна површ,углови екрана X-KDE-Keywords[sr@ijekavianlatin]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,KWin,prozor,menadžer,efekat,ugao,ivica,radnja,prebaci,površ,ivice ekrana,ivice površi,maksimizovanje prozora,popločavanje prozora,stranice prozora,ponašanje prozora,menjanje površi,virtuelna površ,uglovi ekrana X-KDE-Keywords[sr@latin]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,KWin,prozor,menadžer,efekat,ugao,ivica,radnja,prebaci,površ,ivice ekrana,ivice površi,maksimizovanje prozora,popločavanje prozora,stranice prozora,ponašanje prozora,menjanje površi,virtuelna površ,uglovi ekrana X-KDE-Keywords[sv]=kwin,fönster,hanterare,effekt,kant,gräns,åtgärd,byta,skrivbord,kwin skärmkanter,skrivbordskanter,maximera fönster,lägg fönster sida vid sida,skärmsidan, skärmbeteende,skrivbordsbyte,virtuellt skrivbord,skärmhörn X-KDE-Keywords[tr]=kwin,pencere,yönetici,efekt,kenar,kenarlık,eylem,seç,masaüstü,kwin ekran kenarlıkları,kenarlıklar,masaüstü kenarları,ekran kenarları,pencereleri büyüt,pencereleri döşe,ekranın kenarı,ekran davranışı,masaüstünü seç,sanal masaüstü,ekran köşeleri X-KDE-Keywords[uk]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,вікно,керування,край,кут,межа,сторона,бік,дія,перемикання,стільниця,краї екрана,максимізація,мозаїка,плитка,край екрана,поведінка екрана,перемикання стільниць,віртуальна стільниця X-KDE-Keywords[x-test]=xxkwinxx,xxwindowxx,xxmanagerxx,xxeffectxx,xxcornerxx,xxedgexx,xxborderxx,xxactionxx,xxswitchxx,xxdesktopxx,xxkwin screen edgesxx,xxdesktop edgesxx,xxscreen edgesxx,xxmaximize windowsxx,xxtile windowsxx,xxside of screenxx,xxscreen behaviorxx,xxswitch desktopxx,xxvirtual desktopxx,xxscreen cornersxx X-KDE-Keywords[zh_CN]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,窗口,管理,特效,角,边缘,动作,切换,桌面,kwin 屏幕边缘,屏幕边缘,桌面边缘,最大化窗口,平铺窗口,屏幕行为,桌面切换,虚拟桌面,屏幕角落 X-KDE-Keywords[zh_TW]= kwin,window,manager,effect,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners diff --git a/kcmkwin/kwinscreenedges/kwintouchscreen.desktop b/kcmkwin/kwinscreenedges/kwintouchscreen.desktop index 235e19bfd..729300ab6 100644 --- a/kcmkwin/kwinscreenedges/kwintouchscreen.desktop +++ b/kcmkwin/kwinscreenedges/kwintouchscreen.desktop @@ -1,105 +1,115 @@ [Desktop Entry] Exec=kcmshell5 kwintouchscreen Icon=preferences-desktop Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kcm_kwintouchscreen X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=desktopbehavior X-KDE-Weight=50 Name=Touch Screen Name[ca]=Pantalla tàctil Name[ca@valencia]=Pantalla tàctil Name[cs]=Dotyková obrazovka +Name[da]=Touchskærm Name[de]=Touchscreen Name[el]=Οθόνη αφής Name[en_GB]=Touch Screen Name[es]=Pantalla táctil Name[eu]=Ukimen-pantaila Name[fi]=Kosketusnäyttö Name[fr]=Pavé tactile Name[gl]=Pantalla táctil Name[he]=מסך מגע +Name[hu]=Érintőképernyő Name[it]=Schermo a sfioramento +Name[ko]=터치 스크린 Name[lt]=Jutiklinis ekranas Name[nl]=Aanraakscherm Name[nn]=Trykkskjerm Name[pa]=ਟੱਚ ਸਕਰੀਨ Name[pl]=Ekran dotykowy Name[pt]=Ecrã Táctil Name[pt_BR]=Touch Screen Name[ru]=Сенсорный экран Name[sk]=Dotyková obrazovka Name[sl]=Zaslon na dotik Name[sr]=Додирник Name[sr@ijekavian]=Додирник Name[sr@ijekavianlatin]=Dodirnik Name[sr@latin]=Dodirnik Name[sv]=Pekskärm Name[tr]=Dokunmatik Ekran Name[uk]=Сенсорна панель Name[x-test]=xxTouch Screenxx Name[zh_CN]=触摸屏 Name[zh_TW]=觸控螢幕 Comment=Touch screen swipe gestures Comment[ca]=Gestos de lliscament en la pantalla tàctil Comment[ca@valencia]=Gestos de lliscament en la pantalla tàctil +Comment[da]=Strygegestusser til touchskærm Comment[en_GB]=Touch screen swipe gestures Comment[es]=Gestos de deslizamiento en pantalla táctil Comment[eu]=Ukipen-pantailan irrist-keinuak Comment[fi]=Kosketusnäytön pyyhkäisyeleet Comment[fr]=Mouvements sur le pavé tactile Comment[gl]=Xestos de pantalla táctil Comment[he]=מחוות החלקה של מסכי מגע +Comment[hu]=Érintőképernyő-gesztusok Comment[it]=Gesti dello schermo a sfioramento +Comment[ko]=터치 스크린 밀기 제스처 Comment[nl]=Veeggebaren voor aanraakscherm Comment[nn]=Fingerrørsler på trykkskjerm Comment[pa]=ਟੱਚ ਸਕਰੀਨ ਸਕਰਾਉਣ ਜੈਸਚਰ Comment[pl]=Gesty na ekranie dotykowym Comment[pt]=Gestos para deslizar o ecrã táctil Comment[pt_BR]=Gestos no touch screen Comment[ru]=Действия при проведении по сенсорному экрану Comment[sk]=Ťahacie gestá dotykovej obrazovky Comment[sl]=Kretnje vlečenja za zaslon na dotik Comment[sr]=Гестови замаха на додирнику Comment[sr@ijekavian]=Гестови замаха на додирнику Comment[sr@ijekavianlatin]=Gestovi zamaha na dodirniku Comment[sr@latin]=Gestovi zamaha na dodirniku Comment[sv]=Draggester för pekskärm Comment[tr]=Dokunmatik ekran kaydırma hareketleri Comment[uk]=Жести на сенсорній панелі Comment[x-test]=xxTouch screen swipe gesturesxx Comment[zh_CN]=触摸屏滑动手势 Comment[zh_TW]=觸控螢幕滑動手勢 X-KDE-Keywords=kwin,window,manager,effect,edge,border,action,switch,desktop,desktop edges,screen edges,side of screen,screen behavior,touch screen X-KDE-Keywords[ca]=kwin,finestra,gestor,efecte,vora,borde,acció,canvi,escriptori,vores d'escriptori,vores de pantalla,costat de pantalla,comportament de la pantalla,pantalla tàctil X-KDE-Keywords[ca@valencia]=kwin,finestra,gestor,efecte,vora,borde,acció,canvi,escriptori,vores d'escriptori,vores de pantalla,costat de pantalla,comportament de la pantalla,pantalla tàctil +X-KDE-Keywords[da]=kwin,vindue,håndtering,manager,effekt,hjørne,kant,handling,skift,skrivebord,kwin skærmkanter,skrivebordskanter,skærmkanter,maksimer vinduer,tile windows,fliser,felter,siden af skærmen,skærmens opførsel,touch,skift skrivebord,virtuelle skriveborde,skærmhjørner,hjørner +X-KDE-Keywords[de]=KWin,Fenster,Verwaltung,Effekt,Kante,Rand,Aktion,Wechseln,Desktop,Arbeitsfläche,Bildschirmkanten,Desktopkanten,Bildschirmseite,Bildschirmverhalten,Touchscreen X-KDE-Keywords[en_GB]=kwin,window,manager,effect,edge,border,action,switch,desktop,desktop edges,screen edges,side of screen,screen behaviour,touch screen X-KDE-Keywords[es]=kwin,ventana,gestor,efecto,esquina,borde,acción,cambiar,escritorio,bordes del escritorio,bordes de la pantalla,lado de la pantalla,comportamiento de la pantalla,pantalla táctil X-KDE-Keywords[eu]=kwin,leiho,kudeatzaile,efektu,izkin,ertz,ekintza,aldatu,mahaigain,mahaigainaren ertzak,pantailen ertzak,pantailaren aldea,pantailaren portaera,ukipen-pantaila X-KDE-Keywords[fi]=kwin,ikkuna,hallinta,tehoste,kulma,laita,reuna,toiminto,vaihda,työpöytä,työpöydän reunat,näytön reunat,näytön laita,näytön käyttäytyminen,kosketusnäyttö X-KDE-Keywords[fr]=kwin, fenêtre, gestionnaire, effet, bord, bordure, action, bascule, bureau, bords du bureau, bords de l'écran, côté de l'écran, comportement de l'écran, pavé tactile X-KDE-Keywords[gl]=kwin,window,xanela,manager,xestor,effect,efecto,edge,beira,bordo,contorno,esquina,border,action,acción,switch,cambiar,conmutar,trocar,desktop,escritorio,desktop edges,screen edges,pantalla,side of screen,screen behavior,comportamento,touch screen,táctil +X-KDE-Keywords[hu]=kwin,ablak,kezelő,effektus,szél,szegély,művelet,váltás,asztal,asztalszél,képernyőszél,képernyőoldal,képernyő működése,érintőképernyő X-KDE-Keywords[it]=kwin,finestra,gestore,effetto,angolo,bordo,azione,scambiatore,desktop,bordi desktop,bordi schermo,lato dello schermo,comportamento schermo,schermo a sfioramento +X-KDE-Keywords[ko]=kwin,window,manager,effect,edge,border,action,switch,desktop,desktop edges,screen edges,side of screen,screen behavior,touch screen,창,관리자,효과,경계,경계선,동작,액션,데스크톱,화면 경계,경계,터치,터치 스크린,터치스크린 X-KDE-Keywords[nl]=kwin,venster,beheerder,effect,kant,rand,actie,omschakelen,bureaublad,bureaubladkanten,schermkanten,zijkant van het scherm,schermgedrag,aanraakscherm X-KDE-Keywords[nn]=kwin,vindauge,handsamar,effekt,kant,ramme,handling,byte,skrivebord,skrivebordkantar,skjermkantar,skjermside,skjermåtferd,trykkskjerm X-KDE-Keywords[pl]=kwin,okno,menadżer,efekt,krawędź,obramowanie,działanie,przełącz,pulpit,krawędzie pulpitu,krawędzie ekranu,strona ekranu,zachowanie ekranu,ekran dotykowy X-KDE-Keywords[pt]=kwin,janela,gestor,efeito,extremo,contorno,acção,mudar,ecrã,extremos do ecrã no kwin,extremos do ecrã,maximizar as janelas,janelas lado-a-lado,lado do ecrã,comportamento do ecrã,ecrã táctil X-KDE-Keywords[pt_BR]=kwin,janela,gerenciador,efeito,canto,contorno,borda,ação,mudar,área de trabalho,cantos da área de trabalho, desktop,lado da tela,comportamento da tela,touch screen X-KDE-Keywords[ru]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,окно,окон,диспетчер,эффект,край,граница,действие,переключить,рабочий стол,края экрана kwin,края экрана,края рабочего стола,распахнуть окна,мозаика окон,край экрана,поведение экрана,переключить рабочий стол,виртуальный рабочий стол,углы рабочего стола,углы экрана,тачскрин,сенсорный экран,touch screen X-KDE-Keywords[sk]=Kwin, okná, manažér, efekt, okraj, hranice, akcie, prepínač, desktop, okraje plochy,Okraje obrazovky, bočná strana obrazovky, správanie obrazovky, dotyková obrazovka X-KDE-Keywords[sl]=kwin,okno,upravljalnik oken,upravljanje oken,učinki,rob,obroba,dejanje,preklopi,preklapljanje,robovi namizja,robovi zaslona,rob zaslona,obnašanje zaslona,zaslon na dotik X-KDE-Keywords[sr]=kwin,window,manager,effect,edge,border,action,switch,desktop,desktop edges,screen edges,side of screen,screen behavior,touch screen,К‑вин,прозор,менаџер,ефекат,ивица,радња,пребаци,површ,ивице екрана,ивице површи,странице прозора,понашање прозора,додирник X-KDE-Keywords[sr@ijekavian]=kwin,window,manager,effect,edge,border,action,switch,desktop,desktop edges,screen edges,side of screen,screen behavior,touch screen,К‑вин,прозор,менаџер,ефекат,ивица,радња,пребаци,површ,ивице екрана,ивице површи,странице прозора,понашање прозора,додирник X-KDE-Keywords[sr@ijekavianlatin]=kwin,window,manager,effect,edge,border,action,switch,desktop,desktop edges,screen edges,side of screen,screen behavior,touch screen,KWin,prozor,menadžer,efekat,ivica,radnja,prebaci,površ,ivice ekrana,ivice površi,stranice prozora,ponašanje prozora,dodirnik X-KDE-Keywords[sr@latin]=kwin,window,manager,effect,edge,border,action,switch,desktop,desktop edges,screen edges,side of screen,screen behavior,touch screen,KWin,prozor,menadžer,efekat,ivica,radnja,prebaci,površ,ivice ekrana,ivice površi,stranice prozora,ponašanje prozora,dodirnik X-KDE-Keywords[sv]=kwin,fönster,hanterare,effekt,kant,gräns,åtgärd,byta,skrivbord,skrivbordskanter,skärmkanter,skärmsidan,skärmbeteende,pekskärm X-KDE-Keywords[tr]=kwin,pencere,yönetici,efekt,kenar,sınır,eylem,geçiş,masaüstü,masaüstü kenarları,ekran kenarları,ekranın yan tarafı,ekran davranışı,dokunmatik ekran X-KDE-Keywords[uk]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,desktop edges,screen edges,side of screen,screen behavior,touch screen,вікно,керування,край,кут,межа,сторона,бік,дія,перемикання,стільниця,плитка,край екрана,поведінка екрана,перемикання стільниць,віртуальна стільниця,сенсорна панель X-KDE-Keywords[x-test]=xxkwinxx,xxwindowxx,xxmanagerxx,xxeffectxx,xxedgexx,xxborderxx,xxactionxx,xxswitchxx,xxdesktopxx,xxdesktop edgesxx,xxscreen edgesxx,xxside of screenxx,xxscreen behaviorxx,xxtouch screenxx X-KDE-Keywords[zh_CN]=kwin,window,manager,effect,corner,edge,border,action,switch,desktop,kwin screen edges,desktop edges,screen edges,maximize windows,tile windows,side of screen,screen behavior,switch desktop,virtual desktop,screen corners,窗口,管理,特效,角,边缘,动作,切换,桌面,kwin 屏幕边缘,屏幕边缘,桌面边缘,最大化窗口,平铺窗口,屏幕行为,桌面切换,虚拟桌面,屏幕角落 X-KDE-Keywords[zh_TW]=kwin,window,manager,effect,edge,border,action,switch,desktop,desktop edges,screen edges,side of screen,screen behavior,touch screen diff --git a/kcmkwin/kwinscreenedges/main.ui b/kcmkwin/kwinscreenedges/main.ui index 33e21c5d2..b3813f36f 100644 --- a/kcmkwin/kwinscreenedges/main.ui +++ b/kcmkwin/kwinscreenedges/main.ui @@ -1,356 +1,356 @@ KWinScreenEdgesConfigForm 0 0 488 511 - Active Screen Edge Actions + Active Screen Corners and Edges 200 200 Qt::StrongFocus - To trigger an action push your mouse cursor against the edge of the screen in the action's direction. + Trigger an action by pushing the mouse cursor against the corresponding screen edge or corner Qt::AlignCenter true Window Management - Maximize windows by dragging them to the top of the screen + Maximize windows by dragging them to the top edge of the screen - Tile windows by dragging them to the side of the screen + Tile windows by dragging them to the left or right edges of the screen Qt::Horizontal QSizePolicy::Fixed 40 20 false Quarter tiling triggered in the outer electricBorderCornerRatio false % 1 49 false of the screen Qt::Horizontal 40 20 true Other Settings QFormLayout::AllNonFixedFieldsGrow 0 Change desktop when the mouse cursor is pushed against the edge of the screen &Switch desktop on edge: desktopSwitchCombo Disabled Only When Moving Windows Always Enabled Amount of time required for the mouse cursor to be pushed against the edge of the screen before the action is triggered Activation &delay: activationDelaySpin ms 1000 50 0 true Amount of time required after triggering an action until the next trigger can occur &Reactivation delay: triggerCooldownSpin true ms 1000 50 0 Qt::Vertical 20 0 Qt::Vertical 20 0 KComboBox QComboBox
kcombobox.h
KWin::Monitor QWidget
monitor.h
1
monitor desktopSwitchCombo activationDelaySpin triggerCooldownSpin quickTileBox toggled(bool) label_3 setEnabled(bool) 93 306 105 329 quickTileBox toggled(bool) electricBorderCornerRatio setEnabled(bool) 164 312 301 345 quickTileBox toggled(bool) label_4 setEnabled(bool) 220 305 340 329
diff --git a/kcmkwin/kwinscreenedges/touch.ui b/kcmkwin/kwinscreenedges/touch.ui index 13b31206c..997989bb9 100644 --- a/kcmkwin/kwinscreenedges/touch.ui +++ b/kcmkwin/kwinscreenedges/touch.ui @@ -1,71 +1,71 @@ KWinScreenEdgesConfigForm 0 0 748 332 0 0 200 200 Qt::StrongFocus - To trigger an action swipe from the screen edge towards the center of the screen. + Trigger an action by swiping from the screen edge towards the center of the screen Qt::AlignCenter true Qt::Vertical 20 0 KWin::Monitor QWidget
monitor.h
1
diff --git a/kcmkwin/kwinscripts/main.cpp b/kcmkwin/kwinscripts/main.cpp index f5ee04b30..baa517511 100644 --- a/kcmkwin/kwinscripts/main.cpp +++ b/kcmkwin/kwinscripts/main.cpp @@ -1,24 +1,26 @@ /* * Copyright (c) 2011 Tamas Krutki * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "module.h" K_PLUGIN_FACTORY(KcmKWinScriptsFactory, registerPlugin("kwin-scripts");) + +#include "main.moc" diff --git a/kcmkwin/kwinscripts/module.cpp b/kcmkwin/kwinscripts/module.cpp index a0d698ec2..846f5a6ba 100644 --- a/kcmkwin/kwinscripts/module.cpp +++ b/kcmkwin/kwinscripts/module.cpp @@ -1,169 +1,165 @@ /* * Copyright (c) 2011 Tamas Krutki * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "module.h" #include "ui_module.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "version.h" -K_PLUGIN_FACTORY_DECLARATION(KcmKWinScriptsFactory) - Module::Module(QWidget *parent, const QVariantList &args) : KCModule(parent, args), ui(new Ui::Module), m_kwinConfig(KSharedConfig::openConfig("kwinrc")) { KAboutData *about = new KAboutData("kwin-scripts", i18n("KWin Scripts"), global_s_versionStringFull, i18n("Configure KWin scripts"), KAboutLicense::GPL_V2); about->addAuthor(i18n("Tamás Krutki")); setAboutData(about); ui->setupUi(this); ui->messageWidget->hide(); ui->ghnsButton->setConfigFile(QStringLiteral("kwinscripts.knsrc")); connect(ui->ghnsButton, &KNS3::Button::dialogFinished, this, [this](const KNS3::Entry::List &changedEntries) { if (!changedEntries.isEmpty()) { updateListViewContents(); } }); connect(ui->scriptSelector, SIGNAL(changed(bool)), this, SLOT(changed())); connect(ui->importScriptButton, SIGNAL(clicked()), SLOT(importScript())); updateListViewContents(); } Module::~Module() { delete ui; } void Module::importScript() { ui->messageWidget->animatedHide(); QString path = QFileDialog::getOpenFileName(nullptr, i18n("Import KWin Script"), QDir::homePath(), i18n("*.kwinscript|KWin scripts (*.kwinscript)")); if (path.isNull()) { return; } using namespace KPackage; PackageStructure *structure = PackageLoader::self()->loadPackageStructure(QStringLiteral("KWin/Script")); Package package(structure); KJob *installJob = package.update(path); installJob->setProperty("packagePath", path); // so we can retrieve it later for showing the script's name connect(installJob, &KJob::result, this, &Module::importScriptInstallFinished); } void Module::importScriptInstallFinished(KJob *job) { // if the applet is already installed, just add it to the containment if (job->error() != KJob::NoError) { ui->messageWidget->setText(i18nc("Placeholder is error message returned from the install service", "Cannot import selected script.\n%1", job->errorString())); ui->messageWidget->setMessageType(KMessageWidget::Error); ui->messageWidget->animatedShow(); return; } using namespace KPackage; // so we can show the name of the package we just imported PackageStructure *structure = PackageLoader::self()->loadPackageStructure(QStringLiteral("KWin/Script")); Package package(structure); package.setPath(job->property("packagePath").toString()); Q_ASSERT(package.isValid()); ui->messageWidget->setText(i18nc("Placeholder is name of the script that was imported", "The script \"%1\" was successfully imported.", package.metadata().name())); ui->messageWidget->setMessageType(KMessageWidget::Information); ui->messageWidget->animatedShow(); updateListViewContents(); emit changed(true); } void Module::updateListViewContents() { auto filter = [](const KPluginMetaData &md) { if (md.value(QStringLiteral("X-KWin-Exclude-Listing")) == QLatin1String("true") ) { return false; } return true; }; const QString scriptFolder = QStringLiteral("kwin/scripts/"); const auto scripts = KPackage::PackageLoader::self()->findPackages(QStringLiteral("KWin/Script"), scriptFolder, filter); QList scriptinfos = KPluginInfo::fromMetaData(scripts.toVector()); ui->scriptSelector->addPlugins(scriptinfos, KPluginSelector::ReadConfigFile, QString(), QString(), m_kwinConfig); } void Module::defaults() { ui->scriptSelector->defaults(); emit changed(true); } void Module::load() { updateListViewContents(); ui->scriptSelector->load(); emit changed(false); } void Module::save() { ui->scriptSelector->save(); m_kwinConfig->sync(); QDBusMessage message = QDBusMessage::createMethodCall("org.kde.KWin", "/Scripting", "org.kde.kwin.Scripting", "start"); QDBusConnection::sessionBus().asyncCall(message); emit changed(false); } - -#include "module.moc" diff --git a/kcmkwin/kwintabbox/kwinswitcher.knsrc b/kcmkwin/kwintabbox/kwinswitcher.knsrc index 6311687ad..2b2cc34d2 100644 --- a/kcmkwin/kwintabbox/kwinswitcher.knsrc +++ b/kcmkwin/kwintabbox/kwinswitcher.knsrc @@ -1,44 +1,45 @@ [KNewStuff3] Name=Window Manager Switching Layouts Name[ca]=Disposicions del commutador del gestor de finestres Name[ca@valencia]=Disposicions del commutador del gestor de finestres Name[cs]=Přepínání rozvržení správce oken Name[da]=Skifterlayouts til vindueshåndtering Name[de]=Wechsel-Layout für Fensterverwaltung Name[el]=Διατάξεις εναλλαγής διαχειριστή παραθύρων Name[en_GB]=Window Manager Switching Layouts Name[es]=Esquemas de cambio del gestor de ventanas Name[eu]=Leiho kudeatzailearen aldatzeko-antolamenduak Name[fi]=Ikkunaohjelman vaihdon asettelut Name[fr]=Changement de disposition du gestionnaire de fenêtres Name[gl]=Disposicións de cambio do xestor de xanelas Name[he]=מחליף פריסות של מנהל החלונות Name[hu]=Ablakkezelő-váltó elrendezések Name[ia]=Disposition de commutator de gerente de fenestra Name[it]=Disposizione scambiafinestre del gestore delle finestre Name[ko]=창 관리자 전환기 레이아웃 Name[lt]=Langų perjungimo išdėstymai Name[nl]=Omschakelende indelingen van vensterbeheerder Name[nn]=Veksleoppsett for vindaugshandsamar Name[pa]=ਵਿੰਡੋ ਮੈਨੇਜਰ ਸਵਿੱਚਰ ਲੇਆਉਟ Name[pl]=Układ przełączania zarządzana oknami Name[pt]=Disposições da Mudança de Janelas do KWin Name[pt_BR]=Layouts de mudança do gerenciador de Janelas +Name[ru]=Оформления переключателя окон для KWin Name[sk]=Prepínanie rozložení správcu okien Name[sl]=Razporedi preklapljanja upravljalnika oken Name[sr]=Распореди пребацивања менаџера прозора Name[sr@ijekavian]=Распореди пребацивања менаџера прозора Name[sr@ijekavianlatin]=Rasporedi prebacivanja menadžera prozora Name[sr@latin]=Rasporedi prebacivanja menadžera prozora Name[sv]=Fönsterbyteslayouter Name[tr]=Pencere Yöneticisi Geçiş Düzenleri Name[uk]=Компонування засобу перемикання вікон Name[x-test]=xxWindow Manager Switching Layoutsxx Name[zh_CN]=窗口切换器布局 Name[zh_TW]=視窗切換器佈局 ProvidersUrl=https://download.kde.org/ocs/providers.xml Categories=KWin Switching Layouts StandardResource=tmp InstallationCommand=plasmapkg2 -t windowswitcher -i %f UninstallCommand=plasmapkg2 -t windowswitcher -r %f diff --git a/kwin.notifyrc b/kwin.notifyrc index 5e936b52b..a8cf9776a 100644 --- a/kwin.notifyrc +++ b/kwin.notifyrc @@ -1,285 +1,285 @@ [Global] IconName=kwin Comment=KWin Window Manager Comment[ar]=مدير النوافذ كوين Comment[bg]=Мениджър на прозорци KWin Comment[bs]=Menadžer prozora K‑vin Comment[ca]=Gestor de finestres KWin Comment[ca@valencia]=Gestor de finestres KWin Comment[cs]=Správce oken KWin Comment[da]=KWin vindueshåndtering Comment[de]=KWin-Fensterverwaltung Comment[el]=Διαχειριστής παραθύρων Kwin Comment[en_GB]=KWin Window Manager Comment[es]=Gestor de ventanas KWin Comment[et]=Kwini aknahaldur Comment[eu]=KWin leiho-kudeatzailea Comment[fi]=KWin-ikkunointiohjelma Comment[fr]=Gestionnaire de fenêtres KWin Comment[ga]=Bainisteoir Fuinneog KWin Comment[gl]=Xestor de xanelas KWin Comment[gu]=KWin વિન્ડો સંચાલક Comment[he]=מנהל החלונות KWin Comment[hi]=केविन विंडो प्रबंधक Comment[hr]=Upravitelj prozora KWin Comment[hu]=KWin ablakkezelő Comment[ia]=Gerente de fenestra KWin Comment[id]=Manajer Jendela KWin Comment[is]=KWin gluggastjóri Comment[it]=Gestore delle finestre KWin Comment[ja]=KWin ウィンドウマネージャ Comment[kk]=KWin терезе менеджері Comment[km]=កម្មវិធី​គ្រប់គ្រង​បង្អួច KWin Comment[kn]=ಕೆವಿನ್(KWin) ವಿಂಡೋ ವ್ಯವಸ್ಥಾಪಕ Comment[ko]=KWin 창 관리자 Comment[lt]=KWin Langų tvarkyklė Comment[lv]=KWin logu pārvaldnieks Comment[mr]=के-विन चौकट व्यवस्थापक Comment[nb]=KWin vindusbehandler Comment[nds]=KWin-Finsterpleger Comment[nl]=KWin vensterbeheerder Comment[nn]=KWin vindaugshandsamar Comment[pa]=KWin ਵਿੰਡੋ ਮੈਨੇਜਰ Comment[pl]=Zarządzanie oknami KWin Comment[pt]=Gestor de Janelas KWin Comment[pt_BR]=Gerenciador de janelas KWin Comment[ro]=Gestionar de ferestre KWin Comment[ru]=Диспетчер окон KWin Comment[si]=KWin කවුළු කළමනාකරු Comment[sk]=Správca okien KWin Comment[sl]=Upravljalnik oken KWin Comment[sr]=Менаџер прозора К‑вин Comment[sr@ijekavian]=Менаџер прозора К‑вин Comment[sr@ijekavianlatin]=Menadžer prozora KWin Comment[sr@latin]=Menadžer prozora KWin Comment[sv]=Kwin fönsterhanterare Comment[tg]=Мудири тирезаҳои KWin Comment[th]=ตัวจัดการหน้าต่าง KWin Comment[tr]=KWin Pencere Yöneticisi Comment[ug]=KWin كۆزنەك باشقۇرغۇچ Comment[uk]=Керування вікнами KWin Comment[vi]=Trình quản lý cửa sổ KWin Comment[wa]=Manaedjeu des fniesses KWin Comment[x-test]=xxKWin Window Managerxx Comment[zh_CN]=KWin 窗口管理器 Comment[zh_TW]=KWin 視窗管理員 [Event/compositingsuspendeddbus] Name=Compositing has been suspended Name[ar]=عُلِّق التركيب Name[bg]=Ефектите са временно спрени Name[bs]=Slaganje je suspendovano Name[ca]=S'ha suspès la composició Name[ca@valencia]=S'ha suspès la composició Name[cs]=Kompozice byla pozastavena Name[da]=Compositing er blevet suspenderet Name[de]=Compositing ist ausgesetzt worden Name[el]=Η σύνθεση εικόνας τέθηκε σε αναστολή Name[en_GB]=Compositing has been suspended Name[eo]=Kunmetado prokrastiĝis Name[es]=Se ha suspendido la composición Name[et]=Komposiit on peatatud Name[eu]=Konposizioa eten egin da Name[fi]=Koostaminen on keskeytetty Name[fr]=La composition a été suspendue Name[fy]=Kompositing is ûnderbrútsen Name[ga]=Cuireadh comhshuí ar fionraí Name[gl]=Suspendeuse a composición Name[gu]=કોમ્પોઝિટીંગ બંધ કરવામાં આવ્યું છે Name[he]=השזירה הושהתה Name[hr]=Miješanje je pauzirano Name[hu]=A kompozit mód felfüggesztve Name[ia]=Composition ha essite suspendite Name[id]=Komposit telah disuspensi Name[is]=Gerð skjásamsetningar hefur verið hætt í bili Name[it]=La composizione è stata sospesa Name[ja]=コンポジティングが一時停止されました Name[kk]=Құрастыру аялдатылды Name[km]=ការ​តែង​ត្រូវ​បានផ្អាក Name[kn]=ಮಿಶ್ರಗೊಳಿಕೆಯನ್ನು ತಡೆಹಿಡಿಯಲಾಗಿದೆ Name[ko]=컴포지팅 중지됨 Name[lt]=Komponavimas buvo sustabdytas Name[lv]=Kompozitēšana ir apturēta Name[mai]=कंपोजिटिंग निलंबित कएल गेल अछि Name[ml]=കോമ്പോസിറ്റിങ്ങ് താല്‍കാലികമായി നിര്‍ത്തിയിരിക്കുന്നു Name[mr]=कंपोझिटींग अकार्यक्षम करण्यात आले आहे Name[nb]=Sammensetting er blitt suspendert Name[nds]=Dat Tosamensetten wöör utmaakt Name[nl]=Compositing is uitgesteld Name[nn]=Samansetjinga er stoppa Name[pa]=ਕੰਪੋਜੀਟ ਕਰਨ ਨੂੰ ਨੂੰ ਸਸਪੈਂਡ ਕੀਤਾ ਗਿਆ Name[pl]=Wstrzymano kompozycje Name[pt]=A composição foi suspensa Name[pt_BR]=A composição foi suspensa Name[ro]=Compoziționarea a fost suspendată Name[ru]=Графические эффекты были отключены Name[si]=රචනය අත්හිටුවිය Name[sk]=Kompozícia bola pozastavená Name[sl]=Skladnja 3D je bila prestavljena v pripravljenost Name[sr]=Слагање је суспендовано Name[sr@ijekavian]=Слагање је суспендовано Name[sr@ijekavianlatin]=Slaganje je suspendovano Name[sr@latin]=Slaganje je suspendovano Name[sv]=Sammansättning har stoppats Name[th]=การทำคอมโพสิตถูกหยุดชั่วคราว Name[tr]=Birleşiklik askıya alındı Name[ug]=ئارىلاش مەشغۇلاتى توختىتىلدى Name[uk]=Композитний показ було тимчасово вимкнено Name[wa]=Li môde compôzite a stî djoké Name[x-test]=xxCompositing has been suspendedxx Name[zh_CN]=混成已被中断 Name[zh_TW]=組合效能已被暫停 Comment=Another application has requested to suspend compositing. Comment[ar]=تطبيق أخر طلب تعليق التركيب Comment[bg]=Друго приложение е поискало временно спиране на ефектите. Comment[bs]=Drugi program je zatražio da se slaganje suspenduje. Comment[ca]=Una altra aplicació ha sol·licitat de suspendre la composició. Comment[ca@valencia]=Una altra aplicació ha sol·licitat de suspendre la composició. Comment[cs]=Jiná aplikace si vyžádala pozastavení kompozice. Comment[da]=Et andet program har anmodet om suspendering af compositing. Comment[de]=Eine andere Anwendung hat das Aussetzen von Compositing erbeten. Comment[el]=Κάποια εφαρμογή αιτήθηκε την αναστολή της σύνθεσης εικόνας. Comment[en_GB]=Another application has requested to suspend compositing. Comment[es]=Otra aplicación ha solicitado suspender la composición. Comment[et]=Mingi muu rakendus on nõudnud komposiidi peatamist. Comment[eu]=Beste aplikazio batek konposizioa eteteko eskatu du. Comment[fi]=Toinen sovellus vaati keskeyttämään koostamisen. Comment[fr]=Une autre application a demandé la suspension de la composition. Comment[fy]=In oare applikaasje hat frege om compositing út te stellen. Comment[ga]=Tá feidhmchlár eile ag iarraidh comhshuí a chur ar fionraí. Comment[gl]=Outro aplicativo pediu que a suspensión da composición. Comment[he]=יישום אחר ביקש להשהות את השזירה. Comment[hr]=Neka aplikacija je dala zahtjev za paziranjem miješanja. Comment[hu]=Egy másik alkalmazás a kompozit mód felfüggesztését kérte. Comment[ia]=Altere application ha requirite de suspender le composition. Comment[id]=Aplikasi lain telah meimnta untuk suspensi komposit. Comment[is]=Annað forrit hefur beðið um að skjásamsetningu verði hætt. Comment[it]=Un'altra applicazione ha richiesto di sospendere la composizione. Comment[ja]=他のアプリケーションがコンポジティングの一時停止を要求しました。 Comment[kk]=Басқа қолданбаның талабымен құрастыру аялдатылды. Comment[km]=កម្មវិធី​ផ្សេង​បានស្នើ​ឲ្យ​ផ្អាក​ការ​តែង ។ Comment[kn]=ಮಿಶ್ರಗೊಳಿಕೆಯನ್ನು ತಡೆಹಿಡಿಯುವಂತೆ ಬೇರೊಂದು ಅನ್ವಯವು ಮನವಿ ಸಲ್ಲಿಸಿದೆ. Comment[ko]=다른 프로그램이 컴포지팅을 꺼 달라고 요청했습니다. Comment[lt]=Kita programa paprašė sustabdyti komponavimą Comment[lv]=Kāda programma pieprasīja apturēt kompozitēšanu. Comment[ml]=കമ്പോസിറ്റിംഗ് നിര്‍ത്തിവെയ്ക്കാന്‍ വേറൊരു പ്രയോഗം ആവശ്യപ്പെട്ടിട്ടുണ്ട് Comment[mr]=कंपोझिटींग अकार्यक्षम करण्याची विनंती वेगळ्या अनुप्रयोगाने केलेली आहे. Comment[nb]=Et annet program har bedt om at sammensetting skal suspenderes. Comment[nds]=En anner Programm will dat Tosamensetten utsetten. Comment[nl]=Een andere applicatie heeft verzocht compositing uit te stellen. Comment[nn]=Eit anna program har spurt om stopping av samansetjinga. Comment[pa]=ਹੋਰ ਐਪਲੀਕੇਸ਼ਨ ਕੰਪੋਜੀਸ਼ਨ ਨੂੰ ਸਸਪੈਂਡ ਕਰਨ ਦੀ ਮੰਗ ਕਰ ਚੁੱਕੀ ਹੈ। Comment[pl]=Kolejny program zażądał wyłączenia kompozycji. Comment[pt]=Outra aplicação pediu para suspender a composição. Comment[pt_BR]=Outro aplicativo requisitou suspender a composição. Comment[ro]=Altă aplicație a cerut suspendarea compoziționării. Comment[ru]=Одно из приложений отключило графические эффекты Comment[si]=වෙනත් යෙදුමක් මගින් රචනය අත්හිටුවීමට ඉල්ලා ඇත. Comment[sk]=Iná aplikácia si vyžiadala pozastavenie kompozície. Comment[sl]=Drug program je zahteval prestavitev skladnje 3D v pripravljenost. Comment[sr]=Други програм је затражио да се слагање суспендује. Comment[sr@ijekavian]=Други програм је затражио да се слагање суспендује. Comment[sr@ijekavianlatin]=Drugi program je zatražio da se slaganje suspenduje. Comment[sr@latin]=Drugi program je zatražio da se slaganje suspenduje. Comment[sv]=Ett annat program har begärt att stoppa sammansättning. Comment[th]=โปรแกรมอื่นบางตัวได้ร้องขอทำการพักการทำงานของการทำคอมโพสิต Comment[tr]=Başka bir uygulama birleşikliğin askıya alınmasını istedi. Comment[ug]=باشقا بىر پروگرامما ئارىلاش مەشغۇلاتىنى توختىتىشنى تەلەپ قىلدى. Comment[uk]=Ще одна програма надіслала запит на вимикання композитного режиму. Comment[wa]=Èn ôte programe a dmandé d' djoker l' môde compôzite. Comment[x-test]=xxAnother application has requested to suspend compositing.xx Comment[zh_CN]=另一个应用程序已经请求中断混成操作。 Comment[zh_TW]=另一個應用程式要求暫停組合效能。 Action=Popup [Event/graphicsreset] Name=Graphics Reset Name[bs]=Reset grafike Name[ca]=Reinici dels gràfics Name[ca@valencia]=Reinici dels gràfics Name[cs]=Resetovat grafiku Name[da]=Grafiknulstilling Name[de]=Grafik-Reset Name[el]=Επαναφορά γραφικών Name[en_GB]=Graphics Reset Name[es]=Reinicio gráfico Name[et]=Graafika lähtestamine Name[eu]=Grafikoak berrezarri Name[fi]=Grafiikan nollaus Name[fr]=Réinitialisation graphique Name[gl]=Reinicio dos gráficos Name[he]=איפוס גרפיקה Name[hu]=Grafikai visszaállítás Name[ia]=Reinitia Graphic Name[id]=Atur Ulang Grafik Name[it]=Azzeramento grafica Name[kk]=Графиканы ысыру Name[ko]=그래픽 초기화 Name[lt]=Grafikos atstatymas Name[nb]=Grafikk tilbakestilt Name[nds]=Grafik-Torüchsetten Name[nl]=Grafische reset Name[nn]=Grafikk tilbakestilt Name[pa]=ਗਰਾਫਿਕਸ ਮੁੜ-ਸੈੱਟ Name[pl]=Ponowny rozruch grafiki Name[pt]=Reinício Gráfico Name[pt_BR]=Reinício gráfico Name[ro]=Reinițializare grafică Name[ru]=Сброс графики Name[sk]=Grafické vynulovanie Name[sl]=Ponastavitev grafike Name[sr]=Ресетовање графике Name[sr@ijekavian]=Ресетовање графике Name[sr@ijekavianlatin]=Resetovanje grafike Name[sr@latin]=Resetovanje grafike Name[sv]=Grafikåterställning Name[tr]=Grafik Sıfırlama Name[uk]=Скидання графіки Name[x-test]=xxGraphics Resetxx Name[zh_CN]=图形重置 Name[zh_TW]=圖形重置 Comment=A graphics reset event occurred Comment[bs]=Grafički reset događaj se desio Comment[ca]=Ha ocorregut un esdeveniment de reinici dels gràfics Comment[ca@valencia]=Ha ocorregut un esdeveniment de reinici dels gràfics Comment[cs]=Nastala událost resetování grafiky Comment[da]=En grafiknulstillingshændelse fandt sted Comment[de]=Ein Zurücksetzen der Grafik ist aufgetreten Comment[el]=Συνέβη μια επαναφορά των γραφικών Comment[en_GB]=A graphics reset event occurred Comment[es]=Ha ocurrido un evento de reinicio gráfico Comment[et]=Toimus graafika lähtestamise sündmus Comment[eu]=Grafikoak berrezartzeko gertaera bat jazo da Comment[fi]=Sattui grafiikan nollaustapahtuma Comment[fr]=Un évènement de réinitialisation graphique est intervenu -Comment[gl]=Aconteceu un reinicio de gráficos +Comment[gl]=Aconteceu un evento de reinicio de gráficos Comment[he]=הגרפיקה אופסה Comment[hu]=Egy grafikai visszaállítás esemény történt Comment[ia]=Il necessita un evento de reinitiar le graphic Comment[id]=Peristiwa atur ulang grafik terjadi Comment[it]=Si è verificato un evento di azzeramento della grafica Comment[kk]=Графиканы ысыру оқиғасы болды Comment[ko]=그래픽 초기화 이벤트가 발생함 Comment[lt]=Įvyko grafikos pirminės būsenos atkūrimo veiksmas Comment[nb]=Det har foregått en grafikk-tilbakestilling Comment[nds]=Dat geev en Grafik-Torüchsett-Begeefnis Comment[nl]=Een gebeurtenis van een grafische reset deed zich voor Comment[nn]=Det har skjedd ei grafikktilbakestilling Comment[pl]=Nastąpiło zdarzenie ponownego rozruchu systemu graficznego Comment[pt]=Ocorreu um evento de reinício gráfico Comment[pt_BR]=Ocorreu um evento de reinício gráfico Comment[ro]=A intervenit un eveniment de reinițializare a graficii Comment[ru]=Произошёл сброс графической системы Comment[sk]=Nastala chyba grafického vynulovania Comment[sl]=Prišlo je do dogodka ponastavitve grafike Comment[sr]=Дошло је до ресетовања графике Comment[sr@ijekavian]=Дошло је до ресетовања графике Comment[sr@ijekavianlatin]=Došlo je do resetovanja grafike Comment[sr@latin]=Došlo je do resetovanja grafike Comment[sv]=En grafikåterställningshändelse inträffade Comment[tr]=Bir grafik sıfırlama olayı oluştu Comment[uk]=Сталася подія відновлення початкового стану графіки Comment[x-test]=xxA graphics reset event occurredxx Comment[zh_CN]=发生了图形重置事件 Comment[zh_TW]=發生了圖形重置事件 Action=Popup diff --git a/layers.cpp b/layers.cpp index 179423758..caa7db1d3 100644 --- a/layers.cpp +++ b/layers.cpp @@ -1,837 +1,822 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // SELI zmenit doc /* This file contains things relevant to stacking order and layers. Design: Normal unconstrained stacking order, as requested by the user (by clicking on windows to raise them, etc.), is in Workspace::unconstrained_stacking_order. That list shouldn't be used at all, except for building Workspace::stacking_order. The building is done in Workspace::constrainedStackingOrder(). Only Workspace::stackingOrder() should be used to get the stacking order, because it also checks the stacking order is up to date. All clients are also stored in Workspace::clients (except for isDesktop() clients, as those are very special, and are stored in Workspace::desktops), in the order the clients were created. Every window has one layer assigned in which it is. There are 7 layers, from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer, NotificationLayer, ActiveLayer and OnScreenDisplayLayer (see also NETWM sect.7.10.). The layer a window is in depends on the window type, and on other things like whether the window is active. We extend the layers provided in NETWM by the NotificationLayer and OnScreenDisplayLayer. The NoficationLayer contains notification windows which are kept above all windows except the active fullscreen window. The OnScreenDisplayLayer is used for eg. volume and brightness change feedback and is kept above all windows since it provides immediate response to a user action. NET::Splash clients belong to the Normal layer. NET::TopMenu clients belong to Dock layer. Clients that are both NET::Dock and NET::KeepBelow are in the Normal layer in order to keep the 'allow window to cover the panel' Kicker setting to work as intended (this may look like a slight spec violation, but a) I have no better idea, b) the spec allows adjusting the stacking order if the WM thinks it's a good idea . We put all NET::KeepAbove above all Docks too, even though the spec suggests putting them in the same layer. Most transients are in the same layer as their mainwindow, see Workspace::constrainedStackingOrder(), they may also be in higher layers, but they should never be below their mainwindow. When some client attribute changes (above/below flag, transiency...), Workspace::updateClientLayer() should be called in order to make sure it's moved to the appropriate layer ClientList if needed. Currently the things that affect client in which layer a client belongs: KeepAbove/Keep Below flags, window type, fullscreen state and whether the client is active, mainclient (transiency). Make sure updateStackingOrder() is called in order to make Workspace::stackingOrder() up to date and propagated to the world. Using Workspace::blockStackingUpdates() (or the StackingUpdatesBlocker helper class) it's possible to temporarily disable updates and the stacking order will be updated once after it's allowed again. */ #include #include "utils.h" #include "client.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #include "tabbox.h" #include "group.h" #include "rules.h" #include "screens.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "composite.h" #include "screenedge.h" #include "shell_client.h" #include "wayland_server.h" #include namespace KWin { //******************************* // Workspace //******************************* void Workspace::updateClientLayer(AbstractClient* c) { if (c) c->updateLayer(); } void Workspace::updateStackingOrder(bool propagate_new_clients) { if (block_stacking_updates > 0) { if (propagate_new_clients) blocked_propagating_new_clients = true; return; } ToplevelList new_stacking_order = constrainedStackingOrder(); bool changed = (force_restacking || new_stacking_order != stacking_order); force_restacking = false; stacking_order = new_stacking_order; if (changed || propagate_new_clients) { propagateClients(propagate_new_clients); emit stackingOrderChanged(); if (m_compositor) { m_compositor->addRepaintFull(); } if (active_client) active_client->updateMouseGrab(); } } /*! * Some fullscreen effects have to raise the screenedge on top of an input window, thus all windows * this function puts them back where they belong for regular use and is some cheap variant of * the regular propagateClients function in that it completely ignores managed clients and everything * else and also does not update the NETWM property. * Called from Effects::destroyInputWindow so far. */ void Workspace::stackScreenEdgesUnderOverrideRedirect() { if (!rootInfo()) { return; } Xcb::restackWindows(QVector() << rootInfo()->supportWindow() << ScreenEdges::self()->windows()); } /*! Propagates the managed clients to the world. Called ONLY from updateStackingOrder(). */ void Workspace::propagateClients(bool propagate_new_clients) { if (!rootInfo()) { return; } // restack the windows according to the stacking order // supportWindow > electric borders > clients > hidden clients QVector newWindowStack; // Stack all windows under the support window. The support window is // not used for anything (besides the NETWM property), and it's not shown, // but it was lowered after kwin startup. Stacking all clients below // it ensures that no client will be ever shown above override-redirect // windows (e.g. popups). newWindowStack << rootInfo()->supportWindow(); newWindowStack << ScreenEdges::self()->windows(); newWindowStack.reserve(newWindowStack.size() + 2*stacking_order.size()); // *2 for inputWindow for (int i = stacking_order.size() - 1; i >= 0; --i) { Client *client = qobject_cast(stacking_order.at(i)); if (!client || client->hiddenPreview()) { continue; } if (client->inputId()) // Stack the input window above the frame newWindowStack << client->inputId(); newWindowStack << client->frameId(); } // when having hidden previews, stack hidden windows below everything else // (as far as pure X stacking order is concerned), in order to avoid having // these windows that should be unmapped to interfere with other windows for (int i = stacking_order.size() - 1; i >= 0; --i) { Client *client = qobject_cast(stacking_order.at(i)); if (!client || !client->hiddenPreview()) continue; newWindowStack << client->frameId(); } // TODO isn't it too inefficient to restack always all clients? // TODO don't restack not visible windows? assert(newWindowStack.at(0) == rootInfo()->supportWindow()); Xcb::restackWindows(newWindowStack); int pos = 0; xcb_window_t *cl(nullptr); if (propagate_new_clients) { cl = new xcb_window_t[ desktops.count() + clients.count()]; // TODO this is still not completely in the map order for (ClientList::ConstIterator it = desktops.constBegin(); it != desktops.constEnd(); ++it) cl[pos++] = (*it)->window(); for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) cl[pos++] = (*it)->window(); rootInfo()->setClientList(cl, pos); delete [] cl; } cl = new xcb_window_t[ stacking_order.count()]; pos = 0; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { if ((*it)->isClient()) cl[pos++] = (*it)->window(); } rootInfo()->setClientListStacking(cl, pos); delete [] cl; // Make the cached stacking order invalid here, in case we need the new stacking order before we get // the matching event, due to X being asynchronous. markXStackingOrderAsDirty(); } /*! Returns topmost visible client. Windows on the dock, the desktop or of any other special kind are excluded. Also if the window doesn't accept focus it's excluded. */ // TODO misleading name for this method, too many slightly different ways to use it AbstractClient* Workspace::topClientOnDesktop(int desktop, int screen, bool unconstrained, bool only_normal) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); ToplevelList list; if (!unconstrained) list = stacking_order; else list = unconstrained_stacking_order; for (int i = list.size() - 1; i >= 0; --i) { AbstractClient *c = qobject_cast(list.at(i)); if (!c) { continue; } if (c->isOnDesktop(desktop) && c->isShown(false) && c->isOnCurrentActivity()) { if (screen != -1 && c->screen() != screen) continue; if (!only_normal) return c; if (c->wantsTabFocus() && !c->isSpecialWindow()) return c; } } return 0; } AbstractClient* Workspace::findDesktop(bool topmost, int desktop) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); if (topmost) { for (int i = stacking_order.size() - 1; i >= 0; i--) { AbstractClient *c = qobject_cast(stacking_order.at(i)); if (c && c->isOnDesktop(desktop) && c->isDesktop() && c->isShown(true)) return c; } } else { // bottom-most foreach (Toplevel * c, stacking_order) { AbstractClient *client = qobject_cast(c); if (client && c->isOnDesktop(desktop) && c->isDesktop() && client->isShown(true)) return client; } } return NULL; } void Workspace::raiseOrLowerClient(AbstractClient *c) { if (!c) return; AbstractClient* topmost = NULL; // TODO Q_ASSERT( block_stacking_updates == 0 ); if (most_recently_raised && stacking_order.contains(most_recently_raised) && most_recently_raised->isShown(true) && c->isOnCurrentDesktop()) topmost = most_recently_raised; else topmost = topClientOnDesktop(c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(), options->isSeparateScreenFocus() ? c->screen() : -1); if (c == topmost) lowerClient(c); else raiseClient(c); } void Workspace::lowerClient(AbstractClient* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.prepend(c); if (!nogroup && c->isTransient()) { // lower also all windows in the group, in their reversed stacking order ClientList wins; if (Client *client = dynamic_cast(c)) { wins = ensureStackingOrder(client->group()->members()); } for (int i = wins.size() - 1; i >= 0; --i) { if (wins[ i ] != c) lowerClient(wins[ i ], true); } } if (c == most_recently_raised) most_recently_raised = 0; } void Workspace::lowerClientWithinApplication(AbstractClient* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); bool lowered = false; // first try to put it below the bottom-most window of the application for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it) { AbstractClient *client = qobject_cast(*it); if (!client) { continue; } if (AbstractClient::belongToSameApplication(client, c)) { unconstrained_stacking_order.insert(it, c); lowered = true; break; } } if (!lowered) unconstrained_stacking_order.prepend(c); // ignore mainwindows } void Workspace::raiseClient(AbstractClient* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); if (!nogroup && c->isTransient()) { QList transients; AbstractClient *transient_parent = c; while ((transient_parent = transient_parent->transientFor())) transients << transient_parent; foreach (transient_parent, transients) raiseClient(transient_parent, true); } unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.append(c); if (!c->isSpecialWindow()) { most_recently_raised = c; } } void Workspace::raiseClientWithinApplication(AbstractClient* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); // ignore mainwindows // first try to put it above the top-most window of the application for (int i = unconstrained_stacking_order.size() - 1; i > -1 ; --i) { AbstractClient *other = qobject_cast(unconstrained_stacking_order.at(i)); if (!other) { continue; } if (other == c) // don't lower it just because it asked to be raised return; if (AbstractClient::belongToSameApplication(other, c)) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(other) + 1, c); // insert after the found one break; } } } void Workspace::raiseClientRequest(KWin::AbstractClient *c, NET::RequestSource src, xcb_timestamp_t timestamp) { if (src == NET::FromTool || allowFullClientRaising(c, timestamp)) raiseClient(c); else { raiseClientWithinApplication(c); c->demandAttention(); } } void Workspace::lowerClientRequest(KWin::Client *c, NET::RequestSource src, xcb_timestamp_t /*timestamp*/) { // If the client has support for all this focus stealing prevention stuff, // do only lowering within the application, as that's the more logical // variant of lowering when application requests it. // No demanding of attention here of course. if (src == NET::FromTool || !c->hasUserTimeSupport()) lowerClient(c); else lowerClientWithinApplication(c); } void Workspace::lowerClientRequest(KWin::AbstractClient *c) { lowerClientWithinApplication(c); } void Workspace::restack(AbstractClient* c, AbstractClient* under, bool force) { assert(unconstrained_stacking_order.contains(under)); if (!force && !AbstractClient::belongToSameApplication(under, c)) { // put in the stacking order below _all_ windows belonging to the active application for (int i = 0; i < unconstrained_stacking_order.size(); ++i) { AbstractClient *other = qobject_cast(unconstrained_stacking_order.at(i)); if (other && other->layer() == c->layer() && AbstractClient::belongToSameApplication(under, other)) { under = (c == other) ? 0 : other; break; } } } if (under) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(under), c); } assert(unconstrained_stacking_order.contains(c)); FocusChain::self()->moveAfterClient(c, under); updateStackingOrder(); } void Workspace::restackClientUnderActive(AbstractClient* c) { if (!active_client || active_client == c || active_client->layer() != c->layer()) { raiseClient(c); return; } restack(c, active_client); } void Workspace::restoreSessionStackingOrder(Client* c) { if (c->sessionStackingOrder() < 0) return; StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); // from bottom it != unconstrained_stacking_order.end(); ++it) { Client *current = qobject_cast(*it); if (!current) { continue; } if (current->sessionStackingOrder() > c->sessionStackingOrder()) { unconstrained_stacking_order.insert(it, c); return; } } unconstrained_stacking_order.append(c); } /*! Returns a stacking order based upon \a list that fulfills certain contained. */ ToplevelList Workspace::constrainedStackingOrder() { ToplevelList layer[ NumLayers ]; // build the order from layers QVector< QMap > minimum_layer(screens()->count()); for (ToplevelList::ConstIterator it = unconstrained_stacking_order.constBegin(), end = unconstrained_stacking_order.constEnd(); it != end; ++it) { Layer l = (*it)->layer(); const int screen = (*it)->screen(); Client *c = qobject_cast(*it); QMap< Group*, Layer >::iterator mLayer = minimum_layer[screen].find(c ? c->group() : NULL); if (mLayer != minimum_layer[screen].end()) { // If a window is raised above some other window in the same window group // which is in the ActiveLayer (i.e. it's fulscreened), make sure it stays // above that window (see #95731). if (*mLayer == ActiveLayer && (l > BelowLayer)) l = ActiveLayer; *mLayer = l; } else if (c) { minimum_layer[screen].insertMulti(c->group(), l); } layer[ l ].append(*it); } ToplevelList stacking; for (Layer lay = FirstLayer; lay < NumLayers; ++lay) stacking += layer[ lay ]; // now keep transients above their mainwindows // TODO this could(?) use some optimization for (int i = stacking.size() - 1; i >= 0; ) { AbstractClient *current = qobject_cast(stacking[i]); if (!current || !current->isTransient()) { --i; continue; } int i2 = -1; Client *ccurrent = qobject_cast(current); if (ccurrent && ccurrent->groupTransient()) { if (ccurrent->group()->members().count() > 0) { // find topmost client this one is transient for for (i2 = stacking.size() - 1; i2 >= 0; --i2) { if (stacking[ i2 ] == stacking[ i ]) { i2 = -1; // don't reorder, already the topmost in the group break; } AbstractClient *c2 = qobject_cast(stacking[ i2 ]); if (!c2) { continue; } if (c2->hasTransient(current, true) && keepTransientAbove(c2, current)) break; } } // else i2 remains pointing at -1 } else { for (i2 = stacking.size() - 1; i2 >= 0; --i2) { AbstractClient *c2 = qobject_cast(stacking[ i2 ]); if (!c2) { continue; } if (c2 == current) { i2 = -1; // don't reorder, already on top of its mainwindow break; } if (c2 == current->transientFor() && keepTransientAbove(c2, current)) break; } } if (i2 == -1) { --i; continue; } stacking.removeAt(i); --i; // move onto the next item (for next for () iteration) --i2; // adjust index of the mainwindow after the remove above if (!current->transients().isEmpty()) // this one now can be possibly above its transients, i = i2; // so go again higher in the stack order and possibly move those transients again ++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end() stacking.insert(i2, current); } return stacking; } void Workspace::blockStackingUpdates(bool block) { if (block) { if (block_stacking_updates == 0) blocked_propagating_new_clients = false; ++block_stacking_updates; } else // !block if (--block_stacking_updates == 0) { updateStackingOrder(blocked_propagating_new_clients); if (effects) static_cast(effects)->checkInputWindowStacking(); } } namespace { template QList ensureStackingOrderInList(const ToplevelList &stackingOrder, const QList &list) { static_assert(std::is_base_of::value, "U must be derived from T"); // TODO Q_ASSERT( block_stacking_updates == 0 ); if (list.count() < 2) return list; // TODO is this worth optimizing? QList result = list; for (auto it = stackingOrder.begin(); it != stackingOrder.end(); ++it) { T *c = qobject_cast(*it); if (!c) { continue; } if (result.removeAll(c) != 0) result.append(c); } return result; } } // Ensure list is in stacking order ClientList Workspace::ensureStackingOrder(const ClientList& list) const { return ensureStackingOrderInList(stacking_order, list); } QList Workspace::ensureStackingOrder(const QList &list) const { return ensureStackingOrderInList(stacking_order, list); } // check whether a transient should be actually kept above its mainwindow // there may be some special cases where this rule shouldn't be enfored bool Workspace::keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient) { // #93832 - don't keep splashscreens above dialogs if (transient->isSplash() && mainwindow->isDialog()) return false; // This is rather a hack for #76026. Don't keep non-modal dialogs above // the mainwindow, but only if they're group transient (since only such dialogs // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker) // needs to be found. if (const Client *ct = dynamic_cast(transient)) { if (ct->isDialog() && !ct->isModal() && ct->groupTransient()) return false; } // #63223 - don't keep transients above docks, because the dock is kept high, // and e.g. dialogs for them would be too high too // ignore this if the transient has a placement hint which indicates it should go above it's parent if (mainwindow->isDock() && !transient->hasTransientPlacementHint()) return false; return true; } // Returns all windows in their stacking order on the root window. ToplevelList Workspace::xStackingOrder() const { if (m_xStackingDirty) { const_cast(this)->updateXStackingOrder(); } return x_stacking; } void Workspace::updateXStackingOrder() { x_stacking.clear(); std::unique_ptr tree{std::move(m_xStackingQueryTree)}; // use our own stacking order, not the X one, as they may differ foreach (Toplevel * c, stacking_order) x_stacking.append(c); if (tree && !tree->isNull()) { xcb_window_t *windows = tree->children(); const auto count = tree->data()->children_len; int foundUnmanagedCount = unmanaged.count(); for (unsigned int i = 0; i < count; ++i) { for (auto it = unmanaged.constBegin(); it != unmanaged.constEnd(); ++it) { Unmanaged *u = *it; if (u->window() == windows[i]) { x_stacking.append(u); foundUnmanagedCount--; break; } } if (foundUnmanagedCount == 0) { break; } } } if (waylandServer()) { const auto clients = waylandServer()->internalClients(); for (auto c: clients) { if (c->isShown(false)) { x_stacking << c; } } } m_xStackingDirty = false; } //******************************* // Client //******************************* void Client::restackWindow(xcb_window_t above, int detail, NET::RequestSource src, xcb_timestamp_t timestamp, bool send_event) { Client *other = 0; if (detail == XCB_STACK_MODE_OPPOSITE) { other = workspace()->findClient(Predicate::WindowMatch, above); if (!other) { workspace()->raiseOrLowerClient(this); return; } ToplevelList::const_iterator it = workspace()->stackingOrder().constBegin(), end = workspace()->stackingOrder().constEnd(); while (it != end) { if (*it == this) { detail = XCB_STACK_MODE_ABOVE; break; } else if (*it == other) { detail = XCB_STACK_MODE_BELOW; break; } ++it; } } else if (detail == XCB_STACK_MODE_TOP_IF) { other = workspace()->findClient(Predicate::WindowMatch, above); if (other && other->geometry().intersects(geometry())) workspace()->raiseClientRequest(this, src, timestamp); return; } else if (detail == XCB_STACK_MODE_BOTTOM_IF) { other = workspace()->findClient(Predicate::WindowMatch, above); if (other && other->geometry().intersects(geometry())) workspace()->lowerClientRequest(this, src, timestamp); return; } if (!other) other = workspace()->findClient(Predicate::WindowMatch, above); if (other && detail == XCB_STACK_MODE_ABOVE) { ToplevelList::const_iterator it = workspace()->stackingOrder().constEnd(), begin = workspace()->stackingOrder().constBegin(); while (--it != begin) { if (*it == other) { // the other one is top on stack it = begin; // invalidate src = NET::FromTool; // force break; } Client *c = qobject_cast(*it); if (!c || !( (*it)->isNormalWindow() && c->isShown(true) && (*it)->isOnCurrentDesktop() && (*it)->isOnCurrentActivity() && (*it)->isOnScreen(screen()) )) continue; // irrelevant clients if (*(it - 1) == other) break; // "it" is the one above the target one, stack below "it" } if (it != begin && (*(it - 1) == other)) other = qobject_cast(*it); else other = 0; } if (other) workspace()->restack(this, other); else if (detail == XCB_STACK_MODE_BELOW) workspace()->lowerClientRequest(this, src, timestamp); else if (detail == XCB_STACK_MODE_ABOVE) workspace()->raiseClientRequest(this, src, timestamp); if (send_event) sendSyntheticConfigureNotify(); } void Client::doSetKeepAbove() { // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Layer); } void Client::doSetKeepBelow() { // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Layer); } bool Client::belongsToDesktop() const { foreach (const Client *c, group()->members()) { if (c->isDesktop()) return true; } return false; } bool rec_checkTransientOnTop(const QList &transients, const Client *topmost) { foreach (const AbstractClient *transient, transients) { if (transient == topmost || rec_checkTransientOnTop(transient->transients(), topmost)) { return true; } } return false; } -bool Client::isActiveFullScreen() const -{ - if (AbstractClient::isActiveFullScreen()) { - return true; - } - if (!isFullScreen()) - return false; - - const Client* ac = dynamic_cast(workspace()->mostRecentlyActivatedClient()); // instead of activeClient() - avoids flicker - // according to NETWM spec implementation notes suggests - // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer. - // we'll also take the screen into account - return ac && (this->group() == ac->group()); -} - } // namespace diff --git a/libinput/connection.cpp b/libinput/connection.cpp index d1a995462..20a3b93ec 100644 --- a/libinput/connection.cpp +++ b/libinput/connection.cpp @@ -1,582 +1,705 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "connection.h" #include "context.h" #include "device.h" #include "events.h" +#ifndef KWIN_BUILD_TESTING +#include "../screens.h" +#endif #include "../logind.h" #include "../udev.h" #include "libinput_logging.h" #include #include #include #include #include #include #include #include +#include namespace KWin { namespace LibInput { class ConnectionAdaptor : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.InputDeviceManager") Q_PROPERTY(QStringList devicesSysNames READ devicesSysNames CONSTANT) private: Connection *m_con; public: ConnectionAdaptor(Connection *con) : m_con(con) { connect(con, &Connection::deviceAddedSysName, this, &ConnectionAdaptor::deviceAdded, Qt::QueuedConnection); connect(con, &Connection::deviceRemovedSysName, this, &ConnectionAdaptor::deviceRemoved, Qt::QueuedConnection); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/InputDevice"), QStringLiteral("org.kde.KWin.InputDeviceManager"), this, QDBusConnection::ExportAllProperties | QDBusConnection::ExportAllSignals ); } ~ConnectionAdaptor() { QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/org/kde/KWin/InputDeviceManager")); } QStringList devicesSysNames() { // TODO: is this allowed? directly calling function of object in another thread!? // otherwise use signal-slot mechanism return m_con->devicesSysNames(); } Q_SIGNALS: void deviceAdded(QString sysName); void deviceRemoved(QString sysName); }; Connection *Connection::s_self = nullptr; -QThread *Connection::s_thread = nullptr; +QPointer Connection::s_thread; static ConnectionAdaptor *s_adaptor = nullptr; static Context *s_context = nullptr; static quint32 toLibinputLEDS(Xkb::LEDs leds) { quint32 libinputLeds = 0; if (leds.testFlag(Xkb::LED::NumLock)) { libinputLeds = libinputLeds | LIBINPUT_LED_NUM_LOCK; } if (leds.testFlag(Xkb::LED::CapsLock)) { libinputLeds = libinputLeds | LIBINPUT_LED_CAPS_LOCK; } if (leds.testFlag(Xkb::LED::ScrollLock)) { libinputLeds = libinputLeds | LIBINPUT_LED_SCROLL_LOCK; } return libinputLeds; } Connection::Connection(QObject *parent) : Connection(nullptr, parent) { // only here to fix build, using will crash, BUG 343529 } +void Connection::createThread() +{ + if (s_thread) { + return; + } + s_thread = new QThread(); + s_thread->setObjectName(QStringLiteral("libinput-connection")); + s_thread->start(); +} + Connection *Connection::create(QObject *parent) { Q_ASSERT(!s_self); static Udev s_udev; if (!s_udev.isValid()) { qCWarning(KWIN_LIBINPUT) << "Failed to initialize udev"; return nullptr; } if (!s_context) { s_context = new Context(s_udev); if (!s_context->isValid()) { qCWarning(KWIN_LIBINPUT) << "Failed to create context from udev"; delete s_context; s_context = nullptr; return nullptr; } // TODO: don't hardcode seat name if (!s_context->assignSeat("seat0")) { qCWarning(KWIN_LIBINPUT) << "Failed to assign seat seat0"; delete s_context; s_context = nullptr; return nullptr; } } - s_thread = new QThread(); + Connection::createThread(); s_self = new Connection(s_context); s_self->moveToThread(s_thread); - s_thread->start(); QObject::connect(s_thread, &QThread::finished, s_self, &QObject::deleteLater); QObject::connect(s_thread, &QThread::finished, s_thread, &QObject::deleteLater); QObject::connect(parent, &QObject::destroyed, s_thread, &QThread::quit); if (!s_adaptor) { s_adaptor = new ConnectionAdaptor(s_self); } return s_self; } Connection::Connection(Context *input, QObject *parent) : QObject(parent) , m_input(input) , m_notifier(nullptr) , m_mutex(QMutex::Recursive) , m_leds() { Q_ASSERT(m_input); // need to connect to KGlobalSettings as the mouse KCM does not emit a dedicated signal QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), QStringLiteral("notifyChange"), this, SLOT(slotKGlobalSettingsNotifyChange(int,int))); } Connection::~Connection() { delete s_adaptor; s_adaptor = nullptr; s_self = nullptr; delete s_context; s_context = nullptr; } void Connection::setup() { QMetaObject::invokeMethod(this, "doSetup", Qt::QueuedConnection); } void Connection::doSetup() { connect(s_self, &Connection::deviceAdded, s_self, [](Device* device) { emit s_self->deviceAddedSysName(device->sysName()); }); connect(s_self, &Connection::deviceRemoved, s_self, [](Device* device) { emit s_self->deviceRemovedSysName(device->sysName()); }); Q_ASSERT(!m_notifier); m_notifier = new QSocketNotifier(m_input->fileDescriptor(), QSocketNotifier::Read, this); connect(m_notifier, &QSocketNotifier::activated, this, &Connection::handleEvent); LogindIntegration *logind = LogindIntegration::self(); connect(logind, &LogindIntegration::sessionActiveChanged, this, [this](bool active) { if (active) { if (!m_input->isSuspended()) { return; } m_input->resume(); wasSuspended = true; } else { deactivate(); } } ); handleEvent(); } void Connection::deactivate() { if (m_input->isSuspended()) { return; } m_keyboardBeforeSuspend = hasKeyboard(); m_alphaNumericKeyboardBeforeSuspend = hasAlphaNumericKeyboard(); m_pointerBeforeSuspend = hasPointer(); m_touchBeforeSuspend = hasTouch(); + m_tabletModeSwitchBeforeSuspend = hasTabletModeSwitch(); m_input->suspend(); handleEvent(); } void Connection::handleEvent() { QMutexLocker locker(&m_mutex); const bool wasEmpty = m_eventQueue.isEmpty(); do { m_input->dispatch(); Event *event = m_input->event(); if (!event) { break; } m_eventQueue << event; } while (true); if (wasEmpty && !m_eventQueue.isEmpty()) { emit eventsRead(); } } void Connection::processEvents() { QMutexLocker locker(&m_mutex); while (!m_eventQueue.isEmpty()) { QScopedPointer event(m_eventQueue.takeFirst()); switch (event->type()) { case LIBINPUT_EVENT_DEVICE_ADDED: { auto device = new Device(event->nativeDevice()); device->moveToThread(s_thread); m_devices << device; if (device->isKeyboard()) { m_keyboard++; if (device->isAlphaNumericKeyboard()) { m_alphaNumericKeyboard++; if (m_alphaNumericKeyboard == 1) { emit hasAlphaNumericKeyboardChanged(true); } } if (m_keyboard == 1) { emit hasKeyboardChanged(true); } } if (device->isPointer()) { m_pointer++; if (m_pointer == 1) { emit hasPointerChanged(true); } } if (device->isTouch()) { m_touch++; if (m_touch == 1) { emit hasTouchChanged(true); } } + if (device->isTabletModeSwitch()) { + m_tabletModeSwitch++; + if (m_tabletModeSwitch == 1) { + emit hasTabletModeSwitchChanged(true); + } + } applyDeviceConfig(device); + applyScreenToDevice(device); // enable possible leds libinput_device_led_update(device->device(), static_cast(toLibinputLEDS(m_leds))); emit deviceAdded(device); break; } case LIBINPUT_EVENT_DEVICE_REMOVED: { auto it = std::find_if(m_devices.begin(), m_devices.end(), [&event] (Device *d) { return event->device() == d; } ); if (it == m_devices.end()) { // we don't know this device break; } auto device = *it; m_devices.erase(it); emit deviceRemoved(device); if (device->isKeyboard()) { m_keyboard--; if (device->isAlphaNumericKeyboard()) { m_alphaNumericKeyboard--; if (m_alphaNumericKeyboard == 0) { emit hasAlphaNumericKeyboardChanged(false); } } if (m_keyboard == 0) { emit hasKeyboardChanged(false); } } if (device->isPointer()) { m_pointer--; if (m_pointer == 0) { emit hasPointerChanged(false); } } if (device->isTouch()) { m_touch--; if (m_touch == 0) { emit hasTouchChanged(false); } } + if (device->isTabletModeSwitch()) { + m_tabletModeSwitch--; + if (m_tabletModeSwitch == 0) { + emit hasTabletModeSwitchChanged(false); + } + } device->deleteLater(); break; } case LIBINPUT_EVENT_KEYBOARD_KEY: { KeyEvent *ke = static_cast(event.data()); emit keyChanged(ke->key(), ke->state(), ke->time(), ke->device()); break; } case LIBINPUT_EVENT_POINTER_AXIS: { PointerEvent *pe = static_cast(event.data()); struct Axis { qreal delta = 0.0; quint32 time = 0; }; QMap deltas; auto update = [&deltas] (PointerEvent *pe) { const auto axis = pe->axis(); for (auto it = axis.begin(); it != axis.end(); ++it) { deltas[*it].delta += pe->axisValue(*it); deltas[*it].time = pe->time(); } }; update(pe); auto it = m_eventQueue.begin(); while (it != m_eventQueue.end()) { if ((*it)->type() == LIBINPUT_EVENT_POINTER_AXIS) { QScopedPointer p(static_cast(*it)); update(p.data()); it = m_eventQueue.erase(it); } else { break; } } for (auto it = deltas.constBegin(); it != deltas.constEnd(); ++it) { emit pointerAxisChanged(it.key(), it.value().delta, it.value().time, pe->device()); } break; } case LIBINPUT_EVENT_POINTER_BUTTON: { PointerEvent *pe = static_cast(event.data()); emit pointerButtonChanged(pe->button(), pe->buttonState(), pe->time(), pe->device()); break; } case LIBINPUT_EVENT_POINTER_MOTION: { PointerEvent *pe = static_cast(event.data()); auto delta = pe->delta(); auto deltaNonAccel = pe->deltaUnaccelerated(); quint32 latestTime = pe->time(); quint64 latestTimeUsec = pe->timeMicroseconds(); auto it = m_eventQueue.begin(); while (it != m_eventQueue.end()) { if ((*it)->type() == LIBINPUT_EVENT_POINTER_MOTION) { QScopedPointer p(static_cast(*it)); delta += p->delta(); deltaNonAccel += p->deltaUnaccelerated(); latestTime = p->time(); latestTimeUsec = p->timeMicroseconds(); it = m_eventQueue.erase(it); } else { break; } } emit pointerMotion(delta, deltaNonAccel, latestTime, latestTimeUsec, pe->device()); break; } case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { PointerEvent *pe = static_cast(event.data()); emit pointerMotionAbsolute(pe->absolutePos(), pe->absolutePos(m_size), pe->time(), pe->device()); break; } case LIBINPUT_EVENT_TOUCH_DOWN: { +#ifndef KWIN_BUILD_TESTING TouchEvent *te = static_cast(event.data()); - emit touchDown(te->id(), te->absolutePos(m_size), te->time(), te->device()); + const auto &geo = screens()->geometry(te->device()->screenId()); + emit touchDown(te->id(), geo.topLeft() + te->absolutePos(geo.size()), te->time(), te->device()); break; +#endif } case LIBINPUT_EVENT_TOUCH_UP: { TouchEvent *te = static_cast(event.data()); emit touchUp(te->id(), te->time(), te->device()); break; } case LIBINPUT_EVENT_TOUCH_MOTION: { +#ifndef KWIN_BUILD_TESTING TouchEvent *te = static_cast(event.data()); - emit touchMotion(te->id(), te->absolutePos(m_size), te->time(), te->device()); + const auto &geo = screens()->geometry(te->device()->screenId()); + emit touchMotion(te->id(), geo.topLeft() + te->absolutePos(geo.size()), te->time(), te->device()); break; +#endif } case LIBINPUT_EVENT_TOUCH_CANCEL: { emit touchCanceled(event->device()); break; } case LIBINPUT_EVENT_TOUCH_FRAME: { emit touchFrame(event->device()); break; } case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: { PinchGestureEvent *pe = static_cast(event.data()); emit pinchGestureBegin(pe->fingerCount(), pe->time(), pe->device()); break; } case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: { PinchGestureEvent *pe = static_cast(event.data()); emit pinchGestureUpdate(pe->scale(), pe->angleDelta(), pe->delta(), pe->time(), pe->device()); break; } case LIBINPUT_EVENT_GESTURE_PINCH_END: { PinchGestureEvent *pe = static_cast(event.data()); if (pe->isCancelled()) { emit pinchGestureCancelled(pe->time(), pe->device()); } else { emit pinchGestureEnd(pe->time(), pe->device()); } break; } case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: { SwipeGestureEvent *se = static_cast(event.data()); emit swipeGestureBegin(se->fingerCount(), se->time(), se->device()); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: { SwipeGestureEvent *se = static_cast(event.data()); emit swipeGestureUpdate(se->delta(), se->time(), se->device()); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_END: { SwipeGestureEvent *se = static_cast(event.data()); if (se->isCancelled()) { emit swipeGestureCancelled(se->time(), se->device()); } else { emit swipeGestureEnd(se->time(), se->device()); } break; } + case LIBINPUT_EVENT_SWITCH_TOGGLE: { + SwitchEvent *se = static_cast(event.data()); + switch (se->state()) { + case SwitchEvent::State::Off: + emit switchToggledOff(se->time(), se->timeMicroseconds(), se->device()); + break; + case SwitchEvent::State::On: + emit switchToggledOn(se->time(), se->timeMicroseconds(), se->device()); + break; + default: + Q_UNREACHABLE(); + } + break; + } default: // nothing break; } } if (wasSuspended) { if (m_keyboardBeforeSuspend && !m_keyboard) { emit hasKeyboardChanged(false); } if (m_alphaNumericKeyboardBeforeSuspend && !m_alphaNumericKeyboard) { emit hasAlphaNumericKeyboardChanged(false); } if (m_pointerBeforeSuspend && !m_pointer) { emit hasPointerChanged(false); } if (m_touchBeforeSuspend && !m_touch) { emit hasTouchChanged(false); } + if (m_tabletModeSwitchBeforeSuspend && !m_tabletModeSwitch) { + emit hasTabletModeSwitchChanged(false); + } wasSuspended = false; } } void Connection::setScreenSize(const QSize &size) { m_size = size; } +void Connection::updateScreens() +{ + QMutexLocker locker(&m_mutex); + for (auto device: qAsConst(m_devices)) { + applyScreenToDevice(device); + } +} + + +void Connection::applyScreenToDevice(Device *device) +{ +#ifndef KWIN_BUILD_TESTING + QMutexLocker locker(&m_mutex); + if (!device->isTouch()) { + return; + } + int id = -1; + // let's try to find a screen for it + if (screens()->count() == 1) { + id = 0; + } + if (id == -1 && !device->outputName().isEmpty()) { + // we have an output name, try to find a screen with matching name + for (int i = 0; i < screens()->count(); i++) { + if (screens()->name(i) == device->outputName()) { + id = i; + break; + } + } + } + if (id == -1) { + // do we have an internal screen? + int internalId = -1; + for (int i = 0; i < screens()->count(); i++) { + if (screens()->isInternal(i)) { + internalId = i; + break; + } + } + auto testScreenMatches = [device] (int id) { + const auto &size = device->size(); + const auto &screenSize = screens()->physicalSize(id); + return std::round(size.width()) == std::round(screenSize.width()) + && std::round(size.height()) == std::round(screenSize.height()); + }; + if (internalId != -1 && testScreenMatches(internalId)) { + id = internalId; + } + // let's compare all screens for size + for (int i = 0; i < screens()->count(); i++) { + if (testScreenMatches(i)) { + id = i; + break; + } + } + if (id == -1) { + // still not found + if (internalId != -1) { + // we have an internal id, so let's use that + id = internalId; + } else { + // just take first screen, we have no clue + id = 0; + } + } + } + device->setScreenId(id); + device->setOrientation(screens()->orientation(id)); +#else + Q_UNUSED(device) +#endif +} + bool Connection::isSuspended() const { if (!s_context) { return false; } return s_context->isSuspended(); } void Connection::applyDeviceConfig(Device *device) { // pass configuration to Device device->setConfig(m_config->group("Libinput").group(QString::number(device->vendor())).group(QString::number(device->product())).group(device->name())); device->loadConfiguration(); if (device->isPointer() && !device->isTouchpad()) { const KConfigGroup group = m_config->group("Mouse"); device->setLeftHanded(group.readEntry("MouseButtonMapping", "RightHanded") == QLatin1String("LeftHanded")); qreal accel = group.readEntry("Acceleration", -1.0); if (qFuzzyCompare(accel, -1.0) || qFuzzyCompare(accel, 1.0)) { // default value device->setPointerAcceleration(0.0); } else { // the X11-based config is mapped in [0.1,20.0] with 1.0 being the "normal" setting - we assume that's the default if (accel < 1.0) { device->setPointerAcceleration(-1.0 + ((accel * 10.0) - 1.0) / 9.0); } else { device->setPointerAcceleration((accel -1.0)/19.0); } } } } void Connection::slotKGlobalSettingsNotifyChange(int type, int arg) { if (type == 3 /**SettingsChanged**/ && arg == 0 /** SETTINGS_MOUSE **/) { m_config->reparseConfiguration(); for (auto it = m_devices.constBegin(), end = m_devices.constEnd(); it != end; ++it) { if ((*it)->isPointer()) { applyDeviceConfig(*it); } } } } void Connection::toggleTouchpads() { bool changed = false; m_touchpadsEnabled = !m_touchpadsEnabled; for (auto it = m_devices.constBegin(); it != m_devices.constEnd(); ++it) { auto device = *it; if (!device->isTouchpad()) { continue; } const bool old = device->isEnabled(); device->setEnabled(m_touchpadsEnabled); if (old != device->isEnabled()) { changed = true; } } if (changed) { // send OSD message QDBusMessage msg = QDBusMessage::createMethodCall( QStringLiteral("org.kde.plasmashell"), QStringLiteral("/org/kde/osdService"), QStringLiteral("org.kde.osdService"), QStringLiteral("touchpadEnabledChanged") ); msg.setArguments({m_touchpadsEnabled}); QDBusConnection::sessionBus().asyncCall(msg); } } void Connection::enableTouchpads() { if (m_touchpadsEnabled) { return; } toggleTouchpads(); } void Connection::disableTouchpads() { if (!m_touchpadsEnabled) { return; } toggleTouchpads(); } void Connection::updateLEDs(Xkb::LEDs leds) { if (m_leds == leds) { return; } m_leds = leds; // update on devices const libinput_led l = static_cast(toLibinputLEDS(leds)); for (auto it = m_devices.constBegin(), end = m_devices.constEnd(); it != end; ++it) { libinput_device_led_update((*it)->device(), l); } } QStringList Connection::devicesSysNames() const { QStringList sl; foreach (Device *d, m_devices) { sl.append(d->sysName()); } return sl; } } } #include "connection.moc" diff --git a/libinput/connection.h b/libinput/connection.h index f08b1d5bd..44495ed19 100644 --- a/libinput/connection.h +++ b/libinput/connection.h @@ -1,158 +1,172 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_LIBINPUT_CONNECTION_H #define KWIN_LIBINPUT_CONNECTION_H #include "../input.h" #include "../keyboard_input.h" #include #include +#include #include #include #include #include class QSocketNotifier; class QThread; namespace KWin { namespace LibInput { class Event; class Device; class Context; class Connection : public QObject { Q_OBJECT public: ~Connection(); void setInputConfig(const KSharedConfigPtr &config) { m_config = config; } void setup(); /** * Sets the screen @p size. This is needed for mapping absolute pointer events to * the screen data. **/ void setScreenSize(const QSize &size); + void updateScreens(); + bool hasKeyboard() const { return m_keyboard > 0; } bool hasAlphaNumericKeyboard() const { return m_alphaNumericKeyboard > 0; } bool hasTouch() const { return m_touch > 0; } bool hasPointer() const { return m_pointer > 0; } + bool hasTabletModeSwitch() const { + return m_tabletModeSwitch > 0; + } bool isSuspended() const; void deactivate(); void processEvents(); void toggleTouchpads(); void enableTouchpads(); void disableTouchpads(); QVector devices() const { return m_devices; } QStringList devicesSysNames() const; void updateLEDs(KWin::Xkb::LEDs leds); + static void createThread(); + Q_SIGNALS: void keyChanged(quint32 key, KWin::InputRedirection::KeyboardKeyState, quint32 time, KWin::LibInput::Device *device); void pointerButtonChanged(quint32 button, KWin::InputRedirection::PointerButtonState state, quint32 time, KWin::LibInput::Device *device); void pointerMotionAbsolute(QPointF orig, QPointF screen, quint32 time, KWin::LibInput::Device *device); void pointerMotion(const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint32 time, quint64 timeMicroseconds, KWin::LibInput::Device *device); void pointerAxisChanged(KWin::InputRedirection::PointerAxis axis, qreal delta, quint32 time, KWin::LibInput::Device *device); void touchFrame(KWin::LibInput::Device *device); void touchCanceled(KWin::LibInput::Device *device); void touchDown(qint32 id, const QPointF &absolutePos, quint32 time, KWin::LibInput::Device *device); void touchUp(qint32 id, quint32 time, KWin::LibInput::Device *device); void touchMotion(qint32 id, const QPointF &absolutePos, quint32 time, KWin::LibInput::Device *device); void hasKeyboardChanged(bool); void hasAlphaNumericKeyboardChanged(bool); void hasPointerChanged(bool); void hasTouchChanged(bool); + void hasTabletModeSwitchChanged(bool); void deviceAdded(KWin::LibInput::Device *); void deviceRemoved(KWin::LibInput::Device *); void deviceAddedSysName(QString); void deviceRemovedSysName(QString); void swipeGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device); void swipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device); void swipeGestureEnd(quint32 time, KWin::LibInput::Device *device); void swipeGestureCancelled(quint32 time, KWin::LibInput::Device *device); void pinchGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device); void pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time, KWin::LibInput::Device *device); void pinchGestureEnd(quint32 time, KWin::LibInput::Device *device); void pinchGestureCancelled(quint32 time, KWin::LibInput::Device *device); + void switchToggledOn(quint32 time, quint64 timeMicroseconds, KWin::LibInput::Device *device); + void switchToggledOff(quint32 time, quint64 timeMicroseconds, KWin::LibInput::Device *device); void eventsRead(); private Q_SLOTS: void doSetup(); void slotKGlobalSettingsNotifyChange(int type, int arg); private: Connection(Context *input, QObject *parent = nullptr); void handleEvent(); void applyDeviceConfig(Device *device); + void applyScreenToDevice(Device *device); Context *m_input; QSocketNotifier *m_notifier; QSize m_size; int m_keyboard = 0; int m_alphaNumericKeyboard = 0; int m_pointer = 0; int m_touch = 0; + int m_tabletModeSwitch = 0; bool m_keyboardBeforeSuspend = false; bool m_alphaNumericKeyboardBeforeSuspend = false; bool m_pointerBeforeSuspend = false; bool m_touchBeforeSuspend = false; + bool m_tabletModeSwitchBeforeSuspend = false; QMutex m_mutex; QVector m_eventQueue; bool wasSuspended = false; QVector m_devices; KSharedConfigPtr m_config; bool m_touchpadsEnabled = true; Xkb::LEDs m_leds; KWIN_SINGLETON(Connection) - static QThread *s_thread; + static QPointer s_thread; }; } } #endif diff --git a/libinput/device.cpp b/libinput/device.cpp index b5a469d11..330499d39 100644 --- a/libinput/device.cpp +++ b/libinput/device.cpp @@ -1,428 +1,500 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "device.h" #include #include #include namespace KWin { namespace LibInput { static bool checkAlphaNumericKeyboard(libinput_device *device) { for (uint i = KEY_1; i <= KEY_0; i++) { if (libinput_device_keyboard_has_key(device, i) == 0) { return false; } } for (uint i = KEY_Q; i <= KEY_P; i++) { if (libinput_device_keyboard_has_key(device, i) == 0) { return false; } } for (uint i = KEY_A; i <= KEY_L; i++) { if (libinput_device_keyboard_has_key(device, i) == 0) { return false; } } for (uint i = KEY_Z; i <= KEY_M; i++) { if (libinput_device_keyboard_has_key(device, i) == 0) { return false; } } return true; } QVector Device::s_devices; Device *Device::getDevice(libinput_device *native) { auto it = std::find_if(s_devices.constBegin(), s_devices.constEnd(), [native] (const Device *d) { return d->device() == native; } ); if (it != s_devices.constEnd()) { return *it; } return nullptr; } enum class ConfigKey { Enabled, LeftHanded, DisableWhileTyping, PointerAcceleration, PointerAccelerationProfile, TapToClick, LmrTapButtonMap, TapAndDrag, TapDragLock, MiddleButtonEmulation, NaturalScroll, ScrollMethod, ScrollButton }; struct ConfigData { explicit ConfigData(QByteArray _key, void (Device::*_setter)(bool), bool (Device::*_defaultValue)() const = nullptr) : key(_key) { booleanSetter.setter = _setter; booleanSetter.defaultValue = _defaultValue; } explicit ConfigData(QByteArray _key, void (Device::*_setter)(quint32), quint32 (Device::*_defaultValue)() const = nullptr) : key(_key) { quint32Setter.setter = _setter; quint32Setter.defaultValue = _defaultValue; } explicit ConfigData(QByteArray _key, void (Device::*_setter)(QString), QString (Device::*_defaultValue)() const = nullptr) : key(_key) { stringSetter.setter = _setter; stringSetter.defaultValue = _defaultValue; } QByteArray key; struct { void (Device::*setter)(bool) = nullptr; bool (Device::*defaultValue)() const; } booleanSetter; struct { void (Device::*setter)(quint32) = nullptr; quint32 (Device::*defaultValue)() const; } quint32Setter; struct { void (Device::*setter)(QString) = nullptr; QString (Device::*defaultValue)() const; } stringSetter; }; static const QMap s_configData { {ConfigKey::Enabled, ConfigData(QByteArrayLiteral("Enabled"), &Device::setEnabled)}, {ConfigKey::LeftHanded, ConfigData(QByteArrayLiteral("LeftHanded"), &Device::setLeftHanded, &Device::leftHandedEnabledByDefault)}, {ConfigKey::DisableWhileTyping, ConfigData(QByteArrayLiteral("DisableWhileTyping"), &Device::setDisableWhileTyping, &Device::disableWhileTypingEnabledByDefault)}, {ConfigKey::PointerAcceleration, ConfigData(QByteArrayLiteral("PointerAcceleration"), &Device::setPointerAccelerationFromString, &Device::defaultPointerAccelerationToString)}, {ConfigKey::PointerAccelerationProfile, ConfigData(QByteArrayLiteral("PointerAccelerationProfile"), &Device::setPointerAccelerationProfileFromInt, &Device::defaultPointerAccelerationProfileToInt)}, {ConfigKey::TapToClick, ConfigData(QByteArrayLiteral("TapToClick"), &Device::setTapToClick, &Device::tapToClickEnabledByDefault)}, {ConfigKey::TapAndDrag, ConfigData(QByteArrayLiteral("TapAndDrag"), &Device::setTapAndDrag, &Device::tapAndDragEnabledByDefault)}, {ConfigKey::TapDragLock, ConfigData(QByteArrayLiteral("TapDragLock"), &Device::setTapDragLock, &Device::tapDragLockEnabledByDefault)}, {ConfigKey::MiddleButtonEmulation, ConfigData(QByteArrayLiteral("MiddleButtonEmulation"), &Device::setMiddleEmulation, &Device::middleEmulationEnabledByDefault)}, {ConfigKey::LmrTapButtonMap, ConfigData(QByteArrayLiteral("LmrTapButtonMap"), &Device::setLmrTapButtonMap, &Device::lmrTapButtonMapEnabledByDefault)}, {ConfigKey::NaturalScroll, ConfigData(QByteArrayLiteral("NaturalScroll"), &Device::setNaturalScroll, &Device::naturalScrollEnabledByDefault)}, {ConfigKey::ScrollMethod, ConfigData(QByteArrayLiteral("ScrollMethod"), &Device::activateScrollMethodFromInt, &Device::defaultScrollMethodToInt)}, {ConfigKey::ScrollButton, ConfigData(QByteArrayLiteral("ScrollButton"), &Device::setScrollButton, &Device::defaultScrollButton)} }; +namespace { +QMatrix4x4 defaultCalibrationMatrix(libinput_device *device) +{ + float matrix[6]; + const int ret = libinput_device_config_calibration_get_default_matrix(device, matrix); + if (ret == 0) { + return QMatrix4x4(); + } + return QMatrix4x4{ + matrix[0], matrix[1], matrix[2], 0.0f, + matrix[3], matrix[4], matrix[5], 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; +} +} + Device::Device(libinput_device *device, QObject *parent) : QObject(parent) , m_device(device) , m_keyboard(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_KEYBOARD)) , m_pointer(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_POINTER)) , m_touch(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TOUCH)) , m_tabletTool(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) #if 0 // next libinput version , m_tabletPad(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TABLET_PAD)) #else , m_tabletPad(false) #endif , m_supportsGesture(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_GESTURE)) + , m_switch(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_SWITCH)) + , m_lidSwitch(m_switch ? libinput_device_switch_has_switch(m_device, LIBINPUT_SWITCH_LID) : false) + , m_tabletSwitch(m_switch ? libinput_device_switch_has_switch(m_device, LIBINPUT_SWITCH_TABLET_MODE) : false) , m_name(QString::fromLocal8Bit(libinput_device_get_name(m_device))) , m_sysName(QString::fromLocal8Bit(libinput_device_get_sysname(m_device))) , m_outputName(QString::fromLocal8Bit(libinput_device_get_output_name(m_device))) , m_product(libinput_device_get_id_product(m_device)) , m_vendor(libinput_device_get_id_vendor(m_device)) , m_tapFingerCount(libinput_device_config_tap_get_finger_count(m_device)) , m_defaultTapButtonMap(libinput_device_config_tap_get_default_button_map(m_device)) , m_tapButtonMap(libinput_device_config_tap_get_button_map(m_device)) , m_tapToClickEnabledByDefault(libinput_device_config_tap_get_default_enabled(m_device) == LIBINPUT_CONFIG_TAP_ENABLED) , m_tapToClick(libinput_device_config_tap_get_enabled(m_device)) , m_tapAndDragEnabledByDefault(libinput_device_config_tap_get_default_drag_enabled(m_device)) , m_tapAndDrag(libinput_device_config_tap_get_drag_enabled(m_device)) , m_tapDragLockEnabledByDefault(libinput_device_config_tap_get_default_drag_lock_enabled(m_device)) , m_tapDragLock(libinput_device_config_tap_get_drag_lock_enabled(m_device)) , m_supportsDisableWhileTyping(libinput_device_config_dwt_is_available(m_device)) , m_supportsPointerAcceleration(libinput_device_config_accel_is_available(m_device)) , m_supportsLeftHanded(libinput_device_config_left_handed_is_available(m_device)) , m_supportsCalibrationMatrix(libinput_device_config_calibration_has_matrix(m_device)) , m_supportsDisableEvents(libinput_device_config_send_events_get_modes(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED) , m_supportsDisableEventsOnExternalMouse(libinput_device_config_send_events_get_modes(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE) , m_supportsMiddleEmulation(libinput_device_config_middle_emulation_is_available(m_device)) , m_supportsNaturalScroll(libinput_device_config_scroll_has_natural_scroll(m_device)) , m_supportedScrollMethods(libinput_device_config_scroll_get_methods(m_device)) , m_leftHandedEnabledByDefault(libinput_device_config_left_handed_get_default(m_device)) , m_middleEmulationEnabledByDefault(libinput_device_config_middle_emulation_get_default_enabled(m_device) == LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED) , m_naturalScrollEnabledByDefault(libinput_device_config_scroll_get_default_natural_scroll_enabled(m_device)) , m_defaultScrollMethod(libinput_device_config_scroll_get_default_method(m_device)) , m_defaultScrollButton(libinput_device_config_scroll_get_default_button(m_device)) , m_disableWhileTypingEnabledByDefault(libinput_device_config_dwt_get_default_enabled(m_device)) , m_disableWhileTyping(m_supportsDisableWhileTyping ? libinput_device_config_dwt_get_enabled(m_device) == LIBINPUT_CONFIG_DWT_ENABLED : false) , m_middleEmulation(libinput_device_config_middle_emulation_get_enabled(m_device) == LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED) , m_leftHanded(m_supportsLeftHanded ? libinput_device_config_left_handed_get(m_device) : false) , m_naturalScroll(m_supportsNaturalScroll ? libinput_device_config_scroll_get_natural_scroll_enabled(m_device) : false) , m_scrollMethod(libinput_device_config_scroll_get_method(m_device)) , m_scrollButton(libinput_device_config_scroll_get_button(m_device)) , m_defaultPointerAcceleration(libinput_device_config_accel_get_default_speed(m_device)) , m_pointerAcceleration(libinput_device_config_accel_get_speed(m_device)) , m_supportedPointerAccelerationProfiles(libinput_device_config_accel_get_profiles(m_device)) , m_defaultPointerAccelerationProfile(libinput_device_config_accel_get_default_profile(m_device)) , m_pointerAccelerationProfile(libinput_device_config_accel_get_profile(m_device)) , m_enabled(m_supportsDisableEvents ? libinput_device_config_send_events_get_mode(m_device) == LIBINPUT_CONFIG_SEND_EVENTS_ENABLED : true) , m_config() + , m_defaultCalibrationMatrix(m_supportsCalibrationMatrix ? defaultCalibrationMatrix(m_device) : QMatrix4x4{}) { libinput_device_ref(m_device); qreal width = 0; qreal height = 0; if (libinput_device_get_size(m_device, &width, &height) == 0) { m_size = QSizeF(width, height); } if (m_pointer) { if (libinput_device_pointer_has_button(m_device, BTN_LEFT) == 1) { m_supportedButtons |= Qt::LeftButton; } if (libinput_device_pointer_has_button(m_device, BTN_MIDDLE) == 1) { m_supportedButtons |= Qt::MiddleButton; } if (libinput_device_pointer_has_button(m_device, BTN_RIGHT) == 1) { m_supportedButtons |= Qt::RightButton; } if (libinput_device_pointer_has_button(m_device, BTN_SIDE) == 1) { m_supportedButtons |= Qt::ExtraButton1; } if (libinput_device_pointer_has_button(m_device, BTN_EXTRA) == 1) { m_supportedButtons |= Qt::ExtraButton2; } if (libinput_device_pointer_has_button(m_device, BTN_BACK) == 1) { m_supportedButtons |= Qt::BackButton; } if (libinput_device_pointer_has_button(m_device, BTN_FORWARD) == 1) { m_supportedButtons |= Qt::ForwardButton; } if (libinput_device_pointer_has_button(m_device, BTN_TASK) == 1) { m_supportedButtons |= Qt::TaskButton; } } if (m_keyboard) { m_alphaNumericKeyboard = checkAlphaNumericKeyboard(m_device); } s_devices << this; QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/InputDevice/") + m_sysName, QStringLiteral("org.kde.KWin.InputDevice"), this, QDBusConnection::ExportAllProperties ); } Device::~Device() { s_devices.removeOne(this); QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/org/kde/KWin/InputDevice/") + m_sysName); libinput_device_unref(m_device); } template void Device::writeEntry(const ConfigKey &key, const T &value) { if (!m_config.isValid()) { return; } if (m_loading) { return; } auto it = s_configData.find(key); Q_ASSERT(it != s_configData.end()); m_config.writeEntry(it.value().key.constData(), value); m_config.sync(); } template void Device::readEntry(const QByteArray &key, const Setter &s, const T &defaultValue) { if (!s.setter) { return; } (this->*(s.setter))(m_config.readEntry(key.constData(), s.defaultValue ? (this->*(s.defaultValue))() : defaultValue)); } void Device::loadConfiguration() { if (!m_config.isValid()) { return; } m_loading = true; for (auto it = s_configData.begin(), end = s_configData.end(); it != end; ++it) { const auto key = it.value().key; if (!m_config.hasKey(key.constData())) { continue; } readEntry(key, it.value().booleanSetter, true); readEntry(key, it.value().quint32Setter, 0); readEntry(key, it.value().stringSetter, ""); }; m_loading = false; } void Device::setPointerAcceleration(qreal acceleration) { if (!m_supportsPointerAcceleration) { return; } acceleration = qBound(-1.0, acceleration, 1.0); if (libinput_device_config_accel_set_speed(m_device, acceleration) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_pointerAcceleration != acceleration) { m_pointerAcceleration = acceleration; emit pointerAccelerationChanged(); writeEntry(ConfigKey::PointerAcceleration, QString::number(acceleration, 'f', 3)); } } } void Device::setScrollButton(quint32 button) { if (!(m_supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN)) { return; } if (libinput_device_config_scroll_set_button(m_device, button) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_scrollButton != button) { m_scrollButton = button; writeEntry(ConfigKey::ScrollButton, m_scrollButton); emit scrollButtonChanged(); } } } void Device::setPointerAccelerationProfile(bool set, enum libinput_config_accel_profile profile) { if (!(m_supportedPointerAccelerationProfiles & profile)) { return; } if (!set) { profile = (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) ? LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE : LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; if (!(m_supportedPointerAccelerationProfiles & profile)) { return; } } if (libinput_device_config_accel_set_profile(m_device, profile) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_pointerAccelerationProfile != profile) { m_pointerAccelerationProfile = profile; emit pointerAccelerationProfileChanged(); writeEntry(ConfigKey::PointerAccelerationProfile, (quint32) profile); } } } void Device::setScrollMethod(bool set, enum libinput_config_scroll_method method) { if (!(m_supportedScrollMethods & method)) { return; } bool isCurrent = m_scrollMethod == method; if (!set) { if (isCurrent) { method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; isCurrent = false; } else { return; } } if (libinput_device_config_scroll_set_method(m_device, method) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (!isCurrent) { m_scrollMethod = method; emit scrollMethodChanged(); writeEntry(ConfigKey::ScrollMethod, (quint32) method); } } } void Device::setLmrTapButtonMap(bool set) { enum libinput_config_tap_button_map map = set ? LIBINPUT_CONFIG_TAP_MAP_LMR : LIBINPUT_CONFIG_TAP_MAP_LRM; if (m_tapFingerCount < 2) { return; } if (!set) { map = LIBINPUT_CONFIG_TAP_MAP_LRM; } if (libinput_device_config_tap_set_button_map(m_device, map) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_tapButtonMap != map) { m_tapButtonMap = map; writeEntry(ConfigKey::LmrTapButtonMap, set); emit tapButtonMapChanged(); } } } #define CONFIG(method, condition, function, variable, key) \ void Device::method(bool set) \ { \ if (condition) { \ return; \ } \ if (libinput_device_config_##function(m_device, set) == LIBINPUT_CONFIG_STATUS_SUCCESS) { \ if (m_##variable != set) { \ m_##variable = set; \ writeEntry(ConfigKey::key, m_##variable); \ emit variable##Changed(); \ }\ } \ } CONFIG(setLeftHanded, !m_supportsLeftHanded, left_handed_set, leftHanded, LeftHanded) CONFIG(setNaturalScroll, !m_supportsNaturalScroll, scroll_set_natural_scroll_enabled, naturalScroll, NaturalScroll) #undef CONFIG #define CONFIG(method, condition, function, enum, variable, key) \ void Device::method(bool set) \ { \ if (condition) { \ return; \ } \ if (libinput_device_config_##function(m_device, set ? LIBINPUT_CONFIG_##enum##_ENABLED : LIBINPUT_CONFIG_##enum##_DISABLED) == LIBINPUT_CONFIG_STATUS_SUCCESS) { \ if (m_##variable != set) { \ m_##variable = set; \ writeEntry(ConfigKey::key, m_##variable); \ emit variable##Changed(); \ }\ } \ } CONFIG(setEnabled, !m_supportsDisableEvents, send_events_set_mode, SEND_EVENTS, enabled, Enabled) CONFIG(setDisableWhileTyping, !m_supportsDisableWhileTyping, dwt_set_enabled, DWT, disableWhileTyping, DisableWhileTyping) CONFIG(setTapToClick, m_tapFingerCount == 0, tap_set_enabled, TAP, tapToClick, TapToClick) CONFIG(setTapAndDrag, false, tap_set_drag_enabled, DRAG, tapAndDrag, TapAndDrag) CONFIG(setTapDragLock, false, tap_set_drag_lock_enabled, DRAG_LOCK, tapDragLock, TapDragLock) CONFIG(setMiddleEmulation, m_supportsMiddleEmulation == false, middle_emulation_set_enabled, MIDDLE_EMULATION, middleEmulation, MiddleButtonEmulation) #undef CONFIG +void Device::setOrientation(Qt::ScreenOrientation orientation) +{ + if (!m_supportsCalibrationMatrix) { + return; + } + // 90 deg cw: + static const QMatrix4x4 portraitMatrix{ + 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + // 180 deg cw: + static const QMatrix4x4 invertedLandscapeMatrix{ + -1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, -1.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + // 270 deg cw + static const QMatrix4x4 invertedPortraitMatrix{ + 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + QMatrix4x4 matrix; + switch (orientation) { + case Qt::PortraitOrientation: + matrix = portraitMatrix; + break; + case Qt::InvertedLandscapeOrientation: + matrix = invertedLandscapeMatrix; + break; + case Qt::InvertedPortraitOrientation: + matrix = invertedPortraitMatrix; + break; + case Qt::PrimaryOrientation: + case Qt::LandscapeOrientation: + default: + break; + } + const auto combined = m_defaultCalibrationMatrix * matrix; + const auto columnOrder = combined.constData(); + float m[6] = { + columnOrder[0], columnOrder[4], columnOrder[8], + columnOrder[1], columnOrder[5], columnOrder[9] + }; + libinput_device_config_calibration_set_matrix(m_device, m); +} + } } diff --git a/libinput/device.h b/libinput/device.h index ad4fcd2f7..dcd173f1d 100644 --- a/libinput/device.h +++ b/libinput/device.h @@ -1,496 +1,537 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_LIBINPUT_DEVICE_H #define KWIN_LIBINPUT_DEVICE_H #include #include #include +#include #include #include struct libinput_device; namespace KWin { namespace LibInput { enum class ConfigKey; class Device : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.InputDevice") // // general Q_PROPERTY(bool keyboard READ isKeyboard CONSTANT) Q_PROPERTY(bool alphaNumericKeyboard READ isAlphaNumericKeyboard CONSTANT) Q_PROPERTY(bool pointer READ isPointer CONSTANT) Q_PROPERTY(bool touchpad READ isTouchpad CONSTANT) Q_PROPERTY(bool touch READ isTouch CONSTANT) Q_PROPERTY(bool tabletTool READ isTabletTool CONSTANT) Q_PROPERTY(bool tabletPad READ isTabletPad CONSTANT) Q_PROPERTY(bool gestureSupport READ supportsGesture CONSTANT) Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString sysName READ sysName CONSTANT) Q_PROPERTY(QString outputName READ outputName CONSTANT) Q_PROPERTY(QSizeF size READ size CONSTANT) Q_PROPERTY(quint32 product READ product CONSTANT) Q_PROPERTY(quint32 vendor READ vendor CONSTANT) Q_PROPERTY(bool supportsDisableEvents READ supportsDisableEvents CONSTANT) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) // // advanced Q_PROPERTY(Qt::MouseButtons supportedButtons READ supportedButtons CONSTANT) Q_PROPERTY(bool supportsCalibrationMatrix READ supportsCalibrationMatrix CONSTANT) Q_PROPERTY(bool supportsLeftHanded READ supportsLeftHanded CONSTANT) Q_PROPERTY(bool leftHandedEnabledByDefault READ leftHandedEnabledByDefault CONSTANT) Q_PROPERTY(bool leftHanded READ isLeftHanded WRITE setLeftHanded NOTIFY leftHandedChanged) Q_PROPERTY(bool supportsDisableEventsOnExternalMouse READ supportsDisableEventsOnExternalMouse CONSTANT) Q_PROPERTY(bool supportsDisableWhileTyping READ supportsDisableWhileTyping CONSTANT) Q_PROPERTY(bool disableWhileTypingEnabledByDefault READ disableWhileTypingEnabledByDefault CONSTANT) Q_PROPERTY(bool disableWhileTyping READ isDisableWhileTyping WRITE setDisableWhileTyping NOTIFY disableWhileTypingChanged) // // acceleration speed and profile Q_PROPERTY(bool supportsPointerAcceleration READ supportsPointerAcceleration CONSTANT) Q_PROPERTY(qreal defaultPointerAcceleration READ defaultPointerAcceleration CONSTANT) Q_PROPERTY(qreal pointerAcceleration READ pointerAcceleration WRITE setPointerAcceleration NOTIFY pointerAccelerationChanged) Q_PROPERTY(bool supportsPointerAccelerationProfileFlat READ supportsPointerAccelerationProfileFlat CONSTANT) Q_PROPERTY(bool defaultPointerAccelerationProfileFlat READ defaultPointerAccelerationProfileFlat CONSTANT) Q_PROPERTY(bool pointerAccelerationProfileFlat READ pointerAccelerationProfileFlat WRITE setPointerAccelerationProfileFlat NOTIFY pointerAccelerationProfileChanged) Q_PROPERTY(bool supportsPointerAccelerationProfileAdaptive READ supportsPointerAccelerationProfileAdaptive CONSTANT) Q_PROPERTY(bool defaultPointerAccelerationProfileAdaptive READ defaultPointerAccelerationProfileAdaptive CONSTANT) Q_PROPERTY(bool pointerAccelerationProfileAdaptive READ pointerAccelerationProfileAdaptive WRITE setPointerAccelerationProfileAdaptive NOTIFY pointerAccelerationProfileChanged) // // tapping Q_PROPERTY(int tapFingerCount READ tapFingerCount CONSTANT) Q_PROPERTY(bool tapToClickEnabledByDefault READ tapToClickEnabledByDefault CONSTANT) Q_PROPERTY(bool tapToClick READ isTapToClick WRITE setTapToClick NOTIFY tapToClickChanged) Q_PROPERTY(bool supportsLmrTapButtonMap READ supportsLmrTapButtonMap CONSTANT) Q_PROPERTY(bool lmrTapButtonMapEnabledByDefault READ lmrTapButtonMapEnabledByDefault CONSTANT) Q_PROPERTY(bool lmrTapButtonMap READ lmrTapButtonMap WRITE setLmrTapButtonMap NOTIFY tapButtonMapChanged) Q_PROPERTY(bool tapAndDragEnabledByDefault READ tapAndDragEnabledByDefault CONSTANT) Q_PROPERTY(bool tapAndDrag READ isTapAndDrag WRITE setTapAndDrag NOTIFY tapAndDragChanged) Q_PROPERTY(bool tapDragLockEnabledByDefault READ tapDragLockEnabledByDefault CONSTANT) Q_PROPERTY(bool tapDragLock READ isTapDragLock WRITE setTapDragLock NOTIFY tapDragLockChanged) Q_PROPERTY(bool supportsMiddleEmulation READ supportsMiddleEmulation CONSTANT) Q_PROPERTY(bool middleEmulationEnabledByDefault READ middleEmulationEnabledByDefault CONSTANT) Q_PROPERTY(bool middleEmulation READ isMiddleEmulation WRITE setMiddleEmulation NOTIFY middleEmulationChanged) // // scrolling Q_PROPERTY(bool supportsNaturalScroll READ supportsNaturalScroll CONSTANT) Q_PROPERTY(bool naturalScrollEnabledByDefault READ naturalScrollEnabledByDefault CONSTANT) Q_PROPERTY(bool naturalScroll READ isNaturalScroll WRITE setNaturalScroll NOTIFY naturalScrollChanged) Q_PROPERTY(bool supportsScrollTwoFinger READ supportsScrollTwoFinger CONSTANT) Q_PROPERTY(bool scrollTwoFingerEnabledByDefault READ scrollTwoFingerEnabledByDefault CONSTANT) Q_PROPERTY(bool scrollTwoFinger READ isScrollTwoFinger WRITE setScrollTwoFinger NOTIFY scrollMethodChanged) Q_PROPERTY(bool supportsScrollEdge READ supportsScrollEdge CONSTANT) Q_PROPERTY(bool scrollEdgeEnabledByDefault READ scrollEdgeEnabledByDefault CONSTANT) Q_PROPERTY(bool scrollEdge READ isScrollEdge WRITE setScrollEdge NOTIFY scrollMethodChanged) Q_PROPERTY(bool supportsScrollOnButtonDown READ supportsScrollOnButtonDown CONSTANT) Q_PROPERTY(bool scrollOnButtonDownEnabledByDefault READ scrollOnButtonDownEnabledByDefault CONSTANT) Q_PROPERTY(quint32 defaultScrollButton READ defaultScrollButton CONSTANT) Q_PROPERTY(bool scrollOnButtonDown READ isScrollOnButtonDown WRITE setScrollOnButtonDown NOTIFY scrollMethodChanged) Q_PROPERTY(quint32 scrollButton READ scrollButton WRITE setScrollButton NOTIFY scrollButtonChanged) + // switches + Q_PROPERTY(bool switchDevice READ isSwitch CONSTANT) + Q_PROPERTY(bool lidSwitch READ isLidSwitch CONSTANT) + Q_PROPERTY(bool tabletModeSwitch READ isTabletModeSwitch CONSTANT) + public: explicit Device(libinput_device *device, QObject *parent = nullptr); virtual ~Device(); bool isKeyboard() const { return m_keyboard; } bool isAlphaNumericKeyboard() const { return m_alphaNumericKeyboard; } bool isPointer() const { return m_pointer; } bool isTouchpad() const{ return m_pointer && // ignore all combined devices. E.g. a touchpad on a keyboard we don't want to toggle // as that would result in the keyboard going off as well !(m_keyboard || m_touch || m_tabletPad || m_tabletTool) && // is this a touch pad? We don't really know, let's do some assumptions (m_tapFingerCount > 0 || m_supportsDisableWhileTyping || m_supportsDisableEventsOnExternalMouse); } bool isTouch() const { return m_touch; } bool isTabletTool() const { return m_tabletTool; } bool isTabletPad() const { return m_tabletPad; } bool supportsGesture() const { return m_supportsGesture; } QString name() const { return m_name; } QString sysName() const { return m_sysName; } QString outputName() const { return m_outputName; } QSizeF size() const { return m_size; } quint32 product() const { return m_product; } quint32 vendor() const { return m_vendor; } Qt::MouseButtons supportedButtons() const { return m_supportedButtons; } int tapFingerCount() const { return m_tapFingerCount; } bool tapToClickEnabledByDefault() const { return m_tapToClickEnabledByDefault; } bool isTapToClick() const { return m_tapToClick; } /** * Set the Device to tap to click if @p set is @c true. **/ void setTapToClick(bool set); bool tapAndDragEnabledByDefault() const { return m_tapAndDragEnabledByDefault; } bool isTapAndDrag() const { return m_tapAndDrag; } void setTapAndDrag(bool set); bool tapDragLockEnabledByDefault() const { return m_tapDragLockEnabledByDefault; } bool isTapDragLock() const { return m_tapDragLock; } void setTapDragLock(bool set); bool supportsDisableWhileTyping() const { return m_supportsDisableWhileTyping; } bool disableWhileTypingEnabledByDefault() const { return m_disableWhileTypingEnabledByDefault; } bool supportsPointerAcceleration() const { return m_supportsPointerAcceleration; } bool supportsLeftHanded() const { return m_supportsLeftHanded; } bool supportsCalibrationMatrix() const { return m_supportsCalibrationMatrix; } bool supportsDisableEvents() const { return m_supportsDisableEvents; } bool supportsDisableEventsOnExternalMouse() const { return m_supportsDisableEventsOnExternalMouse; } bool supportsMiddleEmulation() const { return m_supportsMiddleEmulation; } bool supportsNaturalScroll() const { return m_supportsNaturalScroll; } bool supportsScrollTwoFinger() const { return (m_supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_2FG); } bool supportsScrollEdge() const { return (m_supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_EDGE); } bool supportsScrollOnButtonDown() const { return (m_supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); } bool leftHandedEnabledByDefault() const { return m_leftHandedEnabledByDefault; } bool middleEmulationEnabledByDefault() const { return m_middleEmulationEnabledByDefault; } bool naturalScrollEnabledByDefault() const { return m_naturalScrollEnabledByDefault; } enum libinput_config_scroll_method defaultScrollMethod() const { return m_defaultScrollMethod; } quint32 defaultScrollMethodToInt() const { return (quint32) m_defaultScrollMethod; } bool scrollTwoFingerEnabledByDefault() const { return m_defaultScrollMethod == LIBINPUT_CONFIG_SCROLL_2FG; } bool scrollEdgeEnabledByDefault() const { return m_defaultScrollMethod == LIBINPUT_CONFIG_SCROLL_EDGE; } bool scrollOnButtonDownEnabledByDefault() const { return m_defaultScrollMethod == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; } bool supportsLmrTapButtonMap() const { return m_tapFingerCount > 1; } bool lmrTapButtonMapEnabledByDefault() const { return m_defaultTapButtonMap == LIBINPUT_CONFIG_TAP_MAP_LMR; } void setLmrTapButtonMap(bool set); bool lmrTapButtonMap() const { return m_tapButtonMap & LIBINPUT_CONFIG_TAP_MAP_LMR; } quint32 defaultScrollButton() const { return m_defaultScrollButton; } bool isMiddleEmulation() const { return m_middleEmulation; } void setMiddleEmulation(bool set); bool isNaturalScroll() const { return m_naturalScroll; } void setNaturalScroll(bool set); void setScrollMethod(bool set, enum libinput_config_scroll_method method); bool isScrollTwoFinger() const { return m_scrollMethod & LIBINPUT_CONFIG_SCROLL_2FG; } void setScrollTwoFinger(bool set) { setScrollMethod(set, LIBINPUT_CONFIG_SCROLL_2FG); } bool isScrollEdge() const { return m_scrollMethod & LIBINPUT_CONFIG_SCROLL_EDGE; } void setScrollEdge(bool set) { setScrollMethod(set, LIBINPUT_CONFIG_SCROLL_EDGE); } bool isScrollOnButtonDown() const { return m_scrollMethod & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; } void setScrollOnButtonDown(bool set) { setScrollMethod(set, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); } void activateScrollMethodFromInt(quint32 method) { setScrollMethod(true, (libinput_config_scroll_method) method); } quint32 scrollButton() const { return m_scrollButton; } void setScrollButton(quint32 button); void setDisableWhileTyping(bool set); bool isDisableWhileTyping() const { return m_disableWhileTyping; } bool isLeftHanded() const { return m_leftHanded; } /** * Sets the Device to left handed mode if @p set is @c true. * If @p set is @c false the device is set to right handed mode **/ void setLeftHanded(bool set); qreal defaultPointerAcceleration() const { return m_defaultPointerAcceleration; } qreal pointerAcceleration() const { return m_pointerAcceleration; } /** * @param acceleration mapped to range [-1,1] with -1 being the slowest, 1 being the fastest supported acceleration. **/ void setPointerAcceleration(qreal acceleration); void setPointerAccelerationFromString(QString acceleration) { setPointerAcceleration(acceleration.toDouble()); } QString defaultPointerAccelerationToString() const { return QString::number(m_pointerAcceleration, 'f', 3); } bool supportsPointerAccelerationProfileFlat() const { return (m_supportedPointerAccelerationProfiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); } bool supportsPointerAccelerationProfileAdaptive() const { return (m_supportedPointerAccelerationProfiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); } bool defaultPointerAccelerationProfileFlat() const { return (m_defaultPointerAccelerationProfile & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); } bool defaultPointerAccelerationProfileAdaptive() const { return (m_defaultPointerAccelerationProfile & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); } bool pointerAccelerationProfileFlat() const { return (m_pointerAccelerationProfile & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); } bool pointerAccelerationProfileAdaptive() const { return (m_pointerAccelerationProfile & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); } void setPointerAccelerationProfile(bool set, enum libinput_config_accel_profile profile); void setPointerAccelerationProfileFlat(bool set) { setPointerAccelerationProfile(set, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); } void setPointerAccelerationProfileAdaptive(bool set) { setPointerAccelerationProfile(set, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); } void setPointerAccelerationProfileFromInt(quint32 profile) { setPointerAccelerationProfile(true, (libinput_config_accel_profile) profile); } quint32 defaultPointerAccelerationProfileToInt() const { return (quint32) m_defaultPointerAccelerationProfile; } bool isEnabled() const { return m_enabled; } void setEnabled(bool enabled); libinput_device *device() const { return m_device; } /** * Sets the @p config to load the Device configuration from and to store each * successful Device configuration. **/ void setConfig(const KConfigGroup &config) { m_config = config; } + /** + * The id of the screen in KWin identifiers. Set from KWin through @link setScreenId. + **/ + int screenId() const { + return m_screenId; + } + + /** + * Sets the KWin screen id for the device + **/ + void setScreenId(int screenId) { + m_screenId = screenId; + } + + void setOrientation(Qt::ScreenOrientation orientation); + /** * Loads the configuration and applies it to the Device **/ void loadConfiguration(); + bool isSwitch() const { + return m_switch; + } + + bool isLidSwitch() const { + return m_lidSwitch; + } + + bool isTabletModeSwitch() const { + return m_tabletSwitch; + } + /** * All created Devices **/ static QVector devices() { return s_devices; } /** * Gets the Device for @p native. @c null if there is no Device for @p native. **/ static Device *getDevice(libinput_device *native); Q_SIGNALS: void tapButtonMapChanged(); void leftHandedChanged(); void disableWhileTypingChanged(); void pointerAccelerationChanged(); void pointerAccelerationProfileChanged(); void enabledChanged(); void tapToClickChanged(); void tapAndDragChanged(); void tapDragLockChanged(); void middleEmulationChanged(); void naturalScrollChanged(); void scrollMethodChanged(); void scrollButtonChanged(); private: template void writeEntry(const ConfigKey &key, const T &value); template void readEntry(const QByteArray &key, const Setter &s, const T &defaultValue = T()); libinput_device *m_device; bool m_keyboard; bool m_alphaNumericKeyboard = false; bool m_pointer; bool m_touch; bool m_tabletTool; bool m_tabletPad; bool m_supportsGesture; + bool m_switch = false; + bool m_lidSwitch = false; + bool m_tabletSwitch = false; QString m_name; QString m_sysName; QString m_outputName; QSizeF m_size; quint32 m_product; quint32 m_vendor; Qt::MouseButtons m_supportedButtons = Qt::NoButton; int m_tapFingerCount; enum libinput_config_tap_button_map m_defaultTapButtonMap; enum libinput_config_tap_button_map m_tapButtonMap; bool m_tapToClickEnabledByDefault; bool m_tapToClick; bool m_tapAndDragEnabledByDefault; bool m_tapAndDrag; bool m_tapDragLockEnabledByDefault; bool m_tapDragLock; bool m_supportsDisableWhileTyping; bool m_supportsPointerAcceleration; bool m_supportsLeftHanded; bool m_supportsCalibrationMatrix; bool m_supportsDisableEvents; bool m_supportsDisableEventsOnExternalMouse; bool m_supportsMiddleEmulation; bool m_supportsNaturalScroll; quint32 m_supportedScrollMethods; bool m_supportsScrollEdge; bool m_supportsScrollOnButtonDown; bool m_leftHandedEnabledByDefault; bool m_middleEmulationEnabledByDefault; bool m_naturalScrollEnabledByDefault; enum libinput_config_scroll_method m_defaultScrollMethod; quint32 m_defaultScrollButton; bool m_disableWhileTypingEnabledByDefault; bool m_disableWhileTyping; bool m_middleEmulation; bool m_leftHanded; bool m_naturalScroll; enum libinput_config_scroll_method m_scrollMethod; quint32 m_scrollButton; qreal m_defaultPointerAcceleration; qreal m_pointerAcceleration; quint32 m_supportedPointerAccelerationProfiles; enum libinput_config_accel_profile m_defaultPointerAccelerationProfile; enum libinput_config_accel_profile m_pointerAccelerationProfile; bool m_enabled; KConfigGroup m_config; bool m_loading = false; + int m_screenId = 0; + Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation; + QMatrix4x4 m_defaultCalibrationMatrix; + static QVector s_devices; }; } } Q_DECLARE_METATYPE(KWin::LibInput::Device*) #endif diff --git a/libinput/events.cpp b/libinput/events.cpp index 183ab70c2..17efc6425 100644 --- a/libinput/events.cpp +++ b/libinput/events.cpp @@ -1,288 +1,322 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "events.h" #include "device.h" #include namespace KWin { namespace LibInput { Event *Event::create(libinput_event *event) { if (!event) { return nullptr; } const auto t = libinput_event_get_type(event); // TODO: add touch events // TODO: add device notify events switch (t) { case LIBINPUT_EVENT_KEYBOARD_KEY: return new KeyEvent(event); case LIBINPUT_EVENT_POINTER_AXIS: case LIBINPUT_EVENT_POINTER_BUTTON: case LIBINPUT_EVENT_POINTER_MOTION: case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: return new PointerEvent(event, t); case LIBINPUT_EVENT_TOUCH_DOWN: case LIBINPUT_EVENT_TOUCH_UP: case LIBINPUT_EVENT_TOUCH_MOTION: case LIBINPUT_EVENT_TOUCH_CANCEL: case LIBINPUT_EVENT_TOUCH_FRAME: return new TouchEvent(event, t); case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: case LIBINPUT_EVENT_GESTURE_SWIPE_END: return new SwipeGestureEvent(event, t); case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: case LIBINPUT_EVENT_GESTURE_PINCH_END: return new PinchGestureEvent(event, t); + case LIBINPUT_EVENT_SWITCH_TOGGLE: + return new SwitchEvent(event, t); default: return new Event(event, t); } } Event::Event(libinput_event *event, libinput_event_type type) : m_event(event) , m_type(type) , m_device(Device::getDevice(libinput_event_get_device(m_event))) { } Event::~Event() { libinput_event_destroy(m_event); } libinput_device *Event::nativeDevice() const { if (m_device) { return m_device->device(); } return libinput_event_get_device(m_event); } KeyEvent::KeyEvent(libinput_event *event) : Event(event, LIBINPUT_EVENT_KEYBOARD_KEY) , m_keyboardEvent(libinput_event_get_keyboard_event(event)) { } KeyEvent::~KeyEvent() = default; uint32_t KeyEvent::key() const { return libinput_event_keyboard_get_key(m_keyboardEvent); } InputRedirection::KeyboardKeyState KeyEvent::state() const { switch (libinput_event_keyboard_get_key_state(m_keyboardEvent)) { case LIBINPUT_KEY_STATE_PRESSED: return InputRedirection::KeyboardKeyPressed; case LIBINPUT_KEY_STATE_RELEASED: return InputRedirection::KeyboardKeyReleased; } abort(); } uint32_t KeyEvent::time() const { return libinput_event_keyboard_get_time(m_keyboardEvent); } PointerEvent::PointerEvent(libinput_event *event, libinput_event_type type) : Event(event, type) , m_pointerEvent(libinput_event_get_pointer_event(event)) { } PointerEvent::~PointerEvent() = default; QPointF PointerEvent::absolutePos() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); return QPointF(libinput_event_pointer_get_absolute_x(m_pointerEvent), libinput_event_pointer_get_absolute_y(m_pointerEvent)); } QPointF PointerEvent::absolutePos(const QSize &size) const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); return QPointF(libinput_event_pointer_get_absolute_x_transformed(m_pointerEvent, size.width()), libinput_event_pointer_get_absolute_y_transformed(m_pointerEvent, size.height())); } QSizeF PointerEvent::delta() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION); return QSizeF(libinput_event_pointer_get_dx(m_pointerEvent), libinput_event_pointer_get_dy(m_pointerEvent)); } QSizeF PointerEvent::deltaUnaccelerated() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION); return QSizeF(libinput_event_pointer_get_dx_unaccelerated(m_pointerEvent), libinput_event_pointer_get_dy_unaccelerated(m_pointerEvent)); } uint32_t PointerEvent::time() const { return libinput_event_pointer_get_time(m_pointerEvent); } quint64 PointerEvent::timeMicroseconds() const { return libinput_event_pointer_get_time_usec(m_pointerEvent); } uint32_t PointerEvent::button() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_BUTTON); return libinput_event_pointer_get_button(m_pointerEvent); } InputRedirection::PointerButtonState PointerEvent::buttonState() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_BUTTON); switch (libinput_event_pointer_get_button_state(m_pointerEvent)) { case LIBINPUT_BUTTON_STATE_PRESSED: return InputRedirection::PointerButtonPressed; case LIBINPUT_BUTTON_STATE_RELEASED: return InputRedirection::PointerButtonReleased; } abort(); } QVector PointerEvent::axis() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_AXIS); QVector a; if (libinput_event_pointer_has_axis(m_pointerEvent, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) { a << InputRedirection::PointerAxisHorizontal; } if (libinput_event_pointer_has_axis(m_pointerEvent, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) { a << InputRedirection::PointerAxisVertical; } return a; } qreal PointerEvent::axisValue(InputRedirection::PointerAxis axis) const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_AXIS); const libinput_pointer_axis a = axis == InputRedirection::PointerAxisHorizontal ? LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL : LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL; return libinput_event_pointer_get_axis_value(m_pointerEvent, a); } TouchEvent::TouchEvent(libinput_event *event, libinput_event_type type) : Event(event, type) , m_touchEvent(libinput_event_get_touch_event(event)) { } TouchEvent::~TouchEvent() = default; quint32 TouchEvent::time() const { return libinput_event_touch_get_time(m_touchEvent); } QPointF TouchEvent::absolutePos() const { Q_ASSERT(type() == LIBINPUT_EVENT_TOUCH_DOWN || type() == LIBINPUT_EVENT_TOUCH_MOTION); return QPointF(libinput_event_touch_get_x(m_touchEvent), libinput_event_touch_get_y(m_touchEvent)); } QPointF TouchEvent::absolutePos(const QSize &size) const { Q_ASSERT(type() == LIBINPUT_EVENT_TOUCH_DOWN || type() == LIBINPUT_EVENT_TOUCH_MOTION); return QPointF(libinput_event_touch_get_x_transformed(m_touchEvent, size.width()), libinput_event_touch_get_y_transformed(m_touchEvent, size.height())); } qint32 TouchEvent::id() const { Q_ASSERT(type() != LIBINPUT_EVENT_TOUCH_CANCEL && type() != LIBINPUT_EVENT_TOUCH_FRAME); const qint32 slot = libinput_event_touch_get_slot(m_touchEvent); return slot == -1 ? 0 : slot; } GestureEvent::GestureEvent(libinput_event *event, libinput_event_type type) : Event(event, type) , m_gestureEvent(libinput_event_get_gesture_event(event)) { } GestureEvent::~GestureEvent() = default; quint32 GestureEvent::time() const { return libinput_event_gesture_get_time(m_gestureEvent); } int GestureEvent::fingerCount() const { return libinput_event_gesture_get_finger_count(m_gestureEvent); } QSizeF GestureEvent::delta() const { return QSizeF(libinput_event_gesture_get_dx(m_gestureEvent), libinput_event_gesture_get_dy(m_gestureEvent)); } bool GestureEvent::isCancelled() const { return libinput_event_gesture_get_cancelled(m_gestureEvent) != 0; } PinchGestureEvent::PinchGestureEvent(libinput_event *event, libinput_event_type type) : GestureEvent(event, type) { } PinchGestureEvent::~PinchGestureEvent() = default; qreal PinchGestureEvent::scale() const { return libinput_event_gesture_get_scale(m_gestureEvent); } qreal PinchGestureEvent::angleDelta() const { return libinput_event_gesture_get_angle_delta(m_gestureEvent); } SwipeGestureEvent::SwipeGestureEvent(libinput_event *event, libinput_event_type type) : GestureEvent(event, type) { } SwipeGestureEvent::~SwipeGestureEvent() = default; +SwitchEvent::SwitchEvent(libinput_event *event, libinput_event_type type) + : Event(event, type) + , m_switchEvent(libinput_event_get_switch_event(event)) +{ +} + +SwitchEvent::~SwitchEvent() = default; + +SwitchEvent::State SwitchEvent::state() const +{ + switch (libinput_event_switch_get_switch_state(m_switchEvent)) + { + case LIBINPUT_SWITCH_STATE_OFF: + return State::Off; + case LIBINPUT_SWITCH_STATE_ON: + return State::On; + default: + Q_UNREACHABLE(); + } + return State::Off; +} + +quint32 SwitchEvent::time() const +{ + return libinput_event_switch_get_time(m_switchEvent); +} + +quint64 SwitchEvent::timeMicroseconds() const +{ + return libinput_event_switch_get_time_usec(m_switchEvent); +} + } } diff --git a/libinput/events.h b/libinput/events.h index 646844cb7..28d68a6b5 100644 --- a/libinput/events.h +++ b/libinput/events.h @@ -1,184 +1,203 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_LIBINPUT_EVENTS_H #define KWIN_LIBINPUT_EVENTS_H #include "../input.h" #include namespace KWin { namespace LibInput { class Device; class Event { public: virtual ~Event(); libinput_event_type type() const; Device *device() const { return m_device; } libinput_device *nativeDevice() const; operator libinput_event*() { return m_event; } operator libinput_event*() const { return m_event; } static Event *create(libinput_event *event); protected: Event(libinput_event *event, libinput_event_type type); private: libinput_event *m_event; libinput_event_type m_type; Device *m_device; }; class KeyEvent : public Event { public: KeyEvent(libinput_event *event); virtual ~KeyEvent(); uint32_t key() const; InputRedirection::KeyboardKeyState state() const; uint32_t time() const; operator libinput_event_keyboard*() { return m_keyboardEvent; } operator libinput_event_keyboard*() const { return m_keyboardEvent; } private: libinput_event_keyboard *m_keyboardEvent; }; class PointerEvent : public Event { public: PointerEvent(libinput_event* event, libinput_event_type type); virtual ~PointerEvent(); QPointF absolutePos() const; QPointF absolutePos(const QSize &size) const; QSizeF delta() const; QSizeF deltaUnaccelerated() const; uint32_t button() const; InputRedirection::PointerButtonState buttonState() const; uint32_t time() const; quint64 timeMicroseconds() const; QVector axis() const; qreal axisValue(InputRedirection::PointerAxis a) const; operator libinput_event_pointer*() { return m_pointerEvent; } operator libinput_event_pointer*() const { return m_pointerEvent; } private: libinput_event_pointer *m_pointerEvent; }; class TouchEvent : public Event { public: TouchEvent(libinput_event *event, libinput_event_type type); virtual ~TouchEvent(); quint32 time() const; QPointF absolutePos() const; QPointF absolutePos(const QSize &size) const; qint32 id() const; operator libinput_event_touch*() { return m_touchEvent; } operator libinput_event_touch*() const { return m_touchEvent; } private: libinput_event_touch *m_touchEvent; }; class GestureEvent : public Event { public: virtual ~GestureEvent(); quint32 time() const; int fingerCount() const; QSizeF delta() const; bool isCancelled() const; operator libinput_event_gesture*() { return m_gestureEvent; } operator libinput_event_gesture*() const { return m_gestureEvent; } protected: GestureEvent(libinput_event *event, libinput_event_type type); libinput_event_gesture *m_gestureEvent; }; class PinchGestureEvent : public GestureEvent { public: PinchGestureEvent(libinput_event *event, libinput_event_type type); virtual ~PinchGestureEvent(); qreal scale() const; qreal angleDelta() const; }; class SwipeGestureEvent : public GestureEvent { public: SwipeGestureEvent(libinput_event *event, libinput_event_type type); virtual ~SwipeGestureEvent(); }; +class SwitchEvent : public Event +{ +public: + SwitchEvent(libinput_event *event, libinput_event_type type); + ~SwitchEvent() override; + + enum class State { + Off, + On + }; + State state() const; + + quint32 time() const; + quint64 timeMicroseconds() const; + +private: + libinput_event_switch *m_switchEvent; +}; + inline libinput_event_type Event::type() const { return m_type; } } } #endif diff --git a/libkwineffects/kwineffects.cpp b/libkwineffects/kwineffects.cpp index fdcae25da..47aeb2437 100644 --- a/libkwineffects/kwineffects.cpp +++ b/libkwineffects/kwineffects.cpp @@ -1,1883 +1,1883 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray 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 "kwineffects.h" #include "config-kwin.h" #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include "kwinxrenderutils.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #endif #if defined(__GNUC__) # define KWIN_ALIGN(n) __attribute((aligned(n))) # if defined(__SSE2__) # define HAVE_SSE2 # endif #elif defined(__INTEL_COMPILER) # define KWIN_ALIGN(n) __declspec(align(n)) # define HAVE_SSE2 #else # define KWIN_ALIGN(n) #endif #ifdef HAVE_SSE2 # include #endif namespace KWin { void WindowPrePaintData::setTranslucent() { mask |= Effect::PAINT_WINDOW_TRANSLUCENT; mask &= ~Effect::PAINT_WINDOW_OPAQUE; clip = QRegion(); // cannot clip, will be transparent } void WindowPrePaintData::setTransformed() { mask |= Effect::PAINT_WINDOW_TRANSFORMED; } class PaintDataPrivate { public: QGraphicsScale scale; QVector3D translation; QGraphicsRotation rotation; }; PaintData::PaintData() : d(new PaintDataPrivate()) { } PaintData::~PaintData() { delete d; } qreal PaintData::xScale() const { return d->scale.xScale(); } qreal PaintData::yScale() const { return d->scale.yScale(); } qreal PaintData::zScale() const { return d->scale.zScale(); } void PaintData::setScale(const QVector2D &scale) { d->scale.setXScale(scale.x()); d->scale.setYScale(scale.y()); } void PaintData::setScale(const QVector3D &scale) { d->scale.setXScale(scale.x()); d->scale.setYScale(scale.y()); d->scale.setZScale(scale.z()); } void PaintData::setXScale(qreal scale) { d->scale.setXScale(scale); } void PaintData::setYScale(qreal scale) { d->scale.setYScale(scale); } void PaintData::setZScale(qreal scale) { d->scale.setZScale(scale); } const QGraphicsScale &PaintData::scale() const { return d->scale; } void PaintData::setXTranslation(qreal translate) { d->translation.setX(translate); } void PaintData::setYTranslation(qreal translate) { d->translation.setY(translate); } void PaintData::setZTranslation(qreal translate) { d->translation.setZ(translate); } void PaintData::translate(qreal x, qreal y, qreal z) { translate(QVector3D(x, y, z)); } void PaintData::translate(const QVector3D &t) { d->translation += t; } qreal PaintData::xTranslation() const { return d->translation.x(); } qreal PaintData::yTranslation() const { return d->translation.y(); } qreal PaintData::zTranslation() const { return d->translation.z(); } const QVector3D &PaintData::translation() const { return d->translation; } qreal PaintData::rotationAngle() const { return d->rotation.angle(); } QVector3D PaintData::rotationAxis() const { return d->rotation.axis(); } QVector3D PaintData::rotationOrigin() const { return d->rotation.origin(); } void PaintData::setRotationAngle(qreal angle) { d->rotation.setAngle(angle); } void PaintData::setRotationAxis(Qt::Axis axis) { d->rotation.setAxis(axis); } void PaintData::setRotationAxis(const QVector3D &axis) { d->rotation.setAxis(axis); } void PaintData::setRotationOrigin(const QVector3D &origin) { d->rotation.setOrigin(origin); } class WindowPaintDataPrivate { public: qreal opacity; qreal saturation; qreal brightness; int screen; qreal crossFadeProgress; QMatrix4x4 pMatrix; QMatrix4x4 mvMatrix; QMatrix4x4 screenProjectionMatrix; }; WindowPaintData::WindowPaintData(EffectWindow *w) : WindowPaintData(w, QMatrix4x4()) { } WindowPaintData::WindowPaintData(EffectWindow* w, const QMatrix4x4 &screenProjectionMatrix) : PaintData() , shader(nullptr) , d(new WindowPaintDataPrivate()) { d->screenProjectionMatrix = screenProjectionMatrix; quads = w->buildQuads(); setOpacity(w->opacity()); setSaturation(1.0); setBrightness(1.0); setScreen(0); setCrossFadeProgress(1.0); } WindowPaintData::WindowPaintData(const WindowPaintData &other) : PaintData() , quads(other.quads) , shader(other.shader) , d(new WindowPaintDataPrivate()) { setXScale(other.xScale()); setYScale(other.yScale()); setZScale(other.zScale()); translate(other.translation()); setRotationOrigin(other.rotationOrigin()); setRotationAxis(other.rotationAxis()); setRotationAngle(other.rotationAngle()); setOpacity(other.opacity()); setSaturation(other.saturation()); setBrightness(other.brightness()); setScreen(other.screen()); setCrossFadeProgress(other.crossFadeProgress()); setProjectionMatrix(other.projectionMatrix()); setModelViewMatrix(other.modelViewMatrix()); d->screenProjectionMatrix = other.d->screenProjectionMatrix; } WindowPaintData::~WindowPaintData() { delete d; } qreal WindowPaintData::opacity() const { return d->opacity; } qreal WindowPaintData::saturation() const { return d->saturation; } qreal WindowPaintData::brightness() const { return d->brightness; } int WindowPaintData::screen() const { return d->screen; } void WindowPaintData::setOpacity(qreal opacity) { d->opacity = opacity; } void WindowPaintData::setSaturation(qreal saturation) const { d->saturation = saturation; } void WindowPaintData::setBrightness(qreal brightness) { d->brightness = brightness; } void WindowPaintData::setScreen(int screen) const { d->screen = screen; } qreal WindowPaintData::crossFadeProgress() const { return d->crossFadeProgress; } void WindowPaintData::setCrossFadeProgress(qreal factor) { d->crossFadeProgress = qBound(qreal(0.0), factor, qreal(1.0)); } qreal WindowPaintData::multiplyOpacity(qreal factor) { d->opacity *= factor; return d->opacity; } qreal WindowPaintData::multiplySaturation(qreal factor) { d->saturation *= factor; return d->saturation; } qreal WindowPaintData::multiplyBrightness(qreal factor) { d->brightness *= factor; return d->brightness; } void WindowPaintData::setProjectionMatrix(const QMatrix4x4 &matrix) { d->pMatrix = matrix; } QMatrix4x4 WindowPaintData::projectionMatrix() const { return d->pMatrix; } QMatrix4x4 &WindowPaintData::rprojectionMatrix() { return d->pMatrix; } void WindowPaintData::setModelViewMatrix(const QMatrix4x4 &matrix) { d->mvMatrix = matrix; } QMatrix4x4 WindowPaintData::modelViewMatrix() const { return d->mvMatrix; } QMatrix4x4 &WindowPaintData::rmodelViewMatrix() { return d->mvMatrix; } WindowPaintData &WindowPaintData::operator*=(qreal scale) { this->setXScale(this->xScale() * scale); this->setYScale(this->yScale() * scale); this->setZScale(this->zScale() * scale); return *this; } WindowPaintData &WindowPaintData::operator*=(const QVector2D &scale) { this->setXScale(this->xScale() * scale.x()); this->setYScale(this->yScale() * scale.y()); return *this; } WindowPaintData &WindowPaintData::operator*=(const QVector3D &scale) { this->setXScale(this->xScale() * scale.x()); this->setYScale(this->yScale() * scale.y()); this->setZScale(this->zScale() * scale.z()); return *this; } WindowPaintData &WindowPaintData::operator+=(const QPointF &translation) { return this->operator+=(QVector3D(translation)); } WindowPaintData &WindowPaintData::operator+=(const QPoint &translation) { return this->operator+=(QVector3D(translation)); } WindowPaintData &WindowPaintData::operator+=(const QVector2D &translation) { return this->operator+=(QVector3D(translation)); } WindowPaintData &WindowPaintData::operator+=(const QVector3D &translation) { translate(translation); return *this; } QMatrix4x4 WindowPaintData::screenProjectionMatrix() const { return d->screenProjectionMatrix; } class ScreenPaintData::Private { public: QMatrix4x4 projectionMatrix; QRect outputGeometry; }; ScreenPaintData::ScreenPaintData() : PaintData() , d(new Private()) { } ScreenPaintData::ScreenPaintData(const QMatrix4x4 &projectionMatrix, const QRect &outputGeometry) : PaintData() , d(new Private()) { d->projectionMatrix = projectionMatrix; d->outputGeometry = outputGeometry; } ScreenPaintData::~ScreenPaintData() = default; ScreenPaintData::ScreenPaintData(const ScreenPaintData &other) : PaintData() , d(new Private()) { translate(other.translation()); setXScale(other.xScale()); setYScale(other.yScale()); setZScale(other.zScale()); setRotationOrigin(other.rotationOrigin()); setRotationAxis(other.rotationAxis()); setRotationAngle(other.rotationAngle()); d->projectionMatrix = other.d->projectionMatrix; d->outputGeometry = other.d->outputGeometry; } ScreenPaintData &ScreenPaintData::operator=(const ScreenPaintData &rhs) { setXScale(rhs.xScale()); setYScale(rhs.yScale()); setZScale(rhs.zScale()); setXTranslation(rhs.xTranslation()); setYTranslation(rhs.yTranslation()); setZTranslation(rhs.zTranslation()); setRotationOrigin(rhs.rotationOrigin()); setRotationAxis(rhs.rotationAxis()); setRotationAngle(rhs.rotationAngle()); d->projectionMatrix = rhs.d->projectionMatrix; d->outputGeometry = rhs.d->outputGeometry; return *this; } ScreenPaintData &ScreenPaintData::operator*=(qreal scale) { setXScale(this->xScale() * scale); setYScale(this->yScale() * scale); setZScale(this->zScale() * scale); return *this; } ScreenPaintData &ScreenPaintData::operator*=(const QVector2D &scale) { setXScale(this->xScale() * scale.x()); setYScale(this->yScale() * scale.y()); return *this; } ScreenPaintData &ScreenPaintData::operator*=(const QVector3D &scale) { setXScale(this->xScale() * scale.x()); setYScale(this->yScale() * scale.y()); setZScale(this->zScale() * scale.z()); return *this; } ScreenPaintData &ScreenPaintData::operator+=(const QPointF &translation) { return this->operator+=(QVector3D(translation)); } ScreenPaintData &ScreenPaintData::operator+=(const QPoint &translation) { return this->operator+=(QVector3D(translation)); } ScreenPaintData &ScreenPaintData::operator+=(const QVector2D &translation) { return this->operator+=(QVector3D(translation)); } ScreenPaintData &ScreenPaintData::operator+=(const QVector3D &translation) { translate(translation); return *this; } QMatrix4x4 ScreenPaintData::projectionMatrix() const { return d->projectionMatrix; } QRect ScreenPaintData::outputGeometry() const { return d->outputGeometry; } //**************************************** // Effect //**************************************** Effect::Effect() { } Effect::~Effect() { } void Effect::reconfigure(ReconfigureFlags) { } void* Effect::proxy() { return nullptr; } void Effect::windowInputMouseEvent(QEvent*) { } void Effect::grabbedKeyboardEvent(QKeyEvent*) { } bool Effect::borderActivated(ElectricBorder) { return false; } void Effect::prePaintScreen(ScreenPrePaintData& data, int time) { effects->prePaintScreen(data, time); } void Effect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); } void Effect::postPaintScreen() { effects->postPaintScreen(); } void Effect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { effects->prePaintWindow(w, data, time); } void Effect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->paintWindow(w, mask, region, data); } void Effect::postPaintWindow(EffectWindow* w) { effects->postPaintWindow(w); } void Effect::paintEffectFrame(KWin::EffectFrame* frame, QRegion region, double opacity, double frameOpacity) { effects->paintEffectFrame(frame, region, opacity, frameOpacity); } bool Effect::provides(Feature) { return false; } bool Effect::isActive() const { return true; } QString Effect::debug(const QString &) const { return QString(); } void Effect::drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->drawWindow(w, mask, region, data); } void Effect::buildQuads(EffectWindow* w, WindowQuadList& quadList) { effects->buildQuads(w, quadList); } void Effect::setPositionTransformations(WindowPaintData& data, QRect& region, EffectWindow* w, const QRect& r, Qt::AspectRatioMode aspect) { QSize size = w->size(); size.scale(r.size(), aspect); data.setXScale(size.width() / double(w->width())); data.setYScale(size.height() / double(w->height())); int width = int(w->width() * data.xScale()); int height = int(w->height() * data.yScale()); int x = r.x() + (r.width() - width) / 2; int y = r.y() + (r.height() - height) / 2; region = QRect(x, y, width, height); data.setXTranslation(x - w->x()); data.setYTranslation(y - w->y()); } QPoint Effect::cursorPos() { return effects->cursorPos(); } double Effect::animationTime(const KConfigGroup& cfg, const QString& key, int defaultTime) { int time = cfg.readEntry(key, 0); return time != 0 ? time : qMax(defaultTime * effects->animationTimeFactor(), 1.); } double Effect::animationTime(int defaultTime) { // at least 1ms, otherwise 0ms times can break some things return qMax(defaultTime * effects->animationTimeFactor(), 1.); } int Effect::requestedEffectChainPosition() const { return 0; } xcb_connection_t *Effect::xcbConnection() const { return effects->xcbConnection(); } xcb_window_t Effect::x11RootWindow() const { return effects->x11RootWindow(); } bool Effect::touchDown(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) return false; } bool Effect::touchMotion(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) return false; } bool Effect::touchUp(quint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) return false; } bool Effect::perform(Feature feature, const QVariantList &arguments) { Q_UNUSED(feature) Q_UNUSED(arguments) return false; } //**************************************** // EffectFactory //**************************************** EffectPluginFactory::EffectPluginFactory() { } EffectPluginFactory::~EffectPluginFactory() { } bool EffectPluginFactory::enabledByDefault() const { return true; } bool EffectPluginFactory::isSupported() const { return true; } //**************************************** // EffectsHandler //**************************************** EffectsHandler::EffectsHandler(CompositingType type) : compositing_type(type) { if (compositing_type == NoCompositing) return; KWin::effects = this; } EffectsHandler::~EffectsHandler() { // All effects should already be unloaded by Impl dtor assert(loaded_effects.count() == 0); } CompositingType EffectsHandler::compositingType() const { return compositing_type; } bool EffectsHandler::isOpenGLCompositing() const { return compositing_type & OpenGLCompositing; } EffectsHandler* effects = nullptr; //**************************************** // EffectWindow //**************************************** EffectWindow::EffectWindow(QObject *parent) : QObject(parent) { } EffectWindow::~EffectWindow() { } #define WINDOW_HELPER( rettype, prototype, propertyname ) \ rettype EffectWindow::prototype ( ) const \ { \ return parent()->property( propertyname ).value< rettype >(); \ } WINDOW_HELPER(double, opacity, "opacity") WINDOW_HELPER(bool, hasAlpha, "alpha") WINDOW_HELPER(int, x, "x") WINDOW_HELPER(int, y, "y") WINDOW_HELPER(int, width, "width") WINDOW_HELPER(int, height, "height") WINDOW_HELPER(QPoint, pos, "pos") WINDOW_HELPER(QSize, size, "size") WINDOW_HELPER(int, screen, "screen") WINDOW_HELPER(QRect, geometry, "geometry") WINDOW_HELPER(QRect, expandedGeometry, "visibleRect") WINDOW_HELPER(QRect, rect, "rect") WINDOW_HELPER(int, desktop, "desktop") WINDOW_HELPER(bool, isDesktop, "desktopWindow") WINDOW_HELPER(bool, isDock, "dock") WINDOW_HELPER(bool, isToolbar, "toolbar") WINDOW_HELPER(bool, isMenu, "menu") WINDOW_HELPER(bool, isNormalWindow, "normalWindow") WINDOW_HELPER(bool, isDialog, "dialog") WINDOW_HELPER(bool, isSplash, "splash") WINDOW_HELPER(bool, isUtility, "utility") WINDOW_HELPER(bool, isDropdownMenu, "dropdownMenu") WINDOW_HELPER(bool, isPopupMenu, "popupMenu") WINDOW_HELPER(bool, isTooltip, "tooltip") WINDOW_HELPER(bool, isNotification, "notification") WINDOW_HELPER(bool, isOnScreenDisplay, "onScreenDisplay") WINDOW_HELPER(bool, isComboBox, "comboBox") WINDOW_HELPER(bool, isDNDIcon, "dndIcon") WINDOW_HELPER(bool, isManaged, "managed") WINDOW_HELPER(bool, isDeleted, "deleted") WINDOW_HELPER(bool, hasOwnShape, "shaped") WINDOW_HELPER(QString, windowRole, "windowRole") WINDOW_HELPER(QStringList, activities, "activities") WINDOW_HELPER(bool, skipsCloseAnimation, "skipsCloseAnimation") WINDOW_HELPER(KWayland::Server::SurfaceInterface *, surface, "surface") QString EffectWindow::windowClass() const { return parent()->property("resourceName").toString() + QLatin1Char(' ') + parent()->property("resourceClass").toString(); } QRect EffectWindow::contentsRect() const { return QRect(parent()->property("clientPos").toPoint(), parent()->property("clientSize").toSize()); } NET::WindowType EffectWindow::windowType() const { return static_cast(parent()->property("windowType").toInt()); } bool EffectWindow::isOnActivity(QString activity) const { const QStringList activities = parent()->property("activities").toStringList(); return activities.isEmpty() || activities.contains(activity); } bool EffectWindow::isOnAllActivities() const { return parent()->property("activities").toStringList().isEmpty(); } #undef WINDOW_HELPER #define WINDOW_HELPER_DEFAULT( rettype, prototype, propertyname, defaultValue ) \ rettype EffectWindow::prototype ( ) const \ { \ const QVariant variant = parent()->property( propertyname ); \ if (!variant.isValid()) { \ return defaultValue; \ } \ return variant.value< rettype >(); \ } WINDOW_HELPER_DEFAULT(bool, isMinimized, "minimized", false) WINDOW_HELPER_DEFAULT(bool, isMovable, "moveable", false) WINDOW_HELPER_DEFAULT(bool, isMovableAcrossScreens, "moveableAcrossScreens", false) WINDOW_HELPER_DEFAULT(QString, caption, "caption", QString()) WINDOW_HELPER_DEFAULT(bool, keepAbove, "keepAbove", true) WINDOW_HELPER_DEFAULT(bool, isModal, "modal", false) WINDOW_HELPER_DEFAULT(QSize, basicUnit, "basicUnit", QSize(1, 1)) WINDOW_HELPER_DEFAULT(bool, isUserMove, "move", false) WINDOW_HELPER_DEFAULT(bool, isUserResize, "resize", false) WINDOW_HELPER_DEFAULT(QRect, iconGeometry, "iconGeometry", QRect()) WINDOW_HELPER_DEFAULT(bool, isSpecialWindow, "specialWindow", true) WINDOW_HELPER_DEFAULT(bool, acceptsFocus, "wantsInput", true) // We don't actually know... WINDOW_HELPER_DEFAULT(QIcon, icon, "icon", QIcon()) WINDOW_HELPER_DEFAULT(bool, isSkipSwitcher, "skipSwitcher", false) WINDOW_HELPER_DEFAULT(bool, isCurrentTab, "isCurrentTab", true) WINDOW_HELPER_DEFAULT(bool, decorationHasAlpha, "decorationHasAlpha", false) WINDOW_HELPER_DEFAULT(bool, isFullScreen, "fullScreen", false) WINDOW_HELPER_DEFAULT(bool, isUnresponsive, "unresponsive", false) #undef WINDOW_HELPER_DEFAULT #define WINDOW_HELPER_SETTER( prototype, propertyname, args, value ) \ void EffectWindow::prototype ( args ) \ {\ const QVariant variant = parent()->property( propertyname ); \ if (variant.isValid()) { \ parent()->setProperty( propertyname, value ); \ } \ } WINDOW_HELPER_SETTER(minimize, "minimized",,true) WINDOW_HELPER_SETTER(unminimize, "minimized",,false) #undef WINDOW_HELPER_SETTER void EffectWindow::setMinimized(bool min) { if (min) { minimize(); } else { unminimize(); } } void EffectWindow::closeWindow() const { QMetaObject::invokeMethod(parent(), "closeWindow"); } void EffectWindow::addRepaint(int x, int y, int w, int h) { QMetaObject::invokeMethod(parent(), "addRepaint", Q_ARG(int, x), Q_ARG(int, y), Q_ARG(int, w), Q_ARG(int, h)); } void EffectWindow::addRepaint(const QRect &r) { QMetaObject::invokeMethod(parent(), "addRepaint", Q_ARG(const QRect&, r)); } void EffectWindow::addRepaintFull() { QMetaObject::invokeMethod(parent(), "addRepaintFull"); } void EffectWindow::addLayerRepaint(int x, int y, int w, int h) { QMetaObject::invokeMethod(parent(), "addLayerRepaint", Q_ARG(int, x), Q_ARG(int, y), Q_ARG(int, w), Q_ARG(int, h)); } void EffectWindow::addLayerRepaint(const QRect &r) { QMetaObject::invokeMethod(parent(), "addLayerRepaint", Q_ARG(const QRect&, r)); } bool EffectWindow::isOnCurrentActivity() const { return isOnActivity(effects->currentActivity()); } bool EffectWindow::isOnCurrentDesktop() const { return isOnDesktop(effects->currentDesktop()); } bool EffectWindow::isOnDesktop(int d) const { return desktop() == d || isOnAllDesktops(); } bool EffectWindow::isOnAllDesktops() const { return desktop() == NET::OnAllDesktops; } bool EffectWindow::hasDecoration() const { return contentsRect() != QRect(0, 0, width(), height()); } bool EffectWindow::isVisible() const { return !isMinimized() && isOnCurrentDesktop() && isOnCurrentActivity(); } //**************************************** // EffectWindowGroup //**************************************** EffectWindowGroup::~EffectWindowGroup() { } /*************************************************************** WindowQuad ***************************************************************/ WindowQuad WindowQuad::makeSubQuad(double x1, double y1, double x2, double y2) const { assert(x1 < x2 && y1 < y2 && x1 >= left() && x2 <= right() && y1 >= top() && y2 <= bottom()); #ifndef NDEBUG if (isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif WindowQuad ret(*this); // vertices are clockwise starting from topleft ret.verts[ 0 ].px = x1; ret.verts[ 3 ].px = x1; ret.verts[ 1 ].px = x2; ret.verts[ 2 ].px = x2; ret.verts[ 0 ].py = y1; ret.verts[ 1 ].py = y1; ret.verts[ 2 ].py = y2; ret.verts[ 3 ].py = y2; // original x/y are supposed to be the same, no transforming is done here ret.verts[ 0 ].ox = x1; ret.verts[ 3 ].ox = x1; ret.verts[ 1 ].ox = x2; ret.verts[ 2 ].ox = x2; ret.verts[ 0 ].oy = y1; ret.verts[ 1 ].oy = y1; ret.verts[ 2 ].oy = y2; ret.verts[ 3 ].oy = y2; const double my_u0 = verts[0].tx; const double my_u1 = verts[2].tx; const double my_v0 = verts[0].ty; const double my_v1 = verts[2].ty; const double width = right() - left(); const double height = bottom() - top(); const double texWidth = my_u1 - my_u0; const double texHeight = my_v1 - my_v0; if (!uvAxisSwapped()) { const double u0 = (x1 - left()) / width * texWidth + my_u0; const double u1 = (x2 - left()) / width * texWidth + my_u0; const double v0 = (y1 - top()) / height * texHeight + my_v0; const double v1 = (y2 - top()) / height * texHeight + my_v0; ret.verts[0].tx = u0; ret.verts[3].tx = u0; ret.verts[1].tx = u1; ret.verts[2].tx = u1; ret.verts[0].ty = v0; ret.verts[1].ty = v0; ret.verts[2].ty = v1; ret.verts[3].ty = v1; } else { const double u0 = (y1 - top()) / height * texWidth + my_u0; const double u1 = (y2 - top()) / height * texWidth + my_u0; const double v0 = (x1 - left()) / width * texHeight + my_v0; const double v1 = (x2 - left()) / width * texHeight + my_v0; ret.verts[0].tx = u0; ret.verts[1].tx = u0; ret.verts[2].tx = u1; ret.verts[3].tx = u1; ret.verts[0].ty = v0; ret.verts[3].ty = v0; ret.verts[1].ty = v1; ret.verts[2].ty = v1; } ret.setUVAxisSwapped(uvAxisSwapped()); return ret; } bool WindowQuad::smoothNeeded() const { // smoothing is needed if the width or height of the quad does not match the original size double width = verts[ 1 ].ox - verts[ 0 ].ox; double height = verts[ 2 ].oy - verts[ 1 ].oy; return(verts[ 1 ].px - verts[ 0 ].px != width || verts[ 2 ].px - verts[ 3 ].px != width || verts[ 2 ].py - verts[ 1 ].py != height || verts[ 3 ].py - verts[ 0 ].py != height); } /*************************************************************** WindowQuadList ***************************************************************/ WindowQuadList WindowQuadList::splitAtX(double x) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif bool wholeleft = true; bool wholeright = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].x() < x) wholeright = false; if (quad[ i ].x() > x) wholeleft = false; } if (wholeleft || wholeright) { // is whole in one split part ret.append(quad); continue; } if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), x, quad.bottom())); ret.append(quad.makeSubQuad(x, quad.top(), quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::splitAtY(double y) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif bool wholetop = true; bool wholebottom = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].y() < y) wholebottom = false; if (quad[ i ].y() > y) wholetop = false; } if (wholetop || wholebottom) { // is whole in one split part ret.append(quad); continue; } if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), quad.right(), y)); ret.append(quad.makeSubQuad(quad.left(), y, quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::makeGrid(int maxQuadSize) const { if (empty()) return *this; // Find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad &quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } WindowQuadList ret; foreach (const WindowQuad &quad, *this) { const double quadLeft = quad.left(); const double quadRight = quad.right(); const double quadTop = quad.top(); const double quadBottom = quad.bottom(); // Compute the top-left corner of the first intersecting grid cell const double xBegin = left + qFloor((quadLeft - left) / maxQuadSize) * maxQuadSize; const double yBegin = top + qFloor((quadTop - top) / maxQuadSize) * maxQuadSize; // Loop over all intersecting cells and add sub-quads for (double y = yBegin; y < quadBottom; y += maxQuadSize) { const double y0 = qMax(y, quadTop); const double y1 = qMin(quadBottom, y + maxQuadSize); for (double x = xBegin; x < quadRight; x += maxQuadSize) { const double x0 = qMax(x, quadLeft); const double x1 = qMin(quadRight, x + maxQuadSize); ret.append(quad.makeSubQuad(x0, y0, x1, y1)); } } } return ret; } WindowQuadList WindowQuadList::makeRegularGrid(int xSubdivisions, int ySubdivisions) const { if (empty()) return *this; // Find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad &quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } double xIncrement = (right - left) / xSubdivisions; double yIncrement = (bottom - top) / ySubdivisions; WindowQuadList ret; foreach (const WindowQuad &quad, *this) { const double quadLeft = quad.left(); const double quadRight = quad.right(); const double quadTop = quad.top(); const double quadBottom = quad.bottom(); // Compute the top-left corner of the first intersecting grid cell const double xBegin = left + qFloor((quadLeft - left) / xIncrement) * xIncrement; const double yBegin = top + qFloor((quadTop - top) / yIncrement) * yIncrement; // Loop over all intersecting cells and add sub-quads for (double y = yBegin; y < quadBottom; y += yIncrement) { const double y0 = qMax(y, quadTop); const double y1 = qMin(quadBottom, y + yIncrement); for (double x = xBegin; x < quadRight; x += xIncrement) { const double x0 = qMax(x, quadLeft); const double x1 = qMin(quadRight, x + xIncrement); ret.append(quad.makeSubQuad(x0, y0, x1, y1)); } } } return ret; } #ifndef GL_TRIANGLES # define GL_TRIANGLES 0x0004 #endif #ifndef GL_QUADS # define GL_QUADS 0x0007 #endif void WindowQuadList::makeInterleavedArrays(unsigned int type, GLVertex2D *vertices, const QMatrix4x4 &textureMatrix) const { // Since we know that the texture matrix just scales and translates // we can use this information to optimize the transformation const QVector2D coeff(textureMatrix(0, 0), textureMatrix(1, 1)); const QVector2D offset(textureMatrix(0, 3), textureMatrix(1, 3)); GLVertex2D *vertex = vertices; assert(type == GL_QUADS || type == GL_TRIANGLES); switch (type) { case GL_QUADS: #ifdef HAVE_SSE2 if (!(intptr_t(vertex) & 0xf)) { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); KWIN_ALIGN(16) GLVertex2D v[4]; for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; v[j].position = QVector2D(wv.x(), wv.y()); v[j].texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; } const __m128i *srcP = (const __m128i *) &v; __m128i *dstP = (__m128i *) vertex; _mm_stream_si128(&dstP[0], _mm_load_si128(&srcP[0])); // Top-left _mm_stream_si128(&dstP[1], _mm_load_si128(&srcP[1])); // Top-right _mm_stream_si128(&dstP[2], _mm_load_si128(&srcP[2])); // Bottom-right _mm_stream_si128(&dstP[3], _mm_load_si128(&srcP[3])); // Bottom-left vertex += 4; } } else #endif // HAVE_SSE2 { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; GLVertex2D v; v.position = QVector2D(wv.x(), wv.y()); v.texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; *(vertex++) = v; } } } break; case GL_TRIANGLES: #ifdef HAVE_SSE2 if (!(intptr_t(vertex) & 0xf)) { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); KWIN_ALIGN(16) GLVertex2D v[4]; for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; v[j].position = QVector2D(wv.x(), wv.y()); v[j].texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; } const __m128i *srcP = (const __m128i *) &v; __m128i *dstP = (__m128i *) vertex; __m128i src[4]; src[0] = _mm_load_si128(&srcP[0]); // Top-left src[1] = _mm_load_si128(&srcP[1]); // Top-right src[2] = _mm_load_si128(&srcP[2]); // Bottom-right src[3] = _mm_load_si128(&srcP[3]); // Bottom-left // First triangle _mm_stream_si128(&dstP[0], src[1]); // Top-right _mm_stream_si128(&dstP[1], src[0]); // Top-left _mm_stream_si128(&dstP[2], src[3]); // Bottom-left // Second triangle _mm_stream_si128(&dstP[3], src[3]); // Bottom-left _mm_stream_si128(&dstP[4], src[2]); // Bottom-right _mm_stream_si128(&dstP[5], src[1]); // Top-right vertex += 6; } } else #endif // HAVE_SSE2 { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); GLVertex2D v[4]; // Four unique vertices / quad for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; v[j].position = QVector2D(wv.x(), wv.y()); v[j].texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; } // First triangle *(vertex++) = v[1]; // Top-right *(vertex++) = v[0]; // Top-left *(vertex++) = v[3]; // Bottom-left // Second triangle *(vertex++) = v[3]; // Bottom-left *(vertex++) = v[2]; // Bottom-right *(vertex++) = v[1]; // Top-right } } break; default: break; } } void WindowQuadList::makeArrays(float **vertices, float **texcoords, const QSizeF &size, bool yInverted) const { *vertices = new float[count() * 6 * 2]; *texcoords = new float[count() * 6 * 2]; float *vpos = *vertices; float *tpos = *texcoords; // Note: The positions in a WindowQuad are stored in clockwise order const int index[] = { 1, 0, 3, 3, 2, 1 }; for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); for (int j = 0; j < 6; j++) { const WindowVertex &wv = quad[index[j]]; *vpos++ = wv.x(); *vpos++ = wv.y(); *tpos++ = wv.u() / size.width(); *tpos++ = yInverted ? (wv.v() / size.height()) : (1.0 - wv.v() / size.height()); } } } WindowQuadList WindowQuadList::select(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() != type) { // something else than ones to select, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() == type) ret.append(q); } return ret; } } return *this; // nothing to filter out } WindowQuadList WindowQuadList::filterOut(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() == type) { // something to filter out, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() != type) ret.append(q); } return ret; } } return *this; // nothing to filter out } bool WindowQuadList::smoothNeeded() const { foreach (const WindowQuad & q, *this) if (q.smoothNeeded()) return true; return false; } bool WindowQuadList::isTransformed() const { foreach (const WindowQuad & q, *this) if (q.isTransformed()) return true; return false; } /*************************************************************** PaintClipper ***************************************************************/ QStack< QRegion >* PaintClipper::areas = nullptr; PaintClipper::PaintClipper(const QRegion& allowed_area) : area(allowed_area) { push(area); } PaintClipper::~PaintClipper() { pop(area); } void PaintClipper::push(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) // don't push these return; if (areas == nullptr) areas = new QStack< QRegion >; areas->push(allowed_area); } void PaintClipper::pop(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) return; Q_ASSERT(areas != nullptr); Q_ASSERT(areas->top() == allowed_area); areas->pop(); if (areas->isEmpty()) { delete areas; areas = nullptr; } } bool PaintClipper::clip() { return areas != nullptr; } QRegion PaintClipper::paintArea() { assert(areas != nullptr); // can be called only with clip() == true const QSize &s = effects->virtualScreenSize(); QRegion ret = QRegion(0, 0, s.width(), s.height()); foreach (const QRegion & r, *areas) ret &= r; return ret; } struct PaintClipper::Iterator::Data { Data() : index(0) {} int index; - QVector< QRect > rects; + QRegion region; }; PaintClipper::Iterator::Iterator() : data(new Data) { if (clip() && effects->isOpenGLCompositing()) { - data->rects = paintArea().rects(); + data->region = paintArea(); data->index = -1; next(); // move to the first one } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) { XFixesRegion region(paintArea()); xcb_xfixes_set_picture_clip_region(connection(), effects->xrenderBufferPicture(), region, 0, 0); } #endif } PaintClipper::Iterator::~Iterator() { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) xcb_xfixes_set_picture_clip_region(connection(), effects->xrenderBufferPicture(), XCB_XFIXES_REGION_NONE, 0, 0); #endif delete data; } bool PaintClipper::Iterator::isDone() { if (!clip()) return data->index == 1; // run once if (effects->isOpenGLCompositing()) - return data->index >= data->rects.count(); // run once per each area + return data->index >= data->region.rectCount(); // run once per each area #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) return data->index == 1; // run once #endif abort(); } void PaintClipper::Iterator::next() { data->index++; } QRect PaintClipper::Iterator::boundingRect() const { if (!clip()) return infiniteRegion(); if (effects->isOpenGLCompositing()) - return data->rects[ data->index ]; + return *(data->region.begin() + data->index); #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) - return paintArea().boundingRect(); + return data->region.boundingRect(); #endif abort(); return infiniteRegion(); } /*************************************************************** Motion1D ***************************************************************/ Motion1D::Motion1D(double initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion1D::Motion1D(const Motion1D &other) : Motion(other) { } Motion1D::~Motion1D() { } /*************************************************************** Motion2D ***************************************************************/ Motion2D::Motion2D(QPointF initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion2D::Motion2D(const Motion2D &other) : Motion(other) { } Motion2D::~Motion2D() { } /*************************************************************** WindowMotionManager ***************************************************************/ WindowMotionManager::WindowMotionManager(bool useGlobalAnimationModifier) : m_useGlobalAnimationModifier(useGlobalAnimationModifier) { // TODO: Allow developer to modify motion attributes } // TODO: What happens when the window moves by an external force? WindowMotionManager::~WindowMotionManager() { } void WindowMotionManager::manage(EffectWindow *w) { if (m_managedWindows.contains(w)) return; double strength = 0.08; double smoothness = 4.0; if (m_useGlobalAnimationModifier && effects->animationTimeFactor()) { // If the factor is == 0 then we just skip the calculation completely strength = 0.08 / effects->animationTimeFactor(); smoothness = effects->animationTimeFactor() * 4.0; } WindowMotion &motion = m_managedWindows[ w ]; motion.translation.setStrength(strength); motion.translation.setSmoothness(smoothness); motion.scale.setStrength(strength * 1.33); motion.scale.setSmoothness(smoothness / 2.0); motion.translation.setValue(w->pos()); motion.scale.setValue(QPointF(1.0, 1.0)); } void WindowMotionManager::unmanage(EffectWindow *w) { m_movingWindowsSet.remove(w); m_managedWindows.remove(w); } void WindowMotionManager::unmanageAll() { m_managedWindows.clear(); m_movingWindowsSet.clear(); } void WindowMotionManager::calculate(int time) { if (!effects->animationTimeFactor()) { // Just skip it completely if the user wants no animation m_movingWindowsSet.clear(); QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); motion->translation.finish(); motion->scale.finish(); } } QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); int stopped = 0; // TODO: What happens when distance() == 0 but we are still moving fast? // TODO: Motion needs to be calculated from the window's center Motion2D *trans = &motion->translation; if (trans->distance().isNull()) ++stopped; else { // Still moving trans->calculate(time); const short fx = trans->target().x() <= trans->startValue().x() ? -1 : 1; const short fy = trans->target().y() <= trans->startValue().y() ? -1 : 1; if (trans->distance().x()*fx/0.5 < 1.0 && trans->velocity().x()*fx/0.2 < 1.0 && trans->distance().y()*fy/0.5 < 1.0 && trans->velocity().y()*fy/0.2 < 1.0) { // Hide tiny oscillations motion->translation.finish(); ++stopped; } } Motion2D *scale = &motion->scale; if (scale->distance().isNull()) ++stopped; else { // Still scaling scale->calculate(time); const short fx = scale->target().x() < 1.0 ? -1 : 1; const short fy = scale->target().y() < 1.0 ? -1 : 1; if (scale->distance().x()*fx/0.001 < 1.0 && scale->velocity().x()*fx/0.05 < 1.0 && scale->distance().y()*fy/0.001 < 1.0 && scale->velocity().y()*fy/0.05 < 1.0) { // Hide tiny oscillations motion->scale.finish(); ++stopped; } } // We just finished this window's motion if (stopped == 2) m_movingWindowsSet.remove(it.key()); } } void WindowMotionManager::reset() { QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); EffectWindow *window = it.key(); motion->translation.setTarget(window->pos()); motion->translation.finish(); motion->scale.setTarget(QPointF(1.0, 1.0)); motion->scale.finish(); } } void WindowMotionManager::reset(EffectWindow *w) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; WindowMotion *motion = &it.value(); motion->translation.setTarget(w->pos()); motion->translation.finish(); motion->scale.setTarget(QPointF(1.0, 1.0)); motion->scale.finish(); } void WindowMotionManager::apply(EffectWindow *w, WindowPaintData &data) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) WindowMotion *motion = &it.value(); data += (motion->translation.value() - QPointF(w->x(), w->y())); data *= QVector2D(motion->scale.value()); } void WindowMotionManager::moveWindow(EffectWindow *w, QPoint target, double scale, double yScale) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) abort(); // Notify the effect author that they did something wrong WindowMotion *motion = &it.value(); if (yScale == 0.0) yScale = scale; QPointF scalePoint(scale, yScale); if (motion->translation.value() == target && motion->scale.value() == scalePoint) return; // Window already at that position motion->translation.setTarget(target); motion->scale.setTarget(scalePoint); m_movingWindowsSet << w; } QRectF WindowMotionManager::transformedGeometry(EffectWindow *w) const { QHash::const_iterator it = m_managedWindows.constFind(w); if (it == m_managedWindows.end()) return w->geometry(); const WindowMotion *motion = &it.value(); QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(motion->translation.value()); geometry.setWidth(geometry.width() * motion->scale.value().x()); geometry.setHeight(geometry.height() * motion->scale.value().y()); return geometry; } void WindowMotionManager::setTransformedGeometry(EffectWindow *w, const QRectF &geometry) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; WindowMotion *motion = &it.value(); motion->translation.setValue(geometry.topLeft()); motion->scale.setValue(QPointF(geometry.width() / qreal(w->width()), geometry.height() / qreal(w->height()))); } QRectF WindowMotionManager::targetGeometry(EffectWindow *w) const { QHash::const_iterator it = m_managedWindows.constFind(w); if (it == m_managedWindows.end()) return w->geometry(); const WindowMotion *motion = &it.value(); QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(motion->translation.target()); geometry.setWidth(geometry.width() * motion->scale.target().x()); geometry.setHeight(geometry.height() * motion->scale.target().y()); return geometry; } EffectWindow* WindowMotionManager::windowAtPoint(QPoint point, bool useStackingOrder) const { Q_UNUSED(useStackingOrder); // TODO: Stacking order uses EffectsHandler::stackingOrder() then filters by m_managedWindows QHash< EffectWindow*, WindowMotion >::ConstIterator it = m_managedWindows.constBegin(); while (it != m_managedWindows.constEnd()) { if (transformedGeometry(it.key()).contains(point)) return it.key(); ++it; } return nullptr; } /*************************************************************** EffectFramePrivate ***************************************************************/ class EffectFramePrivate { public: EffectFramePrivate(); ~EffectFramePrivate(); bool crossFading; qreal crossFadeProgress; QMatrix4x4 screenProjectionMatrix; }; EffectFramePrivate::EffectFramePrivate() : crossFading(false) , crossFadeProgress(1.0) { } EffectFramePrivate::~EffectFramePrivate() { } /*************************************************************** EffectFrame ***************************************************************/ EffectFrame::EffectFrame() : d(new EffectFramePrivate) { } EffectFrame::~EffectFrame() { delete d; } qreal EffectFrame::crossFadeProgress() const { return d->crossFadeProgress; } void EffectFrame::setCrossFadeProgress(qreal progress) { d->crossFadeProgress = progress; } bool EffectFrame::isCrossFade() const { return d->crossFading; } void EffectFrame::enableCrossFade(bool enable) { d->crossFading = enable; } QMatrix4x4 EffectFrame::screenProjectionMatrix() const { return d->screenProjectionMatrix; } void EffectFrame::setScreenProjectionMatrix(const QMatrix4x4 &spm) { d->screenProjectionMatrix = spm; } } // namespace diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h index cf9fe42cc..04e77aa1f 100644 --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -1,3496 +1,3497 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray Copyright (C) 2010, 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWINEFFECTS_H #define KWINEFFECTS_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KConfigGroup; class QFont; class QGraphicsScale; class QKeyEvent; class QMatrix4x4; +class QAction; /** * Logging category to be used inside the KWin effects. * Do not use in this library. **/ Q_DECLARE_LOGGING_CATEGORY(KWINEFFECTS) namespace KWayland { namespace Server { class SurfaceInterface; class Display; } } namespace KWin { class PaintDataPrivate; class WindowPaintDataPrivate; class EffectWindow; class EffectWindowGroup; class EffectFrame; class EffectFramePrivate; class Effect; class WindowQuad; class GLShader; class XRenderPicture; class WindowQuadList; class WindowPrePaintData; class WindowPaintData; class ScreenPrePaintData; class ScreenPaintData; typedef QPair< QString, Effect* > EffectPair; typedef QList< KWin::EffectWindow* > EffectWindowList; /** @defgroup kwineffects KWin effects library * KWin effects library contains necessary classes for creating new KWin * compositing effects. * * @section creating Creating new effects * This example will demonstrate the basics of creating an effect. We'll use * CoolEffect as the class name, cooleffect as internal name and * "Cool Effect" as user-visible name of the effect. * * This example doesn't demonstrate how to write the effect's code. For that, * see the documentation of the Effect class. * * @subsection creating-class CoolEffect class * First you need to create CoolEffect class which has to be a subclass of * @ref KWin::Effect. In that class you can reimplement various virtual * methods to control how and where the windows are drawn. * * @subsection creating-macro KWIN_EFFECT_FACTORY macro * This library provides a specialized KPluginFactory subclass and macros to * create a sub class. This subclass of KPluginFactory has to be used, otherwise * KWin won't load the plugin. Use the @ref KWIN_EFFECT_FACTORY macro to create the * plugin factory. * * @subsection creating-buildsystem Buildsystem * To build the effect, you can use the KWIN_ADD_EFFECT() cmake macro which * can be found in effects/CMakeLists.txt file in KWin's source. First * argument of the macro is the name of the library that will contain * your effect. Although not strictly required, it is usually a good idea to * use the same name as your effect's internal name there. Following arguments * to the macro are the files containing your effect's source. If our effect's * source is in cooleffect.cpp, we'd use following: * @code * KWIN_ADD_EFFECT(cooleffect cooleffect.cpp) * @endcode * * This macro takes care of compiling your effect. You'll also need to install * your effect's .desktop file, so the example CMakeLists.txt file would be * as follows: * @code * KWIN_ADD_EFFECT(cooleffect cooleffect.cpp) * install( FILES cooleffect.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) * @endcode * * @subsection creating-desktop Effect's .desktop file * You will also need to create .desktop file to set name, description, icon * and other properties of your effect. Important fields of the .desktop file * are: * @li Name User-visible name of your effect * @li Icon Name of the icon of the effect * @li Comment Short description of the effect * @li Type must be "Service" * @li X-KDE-ServiceTypes must be "KWin/Effect" * @li X-KDE-PluginInfo-Name effect's internal name as passed to the KWIN_EFFECT macro plus "kwin4_effect_" prefix * @li X-KDE-PluginInfo-Category effect's category. Should be one of Appearance, Accessibility, Window Management, Demos, Tests, Misc * @li X-KDE-PluginInfo-EnabledByDefault whether the effect should be enabled by default (use sparingly). Default is false * @li X-KDE-Library name of the library containing the effect. This is the first argument passed to the KWIN_ADD_EFFECT macro in cmake file plus "kwin4_effect_" prefix. * * Example cooleffect.desktop file follows: * @code [Desktop Entry] Name=Cool Effect Comment=The coolest effect you've ever seen Icon=preferences-system-windows-effect-cooleffect Type=Service X-KDE-ServiceTypes=KWin/Effect X-KDE-PluginInfo-Author=My Name X-KDE-PluginInfo-Email=my@email.here X-KDE-PluginInfo-Name=kwin4_effect_cooleffect X-KDE-PluginInfo-Category=Misc X-KDE-Library=kwin4_effect_cooleffect * @endcode * * * @section accessing Accessing windows and workspace * Effects can gain access to the properties of windows and workspace via * EffectWindow and EffectsHandler classes. * * There is one global EffectsHandler object which you can access using the * @ref effects pointer. * For each window, there is an EffectWindow object which can be used to read * window properties such as position and also to change them. * * For more information about this, see the documentation of the corresponding * classes. * * @{ **/ #define KWIN_EFFECT_API_MAKE_VERSION( major, minor ) (( major ) << 8 | ( minor )) #define KWIN_EFFECT_API_VERSION_MAJOR 0 #define KWIN_EFFECT_API_VERSION_MINOR 224 #define KWIN_EFFECT_API_VERSION KWIN_EFFECT_API_MAKE_VERSION( \ KWIN_EFFECT_API_VERSION_MAJOR, KWIN_EFFECT_API_VERSION_MINOR ) enum WindowQuadType { WindowQuadError, // for the stupid default ctor WindowQuadContents, WindowQuadDecoration, // Shadow Quad types WindowQuadShadow, // OpenGL only. The other shadow types are only used by Xrender WindowQuadShadowTop, WindowQuadShadowTopRight, WindowQuadShadowRight, WindowQuadShadowBottomRight, WindowQuadShadowBottom, WindowQuadShadowBottomLeft, WindowQuadShadowLeft, WindowQuadShadowTopLeft, EFFECT_QUAD_TYPE_START = 100 ///< @internal }; /** * EffectWindow::setData() and EffectWindow::data() global roles. * All values between 0 and 999 are reserved for global roles. */ enum DataRole { // Grab roles are used to force all other animations to ignore the window. // The value of the data is set to the Effect's `this` value. WindowAddedGrabRole = 1, WindowClosedGrabRole, WindowMinimizedGrabRole, WindowUnminimizedGrabRole, WindowForceBlurRole, ///< For fullscreen effects to enforce blurring of windows, WindowBlurBehindRole, ///< For single windows to blur behind WindowForceBackgroundContrastRole, ///< For fullscreen effects to enforce the background contrast, WindowBackgroundContrastRole, ///< For single windows to enable Background contrast LanczosCacheRole }; /** * Style types used by @ref EffectFrame. * @since 4.6 */ enum EffectFrameStyle { EffectFrameNone, ///< Displays no frame around the contents. EffectFrameUnstyled, ///< Displays a basic box around the contents. EffectFrameStyled ///< Displays a Plasma-styled frame around the contents. }; /** * Infinite region (i.e. a special region type saying that everything needs to be painted). */ KWINEFFECTS_EXPORT inline QRect infiniteRegion() { // INT_MIN / 2 because width/height is used (INT_MIN+INT_MAX==-1) return QRect(INT_MIN / 2, INT_MIN / 2, INT_MAX, INT_MAX); } /** * @short Base class for all KWin effects * * This is the base class for all effects. By reimplementing virtual methods * of this class, you can customize how the windows are painted. * * The virtual methods are used for painting and need to be implemented for * custom painting. * * In order to react to state changes (e.g. a window gets closed) the effect * should provide slots for the signals emitted by the EffectsHandler. * * @section Chaining * Most methods of this class are called in chain style. This means that when * effects A and B area active then first e.g. A::paintWindow() is called and * then from within that method B::paintWindow() is called (although * indirectly). To achieve this, you need to make sure to call corresponding * method in EffectsHandler class from each such method (using @ref effects * pointer): * @code * void MyEffect::postPaintScreen() * { * // Do your own processing here * ... * // Call corresponding EffectsHandler method * effects->postPaintScreen(); * } * @endcode * * @section Effectsptr Effects pointer * @ref effects pointer points to the global EffectsHandler object that you can * use to interact with the windows. * * @section painting Painting stages * Painting of windows is done in three stages: * @li First, the prepaint pass.
* Here you can specify how the windows will be painted, e.g. that they will * be translucent and transformed. * @li Second, the paint pass.
* Here the actual painting takes place. You can change attributes such as * opacity of windows as well as apply transformations to them. You can also * paint something onto the screen yourself. * @li Finally, the postpaint pass.
* Here you can mark windows, part of windows or even the entire screen for * repainting to create animations. * * For each stage there are *Screen() and *Window() methods. The window method * is called for every window which the screen method is usually called just * once. * * @section OpenGL * Effects can use OpenGL if EffectsHandler::isOpenGLCompositing() returns @c true. * The OpenGL context may not always be current when code inside the effect is * executed. The framework ensures that the OpenGL context is current when the Effect * gets created, destroyed or reconfigured and during the painting stages. All virtual * methods which have the OpenGL context current are documented. * * If OpenGL code is going to be executed outside the painting stages, e.g. in reaction * to a global shortcut, it is the task of the Effect to make the OpenGL context current: * @code * effects->makeOpenGLContextCurrent(); * @endcode * * There is in general no need to call the matching doneCurrent method. **/ class KWINEFFECTS_EXPORT Effect : public QObject { Q_OBJECT public: /** Flags controlling how painting is done. */ // TODO: is that ok here? enum { /** * Window (or at least part of it) will be painted opaque. **/ PAINT_WINDOW_OPAQUE = 1 << 0, /** * Window (or at least part of it) will be painted translucent. **/ PAINT_WINDOW_TRANSLUCENT = 1 << 1, /** * Window will be painted with transformed geometry. **/ PAINT_WINDOW_TRANSFORMED = 1 << 2, /** * Paint only a region of the screen (can be optimized, cannot * be used together with TRANSFORMED flags). **/ PAINT_SCREEN_REGION = 1 << 3, /** * The whole screen will be painted with transformed geometry. * Forces the entire screen to be painted. **/ PAINT_SCREEN_TRANSFORMED = 1 << 4, /** * At least one window will be painted with transformed geometry. * Forces the entire screen to be painted. **/ PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS = 1 << 5, /** * Clear whole background as the very first step, without optimizing it **/ PAINT_SCREEN_BACKGROUND_FIRST = 1 << 6, // PAINT_DECORATION_ONLY = 1 << 7 has been deprecated /** * Window will be painted with a lanczos filter. **/ PAINT_WINDOW_LANCZOS = 1 << 8 // PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_WITHOUT_FULL_REPAINTS = 1 << 9 has been removed }; enum Feature { Nothing = 0, Resize, GeometryTip, Outline, ScreenInversion, Blur, Contrast, HighlightWindows }; /** * Constructs new Effect object. * * In OpenGL based compositing, the frameworks ensures that the context is current * when the Effect is constructed. **/ Effect(); /** * Destructs the Effect object. * * In OpenGL based compositing, the frameworks ensures that the context is current * when the Effect is destroyed. **/ virtual ~Effect(); /** * Flags describing which parts of configuration have changed. */ enum ReconfigureFlag { ReconfigureAll = 1 << 0 /// Everything needs to be reconfigured. }; Q_DECLARE_FLAGS(ReconfigureFlags, ReconfigureFlag) /** * Called when configuration changes (either the effect's or KWin's global). * * In OpenGL based compositing, the frameworks ensures that the context is current * when the Effect is reconfigured. If this method is called from within the Effect it is * required to ensure that the context is current if the implementation does OpenGL calls. */ virtual void reconfigure(ReconfigureFlags flags); /** * Called when another effect requests the proxy for this effect. */ virtual void* proxy(); /** * Called before starting to paint the screen. * In this method you can: * @li set whether the windows or the entire screen will be transformed * @li change the region of the screen that will be painted * @li do various housekeeping tasks such as initing your effect's variables for the upcoming paint pass or updating animation's progress * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void prePaintScreen(ScreenPrePaintData& data, int time); /** * In this method you can: * @li paint something on top of the windows (by painting after calling * effects->paintScreen()) * @li paint multiple desktops and/or multiple copies of the same desktop * by calling effects->paintScreen() multiple times * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); /** * Called after all the painting has been finished. * In this method you can: * @li schedule next repaint in case of animations * You shouldn't paint anything here. * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void postPaintScreen(); /** * Called for every window before the actual paint pass * In this method you can: * @li enable or disable painting of the window (e.g. enable paiting of minimized window) * @li set window to be painted with translucency * @li set window to be transformed * @li request the window to be divided into multiple parts * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); /** * This is the main method for painting windows. * In this method you can: * @li do various transformations * @li change opacity of the window * @li change brightness and/or saturation, if it's supported * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); /** * Called for every window after all painting has been finished. * In this method you can: * @li schedule next repaint for individual window(s) in case of animations * You shouldn't paint anything here. * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void postPaintWindow(EffectWindow* w); /** * This method is called directly before painting an @ref EffectFrame. * You can implement this method if you need to bind a shader or perform * other operations before the frame is rendered. * @param frame The EffectFrame which will be rendered * @param region Region to restrict painting to * @param opacity Opacity of text/icon * @param frameOpacity Opacity of background * @since 4.6 * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void paintEffectFrame(EffectFrame* frame, QRegion region, double opacity, double frameOpacity); /** * Called on Transparent resizes. * return true if your effect substitutes questioned feature */ virtual bool provides(Feature); /** * Performs the @p feature with the @p arguments. * * This allows to have specific protocols between KWin core and an Effect. * * The method is supposed to return @c true if it performed the features, * @c false otherwise. * * The default implementation returns @c false. * @since 5.8 **/ virtual bool perform(Feature feature, const QVariantList &arguments); /** * Can be called to draw multiple copies (e.g. thumbnails) of a window. * You can change window's opacity/brightness/etc here, but you can't * do any transformations. * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); /** * Define new window quads so that they can be transformed by other effects. * It's up to the effect to keep track of them. **/ virtual void buildQuads(EffectWindow* w, WindowQuadList& quadList); virtual void windowInputMouseEvent(QEvent* e); virtual void grabbedKeyboardEvent(QKeyEvent* e); /** * Overwrite this method to indicate whether your effect will be doing something in * the next frame to be rendered. If the method returns @c false the effect will be * excluded from the chained methods in the next rendered frame. * * This method is called always directly before the paint loop begins. So it is totally * fine to e.g. react on a window event, issue a repaint to trigger an animation and * change a flag to indicate that this method returns @c true. * * As the method is called each frame, you should not perform complex calculations. * Best use just a boolean flag. * * The default implementation of this method returns @c true. * @since 4.8 **/ virtual bool isActive() const; /** * Reimplement this method to provide online debugging. * This could be as trivial as printing specific detail informations about the effect state * but could also be used to move the effect in and out of a special debug modes, clear bogus * data, etc. * Notice that the functions is const by intent! Whenever you alter the state of the object * due to random user input, you should do so with greatest care, hence const_cast<> your * object - signalling "let me alone, i know what i'm doing" * @param parameter A freeform string user input for your effect to interpret. * @since 4.11 */ virtual QString debug(const QString ¶meter) const; /** * Reimplement this method to indicate where in the Effect chain the Effect should be placed. * * A low number indicates early chain position, thus before other Effects got called, a high * number indicates a late position. The returned number should be in the interval [0, 100]. * The default value is 0. * * In KWin4 this information was provided in the Effect's desktop file as property * X-KDE-Ordering. In the case of Scripted Effects this property is still used. * * @since 5.0 **/ virtual int requestedEffectChainPosition() const; /** * A touch point was pressed. * * If the effect wants to exclusively use the touch event it should return @c true. * If @c false is returned the touch event is passed to further effects. * * In general an Effect should only return @c true if it is the exclusive effect getting * input events. E.g. has grabbed mouse events. * * Default implementation returns @c false. * * @param id The unique id of the touch point * @param pos The position of the touch point in global coordinates * @param time Timestamp * * @see touchMotion * @see touchUp * @since 5.8 **/ virtual bool touchDown(quint32 id, const QPointF &pos, quint32 time); /** * A touch point moved. * * If the effect wants to exclusively use the touch event it should return @c true. * If @c false is returned the touch event is passed to further effects. * * In general an Effect should only return @c true if it is the exclusive effect getting * input events. E.g. has grabbed mouse events. * * Default implementation returns @c false. * * @param id The unique id of the touch point * @param pos The position of the touch point in global coordinates * @param time Timestamp * * @see touchDown * @see touchUp * @since 5.8 **/ virtual bool touchMotion(quint32 id, const QPointF &pos, quint32 time); /** * A touch point was released. * * If the effect wants to exclusively use the touch event it should return @c true. * If @c false is returned the touch event is passed to further effects. * * In general an Effect should only return @c true if it is the exclusive effect getting * input events. E.g. has grabbed mouse events. * * Default implementation returns @c false. * * @param id The unique id of the touch point * @param time Timestamp * * @see touchDown * @see touchMotion * @since 5.8 **/ virtual bool touchUp(quint32 id, quint32 time); static QPoint cursorPos(); /** * Read animation time from the configuration and possibly adjust using animationTimeFactor(). * The configuration value in the effect should also have special value 'default' (set using * QSpinBox::setSpecialValueText()) with the value 0. This special value is adjusted * using the global animation speed, otherwise the exact time configured is returned. * @param cfg configuration group to read value from * @param key configuration key to read value from * @param defaultTime default animation time in milliseconds */ // return type is intentionally double so that one can divide using it without losing data static double animationTime(const KConfigGroup& cfg, const QString& key, int defaultTime); /** * @overload Use this variant if the animation time is hardcoded and not configurable * in the effect itself. */ static double animationTime(int defaultTime); /** * @overload Use this variant if animation time is provided through a KConfigXT generated class * having a property called "duration". **/ template int animationTime(int defaultDuration); /** * Linearly interpolates between @p x and @p y. * * Returns @p x when @p a = 0; returns @p y when @p a = 1. **/ static double interpolate(double x, double y, double a) { return x * (1 - a) + y * a; } /** Helper to set WindowPaintData and QRegion to necessary transformations so that * a following drawWindow() would put the window at the requested geometry (useful for thumbnails) **/ static void setPositionTransformations(WindowPaintData& data, QRect& region, EffectWindow* w, const QRect& r, Qt::AspectRatioMode aspect); public Q_SLOTS: virtual bool borderActivated(ElectricBorder border); protected: xcb_connection_t *xcbConnection() const; xcb_window_t x11RootWindow() const; /** * An implementing class can call this with it's kconfig compiled singleton class. * This method will perform the instance on the class. * @since 5.9 **/ template void initConfig(); }; /** * Prefer the KWIN_EFFECT_FACTORY macros. */ class KWINEFFECTS_EXPORT EffectPluginFactory : public KPluginFactory { Q_OBJECT public: EffectPluginFactory(); virtual ~EffectPluginFactory(); /** * Returns whether the Effect is supported. * * An Effect can implement this method to determine at runtime whether the Effect is supported. * * If the current compositing backend is not supported it should return @c false. * * This method is optional, by default @c true is returned. */ virtual bool isSupported() const; /** * Returns whether the Effect should get enabled by default. * * This function provides a way for an effect to override the default at runtime, * e.g. based on the capabilities of the hardware. * * This method is optional; the effect doesn't have to provide it. * * Note that this function is only called if the supported() function returns true, * and if X-KDE-PluginInfo-EnabledByDefault is set to true in the .desktop file. * * This method is optional, by default @c true is returned. */ virtual bool enabledByDefault() const; /** * This method returns the created Effect. */ virtual KWin::Effect *createEffect() const = 0; }; /** * Defines an EffectPluginFactory sub class with customized isSupported and enabledByDefault methods. * * If the Effect to be created does not need the isSupported or enabledByDefault methods prefer * the simplified KWIN_EFFECT_FACTORY, KWIN_EFFECT_FACTORY_SUPPORTED or KWIN_EFFECT_FACTORY_ENABLED * macros which create an EffectPluginFactory with a useable default value. * * The macro also adds a useable K_EXPORT_PLUGIN_VERSION to the definition. KWin will not load * any Effect with a non-matching plugin version. This API is not providing binary compatibility * and thus the effect plugin must be compiled against the same kwineffects library version as * KWin. * * @param factoryName The name to be used for the EffectPluginFactory * @param className The class name of the Effect sub class which is to be created by the factory * @param jsonFile Name of the json file to be compiled into the plugin as metadata * @param supported Source code to go into the isSupported() method, must return a boolean * @param enabled Source code to go into the enabledByDefault() method, must return a boolean **/ #define KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( factoryName, className, jsonFile, supported, enabled ) \ class factoryName : public KWin::EffectPluginFactory \ { \ Q_OBJECT \ Q_PLUGIN_METADATA(IID KPluginFactory_iid FILE jsonFile) \ Q_INTERFACES(KPluginFactory) \ public: \ explicit factoryName() {} \ ~factoryName() {} \ bool isSupported() const override { \ supported \ } \ bool enabledByDefault() const override { \ enabled \ } \ KWin::Effect *createEffect() const override { \ return new className(); \ } \ }; \ K_EXPORT_PLUGIN_VERSION(quint32(KWIN_EFFECT_API_VERSION)) #define KWIN_EFFECT_FACTORY_ENABLED( factoryName, className, jsonFile, enabled ) \ KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( factoryName, className, jsonFile, return true;, enabled ) #define KWIN_EFFECT_FACTORY_SUPPORTED( factoryName, classname, jsonFile, supported ) \ KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( factoryName, className, jsonFile, supported, return true; ) #define KWIN_EFFECT_FACTORY( factoryName, classname, jsonFile ) \ KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( factoryName, className, jsonFile, return true;, return true; ) /** * @short Manager class that handles all the effects. * * This class creates Effect objects and calls it's appropriate methods. * * Effect objects can call methods of this class to interact with the * workspace, e.g. to activate or move a specific window, change current * desktop or create a special input window to receive mouse and keyboard * events. **/ class KWINEFFECTS_EXPORT EffectsHandler : public QObject { Q_OBJECT Q_PROPERTY(int currentDesktop READ currentDesktop WRITE setCurrentDesktop NOTIFY desktopChanged) Q_PROPERTY(QString currentActivity READ currentActivity NOTIFY currentActivityChanged) Q_PROPERTY(KWin::EffectWindow *activeWindow READ activeWindow WRITE activateWindow NOTIFY windowActivated) Q_PROPERTY(QSize desktopGridSize READ desktopGridSize) Q_PROPERTY(int desktopGridWidth READ desktopGridWidth) Q_PROPERTY(int desktopGridHeight READ desktopGridHeight) Q_PROPERTY(int workspaceWidth READ workspaceWidth) Q_PROPERTY(int workspaceHeight READ workspaceHeight) /** * The number of desktops currently used. Minimum number of desktops is 1, maximum 20. **/ Q_PROPERTY(int desktops READ numberOfDesktops WRITE setNumberOfDesktops NOTIFY numberDesktopsChanged) Q_PROPERTY(bool optionRollOverDesktops READ optionRollOverDesktops) Q_PROPERTY(int activeScreen READ activeScreen) Q_PROPERTY(int numScreens READ numScreens NOTIFY numberScreensChanged) /** * Factor by which animation speed in the effect should be modified (multiplied). * If configurable in the effect itself, the option should have also 'default' * animation speed. The actual value should be determined using animationTime(). * Note: The factor can be also 0, so make sure your code can cope with 0ms time * if used manually. */ Q_PROPERTY(qreal animationTimeFactor READ animationTimeFactor) Q_PROPERTY(QList< KWin::EffectWindow* > stackingOrder READ stackingOrder) /** * Whether window decorations use the alpha channel. **/ Q_PROPERTY(bool decorationsHaveAlpha READ decorationsHaveAlpha) /** * Whether the window decorations support blurring behind the decoration. **/ Q_PROPERTY(bool decorationSupportsBlurBehind READ decorationSupportsBlurBehind) Q_PROPERTY(CompositingType compositingType READ compositingType CONSTANT) Q_PROPERTY(QPoint cursorPos READ cursorPos) Q_PROPERTY(QSize virtualScreenSize READ virtualScreenSize NOTIFY virtualScreenSizeChanged) Q_PROPERTY(QRect virtualScreenGeometry READ virtualScreenGeometry NOTIFY virtualScreenGeometryChanged) friend class Effect; public: explicit EffectsHandler(CompositingType type); virtual ~EffectsHandler(); // for use by effects virtual void prePaintScreen(ScreenPrePaintData& data, int time) = 0; virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data) = 0; virtual void postPaintScreen() = 0; virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) = 0; virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) = 0; virtual void postPaintWindow(EffectWindow* w) = 0; virtual void paintEffectFrame(EffectFrame* frame, QRegion region, double opacity, double frameOpacity) = 0; virtual void drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) = 0; virtual void buildQuads(EffectWindow* w, WindowQuadList& quadList) = 0; virtual QVariant kwinOption(KWinOption kwopt) = 0; /** * Sets the cursor while the mouse is intercepted. * @see startMouseInterception * @since 4.11 **/ virtual void defineCursor(Qt::CursorShape shape) = 0; virtual QPoint cursorPos() const = 0; virtual bool grabKeyboard(Effect* effect) = 0; virtual void ungrabKeyboard() = 0; /** * Ensures that all mouse events are sent to the @p effect. * No window will get the mouse events. Only fullscreen effects providing a custom user interface should * be using this method. The input events are delivered to Effect::windowInputMouseEvent. * * NOTE: this method does not perform an X11 mouse grab. On X11 a fullscreen input window is raised above * all other windows, but no grab is performed. * * @param shape Sets the cursor to be used while the mouse is intercepted * @see stopMouseInterception * @see Effect::windowInputMouseEvent * @since 4.11 **/ virtual void startMouseInterception(Effect *effect, Qt::CursorShape shape) = 0; /** * Releases the hold mouse interception for @p effect * @see startMouseInterception * @since 4.11 **/ virtual void stopMouseInterception(Effect *effect) = 0; /** * @brief Registers a global shortcut with the provided @p action. * * @param shortcut The global shortcut which should trigger the action * @param action The action which gets triggered when the shortcut matches */ virtual void registerGlobalShortcut(const QKeySequence &shortcut, QAction *action) = 0; /** * @brief Registers a global pointer shortcut with the provided @p action. * * @param modifiers The keyboard modifiers which need to be holded * @param pointerButtons The pointer buttons which need to be pressed * @param action The action which gets triggered when the shortcut matches **/ virtual void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) = 0; /** * @brief Registers a global axis shortcut with the provided @p action. * * @param modifiers The keyboard modifiers which need to be holded * @param axis The direction in which the axis needs to be moved * @param action The action which gets triggered when the shortcut matches **/ virtual void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) = 0; /** * @brief Registers a global touchpad swipe gesture shortcut with the provided @p action. * * @param direction The direction for the swipe * @param action The action which gets triggered when the gesture triggers * @since 5.10 **/ virtual void registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action) = 0; /** * Retrieve the proxy class for an effect if it has one. Will return NULL if * the effect isn't loaded or doesn't have a proxy class. */ virtual void* getProxy(QString name) = 0; // Mouse polling virtual void startMousePolling() = 0; virtual void stopMousePolling() = 0; virtual void reserveElectricBorder(ElectricBorder border, Effect *effect) = 0; virtual void unreserveElectricBorder(ElectricBorder border, Effect *effect) = 0; /** * Registers the given @p action for the given @p border to be activated through * a touch swipe gesture. * * If the @p border gets triggered through a touch swipe gesture the @link{QAction::triggered} * signal gets invoked. * * To unregister the touch screen action either delete the @p action or * invoke @link{unregisterTouchBorder}. * * @see unregisterTouchBorder * @since 5.10 **/ virtual void registerTouchBorder(ElectricBorder border, QAction *action) = 0; /** * Unregisters the given @p action for the given touch @p border. * * @see registerTouchBorder * @since 5.10 **/ virtual void unregisterTouchBorder(ElectricBorder border, QAction *action) = 0; // functions that allow controlling windows/desktop virtual void activateWindow(KWin::EffectWindow* c) = 0; virtual KWin::EffectWindow* activeWindow() const = 0 ; Q_SCRIPTABLE virtual void moveWindow(KWin::EffectWindow* w, const QPoint& pos, bool snap = false, double snapAdjust = 1.0) = 0; Q_SCRIPTABLE virtual void windowToDesktop(KWin::EffectWindow* w, int desktop) = 0; Q_SCRIPTABLE virtual void windowToScreen(KWin::EffectWindow* w, int screen) = 0; virtual void setShowingDesktop(bool showing) = 0; // Activities /** * @returns The ID of the current activity. */ virtual QString currentActivity() const = 0; // Desktops /** * @returns The ID of the current desktop. */ virtual int currentDesktop() const = 0; /** * @returns Total number of desktops currently in existence. */ virtual int numberOfDesktops() const = 0; /** * Set the current desktop to @a desktop. */ virtual void setCurrentDesktop(int desktop) = 0; /** * Sets the total number of desktops to @a desktops. */ virtual void setNumberOfDesktops(int desktops) = 0; /** * @returns The size of desktop layout in grid units. */ virtual QSize desktopGridSize() const = 0; /** * @returns The width of desktop layout in grid units. */ virtual int desktopGridWidth() const = 0; /** * @returns The height of desktop layout in grid units. */ virtual int desktopGridHeight() const = 0; /** * @returns The width of desktop layout in pixels. */ virtual int workspaceWidth() const = 0; /** * @returns The height of desktop layout in pixels. */ virtual int workspaceHeight() const = 0; /** * @returns The ID of the desktop at the point @a coords or 0 if no desktop exists at that * point. @a coords is to be in grid units. */ virtual int desktopAtCoords(QPoint coords) const = 0; /** * @returns The coords of desktop @a id in grid units. */ virtual QPoint desktopGridCoords(int id) const = 0; /** * @returns The coords of the top-left corner of desktop @a id in pixels. */ virtual QPoint desktopCoords(int id) const = 0; /** * @returns The ID of the desktop above desktop @a id. Wraps around to the bottom of * the layout if @a wrap is set. If @a id is not set use the current one. */ Q_SCRIPTABLE virtual int desktopAbove(int desktop = 0, bool wrap = true) const = 0; /** * @returns The ID of the desktop to the right of desktop @a id. Wraps around to the * left of the layout if @a wrap is set. If @a id is not set use the current one. */ Q_SCRIPTABLE virtual int desktopToRight(int desktop = 0, bool wrap = true) const = 0; /** * @returns The ID of the desktop below desktop @a id. Wraps around to the top of the * layout if @a wrap is set. If @a id is not set use the current one. */ Q_SCRIPTABLE virtual int desktopBelow(int desktop = 0, bool wrap = true) const = 0; /** * @returns The ID of the desktop to the left of desktop @a id. Wraps around to the * right of the layout if @a wrap is set. If @a id is not set use the current one. */ Q_SCRIPTABLE virtual int desktopToLeft(int desktop = 0, bool wrap = true) const = 0; Q_SCRIPTABLE virtual QString desktopName(int desktop) const = 0; virtual bool optionRollOverDesktops() const = 0; virtual int activeScreen() const = 0; // Xinerama virtual int numScreens() const = 0; // Xinerama Q_SCRIPTABLE virtual int screenNumber(const QPoint& pos) const = 0; // Xinerama virtual QRect clientArea(clientAreaOption, int screen, int desktop) const = 0; virtual QRect clientArea(clientAreaOption, const EffectWindow* c) const = 0; virtual QRect clientArea(clientAreaOption, const QPoint& p, int desktop) const = 0; /** * The bounding size of all screens combined. Overlapping areas * are not counted multiple times. * * @see virtualScreenGeometry() * @see virtualScreenSizeChanged() * @since 5.0 **/ virtual QSize virtualScreenSize() const = 0; /** * The bounding geometry of all outputs combined. Always starts at (0,0) and has * virtualScreenSize as it's size. * * @see virtualScreenSize() * @see virtualScreenGeometryChanged() * @since 5.0 **/ virtual QRect virtualScreenGeometry() const = 0; /** * Factor by which animation speed in the effect should be modified (multiplied). * If configurable in the effect itself, the option should have also 'default' * animation speed. The actual value should be determined using animationTime(). * Note: The factor can be also 0, so make sure your code can cope with 0ms time * if used manually. */ virtual double animationTimeFactor() const = 0; virtual WindowQuadType newWindowQuadType() = 0; Q_SCRIPTABLE virtual KWin::EffectWindow* findWindow(WId id) const = 0; Q_SCRIPTABLE virtual KWin::EffectWindow* findWindow(KWayland::Server::SurfaceInterface *surf) const = 0; virtual EffectWindowList stackingOrder() const = 0; // window will be temporarily painted as if being at the top of the stack Q_SCRIPTABLE virtual void setElevatedWindow(KWin::EffectWindow* w, bool set) = 0; virtual void setTabBoxWindow(EffectWindow*) = 0; virtual void setTabBoxDesktop(int) = 0; virtual EffectWindowList currentTabBoxWindowList() const = 0; virtual void refTabBox() = 0; virtual void unrefTabBox() = 0; virtual void closeTabBox() = 0; virtual QList< int > currentTabBoxDesktopList() const = 0; virtual int currentTabBoxDesktop() const = 0; virtual EffectWindow* currentTabBoxWindow() const = 0; virtual void setActiveFullScreenEffect(Effect* e) = 0; virtual Effect* activeFullScreenEffect() const = 0; /** * Schedules the entire workspace to be repainted next time. * If you call it during painting (including prepaint) then it does not * affect the current painting. **/ Q_SCRIPTABLE virtual void addRepaintFull() = 0; Q_SCRIPTABLE virtual void addRepaint(const QRect& r) = 0; Q_SCRIPTABLE virtual void addRepaint(const QRegion& r) = 0; Q_SCRIPTABLE virtual void addRepaint(int x, int y, int w, int h) = 0; CompositingType compositingType() const; /** * @brief Whether the Compositor is OpenGL based (either GL 1 or 2). * * @return bool @c true in case of OpenGL based Compositor, @c false otherwise **/ bool isOpenGLCompositing() const; virtual unsigned long xrenderBufferPicture() = 0; /** * @brief Provides access to the QPainter which is rendering to the back buffer. * * Only relevant for CompositingType QPainterCompositing. For all other compositing types * @c null is returned. * * @return QPainter* The Scene's QPainter or @c null. */ virtual QPainter *scenePainter() = 0; virtual void reconfigure() = 0; virtual QByteArray readRootProperty(long atom, long type, int format) const = 0; /** * @brief Announces support for the feature with the given name. If no other Effect * has announced support for this feature yet, an X11 property will be installed on * the root window. * * The Effect will be notified for events through the signal propertyNotify(). * * To remove the support again use @link removeSupportProperty. When an Effect is * destroyed it is automatically taken care of removing the support. It is not * required to call @link removeSupportProperty in the Effect's cleanup handling. * * @param propertyName The name of the property to announce support for * @param effect The effect which announces support * @return xcb_atom_t The created X11 atom * @see removeSupportProperty * @since 4.11 **/ virtual xcb_atom_t announceSupportProperty(const QByteArray &propertyName, Effect *effect) = 0; /** * @brief Removes support for the feature with the given name. If there is no other Effect left * which has announced support for the given property, the property will be removed from the * root window. * * In case the Effect had not registered support, calling this function does not change anything. * * @param propertyName The name of the property to remove support for * @param effect The effect which had registered the property. * @see announceSupportProperty * @since 4.11 **/ virtual void removeSupportProperty(const QByteArray &propertyName, Effect *effect) = 0; /** * Returns @a true if the active window decoration has shadow API hooks. */ virtual bool hasDecorationShadows() const = 0; /** * Returns @a true if the window decorations use the alpha channel, and @a false otherwise. * @since 4.5 */ virtual bool decorationsHaveAlpha() const = 0; /** * Returns @a true if the window decorations support blurring behind the decoration, and @a false otherwise * @since 4.6 */ virtual bool decorationSupportsBlurBehind() const = 0; /** * Creates a new frame object. If the frame does not have a static size * then it will be located at @a position with @a alignment. A * non-static frame will automatically adjust its size to fit the contents. * @returns A new @ref EffectFrame. It is the responsibility of the caller to delete the * EffectFrame. * @since 4.6 */ virtual EffectFrame* effectFrame(EffectFrameStyle style, bool staticSize = true, const QPoint& position = QPoint(-1, -1), Qt::Alignment alignment = Qt::AlignCenter) const = 0; /** * Allows an effect to trigger a reload of itself. * This can be used by an effect which needs to be reloaded when screen geometry changes. * It is possible that the effect cannot be loaded again as it's supported method does no longer * hold. * @param effect The effect to reload * @since 4.8 **/ virtual void reloadEffect(Effect *effect) = 0; /** * Whether the screen is currently considered as locked. * Note for technical reasons this is not always possible to detect. The screen will only * be considered as locked if the screen locking process implements the * org.freedesktop.ScreenSaver interface. * * @returns @c true if the screen is currently locked, @c false otherwise * @see screenLockingChanged * @since 4.11 **/ virtual bool isScreenLocked() const = 0; /** * @brief Makes the OpenGL compositing context current. * * If the compositing backend is not using OpenGL, this method returns @c false. * * @return bool @c true if the context became current, @c false otherwise. */ virtual bool makeOpenGLContextCurrent() = 0; /** * @brief Makes a null OpenGL context current resulting in no context * being current. * * If the compositing backend is not OpenGL based, this method is a noop. * * There is normally no reason for an Effect to call this method. */ virtual void doneOpenGLContextCurrent() = 0; virtual xcb_connection_t *xcbConnection() const = 0; virtual xcb_window_t x11RootWindow() const = 0; /** * Interface to the Wayland display: this is relevant only * on Wayland, on X11 it will be nullptr * @since 5.5 */ virtual KWayland::Server::Display *waylandDisplay() const = 0; /** * Whether animations are supported by the Scene. * If this method returns @c false Effects are supposed to not * animate transitions. * * @returns Whether the Scene can drive animations * @since 5.8 **/ virtual bool animationsSupported() const = 0; /** * The current cursor image of the Platform. * @see cursorPos * @since 5.9 **/ virtual PlatformCursorImage cursorImage() const = 0; /** * The cursor image should be hidden. * @see showCursor * @since 5.9 **/ virtual void hideCursor() = 0; /** * The cursor image should be shown again after having been hidden.. * @see hideCursor * @since 5.9 **/ virtual void showCursor() = 0; /** * Starts an interactive window selection process. * * Once the user selected a window the @p callback is invoked with the selected EffectWindow as * argument. In case the user cancels the interactive window selection or selecting a window is currently * not possible (e.g. screen locked) the @p callback is invoked with a @c nullptr argument. * * During the interactive window selection the cursor is turned into a crosshair cursor. * * @param callback The function to invoke once the interactive window selection ends * @since 5.9 **/ virtual void startInteractiveWindowSelection(std::function callback) = 0; /** * Starts an interactive position selection process. * * Once the user selected a position on the screen the @p callback is invoked with * the selected point as argument. In case the user cancels the interactive position selection * or selecting a position is currently not possible (e.g. screen locked) the @p callback * is invoked with a point at @c -1 as x and y argument. * * During the interactive window selection the cursor is turned into a crosshair cursor. * * @param callback The function to invoke once the interactive position selection ends * @since 5.9 **/ virtual void startInteractivePositionSelection(std::function callback) = 0; /** * Shows an on-screen-message. To hide it again use @link{hideOnScreenMessage}. * * @param message The message to show * @param iconName The optional themed icon name * @see hideOnScreenMessage * @since 5.9 **/ virtual void showOnScreenMessage(const QString &message, const QString &iconName = QString()) = 0; /** * Flags for how to hide a shown on-screen-message * @see hideOnScreenMessage * @since 5.9 **/ enum class OnScreenMessageHideFlag { /** * The on-screen-message should skip the close window animation. * @see EffectWindow::skipsCloseAnimation **/ SkipsCloseAnimation = 1 }; Q_DECLARE_FLAGS(OnScreenMessageHideFlags, OnScreenMessageHideFlag) /** * Hides a previously shown on-screen-message again. * @param flags The flags for how to hide the message * @see showOnScreenMessage * @since 5.9 **/ virtual void hideOnScreenMessage(OnScreenMessageHideFlags flags = OnScreenMessageHideFlags()) = 0; /* * @returns The configuration used by the EffectsHandler. * @since 5.10 **/ virtual KSharedConfigPtr config() const = 0; /** * @returns The global input configuration (kcminputrc) * @since 5.10 **/ virtual KSharedConfigPtr inputConfig() const = 0; Q_SIGNALS: /** * Signal emitted when the current desktop changed. * @param oldDesktop The previously current desktop * @param newDesktop The new current desktop * @param with The window which is taken over to the new desktop, can be NULL * @since 4.9 */ void desktopChanged(int oldDesktop, int newDesktop, KWin::EffectWindow *with); /** * @since 4.7 * @deprecated */ void desktopChanged(int oldDesktop, int newDesktop); /** * Signal emitted when a window moved to another desktop * NOTICE that this does NOT imply that the desktop has changed * The @param window which is moved to the new desktop * @param oldDesktop The previous desktop of the window * @param newDesktop The new desktop of the window * @since 4.11.4 */ void desktopPresenceChanged(KWin::EffectWindow *window, int oldDesktop, int newDesktop); /** * Signal emitted when the number of currently existing desktops is changed. * @param old The previous number of desktops in used. * @see EffectsHandler::numberOfDesktops. * @since 4.7 */ void numberDesktopsChanged(uint old); /** * Signal emitted when the number of screens changed. * @since 5.0 **/ void numberScreensChanged(); /** * Signal emitted when the desktop showing ("dashboard") state changed * The desktop is risen to the keepAbove layer, you may want to elevate * windows or such. * @since 5.3 **/ void showingDesktopChanged(bool); /** * Signal emitted when a new window has been added to the Workspace. * @param w The added window * @since 4.7 **/ void windowAdded(KWin::EffectWindow *w); /** * Signal emitted when a window is being removed from the Workspace. * An effect which wants to animate the window closing should connect * to this signal and reference the window by using * @link EffectWindow::refWindow * @param w The window which is being closed * @since 4.7 **/ void windowClosed(KWin::EffectWindow *w); /** * Signal emitted when a window get's activated. * @param w The new active window, or @c NULL if there is no active window. * @since 4.7 **/ void windowActivated(KWin::EffectWindow *w); /** * Signal emitted when a window is deleted. * This means that a closed window is not referenced any more. * An effect bookkeeping the closed windows should connect to this * signal to clean up the internal references. * @param w The window which is going to be deleted. * @see EffectWindow::refWindow * @see EffectWindow::unrefWindow * @see windowClosed * @since 4.7 **/ void windowDeleted(KWin::EffectWindow *w); /** * Signal emitted when a user begins a window move or resize operation. * To figure out whether the user resizes or moves the window use * @link EffectWindow::isUserMove or @link EffectWindow::isUserResize. * Whenever the geometry is updated the signal @link windowStepUserMovedResized * is emitted with the current geometry. * The move/resize operation ends with the signal @link windowFinishUserMovedResized. * Only one window can be moved/resized by the user at the same time! * @param w The window which is being moved/resized * @see windowStepUserMovedResized * @see windowFinishUserMovedResized * @see EffectWindow::isUserMove * @see EffectWindow::isUserResize * @since 4.7 **/ void windowStartUserMovedResized(KWin::EffectWindow *w); /** * Signal emitted during a move/resize operation when the user changed the geometry. * Please note: KWin supports two operation modes. In one mode all changes are applied * instantly. This means the window's geometry matches the passed in @p geometry. In the * other mode the geometry is changed after the user ended the move/resize mode. * The @p geometry differs from the window's geometry. Also the window's pixmap still has * the same size as before. Depending what the effect wants to do it would be recommended * to scale/translate the window. * @param w The window which is being moved/resized * @param geometry The geometry of the window in the current move/resize step. * @see windowStartUserMovedResized * @see windowFinishUserMovedResized * @see EffectWindow::isUserMove * @see EffectWindow::isUserResize * @since 4.7 **/ void windowStepUserMovedResized(KWin::EffectWindow *w, const QRect &geometry); /** * Signal emitted when the user finishes move/resize of window @p w. * @param w The window which has been moved/resized * @see windowStartUserMovedResized * @see windowFinishUserMovedResized * @since 4.7 **/ void windowFinishUserMovedResized(KWin::EffectWindow *w); /** * Signal emitted when the maximized state of the window @p w changed. * A window can be in one of four states: * @li restored: both @p horizontal and @p vertical are @c false * @li horizontally maximized: @p horizontal is @c true and @p vertical is @c false * @li vertically maximized: @p horizontal is @c false and @p vertical is @c true * @li completely maximized: both @p horizontal and @p vertical are @C true * @param w The window whose maximized state changed * @param horizontal If @c true maximized horizontally * @param vertical If @c true maximized vertically * @since 4.7 **/ void windowMaximizedStateChanged(KWin::EffectWindow *w, bool horizontal, bool vertical); /** * Signal emitted when the geometry or shape of a window changed. * This is caused if the window changes geometry without user interaction. * E.g. the decoration is changed. This is in opposite to windowUserMovedResized * which is caused by direct user interaction. * @param w The window whose geometry changed * @param old The previous geometry * @see windowUserMovedResized * @since 4.7 **/ void windowGeometryShapeChanged(KWin::EffectWindow *w, const QRect &old); /** * Signal emitted when the padding of a window changed. (eg. shadow size) * @param w The window whose geometry changed * @param old The previous expandedGeometry() * @since 4.9 **/ void windowPaddingChanged(KWin::EffectWindow *w, const QRect &old); /** * Signal emitted when the windows opacity is changed. * @param w The window whose opacity level is changed. * @param oldOpacity The previous opacity level * @param newOpacity The new opacity level * @since 4.7 **/ void windowOpacityChanged(KWin::EffectWindow *w, qreal oldOpacity, qreal newOpacity); /** * Signal emitted when a window got minimized. * @param w The window which was minimized * @since 4.7 **/ void windowMinimized(KWin::EffectWindow *w); /** * Signal emitted when a window got unminimized. * @param w The window which was unminimized * @since 4.7 **/ void windowUnminimized(KWin::EffectWindow *w); /** * Signal emitted when a window either becomes modal (ie. blocking for its main client) or looses that state. * @param w The window which was unminimized * @since 4.11 **/ void windowModalityChanged(KWin::EffectWindow *w); /** * Signal emitted when a window either became unresponsive (eg. app froze or crashed) * or respoonsive * @param w The window that became (un)responsive * @param unresponsive Whether the window is responsive or unresponsive * @since 5.10 */ void windowUnresponsiveChanged(KWin::EffectWindow *w, bool unresponsive); /** * Signal emitted when an area of a window is scheduled for repainting. * Use this signal in an effect if another area needs to be synced as well. * @param w The window which is scheduled for repainting * @param r Always empty. * @since 4.7 **/ void windowDamaged(KWin::EffectWindow *w, const QRect &r); /** * Signal emitted when a tabbox is added. * An effect who wants to replace the tabbox with itself should use @link refTabBox. * @param mode The TabBoxMode. * @see refTabBox * @see tabBoxClosed * @see tabBoxUpdated * @see tabBoxKeyEvent * @since 4.7 **/ void tabBoxAdded(int mode); /** * Signal emitted when the TabBox was closed by KWin core. * An effect which referenced the TabBox should use @link unrefTabBox to unref again. * @see unrefTabBox * @see tabBoxAdded * @since 4.7 **/ void tabBoxClosed(); /** * Signal emitted when the selected TabBox window changed or the TabBox List changed. * An effect should only response to this signal if it referenced the TabBox with @link refTabBox. * @see refTabBox * @see currentTabBoxWindowList * @see currentTabBoxDesktopList * @see currentTabBoxWindow * @see currentTabBoxDesktop * @since 4.7 **/ void tabBoxUpdated(); /** * Signal emitted when a key event, which is not handled by TabBox directly is, happens while * TabBox is active. An effect might use the key event to e.g. change the selected window. * An effect should only response to this signal if it referenced the TabBox with @link refTabBox. * @param event The key event not handled by TabBox directly * @see refTabBox * @since 4.7 **/ void tabBoxKeyEvent(QKeyEvent* event); void currentTabAboutToChange(KWin::EffectWindow* from, KWin::EffectWindow* to); void tabAdded(KWin::EffectWindow* from, KWin::EffectWindow* to); // from merged with to void tabRemoved(KWin::EffectWindow* c, KWin::EffectWindow* group); // c removed from group /** * Signal emitted when mouse changed. * If an effect needs to get updated mouse positions, it needs to first call @link startMousePolling. * For a fullscreen effect it is better to use an input window and react on @link windowInputMouseEvent. * @param pos The new mouse position * @param oldpos The previously mouse position * @param buttons The pressed mouse buttons * @param oldbuttons The previously pressed mouse buttons * @param modifiers Pressed keyboard modifiers * @param oldmodifiers Previously pressed keyboard modifiers. * @see startMousePolling * @since 4.7 **/ void mouseChanged(const QPoint& pos, const QPoint& oldpos, Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers); /** * Signal emitted when the cursor shape changed. * You'll likely want to query the current cursor as reaction: xcb_xfixes_get_cursor_image_unchecked * Connection to this signal is tracked, so if you don't need it anymore, disconnect from it to stop cursor event filtering */ void cursorShapeChanged(); /** * Receives events registered for using @link registerPropertyType. * Use readProperty() to get the property data. * Note that the property may be already set on the window, so doing the same * processing from windowAdded() (e.g. simply calling propertyNotify() from it) * is usually needed. * @param w The window whose property changed, is @c null if it is a root window property * @param atom The property * @since 4.7 */ void propertyNotify(KWin::EffectWindow* w, long atom); /** * Signal emitted after the screen geometry changed (e.g. add of a monitor). * Effects using displayWidth()/displayHeight() to cache information should * react on this signal and update the caches. * @param size The new screen size * @since 4.8 **/ void screenGeometryChanged(const QSize &size); /** * This signal is emitted when the global * activity is changed * @param id id of the new current activity * @since 4.9 **/ void currentActivityChanged(const QString &id); /** * This signal is emitted when a new activity is added * @param id id of the new activity * @since 4.9 */ void activityAdded(const QString &id); /** * This signal is emitted when the activity * is removed * @param id id of the removed activity * @since 4.9 */ void activityRemoved(const QString &id); /** * This signal is emitted when the screen got locked or unlocked. * @param locked @c true if the screen is now locked, @c false if it is now unlocked * @since 4.11 **/ void screenLockingChanged(bool locked); /** * This signels is emitted when ever the stacking order is change, ie. a window is risen * or lowered * @since 4.10 */ void stackingOrderChanged(); /** * This signal is emitted when the user starts to approach the @p border with the mouse. * The @p factor describes how far away the mouse is in a relative mean. The values are in * [0.0, 1.0] with 0.0 being emitted when first entered and on leaving. The value 1.0 means that * the @p border is reached with the mouse. So the values are well suited for animations. * The signal is always emitted when the mouse cursor position changes. * @param border The screen edge which is being approached * @param factor Value in range [0.0,1.0] to describe how close the mouse is to the border * @param geometry The geometry of the edge which is being approached * @since 4.11 **/ void screenEdgeApproaching(ElectricBorder border, qreal factor, const QRect &geometry); /** * Emitted whenever the virtualScreenSize changes. * @see virtualScreenSize() * @since 5.0 **/ void virtualScreenSizeChanged(); /** * Emitted whenever the virtualScreenGeometry changes. * @see virtualScreenGeometry() * @since 5.0 **/ void virtualScreenGeometryChanged(); /** * The window @p w gets shown again. The window was previously * initially shown with @link{windowAdded} and hidden with @link{windowHidden}. * * @see windowHidden * @see windowAdded * @since 5.8 **/ void windowShown(KWin::EffectWindow *w); /** * The window @p w got hidden but not yet closed. * This can happen when a window is still being used and is supposed to be shown again * with @link{windowShown}. On X11 an example is autohiding panels. On Wayland every * window first goes through the window hidden state and might get shown again, or might * get closed the normal way. * * @see windowShown * @see windowClosed * @since 5.8 **/ void windowHidden(KWin::EffectWindow *w); /** * This signal gets emitted when the data on EffectWindow @p w for @p role changed. * * An Effect can connect to this signal to read the new value and react on it. * E.g. an Effect which does not operate on windows grabbed by another Effect wants * to cancel the already scheduled animation if another Effect adds a grab. * * @param w The EffectWindow for which the data changed * @param role The data role which changed * @see EffectWindow::setData * @see EffectWindow::data * @since 5.8.4 **/ void windowDataChanged(KWin::EffectWindow *w, int role); /** * The xcb connection changed, either a new xcbConnection got created or the existing one * got destroyed. * Effects can use this to refetch the properties they want to set. * * When the xcbConnection changes also the @link{x11RootWindow} becomes invalid. * @see xcbConnection * @see x11RootWindow * @since 5.11 **/ void xcbConnectionChanged(); protected: QVector< EffectPair > loaded_effects; //QHash< QString, EffectFactory* > effect_factories; CompositingType compositing_type; }; /** * @short Representation of a window used by/for Effect classes. * * The purpose is to hide internal data and also to serve as a single * representation for the case when Client/Unmanaged becomes Deleted. **/ class KWINEFFECTS_EXPORT EffectWindow : public QObject { Q_OBJECT Q_PROPERTY(bool alpha READ hasAlpha CONSTANT) Q_PROPERTY(QRect geometry READ geometry) Q_PROPERTY(QRect expandedGeometry READ expandedGeometry) Q_PROPERTY(int height READ height) Q_PROPERTY(qreal opacity READ opacity) Q_PROPERTY(QPoint pos READ pos) Q_PROPERTY(int screen READ screen) Q_PROPERTY(QSize size READ size) Q_PROPERTY(int width READ width) Q_PROPERTY(int x READ x) Q_PROPERTY(int y READ y) Q_PROPERTY(int desktop READ desktop) Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops) Q_PROPERTY(bool onCurrentDesktop READ isOnCurrentDesktop) Q_PROPERTY(QRect rect READ rect) Q_PROPERTY(QString windowClass READ windowClass) Q_PROPERTY(QString windowRole READ windowRole) /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool desktopWindow READ isDesktop) /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dock READ isDock) /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool toolbar READ isToolbar) /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool menu READ isMenu) /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool normalWindow READ isNormalWindow) /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dialog READ isDialog) /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool splash READ isSplash) /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool utility READ isUtility) /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dropdownMenu READ isDropdownMenu) /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool popupMenu READ isPopupMenu) /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool tooltip READ isTooltip) /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool notification READ isNotification) /** * Returns whether the window is an on screen display window * using the non-standard _KDE_NET_WM_WINDOW_TYPE_ON_SCREEN_DISPLAY */ Q_PROPERTY(bool onScreenDisplay READ isOnScreenDisplay) /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool comboBox READ isComboBox) /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dndIcon READ isDNDIcon) /** * Returns the NETWM window type * See http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(int windowType READ windowType) /** * Whether this EffectWindow is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). **/ Q_PROPERTY(bool managed READ isManaged) /** * Whether this EffectWindow represents an already deleted window and only kept for the compositor for animations. **/ Q_PROPERTY(bool deleted READ isDeleted) /** * Whether the window has an own shape **/ Q_PROPERTY(bool shaped READ hasOwnShape) /** * The Window's shape **/ Q_PROPERTY(QRegion shape READ shape) /** * The Caption of the window. Read from WM_NAME property together with a suffix for hostname and shortcut. **/ Q_PROPERTY(QString caption READ caption) /** * Whether the window is set to be kept above other windows. **/ Q_PROPERTY(bool keepAbove READ keepAbove) /** * Whether the window is minimized. **/ Q_PROPERTY(bool minimized READ isMinimized WRITE setMinimized) /** * Whether the window represents a modal window. **/ Q_PROPERTY(bool modal READ isModal) /** * Whether the window is moveable. Even if it is not moveable, it might be possible to move * it to another screen. * @see moveableAcrossScreens **/ Q_PROPERTY(bool moveable READ isMovable) /** * Whether the window can be moved to another screen. * @see moveable **/ Q_PROPERTY(bool moveableAcrossScreens READ isMovableAcrossScreens) /** * By how much the window wishes to grow/shrink at least. Usually QSize(1,1). * MAY BE DISOBEYED BY THE WM! It's only for information, do NOT rely on it at all. */ Q_PROPERTY(QSize basicUnit READ basicUnit) /** * Whether the window is currently being moved by the user. **/ Q_PROPERTY(bool move READ isUserMove) /** * Whether the window is currently being resized by the user. **/ Q_PROPERTY(bool resize READ isUserResize) /** * The optional geometry representing the minimized Client in e.g a taskbar. * See _NET_WM_ICON_GEOMETRY at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(QRect iconGeometry READ iconGeometry) /** * Returns whether the window is any of special windows types (desktop, dock, splash, ...), * i.e. window types that usually don't have a window frame and the user does not use window * management (moving, raising,...) on them. **/ Q_PROPERTY(bool specialWindow READ isSpecialWindow) Q_PROPERTY(QIcon icon READ icon) /** * Whether the window should be excluded from window switching effects. **/ Q_PROPERTY(bool skipSwitcher READ isSkipSwitcher) /** * Geometry of the actual window contents inside the whole (including decorations) window. */ Q_PROPERTY(QRect contentsRect READ contentsRect) /** * Geometry of the transparent rect in the decoration. * May be different from contentsRect if the decoration is extended into the client area. */ Q_PROPERTY(QRect decorationInnerRect READ decorationInnerRect) Q_PROPERTY(bool hasDecoration READ hasDecoration) Q_PROPERTY(QStringList activities READ activities) Q_PROPERTY(bool onCurrentActivity READ isOnCurrentActivity) Q_PROPERTY(bool onAllActivities READ isOnAllActivities) /** * Whether the decoration currently uses an alpha channel. * @since 4.10 **/ Q_PROPERTY(bool decorationHasAlpha READ decorationHasAlpha) /** * Whether the window is currently visible to the user, that is: *
    *
  • Not minimized
  • *
  • On current desktop
  • *
  • On current activity
  • *
* @since 4.11 **/ Q_PROPERTY(bool visible READ isVisible) /** * Whether the window does not want to be animated on window close. * In case this property is @c true it is not useful to start an animation on window close. * The window will not be visible, but the animation hooks are executed. * @since 5.0 **/ Q_PROPERTY(bool skipsCloseAnimation READ skipsCloseAnimation) /** * Interface to the corresponding wayland surface. * relevant only in Wayland, on X11 it will be nullptr */ Q_PROPERTY(KWayland::Server::SurfaceInterface *surface READ surface) /** * Whether the window is fullscreen. * @since 5.6 **/ Q_PROPERTY(bool fullScreen READ isFullScreen) /** * Whether this client is unresponsive. * * When an application failed to react on a ping request in time, it is * considered unresponsive. This usually indicates that the application froze or crashed. * * @since 5.10 */ Q_PROPERTY(bool unresponsive READ isUnresponsive) public: /** Flags explaining why painting should be disabled */ enum { /** Window will not be painted */ PAINT_DISABLED = 1 << 0, /** Window will not be painted because it is deleted */ PAINT_DISABLED_BY_DELETE = 1 << 1, /** Window will not be painted because of which desktop it's on */ PAINT_DISABLED_BY_DESKTOP = 1 << 2, /** Window will not be painted because it is minimized */ PAINT_DISABLED_BY_MINIMIZE = 1 << 3, /** Window will not be painted because it is not the active window in a client group */ PAINT_DISABLED_BY_TAB_GROUP = 1 << 4, /** Window will not be painted because it's not on the current activity */ PAINT_DISABLED_BY_ACTIVITY = 1 << 5 }; explicit EffectWindow(QObject *parent = nullptr); virtual ~EffectWindow(); virtual void enablePainting(int reason) = 0; virtual void disablePainting(int reason) = 0; virtual bool isPaintingEnabled() = 0; Q_SCRIPTABLE void addRepaint(const QRect& r); Q_SCRIPTABLE void addRepaint(int x, int y, int w, int h); Q_SCRIPTABLE void addRepaintFull(); Q_SCRIPTABLE void addLayerRepaint(const QRect& r); Q_SCRIPTABLE void addLayerRepaint(int x, int y, int w, int h); virtual void refWindow() = 0; virtual void unrefWindow() = 0; bool isDeleted() const; bool isMinimized() const; double opacity() const; bool hasAlpha() const; bool isOnCurrentActivity() const; Q_SCRIPTABLE bool isOnActivity(QString id) const; bool isOnAllActivities() const; QStringList activities() const; bool isOnDesktop(int d) const; bool isOnCurrentDesktop() const; bool isOnAllDesktops() const; int desktop() const; // prefer isOnXXX() int x() const; int y() const; int width() const; int height() const; /** * By how much the window wishes to grow/shrink at least. Usually QSize(1,1). * MAY BE DISOBEYED BY THE WM! It's only for information, do NOT rely on it at all. */ QSize basicUnit() const; QRect geometry() const; /** * Geometry of the window including decoration and potentially shadows. * May be different from geometry() if the window has a shadow. * @since 4.9 */ QRect expandedGeometry() const; virtual QRegion shape() const = 0; int screen() const; /** @internal Do not use */ bool hasOwnShape() const; // only for shadow effect, for now QPoint pos() const; QSize size() const; QRect rect() const; bool isMovable() const; bool isMovableAcrossScreens() const; bool isUserMove() const; bool isUserResize() const; QRect iconGeometry() const; /** * Geometry of the actual window contents inside the whole (including decorations) window. */ QRect contentsRect() const; /** * Geometry of the transparent rect in the decoration. * May be different from contentsRect() if the decoration is extended into the client area. * @since 4.5 */ virtual QRect decorationInnerRect() const = 0; bool hasDecoration() const; bool decorationHasAlpha() const; virtual QByteArray readProperty(long atom, long type, int format) const = 0; virtual void deleteProperty(long atom) const = 0; QString caption() const; QIcon icon() const; QString windowClass() const; QString windowRole() const; virtual const EffectWindowGroup* group() const = 0; /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDesktop() const; /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDock() const; /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isToolbar() const; /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isMenu() const; /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isNormalWindow() const; // normal as in 'NET::Normal or NET::Unknown non-transient' /** * Returns whether the window is any of special windows types (desktop, dock, splash, ...), * i.e. window types that usually don't have a window frame and the user does not use window * management (moving, raising,...) on them. */ bool isSpecialWindow() const; /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDialog() const; /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isSplash() const; /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isUtility() const; /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDropdownMenu() const; /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isPopupMenu() const; // a context popup, not dropdown, not torn-off /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isTooltip() const; /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isNotification() const; /** * Returns whether the window is an on screen display window * using the non-standard _KDE_NET_WM_WINDOW_TYPE_ON_SCREEN_DISPLAY */ bool isOnScreenDisplay() const; /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isComboBox() const; /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDNDIcon() const; /** * Returns the NETWM window type * See http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ NET::WindowType windowType() const; /** * Returns whether the window is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). */ bool isManaged() const; // whether it's managed or override-redirect /** * Returns whether or not the window can accept keyboard focus. */ bool acceptsFocus() const; /** * Returns whether or not the window is kept above all other windows. */ bool keepAbove() const; bool isModal() const; Q_SCRIPTABLE virtual KWin::EffectWindow* findModal() = 0; Q_SCRIPTABLE virtual QList mainWindows() const = 0; /** * Returns whether the window should be excluded from window switching effects. * @since 4.5 */ bool isSkipSwitcher() const; /** * Returns the unmodified window quad list. Can also be used to force rebuilding. */ virtual WindowQuadList buildQuads(bool force = false) const = 0; void setMinimized(bool minimize); void minimize(); void unminimize(); Q_SCRIPTABLE void closeWindow() const; bool isCurrentTab() const; /** * @since 4.11 **/ bool isVisible() const; /** * @since 5.0 **/ bool skipsCloseAnimation() const; /** * @since 5.5 */ KWayland::Server::SurfaceInterface *surface() const; /** * @since 5.6 **/ bool isFullScreen() const; /** * @since 5.10 */ bool isUnresponsive() const; /** * Can be used to by effects to store arbitrary data in the EffectWindow. * * Invoking this method will emit the signal EffectsHandler::windowDataChanged. * @see EffectsHandler::windowDataChanged */ Q_SCRIPTABLE virtual void setData(int role, const QVariant &data) = 0; Q_SCRIPTABLE virtual QVariant data(int role) const = 0; /** * @brief References the previous window pixmap to prevent discarding. * * This method allows to reference the previous window pixmap in case that a window changed * its size, which requires a new window pixmap. By referencing the previous (and then outdated) * window pixmap an effect can for example cross fade the current window pixmap with the previous * one. This allows for smoother transitions for window geometry changes. * * If an effect calls this method on a window it also needs to call @link unreferencePreviousWindowPixmap * once it does no longer need the previous window pixmap. * * Note: the window pixmap is not kept forever even when referenced. If the geometry changes again, so that * a new window pixmap is created, the previous window pixmap will be exchanged with the current one. This * means it's still possible to have rendering glitches. An effect is supposed to track for itself the changes * to the window's geometry and decide how the transition should continue in such a situation. * * @see unreferencePreviousWindowPixmap * @since 4.11 */ virtual void referencePreviousWindowPixmap() = 0; /** * @brief Unreferences the previous window pixmap. Only relevant after @link referencePreviousWindowPixmap had * been called. * * @see referencePreviousWindowPixmap * @since 4.11 */ virtual void unreferencePreviousWindowPixmap() = 0; }; class KWINEFFECTS_EXPORT EffectWindowGroup { public: virtual ~EffectWindowGroup(); virtual EffectWindowList members() const = 0; }; struct GLVertex2D { QVector2D position; QVector2D texcoord; }; struct GLVertex3D { QVector3D position; QVector2D texcoord; }; /** * @short Vertex class * * A vertex is one position in a window. WindowQuad consists of four WindowVertex objects * and represents one part of a window. **/ class KWINEFFECTS_EXPORT WindowVertex { public: WindowVertex(); WindowVertex(const QPointF &position, const QPointF &textureCoordinate); WindowVertex(double x, double y, double tx, double ty); double x() const { return px; } double y() const { return py; } double u() const { return tx; } double v() const { return ty; } double originalX() const { return ox; } double originalY() const { return oy; } double textureX() const { return tx; } double textureY() const { return ty; } void move(double x, double y); void setX(double x); void setY(double y); private: friend class WindowQuad; friend class WindowQuadList; double px, py; // position double ox, oy; // origional position double tx, ty; // texture coords }; /** * @short Class representing one area of a window. * * WindowQuads consists of four WindowVertex objects and represents one part of a window. */ // NOTE: This class expects the (original) vertices to be in the clockwise order starting from topleft. class KWINEFFECTS_EXPORT WindowQuad { public: explicit WindowQuad(WindowQuadType type, int id = -1); WindowQuad makeSubQuad(double x1, double y1, double x2, double y2) const; WindowVertex& operator[](int index); const WindowVertex& operator[](int index) const; WindowQuadType type() const; void setUVAxisSwapped(bool value) { uvSwapped = value; } bool uvAxisSwapped() const { return uvSwapped; } int id() const; bool decoration() const; bool effect() const; double left() const; double right() const; double top() const; double bottom() const; double originalLeft() const; double originalRight() const; double originalTop() const; double originalBottom() const; bool smoothNeeded() const; bool isTransformed() const; private: friend class WindowQuadList; WindowVertex verts[ 4 ]; WindowQuadType quadType; // 0 - contents, 1 - decoration bool uvSwapped; int quadID; }; class KWINEFFECTS_EXPORT WindowQuadList : public QList< WindowQuad > { public: WindowQuadList splitAtX(double x) const; WindowQuadList splitAtY(double y) const; WindowQuadList makeGrid(int maxquadsize) const; WindowQuadList makeRegularGrid(int xSubdivisions, int ySubdivisions) const; WindowQuadList select(WindowQuadType type) const; WindowQuadList filterOut(WindowQuadType type) const; bool smoothNeeded() const; void makeInterleavedArrays(unsigned int type, GLVertex2D *vertices, const QMatrix4x4 &matrix) const; void makeArrays(float** vertices, float** texcoords, const QSizeF &size, bool yInverted) const; bool isTransformed() const; }; class KWINEFFECTS_EXPORT WindowPrePaintData { public: int mask; /** * Region that will be painted, in screen coordinates. **/ QRegion paint; /** * The clip region will be subtracted from paint region of following windows. * I.e. window will definitely cover it's clip region **/ QRegion clip; WindowQuadList quads; /** * Simple helper that sets data to say the window will be painted as non-opaque. * Takes also care of changing the regions. */ void setTranslucent(); /** * Helper to mark that this window will be transformed **/ void setTransformed(); }; class KWINEFFECTS_EXPORT PaintData { public: virtual ~PaintData(); /** * @returns scale factor in X direction. * @since 4.10 **/ qreal xScale() const; /** * @returns scale factor in Y direction. * @since 4.10 **/ qreal yScale() const; /** * @returns scale factor in Z direction. * @since 4.10 **/ qreal zScale() const; /** * Sets the scale factor in X direction to @p scale * @param scale The scale factor in X direction * @since 4.10 **/ void setXScale(qreal scale); /** * Sets the scale factor in Y direction to @p scale * @param scale The scale factor in Y direction * @since 4.10 **/ void setYScale(qreal scale); /** * Sets the scale factor in Z direction to @p scale * @param scale The scale factor in Z direction * @since 4.10 **/ void setZScale(qreal scale); /** * Sets the scale factor in X and Y direction. * @param scale The scale factor for X and Y direction * @since 4.10 **/ void setScale(const QVector2D &scale); /** * Sets the scale factor in X, Y and Z direction * @param scale The scale factor for X, Y and Z direction * @since 4.10 **/ void setScale(const QVector3D &scale); const QGraphicsScale &scale() const; const QVector3D &translation() const; /** * @returns the translation in X direction. * @since 4.10 **/ qreal xTranslation() const; /** * @returns the translation in Y direction. * @since 4.10 **/ qreal yTranslation() const; /** * @returns the translation in Z direction. * @since 4.10 **/ qreal zTranslation() const; /** * Sets the translation in X direction to @p translate. * @since 4.10 **/ void setXTranslation(qreal translate); /** * Sets the translation in Y direction to @p translate. * @since 4.10 **/ void setYTranslation(qreal translate); /** * Sets the translation in Z direction to @p translate. * @since 4.10 **/ void setZTranslation(qreal translate); /** * Performs a translation by adding the values component wise. * @param x Translation in X direction * @param y Translation in Y direction * @param z Translation in Z direction * @since 4.10 **/ void translate(qreal x, qreal y = 0.0, qreal z = 0.0); /** * Performs a translation by adding the values component wise. * Overloaded method for convenience. * @param translate The translation * @since 4.10 **/ void translate(const QVector3D &translate); /** * Sets the rotation angle. * @param angle The new rotation angle. * @since 4.10 * @see rotationAngle() **/ void setRotationAngle(qreal angle); /** * Returns the rotation angle. * Initially 0.0. * @returns The current rotation angle. * @since 4.10 * @see setRotationAngle **/ qreal rotationAngle() const; /** * Sets the rotation origin. * @param origin The new rotation origin. * @since 4.10 * @see rotationOrigin() **/ void setRotationOrigin(const QVector3D &origin); /** * Returns the rotation origin. That is the point in space which is fixed during the rotation. * Initially this is 0/0/0. * @returns The rotation's origin * @since 4.10 * @see setRotationOrigin() **/ QVector3D rotationOrigin() const; /** * Sets the rotation axis. * Set a component to 1.0 to rotate around this axis and to 0.0 to disable rotation around the * axis. * @param axis A vector holding information on which axis to rotate * @since 4.10 * @see rotationAxis() **/ void setRotationAxis(const QVector3D &axis); /** * Sets the rotation axis. * Overloaded method for convenience. * @param axis The axis around which should be rotated. * @since 4.10 * @see rotationAxis() **/ void setRotationAxis(Qt::Axis axis); /** * The current rotation axis. * By default the rotation is (0/0/1) which means a rotation around the z axis. * @returns The current rotation axis. * @since 4.10 * @see setRotationAxis **/ QVector3D rotationAxis() const; protected: PaintData(); PaintData(const PaintData &other); private: PaintDataPrivate * const d; }; class KWINEFFECTS_EXPORT WindowPaintData : public PaintData { public: explicit WindowPaintData(EffectWindow* w); explicit WindowPaintData(EffectWindow* w, const QMatrix4x4 &screenProjectionMatrix); WindowPaintData(const WindowPaintData &other); virtual ~WindowPaintData(); /** * Scales the window by @p scale factor. * Multiplies all three components by the given factor. * @since 4.10 **/ WindowPaintData& operator*=(qreal scale); /** * Scales the window by @p scale factor. * Performs a component wise multiplication on x and y components. * @since 4.10 **/ WindowPaintData& operator*=(const QVector2D &scale); /** * Scales the window by @p scale factor. * Performs a component wise multiplication. * @since 4.10 **/ WindowPaintData& operator*=(const QVector3D &scale); /** * Translates the window by the given @p translation and returns a reference to the ScreenPaintData. * @since 4.10 **/ WindowPaintData& operator+=(const QPointF &translation); /** * Translates the window by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ WindowPaintData& operator+=(const QPoint &translation); /** * Translates the window by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ WindowPaintData& operator+=(const QVector2D &translation); /** * Translates the window by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ WindowPaintData& operator+=(const QVector3D &translation); /** * Window opacity, in range 0 = transparent to 1 = fully opaque * @see setOpacity * @since 4.10 */ qreal opacity() const; /** * Sets the window opacity to the new @p opacity. * If you want to modify the existing opacity level consider using multiplyOpacity. * @param opacity The new opacity level * @since 4.10 **/ void setOpacity(qreal opacity); /** * Multiplies the current opacity with the @p factor. * @param factor Factor with which the opacity should be multiplied * @return New opacity level * @since 4.10 **/ qreal multiplyOpacity(qreal factor); /** * Saturation of the window, in range [0; 1] * 1 means that the window is unchanged, 0 means that it's completely * unsaturated (greyscale). 0.5 would make the colors less intense, * but not completely grey * Use EffectsHandler::saturationSupported() to find out whether saturation * is supported by the system, otherwise this value has no effect. * @return The current saturation * @see setSaturation() * @since 4.10 **/ qreal saturation() const; /** * Sets the window saturation level to @p saturation. * If you want to modify the existing saturation level consider using multiplySaturation. * @param saturation The new saturation level * @since 4.10 **/ void setSaturation(qreal saturation) const; /** * Multiplies the current saturation with @p factor. * @param factor with which the saturation should be multiplied * @return New saturation level * @since 4.10 **/ qreal multiplySaturation(qreal factor); /** * Brightness of the window, in range [0; 1] * 1 means that the window is unchanged, 0 means that it's completely * black. 0.5 would make it 50% darker than usual **/ qreal brightness() const; /** * Sets the window brightness level to @p brightness. * If you want to modify the existing brightness level consider using multiplyBrightness. * @param brightness The new brightness level **/ void setBrightness(qreal brightness); /** * Multiplies the current brightness level with @p factor. * @param factor with which the brightness should be multiplied. * @return New brightness level * @since 4.10 **/ qreal multiplyBrightness(qreal factor); /** * The screen number for which the painting should be done. * This affects color correction (different screens may need different * color correction lookup tables because they have different ICC profiles). * @return screen for which painting should be done */ int screen() const; /** * @param screen New screen number * A value less than 0 will indicate that a default profile should be done. */ void setScreen(int screen) const; /** * @brief Sets the cross fading @p factor to fade over with previously sized window. * If @c 1.0 only the current window is used, if @c 0.0 only the previous window is used. * * By default only the current window is used. This factor can only make any visual difference * if the previous window get referenced. * * @param factor The cross fade factor between @c 0.0 (previous window) and @c 1.0 (current window) * @see crossFadeProgress */ void setCrossFadeProgress(qreal factor); /** * @see setCrossFadeProgress */ qreal crossFadeProgress() const; /** * Sets the projection matrix that will be used when painting the window. * * The default projection matrix can be overridden by setting this matrix * to a non-identity matrix. */ void setProjectionMatrix(const QMatrix4x4 &matrix); /** * Returns the current projection matrix. * * The default value for this matrix is the identity matrix. */ QMatrix4x4 projectionMatrix() const; /** * Returns a reference to the projection matrix. */ QMatrix4x4 &rprojectionMatrix(); /** * Sets the model-view matrix that will be used when painting the window. * * The default model-view matrix can be overridden by setting this matrix * to a non-identity matrix. */ void setModelViewMatrix(const QMatrix4x4 &matrix); /** * Returns the current model-view matrix. * * The default value for this matrix is the identity matrix. */ QMatrix4x4 modelViewMatrix() const; /** * Returns a reference to the model-view matrix. */ QMatrix4x4 &rmodelViewMatrix(); /** * Returns The projection matrix as used by the current screen painting pass * including screen transformations. * * @since 5.6 **/ QMatrix4x4 screenProjectionMatrix() const; WindowQuadList quads; /** * Shader to be used for rendering, if any. */ GLShader* shader; private: WindowPaintDataPrivate * const d; }; class KWINEFFECTS_EXPORT ScreenPaintData : public PaintData { public: ScreenPaintData(); ScreenPaintData(const QMatrix4x4 &projectionMatrix, const QRect &outputGeometry = QRect()); ScreenPaintData(const ScreenPaintData &other); virtual ~ScreenPaintData(); /** * Scales the screen by @p scale factor. * Multiplies all three components by the given factor. * @since 4.10 **/ ScreenPaintData& operator*=(qreal scale); /** * Scales the screen by @p scale factor. * Performs a component wise multiplication on x and y components. * @since 4.10 **/ ScreenPaintData& operator*=(const QVector2D &scale); /** * Scales the screen by @p scale factor. * Performs a component wise multiplication. * @since 4.10 **/ ScreenPaintData& operator*=(const QVector3D &scale); /** * Translates the screen by the given @p translation and returns a reference to the ScreenPaintData. * @since 4.10 **/ ScreenPaintData& operator+=(const QPointF &translation); /** * Translates the screen by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ ScreenPaintData& operator+=(const QPoint &translation); /** * Translates the screen by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ ScreenPaintData& operator+=(const QVector2D &translation); /** * Translates the screen by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ ScreenPaintData& operator+=(const QVector3D &translation); ScreenPaintData& operator=(const ScreenPaintData &rhs); /** * The projection matrix used by the scene for the current rendering pass. * On non-OpenGL compositors it's set to Identity matrix. * @since 5.6 **/ QMatrix4x4 projectionMatrix() const; /** * The geometry of the currently rendered output. * Only set for per-output rendering (e.g. Wayland). * * This geometry can be used as a hint about the native window the OpenGL context * is bound. OpenGL calls need to be translated to this geometry. * @since 5.9 **/ QRect outputGeometry() const; private: class Private; QScopedPointer d; }; class KWINEFFECTS_EXPORT ScreenPrePaintData { public: int mask; QRegion paint; }; /** * @short Helper class for restricting painting area only to allowed area. * * This helper class helps specifying areas that should be painted, clipping * out the rest. The simplest usage is creating an object on the stack * and giving it the area that is allowed to be painted to. When the object * is destroyed, the restriction will be removed. * Note that all painting code must use paintArea() to actually perform the clipping. */ class KWINEFFECTS_EXPORT PaintClipper { public: /** * Calls push(). */ explicit PaintClipper(const QRegion& allowed_area); /** * Calls pop(). */ ~PaintClipper(); /** * Allows painting only in the given area. When areas have been already * specified, painting is allowed only in the intersection of all areas. */ static void push(const QRegion& allowed_area); /** * Removes the given area. It must match the top item in the stack. */ static void pop(const QRegion& allowed_area); /** * Returns true if any clipping should be performed. */ static bool clip(); /** * If clip() returns true, this function gives the resulting area in which * painting is allowed. It is usually simpler to use the helper Iterator class. */ static QRegion paintArea(); /** * Helper class to perform the clipped painting. The usage is: * @code * for ( PaintClipper::Iterator iterator; * !iterator.isDone(); * iterator.next()) * { // do the painting, possibly use iterator.boundingRect() * } * @endcode */ class KWINEFFECTS_EXPORT Iterator { public: Iterator(); ~Iterator(); bool isDone(); void next(); QRect boundingRect() const; private: struct Data; Data* data; }; private: QRegion area; static QStack< QRegion >* areas; }; /** * @internal */ template class KWINEFFECTS_EXPORT Motion { public: /** * Creates a new motion object. "Strength" is the amount of * acceleration that is applied to the object when the target * changes and "smoothness" relates to how fast the object * can change its direction and speed. */ explicit Motion(T initial, double strength, double smoothness); /** * Creates an exact copy of another motion object, including * position, target and velocity. */ Motion(const Motion &other); ~Motion(); inline T value() const { return m_value; } inline void setValue(const T value) { m_value = value; } inline T target() const { return m_target; } inline void setTarget(const T target) { m_start = m_value; m_target = target; } inline T velocity() const { return m_velocity; } inline void setVelocity(const T velocity) { m_velocity = velocity; } inline double strength() const { return m_strength; } inline void setStrength(const double strength) { m_strength = strength; } inline double smoothness() const { return m_smoothness; } inline void setSmoothness(const double smoothness) { m_smoothness = smoothness; } inline T startValue() { return m_start; } /** * The distance between the current position and the target. */ inline T distance() const { return m_target - m_value; } /** * Calculates the new position if not at the target. Called * once per frame only. */ void calculate(const int msec); /** * Place the object on top of the target immediately, * bypassing all movement calculation. */ void finish(); private: T m_value; T m_start; T m_target; T m_velocity; double m_strength; double m_smoothness; }; /** * @short A single 1D motion dynamics object. * * This class represents a single object that can be moved around a * 1D space. Although it can be used directly by itself it is * recommended to use a motion manager instead. */ class KWINEFFECTS_EXPORT Motion1D : public Motion { public: explicit Motion1D(double initial = 0.0, double strength = 0.08, double smoothness = 4.0); Motion1D(const Motion1D &other); ~Motion1D(); }; /** * @short A single 2D motion dynamics object. * * This class represents a single object that can be moved around a * 2D space. Although it can be used directly by itself it is * recommended to use a motion manager instead. */ class KWINEFFECTS_EXPORT Motion2D : public Motion { public: explicit Motion2D(QPointF initial = QPointF(), double strength = 0.08, double smoothness = 4.0); Motion2D(const Motion2D &other); ~Motion2D(); }; /** * @short Helper class for motion dynamics in KWin effects. * * This motion manager class is intended to help KWin effect authors * move windows across the screen smoothly and naturally. Once * windows are registered by the manager the effect can issue move * commands with the moveWindow() methods. The position of any * managed window can be determined in realtime by the * transformedGeometry() method. As the manager knows if any windows * are moving at any given time it can also be used as a notifier as * to see whether the effect is active or not. */ class KWINEFFECTS_EXPORT WindowMotionManager { public: /** * Creates a new window manager object. */ explicit WindowMotionManager(bool useGlobalAnimationModifier = true); ~WindowMotionManager(); /** * Register a window for managing. */ void manage(EffectWindow *w); /** * Register a list of windows for managing. */ inline void manage(EffectWindowList list) { for (int i = 0; i < list.size(); i++) manage(list.at(i)); } /** * Deregister a window. All transformations applied to the * window will be permanently removed and cannot be recovered. */ void unmanage(EffectWindow *w); /** * Deregister all windows, returning the manager to its * originally initiated state. */ void unmanageAll(); /** * Determine the new positions for windows that have not * reached their target. Called once per frame, usually in * prePaintScreen(). Remember to set the * Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS flag. */ void calculate(int time); /** * Modify a registered window's paint data to make it appear * at its real location on the screen. Usually called in * paintWindow(). Remember to flag the window as having been * transformed in prePaintWindow() by calling * WindowPrePaintData::setTransformed() */ void apply(EffectWindow *w, WindowPaintData &data); /** * Set all motion targets and values back to where the * windows were before transformations. The same as * unmanaging then remanaging all windows. */ void reset(); /** * Resets the motion target and current value of a single * window. */ void reset(EffectWindow *w); /** * Ask the manager to move the window to the target position * with the specified scale. If `yScale` is not provided or * set to 0.0, `scale` will be used as the scale in the * vertical direction as well as in the horizontal direction. */ void moveWindow(EffectWindow *w, QPoint target, double scale = 1.0, double yScale = 0.0); /** * This is an overloaded method, provided for convenience. * * Ask the manager to move the window to the target rectangle. * Automatically determines scale. */ inline void moveWindow(EffectWindow *w, QRect target) { // TODO: Scale might be slightly different in the comparison due to rounding moveWindow(w, target.topLeft(), target.width() / double(w->width()), target.height() / double(w->height())); } /** * Retrieve the current tranformed geometry of a registered * window. */ QRectF transformedGeometry(EffectWindow *w) const; /** * Sets the current transformed geometry of a registered window to the given geometry. * @see transformedGeometry * @since 4.5 */ void setTransformedGeometry(EffectWindow *w, const QRectF &geometry); /** * Retrieve the current target geometry of a registered * window. */ QRectF targetGeometry(EffectWindow *w) const; /** * Return the window that has its transformed geometry under * the specified point. It is recommended to use the stacking * order as it's what the user sees, but it is slightly * slower to process. */ EffectWindow* windowAtPoint(QPoint point, bool useStackingOrder = true) const; /** * Return a list of all currently registered windows. */ inline EffectWindowList managedWindows() const { return m_managedWindows.keys(); } /** * Returns whether or not a specified window is being managed * by this manager object. */ inline bool isManaging(EffectWindow *w) const { return m_managedWindows.contains(w); } /** * Returns whether or not this manager object is actually * managing any windows or not. */ inline bool managingWindows() const { return !m_managedWindows.empty(); } /** * Returns whether all windows have reached their targets yet * or not. Can be used to see if an effect should be * processed and displayed or not. */ inline bool areWindowsMoving() const { return !m_movingWindowsSet.isEmpty(); } /** * Returns whether a window has reached its targets yet * or not. */ inline bool isWindowMoving(EffectWindow *w) const { return m_movingWindowsSet.contains(w); } private: bool m_useGlobalAnimationModifier; struct WindowMotion { // TODO: Rotation, etc? Motion2D translation; // Absolute position Motion2D scale; // xScale and yScale }; QHash m_managedWindows; QSet m_movingWindowsSet; }; /** * @short Helper class for displaying text and icons in frames. * * Paints text and/or and icon with an optional frame around them. The * available frames includes one that follows the default Plasma theme and * another that doesn't. * It is recommended to use this class whenever displaying text. */ class KWINEFFECTS_EXPORT EffectFrame { public: EffectFrame(); virtual ~EffectFrame(); /** * Delete any existing textures to free up graphics memory. They will * be automatically recreated the next time they are required. */ virtual void free() = 0; /** * Render the frame. */ virtual void render(QRegion region = infiniteRegion(), double opacity = 1.0, double frameOpacity = 1.0) = 0; virtual void setPosition(const QPoint& point) = 0; /** * Set the text alignment for static frames and the position alignment * for non-static. */ virtual void setAlignment(Qt::Alignment alignment) = 0; virtual Qt::Alignment alignment() const = 0; virtual void setGeometry(const QRect& geometry, bool force = false) = 0; virtual const QRect& geometry() const = 0; virtual void setText(const QString& text) = 0; virtual const QString& text() const = 0; virtual void setFont(const QFont& font) = 0; virtual const QFont& font() const = 0; /** * Set the icon that will appear on the left-hand size of the frame. */ virtual void setIcon(const QIcon& icon) = 0; virtual const QIcon& icon() const = 0; virtual void setIconSize(const QSize& size) = 0; virtual const QSize& iconSize() const = 0; /** * Sets the geometry of a selection. * To remove the selection set a null rect. * @param selection The geometry of the selection in screen coordinates. **/ virtual void setSelection(const QRect& selection) = 0; /** * @param shader The GLShader for rendering. **/ virtual void setShader(GLShader* shader) = 0; /** * @returns The GLShader used for rendering or null if none. **/ virtual GLShader* shader() const = 0; /** * @returns The style of this EffectFrame. **/ virtual EffectFrameStyle style() const = 0; /** * If @p enable is @c true cross fading between icons and text is enabled * By default disabled. Use setCrossFadeProgress to cross fade. * Cross Fading is currently only available if OpenGL is used. * @param enable @c true enables cross fading, @c false disables it again * @see isCrossFade * @see setCrossFadeProgress * @since 4.6 **/ void enableCrossFade(bool enable); /** * @returns @c true if cross fading is enabled, @c false otherwise * @see enableCrossFade * @since 4.6 **/ bool isCrossFade() const; /** * Sets the current progress for cross fading the last used icon/text * with current icon/text to @p progress. * A value of 0.0 means completely old icon/text, a value of 1.0 means * completely current icon/text. * Default value is 1.0. You have to enable cross fade before using it. * Cross Fading is currently only available if OpenGL is used. * @see enableCrossFade * @see isCrossFade * @see crossFadeProgress * @since 4.6 **/ void setCrossFadeProgress(qreal progress); /** * @returns The current progress for cross fading * @see setCrossFadeProgress * @see enableCrossFade * @see isCrossFade * @since 4.6 **/ qreal crossFadeProgress() const; /** * Returns The projection matrix as used by the current screen painting pass * including screen transformations. * * This matrix is only valid during a rendering pass started by render. * * @since 5.6 * @see render * @see EffectsHandler::paintEffectFrame * @see Effect::paintEffectFrame **/ QMatrix4x4 screenProjectionMatrix() const; protected: void setScreenProjectionMatrix(const QMatrix4x4 &projection); private: EffectFramePrivate* const d; }; /** * Pointer to the global EffectsHandler object. **/ extern KWINEFFECTS_EXPORT EffectsHandler* effects; /*************************************************************** WindowVertex ***************************************************************/ inline WindowVertex::WindowVertex() : px(0), py(0), ox(0), oy(0), tx(0), ty(0) { } inline WindowVertex::WindowVertex(double _x, double _y, double _tx, double _ty) : px(_x), py(_y), ox(_x), oy(_y), tx(_tx), ty(_ty) { } inline WindowVertex::WindowVertex(const QPointF &position, const QPointF &texturePosition) : px(position.x()), py(position.y()), ox(position.x()), oy(position.y()), tx(texturePosition.x()), ty(texturePosition.y()) { } inline void WindowVertex::move(double x, double y) { px = x; py = y; } inline void WindowVertex::setX(double x) { px = x; } inline void WindowVertex::setY(double y) { py = y; } /*************************************************************** WindowQuad ***************************************************************/ inline WindowQuad::WindowQuad(WindowQuadType t, int id) : quadType(t) , uvSwapped(false) , quadID(id) { } inline WindowVertex& WindowQuad::operator[](int index) { assert(index >= 0 && index < 4); return verts[ index ]; } inline const WindowVertex& WindowQuad::operator[](int index) const { assert(index >= 0 && index < 4); return verts[ index ]; } inline WindowQuadType WindowQuad::type() const { assert(quadType != WindowQuadError); return quadType; } inline int WindowQuad::id() const { return quadID; } inline bool WindowQuad::decoration() const { assert(quadType != WindowQuadError); return quadType == WindowQuadDecoration; } inline bool WindowQuad::effect() const { assert(quadType != WindowQuadError); return quadType >= EFFECT_QUAD_TYPE_START; } inline bool WindowQuad::isTransformed() const { return !(verts[ 0 ].px == verts[ 0 ].ox && verts[ 0 ].py == verts[ 0 ].oy && verts[ 1 ].px == verts[ 1 ].ox && verts[ 1 ].py == verts[ 1 ].oy && verts[ 2 ].px == verts[ 2 ].ox && verts[ 2 ].py == verts[ 2 ].oy && verts[ 3 ].px == verts[ 3 ].ox && verts[ 3 ].py == verts[ 3 ].oy); } inline double WindowQuad::left() const { return qMin(verts[ 0 ].px, qMin(verts[ 1 ].px, qMin(verts[ 2 ].px, verts[ 3 ].px))); } inline double WindowQuad::right() const { return qMax(verts[ 0 ].px, qMax(verts[ 1 ].px, qMax(verts[ 2 ].px, verts[ 3 ].px))); } inline double WindowQuad::top() const { return qMin(verts[ 0 ].py, qMin(verts[ 1 ].py, qMin(verts[ 2 ].py, verts[ 3 ].py))); } inline double WindowQuad::bottom() const { return qMax(verts[ 0 ].py, qMax(verts[ 1 ].py, qMax(verts[ 2 ].py, verts[ 3 ].py))); } inline double WindowQuad::originalLeft() const { return verts[ 0 ].ox; } inline double WindowQuad::originalRight() const { return verts[ 2 ].ox; } inline double WindowQuad::originalTop() const { return verts[ 0 ].oy; } inline double WindowQuad::originalBottom() const { return verts[ 2 ].oy; } /*************************************************************** Motion ***************************************************************/ template Motion::Motion(T initial, double strength, double smoothness) : m_value(initial) , m_start(initial) , m_target(initial) , m_velocity() , m_strength(strength) , m_smoothness(smoothness) { } template Motion::Motion(const Motion &other) : m_value(other.value()) , m_start(other.target()) , m_target(other.target()) , m_velocity(other.velocity()) , m_strength(other.strength()) , m_smoothness(other.smoothness()) { } template Motion::~Motion() { } template void Motion::calculate(const int msec) { if (m_value == m_target && m_velocity == T()) // At target and not moving return; // Poor man's time independent calculation int steps = qMax(1, msec / 5); for (int i = 0; i < steps; i++) { T diff = m_target - m_value; T strength = diff * m_strength; m_velocity = (m_smoothness * m_velocity + strength) / (m_smoothness + 1.0); m_value += m_velocity; } } template void Motion::finish() { m_value = m_target; m_velocity = T(); } /*************************************************************** Effect ***************************************************************/ template int Effect::animationTime(int defaultDuration) { return animationTime(T::duration() != 0 ? T::duration() : defaultDuration); } template void Effect::initConfig() { T::instance(effects->config()); } } // namespace Q_DECLARE_METATYPE(KWin::EffectWindow*) Q_DECLARE_METATYPE(QList) /** @} */ #endif // KWINEFFECTS_H diff --git a/libkwineffects/kwinglplatform.cpp b/libkwineffects/kwinglplatform.cpp index 2b7b9a4c4..f5febc05b 100644 --- a/libkwineffects/kwinglplatform.cpp +++ b/libkwineffects/kwinglplatform.cpp @@ -1,1153 +1,1159 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Fredrik Höglund 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 "kwinglplatform.h" #include #include #include #include #include #include #include #include #include namespace KWin { GLPlatform *GLPlatform::s_platform = 0; static qint64 parseVersionString(const QByteArray &version) { // Skip any leading non digit int start = 0; while (start < version.length() && !QChar::fromLatin1(version[start]).isDigit()) start++; // Strip any non digit, non '.' characters from the end int end = start; while (end < version.length() && (version[end] == '.' || QChar::fromLatin1(version[end]).isDigit())) end++; const QByteArray result = version.mid(start, end-start); const QList tokens = result.split('.'); const qint64 major = tokens.at(0).toInt(); const qint64 minor = tokens.count() > 1 ? tokens.at(1).toInt() : 0; const qint64 patch = tokens.count() > 2 ? tokens.at(2).toInt() : 0; return kVersionNumber(major, minor, patch); } static qint64 getXServerVersion() { qint64 major, minor, patch; major = 0; minor = 0; patch = 0; if (xcb_connection_t *c = connection()) { auto setup = xcb_get_setup(c); const QByteArray vendorName(xcb_setup_vendor(setup), xcb_setup_vendor_length(setup)); if (vendorName.contains("X.Org")) { const int release = setup->release_number; major = (release / 10000000); minor = (release / 100000) % 100; patch = (release / 1000) % 100; } } return kVersionNumber(major, minor, patch); } static qint64 getKernelVersion() { struct utsname name; uname(&name); if (qstrcmp(name.sysname, "Linux") == 0) return parseVersionString(name.release); return 0; } // Extracts the portion of a string that matches a regular expression static QString extract(const QString &string, const QString &match, int offset = 0) { QString result; QRegExp rx(match); int pos = rx.indexIn(string, offset); if (pos != -1) result = string.mid(pos, rx.matchedLength()); return result; } static ChipClass detectRadeonClass(const QByteArray &chipset) { if (chipset.isEmpty()) return UnknownRadeon; if (chipset.contains("R100") || chipset.contains("RV100") || chipset.contains("RS100")) return R100; if (chipset.contains("RV200") || chipset.contains("RS200") || chipset.contains("R200") || chipset.contains("RV250") || chipset.contains("RS300") || chipset.contains("RV280")) return R200; if (chipset.contains("R300") || chipset.contains("R350") || chipset.contains("R360") || chipset.contains("RV350") || chipset.contains("RV370") || chipset.contains("RV380")) return R300; if (chipset.contains("R420") || chipset.contains("R423") || chipset.contains("R430") || chipset.contains("R480") || chipset.contains("R481") || chipset.contains("RV410") || chipset.contains("RS400") || chipset.contains("RC410") || chipset.contains("RS480") || chipset.contains("RS482") || chipset.contains("RS600") || chipset.contains("RS690") || chipset.contains("RS740")) return R400; if (chipset.contains("RV515") || chipset.contains("R520") || chipset.contains("RV530") || chipset.contains("R580") || chipset.contains("RV560") || chipset.contains("RV570")) return R500; if (chipset.contains("R600") || chipset.contains("RV610") || chipset.contains("RV630") || chipset.contains("RV670") || chipset.contains("RV620") || chipset.contains("RV635") || chipset.contains("RS780") || chipset.contains("RS880")) return R600; if (chipset.contains("R700") || chipset.contains("RV770") || chipset.contains("RV730") || chipset.contains("RV710") || chipset.contains("RV740")) return R700; if (chipset.contains("EVERGREEN") || // Not an actual chipset, but returned by R600G in 7.9 chipset.contains("CEDAR") || chipset.contains("REDWOOD") || chipset.contains("JUNIPER") || chipset.contains("CYPRESS") || chipset.contains("HEMLOCK") || chipset.contains("PALM")) return Evergreen; if (chipset.contains("SUMO") || chipset.contains("SUMO2") || chipset.contains("BARTS") || chipset.contains("TURKS") || chipset.contains("CAICOS") || chipset.contains("CAYMAN")) return NorthernIslands; const QString chipset16 = QString::fromLatin1(chipset); QString name = extract(chipset16, QStringLiteral("HD [0-9]{4}")); // HD followed by a space and 4 digits if (!name.isEmpty()) { const int id = name.rightRef(4).toInt(); if (id == 6250 || id == 6310) // Palm return Evergreen; if (id >= 6000 && id < 7000) return NorthernIslands; // HD 6xxx if (id >= 5000 && id < 6000) return Evergreen; // HD 5xxx if (id >= 4000 && id < 5000) return R700; // HD 4xxx if (id >= 2000 && id < 4000) // HD 2xxx/3xxx return R600; return UnknownRadeon; } name = extract(chipset16, QStringLiteral("X[0-9]{3,4}")); // X followed by 3-4 digits if (!name.isEmpty()) { const int id = name.midRef(1, -1).toInt(); // X1xxx if (id >= 1300) return R500; // X7xx, X8xx, X12xx, 2100 if ((id >= 700 && id < 1000) || id >= 1200) return R400; // X200, X3xx, X5xx, X6xx, X10xx, X11xx if ((id >= 300 && id < 700) || (id >= 1000 && id < 1200)) return R300; return UnknownRadeon; } name = extract(chipset16, QStringLiteral("\\b[0-9]{4}\\b")); // A group of 4 digits if (!name.isEmpty()) { const int id = name.toInt(); // 7xxx if (id >= 7000 && id < 8000) return R100; // 8xxx, 9xxx if (id >= 8000 && id < 9500) return R200; // 9xxx if (id >= 9500) return R300; if (id == 2100) return R400; } return UnknownRadeon; } static ChipClass detectNVidiaClass(const QString &chipset) { QString name = extract(chipset, QStringLiteral("\\bNV[0-9,A-F]{2}\\b")); // NV followed by two hexadecimal digits if (!name.isEmpty()) { const int id = chipset.midRef(2, -1).toInt(0, 16); // Strip the 'NV' from the id switch(id & 0xf0) { case 0x00: case 0x10: return NV10; case 0x20: return NV20; case 0x30: return NV30; case 0x40: case 0x60: return NV40; case 0x50: case 0x80: case 0x90: case 0xA0: return G80; default: return UnknownNVidia; } } if (chipset.contains(QLatin1String("GeForce2")) || chipset.contains(QLatin1String("GeForce 256"))) return NV10; if (chipset.contains(QLatin1String("GeForce3"))) return NV20; if (chipset.contains(QLatin1String("GeForce4"))) { if (chipset.contains(QLatin1String("MX 420")) || chipset.contains(QLatin1String("MX 440")) || // including MX 440SE chipset.contains(QLatin1String("MX 460")) || chipset.contains(QLatin1String("MX 4000")) || chipset.contains(QLatin1String("PCX 4300"))) return NV10; return NV20; } // GeForce 5,6,7,8,9 name = extract(chipset, QStringLiteral("GeForce (FX |PCX |Go )?\\d{4}(M|\\b)")).trimmed(); if (!name.isEmpty()) { if (!name[name.length() - 1].isDigit()) name.chop(1); const int id = name.rightRef(4).toInt(); if (id < 6000) return NV30; if (id >= 6000 && id < 8000) return NV40; if (id >= 8000) return G80; return UnknownNVidia; } // GeForce 100/200/300/400/500 name = extract(chipset, QStringLiteral("GeForce (G |GT |GTX |GTS )?\\d{3}(M|\\b)")).trimmed(); if (!name.isEmpty()) { if (!name[name.length() - 1].isDigit()) name.chop(1); const int id = name.rightRef(3).toInt(); if (id >= 100 && id < 600) { if (id >= 400) return GF100; return G80; } return UnknownNVidia; } return UnknownNVidia; } static inline ChipClass detectNVidiaClass(const QByteArray &chipset) { return detectNVidiaClass(QString::fromLatin1(chipset)); } static ChipClass detectIntelClass(const QByteArray &chipset) { // see mesa repository: src/mesa/drivers/dri/intel/intel_context.c // GL 1.3, DX8? SM ? if (chipset.contains("845G") || chipset.contains("830M") || chipset.contains("852GM/855GM") || chipset.contains("865G")) return I8XX; // GL 1.4, DX 9.0, SM 2.0 if (chipset.contains("915G") || chipset.contains("E7221G") || chipset.contains("915GM") || chipset.contains("945G") || // DX 9.0c chipset.contains("945GM") || chipset.contains("945GME") || chipset.contains("Q33") || // GL1.5 chipset.contains("Q35") || chipset.contains("G33") || chipset.contains("965Q") || // GMA 3000, but apparently considered gen 4 by the driver chipset.contains("946GZ") || // GMA 3000, but apparently considered gen 4 by the driver chipset.contains("IGD")) return I915; // GL 2.0, DX 9.0c, SM 3.0 if (chipset.contains("965G") || chipset.contains("G45/G43") || // SM 4.0 chipset.contains("965GM") || // GL 2.1 chipset.contains("965GME/GLE") || chipset.contains("GM45") || chipset.contains("Q45/Q43") || chipset.contains("G41") || chipset.contains("B43") || chipset.contains("Ironlake")) return I965; // GL 3.1, CL 1.1, DX 10.1 if (chipset.contains("Sandybridge")) { return SandyBridge; } // GL4.0, CL1.1, DX11, SM 5.0 if (chipset.contains("Ivybridge")) { return IvyBridge; } // GL4.0, CL1.2, DX11.1, SM 5.0 if (chipset.contains("Haswell")) { return Haswell; } return UnknownIntel; } static ChipClass detectQualcommClass(const QByteArray &chipClass) { if (!chipClass.contains("Adreno")) { return UnknownChipClass; } const auto parts = chipClass.split(' '); if (parts.count() < 3) { return UnknownAdreno; } bool ok = false; const int value = parts.at(2).toInt(&ok); if (ok) { if (value >= 100 && value < 200) { return Adreno1XX; } if (value >= 200 && value < 300) { return Adreno2XX; } if (value >= 300 && value < 400) { return Adreno3XX; } if (value >= 400 && value < 500) { return Adreno4XX; } if (value >= 500 && value < 600) { return Adreno5XX; } } return UnknownAdreno; } QString GLPlatform::versionToString(qint64 version) { return QString::fromLatin1(versionToString8(version)); } QByteArray GLPlatform::versionToString8(qint64 version) { int major = (version >> 32); int minor = (version >> 16) & 0xffff; int patch = version & 0xffff; QByteArray string = QByteArray::number(major) + '.' + QByteArray::number(minor); if (patch != 0) string += '.' + QByteArray::number(patch); return string; } QString GLPlatform::driverToString(Driver driver) { return QString::fromLatin1(driverToString8(driver)); } QByteArray GLPlatform::driverToString8(Driver driver) { switch(driver) { case Driver_R100: return QByteArrayLiteral("Radeon"); case Driver_R200: return QByteArrayLiteral("R200"); case Driver_R300C: return QByteArrayLiteral("R300C"); case Driver_R300G: return QByteArrayLiteral("R300G"); case Driver_R600C: return QByteArrayLiteral("R600C"); case Driver_R600G: return QByteArrayLiteral("R600G"); case Driver_Nouveau: return QByteArrayLiteral("Nouveau"); case Driver_Intel: return QByteArrayLiteral("Intel"); case Driver_NVidia: return QByteArrayLiteral("NVIDIA"); case Driver_Catalyst: return QByteArrayLiteral("Catalyst"); case Driver_Swrast: return QByteArrayLiteral("Software rasterizer"); case Driver_Softpipe: return QByteArrayLiteral("softpipe"); case Driver_Llvmpipe: return QByteArrayLiteral("LLVMpipe"); case Driver_VirtualBox: return QByteArrayLiteral("VirtualBox (Chromium)"); case Driver_VMware: return QByteArrayLiteral("VMware (SVGA3D)"); case Driver_Qualcomm: return QByteArrayLiteral("Qualcomm"); default: return QByteArrayLiteral("Unknown"); } } QString GLPlatform::chipClassToString(ChipClass chipClass) { return QString::fromLatin1(chipClassToString8(chipClass)); } QByteArray GLPlatform::chipClassToString8(ChipClass chipClass) { switch(chipClass) { case R100: return QByteArrayLiteral("R100"); case R200: return QByteArrayLiteral("R200"); case R300: return QByteArrayLiteral("R300"); case R400: return QByteArrayLiteral("R400"); case R500: return QByteArrayLiteral("R500"); case R600: return QByteArrayLiteral("R600"); case R700: return QByteArrayLiteral("R700"); case Evergreen: return QByteArrayLiteral("EVERGREEN"); case NorthernIslands: return QByteArrayLiteral("NI"); case NV10: return QByteArrayLiteral("NV10"); case NV20: return QByteArrayLiteral("NV20"); case NV30: return QByteArrayLiteral("NV30"); case NV40: return QByteArrayLiteral("NV40/G70"); case G80: return QByteArrayLiteral("G80/G90"); case GF100: return QByteArrayLiteral("GF100"); case I8XX: return QByteArrayLiteral("i830/i835"); case I915: return QByteArrayLiteral("i915/i945"); case I965: return QByteArrayLiteral("i965"); case SandyBridge: return QByteArrayLiteral("SandyBridge"); case IvyBridge: return QByteArrayLiteral("IvyBridge"); case Haswell: return QByteArrayLiteral("Haswell"); case Adreno1XX: return QByteArrayLiteral("Adreno 1xx series"); case Adreno2XX: return QByteArrayLiteral("Adreno 2xx series"); case Adreno3XX: return QByteArrayLiteral("Adreno 3xx series"); case Adreno4XX: return QByteArrayLiteral("Adreno 4xx series"); case Adreno5XX: return QByteArrayLiteral("Adreno 5xx series"); default: return QByteArrayLiteral("Unknown"); } } // ------- GLPlatform::GLPlatform() : m_driver(Driver_Unknown), m_chipClass(UnknownChipClass), m_recommendedCompositor(XRenderCompositing), m_glVersion(0), m_glslVersion(0), m_mesaVersion(0), m_driverVersion(0), m_galliumVersion(0), m_serverVersion(0), m_kernelVersion(0), m_looseBinding(false), m_supportsGLSL(false), m_limitedGLSL(false), m_textureNPOT(false), m_limitedNPOT(false), m_virtualMachine(false), m_preferBufferSubData(false), m_platformInterface(NoOpenGLPlatformInterface), m_gles(false) { } GLPlatform::~GLPlatform() { } void GLPlatform::detect(OpenGLPlatformInterface platformInterface) { m_platformInterface = platformInterface; m_vendor = (const char*)glGetString(GL_VENDOR); m_renderer = (const char*)glGetString(GL_RENDERER); m_version = (const char*)glGetString(GL_VERSION); // Parse the OpenGL version const QList versionTokens = m_version.split(' '); if (versionTokens.count() > 0) { const QByteArray version = QByteArray(m_version); m_glVersion = parseVersionString(version); if (platformInterface == EglPlatformInterface) { // only EGL can have OpenGLES, GLX is OpenGL only if (version.startsWith("OpenGL ES")) { // from GLES 2: "Returns a version or release number of the form OpenGLES." // from GLES 3: "Returns a version or release number." and "The version number uses one of these forms: major_number.minor_number major_number.minor_number.release_number" m_gles = true; } } } if (!isGLES() && m_glVersion >= kVersionNumber(3, 0)) { int count; glGetIntegerv(GL_NUM_EXTENSIONS, &count); for (int i = 0; i < count; i++) { const char *name = (const char *) glGetStringi(GL_EXTENSIONS, i); m_extensions.insert(name); } } else { const QByteArray extensions = (const char *) glGetString(GL_EXTENSIONS); m_extensions = QSet::fromList(extensions.split(' ')); } // Parse the Mesa version const int mesaIndex = versionTokens.indexOf("Mesa"); if (mesaIndex != -1) { const QByteArray version = versionTokens.at(mesaIndex + 1); m_mesaVersion = parseVersionString(version); } if (isGLES()) { m_supportsGLSL = true; m_textureNPOT = true; } else { m_supportsGLSL = m_extensions.contains("GL_ARB_shader_objects") && m_extensions.contains("GL_ARB_fragment_shader") && m_extensions.contains("GL_ARB_vertex_shader"); m_textureNPOT = m_extensions.contains("GL_ARB_texture_non_power_of_two"); } m_serverVersion = getXServerVersion(); m_kernelVersion = getKernelVersion(); m_glslVersion = 0; m_glsl_version.clear(); if (m_supportsGLSL) { // Parse the GLSL version m_glsl_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); m_glslVersion = parseVersionString(m_glsl_version); } m_chipset = QByteArrayLiteral("Unknown"); m_preferBufferSubData = false; // Mesa classic drivers // ==================================================== // Radeon if (m_renderer.startsWith("Mesa DRI R")) { // Sample renderer string: Mesa DRI R600 (RV740 94B3) 20090101 x86/MMX/SSE2 TCL DRI2 const QList tokens = m_renderer.split(' '); const QByteArray chipClass = tokens.at(2); m_chipset = tokens.at(3).mid(1, -1); // Strip the leading '(' if (chipClass == "R100") // Vendor: Tungsten Graphics, Inc. m_driver = Driver_R100; else if (chipClass == "R200") // Vendor: Tungsten Graphics, Inc. m_driver = Driver_R200; else if (chipClass == "R300") // Vendor: DRI R300 Project m_driver = Driver_R300C; else if (chipClass == "R600") // Vendor: Advanced Micro Devices, Inc. m_driver = Driver_R600C; m_chipClass = detectRadeonClass(m_chipset); } // Intel else if (m_renderer.contains("Intel")) { // Vendor: Tungsten Graphics, Inc. // Sample renderer string: Mesa DRI Mobile Intel® GM45 Express Chipset GEM 20100328 2010Q1 QByteArray chipset; if (m_renderer.startsWith("Intel(R) Integrated Graphics Device")) chipset = "IGD"; else chipset = m_renderer; m_driver = Driver_Intel; m_chipClass = detectIntelClass(chipset); } + // Properietary drivers + // ==================================================== + else if (m_vendor == "ATI Technologies Inc.") { + m_chipClass = detectRadeonClass(m_renderer); + m_driver = Driver_Catalyst; + + if (versionTokens.count() > 1 && versionTokens.at(2)[0] == '(') + m_driverVersion = parseVersionString(versionTokens.at(1)); + else if (versionTokens.count() > 0) + m_driverVersion = parseVersionString(versionTokens.at(0)); + else + m_driverVersion = 0; + } + + else if (m_vendor == "NVIDIA Corporation") { + m_chipClass = detectNVidiaClass(m_renderer); + m_driver = Driver_NVidia; + + int index = versionTokens.indexOf("NVIDIA"); + if (versionTokens.count() > index) + m_driverVersion = parseVersionString(versionTokens.at(index + 1)); + else + m_driverVersion = 0; + } + + else if (m_vendor == "Qualcomm") { + m_driver = Driver_Qualcomm; + m_chipClass = detectQualcommClass(m_renderer); + } + + else if (m_renderer == "Software Rasterizer") { + m_driver = Driver_Swrast; + } + + // Virtual Hardware + // ==================================================== + else if (m_vendor == "Humper" && m_renderer == "Chromium") { + // Virtual Box + m_driver = Driver_VirtualBox; + + const int index = versionTokens.indexOf("Chromium"); + if (versionTokens.count() > index) + m_driverVersion = parseVersionString(versionTokens.at(index + 1)); + else + m_driverVersion = 0; + } + // Gallium drivers // ==================================================== - else if (m_renderer.contains("Gallium")) { - // Sample renderer string: Gallium 0.4 on AMD RV740 + else { const QList tokens = m_renderer.split(' '); - m_galliumVersion = parseVersionString(tokens.at(1)); - m_chipset = (tokens.at(3) == "AMD" || tokens.at(3) == "ATI") ? - tokens.at(4) : tokens.at(3); + if (m_renderer.contains("Gallium")) { + // Sample renderer string: Gallium 0.4 on AMD RV740 + m_galliumVersion = parseVersionString(tokens.at(1)); + m_chipset = (tokens.at(3) == "AMD" || tokens.at(3) == "ATI") ? + tokens.at(4) : tokens.at(3); + } + else { + // The renderer string does not contain "Gallium" anymore. + m_chipset = tokens.at(0); + // We don't know the actual version anymore, but it's at least 0.4. + m_galliumVersion = kVersionNumber(0, 4, 0); + } // R300G if (m_vendor == QByteArrayLiteral("X.Org R300 Project")) { m_chipClass = detectRadeonClass(m_chipset); m_driver = Driver_R300G; } // R600G else if (m_vendor == "X.Org" && (m_renderer.contains("R6") || m_renderer.contains("R7") || m_renderer.contains("RV6") || m_renderer.contains("RV7") || m_renderer.contains("RS780") || m_renderer.contains("RS880") || m_renderer.contains("CEDAR") || m_renderer.contains("REDWOOD") || m_renderer.contains("JUNIPER") || m_renderer.contains("CYPRESS") || m_renderer.contains("HEMLOCK") || m_renderer.contains("PALM") || m_renderer.contains("EVERGREEN") || m_renderer.contains("SUMO") || m_renderer.contains("SUMO2") || m_renderer.contains("BARTS") || m_renderer.contains("TURKS") || m_renderer.contains("CAICOS") || m_renderer.contains("CAYMAN"))) { m_chipClass = detectRadeonClass(m_chipset); m_driver = Driver_R600G; } // Nouveau else if (m_vendor == "nouveau") { m_chipClass = detectNVidiaClass(m_chipset); m_driver = Driver_Nouveau; } // softpipe else if (m_vendor == "VMware, Inc." && m_chipset == "softpipe" ) { m_driver = Driver_Softpipe; } // llvmpipe else if (m_vendor == "VMware, Inc." && m_chipset == "llvmpipe") { m_driver = Driver_Llvmpipe; } // SVGA3D else if (m_vendor == "VMware, Inc." && m_chipset.contains("SVGA3D")) { m_driver = Driver_VMware; } } - - // Properietary drivers - // ==================================================== - else if (m_vendor == "ATI Technologies Inc.") { - m_chipClass = detectRadeonClass(m_renderer); - m_driver = Driver_Catalyst; - - if (versionTokens.count() > 1 && versionTokens.at(2)[0] == '(') - m_driverVersion = parseVersionString(versionTokens.at(1)); - else if (versionTokens.count() > 0) - m_driverVersion = parseVersionString(versionTokens.at(0)); - else - m_driverVersion = 0; - } - - else if (m_vendor == "NVIDIA Corporation") { - m_chipClass = detectNVidiaClass(m_renderer); - m_driver = Driver_NVidia; - - int index = versionTokens.indexOf("NVIDIA"); - if (versionTokens.count() > index) - m_driverVersion = parseVersionString(versionTokens.at(index + 1)); - else - m_driverVersion = 0; - } - - else if (m_vendor == "Qualcomm") { - m_driver = Driver_Qualcomm; - m_chipClass = detectQualcommClass(m_renderer); - } - - else if (m_renderer == "Software Rasterizer") { - m_driver = Driver_Swrast; - } - - // Virtual Hardware - // ==================================================== - else if (m_vendor == "Humper" && m_renderer == "Chromium") { - // Virtual Box - m_driver = Driver_VirtualBox; - - const int index = versionTokens.indexOf("Chromium"); - if (versionTokens.count() > index) - m_driverVersion = parseVersionString(versionTokens.at(index + 1)); - else - m_driverVersion = 0; - } - - // Driver/GPU specific features // ==================================================== if (isRadeon()) { // R200 technically has a programmable pipeline, but since it's SM 1.4, // it's too limited to to be of any practical value to us. if (m_chipClass < R300) m_supportsGLSL = false; m_limitedGLSL = false; m_limitedNPOT = false; if (m_chipClass < R600) { if (driver() == Driver_Catalyst) m_textureNPOT = m_limitedNPOT = false; // Software fallback else if (driver() == Driver_R300G) m_limitedNPOT = m_textureNPOT; m_limitedGLSL = m_supportsGLSL; } if (m_chipClass < R300) { // fallback to XRender for R100 and R200 m_recommendedCompositor = XRenderCompositing; } else if (m_chipClass < R600) { // XRender due to NPOT limitations not supported by KWin's shaders m_recommendedCompositor = XRenderCompositing; } else { m_recommendedCompositor = OpenGL2Compositing; } if (driver() == Driver_R600G || (driver() == Driver_R600C && m_renderer.contains("DRI2"))) { m_looseBinding = true; } } if (isNvidia()) { if (m_driver == Driver_NVidia && m_chipClass < NV40) m_supportsGLSL = false; // High likelihood of software emulation if (m_driver == Driver_NVidia) { m_looseBinding = true; m_preferBufferSubData = true; } if (m_chipClass < NV40) { m_recommendedCompositor = XRenderCompositing; } else { m_recommendedCompositor = OpenGL2Compositing; } m_limitedNPOT = m_textureNPOT && m_chipClass < NV40; m_limitedGLSL = m_supportsGLSL && m_chipClass < G80; } if (isIntel()) { if (m_chipClass < I915) m_supportsGLSL = false; m_limitedGLSL = m_supportsGLSL && m_chipClass < I965; // see https://bugs.freedesktop.org/show_bug.cgi?id=80349#c1 m_looseBinding = false; if (m_chipClass < I915) { m_recommendedCompositor = XRenderCompositing; } else { m_recommendedCompositor = OpenGL2Compositing; } } if (isMesaDriver() && platformInterface == EglPlatformInterface) { // According to the reference implementation in // mesa/demos/src/egl/opengles1/texture_from_pixmap // the mesa egl implementation does not require a strict binding (so far). m_looseBinding = true; } if (isSoftwareEmulation()) { if (m_driver < Driver_Llvmpipe) { // we recommend XRender m_recommendedCompositor = XRenderCompositing; // Software emulation does not provide GLSL m_limitedGLSL = m_supportsGLSL = false; } else { // llvmpipe does support GLSL m_recommendedCompositor = OpenGL2Compositing; m_limitedGLSL = false; m_supportsGLSL = true; } } if (m_driver == Driver_Qualcomm) { if (m_chipClass == Adreno1XX) { m_recommendedCompositor = NoCompositing; } else { // all other drivers support at least GLES 2 m_recommendedCompositor = OpenGL2Compositing; } } if (m_chipClass == UnknownChipClass && m_driver == Driver_Unknown) { // we don't know the hardware. Let's be optimistic and assume OpenGL compatible hardware m_recommendedCompositor = OpenGL2Compositing; m_supportsGLSL = true; } if (isVirtualBox()) { m_virtualMachine = true; m_recommendedCompositor = OpenGL2Compositing; } if (isVMware()) { m_virtualMachine = true; m_recommendedCompositor = OpenGL2Compositing; } // and force back to shader supported on gles, we wouldn't have got a context if not supported if (isGLES()) { m_supportsGLSL = true; m_limitedGLSL = false; } } static void print(const QByteArray &label, const QByteArray &setting) { std::cout << std::setw(40) << std::left << label.data() << setting.data() << std::endl; } void GLPlatform::printResults() const { print(QByteArrayLiteral("OpenGL vendor string:"), m_vendor); print(QByteArrayLiteral("OpenGL renderer string:"), m_renderer); print(QByteArrayLiteral("OpenGL version string:"), m_version); if (m_supportsGLSL) print(QByteArrayLiteral("OpenGL shading language version string:"), m_glsl_version); print(QByteArrayLiteral("Driver:"), driverToString8(m_driver)); if (!isMesaDriver()) print(QByteArrayLiteral("Driver version:"), versionToString8(m_driverVersion)); print(QByteArrayLiteral("GPU class:"), chipClassToString8(m_chipClass)); print(QByteArrayLiteral("OpenGL version:"), versionToString8(m_glVersion)); if (m_supportsGLSL) print(QByteArrayLiteral("GLSL version:"), versionToString8(m_glslVersion)); if (isMesaDriver()) print(QByteArrayLiteral("Mesa version:"), versionToString8(mesaVersion())); //if (galliumVersion() > 0) // print("Gallium version:", versionToString(m_galliumVersion)); if (serverVersion() > 0) print(QByteArrayLiteral("X server version:"), versionToString8(m_serverVersion)); if (kernelVersion() > 0) print(QByteArrayLiteral("Linux kernel version:"), versionToString8(m_kernelVersion)); print(QByteArrayLiteral("Requires strict binding:"), !m_looseBinding ? QByteArrayLiteral("yes") : QByteArrayLiteral("no")); print(QByteArrayLiteral("GLSL shaders:"), m_supportsGLSL ? (m_limitedGLSL ? QByteArrayLiteral("limited") : QByteArrayLiteral("yes")) : QByteArrayLiteral("no")); print(QByteArrayLiteral("Texture NPOT support:"), m_textureNPOT ? (m_limitedNPOT ? QByteArrayLiteral("limited") : QByteArrayLiteral("yes")) : QByteArrayLiteral("no")); print(QByteArrayLiteral("Virtual Machine:"), m_virtualMachine ? QByteArrayLiteral("yes") : QByteArrayLiteral("no")); } bool GLPlatform::supports(GLFeature feature) const { switch(feature) { case LooseBinding: return m_looseBinding; case GLSL: return m_supportsGLSL; case LimitedGLSL: return m_limitedGLSL; case TextureNPOT: return m_textureNPOT; case LimitedNPOT: return m_limitedNPOT; default: return false; } } qint64 GLPlatform::glVersion() const { return m_glVersion; } qint64 GLPlatform::glslVersion() const { return m_glslVersion; } qint64 GLPlatform::mesaVersion() const { return m_mesaVersion; } qint64 GLPlatform::galliumVersion() const { return m_galliumVersion; } qint64 GLPlatform::serverVersion() const { return m_serverVersion; } qint64 GLPlatform::kernelVersion() const { return m_kernelVersion; } qint64 GLPlatform::driverVersion() const { if (isMesaDriver()) return mesaVersion(); return m_driverVersion; } Driver GLPlatform::driver() const { return m_driver; } ChipClass GLPlatform::chipClass() const { return m_chipClass; } bool GLPlatform::isMesaDriver() const { return mesaVersion() > 0; } bool GLPlatform::isGalliumDriver() const { return galliumVersion() > 0; } bool GLPlatform::isRadeon() const { return m_chipClass >= R100 && m_chipClass <= UnknownRadeon; } bool GLPlatform::isNvidia() const { return m_chipClass >= NV10 && m_chipClass <= UnknownNVidia; } bool GLPlatform::isIntel() const { return m_chipClass >= I8XX && m_chipClass <= UnknownIntel; } bool GLPlatform::isVirtualBox() const { return m_driver == Driver_VirtualBox; } bool GLPlatform::isVMware() const { return m_driver == Driver_VMware; } bool GLPlatform::isSoftwareEmulation() const { return m_driver == Driver_Softpipe || m_driver == Driver_Swrast || m_driver == Driver_Llvmpipe; } bool GLPlatform::isAdreno() const { return m_chipClass >= Adreno1XX && m_chipClass <= UnknownAdreno; } const QByteArray &GLPlatform::glRendererString() const { return m_renderer; } const QByteArray &GLPlatform::glVendorString() const { return m_vendor; } const QByteArray &GLPlatform::glVersionString() const { return m_version; } const QByteArray &GLPlatform::glShadingLanguageVersionString() const { return m_glsl_version; } bool GLPlatform::isLooseBinding() const { return m_looseBinding; } bool GLPlatform::isVirtualMachine() const { return m_virtualMachine; } CompositingType GLPlatform::recommendedCompositor() const { return m_recommendedCompositor; } bool GLPlatform::preferBufferSubData() const { return m_preferBufferSubData; } OpenGLPlatformInterface GLPlatform::platformInterface() const { return m_platformInterface; } bool GLPlatform::isGLES() const { return m_gles; } void GLPlatform::cleanup() { delete s_platform; s_platform = nullptr; } } // namespace KWin diff --git a/libkwineffects/kwinglutils.cpp b/libkwineffects/kwinglutils.cpp index e63061d2f..24a14de09 100644 --- a/libkwineffects/kwinglutils.cpp +++ b/libkwineffects/kwinglutils.cpp @@ -1,2263 +1,2333 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006-2007 Rivo Laks Copyright (C) 2010, 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwinglutils.h" // need to call GLTexturePrivate::initStatic() #include "kwingltexture_p.h" #include "kwineffects.h" #include "kwinglplatform.h" #include "logging_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_GLRENDERTARGET 0 #ifdef __GNUC__ # define likely(x) __builtin_expect(!!(x), 1) # define unlikely(x) __builtin_expect(!!(x), 0) #else # define likely(x) (x) # define unlikely(x) (x) #endif namespace KWin { // Variables // List of all supported GL extensions static QList glExtensions; // Functions void initGL(std::function resolveFunction) { // Get list of supported OpenGL extensions if (hasGLVersion(3, 0)) { int count; glGetIntegerv(GL_NUM_EXTENSIONS, &count); for (int i = 0; i < count; i++) { const QByteArray name = (const char *) glGetStringi(GL_EXTENSIONS, i); glExtensions << name; } } else glExtensions = QByteArray((const char*)glGetString(GL_EXTENSIONS)).split(' '); // handle OpenGL extensions functions glResolveFunctions(resolveFunction); GLTexturePrivate::initStatic(); GLRenderTarget::initStatic(); GLVertexBuffer::initStatic(); } void cleanupGL() { ShaderManager::cleanup(); GLTexturePrivate::cleanup(); GLRenderTarget::cleanup(); GLVertexBuffer::cleanup(); GLPlatform::cleanup(); glExtensions.clear(); } bool hasGLVersion(int major, int minor, int release) { return GLPlatform::instance()->glVersion() >= kVersionNumber(major, minor, release); } bool hasGLExtension(const QByteArray &extension) { return glExtensions.contains(extension); } QList openGLExtensions() { return glExtensions; } static QString formatGLError(GLenum err) { switch(err) { case GL_NO_ERROR: return QStringLiteral("GL_NO_ERROR"); case GL_INVALID_ENUM: return QStringLiteral("GL_INVALID_ENUM"); case GL_INVALID_VALUE: return QStringLiteral("GL_INVALID_VALUE"); case GL_INVALID_OPERATION: return QStringLiteral("GL_INVALID_OPERATION"); case GL_STACK_OVERFLOW: return QStringLiteral("GL_STACK_OVERFLOW"); case GL_STACK_UNDERFLOW: return QStringLiteral("GL_STACK_UNDERFLOW"); case GL_OUT_OF_MEMORY: return QStringLiteral("GL_OUT_OF_MEMORY"); default: return QLatin1String("0x") + QString::number(err, 16); } } bool checkGLError(const char* txt) { GLenum err = glGetError(); if (err == GL_CONTEXT_LOST) { qCWarning(LIBKWINGLUTILS) << "GL error: context lost"; return true; } bool hasError = false; while (err != GL_NO_ERROR) { qCWarning(LIBKWINGLUTILS) << "GL error (" << txt << "): " << formatGLError(err); hasError = true; err = glGetError(); if (err == GL_CONTEXT_LOST) { qCWarning(LIBKWINGLUTILS) << "GL error: context lost"; break; } } return hasError; } //**************************************** // GLShader //**************************************** GLShader::GLShader(unsigned int flags) : mValid(false) , mLocationsResolved(false) , mExplicitLinking(flags & ExplicitLinking) { mProgram = glCreateProgram(); } GLShader::GLShader(const QString& vertexfile, const QString& fragmentfile, unsigned int flags) : mValid(false) , mLocationsResolved(false) , mExplicitLinking(flags & ExplicitLinking) { mProgram = glCreateProgram(); loadFromFiles(vertexfile, fragmentfile); } GLShader::~GLShader() { if (mProgram) { glDeleteProgram(mProgram); } } bool GLShader::loadFromFiles(const QString &vertexFile, const QString &fragmentFile) { QFile vf(vertexFile); if (!vf.open(QIODevice::ReadOnly)) { qCCritical(LIBKWINGLUTILS) << "Couldn't open" << vertexFile << "for reading!"; return false; } const QByteArray vertexSource = vf.readAll(); QFile ff(fragmentFile); if (!ff.open(QIODevice::ReadOnly)) { qCCritical(LIBKWINGLUTILS) << "Couldn't open" << fragmentFile << "for reading!"; return false; } const QByteArray fragmentSource = ff.readAll(); return load(vertexSource, fragmentSource); } bool GLShader::link() { // Be optimistic mValid = true; glLinkProgram(mProgram); // Get the program info log int maxLength, length; glGetProgramiv(mProgram, GL_INFO_LOG_LENGTH, &maxLength); QByteArray log(maxLength, 0); glGetProgramInfoLog(mProgram, maxLength, &length, log.data()); // Make sure the program linked successfully int status; glGetProgramiv(mProgram, GL_LINK_STATUS, &status); if (status == 0) { qCCritical(LIBKWINGLUTILS) << "Failed to link shader:" << endl << log; mValid = false; } else if (length > 0) { qCDebug(LIBKWINGLUTILS) << "Shader link log:" << log; } return mValid; } const QByteArray GLShader::prepareSource(GLenum shaderType, const QByteArray &source) const { Q_UNUSED(shaderType) // Prepare the source code QByteArray ba; if (GLPlatform::instance()->isGLES() && GLPlatform::instance()->glslVersion() < kVersionNumber(3, 0)) { ba.append("precision highp float;\n"); } if (ShaderManager::instance()->isShaderDebug()) { ba.append("#define KWIN_SHADER_DEBUG 1\n"); } ba.append(source); if (GLPlatform::instance()->isGLES() && GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0)) { ba.replace("#version 140", "#version 300 es\n\nprecision highp float;\n"); } return ba; } bool GLShader::compile(GLuint program, GLenum shaderType, const QByteArray &source) const { GLuint shader = glCreateShader(shaderType); QByteArray preparedSource = prepareSource(shaderType, source); const char* src = preparedSource.constData(); glShaderSource(shader, 1, &src, nullptr); // Compile the shader glCompileShader(shader); // Get the shader info log int maxLength, length; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); QByteArray log(maxLength, 0); glGetShaderInfoLog(shader, maxLength, &length, log.data()); // Check the status int status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status == 0) { const char *typeName = (shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment"); qCCritical(LIBKWINGLUTILS) << "Failed to compile" << typeName << "shader:" << endl << log; } else if (length > 0) qCDebug(LIBKWINGLUTILS) << "Shader compile log:" << log; if (status != 0) glAttachShader(program, shader); glDeleteShader(shader); return status != 0; } bool GLShader::load(const QByteArray &vertexSource, const QByteArray &fragmentSource) { // Make sure shaders are actually supported if (!(GLPlatform::instance()->supports(GLSL) && // we lack shader branching for Texture2DRectangle everywhere - and it's probably not worth it GLPlatform::instance()->supports(TextureNPOT))) { qCCritical(LIBKWINGLUTILS) << "Shaders are not supported"; return false; } mValid = false; // Compile the vertex shader if (!vertexSource.isEmpty()) { bool success = compile(mProgram, GL_VERTEX_SHADER, vertexSource); if (!success) return false; } // Compile the fragment shader if (!fragmentSource.isEmpty()) { bool success = compile(mProgram, GL_FRAGMENT_SHADER, fragmentSource); if (!success) return false; } if (mExplicitLinking) return true; // link() sets mValid return link(); } void GLShader::bindAttributeLocation(const char *name, int index) { glBindAttribLocation(mProgram, index, name); } void GLShader::bindFragDataLocation(const char *name, int index) { if (!GLPlatform::instance()->isGLES() && (hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_EXT_gpu_shader4")))) glBindFragDataLocation(mProgram, index, name); } void GLShader::bind() { glUseProgram(mProgram); } void GLShader::unbind() { glUseProgram(0); } void GLShader::resolveLocations() { if (mLocationsResolved) return; mMatrixLocation[TextureMatrix] = uniformLocation("textureMatrix"); mMatrixLocation[ProjectionMatrix] = uniformLocation("projection"); mMatrixLocation[ModelViewMatrix] = uniformLocation("modelview"); mMatrixLocation[ModelViewProjectionMatrix] = uniformLocation("modelViewProjectionMatrix"); mMatrixLocation[WindowTransformation] = uniformLocation("windowTransformation"); mMatrixLocation[ScreenTransformation] = uniformLocation("screenTransformation"); mVec2Location[Offset] = uniformLocation("offset"); mVec4Location[ModulationConstant] = uniformLocation("modulation"); mFloatLocation[Saturation] = uniformLocation("saturation"); mColorLocation[Color] = uniformLocation("geometryColor"); mLocationsResolved = true; } int GLShader::uniformLocation(const char *name) { const int location = glGetUniformLocation(mProgram, name); return location; } bool GLShader::setUniform(GLShader::MatrixUniform uniform, const QMatrix4x4 &matrix) { resolveLocations(); return setUniform(mMatrixLocation[uniform], matrix); } bool GLShader::setUniform(GLShader::Vec2Uniform uniform, const QVector2D &value) { resolveLocations(); return setUniform(mVec2Location[uniform], value); } bool GLShader::setUniform(GLShader::Vec4Uniform uniform, const QVector4D &value) { resolveLocations(); return setUniform(mVec4Location[uniform], value); } bool GLShader::setUniform(GLShader::FloatUniform uniform, float value) { resolveLocations(); return setUniform(mFloatLocation[uniform], value); } bool GLShader::setUniform(GLShader::IntUniform uniform, int value) { resolveLocations(); return setUniform(mIntLocation[uniform], value); } bool GLShader::setUniform(GLShader::ColorUniform uniform, const QVector4D &value) { resolveLocations(); return setUniform(mColorLocation[uniform], value); } bool GLShader::setUniform(GLShader::ColorUniform uniform, const QColor &value) { resolveLocations(); return setUniform(mColorLocation[uniform], value); } bool GLShader::setUniform(const char *name, float value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, int value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QVector2D& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QVector3D& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QVector4D& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QMatrix4x4& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QColor& color) { const int location = uniformLocation(name); return setUniform(location, color); } bool GLShader::setUniform(int location, float value) { if (location >= 0) { glUniform1f(location, value); } return (location >= 0); } bool GLShader::setUniform(int location, int value) { if (location >= 0) { glUniform1i(location, value); } return (location >= 0); } bool GLShader::setUniform(int location, const QVector2D &value) { if (location >= 0) { glUniform2fv(location, 1, (const GLfloat*)&value); } return (location >= 0); } bool GLShader::setUniform(int location, const QVector3D &value) { if (location >= 0) { glUniform3fv(location, 1, (const GLfloat*)&value); } return (location >= 0); } bool GLShader::setUniform(int location, const QVector4D &value) { if (location >= 0) { glUniform4fv(location, 1, (const GLfloat*)&value); } return (location >= 0); } bool GLShader::setUniform(int location, const QMatrix4x4 &value) { if (location >= 0) { GLfloat m[16]; const auto *data = value.constData(); // i is column, j is row for m for (int i = 0; i < 16; ++i) { m[i] = data[i]; } glUniformMatrix4fv(location, 1, GL_FALSE, m); } return (location >= 0); } bool GLShader::setUniform(int location, const QColor &color) { if (location >= 0) { glUniform4f(location, color.redF(), color.greenF(), color.blueF(), color.alphaF()); } return (location >= 0); } int GLShader::attributeLocation(const char* name) { int location = glGetAttribLocation(mProgram, name); return location; } bool GLShader::setAttribute(const char* name, float value) { int location = attributeLocation(name); if (location >= 0) { glVertexAttrib1f(location, value); } return (location >= 0); } QMatrix4x4 GLShader::getUniformMatrix4x4(const char* name) { int location = uniformLocation(name); if (location >= 0) { GLfloat m[16]; glGetnUniformfv(mProgram, location, sizeof(m), m); QMatrix4x4 matrix(m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]); matrix.optimize(); return matrix; } else { return QMatrix4x4(); } } //**************************************** // ShaderManager //**************************************** ShaderManager *ShaderManager::s_shaderManager = nullptr; ShaderManager *ShaderManager::instance() { if (!s_shaderManager) { s_shaderManager = new ShaderManager(); } return s_shaderManager; } void ShaderManager::cleanup() { delete s_shaderManager; s_shaderManager = nullptr; } ShaderManager::ShaderManager() { m_debug = qstrcmp(qgetenv("KWIN_GL_DEBUG"), "1") == 0; const qint64 coreVersionNumber = GLPlatform::instance()->isGLES() ? kVersionNumber(3, 0) : kVersionNumber(1, 40); if (GLPlatform::instance()->glslVersion() >= coreVersionNumber) { m_resourcePath = QStringLiteral(":/effect-shaders-1.40/"); } else { m_resourcePath = QStringLiteral(":/effect-shaders-1.10/"); } } ShaderManager::~ShaderManager() { while (!m_boundShaders.isEmpty()) { popShader(); } qDeleteAll(m_shaderHash); m_shaderHash.clear(); } static bool fuzzyCompare(const QVector4D &lhs, const QVector4D &rhs) { const float epsilon = 1.0f / 255.0f; return lhs[0] >= rhs[0] - epsilon && lhs[0] <= rhs[0] + epsilon && lhs[1] >= rhs[1] - epsilon && lhs[1] <= rhs[1] + epsilon && lhs[2] >= rhs[2] - epsilon && lhs[2] <= rhs[2] + epsilon && lhs[3] >= rhs[3] - epsilon && lhs[3] <= rhs[3] + epsilon; } static bool checkPixel(int x, int y, const QVector4D &expected, const char *file, int line) { uint8_t data[4]; glReadnPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, 4, data); const QVector4D pixel{data[0] / 255.f, data[1] / 255.f, data[2] / 255.f, data[3] / 255.f}; if (fuzzyCompare(pixel, expected)) return true; QMessageLogger(file, line, nullptr).warning() << "Pixel was" << pixel << "expected" << expected; return false; } #define CHECK_PIXEL(x, y, expected) \ checkPixel(x, y, expected, __FILE__, __LINE__) static QVector4D adjustSaturation(const QVector4D &color, float saturation) { const float gray = QVector3D::dotProduct(color.toVector3D(), {0.2126, 0.7152, 0.0722}); return QVector4D{gray, gray, gray, color.w()} * (1.0f - saturation) + color * saturation; } bool ShaderManager::selfTest() { bool pass = true; if (!GLRenderTarget::supported()) { qCWarning(LIBKWINGLUTILS) << "Framebuffer objects not supported - skipping shader tests"; return true; } if (GLPlatform::instance()->isNvidia() && GLPlatform::instance()->glRendererString().contains("Quadro")) { qCWarning(LIBKWINGLUTILS) << "Skipping self test as it is reported to return false positive results on Quadro hardware"; return true; } if (GLPlatform::instance()->isMesaDriver() && GLPlatform::instance()->mesaVersion() >= kVersionNumber(17, 0)) { qCWarning(LIBKWINGLUTILS) << "Skipping self test as it is reported to return false positive results on Mesa drivers"; return true; } // Create the source texture QImage image(2, 2, QImage::Format_ARGB32_Premultiplied); image.setPixel(0, 0, 0xffff0000); // Red image.setPixel(1, 0, 0xff00ff00); // Green image.setPixel(0, 1, 0xff0000ff); // Blue image.setPixel(1, 1, 0xffffffff); // White GLTexture src(image); src.setFilter(GL_NEAREST); // Create the render target GLTexture dst(GL_RGBA8, 32, 32); GLRenderTarget fbo(dst); GLRenderTarget::pushRenderTarget(&fbo); // Set up the vertex buffer GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); const GLVertexAttrib attribs[] { { VA_Position, 2, GL_FLOAT, offsetof(GLVertex2D, position) }, { VA_TexCoord, 2, GL_FLOAT, offsetof(GLVertex2D, texcoord) }, }; vbo->setAttribLayout(attribs, 2, sizeof(GLVertex2D)); GLVertex2D *verts = (GLVertex2D*) vbo->map(6 * sizeof(GLVertex2D)); verts[0] = GLVertex2D{{0, 0}, {0, 0}}; // Top left verts[1] = GLVertex2D{{0, 32}, {0, 1}}; // Bottom left verts[2] = GLVertex2D{{32, 0}, {1, 0}}; // Top right verts[3] = GLVertex2D{{32, 0}, {1, 0}}; // Top right verts[4] = GLVertex2D{{0, 32}, {0, 1}}; // Bottom left verts[5] = GLVertex2D{{32, 32}, {1, 1}}; // Bottom right vbo->unmap(); vbo->bindArrays(); glViewport(0, 0, 32, 32); glClearColor(0, 0, 0, 0); // Set up the projection matrix QMatrix4x4 matrix; matrix.ortho(QRect(0, 0, 32, 32)); // Bind the source texture src.bind(); const QVector4D red {1.0f, 0.0f, 0.0f, 1.0f}; const QVector4D green {0.0f, 1.0f, 0.0f, 1.0f}; const QVector4D blue {0.0f, 0.0f, 1.0f, 1.0f}; const QVector4D white {1.0f, 1.0f, 1.0f, 1.0f}; // Note: To see the line number in error messages, set // QT_MESSAGE_PATTERN="%{message} (%{file}:%{line})" // Test solid color GLShader *shader = pushShader(ShaderTrait::UniformColor); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::Color, green); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, green) && pass; pass = CHECK_PIXEL(24, 24, green) && pass; pass = CHECK_PIXEL(8, 8, green) && pass; pass = CHECK_PIXEL(24, 8, green) && pass; } else { pass = false; } popShader(); // Test texture mapping shader = pushShader(ShaderTrait::MapTexture); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, red) && pass; pass = CHECK_PIXEL(24, 24, green) && pass; pass = CHECK_PIXEL(8, 8, blue) && pass; pass = CHECK_PIXEL(24, 8, white) && pass; } else { pass = false; } popShader(); // Test saturation filter shader = pushShader(ShaderTrait::MapTexture | ShaderTrait::AdjustSaturation); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); const float saturation = .3; shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::Saturation, saturation); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, adjustSaturation(red, saturation)) && pass; pass = CHECK_PIXEL(24, 24, adjustSaturation(green, saturation)) && pass; pass = CHECK_PIXEL(8, 8, adjustSaturation(blue, saturation)) && pass; pass = CHECK_PIXEL(24, 8, adjustSaturation(white, saturation)) && pass; } else { pass = false; } popShader(); // Test modulation filter shader = pushShader(ShaderTrait::MapTexture | ShaderTrait::Modulate); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); const QVector4D modulation{.3f, .4f, .5f, .6f}; shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::ModulationConstant, modulation); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, red * modulation) && pass; pass = CHECK_PIXEL(24, 24, green * modulation) && pass; pass = CHECK_PIXEL(8, 8, blue * modulation) && pass; pass = CHECK_PIXEL(24, 8, white * modulation) && pass; } else { pass = false; } popShader(); // Test saturation + modulation shader = pushShader(ShaderTrait::MapTexture | ShaderTrait::AdjustSaturation | ShaderTrait::Modulate); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); const QVector4D modulation{.3f, .4f, .5f, .6f}; const float saturation = .3; shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::ModulationConstant, modulation); shader->setUniform(GLShader::Saturation, saturation); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, adjustSaturation(red * modulation, saturation)) && pass; pass = CHECK_PIXEL(24, 24, adjustSaturation(green * modulation, saturation)) && pass; pass = CHECK_PIXEL(8, 8, adjustSaturation(blue * modulation, saturation)) && pass; pass = CHECK_PIXEL(24, 8, adjustSaturation(white * modulation, saturation)) && pass; } else { pass = false; } popShader(); vbo->unbindArrays(); GLRenderTarget::popRenderTarget(); return pass; } QByteArray ShaderManager::generateVertexSource(ShaderTraits traits) const { QByteArray source; QTextStream stream(&source); GLPlatform * const gl = GLPlatform::instance(); QByteArray attribute, varying; if (!gl->isGLES()) { const bool glsl_140 = gl->glslVersion() >= kVersionNumber(1, 40); attribute = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute"); varying = glsl_140 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying"); if (glsl_140) stream << "#version 140\n\n"; } else { const bool glsl_es_300 = gl->glslVersion() >= kVersionNumber(3, 0); attribute = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute"); varying = glsl_es_300 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying"); if (glsl_es_300) stream << "#version 300 es\n\n"; } stream << attribute << " vec4 position;\n"; if (traits & ShaderTrait::MapTexture) { stream << attribute << " vec4 texcoord;\n\n"; stream << varying << " vec2 texcoord0;\n\n"; } else stream << "\n"; stream << "uniform mat4 modelViewProjectionMatrix;\n\n"; stream << "void main()\n{\n"; if (traits & ShaderTrait::MapTexture) stream << " texcoord0 = texcoord.st;\n"; stream << " gl_Position = modelViewProjectionMatrix * position;\n"; stream << "}\n"; stream.flush(); return source; } QByteArray ShaderManager::generateFragmentSource(ShaderTraits traits) const { QByteArray source; QTextStream stream(&source); GLPlatform * const gl = GLPlatform::instance(); QByteArray varying, output, textureLookup; if (!gl->isGLES()) { const bool glsl_140 = gl->glslVersion() >= kVersionNumber(1, 40); if (glsl_140) stream << "#version 140\n\n"; varying = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying"); textureLookup = glsl_140 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D"); output = glsl_140 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor"); } else { const bool glsl_es_300 = GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0); if (glsl_es_300) stream << "#version 300 es\n\n"; // From the GLSL ES specification: // // "The fragment language has no default precision qualifier for floating point types." stream << "precision highp float;\n\n"; varying = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying"); textureLookup = glsl_es_300 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D"); output = glsl_es_300 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor"); } if (traits & ShaderTrait::MapTexture) { stream << "uniform sampler2D sampler;\n"; if (traits & ShaderTrait::Modulate) stream << "uniform vec4 modulation;\n"; if (traits & ShaderTrait::AdjustSaturation) stream << "uniform float saturation;\n"; stream << "\n" << varying << " vec2 texcoord0;\n"; } else if (traits & ShaderTrait::UniformColor) stream << "uniform vec4 geometryColor;\n"; if (output != QByteArrayLiteral("gl_FragColor")) stream << "\nout vec4 " << output << ";\n"; stream << "\nvoid main(void)\n{\n"; if (traits & ShaderTrait::MapTexture) { if (traits & (ShaderTrait::Modulate | ShaderTrait::AdjustSaturation)) { stream << " vec4 texel = " << textureLookup << "(sampler, texcoord0);\n"; if (traits & ShaderTrait::Modulate) stream << " texel *= modulation;\n"; if (traits & ShaderTrait::AdjustSaturation) stream << " texel.rgb = mix(vec3(dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722))), texel.rgb, saturation);\n"; stream << " " << output << " = texel;\n"; } else { stream << " " << output << " = " << textureLookup << "(sampler, texcoord0);\n"; } } else if (traits & ShaderTrait::UniformColor) stream << " " << output << " = geometryColor;\n"; stream << "}"; stream.flush(); return source; } GLShader *ShaderManager::generateShader(ShaderTraits traits) { return generateCustomShader(traits); } GLShader *ShaderManager::generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource, const QByteArray &fragmentSource) { const QByteArray vertex = vertexSource.isEmpty() ? generateVertexSource(traits) : vertexSource; const QByteArray fragment = fragmentSource.isEmpty() ? generateFragmentSource(traits) : fragmentSource; #if 0 qCDebug(LIBKWINGLUTILS) << "**************"; qCDebug(LIBKWINGLUTILS) << vertex; qCDebug(LIBKWINGLUTILS) << "**************"; qCDebug(LIBKWINGLUTILS) << fragment; qCDebug(LIBKWINGLUTILS) << "**************"; #endif GLShader *shader = new GLShader(GLShader::ExplicitLinking); shader->load(vertex, fragment); shader->bindAttributeLocation("position", VA_Position); shader->bindAttributeLocation("texcoord", VA_TexCoord); shader->bindFragDataLocation("fragColor", 0); shader->link(); return shader; } GLShader *ShaderManager::generateShaderFromResources(ShaderTraits traits, const QString &vertexFile, const QString &fragmentFile) { auto loadShaderFile = [this] (const QString &fileName) { QFile file(m_resourcePath + fileName); if (file.open(QIODevice::ReadOnly)) { return file.readAll(); } qCCritical(LIBKWINGLUTILS) << "Failed to read shader " << fileName; return QByteArray(); }; QByteArray vertexSource; QByteArray fragmentSource; if (!vertexFile.isEmpty()) { vertexSource = loadShaderFile(vertexFile); if (vertexSource.isEmpty()) { return new GLShader(); } } if (!fragmentFile.isEmpty()) { fragmentSource = loadShaderFile(fragmentFile); if (fragmentSource.isEmpty()) { return new GLShader(); } } return generateCustomShader(traits, vertexSource, fragmentSource); } GLShader *ShaderManager::shader(ShaderTraits traits) { GLShader *shader = m_shaderHash.value(traits); if (!shader) { shader = generateShader(traits); m_shaderHash.insert(traits, shader); } return shader; } GLShader *ShaderManager::getBoundShader() const { if (m_boundShaders.isEmpty()) { return nullptr; } else { return m_boundShaders.top(); } } bool ShaderManager::isShaderBound() const { return !m_boundShaders.isEmpty(); } bool ShaderManager::isShaderDebug() const { return m_debug; } GLShader *ShaderManager::pushShader(ShaderTraits traits) { GLShader *shader = this->shader(traits); pushShader(shader); return shader; } void ShaderManager::pushShader(GLShader *shader) { // only bind shader if it is not already bound if (shader != getBoundShader()) { shader->bind(); } m_boundShaders.push(shader); } void ShaderManager::popShader() { if (m_boundShaders.isEmpty()) { return; } GLShader *shader = m_boundShaders.pop(); if (m_boundShaders.isEmpty()) { // no more shader bound - unbind shader->unbind(); } else if (shader != m_boundShaders.top()) { // only rebind if a different shader is on top of stack m_boundShaders.top()->bind(); } } void ShaderManager::bindFragDataLocations(GLShader *shader) { shader->bindFragDataLocation("fragColor", 0); } void ShaderManager::bindAttributeLocations(GLShader *shader) const { shader->bindAttributeLocation("vertex", VA_Position); shader->bindAttributeLocation("texCoord", VA_TexCoord); } GLShader *ShaderManager::loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource) { GLShader *shader = new GLShader(GLShader::ExplicitLinking); shader->load(vertexSource, fragmentSource); bindAttributeLocations(shader); bindFragDataLocations(shader); shader->link(); return shader; } /*** GLRenderTarget ***/ bool GLRenderTarget::sSupported = false; bool GLRenderTarget::s_blitSupported = false; QStack GLRenderTarget::s_renderTargets = QStack(); QSize GLRenderTarget::s_virtualScreenSize; QRect GLRenderTarget::s_virtualScreenGeometry; qreal GLRenderTarget::s_virtualScreenScale = 1.0; GLint GLRenderTarget::s_virtualScreenViewport[4]; void GLRenderTarget::initStatic() { if (GLPlatform::instance()->isGLES()) { sSupported = true; s_blitSupported = hasGLVersion(3, 0); } else { sSupported = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_framebuffer_object")); s_blitSupported = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_framebuffer_blit")); } } void GLRenderTarget::cleanup() { Q_ASSERT(s_renderTargets.isEmpty()); sSupported = false; s_blitSupported = false; } bool GLRenderTarget::isRenderTargetBound() { return !s_renderTargets.isEmpty(); } bool GLRenderTarget::blitSupported() { return s_blitSupported; } void GLRenderTarget::pushRenderTarget(GLRenderTarget* target) { if (s_renderTargets.isEmpty()) { glGetIntegerv(GL_VIEWPORT, s_virtualScreenViewport); } target->enable(); s_renderTargets.push(target); } +void GLRenderTarget::pushRenderTargets(QStack targets) +{ + if (s_renderTargets.isEmpty()) { + glGetIntegerv(GL_VIEWPORT, s_virtualScreenViewport); + s_renderTargets = targets; + } else { + s_renderTargets.reserve(s_renderTargets.size() + targets.size()); + + /* + * Merging the two stacks. + * We cheat a little bit by using the inherited QVector functions. + * This is to not have the targets stack in reverse order without + * having to use a helper QStack first to reverse the order. + */ + while (!targets.isEmpty()) { + s_renderTargets.push(targets.first()); + targets.removeFirst(); + } + } + + s_renderTargets.top()->enable(); +} + GLRenderTarget* GLRenderTarget::popRenderTarget() { GLRenderTarget* ret = s_renderTargets.pop(); - ret->disable(); + ret->setTextureDirty(); if (!s_renderTargets.isEmpty()) { s_renderTargets.top()->enable(); } else { + ret->disable(); glViewport (s_virtualScreenViewport[0], s_virtualScreenViewport[1], s_virtualScreenViewport[2], s_virtualScreenViewport[3]); } + return ret; } +GLRenderTarget::GLRenderTarget() +{ + // Reset variables + mValid = false; + mTexture = GLTexture(); +} + GLRenderTarget::GLRenderTarget(const GLTexture& color) { // Reset variables mValid = false; mTexture = color; // Make sure FBO is supported if (sSupported && !mTexture.isNull()) { initFBO(); } else qCCritical(LIBKWINGLUTILS) << "Render targets aren't supported!"; } GLRenderTarget::~GLRenderTarget() { if (mValid) { glDeleteFramebuffers(1, &mFramebuffer); } } bool GLRenderTarget::enable() { + if (!mValid) { + initFBO(); + } + if (!valid()) { qCCritical(LIBKWINGLUTILS) << "Can't enable invalid render target!"; return false; } glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); glViewport(0, 0, mTexture.width(), mTexture.height()); mTexture.setDirty(); return true; } bool GLRenderTarget::disable() { + if (!mValid) { + initFBO(); + } + if (!valid()) { qCCritical(LIBKWINGLUTILS) << "Can't disable invalid render target!"; return false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); mTexture.setDirty(); return true; } static QString formatFramebufferStatus(GLenum status) { switch(status) { case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: // An attachment is the wrong type / is invalid / has 0 width or height return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: // There are no images attached to the framebuffer return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); case GL_FRAMEBUFFER_UNSUPPORTED: // A format or the combination of formats of the attachments is unsupported return QStringLiteral("GL_FRAMEBUFFER_UNSUPPORTED"); case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: // Not all attached images have the same width and height return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT"); case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: // The color attachments don't have the same format return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT"); case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: // The attachments don't have the same number of samples return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"); case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: // The draw buffer is missing return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"); case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: // The read buffer is missing return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"); default: return QStringLiteral("Unknown (0x") + QString::number(status, 16) + QStringLiteral(")"); } } void GLRenderTarget::initFBO() { #if DEBUG_GLRENDERTARGET GLenum err = glGetError(); if (err != GL_NO_ERROR) qCCritical(LIBKWINGLUTILS) << "Error status when entering GLRenderTarget::initFBO: " << formatGLError(err); #endif glGenFramebuffers(1, &mFramebuffer); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { qCCritical(LIBKWINGLUTILS) << "glGenFramebuffers failed: " << formatGLError(err); return; } #endif glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { qCCritical(LIBKWINGLUTILS) << "glBindFramebuffer failed: " << formatGLError(err); glDeleteFramebuffers(1, &mFramebuffer); return; } #endif glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTexture.target(), mTexture.texture(), 0); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { qCCritical(LIBKWINGLUTILS) << "glFramebufferTexture2D failed: " << formatGLError(err); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &mFramebuffer); return; } #endif const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); glBindFramebuffer(GL_FRAMEBUFFER, 0); if (status != GL_FRAMEBUFFER_COMPLETE) { // We have an incomplete framebuffer, consider it invalid if (status == 0) qCCritical(LIBKWINGLUTILS) << "glCheckFramebufferStatus failed: " << formatGLError(glGetError()); else qCCritical(LIBKWINGLUTILS) << "Invalid framebuffer status: " << formatFramebufferStatus(status); glDeleteFramebuffers(1, &mFramebuffer); return; } mValid = true; } void GLRenderTarget::blitFromFramebuffer(const QRect &source, const QRect &destination, GLenum filter) { if (!GLRenderTarget::blitSupported()) { return; } + + if (!mValid) { + initFBO(); + } + GLRenderTarget::pushRenderTarget(this); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebuffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); const QRect s = source.isNull() ? s_virtualScreenGeometry : source; const QRect d = destination.isNull() ? QRect(0, 0, mTexture.width(), mTexture.height()) : destination; glBlitFramebuffer((s.x() - s_virtualScreenGeometry.x()) * s_virtualScreenScale, - (s_virtualScreenGeometry.height() - s_virtualScreenGeometry.y() - s.y() - s.height()) * s_virtualScreenScale, + (s_virtualScreenGeometry.height() - s_virtualScreenGeometry.y() + s.y() - s.height()) * s_virtualScreenScale, (s.x() - s_virtualScreenGeometry.x() + s.width()) * s_virtualScreenScale, - (s_virtualScreenGeometry.height() - s_virtualScreenGeometry.y() - s.y()) * s_virtualScreenScale, + (s_virtualScreenGeometry.height() - s_virtualScreenGeometry.y() + s.y()) * s_virtualScreenScale, d.x(), mTexture.height() - d.y() - d.height(), d.x() + d.width(), mTexture.height() - d.y(), GL_COLOR_BUFFER_BIT, filter); GLRenderTarget::popRenderTarget(); } void GLRenderTarget::attachTexture(const GLTexture& target) { - if (!mValid || mTexture.texture() == target.texture()) { + if (!mValid) { + initFBO(); + } + + if (mTexture.texture() == target.texture()) { return; } pushRenderTarget(this); mTexture = target; glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTexture.target(), mTexture.texture(), 0); popRenderTarget(); } +void GLRenderTarget::detachTexture() +{ + if (mTexture.isNull()) { + return; + } + + pushRenderTarget(this); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + mTexture.target(), 0, 0); + + popRenderTarget(); +} + // ------------------------------------------------------------------ static const uint16_t indices[] = { 1, 0, 3, 3, 2, 1, 5, 4, 7, 7, 6, 5, 9, 8, 11, 11, 10, 9, 13, 12, 15, 15, 14, 13, 17, 16, 19, 19, 18, 17, 21, 20, 23, 23, 22, 21, 25, 24, 27, 27, 26, 25, 29, 28, 31, 31, 30, 29, 33, 32, 35, 35, 34, 33, 37, 36, 39, 39, 38, 37, 41, 40, 43, 43, 42, 41, 45, 44, 47, 47, 46, 45, 49, 48, 51, 51, 50, 49, 53, 52, 55, 55, 54, 53, 57, 56, 59, 59, 58, 57, 61, 60, 63, 63, 62, 61, 65, 64, 67, 67, 66, 65, 69, 68, 71, 71, 70, 69, 73, 72, 75, 75, 74, 73, 77, 76, 79, 79, 78, 77, 81, 80, 83, 83, 82, 81, 85, 84, 87, 87, 86, 85, 89, 88, 91, 91, 90, 89, 93, 92, 95, 95, 94, 93, 97, 96, 99, 99, 98, 97, 101, 100, 103, 103, 102, 101, 105, 104, 107, 107, 106, 105, 109, 108, 111, 111, 110, 109, 113, 112, 115, 115, 114, 113, 117, 116, 119, 119, 118, 117, 121, 120, 123, 123, 122, 121, 125, 124, 127, 127, 126, 125, 129, 128, 131, 131, 130, 129, 133, 132, 135, 135, 134, 133, 137, 136, 139, 139, 138, 137, 141, 140, 143, 143, 142, 141, 145, 144, 147, 147, 146, 145, 149, 148, 151, 151, 150, 149, 153, 152, 155, 155, 154, 153, 157, 156, 159, 159, 158, 157, 161, 160, 163, 163, 162, 161, 165, 164, 167, 167, 166, 165, 169, 168, 171, 171, 170, 169, 173, 172, 175, 175, 174, 173, 177, 176, 179, 179, 178, 177, 181, 180, 183, 183, 182, 181, 185, 184, 187, 187, 186, 185, 189, 188, 191, 191, 190, 189, 193, 192, 195, 195, 194, 193, 197, 196, 199, 199, 198, 197, 201, 200, 203, 203, 202, 201, 205, 204, 207, 207, 206, 205, 209, 208, 211, 211, 210, 209, 213, 212, 215, 215, 214, 213, 217, 216, 219, 219, 218, 217, 221, 220, 223, 223, 222, 221, 225, 224, 227, 227, 226, 225, 229, 228, 231, 231, 230, 229, 233, 232, 235, 235, 234, 233, 237, 236, 239, 239, 238, 237, 241, 240, 243, 243, 242, 241, 245, 244, 247, 247, 246, 245, 249, 248, 251, 251, 250, 249, 253, 252, 255, 255, 254, 253, 257, 256, 259, 259, 258, 257, 261, 260, 263, 263, 262, 261, 265, 264, 267, 267, 266, 265, 269, 268, 271, 271, 270, 269, 273, 272, 275, 275, 274, 273, 277, 276, 279, 279, 278, 277, 281, 280, 283, 283, 282, 281, 285, 284, 287, 287, 286, 285, 289, 288, 291, 291, 290, 289, 293, 292, 295, 295, 294, 293, 297, 296, 299, 299, 298, 297, 301, 300, 303, 303, 302, 301, 305, 304, 307, 307, 306, 305, 309, 308, 311, 311, 310, 309, 313, 312, 315, 315, 314, 313, 317, 316, 319, 319, 318, 317, 321, 320, 323, 323, 322, 321, 325, 324, 327, 327, 326, 325, 329, 328, 331, 331, 330, 329, 333, 332, 335, 335, 334, 333, 337, 336, 339, 339, 338, 337, 341, 340, 343, 343, 342, 341, 345, 344, 347, 347, 346, 345, 349, 348, 351, 351, 350, 349, 353, 352, 355, 355, 354, 353, 357, 356, 359, 359, 358, 357, 361, 360, 363, 363, 362, 361, 365, 364, 367, 367, 366, 365, 369, 368, 371, 371, 370, 369, 373, 372, 375, 375, 374, 373, 377, 376, 379, 379, 378, 377, 381, 380, 383, 383, 382, 381, 385, 384, 387, 387, 386, 385, 389, 388, 391, 391, 390, 389, 393, 392, 395, 395, 394, 393, 397, 396, 399, 399, 398, 397, 401, 400, 403, 403, 402, 401, 405, 404, 407, 407, 406, 405, 409, 408, 411, 411, 410, 409, 413, 412, 415, 415, 414, 413, 417, 416, 419, 419, 418, 417, 421, 420, 423, 423, 422, 421, 425, 424, 427, 427, 426, 425, 429, 428, 431, 431, 430, 429, 433, 432, 435, 435, 434, 433, 437, 436, 439, 439, 438, 437, 441, 440, 443, 443, 442, 441, 445, 444, 447, 447, 446, 445, 449, 448, 451, 451, 450, 449, 453, 452, 455, 455, 454, 453, 457, 456, 459, 459, 458, 457, 461, 460, 463, 463, 462, 461, 465, 464, 467, 467, 466, 465, 469, 468, 471, 471, 470, 469, 473, 472, 475, 475, 474, 473, 477, 476, 479, 479, 478, 477, 481, 480, 483, 483, 482, 481, 485, 484, 487, 487, 486, 485, 489, 488, 491, 491, 490, 489, 493, 492, 495, 495, 494, 493, 497, 496, 499, 499, 498, 497, 501, 500, 503, 503, 502, 501, 505, 504, 507, 507, 506, 505, 509, 508, 511, 511, 510, 509, 513, 512, 515, 515, 514, 513, 517, 516, 519, 519, 518, 517, 521, 520, 523, 523, 522, 521, 525, 524, 527, 527, 526, 525, 529, 528, 531, 531, 530, 529, 533, 532, 535, 535, 534, 533, 537, 536, 539, 539, 538, 537, 541, 540, 543, 543, 542, 541, 545, 544, 547, 547, 546, 545, 549, 548, 551, 551, 550, 549, 553, 552, 555, 555, 554, 553, 557, 556, 559, 559, 558, 557, 561, 560, 563, 563, 562, 561, 565, 564, 567, 567, 566, 565, 569, 568, 571, 571, 570, 569, 573, 572, 575, 575, 574, 573, 577, 576, 579, 579, 578, 577, 581, 580, 583, 583, 582, 581, 585, 584, 587, 587, 586, 585, 589, 588, 591, 591, 590, 589, 593, 592, 595, 595, 594, 593, 597, 596, 599, 599, 598, 597, 601, 600, 603, 603, 602, 601, 605, 604, 607, 607, 606, 605, 609, 608, 611, 611, 610, 609, 613, 612, 615, 615, 614, 613, 617, 616, 619, 619, 618, 617, 621, 620, 623, 623, 622, 621, 625, 624, 627, 627, 626, 625, 629, 628, 631, 631, 630, 629, 633, 632, 635, 635, 634, 633, 637, 636, 639, 639, 638, 637, 641, 640, 643, 643, 642, 641, 645, 644, 647, 647, 646, 645, 649, 648, 651, 651, 650, 649, 653, 652, 655, 655, 654, 653, 657, 656, 659, 659, 658, 657, 661, 660, 663, 663, 662, 661, 665, 664, 667, 667, 666, 665, 669, 668, 671, 671, 670, 669, 673, 672, 675, 675, 674, 673, 677, 676, 679, 679, 678, 677, 681, 680, 683, 683, 682, 681, 685, 684, 687, 687, 686, 685, 689, 688, 691, 691, 690, 689, 693, 692, 695, 695, 694, 693, 697, 696, 699, 699, 698, 697, 701, 700, 703, 703, 702, 701, 705, 704, 707, 707, 706, 705, 709, 708, 711, 711, 710, 709, 713, 712, 715, 715, 714, 713, 717, 716, 719, 719, 718, 717, 721, 720, 723, 723, 722, 721, 725, 724, 727, 727, 726, 725, 729, 728, 731, 731, 730, 729, 733, 732, 735, 735, 734, 733, 737, 736, 739, 739, 738, 737, 741, 740, 743, 743, 742, 741, 745, 744, 747, 747, 746, 745, 749, 748, 751, 751, 750, 749, 753, 752, 755, 755, 754, 753, 757, 756, 759, 759, 758, 757, 761, 760, 763, 763, 762, 761, 765, 764, 767, 767, 766, 765, 769, 768, 771, 771, 770, 769, 773, 772, 775, 775, 774, 773, 777, 776, 779, 779, 778, 777, 781, 780, 783, 783, 782, 781, 785, 784, 787, 787, 786, 785, 789, 788, 791, 791, 790, 789, 793, 792, 795, 795, 794, 793, 797, 796, 799, 799, 798, 797, 801, 800, 803, 803, 802, 801, 805, 804, 807, 807, 806, 805, 809, 808, 811, 811, 810, 809, 813, 812, 815, 815, 814, 813, 817, 816, 819, 819, 818, 817, 821, 820, 823, 823, 822, 821, 825, 824, 827, 827, 826, 825, 829, 828, 831, 831, 830, 829, 833, 832, 835, 835, 834, 833, 837, 836, 839, 839, 838, 837, 841, 840, 843, 843, 842, 841, 845, 844, 847, 847, 846, 845, 849, 848, 851, 851, 850, 849, 853, 852, 855, 855, 854, 853, 857, 856, 859, 859, 858, 857, 861, 860, 863, 863, 862, 861, 865, 864, 867, 867, 866, 865, 869, 868, 871, 871, 870, 869, 873, 872, 875, 875, 874, 873, 877, 876, 879, 879, 878, 877, 881, 880, 883, 883, 882, 881, 885, 884, 887, 887, 886, 885, 889, 888, 891, 891, 890, 889, 893, 892, 895, 895, 894, 893, 897, 896, 899, 899, 898, 897, 901, 900, 903, 903, 902, 901, 905, 904, 907, 907, 906, 905, 909, 908, 911, 911, 910, 909, 913, 912, 915, 915, 914, 913, 917, 916, 919, 919, 918, 917, 921, 920, 923, 923, 922, 921, 925, 924, 927, 927, 926, 925, 929, 928, 931, 931, 930, 929, 933, 932, 935, 935, 934, 933, 937, 936, 939, 939, 938, 937, 941, 940, 943, 943, 942, 941, 945, 944, 947, 947, 946, 945, 949, 948, 951, 951, 950, 949, 953, 952, 955, 955, 954, 953, 957, 956, 959, 959, 958, 957, 961, 960, 963, 963, 962, 961, 965, 964, 967, 967, 966, 965, 969, 968, 971, 971, 970, 969, 973, 972, 975, 975, 974, 973, 977, 976, 979, 979, 978, 977, 981, 980, 983, 983, 982, 981, 985, 984, 987, 987, 986, 985, 989, 988, 991, 991, 990, 989, 993, 992, 995, 995, 994, 993, 997, 996, 999, 999, 998, 997, 1001, 1000, 1003, 1003, 1002, 1001, 1005, 1004, 1007, 1007, 1006, 1005, 1009, 1008, 1011, 1011, 1010, 1009, 1013, 1012, 1015, 1015, 1014, 1013, 1017, 1016, 1019, 1019, 1018, 1017, 1021, 1020, 1023, 1023, 1022, 1021, 1025, 1024, 1027, 1027, 1026, 1025, 1029, 1028, 1031, 1031, 1030, 1029, 1033, 1032, 1035, 1035, 1034, 1033, 1037, 1036, 1039, 1039, 1038, 1037, 1041, 1040, 1043, 1043, 1042, 1041, 1045, 1044, 1047, 1047, 1046, 1045, 1049, 1048, 1051, 1051, 1050, 1049, 1053, 1052, 1055, 1055, 1054, 1053, 1057, 1056, 1059, 1059, 1058, 1057, 1061, 1060, 1063, 1063, 1062, 1061, 1065, 1064, 1067, 1067, 1066, 1065, 1069, 1068, 1071, 1071, 1070, 1069, 1073, 1072, 1075, 1075, 1074, 1073, 1077, 1076, 1079, 1079, 1078, 1077, 1081, 1080, 1083, 1083, 1082, 1081, 1085, 1084, 1087, 1087, 1086, 1085, 1089, 1088, 1091, 1091, 1090, 1089, 1093, 1092, 1095, 1095, 1094, 1093, 1097, 1096, 1099, 1099, 1098, 1097, 1101, 1100, 1103, 1103, 1102, 1101, 1105, 1104, 1107, 1107, 1106, 1105, 1109, 1108, 1111, 1111, 1110, 1109, 1113, 1112, 1115, 1115, 1114, 1113, 1117, 1116, 1119, 1119, 1118, 1117, 1121, 1120, 1123, 1123, 1122, 1121, 1125, 1124, 1127, 1127, 1126, 1125, 1129, 1128, 1131, 1131, 1130, 1129, 1133, 1132, 1135, 1135, 1134, 1133, 1137, 1136, 1139, 1139, 1138, 1137, 1141, 1140, 1143, 1143, 1142, 1141, 1145, 1144, 1147, 1147, 1146, 1145, 1149, 1148, 1151, 1151, 1150, 1149, 1153, 1152, 1155, 1155, 1154, 1153, 1157, 1156, 1159, 1159, 1158, 1157, 1161, 1160, 1163, 1163, 1162, 1161, 1165, 1164, 1167, 1167, 1166, 1165, 1169, 1168, 1171, 1171, 1170, 1169, 1173, 1172, 1175, 1175, 1174, 1173, 1177, 1176, 1179, 1179, 1178, 1177, 1181, 1180, 1183, 1183, 1182, 1181, 1185, 1184, 1187, 1187, 1186, 1185, 1189, 1188, 1191, 1191, 1190, 1189, 1193, 1192, 1195, 1195, 1194, 1193, 1197, 1196, 1199, 1199, 1198, 1197, 1201, 1200, 1203, 1203, 1202, 1201, 1205, 1204, 1207, 1207, 1206, 1205, 1209, 1208, 1211, 1211, 1210, 1209, 1213, 1212, 1215, 1215, 1214, 1213, 1217, 1216, 1219, 1219, 1218, 1217, 1221, 1220, 1223, 1223, 1222, 1221, 1225, 1224, 1227, 1227, 1226, 1225, 1229, 1228, 1231, 1231, 1230, 1229, 1233, 1232, 1235, 1235, 1234, 1233, 1237, 1236, 1239, 1239, 1238, 1237, 1241, 1240, 1243, 1243, 1242, 1241, 1245, 1244, 1247, 1247, 1246, 1245, 1249, 1248, 1251, 1251, 1250, 1249, 1253, 1252, 1255, 1255, 1254, 1253, 1257, 1256, 1259, 1259, 1258, 1257, 1261, 1260, 1263, 1263, 1262, 1261, 1265, 1264, 1267, 1267, 1266, 1265, 1269, 1268, 1271, 1271, 1270, 1269, 1273, 1272, 1275, 1275, 1274, 1273, 1277, 1276, 1279, 1279, 1278, 1277, 1281, 1280, 1283, 1283, 1282, 1281, 1285, 1284, 1287, 1287, 1286, 1285, 1289, 1288, 1291, 1291, 1290, 1289, 1293, 1292, 1295, 1295, 1294, 1293, 1297, 1296, 1299, 1299, 1298, 1297, 1301, 1300, 1303, 1303, 1302, 1301, 1305, 1304, 1307, 1307, 1306, 1305, 1309, 1308, 1311, 1311, 1310, 1309, 1313, 1312, 1315, 1315, 1314, 1313, 1317, 1316, 1319, 1319, 1318, 1317, 1321, 1320, 1323, 1323, 1322, 1321, 1325, 1324, 1327, 1327, 1326, 1325, 1329, 1328, 1331, 1331, 1330, 1329, 1333, 1332, 1335, 1335, 1334, 1333, 1337, 1336, 1339, 1339, 1338, 1337, 1341, 1340, 1343, 1343, 1342, 1341, 1345, 1344, 1347, 1347, 1346, 1345, 1349, 1348, 1351, 1351, 1350, 1349, 1353, 1352, 1355, 1355, 1354, 1353, 1357, 1356, 1359, 1359, 1358, 1357, 1361, 1360, 1363, 1363, 1362, 1361, 1365, 1364, 1367, 1367, 1366, 1365, 1369, 1368, 1371, 1371, 1370, 1369, 1373, 1372, 1375, 1375, 1374, 1373, 1377, 1376, 1379, 1379, 1378, 1377, 1381, 1380, 1383, 1383, 1382, 1381, 1385, 1384, 1387, 1387, 1386, 1385, 1389, 1388, 1391, 1391, 1390, 1389, 1393, 1392, 1395, 1395, 1394, 1393, 1397, 1396, 1399, 1399, 1398, 1397, 1401, 1400, 1403, 1403, 1402, 1401, 1405, 1404, 1407, 1407, 1406, 1405, 1409, 1408, 1411, 1411, 1410, 1409, 1413, 1412, 1415, 1415, 1414, 1413, 1417, 1416, 1419, 1419, 1418, 1417, 1421, 1420, 1423, 1423, 1422, 1421, 1425, 1424, 1427, 1427, 1426, 1425, 1429, 1428, 1431, 1431, 1430, 1429, 1433, 1432, 1435, 1435, 1434, 1433, 1437, 1436, 1439, 1439, 1438, 1437, 1441, 1440, 1443, 1443, 1442, 1441, 1445, 1444, 1447, 1447, 1446, 1445, 1449, 1448, 1451, 1451, 1450, 1449, 1453, 1452, 1455, 1455, 1454, 1453, 1457, 1456, 1459, 1459, 1458, 1457, 1461, 1460, 1463, 1463, 1462, 1461, 1465, 1464, 1467, 1467, 1466, 1465, 1469, 1468, 1471, 1471, 1470, 1469, 1473, 1472, 1475, 1475, 1474, 1473, 1477, 1476, 1479, 1479, 1478, 1477, 1481, 1480, 1483, 1483, 1482, 1481, 1485, 1484, 1487, 1487, 1486, 1485, 1489, 1488, 1491, 1491, 1490, 1489, 1493, 1492, 1495, 1495, 1494, 1493, 1497, 1496, 1499, 1499, 1498, 1497, 1501, 1500, 1503, 1503, 1502, 1501, 1505, 1504, 1507, 1507, 1506, 1505, 1509, 1508, 1511, 1511, 1510, 1509, 1513, 1512, 1515, 1515, 1514, 1513, 1517, 1516, 1519, 1519, 1518, 1517, 1521, 1520, 1523, 1523, 1522, 1521, 1525, 1524, 1527, 1527, 1526, 1525, 1529, 1528, 1531, 1531, 1530, 1529, 1533, 1532, 1535, 1535, 1534, 1533, 1537, 1536, 1539, 1539, 1538, 1537, 1541, 1540, 1543, 1543, 1542, 1541, 1545, 1544, 1547, 1547, 1546, 1545, 1549, 1548, 1551, 1551, 1550, 1549, 1553, 1552, 1555, 1555, 1554, 1553, 1557, 1556, 1559, 1559, 1558, 1557, 1561, 1560, 1563, 1563, 1562, 1561, 1565, 1564, 1567, 1567, 1566, 1565, 1569, 1568, 1571, 1571, 1570, 1569, 1573, 1572, 1575, 1575, 1574, 1573, 1577, 1576, 1579, 1579, 1578, 1577, 1581, 1580, 1583, 1583, 1582, 1581, 1585, 1584, 1587, 1587, 1586, 1585, 1589, 1588, 1591, 1591, 1590, 1589, 1593, 1592, 1595, 1595, 1594, 1593, 1597, 1596, 1599, 1599, 1598, 1597, 1601, 1600, 1603, 1603, 1602, 1601, 1605, 1604, 1607, 1607, 1606, 1605, 1609, 1608, 1611, 1611, 1610, 1609, 1613, 1612, 1615, 1615, 1614, 1613, 1617, 1616, 1619, 1619, 1618, 1617, 1621, 1620, 1623, 1623, 1622, 1621, 1625, 1624, 1627, 1627, 1626, 1625, 1629, 1628, 1631, 1631, 1630, 1629, 1633, 1632, 1635, 1635, 1634, 1633, 1637, 1636, 1639, 1639, 1638, 1637, 1641, 1640, 1643, 1643, 1642, 1641, 1645, 1644, 1647, 1647, 1646, 1645, 1649, 1648, 1651, 1651, 1650, 1649, 1653, 1652, 1655, 1655, 1654, 1653, 1657, 1656, 1659, 1659, 1658, 1657, 1661, 1660, 1663, 1663, 1662, 1661, 1665, 1664, 1667, 1667, 1666, 1665, 1669, 1668, 1671, 1671, 1670, 1669, 1673, 1672, 1675, 1675, 1674, 1673, 1677, 1676, 1679, 1679, 1678, 1677, 1681, 1680, 1683, 1683, 1682, 1681, 1685, 1684, 1687, 1687, 1686, 1685, 1689, 1688, 1691, 1691, 1690, 1689, 1693, 1692, 1695, 1695, 1694, 1693, 1697, 1696, 1699, 1699, 1698, 1697, 1701, 1700, 1703, 1703, 1702, 1701, 1705, 1704, 1707, 1707, 1706, 1705, 1709, 1708, 1711, 1711, 1710, 1709, 1713, 1712, 1715, 1715, 1714, 1713, 1717, 1716, 1719, 1719, 1718, 1717, 1721, 1720, 1723, 1723, 1722, 1721, 1725, 1724, 1727, 1727, 1726, 1725, 1729, 1728, 1731, 1731, 1730, 1729, 1733, 1732, 1735, 1735, 1734, 1733, 1737, 1736, 1739, 1739, 1738, 1737, 1741, 1740, 1743, 1743, 1742, 1741, 1745, 1744, 1747, 1747, 1746, 1745, 1749, 1748, 1751, 1751, 1750, 1749, 1753, 1752, 1755, 1755, 1754, 1753, 1757, 1756, 1759, 1759, 1758, 1757, 1761, 1760, 1763, 1763, 1762, 1761, 1765, 1764, 1767, 1767, 1766, 1765, 1769, 1768, 1771, 1771, 1770, 1769, 1773, 1772, 1775, 1775, 1774, 1773, 1777, 1776, 1779, 1779, 1778, 1777, 1781, 1780, 1783, 1783, 1782, 1781, 1785, 1784, 1787, 1787, 1786, 1785, 1789, 1788, 1791, 1791, 1790, 1789, 1793, 1792, 1795, 1795, 1794, 1793, 1797, 1796, 1799, 1799, 1798, 1797, 1801, 1800, 1803, 1803, 1802, 1801, 1805, 1804, 1807, 1807, 1806, 1805, 1809, 1808, 1811, 1811, 1810, 1809, 1813, 1812, 1815, 1815, 1814, 1813, 1817, 1816, 1819, 1819, 1818, 1817, 1821, 1820, 1823, 1823, 1822, 1821, 1825, 1824, 1827, 1827, 1826, 1825, 1829, 1828, 1831, 1831, 1830, 1829, 1833, 1832, 1835, 1835, 1834, 1833, 1837, 1836, 1839, 1839, 1838, 1837, 1841, 1840, 1843, 1843, 1842, 1841, 1845, 1844, 1847, 1847, 1846, 1845, 1849, 1848, 1851, 1851, 1850, 1849, 1853, 1852, 1855, 1855, 1854, 1853, 1857, 1856, 1859, 1859, 1858, 1857, 1861, 1860, 1863, 1863, 1862, 1861, 1865, 1864, 1867, 1867, 1866, 1865, 1869, 1868, 1871, 1871, 1870, 1869, 1873, 1872, 1875, 1875, 1874, 1873, 1877, 1876, 1879, 1879, 1878, 1877, 1881, 1880, 1883, 1883, 1882, 1881, 1885, 1884, 1887, 1887, 1886, 1885, 1889, 1888, 1891, 1891, 1890, 1889, 1893, 1892, 1895, 1895, 1894, 1893, 1897, 1896, 1899, 1899, 1898, 1897, 1901, 1900, 1903, 1903, 1902, 1901, 1905, 1904, 1907, 1907, 1906, 1905, 1909, 1908, 1911, 1911, 1910, 1909, 1913, 1912, 1915, 1915, 1914, 1913, 1917, 1916, 1919, 1919, 1918, 1917, 1921, 1920, 1923, 1923, 1922, 1921, 1925, 1924, 1927, 1927, 1926, 1925, 1929, 1928, 1931, 1931, 1930, 1929, 1933, 1932, 1935, 1935, 1934, 1933, 1937, 1936, 1939, 1939, 1938, 1937, 1941, 1940, 1943, 1943, 1942, 1941, 1945, 1944, 1947, 1947, 1946, 1945, 1949, 1948, 1951, 1951, 1950, 1949, 1953, 1952, 1955, 1955, 1954, 1953, 1957, 1956, 1959, 1959, 1958, 1957, 1961, 1960, 1963, 1963, 1962, 1961, 1965, 1964, 1967, 1967, 1966, 1965, 1969, 1968, 1971, 1971, 1970, 1969, 1973, 1972, 1975, 1975, 1974, 1973, 1977, 1976, 1979, 1979, 1978, 1977, 1981, 1980, 1983, 1983, 1982, 1981, 1985, 1984, 1987, 1987, 1986, 1985, 1989, 1988, 1991, 1991, 1990, 1989, 1993, 1992, 1995, 1995, 1994, 1993, 1997, 1996, 1999, 1999, 1998, 1997, 2001, 2000, 2003, 2003, 2002, 2001, 2005, 2004, 2007, 2007, 2006, 2005, 2009, 2008, 2011, 2011, 2010, 2009, 2013, 2012, 2015, 2015, 2014, 2013, 2017, 2016, 2019, 2019, 2018, 2017, 2021, 2020, 2023, 2023, 2022, 2021, 2025, 2024, 2027, 2027, 2026, 2025, 2029, 2028, 2031, 2031, 2030, 2029, 2033, 2032, 2035, 2035, 2034, 2033, 2037, 2036, 2039, 2039, 2038, 2037, 2041, 2040, 2043, 2043, 2042, 2041, 2045, 2044, 2047, 2047, 2046, 2045 }; template T align(T value, int bytes) { return (value + bytes - 1) & ~T(bytes - 1); } class IndexBuffer { public: IndexBuffer(); ~IndexBuffer(); void accomodate(int count); void bind(); private: GLuint m_buffer; size_t m_size; int m_count; }; IndexBuffer::IndexBuffer() { // The maximum number of quads we can render with 16 bit indices is 16,384. // But we start with 512 and grow the buffer as needed. m_size = sizeof(indices); m_count = m_size / (6 * sizeof(uint16_t)); glGenBuffers(1, &m_buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); } IndexBuffer::~IndexBuffer() { glDeleteBuffers(1, &m_buffer); } void IndexBuffer::accomodate(int count) { // Check if we need to grow the buffer. if (count <= m_count) return; count = align(count, 128); size_t size = 6 * sizeof(uint16_t) * count; // Create a new buffer object GLuint buffer; glGenBuffers(1, &buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, nullptr, GL_STATIC_DRAW); // Use the GPU to copy the data from the old object to the new object, glBindBuffer(GL_COPY_READ_BUFFER, m_buffer); glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_ELEMENT_ARRAY_BUFFER, 0, 0, m_size); glDeleteBuffers(1, &m_buffer); glFlush(); // Needed to work around what appears to be a CP DMA issue in r600g // Map the new object and fill in the uninitialized section const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_RANGE_BIT; uint16_t *map = (uint16_t *) glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, m_size, size - m_size, access); const uint16_t index[] = { 1, 0, 3, 3, 2, 1 }; for (int i = m_count; i < count; i++) { for (int j = 0; j < 6; j++) *(map++) = i * 4 + index[j]; } glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); m_buffer = buffer; m_count = count; m_size = size; } void IndexBuffer::bind() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer); } // ------------------------------------------------------------------ class BitRef { public: BitRef(uint32_t &bitfield, int bit) : m_bitfield(bitfield), m_mask(1 << bit) {} void operator = (bool val) { if (val) m_bitfield |= m_mask; else m_bitfield &= ~m_mask; } operator bool () const { return m_bitfield & m_mask; } private: uint32_t &m_bitfield; int const m_mask; }; // ------------------------------------------------------------------ class Bitfield { public: Bitfield() : m_bitfield(0) {} Bitfield(uint32_t bits) : m_bitfield(bits) {} void set(int i) { m_bitfield |= (1 << i); } void clear(int i) { m_bitfield &= ~(1 << i); } BitRef operator [] (int i) { return BitRef(m_bitfield, i); } operator uint32_t () const { return m_bitfield; } private: uint32_t m_bitfield; }; // ------------------------------------------------------------------ class BitfieldIterator { public: BitfieldIterator(uint32_t bitfield) : m_bitfield(bitfield) {} bool hasNext() const { return m_bitfield != 0; } int next() { const int bit = ffs(m_bitfield) - 1; m_bitfield ^= (1 << bit); return bit; } private: uint32_t m_bitfield; }; // ------------------------------------------------------------------ struct VertexAttrib { int size; GLenum type; int offset; }; // ------------------------------------------------------------------ struct BufferFence { GLsync sync; intptr_t nextEnd; bool signaled() const { GLint value; glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &value); return value == GL_SIGNALED; } }; static void deleteAll(std::deque &fences) { for (const BufferFence &fence : fences) glDeleteSync(fence.sync); fences.clear(); } // ------------------------------------------------------------------ template struct FrameSizesArray { public: FrameSizesArray() { m_array.fill(0); } void push(size_t size) { m_array[m_index] = size; m_index = (m_index + 1) % Count; } size_t average() const { size_t sum = 0; for (size_t size : m_array) sum += size; return sum / Count; } private: std::array m_array; int m_index = 0; }; //********************************* // GLVertexBufferPrivate //********************************* class GLVertexBufferPrivate { public: GLVertexBufferPrivate(GLVertexBuffer::UsageHint usageHint) : vertexCount(0) , persistent(false) , useColor(false) , color(0, 0, 0, 255) , bufferSize(0) , bufferEnd(0) , mappedSize(0) , frameSize(0) , nextOffset(0) , baseAddress(0) , map(nullptr) { glGenBuffers(1, &buffer); switch(usageHint) { case GLVertexBuffer::Dynamic: usage = GL_DYNAMIC_DRAW; break; case GLVertexBuffer::Static: usage = GL_STATIC_DRAW; break; default: usage = GL_STREAM_DRAW; break; } } ~GLVertexBufferPrivate() { deleteAll(fences); if (buffer != 0) { glDeleteBuffers(1, &buffer); map = nullptr; } } void interleaveArrays(float *array, int dim, const float *vertices, const float *texcoords, int count); void bindArrays(); void unbindArrays(); void reallocateBuffer(size_t size); GLvoid *mapNextFreeRange(size_t size); void reallocatePersistentBuffer(size_t size); bool awaitFence(intptr_t offset); GLvoid *getIdleRange(size_t size); GLuint buffer; GLenum usage; int stride; int vertexCount; static GLVertexBuffer *streamingBuffer; static bool haveBufferStorage; static bool haveSyncFences; static bool hasMapBufferRange; static bool supportsIndexedQuads; QByteArray dataStore; bool persistent; bool useColor; QVector4D color; size_t bufferSize; intptr_t bufferEnd; size_t mappedSize; size_t frameSize; intptr_t nextOffset; intptr_t baseAddress; uint8_t *map; std::deque fences; FrameSizesArray<4> frameSizes; VertexAttrib attrib[VertexAttributeCount]; Bitfield enabledArrays; static IndexBuffer *s_indexBuffer; }; bool GLVertexBufferPrivate::hasMapBufferRange = false; bool GLVertexBufferPrivate::supportsIndexedQuads = false; GLVertexBuffer *GLVertexBufferPrivate::streamingBuffer = nullptr; bool GLVertexBufferPrivate::haveBufferStorage = false; bool GLVertexBufferPrivate::haveSyncFences = false; IndexBuffer *GLVertexBufferPrivate::s_indexBuffer = nullptr; void GLVertexBufferPrivate::interleaveArrays(float *dst, int dim, const float *vertices, const float *texcoords, int count) { if (!texcoords) { memcpy((void *) dst, vertices, dim * sizeof(float) * count); return; } switch (dim) { case 2: for (int i = 0; i < count; i++) { *(dst++) = *(vertices++); *(dst++) = *(vertices++); *(dst++) = *(texcoords++); *(dst++) = *(texcoords++); } break; case 3: for (int i = 0; i < count; i++) { *(dst++) = *(vertices++); *(dst++) = *(vertices++); *(dst++) = *(vertices++); *(dst++) = *(texcoords++); *(dst++) = *(texcoords++); } break; default: for (int i = 0; i < count; i++) { for (int j = 0; j < dim; j++) *(dst++) = *(vertices++); *(dst++) = *(texcoords++); *(dst++) = *(texcoords++); } } } void GLVertexBufferPrivate::bindArrays() { if (useColor) { GLShader *shader = ShaderManager::instance()->getBoundShader(); shader->setUniform(GLShader::Color, color); } glBindBuffer(GL_ARRAY_BUFFER, buffer); BitfieldIterator it(enabledArrays); while (it.hasNext()) { const int index = it.next(); glVertexAttribPointer(index, attrib[index].size, attrib[index].type, GL_FALSE, stride, (const GLvoid *) (baseAddress + attrib[index].offset)); glEnableVertexAttribArray(index); } } void GLVertexBufferPrivate::unbindArrays() { BitfieldIterator it(enabledArrays); while (it.hasNext()) glDisableVertexAttribArray(it.next()); } void GLVertexBufferPrivate::reallocatePersistentBuffer(size_t size) { if (buffer != 0) { // This also unmaps and unbinds the buffer glDeleteBuffers(1, &buffer); buffer = 0; deleteAll(fences); } if (buffer == 0) glGenBuffers(1, &buffer); // Round the size up to 64 kb size_t minSize = qMax(frameSizes.average() * 3, 128 * 1024); bufferSize = align(qMax(size, minSize), 64 * 1024); const GLbitfield storage = GL_DYNAMIC_STORAGE_BIT; const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferStorage(GL_ARRAY_BUFFER, bufferSize, nullptr, storage | access); map = (uint8_t *) glMapBufferRange(GL_ARRAY_BUFFER, 0, bufferSize, access); nextOffset = 0; bufferEnd = bufferSize; } bool GLVertexBufferPrivate::awaitFence(intptr_t end) { // Skip fences until we reach the end offset while (!fences.empty() && fences.front().nextEnd < end) { glDeleteSync(fences.front().sync); fences.pop_front(); } assert(!fences.empty()); // Wait on the next fence const BufferFence &fence = fences.front(); if (!fence.signaled()) { qCDebug(LIBKWINGLUTILS) << "Stalling on VBO fence"; const GLenum ret = glClientWaitSync(fence.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000); if (ret == GL_TIMEOUT_EXPIRED || ret == GL_WAIT_FAILED) { qCCritical(LIBKWINGLUTILS) << "Wait failed"; return false; } } glDeleteSync(fence.sync); // Update the end pointer bufferEnd = fence.nextEnd; fences.pop_front(); return true; } GLvoid *GLVertexBufferPrivate::getIdleRange(size_t size) { if (unlikely(size > bufferSize)) reallocatePersistentBuffer(size * 2); // Handle wrap-around if (unlikely(nextOffset + size > bufferSize)) { nextOffset = 0; bufferEnd -= bufferSize; for (BufferFence &fence : fences) fence.nextEnd -= bufferSize; // Emit a fence now BufferFence fence; fence.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); fence.nextEnd = bufferSize; fences.emplace_back(fence); } if (unlikely(nextOffset + intptr_t(size) > bufferEnd)) { if (!awaitFence(nextOffset + size)) return nullptr; } return map + nextOffset; } void GLVertexBufferPrivate::reallocateBuffer(size_t size) { // Round the size up to 4 Kb for streaming/dynamic buffers. const size_t minSize = 32768; // Minimum size for streaming buffers const size_t alloc = usage != GL_STATIC_DRAW ? align(qMax(size, minSize), 4096) : size; glBufferData(GL_ARRAY_BUFFER, alloc, 0, usage); bufferSize = alloc; } GLvoid *GLVertexBufferPrivate::mapNextFreeRange(size_t size) { GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT; if ((nextOffset + size) > bufferSize) { // Reallocate the data store if it's too small. if (size > bufferSize) { reallocateBuffer(size); } else { access |= GL_MAP_INVALIDATE_BUFFER_BIT; access ^= GL_MAP_UNSYNCHRONIZED_BIT; } nextOffset = 0; } return glMapBufferRange(GL_ARRAY_BUFFER, nextOffset, size, access); } //********************************* // GLVertexBuffer //********************************* QRect GLVertexBuffer::s_virtualScreenGeometry; +qreal GLVertexBuffer::s_virtualScreenScale; GLVertexBuffer::GLVertexBuffer(UsageHint hint) : d(new GLVertexBufferPrivate(hint)) { } GLVertexBuffer::~GLVertexBuffer() { delete d; } void GLVertexBuffer::setData(const void *data, size_t size) { GLvoid *ptr = map(size); memcpy(ptr, data, size); unmap(); } void GLVertexBuffer::setData(int vertexCount, int dim, const float* vertices, const float* texcoords) { const GLVertexAttrib layout[] = { { VA_Position, dim, GL_FLOAT, 0 }, { VA_TexCoord, 2, GL_FLOAT, int(dim * sizeof(float)) } }; int stride = (texcoords ? dim + 2 : dim) * sizeof(float); int attribCount = texcoords ? 2 : 1; setAttribLayout(layout, attribCount, stride); setVertexCount(vertexCount); GLvoid *ptr = map(vertexCount * stride); d->interleaveArrays((float *) ptr, dim, vertices, texcoords, vertexCount); unmap(); } GLvoid *GLVertexBuffer::map(size_t size) { d->mappedSize = size; d->frameSize += size; if (d->persistent) return d->getIdleRange(size); glBindBuffer(GL_ARRAY_BUFFER, d->buffer); bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData(); if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) return (GLvoid *) d->mapNextFreeRange(size); // If we can't map the buffer we allocate local memory to hold the // buffer data and return a pointer to it. The data will be submitted // to the actual buffer object when the user calls unmap(). if (size_t(d->dataStore.size()) < size) d->dataStore.resize(size); return (GLvoid *) d->dataStore.data(); } void GLVertexBuffer::unmap() { if (d->persistent) { d->baseAddress = d->nextOffset; d->nextOffset += align(d->mappedSize, 16); // Align to 16 bytes for SSE d->mappedSize = 0; return; } bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData(); if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) { glUnmapBuffer(GL_ARRAY_BUFFER); d->baseAddress = d->nextOffset; d->nextOffset += align(d->mappedSize, 16); // Align to 16 bytes for SSE } else { // Upload the data from local memory to the buffer object if (preferBufferSubData) { if ((d->nextOffset + d->mappedSize) > d->bufferSize) { d->reallocateBuffer(d->mappedSize); d->nextOffset = 0; } glBufferSubData(GL_ARRAY_BUFFER, d->nextOffset, d->mappedSize, d->dataStore.constData()); d->baseAddress = d->nextOffset; d->nextOffset += align(d->mappedSize, 16); // Align to 16 bytes for SSE } else { glBufferData(GL_ARRAY_BUFFER, d->mappedSize, d->dataStore.data(), d->usage); d->baseAddress = 0; } // Free the local memory buffer if it's unlikely to be used again if (d->usage == GL_STATIC_DRAW) d->dataStore = QByteArray(); } d->mappedSize = 0; } void GLVertexBuffer::setVertexCount(int count) { d->vertexCount = count; } void GLVertexBuffer::setAttribLayout(const GLVertexAttrib *attribs, int count, int stride) { // Start by disabling all arrays d->enabledArrays = 0; for (int i = 0; i < count; i++) { const int index = attribs[i].index; assert(index >= 0 && index < VertexAttributeCount); assert(!d->enabledArrays[index]); d->attrib[index].size = attribs[i].size; d->attrib[index].type = attribs[i].type; d->attrib[index].offset = attribs[i].relativeOffset; d->enabledArrays[index] = true; } d->stride = stride; } void GLVertexBuffer::render(GLenum primitiveMode) { render(infiniteRegion(), primitiveMode, false); } void GLVertexBuffer::render(const QRegion& region, GLenum primitiveMode, bool hardwareClipping) { d->bindArrays(); draw(region, primitiveMode, 0, d->vertexCount, hardwareClipping); d->unbindArrays(); } void GLVertexBuffer::bindArrays() { d->bindArrays(); } void GLVertexBuffer::unbindArrays() { d->unbindArrays(); } void GLVertexBuffer::draw(GLenum primitiveMode, int first, int count) { draw(infiniteRegion(), primitiveMode, first, count, false); } void GLVertexBuffer::draw(const QRegion ®ion, GLenum primitiveMode, int first, int count, bool hardwareClipping) { if (primitiveMode == GL_QUADS) { IndexBuffer *&indexBuffer = GLVertexBufferPrivate::s_indexBuffer; if (!indexBuffer) indexBuffer = new IndexBuffer; indexBuffer->bind(); indexBuffer->accomodate(count / 4); count = count * 6 / 4; if (!hardwareClipping) { glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first); } else { // Clip using scissoring - foreach (const QRect &r, region.rects()) { - glScissor(r.x() - s_virtualScreenGeometry.x(), s_virtualScreenGeometry.height() - s_virtualScreenGeometry.y() - r.y() - r.height(), r.width(), r.height()); + for (const QRect &r : region) { + glScissor((r.x() - s_virtualScreenGeometry.x()) * s_virtualScreenScale, + (s_virtualScreenGeometry.height() + s_virtualScreenGeometry.y() - r.y() - r.height()) * s_virtualScreenScale, + r.width() * s_virtualScreenScale, + r.height() * s_virtualScreenScale); glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first); } } return; } if (!hardwareClipping) { glDrawArrays(primitiveMode, first, count); } else { // Clip using scissoring - foreach (const QRect &r, region.rects()) { - glScissor(r.x() - s_virtualScreenGeometry.x(), s_virtualScreenGeometry.height() - s_virtualScreenGeometry.y() - r.y() - r.height(), r.width(), r.height()); + for (const QRect &r : region) { + glScissor((r.x() - s_virtualScreenGeometry.x()) * s_virtualScreenScale, + (s_virtualScreenGeometry.height() + s_virtualScreenGeometry.y() - r.y() - r.height()) * s_virtualScreenScale, + r.width() * s_virtualScreenScale, + r.height() * s_virtualScreenScale); glDrawArrays(primitiveMode, first, count); } } } bool GLVertexBuffer::supportsIndexedQuads() { return GLVertexBufferPrivate::supportsIndexedQuads; } bool GLVertexBuffer::isUseColor() const { return d->useColor; } void GLVertexBuffer::setUseColor(bool enable) { d->useColor = enable; } void GLVertexBuffer::setColor(const QColor& color, bool enable) { d->useColor = enable; d->color = QVector4D(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } void GLVertexBuffer::reset() { d->useColor = false; d->color = QVector4D(0, 0, 0, 1); d->vertexCount = 0; } void GLVertexBuffer::endOfFrame() { if (!d->persistent) return; // Emit a fence if we have uploaded data if (d->frameSize > 0) { d->frameSizes.push(d->frameSize); d->frameSize = 0; // Force the buffer to be reallocated at the beginning of the next frame // if the average frame size is greater than half the size of the buffer if (unlikely(d->frameSizes.average() > d->bufferSize / 2)) { deleteAll(d->fences); glDeleteBuffers(1, &d->buffer); d->buffer = 0; d->bufferSize = 0; d->nextOffset = 0; d->map = nullptr; } else { BufferFence fence; fence.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); fence.nextEnd = d->nextOffset + d->bufferSize; d->fences.emplace_back(fence); } } } void GLVertexBuffer::framePosted() { if (!d->persistent) return; // Remove finished fences from the list and update the bufferEnd offset while (d->fences.size() > 1 && d->fences.front().signaled()) { const BufferFence &fence = d->fences.front(); glDeleteSync(fence.sync); d->bufferEnd = fence.nextEnd; d->fences.pop_front(); } } void GLVertexBuffer::initStatic() { if (GLPlatform::instance()->isGLES()) { bool haveBaseVertex = hasGLExtension(QByteArrayLiteral("GL_OES_draw_elements_base_vertex")); bool haveCopyBuffer = hasGLVersion(3, 0); bool haveMapBufferRange = hasGLExtension(QByteArrayLiteral("GL_EXT_map_buffer_range")); GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange; GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange; GLVertexBufferPrivate::haveBufferStorage = hasGLExtension("GL_EXT_buffer_storage"); GLVertexBufferPrivate::haveSyncFences = hasGLVersion(3, 0); } else { bool haveBaseVertex = hasGLVersion(3, 2) || hasGLExtension(QByteArrayLiteral("GL_ARB_draw_elements_base_vertex")); bool haveCopyBuffer = hasGLVersion(3, 1) || hasGLExtension(QByteArrayLiteral("GL_ARB_copy_buffer")); bool haveMapBufferRange = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_map_buffer_range")); GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange; GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange; GLVertexBufferPrivate::haveBufferStorage = hasGLVersion(4, 4) || hasGLExtension("GL_ARB_buffer_storage"); GLVertexBufferPrivate::haveSyncFences = hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync"); } GLVertexBufferPrivate::s_indexBuffer = nullptr; GLVertexBufferPrivate::streamingBuffer = new GLVertexBuffer(GLVertexBuffer::Stream); if (GLVertexBufferPrivate::haveBufferStorage && GLVertexBufferPrivate::haveSyncFences) { if (qgetenv("KWIN_PERSISTENT_VBO") != QByteArrayLiteral("0")) { GLVertexBufferPrivate::streamingBuffer->d->persistent = true; } } } void GLVertexBuffer::cleanup() { delete GLVertexBufferPrivate::s_indexBuffer; GLVertexBufferPrivate::s_indexBuffer = nullptr; GLVertexBufferPrivate::hasMapBufferRange = false; GLVertexBufferPrivate::supportsIndexedQuads = false; delete GLVertexBufferPrivate::streamingBuffer; GLVertexBufferPrivate::streamingBuffer = nullptr; } GLVertexBuffer *GLVertexBuffer::streamingBuffer() { return GLVertexBufferPrivate::streamingBuffer; } } // namespace diff --git a/libkwineffects/kwinglutils.h b/libkwineffects/kwinglutils.h index da2bedfc7..b0045115b 100644 --- a/libkwineffects/kwinglutils.h +++ b/libkwineffects/kwinglutils.h @@ -1,789 +1,825 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006-2007 Rivo Laks Copyright (C) 2010, 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_GLUTILS_H #define KWIN_GLUTILS_H // kwin #include #include "kwinglutils_funcs.h" #include "kwingltexture.h" // Qt #include #include /** @addtogroup kwineffects */ /** @{ */ class QVector2D; class QVector3D; class QVector4D; class QMatrix4x4; template< class K, class V > class QHash; namespace KWin { class GLVertexBuffer; class GLVertexBufferPrivate; // Initializes OpenGL stuff. This includes resolving function pointers as // well as checking for GL version and extensions // Note that GL context has to be created by the time this function is called typedef void (*resolveFuncPtr)(); void KWINGLUTILS_EXPORT initGL(std::function resolveFunction); // Cleans up all resources hold by the GL Context void KWINGLUTILS_EXPORT cleanupGL(); bool KWINGLUTILS_EXPORT hasGLVersion(int major, int minor, int release = 0); // use for both OpenGL and GLX extensions bool KWINGLUTILS_EXPORT hasGLExtension(const QByteArray &extension); // detect OpenGL error (add to various places in code to pinpoint the place) bool KWINGLUTILS_EXPORT checkGLError(const char* txt); QList KWINGLUTILS_EXPORT openGLExtensions(); class KWINGLUTILS_EXPORT GLShader { public: enum Flags { NoFlags = 0, ExplicitLinking = (1 << 0) }; GLShader(const QString &vertexfile, const QString &fragmentfile, unsigned int flags = NoFlags); ~GLShader(); bool isValid() const { return mValid; } void bindAttributeLocation(const char *name, int index); void bindFragDataLocation(const char *name, int index); bool link(); int uniformLocation(const char* name); bool setUniform(const char* name, float value); bool setUniform(const char* name, int value); bool setUniform(const char* name, const QVector2D& value); bool setUniform(const char* name, const QVector3D& value); bool setUniform(const char* name, const QVector4D& value); bool setUniform(const char* name, const QMatrix4x4& value); bool setUniform(const char* name, const QColor& color); bool setUniform(int location, float value); bool setUniform(int location, int value); bool setUniform(int location, const QVector2D &value); bool setUniform(int location, const QVector3D &value); bool setUniform(int location, const QVector4D &value); bool setUniform(int location, const QMatrix4x4 &value); bool setUniform(int location, const QColor &value); int attributeLocation(const char* name); bool setAttribute(const char* name, float value); /** * @return The value of the uniform as a matrix * @since 4.7 **/ QMatrix4x4 getUniformMatrix4x4(const char* name); enum MatrixUniform { TextureMatrix = 0, ProjectionMatrix, ModelViewMatrix, ModelViewProjectionMatrix, WindowTransformation, ScreenTransformation, MatrixCount }; enum Vec2Uniform { Offset, Vec2UniformCount }; enum Vec4Uniform { ModulationConstant, Vec4UniformCount }; enum FloatUniform { Saturation, FloatUniformCount }; enum IntUniform { AlphaToOne, ///< @deprecated no longer used IntUniformCount }; enum ColorUniform { Color, ColorUniformCount }; bool setUniform(MatrixUniform uniform, const QMatrix4x4 &matrix); bool setUniform(Vec2Uniform uniform, const QVector2D &value); bool setUniform(Vec4Uniform uniform, const QVector4D &value); bool setUniform(FloatUniform uniform, float value); bool setUniform(IntUniform uniform, int value); bool setUniform(ColorUniform uniform, const QVector4D &value); bool setUniform(ColorUniform uniform, const QColor &value); protected: GLShader(unsigned int flags = NoFlags); bool loadFromFiles(const QString& vertexfile, const QString& fragmentfile); bool load(const QByteArray &vertexSource, const QByteArray &fragmentSource); const QByteArray prepareSource(GLenum shaderType, const QByteArray &sourceCode) const; bool compile(GLuint program, GLenum shaderType, const QByteArray &sourceCode) const; void bind(); void unbind(); void resolveLocations(); private: unsigned int mProgram; bool mValid:1; bool mLocationsResolved:1; bool mExplicitLinking:1; int mMatrixLocation[MatrixCount]; int mVec2Location[Vec2UniformCount]; int mVec4Location[Vec4UniformCount]; int mFloatLocation[FloatUniformCount]; int mIntLocation[IntUniformCount]; int mColorLocation[ColorUniformCount]; friend class ShaderManager; }; enum class ShaderTrait { MapTexture = (1 << 0), UniformColor = (1 << 1), Modulate = (1 << 2), AdjustSaturation = (1 << 3), }; Q_DECLARE_FLAGS(ShaderTraits, ShaderTrait) /** * @short Manager for Shaders. * * This class provides some built-in shaders to be used by both compositing scene and effects. * The ShaderManager provides methods to bind a built-in or a custom shader and keeps track of * the shaders which have been bound. When a shader is unbound the previously bound shader * will be rebound. * * @author Martin Gräßlin * @since 4.7 **/ class KWINGLUTILS_EXPORT ShaderManager { public: /** * Returns a shader with the given traits, creating it if necessary. */ GLShader *shader(ShaderTraits traits); /** * @return The currently bound shader or @c null if no shader is bound. **/ GLShader *getBoundShader() const; /** * @return @c true if a shader is bound, @c false otherwise **/ bool isShaderBound() const; /** * Is @c true if the environment variable KWIN_GL_DEBUG is set to 1. * In that case shaders are compiled with KWIN_SHADER_DEBUG defined. * @returns @c true if shaders are compiled with debug information * @since 4.8 **/ bool isShaderDebug() const; /** * Pushes the current shader onto the stack and binds a shader * with the given traits. */ GLShader *pushShader(ShaderTraits traits); /** * Binds the @p shader. * To unbind the shader use @link popShader. A previous bound shader will be rebound. * To bind a built-in shader use the more specific method. * @param shader The shader to be bound * @see popShader **/ void pushShader(GLShader *shader); /** * Unbinds the currently bound shader and rebinds a previous stored shader. * If there is no previous shader, no shader will be rebound. * It is not safe to call this method if there is no bound shader. * @see pushShader * @see getBoundShader **/ void popShader(); /** * Creates a GLShader with the specified sources. * The difference to GLShader is that it does not need to be loaded from files. * @param vertexSource The source code of the vertex shader * @param fragmentSource The source code of the fragment shader. * @return The created shader **/ GLShader *loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource); /** * Creates a custom shader with the given @p traits and custom @p vertexSource and or @p fragmentSource. * If the @p vertexSource is empty a vertex shader with the given @p traits is generated. * If it is not empty the @p vertexSource is used as the source for the vertex shader. * * The same applies for argument @p fragmentSource just for the fragment shader. * * So if both @p vertesSource and @p fragmentSource are provided the @p traits are ignored. * If neither are provided a new shader following the @p traits is generated. * * @param traits The shader traits for generating the shader * @param vertesSource optional vertex shader source code to be used instead of shader traits * @param fragmentSource optional fragment shader source code to be used instead of shader traits * @return new generated shader * @since 5.6 **/ GLShader *generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource = QByteArray(), const QByteArray &fragmentSource = QByteArray()); /** * Creates a custom shader with the given @p traits and custom @p vertexFile and or @p fragmentFile. * The file names specified in @p vertexFile and @p fragmentFile are relative paths to the shaders * resource file shipped together with KWin. This means this method can only be used for built-in * effects, for 3rd party effects @link {generateCustomShader} should be used. * * If the @p vertexFile is empty a vertex shader with the given @p traits is generated. * If it is not empty the @p vertexFile is used as the source for the vertex shader. * * The same applies for argument @p fragmentFile just for the fragment shader. * * So if both @p vertexFile and @p fragmentFile are provided the @p traits are ignored. * If neither are provided a new shader following the @p traits is generated. * * @param traits The shader traits for generating the shader * @param vertexFile optional vertex shader source code to be used instead of shader traits * @param fragmentFile optional fragment shader source code to be used instead of shader traits * @return new generated shader * @see generateCustomShader * @since 5.6 **/ GLShader *generateShaderFromResources(ShaderTraits traits, const QString &vertexFile = QString(), const QString &fragmentFile = QString()); /** * Compiles and tests the dynamically generated shaders. * Returns true if successful and false otherwise. */ bool selfTest(); /** * @return a pointer to the ShaderManager instance **/ static ShaderManager *instance(); /** * @internal **/ static void cleanup(); private: ShaderManager(); ~ShaderManager(); void bindFragDataLocations(GLShader *shader); void bindAttributeLocations(GLShader *shader) const; QByteArray generateVertexSource(ShaderTraits traits) const; QByteArray generateFragmentSource(ShaderTraits traits) const; GLShader *generateShader(ShaderTraits traits); QStack m_boundShaders; QHash m_shaderHash; bool m_debug; QString m_resourcePath; static ShaderManager *s_shaderManager; }; /** * An helper class to push a Shader on to ShaderManager's stack and ensuring that the Shader * gets popped again from the stack automatically once the object goes out of life. * * How to use: * @code * { * GLShader *myCustomShaderIWantToPush; * ShaderBinder binder(myCustomShaderIWantToPush); * // do stuff with the shader being pushed on the stack * } * // here the Shader is automatically popped as helper does no longer exist. * @endcode * * @since 4.10 **/ class KWINGLUTILS_EXPORT ShaderBinder { public: /** * @brief Pushes the given @p shader to the ShaderManager's stack. * * @param shader The Shader to push on the stack * @see ShaderManager::pushShader **/ explicit ShaderBinder(GLShader *shader); /** * @brief Pushes the Shader with the given @p traits to the ShaderManager's stack. * * @param traits The traits describing the shader * @see ShaderManager::pushShader * @since 5.6 **/ explicit ShaderBinder(ShaderTraits traits); ~ShaderBinder(); /** * @return The Shader pushed to the Stack. **/ GLShader *shader(); private: GLShader *m_shader; }; inline ShaderBinder::ShaderBinder(GLShader *shader) : m_shader(shader) { ShaderManager::instance()->pushShader(shader); } inline ShaderBinder::ShaderBinder(ShaderTraits traits) : m_shader(nullptr) { m_shader = ShaderManager::instance()->pushShader(traits); } inline ShaderBinder::~ShaderBinder() { ShaderManager::instance()->popShader(); } inline GLShader* ShaderBinder::shader() { return m_shader; } /** * @short Render target object * * Render target object enables you to render onto a texture. This texture can * later be used to e.g. do post-processing of the scene. * * @author Rivo Laks **/ class KWINGLUTILS_EXPORT GLRenderTarget { public: + /** + * Constructs a GLRenderTarget + * @since 5.13 + **/ + explicit GLRenderTarget(); + /** * Constructs a GLRenderTarget * @param color texture where the scene will be rendered onto **/ explicit GLRenderTarget(const GLTexture& color); ~GLRenderTarget(); /** * Enables this render target. * All OpenGL commands from now on affect this render target until the * @ref disable method is called **/ bool enable(); /** * Disables this render target, activating whichever target was active * when @ref enable was called. **/ bool disable(); /** * Sets the target texture * @param target texture where the scene will be rendered on * @since 4.8 **/ void attachTexture(const GLTexture& target); + /** + * Detaches the texture that is currently attached to this framebuffer object. + * @since 5.13 + **/ + void detachTexture(); + bool valid() const { return mValid; } + void setTextureDirty() { + mTexture.setDirty(); + } + static void initStatic(); static bool supported() { return sSupported; } + + /** + * Pushes the render target stack of the input parameter in reverse order. + * @param targets The stack of GLRenderTargets + * @since 5.13 + **/ + static void pushRenderTargets(QStack targets); + static void pushRenderTarget(GLRenderTarget *target); static GLRenderTarget *popRenderTarget(); static bool isRenderTargetBound(); /** * Whether the GL_EXT_framebuffer_blit extension is supported. * This functionality is not available in OpenGL ES 2.0. * * @returns whether framebuffer blitting is supported. * @since 4.8 **/ static bool blitSupported(); /** * Blits the content of the current draw framebuffer into the texture attached to this FBO. * * Be aware that framebuffer blitting may not be supported on all hardware. Use @link blitSupported to check whether * it is supported. * @param source Geometry in screen coordinates which should be blitted, if not specified complete framebuffer is used * @param destination Geometry in attached texture, if not specified complete texture is used as destination * @param filter The filter to use if blitted content needs to be scaled. * @see blitSupported * @since 4.8 **/ void blitFromFramebuffer(const QRect &source = QRect(), const QRect &destination = QRect(), GLenum filter = GL_LINEAR); /** * Sets the virtual screen size to @p s. * @since 5.2 **/ static void setVirtualScreenSize(const QSize &s) { s_virtualScreenSize = s; } /** * Sets the virtual screen geometry to @p g. * This is the geometry of the OpenGL window currently being rendered to * in the virtual geometry space the rendering geometries use. * @see virtualScreenGeometry * @since 5.9 **/ static void setVirtualScreenGeometry(const QRect &g) { s_virtualScreenGeometry = g; } /** * The geometry of the OpenGL window currently being rendered to * in the virtual geometry space the rendering system uses. * @see setVirtualScreenGeometry * @since 5.9 **/ static QRect virtualScreenGeometry() { return s_virtualScreenGeometry; } /** * The scale of the OpenGL window currently being rendered to * * @returns the ratio between the virtual geometry space the rendering * system uses and the target * @since 5.10 */ static void setVirtualScreenScale(qreal scale) { s_virtualScreenScale = scale; } static qreal virtualScreenScale() { return s_virtualScreenScale; } protected: void initFBO(); private: friend void KWin::cleanupGL(); static void cleanup(); static bool sSupported; static bool s_blitSupported; static QStack s_renderTargets; static QSize s_virtualScreenSize; static QRect s_virtualScreenGeometry; static qreal s_virtualScreenScale; static GLint s_virtualScreenViewport[4]; GLTexture mTexture; bool mValid; GLuint mFramebuffer; }; enum VertexAttributeType { VA_Position = 0, VA_TexCoord = 1, VertexAttributeCount = 2 }; /** * Describes the format of a vertex attribute stored in a buffer object. * * The attribute format consists of the attribute index, the number of * vector components, the data type, and the offset of the first element * relative to the start of the vertex data. */ struct GLVertexAttrib { int index; /** The attribute index */ int size; /** The number of components [1..4] */ GLenum type; /** The type (e.g. GL_FLOAT) */ int relativeOffset; /** The relative offset of the attribute */ }; /** * @short Vertex Buffer Object * * This is a short helper class to use vertex buffer objects (VBO). A VBO can be used to buffer * vertex data and to store them on graphics memory. It is the only allowed way to pass vertex * data to the GPU in OpenGL ES 2 and OpenGL 3 with forward compatible mode. * * If VBOs are not supported on the used OpenGL profile this class falls back to legacy * rendering using client arrays. Therefore this class should always be used for rendering geometries. * * @author Martin Gräßlin * @since 4.6 */ class KWINGLUTILS_EXPORT GLVertexBuffer { public: /** * Enum to define how often the vertex data in the buffer object changes. */ enum UsageHint { Dynamic, ///< frequent changes, but used several times for rendering Static, ///< No changes to data Stream ///< Data only used once for rendering, updated very frequently }; explicit GLVertexBuffer(UsageHint hint); ~GLVertexBuffer(); /** * Specifies how interleaved vertex attributes are laid out in * the buffer object. * * Note that the attributes and the stride should be 32 bit aligned * or a performance penalty may be incurred. * * For some hardware the optimal stride is a multiple of 32 bytes. * * Example: * * struct Vertex { * QVector3D position; * QVector2D texcoord; * }; * * const GLVertexAttrib attribs[] = { * { VA_Position, 3, GL_FLOAT, offsetof(Vertex, position) }, * { VA_TexCoord, 2, GL_FLOAT, offsetof(Vertex, texcoord) } * }; * * Vertex vertices[6]; * vbo->setAttribLayout(attribs, 2, sizeof(Vertex)); * vbo->setData(vertices, sizeof(vertices)); */ void setAttribLayout(const GLVertexAttrib *attribs, int count, int stride); /** * Uploads data into the buffer object's data store. */ void setData(const void *data, size_t sizeInBytes); /** * Sets the number of vertices that will be drawn by the render() method. */ void setVertexCount(int count); /** * Sets the vertex data. * @param numberVertices The number of vertices in the arrays * @param dim The dimension of the vertices: 2 for x/y, 3 for x/y/z * @param vertices The vertices, size must equal @a numberVertices * @a dim * @param texcoords The texture coordinates for each vertex. * Size must equal 2 * @a numberVertices. */ void setData(int numberVertices, int dim, const float* vertices, const float* texcoords); /** * Maps an unused range of the data store into the client's address space. * * The data store will be reallocated if it is smaller than the given size. * * The buffer object is mapped for writing, not reading. Attempts to read from * the mapped buffer range may result in system errors, including program * termination. The data in the mapped region is undefined until it has been * written to. If subsequent GL calls access unwritten memory, the results are * undefined and system errors, including program termination, may occur. * * No GL calls that access the buffer object must be made while the buffer * object is mapped. The returned pointer must not be passed as a parameter * value to any GL function. * * It is assumed that the GL_ARRAY_BUFFER_BINDING will not be changed while * the buffer object is mapped. */ GLvoid *map(size_t size); /** * Flushes the mapped buffer range and unmaps the buffer. */ void unmap(); /** * Binds the vertex arrays to the context. */ void bindArrays(); /** * Disables the vertex arrays. */ void unbindArrays(); /** * Draws count vertices beginning with first. */ void draw(GLenum primitiveMode, int first, int count); /** * Draws count vertices beginning with first. */ void draw(const QRegion ®ion, GLenum primitiveMode, int first, int count, bool hardwareClipping = false); /** * Renders the vertex data in given @a primitiveMode. * Please refer to OpenGL documentation of glDrawArrays or glDrawElements for allowed * values for @a primitiveMode. Best is to use GL_TRIANGLES or similar to be future * compatible. */ void render(GLenum primitiveMode); /** * Same as above restricting painting to @a region if @a hardwareClipping is true. * It's within the caller's responsibility to enable GL_SCISSOR_TEST. */ void render(const QRegion& region, GLenum primitiveMode, bool hardwareClipping = false); /** * Sets the color the geometry will be rendered with. * For legacy rendering glColor is used before rendering the geometry. * For core shader a uniform "geometryColor" is expected and is set. * @param color The color to render the geometry * @param enableColor Whether the geometry should be rendered with a color or not * @see setUseColor * @see isUseColor * @since 4.7 **/ void setColor(const QColor& color, bool enableColor = true); /** * @return @c true if geometry will be painted with a color, @c false otherwise * @see setUseColor * @see setColor * @since 4.7 **/ bool isUseColor() const; /** * Enables/Disables rendering the geometry with a color. * If no color is set an opaque, black color is used. * @param enable Enable/Disable rendering with color * @see isUseColor * @see setColor * @since 4.7 **/ void setUseColor(bool enable); /** * Resets the instance to default values. * Useful for shared buffers. * @since 4.7 **/ void reset(); /** * Notifies the vertex buffer that we are done painting the frame. * * @internal */ void endOfFrame(); /** * Notifies the vertex buffer that we have posted the frame. * * @internal */ void framePosted(); /** * @internal */ static void initStatic(); /** * @internal */ static void cleanup(); /** * Returns true if indexed quad mode is supported, and false otherwise. */ static bool supportsIndexedQuads(); /** * @return A shared VBO for streaming data * @since 4.7 **/ static GLVertexBuffer *streamingBuffer(); /** * Sets the virtual screen geometry to @p g. * This is the geometry of the OpenGL window currently being rendered to * in the virtual geometry space the rendering geometries use. * @since 5.9 **/ static void setVirtualScreenGeometry(const QRect &g) { s_virtualScreenGeometry = g; } + /** + * The scale of the OpenGL window currently being rendered to + * + * @returns the ratio between the virtual geometry space the rendering + * system uses and the target + * @since 5.11.3 + */ + static void setVirtualScreenScale(qreal s) { + s_virtualScreenScale = s; + } + private: GLVertexBufferPrivate* const d; static QRect s_virtualScreenGeometry; + static qreal s_virtualScreenScale; }; } // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::ShaderTraits) /** @} */ #endif diff --git a/logind.cpp b/logind.cpp index 4a86d77e0..53d5666dd 100644 --- a/logind.cpp +++ b/logind.cpp @@ -1,455 +1,461 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "logind.h" #include #include #include #include #include #include #include #include #include #include #if HAVE_SYS_SYSMACROS_H #include #endif #ifndef major #include #endif +#include #include #include "utils.h" struct DBusLogindSeat { QString name; QDBusObjectPath path; }; QDBusArgument &operator<<(QDBusArgument &argument, const DBusLogindSeat &seat) { argument.beginStructure(); argument << seat.name << seat.path ; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, DBusLogindSeat &seat) { argument.beginStructure(); argument >> seat.name >> seat.path; argument.endStructure(); return argument; } Q_DECLARE_METATYPE(DBusLogindSeat) namespace KWin { const static QString s_login1Name = QStringLiteral("logind"); const static QString s_login1Service = QStringLiteral("org.freedesktop.login1"); const static QString s_login1Path = QStringLiteral("/org/freedesktop/login1"); const static QString s_login1ManagerInterface = QStringLiteral("org.freedesktop.login1.Manager"); const static QString s_login1SeatInterface = QStringLiteral("org.freedesktop.login1.Seat"); const static QString s_login1SessionInterface = QStringLiteral("org.freedesktop.login1.Session"); const static QString s_login1ActiveProperty = QStringLiteral("Active"); const static QString s_ck2Name = QStringLiteral("ConsoleKit"); const static QString s_ck2Service = QStringLiteral("org.freedesktop.ConsoleKit"); const static QString s_ck2Path = QStringLiteral("/org/freedesktop/ConsoleKit/Manager"); const static QString s_ck2ManagerInterface = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); const static QString s_ck2SeatInterface = QStringLiteral("org.freedesktop.ConsoleKit.Seat"); const static QString s_ck2SessionInterface = QStringLiteral("org.freedesktop.ConsoleKit.Session"); const static QString s_ck2ActiveProperty = QStringLiteral("active"); const static QString s_dbusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties"); LogindIntegration *LogindIntegration::s_self = nullptr; LogindIntegration *LogindIntegration::create(QObject *parent) { Q_ASSERT(!s_self); s_self = new LogindIntegration(parent); return s_self; } LogindIntegration::LogindIntegration(const QDBusConnection &connection, QObject *parent) : QObject(parent) , m_bus(connection) , m_connected(false) , m_sessionControl(false) , m_sessionActive(false) { // check whether the logind service is registered QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"), QStringLiteral("/"), QStringLiteral("org.freedesktop.DBus"), QStringLiteral("ListNames")); QDBusPendingReply async = m_bus.asyncCall(message); QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { QDBusPendingReply reply = *self; self->deleteLater(); if (!reply.isValid()) { return; } if (reply.value().contains(s_login1Service)) { setupSessionController(SessionControllerLogind); } else if (reply.value().contains(s_ck2Service)) { setupSessionController(SessionControllerConsoleKit); } } ); } LogindIntegration::LogindIntegration(QObject *parent) : LogindIntegration(QDBusConnection::systemBus(), parent) { } LogindIntegration::~LogindIntegration() { s_self = nullptr; } void LogindIntegration::setupSessionController(SessionController controller) { if (controller == SessionControllerLogind) { // We have the logind serivce, set it up and use it m_sessionControllerName = s_login1Name; m_sessionControllerService = s_login1Service; m_sessionControllerPath = s_login1Path; m_sessionControllerManagerInterface = s_login1ManagerInterface; m_sessionControllerSeatInterface = s_login1SeatInterface; m_sessionControllerSessionInterface = s_login1SessionInterface; m_sessionControllerActiveProperty = s_login1ActiveProperty; m_logindServiceWatcher = new QDBusServiceWatcher(m_sessionControllerService, m_bus, QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration, this); connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &LogindIntegration::logindServiceRegistered); connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() { m_connected = false; emit connectedChanged(); } ); logindServiceRegistered(); } else if (controller == SessionControllerConsoleKit) { // We have the ConsoleKit serivce, set it up and use it m_sessionControllerName = s_ck2Name; m_sessionControllerService = s_ck2Service; m_sessionControllerPath = s_ck2Path; m_sessionControllerManagerInterface = s_ck2ManagerInterface; m_sessionControllerSeatInterface = s_ck2SeatInterface; m_sessionControllerSessionInterface = s_ck2SessionInterface; m_sessionControllerActiveProperty = s_ck2ActiveProperty; m_logindServiceWatcher = new QDBusServiceWatcher(m_sessionControllerService, m_bus, QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration, this); connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &LogindIntegration::logindServiceRegistered); connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() { m_connected = false; emit connectedChanged(); } ); logindServiceRegistered(); } } void LogindIntegration::logindServiceRegistered() { const QByteArray sessionId = qgetenv("XDG_SESSION_ID"); QString methodName; QVariantList args; if (sessionId.isEmpty()) { methodName = QStringLiteral("GetSessionByPID"); args << (quint32) QCoreApplication::applicationPid(); } else { methodName = QStringLiteral("GetSession"); args << QString::fromLocal8Bit(sessionId); } // get the current session QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionControllerPath, m_sessionControllerManagerInterface, methodName); message.setArguments(args); QDBusPendingReply session = m_bus.asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(session, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { QDBusPendingReply reply = *self; self->deleteLater(); if (m_connected) { return; } if (!reply.isValid()) { qCDebug(KWIN_CORE) << "The session is not registered with " << m_sessionControllerName << " " << reply.error().message(); return; } m_sessionPath = reply.value().path(); qCDebug(KWIN_CORE) << "Session path:" << m_sessionPath; m_connected = true; connectSessionPropertiesChanged(); // activate the session, in case we are not on it QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, m_sessionControllerSessionInterface, QStringLiteral("Activate")); // blocking on purpose m_bus.call(message); getSeat(); getSessionActive(); getVirtualTerminal(); emit connectedChanged(); } ); } void LogindIntegration::connectSessionPropertiesChanged() { m_bus.connect(m_sessionControllerService, m_sessionPath, s_dbusPropertiesInterface, QStringLiteral("PropertiesChanged"), this, SLOT(getSessionActive())); m_bus.connect(m_sessionControllerService, m_sessionPath, s_dbusPropertiesInterface, QStringLiteral("PropertiesChanged"), this, SLOT(getVirtualTerminal())); } void LogindIntegration::getSessionActive() { if (!m_connected || m_sessionPath.isEmpty()) { return; } QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, s_dbusPropertiesInterface, QStringLiteral("Get")); message.setArguments(QVariantList({m_sessionControllerSessionInterface, m_sessionControllerActiveProperty})); QDBusPendingReply reply = m_bus.asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { QDBusPendingReply reply = *self; self->deleteLater(); if (!reply.isValid()) { qCDebug(KWIN_CORE) << "Failed to get Active Property of " << m_sessionControllerName << " session:" << reply.error().message(); return; } const bool active = reply.value().toBool(); if (m_sessionActive != active) { m_sessionActive = active; emit sessionActiveChanged(m_sessionActive); } } ); } void LogindIntegration::getVirtualTerminal() { if (!m_connected || m_sessionPath.isEmpty()) { return; } QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, s_dbusPropertiesInterface, QStringLiteral("Get")); message.setArguments(QVariantList({m_sessionControllerSessionInterface, QStringLiteral("VTNr")})); QDBusPendingReply reply = m_bus.asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { QDBusPendingReply reply = *self; self->deleteLater(); if (!reply.isValid()) { qCDebug(KWIN_CORE) << "Failed to get VTNr Property of " << m_sessionControllerName << " session:" << reply.error().message(); return; } const int vt = reply.value().toUInt(); if (m_vt != (int)vt) { m_vt = vt; emit virtualTerminalChanged(m_vt); } } ); } void LogindIntegration::takeControl() { if (!m_connected || m_sessionPath.isEmpty() || m_sessionControl) { return; } static bool s_recursionCheck = false; if (s_recursionCheck) { return; } s_recursionCheck = true; QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, m_sessionControllerSessionInterface, QStringLiteral("TakeControl")); message.setArguments(QVariantList({QVariant(false)})); QDBusPendingReply session = m_bus.asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(session, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { s_recursionCheck = false; QDBusPendingReply reply = *self; self->deleteLater(); if (!reply.isValid()) { qCDebug(KWIN_CORE) << "Failed to get session control" << reply.error().message(); emit hasSessionControlChanged(false); return; } qCDebug(KWIN_CORE) << "Gained session control"; m_sessionControl = true; emit hasSessionControlChanged(true); m_bus.connect(m_sessionControllerService, m_sessionPath, m_sessionControllerSessionInterface, QStringLiteral("PauseDevice"), this, SLOT(pauseDevice(uint,uint,QString))); } ); } void LogindIntegration::releaseControl() { if (!m_connected || m_sessionPath.isEmpty() || !m_sessionControl) { return; } QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, m_sessionControllerSessionInterface, QStringLiteral("ReleaseControl")); m_bus.asyncCall(message); m_sessionControl = false; emit hasSessionControlChanged(false); } int LogindIntegration::takeDevice(const char *path) { struct stat st; if (stat(path, &st) < 0) { qCDebug(KWIN_CORE) << "Could not stat the path"; return -1; } QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, m_sessionControllerSessionInterface, QStringLiteral("TakeDevice")); message.setArguments(QVariantList({QVariant(major(st.st_rdev)), QVariant(minor(st.st_rdev))})); // intended to be a blocking call QDBusMessage reply = m_bus.call(message); if (reply.type() == QDBusMessage::ErrorMessage) { qCDebug(KWIN_CORE) << "Could not take device" << path << ", cause: " << reply.errorMessage(); return -1; } - return dup(reply.arguments().first().value().fileDescriptor()); + + // The dup syscall removes the CLOEXEC flag as a side-effect. So use fcntl's F_DUPFD_CLOEXEC cmd. + return fcntl(reply.arguments().first().value().fileDescriptor(), F_DUPFD_CLOEXEC, 0); } void LogindIntegration::releaseDevice(int fd) { struct stat st; if (fstat(fd, &st) < 0) { qCDebug(KWIN_CORE) << "Could not stat the file descriptor"; return; } QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, m_sessionControllerSessionInterface, QStringLiteral("ReleaseDevice")); message.setArguments(QVariantList({QVariant(major(st.st_rdev)), QVariant(minor(st.st_rdev))})); m_bus.asyncCall(message); } void LogindIntegration::pauseDevice(uint devMajor, uint devMinor, const QString &type) { if (QString::compare(type, QStringLiteral("pause"), Qt::CaseInsensitive) == 0) { // unconditionally call complete QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, m_sessionControllerSessionInterface, QStringLiteral("PauseDeviceComplete")); message.setArguments(QVariantList({QVariant(devMajor), QVariant(devMinor)})); m_bus.asyncCall(message); } } void LogindIntegration::getSeat() { if (m_sessionPath.isEmpty()) { return; } qDBusRegisterMetaType(); QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, s_dbusPropertiesInterface, QStringLiteral("Get")); message.setArguments(QVariantList({m_sessionControllerSessionInterface, QStringLiteral("Seat")})); message.setArguments(QVariantList({m_sessionControllerSessionInterface, QStringLiteral("Seat")})); QDBusPendingReply reply = m_bus.asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { QDBusPendingReply reply = *self; self->deleteLater(); if (!reply.isValid()) { qCDebug(KWIN_CORE) << "Failed to get Seat Property of " << m_sessionControllerName << " session:" << reply.error().message(); return; } DBusLogindSeat seat = qdbus_cast(reply.value().value()); const QString seatPath = seat.path.path(); qCDebug(KWIN_CORE) << m_sessionControllerName << " seat:" << seat.name << "/" << seatPath; qCDebug(KWIN_CORE) << m_sessionControllerName << " seat:" << seat.name << "/" << seatPath; if (m_seatPath != seatPath) { m_seatPath = seatPath; } + if (m_seatName != seat.name) { + m_seatName = seat.name; + } } ); } void LogindIntegration::switchVirtualTerminal(quint32 vtNr) { if (!m_connected || m_seatPath.isEmpty()) { return; } QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_seatPath, m_sessionControllerSeatInterface, QStringLiteral("SwitchTo")); message.setArguments(QVariantList{vtNr}); m_bus.asyncCall(message); } } // namespace diff --git a/logind.h b/logind.h index d0af39fe1..40e3c4f70 100644 --- a/logind.h +++ b/logind.h @@ -1,107 +1,112 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_LOGIND_H #define KWIN_LOGIND_H #include #include #include class QDBusServiceWatcher; namespace KWin { class KWIN_EXPORT LogindIntegration : public QObject { Q_OBJECT public: ~LogindIntegration(); bool isConnected() const { return m_connected; } bool hasSessionControl() const { return m_sessionControl; } bool isActiveSession() const { return m_sessionActive; } int vt() const { return m_vt; } void switchVirtualTerminal(quint32 vtNr); void takeControl(); void releaseControl(); int takeDevice(const char *path); void releaseDevice(int fd); + const QString seat() const { + return m_seatName; + } + Q_SIGNALS: void connectedChanged(); void hasSessionControlChanged(bool); void sessionActiveChanged(bool); void virtualTerminalChanged(int); private Q_SLOTS: void getSessionActive(); void getVirtualTerminal(); void pauseDevice(uint major, uint minor, const QString &type); private: friend class LogindTest; /** * The DBusConnection argument is needed for the unit test. Logind uses the system bus * on which the unit test's fake logind cannot register to. Thus the unit test need to * be able to do everything over the session bus. This ctor allows the LogindTest to * create a LogindIntegration which listens on the session bus. **/ explicit LogindIntegration(const QDBusConnection &connection, QObject *parent = nullptr); void logindServiceRegistered(); void connectSessionPropertiesChanged(); enum SessionController { SessionControllerLogind, SessionControllerConsoleKit, }; void setupSessionController(SessionController controller); void getSeat(); QDBusConnection m_bus; QDBusServiceWatcher *m_logindServiceWatcher; bool m_connected; QString m_sessionPath; bool m_sessionControl; bool m_sessionActive; int m_vt = -1; + QString m_seatName = QStringLiteral("seat0"); QString m_seatPath; QString m_sessionControllerName; QString m_sessionControllerService; QString m_sessionControllerPath; QString m_sessionControllerManagerInterface; QString m_sessionControllerSeatInterface; QString m_sessionControllerSessionInterface; QString m_sessionControllerActiveProperty; KWIN_SINGLETON(LogindIntegration) }; } #endif diff --git a/main_wayland.cpp b/main_wayland.cpp index 813e914de..03003e156 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -1,820 +1,832 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "main_wayland.h" #include "composite.h" #include "virtualkeyboard.h" #include "workspace.h" #include // kwin #include "platform.h" #include "effects.h" +#include "tabletmodemanager.h" #include "wayland_server.h" #include "xcbutils.h" // KWayland #include #include // KDE #include #include #include +#include + // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include // system #ifdef HAVE_UNISTD_H #include #endif // HAVE_UNISTD_H #if HAVE_SYS_PRCTL_H #include #endif #if HAVE_SYS_PROCCTL_H #include #include #endif #if HAVE_LIBCAP #include #endif #include #include #include namespace KWin { static void sighandler(int) { QApplication::exit(); } static void readDisplay(int pipe); +enum class RealTimeFlags +{ + DontReset, + ResetOnFork +}; + +namespace { +void gainRealTime(RealTimeFlags flags = RealTimeFlags::DontReset) +{ +#if HAVE_SCHED_RESET_ON_FORK + const int minPriority = sched_get_priority_min(SCHED_RR); + struct sched_param sp; + sp.sched_priority = minPriority; + int policy = SCHED_RR; + if (flags == RealTimeFlags::ResetOnFork) { + policy |= SCHED_RESET_ON_FORK; + } + sched_setscheduler(0, policy, &sp); +#endif +} +} + //************************************ // ApplicationWayland //************************************ ApplicationWayland::ApplicationWayland(int &argc, char **argv) : Application(OperationModeWaylandOnly, argc, argv) { } ApplicationWayland::~ApplicationWayland() { if (!waylandServer()) { return; } if (kwinApp()->platform()) { kwinApp()->platform()->setOutputsEnabled(false); } // need to unload all effects prior to destroying X connection as they might do X calls if (effects) { static_cast(effects)->unloadAllEffects(); } destroyWorkspace(); waylandServer()->dispatch(); disconnect(m_xwaylandFailConnection); if (x11Connection()) { Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); destroyAtoms(); emit x11ConnectionAboutToBeDestroyed(); xcb_disconnect(x11Connection()); setX11Connection(nullptr); } if (m_xwaylandProcess) { m_xwaylandProcess->terminate(); while (m_xwaylandProcess->state() != QProcess::NotRunning) { processEvents(QEventLoop::WaitForMoreEvents); } waylandServer()->destroyXWaylandConnection(); } if (QStyle *s = style()) { s->unpolish(this); } waylandServer()->terminateClientConnections(); destroyCompositor(); } void ApplicationWayland::performStartup() { if (m_startXWayland) { setOperationMode(OperationModeXwayland); } // first load options - done internally by a different thread createOptions(); waylandServer()->createInternalConnection(); // try creating the Wayland Backend createInput(); + // now libinput thread has been created, adjust scheduler to not leak into other processes + gainRealTime(RealTimeFlags::ResetOnFork); + VirtualKeyboard::create(this); createBackend(); + TabletModeManager::create(this); } void ApplicationWayland::createBackend() { connect(platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); connect(platform(), &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; QCoreApplication::exit(1); } ); platform()->init(); } void ApplicationWayland::continueStartupWithScreens() { disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); createScreens(); if (operationMode() == OperationModeWaylandOnly) { createCompositor(); connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithSceen); return; } createCompositor(); connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::startXwaylandServer); } void ApplicationWayland::continueStartupWithSceen() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithSceen); startSession(); createWorkspace(); notifyKSplash(); } void ApplicationWayland::continueStartupWithX() { createX11Connection(); xcb_connection_t *c = x11Connection(); if (!c) { // about to quit return; } QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(c), QSocketNotifier::Read, this); auto processXcbEvents = [this, c] { while (auto event = xcb_poll_for_event(c)) { updateX11Time(event); long result = 0; if (QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result)) { free(event); continue; } if (Workspace::self()) { Workspace::self()->workspaceEvent(event); } free(event); } xcb_flush(c); }; connect(notifier, &QSocketNotifier::activated, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); // create selection owner for WM_S0 - magic X display number expected by XWayland KSelectionOwner owner("WM_S0", c, x11RootWindow()); owner.claim(true); createAtoms(); setupEventFilters(); // Check whether another windowmanager is running const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; ScopedCPointer redirectCheck(xcb_request_check(connection(), xcb_change_window_attributes_checked(connection(), rootWindow(), XCB_CW_EVENT_MASK, maskValues))); if (!redirectCheck.isNull()) { fputs(i18n("kwin_wayland: an X11 window manager is running on the X11 Display.\n").toLocal8Bit().constData(), stderr); ::exit(1); } m_environment.insert(QStringLiteral("DISPLAY"), QString::fromUtf8(qgetenv("DISPLAY"))); startSession(); createWorkspace(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort notifyKSplash(); } void ApplicationWayland::startSession() { if (!m_inputMethodServerToStart.isEmpty()) { int socket = dup(waylandServer()->createInputMethodConnection()); if (socket >= 0) { QProcessEnvironment environment = m_environment; environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland")); environment.remove("DISPLAY"); environment.remove("WAYLAND_DISPLAY"); QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, this, [this, p] { if (waylandServer()) { waylandServer()->destroyInputMethodConnection(); } p->deleteLater(); } ); p->setProcessEnvironment(environment); p->start(m_inputMethodServerToStart); p->waitForStarted(); } } // start session if (!m_sessionArgument.isEmpty()) { QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); p->setProcessEnvironment(m_environment); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, this, &ApplicationWayland::quit); p->start(m_sessionArgument); } // start the applications passed to us as command line arguments if (!m_applicationsToStart.isEmpty()) { for (const QString &application: m_applicationsToStart) { // note: this will kill the started process when we exit // this is going to happen anyway as we are the wayland and X server the app connects to QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); p->setProcessEnvironment(m_environment); p->start(application); } } } void ApplicationWayland::createX11Connection() { int screenNumber = 0; xcb_connection_t *c = nullptr; if (m_xcbConnectionFd == -1) { c = xcb_connect(nullptr, &screenNumber); } else { c = xcb_connect_to_fd(m_xcbConnectionFd, nullptr); } if (int error = xcb_connection_has_error(c)) { std::cerr << "FATAL ERROR: Creating connection to XServer failed: " << error << std::endl; exit(1); return; } setX11Connection(c); // we don't support X11 multi-head in Wayland setX11ScreenNumber(screenNumber); setX11RootWindow(defaultScreen()->root); } void ApplicationWayland::startXwaylandServer() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::startXwaylandServer); int pipeFds[2]; if (pipe(pipeFds) != 0) { std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl; exit(1); return; } int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; exit(1); return; } int fd = dup(sx[1]); if (fd < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; exit(20); return; } const int waylandSocket = waylandServer()->createXWaylandConnection(); if (waylandSocket == -1) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(1); return; } const int wlfd = dup(waylandSocket); if (wlfd < 0) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(20); return; } m_xcbConnectionFd = sx[0]; m_xwaylandProcess = new Process(kwinApp()); m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel); m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); QProcessEnvironment env = m_environment; env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); env.insert("EGL_PLATFORM", QByteArrayLiteral("DRM")); m_xwaylandProcess->setProcessEnvironment(env); m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"), QString::number(pipeFds[1]), QStringLiteral("-rootless"), QStringLiteral("-wm"), QString::number(fd)}); m_xwaylandFailConnection = connect(m_xwaylandProcess, static_cast(&QProcess::error), this, [] (QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl; } else { std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl; } exit(1); } ); const int xDisplayPipe = pipeFds[0]; connect(m_xwaylandProcess, &QProcess::started, this, [this, xDisplayPipe] { QFutureWatcher *watcher = new QFutureWatcher(this); QObject::connect(watcher, &QFutureWatcher::finished, this, &ApplicationWayland::continueStartupWithX, Qt::QueuedConnection); QObject::connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater, Qt::QueuedConnection); watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe)); } ); m_xwaylandProcess->start(); close(pipeFds[1]); } static void readDisplay(int pipe) { QFile readPipe; if (!readPipe.open(pipe, QIODevice::ReadOnly)) { std::cerr << "FATAL ERROR failed to open pipe to start X Server" << std::endl; exit(1); } QByteArray displayNumber = readPipe.readLine(); displayNumber.prepend(QByteArray(":")); displayNumber.remove(displayNumber.size() -1, 1); std::cout << "X-Server started on display " << displayNumber.constData() << std::endl; setenv("DISPLAY", displayNumber.constData(), true); // close our pipe close(pipe); } static const QString s_waylandPlugin = QStringLiteral("KWinWaylandWaylandBackend"); static const QString s_x11Plugin = QStringLiteral("KWinWaylandX11Backend"); static const QString s_fbdevPlugin = QStringLiteral("KWinWaylandFbdevBackend"); #if HAVE_DRM static const QString s_drmPlugin = QStringLiteral("KWinWaylandDrmBackend"); #endif #if HAVE_LIBHYBRIS static const QString s_hwcomposerPlugin = QStringLiteral("KWinWaylandHwcomposerBackend"); #endif static const QString s_virtualPlugin = QStringLiteral("KWinWaylandVirtualBackend"); static QString automaticBackendSelection() { if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) { return s_waylandPlugin; } if (qEnvironmentVariableIsSet("DISPLAY")) { return s_x11Plugin; } #if HAVE_LIBHYBRIS if (qEnvironmentVariableIsSet("ANDROID_ROOT")) { return s_hwcomposerPlugin; } #endif #if HAVE_DRM return s_drmPlugin; #endif return s_fbdevPlugin; } static void disablePtrace() { #if HAVE_PR_SET_DUMPABLE // check whether we are running under a debugger const QFileInfo parent(QStringLiteral("/proc/%1/exe").arg(getppid())); if (parent.isSymLink() && (parent.symLinkTarget().endsWith(QLatin1String("/gdb")) || parent.symLinkTarget().endsWith(QLatin1String("/gdbserver")))) { // debugger, don't adjust return; } // disable ptrace in kwin_wayland prctl(PR_SET_DUMPABLE, 0); #endif #if HAVE_PROC_TRACE_CTL // FreeBSD's rudimentary procfs does not support /proc//exe // We could use the P_TRACED flag of the process to find out // if the process is being debugged ond FreeBSD. int mode = PROC_TRACE_CTL_DISABLE; procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode); #endif } static void unsetDumpable(int sig) { #if HAVE_PR_SET_DUMPABLE prctl(PR_SET_DUMPABLE, 1); #endif signal(sig, SIG_IGN); raise(sig); return; } -void gainRealTime() -{ -#if HAVE_SCHED_RESET_ON_FORK - const int minPriority = sched_get_priority_min(SCHED_RR); - struct sched_param sp; - sp.sched_priority = minPriority; - sched_setscheduler(0, SCHED_RR | SCHED_RESET_ON_FORK, &sp); -#endif -} - void dropNiceCapability() { #if HAVE_LIBCAP cap_t caps = cap_get_proc(); if (!caps) { return; } cap_value_t capList[] = { CAP_SYS_NICE }; if (cap_set_flag(caps, CAP_PERMITTED, 1, capList, CAP_CLEAR) == -1) { cap_free(caps); return; } if (cap_set_flag(caps, CAP_EFFECTIVE, 1, capList, CAP_CLEAR) == -1) { cap_free(caps); return; } cap_set_proc(caps); cap_free(caps); #endif } } // namespace int main(int argc, char * argv[]) { KWin::disablePtrace(); KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); KWin::gainRealTime(); KWin::dropNiceCapability(); if (signal(SIGTERM, KWin::sighandler) == SIG_IGN) signal(SIGTERM, SIG_IGN); if (signal(SIGINT, KWin::sighandler) == SIG_IGN) signal(SIGINT, SIG_IGN); if (signal(SIGHUP, KWin::sighandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); signal(SIGABRT, KWin::unsetDumpable); signal(SIGSEGV, KWin::unsetDumpable); signal(SIGPIPE, SIG_IGN); // ensure that no thread takes SIGUSR sigset_t userSignals; sigemptyset(&userSignals); sigaddset(&userSignals, SIGUSR1); sigaddset(&userSignals, SIGUSR2); pthread_sigmask(SIG_BLOCK, &userSignals, nullptr); QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); // enforce our internal qpa plugin, unfortunately command line switch has precedence setenv("QT_QPA_PLATFORM", "wayland-org.kde.kwin.qpa", true); qunsetenv("QT_DEVICE_PIXEL_RATIO"); qputenv("QT_IM_MODULE", "qtvirtualkeyboard"); qputenv("QSG_RENDER_LOOP", "basic"); QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); KWin::ApplicationWayland a(argc, argv); a.setupTranslator(); // reset QT_QPA_PLATFORM to a sane value for any processes started from KWin setenv("QT_QPA_PLATFORM", "wayland", true); KWin::Application::createAboutData(); + KQuickAddons::QtQuickSettings::init(); const auto availablePlugins = KPluginLoader::findPlugins(QStringLiteral("org.kde.kwin.waylandbackends")); auto hasPlugin = [&availablePlugins] (const QString &name) { return std::any_of(availablePlugins.begin(), availablePlugins.end(), [name] (const KPluginMetaData &plugin) { return plugin.pluginId() == name; } ); }; const bool hasWindowedOption = hasPlugin(KWin::s_x11Plugin) || hasPlugin(KWin::s_waylandPlugin); const bool hasSizeOption = hasPlugin(KWin::s_x11Plugin) || hasPlugin(KWin::s_virtualPlugin); const bool hasOutputCountOption = hasPlugin(KWin::s_x11Plugin); const bool hasX11Option = hasPlugin(KWin::s_x11Plugin); const bool hasVirtualOption = hasPlugin(KWin::s_virtualPlugin); const bool hasWaylandOption = hasPlugin(KWin::s_waylandPlugin); const bool hasFramebufferOption = hasPlugin(KWin::s_fbdevPlugin); #if HAVE_DRM const bool hasDrmOption = hasPlugin(KWin::s_drmPlugin); #endif #if HAVE_LIBHYBRIS const bool hasHwcomposerOption = hasPlugin(KWin::s_hwcomposerPlugin); #endif QCommandLineOption xwaylandOption(QStringLiteral("xwayland"), i18n("Start a rootless Xwayland server.")); QCommandLineOption waylandSocketOption(QStringList{QStringLiteral("s"), QStringLiteral("socket")}, i18n("Name of the Wayland socket to listen on. If not set \"wayland-0\" is used."), QStringLiteral("socket")); QCommandLineOption windowedOption(QStringLiteral("windowed"), i18n("Use a nested compositor in windowed mode.")); QCommandLineOption framebufferOption(QStringLiteral("framebuffer"), i18n("Render to framebuffer.")); QCommandLineOption framebufferDeviceOption(QStringLiteral("fb-device"), i18n("The framebuffer device to render to."), QStringLiteral("fbdev")); framebufferDeviceOption.setDefaultValue(QStringLiteral("/dev/fb0")); QCommandLineOption x11DisplayOption(QStringLiteral("x11-display"), i18n("The X11 Display to use in windowed mode on platform X11."), QStringLiteral("display")); QCommandLineOption waylandDisplayOption(QStringLiteral("wayland-display"), i18n("The Wayland Display to use in windowed mode on platform Wayland."), QStringLiteral("display")); QCommandLineOption virtualFbOption(QStringLiteral("virtual"), i18n("Render to a virtual framebuffer.")); QCommandLineOption widthOption(QStringLiteral("width"), i18n("The width for windowed mode. Default width is 1024."), QStringLiteral("width")); widthOption.setDefaultValue(QString::number(1024)); QCommandLineOption heightOption(QStringLiteral("height"), i18n("The height for windowed mode. Default height is 768."), QStringLiteral("height")); heightOption.setDefaultValue(QString::number(768)); QCommandLineOption scaleOption(QStringLiteral("scale"), i18n("The scale for windowed mode. Default value is 1."), QStringLiteral("scale")); scaleOption.setDefaultValue(QString::number(1)); QCommandLineOption outputCountOption(QStringLiteral("output-count"), i18n("The number of windows to open as outputs in windowed mode. Default value is 1"), QStringLiteral("height")); outputCountOption.setDefaultValue(QString::number(1)); QCommandLineParser parser; a.setupCommandLine(&parser); parser.addOption(xwaylandOption); parser.addOption(waylandSocketOption); if (hasWindowedOption) { parser.addOption(windowedOption); } if (hasX11Option) { parser.addOption(x11DisplayOption); } if (hasWaylandOption) { parser.addOption(waylandDisplayOption); } if (hasFramebufferOption) { parser.addOption(framebufferOption); parser.addOption(framebufferDeviceOption); } if (hasVirtualOption) { parser.addOption(virtualFbOption); } if (hasSizeOption) { parser.addOption(widthOption); parser.addOption(heightOption); parser.addOption(scaleOption); } if (hasOutputCountOption) { parser.addOption(outputCountOption); } #if HAVE_LIBHYBRIS QCommandLineOption hwcomposerOption(QStringLiteral("hwcomposer"), i18n("Use libhybris hwcomposer")); if (hasHwcomposerOption) { parser.addOption(hwcomposerOption); } #endif #if HAVE_INPUT QCommandLineOption libinputOption(QStringLiteral("libinput"), i18n("Enable libinput support for input events processing. Note: never use in a nested session.")); parser.addOption(libinputOption); #endif #if HAVE_DRM QCommandLineOption drmOption(QStringLiteral("drm"), i18n("Render through drm node.")); if (hasDrmOption) { parser.addOption(drmOption); } #endif QCommandLineOption inputMethodOption(QStringLiteral("inputmethod"), i18n("Input method that KWin starts."), QStringLiteral("path/to/imserver")); parser.addOption(inputMethodOption); QCommandLineOption listBackendsOption(QStringLiteral("list-backends"), i18n("List all available backends and quit.")); parser.addOption(listBackendsOption); QCommandLineOption screenLockerOption(QStringLiteral("lockscreen"), i18n("Starts the session in locked mode.")); parser.addOption(screenLockerOption); QCommandLineOption exitWithSessionOption(QStringLiteral("exit-with-session"), i18n("Exit after the session application, which is started by KWin, closed."), QStringLiteral("/path/to/session")); parser.addOption(exitWithSessionOption); -#ifdef KWIN_BUILD_ACTIVITIES - QCommandLineOption noActivitiesOption(QStringLiteral("no-kactivities"), - i18n("Disable KActivities integration.")); - parser.addOption(noActivitiesOption); -#endif - parser.addPositionalArgument(QStringLiteral("applications"), i18n("Applications to start once Wayland and Xwayland server are started"), QStringLiteral("[/path/to/application...]")); parser.process(a); a.processCommandLine(&parser); #ifdef KWIN_BUILD_ACTIVITIES - if (parser.isSet(noActivitiesOption)) { - a.setUseKActivities(false); - } + a.setUseKActivities(false); #endif if (parser.isSet(listBackendsOption)) { for (const auto &plugin: availablePlugins) { std::cout << std::setw(40) << std::left << qPrintable(plugin.name()) << qPrintable(plugin.description()) << std::endl; } return 0; } if (parser.isSet(exitWithSessionOption)) { a.setSessionArgument(parser.value(exitWithSessionOption)); } #if HAVE_INPUT KWin::Application::setUseLibinput(parser.isSet(libinputOption)); #endif QString pluginName; QSize initialWindowSize; QByteArray deviceIdentifier; int outputCount = 1; qreal outputScale = 1; #if HAVE_DRM if (hasDrmOption && parser.isSet(drmOption)) { pluginName = KWin::s_drmPlugin; } #endif if (hasSizeOption) { bool ok = false; const int width = parser.value(widthOption).toInt(&ok); if (!ok) { std::cerr << "FATAL ERROR incorrect value for width" << std::endl; return 1; } const int height = parser.value(heightOption).toInt(&ok); if (!ok) { std::cerr << "FATAL ERROR incorrect value for height" << std::endl; return 1; } const qreal scale = parser.value(scaleOption).toDouble(&ok); if (!ok || scale < 1) { std::cerr << "FATAL ERROR incorrect value for scale" << std::endl; return 1; } outputScale = scale; initialWindowSize = QSize(width, height); } if (hasOutputCountOption) { bool ok = false; const int count = parser.value(outputCountOption).toInt(&ok); if (ok) { outputCount = qMax(1, count); } } if (hasWindowedOption && parser.isSet(windowedOption)) { if (hasX11Option && parser.isSet(x11DisplayOption)) { deviceIdentifier = parser.value(x11DisplayOption).toUtf8(); } else if (!(hasWaylandOption && parser.isSet(waylandDisplayOption))) { deviceIdentifier = qgetenv("DISPLAY"); } if (!deviceIdentifier.isEmpty()) { pluginName = KWin::s_x11Plugin; } else if (hasWaylandOption) { if (parser.isSet(waylandDisplayOption)) { deviceIdentifier = parser.value(waylandDisplayOption).toUtf8(); } else { deviceIdentifier = qgetenv("WAYLAND_DISPLAY"); } if (!deviceIdentifier.isEmpty()) { pluginName = KWin::s_waylandPlugin; } } } if (hasFramebufferOption && parser.isSet(framebufferOption)) { pluginName = KWin::s_fbdevPlugin; deviceIdentifier = parser.value(framebufferDeviceOption).toUtf8(); } #if HAVE_LIBHYBRIS if (hasHwcomposerOption && parser.isSet(hwcomposerOption)) { pluginName = KWin::s_hwcomposerPlugin; } #endif if (hasVirtualOption && parser.isSet(virtualFbOption)) { pluginName = KWin::s_virtualPlugin; } if (pluginName.isEmpty()) { std::cerr << "No backend specified through command line argument, trying auto resolution" << std::endl; pluginName = KWin::automaticBackendSelection(); std::cerr << "Selected backend " << pluginName.toStdString() << std::endl; } auto pluginIt = std::find_if(availablePlugins.begin(), availablePlugins.end(), [&pluginName] (const KPluginMetaData &plugin) { return plugin.pluginId() == pluginName; } ); if (pluginIt == availablePlugins.end()) { std::cerr << "FATAL ERROR: could not find a backend" << std::endl; return 1; } // TODO: create backend without having the server running KWin::WaylandServer *server = KWin::WaylandServer::create(&a); KWin::WaylandServer::InitalizationFlags flags; if (parser.isSet(screenLockerOption)) { flags = KWin::WaylandServer::InitalizationFlag::LockScreen; } if (!server->init(parser.value(waylandSocketOption).toUtf8(), flags)) { std::cerr << "FATAL ERROR: could not create Wayland server" << std::endl; return 1; } a.initPlatform(*pluginIt); if (!a.platform()) { std::cerr << "FATAL ERROR: could not instantiate a backend" << std::endl; return 1; } if (!deviceIdentifier.isEmpty()) { a.platform()->setDeviceIdentifier(deviceIdentifier); } if (initialWindowSize.isValid()) { a.platform()->setInitialWindowSize(initialWindowSize); } a.platform()->setInitialOutputScale(outputScale); a.platform()->setInitialOutputCount(outputCount); QObject::connect(&a, &KWin::Application::workspaceCreated, server, &KWin::WaylandServer::initWorkspace); environment.insert(QStringLiteral("WAYLAND_DISPLAY"), server->display()->socketName()); a.setProcessStartupEnvironment(environment); a.setStartXwayland(parser.isSet(xwaylandOption)); a.setApplicationsToStart(parser.positionalArguments()); a.setInputMethodServerToStart(parser.value(inputMethodOption)); a.start(); return a.exec(); } diff --git a/main_x11.cpp b/main_x11.cpp index 1a9528506..765caefee 100644 --- a/main_x11.cpp +++ b/main_x11.cpp @@ -1,467 +1,469 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "main_x11.h" #include // kwin #include "platform.h" #include "sm.h" #include "workspace.h" #include "xcbutils.h" // KDE #include #include #include #include #include +#include // Qt #include #include #include #include #include #include #include #include #include #include // system #ifdef HAVE_UNISTD_H #include #endif // HAVE_UNISTD_H #include Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtCriticalMsg) namespace KWin { static void sighandler(int) { QApplication::exit(); } class AlternativeWMDialog : public QDialog { public: AlternativeWMDialog() : QDialog() { QWidget* mainWidget = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout(mainWidget); QString text = i18n( "KWin is unstable.\n" "It seems to have crashed several times in a row.\n" "You can select another window manager to run:"); QLabel* textLabel = new QLabel(text, mainWidget); layout->addWidget(textLabel); wmList = new QComboBox(mainWidget); wmList->setEditable(true); layout->addWidget(wmList); addWM(QStringLiteral("metacity")); addWM(QStringLiteral("openbox")); addWM(QStringLiteral("fvwm2")); addWM(QStringLiteral(KWIN_INTERNAL_NAME_X11)); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); buttons->button(QDialogButtonBox::Ok)->setDefault(true); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); mainLayout->addWidget(buttons); raise(); } void addWM(const QString& wm) { // TODO: Check if WM is installed if (!QStandardPaths::findExecutable(wm).isEmpty()) wmList->addItem(wm); } QString selectedWM() const { return wmList->currentText(); } private: QComboBox* wmList; }; //************************************ // KWinSelectionOwner //************************************ KWinSelectionOwner::KWinSelectionOwner(int screen_P) : KSelectionOwner(make_selection_atom(screen_P), screen_P) { } xcb_atom_t KWinSelectionOwner::make_selection_atom(int screen_P) { if (screen_P < 0) screen_P = QX11Info::appScreen(); QByteArray screen(QByteArrayLiteral("WM_S")); screen.append(QByteArray::number(screen_P)); ScopedCPointer atom(xcb_intern_atom_reply( connection(), xcb_intern_atom_unchecked(connection(), false, screen.length(), screen.constData()), nullptr)); if (atom.isNull()) { return XCB_ATOM_NONE; } return atom->atom; } void KWinSelectionOwner::getAtoms() { KSelectionOwner::getAtoms(); if (xa_version == XCB_ATOM_NONE) { const QByteArray name(QByteArrayLiteral("VERSION")); ScopedCPointer atom(xcb_intern_atom_reply( connection(), xcb_intern_atom_unchecked(connection(), false, name.length(), name.constData()), nullptr)); if (!atom.isNull()) { xa_version = atom->atom; } } } void KWinSelectionOwner::replyTargets(xcb_atom_t property_P, xcb_window_t requestor_P) { KSelectionOwner::replyTargets(property_P, requestor_P); xcb_atom_t atoms[ 1 ] = { xa_version }; // PropModeAppend ! xcb_change_property(connection(), XCB_PROP_MODE_APPEND, requestor_P, property_P, XCB_ATOM_ATOM, 32, 1, atoms); } bool KWinSelectionOwner::genericReply(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P) { if (target_P == xa_version) { int32_t version[] = { 2, 0 }; xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, requestor_P, property_P, XCB_ATOM_INTEGER, 32, 2, version); } else return KSelectionOwner::genericReply(target_P, property_P, requestor_P); return true; } xcb_atom_t KWinSelectionOwner::xa_version = XCB_ATOM_NONE; //************************************ // ApplicationX11 //************************************ ApplicationX11::ApplicationX11(int &argc, char **argv) : Application(OperationModeX11, argc, argv) , owner() , m_replace(false) { setX11Connection(QX11Info::connection()); setX11RootWindow(QX11Info::appRootWindow()); } ApplicationX11::~ApplicationX11() { destroyCompositor(); destroyWorkspace(); if (!owner.isNull() && owner->ownerWindow() != XCB_WINDOW_NONE) // If there was no --replace (no new WM) Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); } void ApplicationX11::setReplace(bool replace) { m_replace = replace; } void ApplicationX11::lostSelection() { sendPostedEvents(); destroyCompositor(); destroyWorkspace(); // Remove windowmanager privileges Xcb::selectInput(rootWindow(), XCB_EVENT_MASK_PROPERTY_CHANGE); quit(); } void ApplicationX11::performStartup() { crashChecking(); if (Application::x11ScreenNumber() == -1) { Application::setX11ScreenNumber(QX11Info::appScreen()); } // QSessionManager for some reason triggers a very early commitDataRequest // and updates the key - before we create the workspace and load the session // data -> store and pass to the workspace constructor m_originalSessionKey = sessionKey(); owner.reset(new KWinSelectionOwner(Application::x11ScreenNumber())); connect(owner.data(), &KSelectionOwner::failedToClaimOwnership, []{ fputs(i18n("kwin: unable to claim manager selection, another wm running? (try using --replace)\n").toLocal8Bit().constData(), stderr); ::exit(1); }); connect(owner.data(), SIGNAL(lostOwnership()), SLOT(lostSelection())); connect(owner.data(), &KSelectionOwner::claimedOwnership, [this]{ setupEventFilters(); // first load options - done internally by a different thread createOptions(); // Check whether another windowmanager is running const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; ScopedCPointer redirectCheck(xcb_request_check(connection(), xcb_change_window_attributes_checked(connection(), rootWindow(), XCB_CW_EVENT_MASK, maskValues))); if (!redirectCheck.isNull()) { fputs(i18n("kwin: another window manager is running (try using --replace)\n").toLocal8Bit().constData(), stderr); if (!wasCrash()) // if this is a crash-restart, DrKonqi may have stopped the process w/o killing the connection ::exit(1); } createInput(); connect(platform(), &Platform::screensQueried, this, [this] { createWorkspace(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort notifyKSplash(); } ); connect(platform(), &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; ::exit(1); } ); platform()->init(); }); // we need to do an XSync here, otherwise the QPA might crash us later on Xcb::sync(); owner->claim(m_replace || wasCrash(), true); createAtoms(); } bool ApplicationX11::notify(QObject* o, QEvent* e) { if (Workspace::self()->workspaceEvent(e)) return true; return QApplication::notify(o, e); } void ApplicationX11::setupCrashHandler() { KCrash::setEmergencySaveFunction(ApplicationX11::crashHandler); } void ApplicationX11::crashChecking() { setupCrashHandler(); if (crashes >= 4) { // Something has gone seriously wrong AlternativeWMDialog dialog; QString cmd = QStringLiteral(KWIN_INTERNAL_NAME_X11); if (dialog.exec() == QDialog::Accepted) cmd = dialog.selectedWM(); else ::exit(1); if (cmd.length() > 500) { qCDebug(KWIN_CORE) << "Command is too long, truncating"; cmd = cmd.left(500); } qCDebug(KWIN_CORE) << "Starting" << cmd << "and exiting"; char buf[1024]; sprintf(buf, "%s &", cmd.toAscii().data()); system(buf); ::exit(1); } if (crashes >= 2) { // Disable compositing if we have had too many crashes qCDebug(KWIN_CORE) << "Too many crashes recently, disabling compositing"; KConfigGroup compgroup(KSharedConfig::openConfig(), "Compositing"); compgroup.writeEntry("Enabled", false); } // Reset crashes count if we stay up for more that 15 seconds QTimer::singleShot(15 * 1000, this, SLOT(resetCrashesCount())); } void ApplicationX11::crashHandler(int signal) { crashes++; fprintf(stderr, "Application::crashHandler() called with signal %d; recent crashes: %d\n", signal, crashes); char cmd[1024]; sprintf(cmd, "%s --crashes %d &", QFile::encodeName(QCoreApplication::applicationFilePath()).constData(), crashes); sleep(1); system(cmd); } } // namespace extern "C" KWIN_EXPORT int kdemain(int argc, char * argv[]) { KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); int primaryScreen = 0; xcb_connection_t *c = xcb_connect(nullptr, &primaryScreen); if (!c || xcb_connection_has_error(c)) { fprintf(stderr, "%s: FATAL ERROR while trying to open display %s\n", argv[0], qgetenv("DISPLAY").constData()); exit(1); } const int number_of_screens = xcb_setup_roots_length(xcb_get_setup(c)); xcb_disconnect(c); c = nullptr; // multi head auto isMultiHead = []() -> bool { QByteArray multiHead = qgetenv("KDE_MULTIHEAD"); if (!multiHead.isEmpty()) { return (multiHead.toLower() == "true"); } return true; }; if (number_of_screens != 1 && isMultiHead()) { KWin::Application::setX11MultiHead(true); KWin::Application::setX11ScreenNumber(primaryScreen); int pos; // Temporarily needed to reconstruct DISPLAY var if multi-head QByteArray display_name = qgetenv("DISPLAY"); if ((pos = display_name.lastIndexOf('.')) != -1) display_name.remove(pos, 10); // 10 is enough to be sure we removed ".s" QString envir; for (int i = 0; i < number_of_screens; i++) { // If execution doesn't pass by here, then kwin // acts exactly as previously if (i != KWin::Application::x11ScreenNumber() && fork() == 0) { KWin::Application::setX11ScreenNumber(i); QByteArray dBusSuffix = qgetenv("KWIN_DBUS_SERVICE_SUFFIX"); if (!dBusSuffix.isNull()) { dBusSuffix.append("."); } dBusSuffix.append(QByteArrayLiteral("head-")).append(QByteArray::number(i)); qputenv("KWIN_DBUS_SERVICE_SUFFIX", dBusSuffix); // Break here because we are the child process, we don't // want to fork() anymore break; } } // In the next statement, display_name shouldn't contain a screen // number. If it had it, it was removed at the "pos" check envir.sprintf("DISPLAY=%s.%d", display_name.data(), KWin::Application::x11ScreenNumber()); if (putenv(strdup(envir.toAscii().constData()))) { fprintf(stderr, "%s: WARNING: unable to set DISPLAY environment variable\n", argv[0]); perror("putenv()"); } } if (signal(SIGTERM, KWin::sighandler) == SIG_IGN) signal(SIGTERM, SIG_IGN); if (signal(SIGINT, KWin::sighandler) == SIG_IGN) signal(SIGINT, SIG_IGN); if (signal(SIGHUP, KWin::sighandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN); // Disable the glib event loop integration, since it seems to be responsible // for several bug reports about high CPU usage (bug #239963) setenv("QT_NO_GLIB", "1", true); // enforce xcb plugin, unfortunately command line switch has precedence setenv("QT_QPA_PLATFORM", "xcb", true); qunsetenv("QT_DEVICE_PIXEL_RATIO"); QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); KWin::ApplicationX11 a(argc, argv); a.setupTranslator(); KWin::Application::createAboutData(); + KQuickAddons::QtQuickSettings::init(); QCommandLineOption replaceOption(QStringLiteral("replace"), i18n("Replace already-running ICCCM2.0-compliant window manager")); QCommandLineParser parser; a.setupCommandLine(&parser); parser.addOption(replaceOption); #ifdef KWIN_BUILD_ACTIVITIES QCommandLineOption noActivitiesOption(QStringLiteral("no-kactivities"), i18n("Disable KActivities integration.")); parser.addOption(noActivitiesOption); #endif parser.process(a); a.processCommandLine(&parser); a.setReplace(parser.isSet(replaceOption)); #ifdef KWIN_BUILD_ACTIVITIES if (parser.isSet(noActivitiesOption)) { a.setUseKActivities(false); } #endif // perform sanity checks if (a.platformName().toLower() != QStringLiteral("xcb")) { fprintf(stderr, "%s: FATAL ERROR expecting platform xcb but got platform %s\n", argv[0], qPrintable(a.platformName())); exit(1); } if (!QX11Info::display()) { fprintf(stderr, "%s: FATAL ERROR KWin requires Xlib support in the xcb plugin. Do not configure Qt with -no-xcb-xlib\n", argv[0]); exit(1); } // find and load the X11 platform plugin const auto plugins = KPluginLoader::findPluginsById(QStringLiteral("org.kde.kwin.platforms"), QStringLiteral("KWinX11Platform")); if (plugins.isEmpty()) { std::cerr << "FATAL ERROR: KWin could not find the KWinX11Platform plugin" << std::endl; return 1; } a.initPlatform(plugins.first()); if (!a.platform()) { std::cerr << "FATAL ERROR: could not instantiate the platform plugin" << std::endl; return 1; } a.start(); KWin::SessionSaveDoneHelper helper; Q_UNUSED(helper); // The sessionsavedonehelper opens a side channel to the smserver, // listens for events and talks to it, so it needs to be created. return a.exec(); } diff --git a/org.kde.kwin.ColorCorrect.xml b/org.kde.kwin.ColorCorrect.xml new file mode 100644 index 000000000..0b83a0129 --- /dev/null +++ b/org.kde.kwin.ColorCorrect.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.kde.kwin.OrientationSensor.xml b/org.kde.kwin.OrientationSensor.xml new file mode 100644 index 000000000..12f245c62 --- /dev/null +++ b/org.kde.kwin.OrientationSensor.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/orientation_sensor.cpp b/orientation_sensor.cpp new file mode 100644 index 000000000..c78a3794f --- /dev/null +++ b/orientation_sensor.cpp @@ -0,0 +1,153 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 "orientation_sensor.h" +#include + +#include +#include + +#include +#include +#include + +namespace KWin +{ + +OrientationSensor::OrientationSensor(QObject *parent) + : QObject(parent) + , m_sensor(new QOrientationSensor(this)) +{ + connect(m_sensor, &QOrientationSensor::readingChanged, this, + [this] { + auto toOrientation = [] (auto reading) { + switch (reading->orientation()) { + case QOrientationReading::Undefined: + return OrientationSensor::Orientation::Undefined; + case QOrientationReading::TopUp: + return OrientationSensor::Orientation::TopUp; + case QOrientationReading::TopDown: + return OrientationSensor::Orientation::TopDown; + case QOrientationReading::LeftUp: + return OrientationSensor::Orientation::LeftUp; + case QOrientationReading::RightUp: + return OrientationSensor::Orientation::RightUp; + case QOrientationReading::FaceUp: + return OrientationSensor::Orientation::FaceUp; + case QOrientationReading::FaceDown: + return OrientationSensor::Orientation::FaceDown; + default: + Q_UNREACHABLE(); + } + }; + const auto orientation = toOrientation(m_sensor->reading()); + if (m_orientation != orientation) { + m_orientation = orientation; + emit orientationChanged(); + } + } + ); + connect(m_sensor, &QOrientationSensor::activeChanged, this, + [this] { + if (!m_sni) { + return; + } + if (m_sensor->isActive()) { + m_sni->setToolTipTitle(i18n("Automatic screen rotation is enabled")); + } else { + m_sni->setToolTipTitle(i18n("Automatic screen rotation is disabled")); + } + } + ); +} + +OrientationSensor::~OrientationSensor() = default; + +void OrientationSensor::setEnabled(bool enabled) +{ + if (m_enabled == enabled) { + return; + } + m_enabled = enabled; + if (m_enabled) { + loadConfig(); + setupStatusNotifier(); + m_adaptor = new OrientationSensorAdaptor(this); + } else { + delete m_sni; + m_sni = nullptr; + delete m_adaptor; + m_adaptor = nullptr; + } + startStopSensor(); +} + +void OrientationSensor::loadConfig() +{ + if (!m_config) { + return; + } + m_userEnabled = m_config->group("OrientationSensor").readEntry("Enabled", true); +} + +void OrientationSensor::setupStatusNotifier() +{ + if (m_sni) { + return; + } + m_sni = new KStatusNotifierItem(QStringLiteral("kwin-automatic-rotation"), this); + m_sni->setStandardActionsEnabled(false); + m_sni->setCategory(KStatusNotifierItem::Hardware); + m_sni->setStatus(KStatusNotifierItem::Passive); + m_sni->setTitle(i18n("Automatic Screen Rotation")); + // TODO: proper icon with state + m_sni->setIconByName(QStringLiteral("preferences-desktop-display")); + // we start disabled, it gets updated when the sensor becomes active + m_sni->setToolTipTitle(i18n("Automatic screen rotation is disabled")); + connect(m_sni, &KStatusNotifierItem::activateRequested, this, + [this] { + m_userEnabled = !m_userEnabled; + startStopSensor(); + emit userEnabledChanged(m_userEnabled); + } + ); +} + +void OrientationSensor::startStopSensor() +{ + if (m_enabled && m_userEnabled) { + m_sensor->start(); + } else { + m_sensor->stop(); + } +} + +void OrientationSensor::setUserEnabled(bool enabled) +{ + if (m_userEnabled == enabled) { + return; + } + m_userEnabled = enabled; + if (m_config) { + m_config->group("OrientationSensor").writeEntry("Enabled", m_userEnabled); + } + emit userEnabledChanged(m_userEnabled); +} + +} diff --git a/orientation_sensor.h b/orientation_sensor.h new file mode 100644 index 000000000..29b2ef703 --- /dev/null +++ b/orientation_sensor.h @@ -0,0 +1,91 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 . +*********************************************************************/ +#pragma once + +#include + +#include + +#include + +class QOrientationSensor; +class OrientationSensorAdaptor; +class KStatusNotifierItem; + +namespace KWin +{ + +class KWIN_EXPORT OrientationSensor : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.OrientationSensor") + Q_PROPERTY(bool userEnabled READ isUserEnabled WRITE setUserEnabled NOTIFY userEnabledChanged) +public: + explicit OrientationSensor(QObject *parent = nullptr); + ~OrientationSensor(); + + void setEnabled(bool enabled); + + /** + * Just like QOrientationReading::Orientation, + * copied to not leak the QSensors API into internal API. + **/ + enum class Orientation { + Undefined, + TopUp, + TopDown, + LeftUp, + RightUp, + FaceUp, + FaceDown + }; + + Orientation orientation() const { + return m_orientation; + } + + void setConfig(KSharedConfig::Ptr config) { + m_config = config; + } + + bool isUserEnabled() const { + return m_userEnabled; + } + void setUserEnabled(bool enabled); + +Q_SIGNALS: + void orientationChanged(); + void userEnabledChanged(bool); + +private: + void setupStatusNotifier(); + void startStopSensor(); + void loadConfig(); + QOrientationSensor *m_sensor; + bool m_enabled = false; + bool m_userEnabled = true; + Orientation m_orientation = Orientation::Undefined; + KStatusNotifierItem *m_sni = nullptr; + KSharedConfig::Ptr m_config; + OrientationSensorAdaptor *m_adaptor = nullptr; + +}; + +} diff --git a/packageplugins/aurorae/kwin-packagestructure-aurorae.desktop b/packageplugins/aurorae/kwin-packagestructure-aurorae.desktop index 40bf4b0c2..f71461716 100644 --- a/packageplugins/aurorae/kwin-packagestructure-aurorae.desktop +++ b/packageplugins/aurorae/kwin-packagestructure-aurorae.desktop @@ -1,40 +1,43 @@ [Desktop Entry] Name=KWin Aurorae Name[ca]=Aurorae del KWin Name[ca@valencia]=Aurorae del KWin Name[cs]=KWin Aurorae +Name[da]=KWin Aurorae Name[de]=KWin Aurorae Name[el]=KWin Aurorae Name[en_GB]=KWin Aurorae Name[es]=Aurorae de KWin Name[eu]=KWin Aurorae Name[fi]=KWin Aurorae Name[fr]=Module Aurorae de KWin Name[gl]=Aurorae de KWin Name[hu]=KWin Aurorae Name[it]=Aurorae di Kwin +Name[ko]=KWin Aurorae Name[nl]=KWin Aurorae Name[nn]=KWin Aurorae Name[pl]=KWin Aurorae Name[pt]=Aurora do KWin Name[pt_BR]=KWin Aurorae +Name[ru]=Оформление окон Aurorae для KWin Name[sk]=KWin Aurorae Name[sl]=KWin Aurorae Name[sr]=К‑винова аурора Name[sr@ijekavian]=К‑винова аурора Name[sr@ijekavianlatin]=KWinova aurora Name[sr@latin]=KWinova aurora Name[sv]=Kwin Aurora Name[tr]=KWin Aurorae Name[uk]=KWin Aurorae Name[x-test]=xxKWin Auroraexx Name[zh_CN]=KWin 极光 Name[zh_TW]=KWin Aurorae Type=Service X-KDE-ServiceTypes=KPackage/PackageStructure X-KDE-Library=kwin_packagestructure_aurorae X-KDE-PluginInfo-Author=Demitrius Belai X-KDE-PluginInfo-Email=demitriusbelai@gmail.com X-KDE-PluginInfo-Name=KWin/Aurorae X-KDE-PluginInfo-Version=1 diff --git a/packageplugins/decoration/kwin-packagestructure-decoration.desktop b/packageplugins/decoration/kwin-packagestructure-decoration.desktop index 1eb36ac70..e277e0501 100644 --- a/packageplugins/decoration/kwin-packagestructure-decoration.desktop +++ b/packageplugins/decoration/kwin-packagestructure-decoration.desktop @@ -1,43 +1,46 @@ [Desktop Entry] Name=KWin Decoration Name[ca]=Decoració del KWin Name[ca@valencia]=Decoració del KWin Name[cs]=Dekorace KWin +Name[da]=KWin-dekoration Name[de]=KWin-Dekoration Name[el]=Διακοσμήσεις KWin Name[en_GB]=KWin Decoration Name[es]=Decoración de KWin Name[eu]=KWin apainketa Name[fi]=KWin-ikkunakehykset Name[fr]=Décorations KWin Name[gl]=Decoración de KWin Name[hu]=KWin dekoráció Name[ia]=Decorationes de KWin Name[it]=Decorazioni di KWin +Name[ko]=KWin 장식 Name[lt]=Kwin dekoracijos Name[nl]=KWin-decoraties Name[nn]=KWin-dekorasjon Name[pa]=KWin ਸਜਾਵਟ Name[pl]=Wygląd KWin Name[pt]=Decorações do KWin Name[pt_BR]=Decoração do KWin +Name[ru]=Оформление окон для KWin Name[sk]=Dekorácie KWin Name[sl]=Okraski KWin Name[sr]=К‑винова декорација Name[sr@ijekavian]=К‑винова декорација Name[sr@ijekavianlatin]=KWinova dekoracija Name[sr@latin]=KWinova dekoracija Name[sv]=Kwin-dekoration Name[tr]=KWin Dekorasyonu Name[uk]=Обрамлення вікон KWin Name[x-test]=xxKWin Decorationxx Name[zh_CN]=KWin 装饰 Name[zh_TW]=KWin 裝飾 Type=Service X-KDE-ServiceTypes=KPackage/PackageStructure X-KDE-Library=kwin_packagestructure_decoration X-KDE-PluginInfo-Author=Demitrius Belai X-KDE-PluginInfo-Email=demitriusbelai@gmail.com X-KDE-PluginInfo-Name=KWin/Decoration X-KDE-PluginInfo-Version=1 diff --git a/packageplugins/scripts/kwin-packagestructure-scripts.desktop b/packageplugins/scripts/kwin-packagestructure-scripts.desktop index a5fd8476d..db280f30d 100644 --- a/packageplugins/scripts/kwin-packagestructure-scripts.desktop +++ b/packageplugins/scripts/kwin-packagestructure-scripts.desktop @@ -1,45 +1,46 @@ [Desktop Entry] Name=KWin Script Name[ca]=Script del KWin Name[ca@valencia]=Script del KWin Name[cs]=Skript KWinu Name[da]=KWin-script Name[de]=KWin-Skript Name[el]=Σενάριο KWin Name[en_GB]=KWin Script Name[es]=Guion de KWin Name[eu]=KWin scripta Name[fi]=KWin-skripti Name[fr]=Script KWin Name[gl]=Script de KWin Name[hu]=KWin szkript Name[ia]=Script de KWin Name[it]=Script di KWin Name[ko]=KWin 스크립트 Name[lt]=KWin scenarijus Name[nl]=KWin-script Name[nn]=KWin-skript Name[pa]=KWin ਸਕ੍ਰਿਪਟ Name[pl]=Skrypt KWin Name[pt]=Programa do KWin Name[pt_BR]=Script do KWin +Name[ru]=Сценарий KWin Name[sk]=KWin skript Name[sl]=Skript KWin Name[sr]=К‑винова скрипта Name[sr@ijekavian]=К‑винова скрипта Name[sr@ijekavianlatin]=KWinova skripta Name[sr@latin]=KWinova skripta Name[sv]=Kwin-skript Name[tr]=KWin Betiği Name[uk]=Скрипт KWin Name[x-test]=xxKWin Scriptxx Name[zh_CN]=KWin 脚本 Name[zh_TW]=KWin 指令稿 Type=Service X-KDE-ServiceTypes=KPackage/PackageStructure X-KDE-Library=kwin_packagestructure_scripts X-KDE-PluginInfo-Author=Marco Martin X-KDE-PluginInfo-Email=notmart@gmail.com X-KDE-PluginInfo-Name=KWin/Script X-KDE-PluginInfo-Version=1 diff --git a/packageplugins/windowswitcher/kwin-packagestructure-windowswitcher.desktop b/packageplugins/windowswitcher/kwin-packagestructure-windowswitcher.desktop index 69e723223..24a56af86 100644 --- a/packageplugins/windowswitcher/kwin-packagestructure-windowswitcher.desktop +++ b/packageplugins/windowswitcher/kwin-packagestructure-windowswitcher.desktop @@ -1,45 +1,46 @@ [Desktop Entry] Name=KWin Window Switcher Name[ca]=Commutador de finestres del KWin Name[ca@valencia]=Commutador de finestres del KWin Name[cs]=Přepínač oken KWin Name[da]=KWin vinduesskifter Name[de]=KWin-Fensterwechsler Name[el]=Εφαρμογή εναλλαγής παραθύρων Kwin Name[en_GB]=KWin Window Switcher Name[es]=Cambiador de ventanas de KWin Name[eu]=KWin leiho-aldatzailea Name[fi]=KWin-ikkunanvalitsin Name[fr]=Sélecteur de fenêtres de KWin Name[gl]=Selector de xanela de KWin Name[hu]=KWin ablakváltó Name[ia]=Commutator de fenestra de KWin Name[it]=Scambiafinestre di KWin Name[ko]=KWin 창 전환기 Name[lt]=KWin langų perjungiklis Name[nl]=KWin-vensterwisselaar Name[nn]=KWin-vindaugsbytar Name[pa]=KWin ਵਿੰਡੋ ਸਵਿੱਚਰ Name[pl]=Przełącznik okien KWin Name[pt]=Mudança de Janelas do KWin Name[pt_BR]=Layout do seletor de janelas do KWin +Name[ru]=Переключатель окон для KWin Name[sk]=Prepínač okien KWin Name[sl]=Preklopnik oken KWin Name[sr]=К‑винов мењач прозора Name[sr@ijekavian]=К‑винов мењач прозора Name[sr@ijekavianlatin]=KWinov menjač prozora Name[sr@latin]=KWinov menjač prozora Name[sv]=Kwin-fönsterbyte Name[tr]=KWin Pencere Değiştirici Name[uk]=Перемикач вікон KWin Name[x-test]=xxKWin Window Switcherxx Name[zh_CN]=KWin 窗口切换器 Name[zh_TW]=KWin 視窗切換器 Type=Service X-KDE-ServiceTypes=KPackage/PackageStructure X-KDE-Library=kwin_packagestructure_windowswitcher X-KDE-PluginInfo-Author=Marco Martin X-KDE-PluginInfo-Email=notmart@gmail.com X-KDE-PluginInfo-Name=KWin/WindowSwitcher X-KDE-PluginInfo-Version=1 diff --git a/platform.cpp b/platform.cpp index 95269154e..65ce0259d 100644 --- a/platform.cpp +++ b/platform.cpp @@ -1,480 +1,488 @@ /******************************************************************** 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 "platform.h" #include #include "composite.h" #include "cursor.h" #include "effects.h" #include "input.h" #include #include "overlaywindow.h" #include "outline.h" #include "pointer_input.h" #include "scene.h" #include "screenedge.h" #include "wayland_server.h" +#include "colorcorrection/manager.h" + #include namespace KWin { Platform::Platform(QObject *parent) : QObject(parent) , m_eglDisplay(EGL_NO_DISPLAY) { + m_colorCorrect = new ColorCorrect::Manager(this); } Platform::~Platform() { if (m_eglDisplay != EGL_NO_DISPLAY) { eglTerminate(m_eglDisplay); } } QImage Platform::softwareCursor() const { return input()->pointer()->cursorImage(); } QPoint Platform::softwareCursorHotspot() const { return input()->pointer()->cursorHotSpot(); } PlatformCursorImage Platform::cursorImage() const { return PlatformCursorImage(softwareCursor(), softwareCursorHotspot()); } void Platform::hideCursor() { m_hideCursorCounter++; if (m_hideCursorCounter == 1) { doHideCursor(); } } void Platform::doHideCursor() { } void Platform::showCursor() { m_hideCursorCounter--; if (m_hideCursorCounter == 0) { doShowCursor(); } } void Platform::doShowCursor() { } Screens *Platform::createScreens(QObject *parent) { Q_UNUSED(parent) return nullptr; } OpenGLBackend *Platform::createOpenGLBackend() { return nullptr; } QPainterBackend *Platform::createQPainterBackend() { return nullptr; } Edge *Platform::createScreenEdge(ScreenEdges *edges) { return new Edge(edges); } void Platform::createPlatformCursor(QObject *parent) { new InputRedirectionCursor(parent); } void Platform::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { Q_UNUSED(config) qCWarning(KWIN_CORE) << "This backend does not support configuration changes."; // KCoreAddons needs kwayland's 2b3f9509ac1 to not crash if (KCoreAddons::version() >= QT_VERSION_CHECK(5, 39, 0)) { config->setFailed(); } } void Platform::setSoftWareCursor(bool set) { if (m_softWareCursor == set) { return; } m_softWareCursor = set; if (m_softWareCursor) { connect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); connect(this, &Platform::cursorChanged, this, &Platform::triggerCursorRepaint); } else { disconnect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); disconnect(this, &Platform::cursorChanged, this, &Platform::triggerCursorRepaint); } } void Platform::triggerCursorRepaint() { if (!Compositor::self()) { return; } Compositor::self()->addRepaint(m_cursor.lastRenderedGeometry); Compositor::self()->addRepaint(QRect(Cursor::pos() - softwareCursorHotspot(), softwareCursor().size())); } void Platform::markCursorAsRendered() { if (m_softWareCursor) { m_cursor.lastRenderedGeometry = QRect(Cursor::pos() - softwareCursorHotspot(), softwareCursor().size()); } if (input()->pointer()) { input()->pointer()->markCursorAsRendered(); } } void Platform::keyboardKeyPressed(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyPressed, time); } void Platform::keyboardKeyReleased(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyReleased, time); } void Platform::keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!input()) { return; } input()->processKeyboardModifiers(modsDepressed, modsLatched, modsLocked, group); } void Platform::keymapChange(int fd, uint32_t size) { if (!input()) { return; } input()->processKeymapChange(fd, size); } void Platform::pointerAxisHorizontal(qreal delta, quint32 time) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisHorizontal, delta, time); } void Platform::pointerAxisVertical(qreal delta, quint32 time) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisVertical, delta, time); } void Platform::pointerButtonPressed(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonPressed, time); } void Platform::pointerButtonReleased(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonReleased, time); } void Platform::pointerMotion(const QPointF &position, quint32 time) { if (!input()) { return; } input()->processPointerMotion(position, time); } void Platform::touchCancel() { if (!input()) { return; } input()->cancelTouch(); } void Platform::touchDown(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchDown(id, pos, time); } void Platform::touchFrame() { if (!input()) { return; } input()->touchFrame(); } void Platform::touchMotion(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchMotion(id, pos, time); } void Platform::touchUp(qint32 id, quint32 time) { if (!input()) { return; } input()->processTouchUp(id, time); } void Platform::processSwipeGestureBegin(int fingerCount, quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureBegin(fingerCount, time); } void Platform::processSwipeGestureUpdate(const QSizeF &delta, quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureUpdate(delta, time); } void Platform::processSwipeGestureEnd(quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureEnd(time); } void Platform::processSwipeGestureCancelled(quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureCancelled(time); } void Platform::processPinchGestureBegin(int fingerCount, quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureBegin(fingerCount, time); } void Platform::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureUpdate(scale, angleDelta, delta, time); } void Platform::processPinchGestureEnd(quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureEnd(time); } void Platform::processPinchGestureCancelled(quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureCancelled(time); } void Platform::repaint(const QRect &rect) { if (!Compositor::self()) { return; } Compositor::self()->addRepaint(rect); } void Platform::setReady(bool ready) { if (m_ready == ready) { return; } m_ready = ready; emit readyChanged(m_ready); } void Platform::warpPointer(const QPointF &globalPos) { Q_UNUSED(globalPos) } bool Platform::supportsQpaContext() const { if (Compositor *c = Compositor::self()) { return c->scene()->openGLPlatformInterfaceExtensions().contains(QByteArrayLiteral("EGL_KHR_surfaceless_context")); } return false; } EGLDisplay KWin::Platform::sceneEglDisplay() const { return m_eglDisplay; } void Platform::setSceneEglDisplay(EGLDisplay display) { m_eglDisplay = display; } QSize Platform::screenSize() const { return QSize(); } QVector Platform::screenGeometries() const { return QVector({QRect(QPoint(0, 0), screenSize())}); } QVector Platform::screenScales() const { return QVector({1}); } bool Platform::requiresCompositing() const { return true; } bool Platform::compositingPossible() const { return true; } QString Platform::compositingNotPossibleReason() const { return QString(); } bool Platform::openGLCompositingIsBroken() const { return false; } void Platform::createOpenGLSafePoint(OpenGLSafePoint safePoint) { Q_UNUSED(safePoint) } void Platform::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (!input()) { callback(nullptr); return; } input()->startInteractiveWindowSelection(callback, cursorName); } void Platform::startInteractivePositionSelection(std::function callback) { if (!input()) { callback(QPoint(-1, -1)); return; } input()->startInteractivePositionSelection(callback); } void Platform::setupActionForGlobalAccel(QAction *action) { Q_UNUSED(action) } OverlayWindow *Platform::createOverlayWindow() { return nullptr; } void Platform::updateXTime() { } OutlineVisual *Platform::createOutline(Outline *outline) { if (Compositor::compositing()) { return new CompositedOutlineVisual(outline); } return nullptr; } Decoration::Renderer *Platform::createDecorationRenderer(Decoration::DecoratedClientImpl *client) { if (Compositor::self()->hasScene()) { return Compositor::self()->scene()->createDecorationRenderer(client); } return nullptr; } void Platform::invertScreen() { if (effects) { if (Effect *inverter = static_cast(effects)->provides(Effect::ScreenInversion)) { qCDebug(KWIN_CORE) << "inverting screen using Effect plugin"; QMetaObject::invokeMethod(inverter, "toggleScreenInversion", Qt::DirectConnection); } } } void Platform::createEffectsHandler(Compositor *compositor, Scene *scene) { new EffectsHandlerImpl(compositor, scene); } +QString Platform::supportInformation() const +{ + return QStringLiteral("Name: %1\n").arg(metaObject()->className()); +} + } diff --git a/platform.h b/platform.h index 45855df51..e88dff529 100644 --- a/platform.h +++ b/platform.h @@ -1,488 +1,528 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_PLATFORM_H #define KWIN_PLATFORM_H #include #include #include #include #include #include #include class QAction; namespace KWayland { namespace Server { class OutputConfigurationInterface; } } namespace KWin { +namespace ColorCorrect { +class Manager; +struct GammaRamp; +} class Edge; class Compositor; class OverlayWindow; class OpenGLBackend; class Outline; class OutlineVisual; class QPainterBackend; class Scene; class Screens; class ScreenEdges; class Toplevel; class WaylandCursorTheme; namespace Decoration { class Renderer; class DecoratedClientImpl; } class KWIN_EXPORT Platform : public QObject { Q_OBJECT public: virtual ~Platform(); virtual void init() = 0; virtual Screens *createScreens(QObject *parent = nullptr); virtual OpenGLBackend *createOpenGLBackend(); virtual QPainterBackend *createQPainterBackend(); /** * Allows the platform to create a platform specific screen edge. * The default implementation creates a Edge. **/ virtual Edge *createScreenEdge(ScreenEdges *parent); /** * Allows the platform to create a platform specific Cursor. * The default implementation creates an InputRedirectionCursor. **/ virtual void createPlatformCursor(QObject *parent = nullptr); virtual void warpPointer(const QPointF &globalPos); /** * Whether our Compositing EGL display allows a surface less context * so that a sharing context could be created. **/ virtual bool supportsQpaContext() const; /** * The EGLDisplay used by the compositing scene. **/ EGLDisplay sceneEglDisplay() const; void setSceneEglDisplay(EGLDisplay display); /** * The EGLContext used by the compositing scene. **/ virtual EGLContext sceneEglContext() const { return m_context; } /** * Sets the @p context used by the compositing scene. **/ void setSceneEglContext(EGLContext context) { m_context = context; } /** * The first (in case of multiple) EGLSurface used by the compositing scene. **/ EGLSurface sceneEglSurface() const { return m_surface; } /** * Sets the first @p surface used by the compositing scene. * @see sceneEglSurface **/ void setSceneEglSurface(EGLSurface surface) { m_surface = surface; } /** * The EglConfig used by the compositing scene. **/ EGLConfig sceneEglConfig() const { return m_eglConfig; } /** * Sets the @p config used by the compositing scene. * @see sceneEglConfig **/ void setSceneEglConfig(EGLConfig config) { m_eglConfig = config; } /** * Implementing subclasses should provide a size in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns an invalid size. **/ virtual QSize screenSize() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns one QRect positioned at 0/0 with screenSize() as size. **/ virtual QVector screenGeometries() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns a screen with a scale of 1. **/ virtual QVector screenScales() const; /** * Implement this method to receive configuration change requests through KWayland's * OutputManagement interface. * * Base implementation warns that the current backend does not implement this * functionality. */ virtual void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); /** * Whether the Platform requires compositing for rendering. * Default implementation returns @c true. If the implementing Platform allows to be used * without compositing (e.g. rendering is done by the windowing system), re-implement this method. **/ virtual bool requiresCompositing() const; /** * Whether Compositing is possible in the Platform. * Returning @c false in this method makes only sense if @link{requiresCompositing} returns @c false. * * The default implementation returns @c true. * @see requiresCompositing **/ virtual bool compositingPossible() const; /** * Returns a user facing text explaining why compositing is not possible in case * @link{compositingPossible} returns @c false. * * The default implementation returns an empty string. * @see compositingPossible **/ virtual QString compositingNotPossibleReason() const; /** * Whether OpenGL compositing is broken. * The Platform can implement this method if it is able to detect whether OpenGL compositing * broke (e.g. triggered a crash in a previous run). * * Default implementation returns @c false. * @see createOpenGLSafePoint **/ virtual bool openGLCompositingIsBroken() const; enum class OpenGLSafePoint { PreInit, PostInit, PreFrame, PostFrame, PostLastGuardedFrame }; /** * This method is invoked before and after creating the OpenGL rendering Scene. * An implementing Platform can use it to detect crashes triggered by the OpenGL implementation. * This can be used for @link{openGLCompositingIsBroken}. * * The default implementation does nothing. * @see openGLCompositingIsBroken. **/ virtual void createOpenGLSafePoint(OpenGLSafePoint safePoint); /** * Starts an interactive window selection process. * * Once the user selected a window the @p callback is invoked with the selected Toplevel as * argument. In case the user cancels the interactive window selection or selecting a window is currently * not possible (e.g. screen locked) the @p callback is invoked with a @c nullptr argument. * * During the interactive window selection the cursor is turned into a crosshair cursor unless * @p cursorName is provided. The argument @p cursorName is a QByteArray instead of Qt::CursorShape * to support the "pirate" cursor for kill window which is not wrapped by Qt::CursorShape. * * The default implementation forwards to InputRedirection. * * @param callback The function to invoke once the interactive window selection ends * @param cursorName The optional name of the cursor shape to use, default is crosshair **/ virtual void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName = QByteArray()); /** * Starts an interactive position selection process. * * Once the user selected a position on the screen the @p callback is invoked with * the selected point as argument. In case the user cancels the interactive position selection * or selecting a position is currently not possible (e.g. screen locked) the @p callback * is invoked with a point at @c -1 as x and y argument. * * During the interactive window selection the cursor is turned into a crosshair cursor. * * The default implementation forwards to InputRedirection. * * @param callback The function to invoke once the interactive position selection ends **/ virtual void startInteractivePositionSelection(std::function callback); /** * Platform specific preparation for an @p action which is used for KGlobalAccel. * * A platform might need to do preparation for an @p action before * it can be used with KGlobalAccel. * * Code using KGlobalAccel should invoke this method for the @p action * prior to setting up any shortcuts and connections. * * The default implementation does nothing. * * @param action The action which will be used with KGlobalAccel. * @since 5.10 **/ virtual void setupActionForGlobalAccel(QAction *action); bool usesSoftwareCursor() const { return m_softWareCursor; } QImage softwareCursor() const; QPoint softwareCursorHotspot() const; void markCursorAsRendered(); /** * Returns a PlatformCursorImage. By default this is created by softwareCursor and * softwareCursorHotspot. An implementing subclass can use this to provide a better * suited PlatformCursorImage. * * @see softwareCursor * @see softwareCursorHotspot * @since 5.9 **/ virtual PlatformCursorImage cursorImage() const; /** * The Platform cursor image should be hidden. * @see showCursor * @see doHideCursor * @see isCursorHidden * @since 5.9 **/ void hideCursor(); /** * The Platform cursor image should be shown again. * @see hideCursor * @see doShowCursor * @see isCursorHidden * @since 5.9 **/ void showCursor(); /** * Whether the cursor is currently hidden. * @see showCursor * @see hideCursor * @since 5.9 **/ bool isCursorHidden() const { return m_hideCursorCounter > 0; } bool handlesOutputs() const { return m_handlesOutputs; } bool isReady() const { return m_ready; } void setInitialWindowSize(const QSize &size) { m_initialWindowSize = size; } void setDeviceIdentifier(const QByteArray &identifier) { m_deviceIdentifier = identifier; } bool supportsPointerWarping() const { return m_pointerWarping; } bool areOutputsEnabled() const { return m_outputsEnabled; } void setOutputsEnabled(bool enabled) { m_outputsEnabled = enabled; } int initialOutputCount() const { return m_initialOutputCount; } void setInitialOutputCount(int count) { m_initialOutputCount = count; } qreal initialOutputScale() const { return m_initialOutputScale; } void setInitialOutputScale(qreal scale) { m_initialOutputScale = scale; } /** * Creates the OverlayWindow required for X11 based compositors. * Default implementation returns @c nullptr. **/ virtual OverlayWindow *createOverlayWindow(); /** * Allows a platform to update the X11 timestamp. * Mostly for the X11 standalone platform to interact with QX11Info. * * Default implementation does nothing. This means code relying on the X timestamp being up to date, * might not be working. E.g. synced X11 window resizing **/ virtual void updateXTime(); /** * Creates the OutlineVisual for the given @p outline. * Default implementation creates an OutlineVisual suited for composited usage. **/ virtual OutlineVisual *createOutline(Outline *outline); /** * Creates the Decoration::Renderer for the given @p client. * * The default implementation creates a Renderer suited for the Compositor, @c nullptr if there is no Compositor. **/ virtual Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *client); /** * Platform specific way to invert the screen. * Default implementation invokes the invert effect **/ virtual void invertScreen(); /** * Default implementation creates an EffectsHandlerImp; **/ virtual void createEffectsHandler(Compositor *compositor, Scene *scene); /** * The CompositingTypes supported by the Platform. * The first item should be the most preferred one. * @since 5.11 **/ virtual QVector supportedCompositors() const = 0; + /** + * Whether gamma control is supported by the backend. + * @since 5.12 + **/ + bool supportsGammaControl() const { + return m_supportsGammaControl; + } + + ColorCorrect::Manager *colorCorrectManager() { + return m_colorCorrect; + } + + virtual int gammaRampSize(int screen) const { + Q_UNUSED(screen); + return 0; + } + virtual bool setGammaRamp(int screen, ColorCorrect::GammaRamp &gamma) { + Q_UNUSED(screen); + Q_UNUSED(gamma); + return false; + } + + /* + * A string of information to include in kwin debug output + * It should not be translated. + * + * The base implementation prints the name. + * @since 5.12 + */ + virtual QString supportInformation() const; + public Q_SLOTS: void pointerMotion(const QPointF &position, quint32 time); void pointerButtonPressed(quint32 button, quint32 time); void pointerButtonReleased(quint32 button, quint32 time); void pointerAxisHorizontal(qreal delta, quint32 time); void pointerAxisVertical(qreal delta, quint32 time); void keyboardKeyPressed(quint32 key, quint32 time); void keyboardKeyReleased(quint32 key, quint32 time); void keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); void keymapChange(int fd, uint32_t size); void touchDown(qint32 id, const QPointF &pos, quint32 time); void touchUp(qint32 id, quint32 time); void touchMotion(qint32 id, const QPointF &pos, quint32 time); void touchCancel(); void touchFrame(); void processSwipeGestureBegin(int fingerCount, quint32 time); void processSwipeGestureUpdate(const QSizeF &delta, quint32 time); void processSwipeGestureEnd(quint32 time); void processSwipeGestureCancelled(quint32 time); void processPinchGestureBegin(int fingerCount, quint32 time); void processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time); void processPinchGestureEnd(quint32 time); void processPinchGestureCancelled(quint32 time); Q_SIGNALS: void screensQueried(); void initFailed(); void cursorChanged(); void readyChanged(bool); /** * Emitted by backends using a one screen (nested window) approach and when the size of that changes. **/ void screenSizeChanged(); protected: explicit Platform(QObject *parent = nullptr); void setSoftWareCursor(bool set); void handleOutputs() { m_handlesOutputs = true; } void repaint(const QRect &rect); void setReady(bool ready); QSize initialWindowSize() const { return m_initialWindowSize; } QByteArray deviceIdentifier() const { return m_deviceIdentifier; } void setSupportsPointerWarping(bool set) { m_pointerWarping = set; } + void setSupportsGammaControl(bool set) { + m_supportsGammaControl = set; + } /** * Actual platform specific way to hide the cursor. * Sub-classes need to implement if they support hiding the cursor. * * This method is invoked by hideCursor if the cursor needs to be hidden. * The default implementation does nothing. * * @see doShowCursor * @see hideCursor * @see showCursor **/ virtual void doHideCursor(); /** * Actual platform specific way to show the cursor. * Sub-classes need to implement if they support showing the cursor. * * This method is invoked by showCursor if the cursor needs to be shown again. * * @see doShowCursor * @see hideCursor * @see showCursor **/ virtual void doShowCursor(); private: void triggerCursorRepaint(); bool m_softWareCursor = false; struct { QRect lastRenderedGeometry; } m_cursor; bool m_handlesOutputs = false; bool m_ready = false; QSize m_initialWindowSize; QByteArray m_deviceIdentifier; bool m_pointerWarping = false; bool m_outputsEnabled = true; int m_initialOutputCount = 1; qreal m_initialOutputScale = 1; EGLDisplay m_eglDisplay; EGLConfig m_eglConfig = nullptr; EGLContext m_context = EGL_NO_CONTEXT; EGLSurface m_surface = EGL_NO_SURFACE; int m_hideCursorCounter = 0; + ColorCorrect::Manager *m_colorCorrect = nullptr; + bool m_supportsGammaControl = false; }; } Q_DECLARE_INTERFACE(KWin::Platform, "org.kde.kwin.Platform") #endif diff --git a/plugins/kdecorations/aurorae/src/CMakeLists.txt b/plugins/kdecorations/aurorae/src/CMakeLists.txt index 0435a1d45..760b5b8bb 100644 --- a/plugins/kdecorations/aurorae/src/CMakeLists.txt +++ b/plugins/kdecorations/aurorae/src/CMakeLists.txt @@ -1,64 +1,65 @@ ########### decoration ############### include_directories( ./lib ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) set(kwin5_aurorae_PART_SRCS aurorae.cpp decorationoptions.cpp lib/auroraetheme.cpp lib/themeconfig.cpp ) add_library(kwin5_aurorae MODULE ${kwin5_aurorae_PART_SRCS}) +set_target_properties(kwin5_aurorae PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kdecoration2/") target_link_libraries(kwin5_aurorae KDecoration2::KDecoration KF5::ConfigWidgets KF5::I18n KF5::Package KF5::WindowSystem Qt5::Quick Qt5::UiTools ) install(TARGETS kwin5_aurorae DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2) set(decoration_plugin_SRCS decorationplugin.cpp decorationoptions.cpp colorhelper.cpp ) add_library(decorationplugin SHARED ${decoration_plugin_SRCS}) target_link_libraries(decorationplugin Qt5::Quick KDecoration2::KDecoration KF5::ConfigWidgets ) install(TARGETS decorationplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/kwin/decoration) ########### install files ############### install( FILES aurorae.knsrc DESTINATION ${CONFIG_INSTALL_DIR} ) install( FILES qml/aurorae.qml qml/AuroraeButton.qml qml/AuroraeButtonGroup.qml qml/AuroraeMaximizeButton.qml qml/Decoration.qml qml/DecorationButton.qml qml/MenuButton.qml qml/AppMenuButton.qml DESTINATION ${DATA_INSTALL_DIR}/kwin/aurorae ) install( FILES qml/Decoration.qml qml/DecorationButton.qml qml/MenuButton.qml qml/AppMenuButton.qml qml/ButtonGroup.qml qml/qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/kwin/decoration ) install( FILES kwindecoration.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) diff --git a/plugins/kdecorations/aurorae/src/aurorae.cpp b/plugins/kdecorations/aurorae/src/aurorae.cpp index 00632c35a..4c5879be2 100644 --- a/plugins/kdecorations/aurorae/src/aurorae.cpp +++ b/plugins/kdecorations/aurorae/src/aurorae.cpp @@ -1,778 +1,788 @@ /******************************************************************** Copyright (C) 2009, 2010, 2012 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "aurorae.h" #include "auroraetheme.h" #include "config-kwin.h" // qml imports #include "decorationoptions.h" // KDecoration2 #include #include #include // KDE #include #include #include #include #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(AuroraeDecoFactory, "aurorae.json", registerPlugin(); registerPlugin(QStringLiteral("themes")); registerPlugin(QStringLiteral("kcmodule")); ) namespace Aurorae { class Helper { public: void ref(); void unref(); QQmlComponent *component(const QString &theme); QQmlContext *rootContext(); QQmlComponent *svgComponent() { return m_svgComponent.data(); } static Helper &instance(); private: Helper() = default; void init(); QQmlComponent *loadComponent(const QString &themeName); int m_refCount = 0; QScopedPointer m_engine; QHash m_components; QScopedPointer m_svgComponent; }; Helper &Helper::instance() { static Helper s_helper; return s_helper; } void Helper::ref() { m_refCount++; if (m_refCount == 1) { m_engine.reset(new QQmlEngine); init(); } } void Helper::unref() { m_refCount--; if (m_refCount == 0) { // cleanup m_svgComponent.reset(); m_engine.reset(); m_components.clear(); } } static const QString s_defaultTheme = QStringLiteral("kwin4_decoration_qml_plastik"); static const QString s_qmlPackageFolder = QStringLiteral(KWIN_NAME "/decorations/"); /* * KDecoration2::BorderSize doesn't map to the indices used for the Aurorae SVG Button Sizes. * BorderSize defines None and NoSideBorder as index 0 and 1. These do not make sense for Button * Size, thus we need to perform a mapping between the enum value and the config value. **/ static const int s_indexMapper = 2; QQmlComponent *Helper::component(const QString &themeName) { // maybe it's an SVG theme? if (themeName.startsWith(QLatin1Literal("__aurorae__svg__"))) { if (m_svgComponent.isNull()) { /* use logic from KDeclarative::setupBindings(): "addImportPath adds the path at the beginning, so to honour user's paths we need to traverse the list in reverse order" */ QStringListIterator paths(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("module/imports"), QStandardPaths::LocateDirectory)); paths.toBack(); while (paths.hasPrevious()) { m_engine->addImportPath(paths.previous()); } m_svgComponent.reset(new QQmlComponent(m_engine.data())); m_svgComponent->loadUrl(QUrl(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/aurorae/aurorae.qml")))); } // verify that the theme exists if (!QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("aurorae/themes/%1/%1rc").arg(themeName.mid(16))).isEmpty()) { return m_svgComponent.data(); } } // try finding the QML package auto it = m_components.constFind(themeName); if (it != m_components.constEnd()) { return it.value(); } auto component = loadComponent(themeName); if (component) { m_components.insert(themeName, component); return component; } // try loading default component if (themeName != s_defaultTheme) { return loadComponent(s_defaultTheme); } return nullptr; } QQmlComponent *Helper::loadComponent(const QString &themeName) { qCDebug(AURORAE) << "Trying to load QML Decoration " << themeName; const QString internalname = themeName.toLower(); const auto offers = KPackage::PackageLoader::self()->findPackages(QStringLiteral("KWin/Decoration"), s_qmlPackageFolder, [internalname] (const KPluginMetaData &data) { return data.pluginId().compare(internalname, Qt::CaseInsensitive) == 0; } ); if (offers.isEmpty()) { qCCritical(AURORAE) << "Couldn't find QML Decoration " << themeName; // TODO: what to do in error case? return nullptr; } const KPluginMetaData &service = offers.first(); const QString pluginName = service.pluginId(); const QString scriptName = service.value(QStringLiteral("X-Plasma-MainScript")); const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, s_qmlPackageFolder + pluginName + QLatin1String("/contents/") + scriptName); if (file.isNull()) { qCDebug(AURORAE) << "Could not find script file for " << pluginName; // TODO: what to do in error case? return nullptr; } // setup the QML engine /* use logic from KDeclarative::setupBindings(): "addImportPath adds the path at the beginning, so to honour user's paths we need to traverse the list in reverse order" */ QStringListIterator paths(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("module/imports"), QStandardPaths::LocateDirectory)); paths.toBack(); while (paths.hasPrevious()) { m_engine->addImportPath(paths.previous()); } QQmlComponent *component = new QQmlComponent(m_engine.data(), m_engine.data()); component->loadUrl(QUrl::fromLocalFile(file)); return component; } QQmlContext *Helper::rootContext() { return m_engine->rootContext(); } void Helper::init() { // we need to first load our decoration plugin // once it's loaded we can provide the Borders and access them from C++ side // so let's try to locate our plugin: QString pluginPath; for (const QString &path : m_engine->importPathList()) { QDirIterator it(path, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); QFileInfo fileInfo = it.fileInfo(); if (!fileInfo.isFile()) { continue; } if (!fileInfo.path().endsWith(QLatin1String("/org/kde/kwin/decoration"))) { continue; } if (fileInfo.fileName() == QLatin1String("libdecorationplugin.so")) { pluginPath = fileInfo.absoluteFilePath(); break; } } if (!pluginPath.isEmpty()) { break; } } m_engine->importPlugin(pluginPath, "org.kde.kwin.decoration", nullptr); qmlRegisterType("org.kde.kwin.decoration", 0, 1, "Borders"); qmlRegisterType(); qmlRegisterType(); qRegisterMetaType(); } static QString findTheme(const QVariantList &args) { if (args.isEmpty()) { return QString(); } const auto map = args.first().toMap(); auto it = map.constFind(QStringLiteral("theme")); if (it == map.constEnd()) { return QString(); } return it.value().toString(); } Decoration::Decoration(QObject *parent, const QVariantList &args) : KDecoration2::Decoration(parent, args) , m_item(nullptr) , m_borders(nullptr) , m_maximizedBorders(nullptr) , m_extendedBorders(nullptr) , m_padding(nullptr) , m_themeName(s_defaultTheme) { m_themeName = findTheme(args); Helper::instance().ref(); } Decoration::~Decoration() { Helper::instance().unref(); if (m_context) { m_context->makeCurrent(m_offscreenSurface.data()); delete m_renderControl; delete m_view.data(); m_fbo.reset(); delete m_item; m_context->doneCurrent(); } } void Decoration::init() { KDecoration2::Decoration::init(); auto s = settings(); connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, this, &Decoration::configChanged); QQmlContext *context = new QQmlContext(Helper::instance().rootContext(), this); context->setContextProperty(QStringLiteral("decoration"), this); context->setContextProperty(QStringLiteral("decorationSettings"), s.data()); auto component = Helper::instance().component(m_themeName); if (!component) { return; } if (component == Helper::instance().svgComponent()) { // load SVG theme const QString themeName = m_themeName.mid(16); KConfig config(QLatin1String("aurorae/themes/") + themeName + QLatin1Char('/') + themeName + QLatin1String("rc"), KConfig::FullConfig, QStandardPaths::GenericDataLocation); AuroraeTheme *theme = new AuroraeTheme(this); theme->loadTheme(themeName, config); theme->setBorderSize(s->borderSize()); connect(s.data(), &KDecoration2::DecorationSettings::borderSizeChanged, theme, &AuroraeTheme::setBorderSize); auto readButtonSize = [this, theme] { const KSharedConfigPtr conf = KSharedConfig::openConfig(QStringLiteral("auroraerc")); const KConfigGroup themeGroup(conf, m_themeName.mid(16)); theme->setButtonSize((KDecoration2::BorderSize)(themeGroup.readEntry("ButtonSize", int(KDecoration2::BorderSize::Normal) - s_indexMapper) + s_indexMapper)); updateBorders(); }; connect(this, &Decoration::configChanged, theme, readButtonSize); readButtonSize(); // m_theme->setTabDragMimeType(tabDragMimeType()); context->setContextProperty(QStringLiteral("auroraeTheme"), theme); } m_item = qobject_cast< QQuickItem* >(component->create(context)); if (!m_item) { return; } m_item->setParent(this); QVariant visualParent = property("visualParent"); if (visualParent.isValid()) { m_item->setParentItem(visualParent.value()); visualParent.value()->setProperty("drawBackground", false); } else { - // first create the context - QSurfaceFormat format; - format.setDepthBufferSize(16); - format.setStencilBufferSize(8); - m_context.reset(new QOpenGLContext); - m_context->setFormat(format); - m_context->create(); - // and the offscreen surface - m_offscreenSurface.reset(new QOffscreenSurface); - m_offscreenSurface->setFormat(m_context->format()); - m_offscreenSurface->create(); - m_renderControl = new QQuickRenderControl(this); m_view = new QQuickWindow(m_renderControl); + bool usingGL = m_view->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL; m_view->setColor(Qt::transparent); m_view->setFlags(Qt::FramelessWindowHint); + if (usingGL) { + // first create the context + QSurfaceFormat format; + format.setDepthBufferSize(16); + format.setStencilBufferSize(8); + m_context.reset(new QOpenGLContext); + m_context->setFormat(format); + m_context->create(); + // and the offscreen surface + m_offscreenSurface.reset(new QOffscreenSurface); + m_offscreenSurface->setFormat(m_context->format()); + m_offscreenSurface->create(); + + } + //workaround for https://codereview.qt-project.org/#/c/207198/ +#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) + if (!usingGL) { + m_renderControl->sync(); + } +#endif // delay rendering a little bit for better performance m_updateTimer.reset(new QTimer); m_updateTimer->setSingleShot(true); m_updateTimer->setInterval(5); connect(m_updateTimer.data(), &QTimer::timeout, this, - [this] { - if (!m_context->makeCurrent(m_offscreenSurface.data())) { - return; - } - if (m_fbo.isNull() || m_fbo->size() != m_view->size()) { - m_fbo.reset(new QOpenGLFramebufferObject(m_view->size(), QOpenGLFramebufferObject::CombinedDepthStencil)); - if (!m_fbo->isValid()) { - qCWarning(AURORAE) << "Creating FBO as render target failed"; - m_fbo.reset(); + [this, usingGL] { + if (usingGL) { + if (!m_context->makeCurrent(m_offscreenSurface.data())) { return; } + if (m_fbo.isNull() || m_fbo->size() != m_view->size()) { + m_fbo.reset(new QOpenGLFramebufferObject(m_view->size(), QOpenGLFramebufferObject::CombinedDepthStencil)); + if (!m_fbo->isValid()) { + qCWarning(AURORAE) << "Creating FBO as render target failed"; + m_fbo.reset(); + return; + } + } + m_view->setRenderTarget(m_fbo.data()); + m_view->resetOpenGLState(); } - m_view->setRenderTarget(m_fbo.data()); - m_renderControl->polishItems(); - m_renderControl->sync(); - m_renderControl->render(); - m_view->resetOpenGLState(); - m_buffer = m_fbo->toImage(); + m_buffer = m_renderControl->grab(); m_contentRect = QRect(QPoint(0, 0), m_buffer.size()); if (m_padding && (m_padding->left() > 0 || m_padding->top() > 0 || m_padding->right() > 0 || m_padding->bottom() > 0) && !client().data()->isMaximized()) { m_contentRect = m_contentRect.adjusted(m_padding->left(), m_padding->top(), -m_padding->right(), -m_padding->bottom()); } updateShadow(); QOpenGLFramebufferObject::bindDefault(); update(); } ); auto requestUpdate = [this] { if (m_updateTimer->isActive()) { return; } m_updateTimer->start(); }; connect(m_renderControl, &QQuickRenderControl::renderRequested, this, requestUpdate); connect(m_renderControl, &QQuickRenderControl::sceneChanged, this, requestUpdate); m_item->setParentItem(m_view->contentItem()); - m_context->makeCurrent(m_offscreenSurface.data()); - m_renderControl->initialize(m_context.data()); - m_context->doneCurrent(); + if (usingGL) { + m_context->makeCurrent(m_offscreenSurface.data()); + m_renderControl->initialize(m_context.data()); + m_context->doneCurrent(); + } } setupBorders(m_item); if (m_extendedBorders) { auto updateExtendedBorders = [this] { setResizeOnlyBorders(*m_extendedBorders); }; updateExtendedBorders(); connect(m_extendedBorders, &KWin::Borders::leftChanged, this, updateExtendedBorders); connect(m_extendedBorders, &KWin::Borders::rightChanged, this, updateExtendedBorders); connect(m_extendedBorders, &KWin::Borders::topChanged, this, updateExtendedBorders); connect(m_extendedBorders, &KWin::Borders::bottomChanged, this, updateExtendedBorders); } connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateBorders, Qt::QueuedConnection); connect(client().data(), &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::updateBorders); updateBorders(); if (!m_view.isNull()) { auto resizeWindow = [this] { QRect rect(QPoint(0, 0), size()); if (m_padding && !client().data()->isMaximized()) { rect = rect.adjusted(-m_padding->left(), -m_padding->top(), m_padding->right(), m_padding->bottom()); } m_view->setGeometry(rect); }; connect(this, &Decoration::bordersChanged, this, resizeWindow); connect(client().data(), &KDecoration2::DecoratedClient::widthChanged, this, resizeWindow); connect(client().data(), &KDecoration2::DecoratedClient::heightChanged, this, resizeWindow); connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, resizeWindow); connect(client().data(), &KDecoration2::DecoratedClient::shadedChanged, this, resizeWindow); resizeWindow(); } else { // create a dummy shadow for the configuration interface if (m_padding) { auto s = QSharedPointer::create(); s->setPadding(*m_padding); s->setInnerShadowRect(QRect(m_padding->left(), m_padding->top(), 1, 1)); setShadow(s); } } } QVariant Decoration::readConfig(const QString &key, const QVariant &defaultValue) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("auroraerc")); return config->group(m_themeName).readEntry(key, defaultValue); } void Decoration::setupBorders(QQuickItem *item) { m_borders = item->findChild(QStringLiteral("borders")); m_maximizedBorders = item->findChild(QStringLiteral("maximizedBorders")); m_extendedBorders = item->findChild(QStringLiteral("extendedBorders")); m_padding = item->findChild(QStringLiteral("padding")); } void Decoration::updateBorders() { KWin::Borders *b = m_borders; if (client().data()->isMaximized() && m_maximizedBorders) { b = m_maximizedBorders; } if (!b) { return; } setBorders(*b); } void Decoration::paint(QPainter *painter, const QRect &repaintRegion) { Q_UNUSED(repaintRegion) painter->fillRect(rect(), Qt::transparent); painter->drawImage(rect(), m_buffer, m_contentRect); } void Decoration::updateShadow() { bool updateShadow = false; const auto oldShadow = shadow(); if (m_padding && (m_padding->left() > 0 || m_padding->top() > 0 || m_padding->right() > 0 || m_padding->bottom() > 0) && !client().data()->isMaximized()) { if (oldShadow.isNull()) { updateShadow = true; } else { // compare padding if (oldShadow->padding() != *m_padding) { updateShadow = true; } } QImage img(m_buffer.size(), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::transparent); QPainter p(&img); // top p.drawImage(0, 0, m_buffer, 0, 0, img.width(), m_padding->top()); // left p.drawImage(0, m_padding->top(), m_buffer, 0, m_padding->top(), m_padding->left(), m_buffer.height() - m_padding->top()); // bottom p.drawImage(m_padding->left(), m_buffer.height() - m_padding->bottom(), m_buffer, m_padding->left(), m_buffer.height() - m_padding->bottom(), m_buffer.width() - m_padding->left(), m_padding->bottom()); // right p.drawImage(m_buffer.width() - m_padding->right(), m_padding->top(), m_buffer, m_buffer.width() - m_padding->right(), m_padding->top(), m_padding->right(), m_buffer.height() - m_padding->top() - m_padding->bottom()); if (!updateShadow) { updateShadow = (oldShadow->shadow() != img); } if (updateShadow) { auto s = QSharedPointer::create(); s->setShadow(img); s->setPadding(*m_padding); s->setInnerShadowRect(QRect(m_padding->left(), m_padding->top(), m_buffer.width() - m_padding->left() - m_padding->right(), m_buffer.height() - m_padding->top() - m_padding->bottom())); setShadow(s); } } else { if (!oldShadow.isNull()) { setShadow(QSharedPointer()); } } } QMouseEvent Decoration::translatedMouseEvent(QMouseEvent *orig) { if (!m_padding || client().data()->isMaximized()) { orig->setAccepted(false); return *orig; } QMouseEvent event(orig->type(), orig->localPos() + QPointF(m_padding->left(), m_padding->top()), orig->button(), orig->buttons(), orig->modifiers()); event.setAccepted(false); return event; } void Decoration::hoverEnterEvent(QHoverEvent *event) { if (m_view) { event->setAccepted(false); QCoreApplication::sendEvent(m_view.data(), event); } KDecoration2::Decoration::hoverEnterEvent(event); } void Decoration::hoverLeaveEvent(QHoverEvent *event) { if (m_view) { event->setAccepted(false); QCoreApplication::sendEvent(m_view.data(), event); } KDecoration2::Decoration::hoverLeaveEvent(event); } void Decoration::hoverMoveEvent(QHoverEvent *event) { if (m_view) { QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), Qt::NoButton, Qt::NoButton, Qt::NoModifier); QMouseEvent ev = translatedMouseEvent(&mouseEvent); QCoreApplication::sendEvent(m_view.data(), &ev); event->setAccepted(ev.isAccepted()); } KDecoration2::Decoration::hoverMoveEvent(event); } void Decoration::mouseMoveEvent(QMouseEvent *event) { if (m_view) { QMouseEvent ev = translatedMouseEvent(event); QCoreApplication::sendEvent(m_view.data(), &ev); event->setAccepted(ev.isAccepted()); } KDecoration2::Decoration::mouseMoveEvent(event); } void Decoration::mousePressEvent(QMouseEvent *event) { if (m_view) { QMouseEvent ev = translatedMouseEvent(event); QCoreApplication::sendEvent(m_view.data(), &ev); if (ev.button() == Qt::LeftButton) { if (!m_doubleClickTimer.hasExpired(QGuiApplication::styleHints()->mouseDoubleClickInterval())) { QMouseEvent dc(QEvent::MouseButtonDblClick, ev.localPos(), ev.windowPos(), ev.screenPos(), ev.button(), ev.buttons(), ev.modifiers()); QCoreApplication::sendEvent(m_view.data(), &dc); } } m_doubleClickTimer.invalidate(); event->setAccepted(ev.isAccepted()); } KDecoration2::Decoration::mousePressEvent(event); } void Decoration::mouseReleaseEvent(QMouseEvent *event) { if (m_view) { QMouseEvent ev = translatedMouseEvent(event); QCoreApplication::sendEvent(m_view.data(), &ev); event->setAccepted(ev.isAccepted()); if (ev.isAccepted() && ev.button() == Qt::LeftButton) { m_doubleClickTimer.start(); } } KDecoration2::Decoration::mouseReleaseEvent(event); } void Decoration::installTitleItem(QQuickItem *item) { auto update = [this, item] { QRect rect = item->mapRectToScene(item->childrenRect()).toRect(); if (rect.isNull()) { rect = item->parentItem()->mapRectToScene(QRectF(item->x(), item->y(), item->width(), item->height())).toRect(); } setTitleBar(rect); }; update(); connect(item, &QQuickItem::widthChanged, this, update); connect(item, &QQuickItem::heightChanged, this, update); connect(item, &QQuickItem::xChanged, this, update); connect(item, &QQuickItem::yChanged, this, update); } KDecoration2::DecoratedClient *Decoration::clientPointer() const { return client().data(); } ThemeFinder::ThemeFinder(QObject *parent, const QVariantList &args) : QObject(parent) { Q_UNUSED(args) init(); } void ThemeFinder::init() { findAllQmlThemes(); findAllSvgThemes(); } void ThemeFinder::findAllQmlThemes() { const auto offers = KPackage::PackageLoader::self()->findPackages(QStringLiteral("KWin/Decoration"), s_qmlPackageFolder); for (const auto &offer : offers) { m_themes.insert(offer.name(), offer.pluginId()); } } void ThemeFinder::findAllSvgThemes() { QStringList themes; const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("aurorae/themes/"), QStandardPaths::LocateDirectory); QStringList themeDirectories; for (const QString &dir : dirs) { QDir directory = QDir(dir); for (const QString &themeDir : directory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { themeDirectories << dir + themeDir; } } for (const QString &dir : themeDirectories) { for (const QString & file : QDir(dir).entryList(QStringList() << QStringLiteral("metadata.desktop"))) { themes.append(dir + '/' + file); } } for (const QString & theme : themes) { int themeSepIndex = theme.lastIndexOf('/', -1); QString themeRoot = theme.left(themeSepIndex); int themeNameSepIndex = themeRoot.lastIndexOf('/', -1); QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1); KDesktopFile df(theme); QString name = df.readName(); if (name.isEmpty()) { name = packageName; } m_themes.insert(name, QString(QLatin1String("__aurorae__svg__") + packageName)); } } static const QString s_configUiPath = QStringLiteral("kwin/decorations/%1/contents/ui/config.ui"); static const QString s_configXmlPath = QStringLiteral("kwin/decorations/%1/contents/config/main.xml"); bool ThemeFinder::hasConfiguration(const QString &theme) const { if (theme.startsWith(QLatin1String("__aurorae__svg__"))) { return true; } const QString ui = QStandardPaths::locate(QStandardPaths::GenericDataLocation, s_configUiPath.arg(theme)); const QString xml = QStandardPaths::locate(QStandardPaths::GenericDataLocation, s_configXmlPath.arg(theme)); return !(ui.isEmpty() || xml.isEmpty()); } ConfigurationModule::ConfigurationModule(QWidget *parent, const QVariantList &args) : KCModule(parent, args) , m_theme(findTheme(args)) , m_buttonSize(int(KDecoration2::BorderSize::Normal) - s_indexMapper) { setLayout(new QVBoxLayout(this)); init(); } void ConfigurationModule::init() { if (m_theme.startsWith(QLatin1String("__aurorae__svg__"))) { // load the generic setting module initSvg(); } else { initQml(); } } void ConfigurationModule::initSvg() { QWidget *form = new QWidget(this); form->setLayout(new QHBoxLayout(form)); QComboBox *sizes = new QComboBox(form); sizes->addItem(i18nc("@item:inlistbox Button size:", "Tiny")); sizes->addItem(i18nc("@item:inlistbox Button size:", "Normal")); sizes->addItem(i18nc("@item:inlistbox Button size:", "Large")); sizes->addItem(i18nc("@item:inlistbox Button size:", "Very Large")); sizes->addItem(i18nc("@item:inlistbox Button size:", "Huge")); sizes->addItem(i18nc("@item:inlistbox Button size:", "Very Huge")); sizes->addItem(i18nc("@item:inlistbox Button size:", "Oversized")); sizes->setObjectName(QStringLiteral("kcfg_ButtonSize")); QLabel *label = new QLabel(i18n("Button size:"), form); label->setBuddy(sizes); form->layout()->addWidget(label); form->layout()->addWidget(sizes); layout()->addWidget(form); KCoreConfigSkeleton *skel = new KCoreConfigSkeleton(KSharedConfig::openConfig(QStringLiteral("auroraerc")), this); skel->setCurrentGroup(m_theme.mid(16)); skel->addItemInt(QStringLiteral("ButtonSize"), m_buttonSize, int(KDecoration2::BorderSize::Normal) - s_indexMapper, QStringLiteral("ButtonSize")); addConfig(skel, form); } void ConfigurationModule::initQml() { const QString ui = QStandardPaths::locate(QStandardPaths::GenericDataLocation, s_configUiPath.arg(m_theme)); const QString xml = QStandardPaths::locate(QStandardPaths::GenericDataLocation, s_configXmlPath.arg(m_theme)); if (ui.isEmpty() || xml.isEmpty()) { return; } KLocalizedTranslator *translator = new KLocalizedTranslator(this); QCoreApplication::instance()->installTranslator(translator); const KDesktopFile metaData(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/decorations/%1/metadata.desktop").arg(m_theme))); const QString translationDomain = metaData.desktopGroup().readEntry("X-KWin-Config-TranslationDomain", QString()); if (!translationDomain.isEmpty()) { translator->setTranslationDomain(translationDomain); } // load the KConfigSkeleton QFile configFile(xml); KSharedConfigPtr auroraeConfig = KSharedConfig::openConfig("auroraerc"); KConfigGroup configGroup = auroraeConfig->group(m_theme); m_skeleton = new KConfigLoader(configGroup, &configFile, this); // load the ui file QUiLoader *loader = new QUiLoader(this); loader->setLanguageChangeEnabled(true); QFile uiFile(ui); uiFile.open(QFile::ReadOnly); QWidget *customConfigForm = loader->load(&uiFile, this); translator->addContextToMonitor(customConfigForm->objectName()); uiFile.close(); layout()->addWidget(customConfigForm); // connect the ui file with the skeleton addConfig(m_skeleton, customConfigForm); // send a custom event to the translator to retranslate using our translator QEvent le(QEvent::LanguageChange); QCoreApplication::sendEvent(customConfigForm, &le); } } #include "aurorae.moc" diff --git a/plugins/kdecorations/aurorae/src/aurorae.knsrc b/plugins/kdecorations/aurorae/src/aurorae.knsrc index 9649fff06..3238c982b 100644 --- a/plugins/kdecorations/aurorae/src/aurorae.knsrc +++ b/plugins/kdecorations/aurorae/src/aurorae.knsrc @@ -1,40 +1,41 @@ [KNewStuff3] Name=Aurorae Window Decorations Name[ca]=Decoracions de finestra Aurorae Name[ca@valencia]=Decoracions de finestra Aurorae Name[cs]=Dekorace oken Aurorae Name[da]=Aurorae vinduesdekorationer Name[de]=Aurorae-Fensterdekoration Name[el]=Διακοσμήσεις παραθύρου Aurorae Name[en_GB]=Aurorae Window Decorations Name[es]=Decoración de ventanas Aurorae Name[eu]=Aurorae leihoentzako apaingarriak Name[fi]=Aurorae-ikkunakehykset Name[fr]=Décorations de fenêtres Aurorae Name[gl]=Decoracións de xanela de Aurorae Name[hu]=Aurorae ablakdekorációk Name[ia]=Decorationes de fenestra Aurorae Name[it]=Decorazioni delle finestre Aurorae Name[ko]=Aurorae 창 장식 Name[nl]=Aurorea-vensterdecoraties Name[nn]=Aurorae-vindaugsdekorasjonar Name[pl]=Ozdoby okienne Aurorae Name[pt]=Decorações das Janelas do Aurorae Name[pt_BR]=Decorações da janela Aurorae +Name[ru]=Оформления окон Aurorae для KWin Name[sk]=Dekorácie okien Aurorae Name[sl]=Okraski oken Aurorae Name[sr]=Декорације прозора за Ауроре Name[sr@ijekavian]=Декорације прозора за Ауроре Name[sr@ijekavianlatin]=Dekoracije prozora za Aurore Name[sr@latin]=Dekoracije prozora za Aurore Name[sv]=Aurora-fönsterdekorationer Name[tr]=Aurorae Pencere Dekorasyonları Name[uk]=Обрамлення вікон Aurorae Name[x-test]=xxAurorae Window Decorationsxx Name[zh_CN]=Aurorae 窗口装饰 Name[zh_TW]=Aurorae 視窗裝飾 ProvidersUrl=https://download.kde.org/ocs/providers.xml Categories=Window Decoration Aurorae Uncompress=archive TargetDir=aurorae/themes diff --git a/plugins/kdecorations/aurorae/themes/plastik/code/plastikbutton.cpp b/plugins/kdecorations/aurorae/themes/plastik/code/plastikbutton.cpp index e4d03a910..51720ecb1 100644 --- a/plugins/kdecorations/aurorae/themes/plastik/code/plastikbutton.cpp +++ b/plugins/kdecorations/aurorae/themes/plastik/code/plastikbutton.cpp @@ -1,476 +1,470 @@ /******************************************************************** Copyright (C) 2012 Martin Gräßlin Copyright (C) 2003-2005 Sandro Giessl based on the window decoration "Web": Copyright (C) 2001 Rik Hemsley (rikkus) 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 "plastikbutton.h" #include #include #include #include namespace KWin { PlastikButtonProvider::PlastikButtonProvider() : QQuickImageProvider(Pixmap) { } QPixmap PlastikButtonProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) { int origSize = requestedSize.isValid() ? qMin(requestedSize.width(), requestedSize.height()) : 10; if (size) { *size = QSize(origSize, origSize); } QStringList idParts = id.split(QStringLiteral("/")); if (idParts.isEmpty()) { // incorrect id return QQuickImageProvider::requestPixmap(id, size, requestedSize); } bool active = false; bool toggled = false; bool shadow = false; if (idParts.length() > 1 && idParts.at(1) == QStringLiteral("true")) { active = true; } if (idParts.length() > 2 && idParts.at(2) == QStringLiteral("true")) { toggled = true; } if (idParts.length() > 3 && idParts.at(3) == QStringLiteral("true")) { shadow = true; } ButtonIcon button; switch (static_cast(idParts[0].toInt())) { case DecorationButtonClose: button = CloseIcon; break; case DecorationButtonMaximizeRestore: if (toggled) { button = MaxRestoreIcon; } else { button = MaxIcon; } break; case DecorationButtonMinimize: button = MinIcon; break; case DecorationButtonQuickHelp: button = HelpIcon; break; case DecorationButtonOnAllDesktops: if (toggled) { button = NotOnAllDesktopsIcon; } else { button = OnAllDesktopsIcon; } break; case DecorationButtonKeepAbove: if (toggled) { button = NoKeepAboveIcon; } else { button = KeepAboveIcon; } break; case DecorationButtonKeepBelow: if (toggled) { button = NoKeepBelowIcon; } else { button = KeepBelowIcon; } break; case DecorationButtonShade: if (toggled) { button = UnShadeIcon; } else { button = ShadeIcon; } break; case DecorationButtonApplicationMenu: button = AppMenuIcon; break; default: // not recognized icon return QQuickImageProvider::requestPixmap(id, size, requestedSize); } return icon(button, origSize, active, shadow); } QPixmap PlastikButtonProvider::icon(ButtonIcon icon, int size, bool active, bool shadow) { Q_UNUSED(active); if (size%2 == 0) --size; QPixmap image(size, size); image.fill(Qt::transparent); QPainter p(&image); KConfigGroup wmConfig(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("WM")); const QColor color = wmConfig.readEntry("activeForeground", QPalette().color(QPalette::Active, QPalette::HighlightedText)); if (shadow) { p.setPen(KColorScheme::shade(color, KColorScheme::ShadowShade)); } else { p.setPen(color); } const QRect r = image.rect(); // line widths int lwTitleBar = 1; if (r.width() > 16) { lwTitleBar = 4; } else if (r.width() > 4) { lwTitleBar = 2; } int lwArrow = 1; if (r.width() > 16) { lwArrow = 4; } else if (r.width() > 7) { lwArrow = 2; } switch(icon) { case CloseIcon: { int lineWidth = 1; if (r.width() > 16) { lineWidth = 3; } else if (r.width() > 4) { lineWidth = 2; } drawObject(p, DiagonalLine, r.x(), r.y(), r.width(), lineWidth); drawObject(p, CrossDiagonalLine, r.x(), r.bottom(), r.width(), lineWidth); break; } case MaxIcon: { int lineWidth2 = 1; // frame if (r.width() > 16) { lineWidth2 = 2; } else if (r.width() > 4) { lineWidth2 = 1; } drawObject(p, HorizontalLine, r.x(), r.top(), r.width(), lwTitleBar); drawObject(p, HorizontalLine, r.x(), r.bottom()-(lineWidth2-1), r.width(), lineWidth2); drawObject(p, VerticalLine, r.x(), r.top(), r.height(), lineWidth2); drawObject(p, VerticalLine, r.right()-(lineWidth2-1), r.top(), r.height(), lineWidth2); break; } case MaxRestoreIcon: { int lineWidth2 = 1; // frame if (r.width() > 16) { lineWidth2 = 2; } else if (r.width() > 4) { lineWidth2 = 1; } int margin1, margin2; margin1 = margin2 = lineWidth2*2; if (r.width() < 8) margin1 = 1; // background window drawObject(p, HorizontalLine, r.x()+margin1, r.top(), r.width()-margin1, lineWidth2); drawObject(p, HorizontalLine, r.right()-margin2, r.bottom()-(lineWidth2-1)-margin1, margin2, lineWidth2); drawObject(p, VerticalLine, r.x()+margin1, r.top(), margin2, lineWidth2); drawObject(p, VerticalLine, r.right()-(lineWidth2-1), r.top(), r.height()-margin1, lineWidth2); // foreground window drawObject(p, HorizontalLine, r.x(), r.top()+margin2, r.width()-margin2, lwTitleBar); drawObject(p, HorizontalLine, r.x(), r.bottom()-(lineWidth2-1), r.width()-margin2, lineWidth2); drawObject(p, VerticalLine, r.x(), r.top()+margin2, r.height(), lineWidth2); drawObject(p, VerticalLine, r.right()-(lineWidth2-1)-margin2, r.top()+margin2, r.height(), lineWidth2); break; } case MinIcon: { drawObject(p, HorizontalLine, r.x(), r.bottom()-(lwTitleBar-1), r.width(), lwTitleBar); break; } case HelpIcon: { int center = r.x()+r.width()/2 -1; int side = r.width()/4; // paint a question mark... code is quite messy, to be cleaned up later...! :o if (r.width() > 16) { int lineWidth = 3; // top bar drawObject(p, HorizontalLine, center-side+3, r.y(), 2*side-3-1, lineWidth); // top bar rounding drawObject(p, CrossDiagonalLine, center-side-1, r.y()+5, 6, lineWidth); drawObject(p, DiagonalLine, center+side-3, r.y(), 5, lineWidth); // right bar drawObject(p, VerticalLine, center+side+2-lineWidth, r.y()+3, r.height()-(2*lineWidth+side+2+1), lineWidth); // bottom bar drawObject(p, CrossDiagonalLine, center, r.bottom()-2*lineWidth, side+2, lineWidth); drawObject(p, HorizontalLine, center, r.bottom()-3*lineWidth+2, lineWidth, lineWidth); // the dot drawObject(p, HorizontalLine, center, r.bottom()-(lineWidth-1), lineWidth, lineWidth); } else if (r.width() > 8) { int lineWidth = 2; // top bar drawObject(p, HorizontalLine, center-(side-1), r.y(), 2*side-1, lineWidth); // top bar rounding if (r.width() > 9) { drawObject(p, CrossDiagonalLine, center-side-1, r.y()+3, 3, lineWidth); } else { drawObject(p, CrossDiagonalLine, center-side-1, r.y()+2, 3, lineWidth); } drawObject(p, DiagonalLine, center+side-1, r.y(), 3, lineWidth); // right bar drawObject(p, VerticalLine, center+side+2-lineWidth, r.y()+2, r.height()-(2*lineWidth+side+1), lineWidth); // bottom bar drawObject(p, CrossDiagonalLine, center, r.bottom()-2*lineWidth+1, side+2, lineWidth); // the dot drawObject(p, HorizontalLine, center, r.bottom()-(lineWidth-1), lineWidth, lineWidth); } else { int lineWidth = 1; // top bar drawObject(p, HorizontalLine, center-(side-1), r.y(), 2*side, lineWidth); // top bar rounding drawObject(p, CrossDiagonalLine, center-side-1, r.y()+1, 2, lineWidth); // right bar drawObject(p, VerticalLine, center+side+1, r.y(), r.height()-(side+2+1), lineWidth); // bottom bar drawObject(p, CrossDiagonalLine, center, r.bottom()-2, side+2, lineWidth); // the dot drawObject(p, HorizontalLine, center, r.bottom(), 1, 1); } break; } case NotOnAllDesktopsIcon: { int lwMark = r.width()-lwTitleBar*2-2; if (lwMark < 1) lwMark = 3; drawObject(p, HorizontalLine, r.x()+(r.width()-lwMark)/2, r.y()+(r.height()-lwMark)/2, lwMark, lwMark); // Fall through to OnAllDesktopsIcon intended! -#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); -#endif } case OnAllDesktopsIcon: { // horizontal bars drawObject(p, HorizontalLine, r.x()+lwTitleBar, r.y(), r.width()-2*lwTitleBar, lwTitleBar); drawObject(p, HorizontalLine, r.x()+lwTitleBar, r.bottom()-(lwTitleBar-1), r.width()-2*lwTitleBar, lwTitleBar); // vertical bars drawObject(p, VerticalLine, r.x(), r.y()+lwTitleBar, r.height()-2*lwTitleBar, lwTitleBar); drawObject(p, VerticalLine, r.right()-(lwTitleBar-1), r.y()+lwTitleBar, r.height()-2*lwTitleBar, lwTitleBar); break; } case NoKeepAboveIcon: { int center = r.x()+r.width()/2; // arrow drawObject(p, CrossDiagonalLine, r.x(), center+2*lwArrow, center-r.x(), lwArrow); drawObject(p, DiagonalLine, r.x()+center, r.y()+1+2*lwArrow, center-r.x(), lwArrow); if (lwArrow>1) drawObject(p, HorizontalLine, center-(lwArrow-2), r.y()+2*lwArrow, (lwArrow-2)*2, lwArrow); // Fall through to KeepAboveIcon intended! -#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); -#endif } case KeepAboveIcon: { int center = r.x()+r.width()/2; // arrow drawObject(p, CrossDiagonalLine, r.x(), center, center-r.x(), lwArrow); drawObject(p, DiagonalLine, r.x()+center, r.y()+1, center-r.x(), lwArrow); if (lwArrow>1) drawObject(p, HorizontalLine, center-(lwArrow-2), r.y(), (lwArrow-2)*2, lwArrow); break; } case NoKeepBelowIcon: { int center = r.x()+r.width()/2; // arrow drawObject(p, DiagonalLine, r.x(), center-2*lwArrow, center-r.x(), lwArrow); drawObject(p, CrossDiagonalLine, r.x()+center, r.bottom()-1-2*lwArrow, center-r.x(), lwArrow); if (lwArrow>1) drawObject(p, HorizontalLine, center-(lwArrow-2), r.bottom()-(lwArrow-1)-2*lwArrow, (lwArrow-2)*2, lwArrow); // Fall through to KeepBelowIcon intended! -#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); -#endif } case KeepBelowIcon: { int center = r.x()+r.width()/2; // arrow drawObject(p, DiagonalLine, r.x(), center, center-r.x(), lwArrow); drawObject(p, CrossDiagonalLine, r.x()+center, r.bottom()-1, center-r.x(), lwArrow); if (lwArrow>1) drawObject(p, HorizontalLine, center-(lwArrow-2), r.bottom()-(lwArrow-1), (lwArrow-2)*2, lwArrow); break; } case ShadeIcon: { drawObject(p, HorizontalLine, r.x(), r.y(), r.width(), lwTitleBar); break; } case UnShadeIcon: { int lw1 = 1; int lw2 = 1; if (r.width() > 16) { lw1 = 4; lw2 = 2; } else if (r.width() > 7) { lw1 = 2; lw2 = 1; } int h = qMax( (r.width()/2), (lw1+2*lw2) ); // horizontal bars drawObject(p, HorizontalLine, r.x(), r.y(), r.width(), lw1); drawObject(p, HorizontalLine, r.x(), r.x()+h-(lw2-1), r.width(), lw2); // vertical bars drawObject(p, VerticalLine, r.x(), r.y(), h, lw2); drawObject(p, VerticalLine, r.right()-(lw2-1), r.y(), h, lw2); break; } case AppMenuIcon: { drawObject(p, HorizontalLine, r.x(), r.top()+(lwTitleBar-1), r.width(), lwTitleBar); drawObject(p, HorizontalLine, r.x(), r.center().y(), r.width(), lwTitleBar); drawObject(p, HorizontalLine, r.x(), r.bottom()-(lwTitleBar-1), r.width(), lwTitleBar); break; } default: break; } p.end(); return image; } void PlastikButtonProvider::drawObject(QPainter &p, Object object, int x, int y, int length, int lineWidth) { switch(object) { case DiagonalLine: if (lineWidth <= 1) { for (int i = 0; i < length; ++i) { p.drawPoint(x+i,y+i); } } else if (lineWidth <= 2) { for (int i = 0; i < length; ++i) { p.drawPoint(x+i,y+i); } for (int i = 0; i < (length-1); ++i) { p.drawPoint(x+1+i,y+i); p.drawPoint(x+i,y+1+i); } } else { for (int i = 1; i < (length-1); ++i) { p.drawPoint(x+i,y+i); } for (int i = 0; i < (length-1); ++i) { p.drawPoint(x+1+i,y+i); p.drawPoint(x+i,y+1+i); } for (int i = 0; i < (length-2); ++i) { p.drawPoint(x+2+i,y+i); p.drawPoint(x+i,y+2+i); } } break; case CrossDiagonalLine: if (lineWidth <= 1) { for (int i = 0; i < length; ++i) { p.drawPoint(x+i,y-i); } } else if (lineWidth <= 2) { for (int i = 0; i < length; ++i) { p.drawPoint(x+i,y-i); } for (int i = 0; i < (length-1); ++i) { p.drawPoint(x+1+i,y-i); p.drawPoint(x+i,y-1-i); } } else { for (int i = 1; i < (length-1); ++i) { p.drawPoint(x+i,y-i); } for (int i = 0; i < (length-1); ++i) { p.drawPoint(x+1+i,y-i); p.drawPoint(x+i,y-1-i); } for (int i = 0; i < (length-2); ++i) { p.drawPoint(x+2+i,y-i); p.drawPoint(x+i,y-2-i); } } break; case HorizontalLine: for (int i = 0; i < lineWidth; ++i) { p.drawLine(x,y+i, x+length-1, y+i); } break; case VerticalLine: for (int i = 0; i < lineWidth; ++i) { p.drawLine(x+i,y, x+i, y+length-1); } break; default: break; } } } // namespace diff --git a/plugins/kdecorations/aurorae/themes/plastik/package/contents/ui/main.qml b/plugins/kdecorations/aurorae/themes/plastik/package/contents/ui/main.qml index 34a41fd8f..3bbc2f830 100644 --- a/plugins/kdecorations/aurorae/themes/plastik/package/contents/ui/main.qml +++ b/plugins/kdecorations/aurorae/themes/plastik/package/contents/ui/main.qml @@ -1,429 +1,429 @@ /******************************************************************** 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 . *********************************************************************/ import QtQuick 2.0 import org.kde.kwin.decoration 0.1 import org.kde.kwin.decorations.plastik 1.0 Decoration { function readBorderSize() { switch (borderSize) { case DecorationOptions.BorderTiny: borders.setBorders(3); extendedBorders.setAllBorders(0); break; case DecorationOptions.BorderLarge: borders.setBorders(8); extendedBorders.setAllBorders(0); break; case DecorationOptions.BorderVeryLarge: borders.setBorders(12); extendedBorders.setAllBorders(0); break; case DecorationOptions.BorderHuge: borders.setBorders(18); extendedBorders.setAllBorders(0); break; case DecorationOptions.BorderVeryHuge: borders.setBorders(27); extendedBorders.setAllBorders(0); break; case DecorationOptions.BorderOversized: borders.setBorders(40); extendedBorders.setAllBorders(0); break; case DecorationOptions.BorderNoSides: borders.setBorders(4); borders.setSideBorders(1); extendedBorders.setSideBorders(3); break; case DecorationOptions.BorderNone: borders.setBorders(1); extendedBorders.setBorders(3); break; case DecorationOptions.BorderNormal: // fall through to default default: borders.setBorders(4); extendedBorders.setAllBorders(0); break; } } function readConfig() { var titleAlignLeft = decoration.readConfig("titleAlignLeft", true); var titleAlignCenter = decoration.readConfig("titleAlignCenter", false); var titleAlignRight = decoration.readConfig("titleAlignRight", false); if (titleAlignRight) { root.titleAlignment = Text.AlignRight; } else if (titleAlignCenter) { root.titleAlignment = Text.AlignHCenter; } else { if (!titleAlignLeft) { console.log("Error reading title alignment: all alignment options are false"); } root.titleAlignment = Text.AlignLeft; } root.animateButtons = decoration.readConfig("animateButtons", true); root.titleShadow = decoration.readConfig("titleShadow", true); if (decoration.animationsSupported) { root.animationDuration = 150; root.animateButtons = false; } } ColorHelper { id: colorHelper } DecorationOptions { id: options deco: decoration } property int borderSize: decorationSettings.borderSize property alias buttonSize: titleRow.captionHeight property alias titleAlignment: caption.horizontalAlignment property color titleBarColor: options.titleBarColor // set by readConfig after Component completed, ensures that buttons do not flicker property int animationDuration: 0 property bool animateButtons: true property bool titleShadow: true Behavior on titleBarColor { ColorAnimation { duration: root.animationDuration } } id: root alpha: false Rectangle { color: root.titleBarColor anchors { fill: parent } border { width: decoration.client.maximized ? 0 : 2 color: colorHelper.shade(root.titleBarColor, ColorHelper.DarkShade) } Rectangle { id: borderLeft anchors { left: parent.left top: parent.top bottom: parent.bottom leftMargin: 1 bottomMargin: 1 topMargin: 1 } visible: !decoration.client.maximized width: root.borders.left color: root.titleBarColor Rectangle { width: 1 anchors { left: parent.left top: parent.top bottom: parent.bottom } color: colorHelper.shade(root.titleBarColor, ColorHelper.LightShade, colorHelper.contrast - (decoration.client.active ? 0.4 : 0.8)) } } Rectangle { id: borderRight anchors { right: parent.right top: parent.top bottom: parent.bottom rightMargin: 1 bottomMargin: 1 topMargin: 1 } visible: !decoration.client.maximized width: root.borders.right -1 color: root.titleBarColor Rectangle { width: 1 anchors { bottom: parent.bottom top: parent.top + right: parent.right } - x: parent.x + parent.width - 1 color: colorHelper.shade(root.titleBarColor, ColorHelper.DarkShade, colorHelper.contrast - (decoration.client.active ? 0.4 : 0.8)) } } Rectangle { id: borderBottom anchors { left: parent.right right: parent.left bottom: parent.bottom leftMargin: 1 rightMargin: 1 } height: root.borders.bottom visible: !decoration.client.maximized color: root.titleBarColor Rectangle { height: 1 anchors { left: parent.left right: parent.right + bottom: parent.bottom } - y: parent.y + parent.height - 1 color: colorHelper.shade(root.titleBarColor, ColorHelper.DarkShade, colorHelper.contrast - (decoration.client.active ? 0.4 : 0.8)) } } Rectangle { id: top property int topMargin: 1 property real normalHeight: titleRow.normalHeight + topMargin + 1 property real maximizedHeight: titleRow.maximizedHeight + 1 height: decoration.client.maximized ? maximizedHeight : normalHeight anchors { left: parent.left right: parent.right top: parent.top topMargin: decoration.client.maximized ? 0 : top.topMargin leftMargin: decoration.client.maximized ? 0 : 2 rightMargin: decoration.client.maximized ? 0 : 2 } gradient: Gradient { id: topGradient GradientStop { position: 0.0 color: colorHelper.shade(root.titleBarColor, ColorHelper.MidlightShade, colorHelper.contrast - 0.4) } GradientStop { id: middleGradientStop position: 4.0/(decoration.client.maximized ? top.maximizedHeight : top.normalHeight) color: colorHelper.shade(root.titleBarColor, ColorHelper.MidShade, colorHelper.contrast - 0.4) } GradientStop { position: 1.0 color: root.titleBarColor } } Rectangle { height: 1 anchors { top: top.top left: top.left right: top.right } visible: !decoration.client.maximized color: colorHelper.shade(root.titleBarColor, ColorHelper.LightShade, colorHelper.contrast - (decoration.client.active ? 0.4 : 0.8)) } Item { id: titleRow property real captionHeight: caption.implicitHeight + 4 property int topMargin: 3 property int bottomMargin: 1 property real normalHeight: captionHeight + bottomMargin + topMargin property real maximizedHeight: captionHeight + bottomMargin anchors { left: parent.left right: parent.right top: parent.top topMargin: decoration.client.maximized ? 0 : titleRow.topMargin leftMargin: decoration.client.maximized ? 0 : 3 rightMargin: decoration.client.maximized ? 0 : 3 bottomMargin: titleRow.bottomMargin } ButtonGroup { id: leftButtonGroup spacing: 1 explicitSpacer: root.buttonSize menuButton: menuButtonComponent appMenuButton: appMenuButtonComponent minimizeButton: minimizeButtonComponent maximizeButton: maximizeButtonComponent keepBelowButton: keepBelowButtonComponent keepAboveButton: keepAboveButtonComponent helpButton: helpButtonComponent shadeButton: shadeButtonComponent allDesktopsButton: stickyButtonComponent closeButton: closeButtonComponent buttons: options.titleButtonsLeft anchors { top: parent.top left: parent.left } } Text { id: caption textFormat: Text.PlainText anchors { top: parent.top left: leftButtonGroup.right right: rightButtonGroup.left rightMargin: 5 leftMargin: 5 topMargin: 3 } color: options.fontColor Behavior on color { ColorAnimation { duration: root.animationDuration } } text: decoration.client.caption font: options.titleFont style: root.titleShadow ? Text.Raised : Text.Normal styleColor: colorHelper.shade(color, ColorHelper.ShadowShade) elide: Text.ElideMiddle renderType: Text.NativeRendering } ButtonGroup { id: rightButtonGroup spacing: 1 explicitSpacer: root.buttonSize menuButton: menuButtonComponent appMenuButton: appMenuButtonComponent minimizeButton: minimizeButtonComponent maximizeButton: maximizeButtonComponent keepBelowButton: keepBelowButtonComponent keepAboveButton: keepAboveButtonComponent helpButton: helpButtonComponent shadeButton: shadeButtonComponent allDesktopsButton: stickyButtonComponent closeButton: closeButtonComponent buttons: options.titleButtonsRight anchors { top: parent.top right: parent.right } } Component.onCompleted: { decoration.installTitleItem(titleRow); } } } Item { id: innerBorder anchors.fill: parent Rectangle { anchors { left: parent.left right: parent.right } height: 1 y: top.height - 1 visible: decoration.client.maximized color: colorHelper.shade(root.titleBarColor, ColorHelper.MidShade) } Rectangle { anchors { fill: parent leftMargin: root.borders.left - 1 rightMargin: root.borders.right topMargin: root.borders.top - 1 bottomMargin: root.borders.bottom } border { width: 1 color: colorHelper.shade(root.titleBarColor, ColorHelper.MidShade) } visible: !decoration.client.maximized color: root.titleBarColor } } } Component { id: maximizeButtonComponent PlastikButton { objectName: "maximizeButton" buttonType: DecorationOptions.DecorationButtonMaximizeRestore size: root.buttonSize } } Component { id: keepBelowButtonComponent PlastikButton { buttonType: DecorationOptions.DecorationButtonKeepBelow size: root.buttonSize } } Component { id: keepAboveButtonComponent PlastikButton { buttonType: DecorationOptions.DecorationButtonKeepAbove size: root.buttonSize } } Component { id: helpButtonComponent PlastikButton { buttonType: DecorationOptions.DecorationButtonQuickHelp size: root.buttonSize } } Component { id: minimizeButtonComponent PlastikButton { buttonType: DecorationOptions.DecorationButtonMinimize size: root.buttonSize } } Component { id: shadeButtonComponent PlastikButton { buttonType: DecorationOptions.DecorationButtonShade size: root.buttonSize } } Component { id: stickyButtonComponent PlastikButton { buttonType: DecorationOptions.DecorationButtonOnAllDesktops size: root.buttonSize } } Component { id: closeButtonComponent PlastikButton { buttonType: DecorationOptions.DecorationButtonClose size: root.buttonSize } } Component { id: menuButtonComponent MenuButton { width: root.buttonSize height: root.buttonSize } } Component { id: appMenuButtonComponent PlastikButton { buttonType: DecorationOptions.DecorationButtonApplicationMenu size: root.buttonSize } } Component.onCompleted: { borders.setBorders(4); borders.setTitle(top.normalHeight); maximizedBorders.setTitle(top.maximizedHeight); readBorderSize(); readConfig(); } Connections { target: decoration onConfigChanged: root.readConfig() } Connections { target: decorationSettings onBorderSizeChanged: root.readBorderSize(); } } diff --git a/plugins/kglobalaccel/CMakeLists.txt b/plugins/kglobalaccel/CMakeLists.txt index 5b6f8310e..96651f466 100644 --- a/plugins/kglobalaccel/CMakeLists.txt +++ b/plugins/kglobalaccel/CMakeLists.txt @@ -1,16 +1,17 @@ set(kglobalaccel_plugin_SRCS kglobalaccel_plugin.cpp ) add_library(KF5GlobalAccelPrivateKWin MODULE ${kglobalaccel_plugin_SRCS}) +set_target_properties(KF5GlobalAccelPrivateKWin PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kglobalaccel5.platforms/") target_link_libraries(KF5GlobalAccelPrivateKWin KF5::GlobalAccelPrivate kwin ) install( TARGETS KF5GlobalAccelPrivateKWin DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kglobalaccel5.platforms/ ) diff --git a/plugins/platforms/drm/drm.json b/plugins/platforms/drm/drm.json index da6e9357b..da3311ebc 100644 --- a/plugins/platforms/drm/drm.json +++ b/plugins/platforms/drm/drm.json @@ -1,51 +1,51 @@ { "KPlugin": { "Description": "Render through drm node.", "Description[ca@valencia]": "Renderitza mitjançant el node del DRM.", "Description[ca]": "Renderitza mitjançant el node del DRM.", "Description[da]": "Render igennem drm-knude.", "Description[de]": "In DRM-Knoten rendern.", "Description[el]": "Αποτύπωση μέσω λειτουργίας drm.", "Description[es]": "Renderizar a través del nodo DRM.", "Description[et]": "Renderdamine drm režiimis.", "Description[eu]": "Errendatu DRM-korapilunea erabiliz.", - "Description[fi]": "Renderöi DRM-noden läpi.", + "Description[fi]": "Hahmonna DRM-solmun läpi.", "Description[fr]": "Rendre par le biais d'un nœud de rendu « DRM ».", "Description[gl]": "Renderizar a través dun nodo de DRM.", "Description[hu]": "Renderelés drm node-on.", "Description[it]": "Resa tramite nodo drm.", "Description[ko]": "DRM 노드로 렌더링합니다.", "Description[nl]": "Via drm-node renderen.", "Description[nn]": "Teikn opp gjennom drm-node.", "Description[pl]": "Wyświetlaj przez węzeł drm.", "Description[pt]": "Desenhar através de um nó DRM.", "Description[pt_BR]": "Renderizar pelo nó de DRM.", "Description[ru]": "Отрисовка через DRM", "Description[sk]": "Renderovať cez režim drm.", "Description[sl]": "Izriši preko vozlišča drm.", "Description[sr@ijekavian]": "Рендеровање кроз ДРМ чвор.", "Description[sr@ijekavianlatin]": "Renderovanje kroz DRM čvor.", "Description[sr@latin]": "Renderovanje kroz DRM čvor.", "Description[sr]": "Рендеровање кроз ДРМ чвор.", "Description[sv]": "Återge via DRM-nod.", "Description[tr]": "Drm düğümü aracılığıyla gerçekle.", "Description[uk]": "Обробляти через вузол DRM.", "Description[x-test]": "xxRender through drm node.xx", "Description[zh_CN]": "通过 drm 结点渲染。", "Description[zh_TW]": "透過 drm 節點成像。", "Id": "KWinWaylandDrmBackend", "Name": "drm", "Name[ca@valencia]": "DRM", "Name[ca]": "DRM", "Name[de]": "DRM", "Name[eu]": "DRM", "Name[pt]": "DRM", "Name[sr@ijekavian]": "ДРМ", "Name[sr@ijekavianlatin]": "DRM", "Name[sr@latin]": "DRM", "Name[sr]": "ДРМ", "Name[sv]": "DRM", "Name[x-test]": "xxdrmxx" }, "input": false } diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp index d3be841dd..addf5d42b 100644 --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -1,752 +1,793 @@ /******************************************************************** 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 "drm_backend.h" #include "drm_output.h" #include "drm_object_connector.h" #include "drm_object_crtc.h" #include "drm_object_plane.h" #include "composite.h" #include "cursor.h" #include "logging.h" #include "logind.h" #include "main.h" #include "scene_qpainter_drm_backend.h" #include "screens_drm.h" #include "udev.h" #include "wayland_server.h" +#include #if HAVE_GBM #include "egl_gbm_backend.h" #include #endif // KWayland #include #include // KF5 #include #include #include #include // Qt #include #include #include // system #include // drm #include #include #include #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif #ifndef DRM_CAP_CURSOR_HEIGHT #define DRM_CAP_CURSOR_HEIGHT 0x9 #endif #define KWIN_DRM_EVENT_CONTEXT_VERSION 2 namespace KWin { DrmBackend::DrmBackend(QObject *parent) : Platform(parent) , m_udev(new Udev) , m_udevMonitor(m_udev->monitor()) , m_dpmsFilter() { + setSupportsGammaControl(true); handleOutputs(); - m_cursor[0] = nullptr; - m_cursor[1] = nullptr; } DrmBackend::~DrmBackend() { #if HAVE_GBM if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } #endif if (m_fd >= 0) { // wait for pageflips while (m_pageFlipsPending != 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } qDeleteAll(m_outputs); qDeleteAll(m_planes); qDeleteAll(m_crtcs); qDeleteAll(m_connectors); - delete m_cursor[0]; - delete m_cursor[1]; close(m_fd); } } void DrmBackend::init() { qCInfo(KWIN_DRM) << "Initializing DRM backend"; LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openDrm(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &DrmBackend::openDrm); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } } void DrmBackend::outputWentOff() { if (!m_dpmsFilter.isNull()) { // already another output is off return; } m_dpmsFilter.reset(new DpmsInputEventFilter(this)); input()->prependInputEventFilter(m_dpmsFilter.data()); } void DrmBackend::turnOutputsOn() { m_dpmsFilter.reset(); - for (auto it = m_outputs.constBegin(), end = m_outputs.constEnd(); it != end; it++) { + for (auto it = m_enabledOutputs.constBegin(), end = m_enabledOutputs.constEnd(); it != end; it++) { (*it)->setDpms(DrmOutput::DpmsMode::On); } } void DrmBackend::checkOutputsAreOn() { if (m_dpmsFilter.isNull()) { // already disabled, all outputs are on return; } - for (auto it = m_outputs.constBegin(), end = m_outputs.constEnd(); it != end; it++) { + for (auto it = m_enabledOutputs.constBegin(), end = m_enabledOutputs.constEnd(); it != end; it++) { if (!(*it)->isDpmsEnabled()) { // dpms still disabled, need to keep the filter return; } } // all outputs are on, disable the filter m_dpmsFilter.reset(); } void DrmBackend::activate(bool active) { if (active) { qCDebug(KWIN_DRM) << "Activating session."; reactivate(); } else { qCDebug(KWIN_DRM) << "Deactivating session."; deactivate(); } } void DrmBackend::reactivate() { if (m_active) { return; } m_active = true; if (!usesSoftwareCursor()) { - DrmDumbBuffer *c = m_cursor[(m_cursorIndex + 1) % 2]; const QPoint cp = Cursor::pos() - softwareCursorHotspot(); for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; // only relevant in atomic mode o->m_modesetRequested = true; o->pageFlipped(); // TODO: Do we really need this? o->m_crtc->blank(); - o->showCursor(c); + o->showCursor(); o->moveCursor(cp); } } // restart compositor m_pageFlipsPending = 0; if (Compositor *compositor = Compositor::self()) { compositor->bufferSwapComplete(); compositor->addRepaintFull(); } } void DrmBackend::deactivate() { if (!m_active) { return; } // block compositor if (m_pageFlipsPending == 0 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } // hide cursor and disable for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; o->hideCursor(); } m_active = false; } void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { Q_UNUSED(fd) Q_UNUSED(frame) Q_UNUSED(sec) Q_UNUSED(usec) auto output = reinterpret_cast(data); output->pageFlipped(); output->m_backend->m_pageFlipsPending--; if (output->m_backend->m_pageFlipsPending == 0) { // TODO: improve, this currently means we wait for all page flips or all outputs. // It would be better to driver the repaint per output if (output->m_dpmsAtomicOffPending) { output->m_modesetRequested = true; output->dpmsAtomicOff(); } if (Compositor::self()) { Compositor::self()->bufferSwapComplete(); } } } void DrmBackend::openDrm() { connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, &DrmBackend::activate); UdevDevice::Ptr device = m_udev->primaryGpu(); if (!device) { qCWarning(KWIN_DRM) << "Did not find a GPU"; return; } int fd = LogindIntegration::self()->takeDevice(device->devNode()); if (fd < 0) { qCWarning(KWIN_DRM) << "failed to open drm device at" << device->devNode(); return; } m_fd = fd; m_active = true; QSocketNotifier *notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { if (!LogindIntegration::self()->isActiveSession()) { return; } drmEventContext e; memset(&e, 0, sizeof e); e.version = KWIN_DRM_EVENT_CONTEXT_VERSION; e.page_flip_handler = pageFlipHandler; drmHandleEvent(m_fd, &e); } ); m_drmId = device->sysNum(); // trying to activate Atomic Mode Setting (this means also Universal Planes) if (!qEnvironmentVariableIsSet("KWIN_DRM_NO_AMS")) { if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) { qCDebug(KWIN_DRM) << "Using Atomic Mode Setting."; m_atomicModeSetting = true; ScopedDrmPointer planeResources(drmModeGetPlaneResources(m_fd)); if (!planeResources) { qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode"; m_atomicModeSetting = false; } if (m_atomicModeSetting) { qCDebug(KWIN_DRM) << "Number of planes:" << planeResources->count_planes; // create the plane objects for (unsigned int i = 0; i < planeResources->count_planes; ++i) { drmModePlane *kplane = drmModeGetPlane(m_fd, planeResources->planes[i]); - DrmPlane *p = new DrmPlane(kplane->plane_id, this); + DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd); if (p->atomicInit()) { m_planes << p; if (p->type() == DrmPlane::TypeIndex::Overlay) { m_overlayPlanes << p; } } else { delete p; } } if (m_planes.isEmpty()) { qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode"; m_atomicModeSetting = false; } } } else { qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode."; } } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd)); drmModeRes *res = resources.data(); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } for (int i = 0; i < res->count_connectors; ++i) { - m_connectors << new DrmConnector(res->connectors[i], this); + m_connectors << new DrmConnector(res->connectors[i], m_fd); } for (int i = 0; i < res->count_crtcs; ++i) { m_crtcs << new DrmCrtc(res->crtcs[i], this, i); } if (m_atomicModeSetting) { auto tryAtomicInit = [] (DrmObject *obj) -> bool { if (obj->atomicInit()) { return false; } else { delete obj; return true; } }; m_connectors.erase(std::remove_if(m_connectors.begin(), m_connectors.end(), tryAtomicInit), m_connectors.end()); m_crtcs.erase(std::remove_if(m_crtcs.begin(), m_crtcs.end(), tryAtomicInit), m_crtcs.end()); } + initCursor(); updateOutputs(); if (m_outputs.isEmpty()) { qCWarning(KWIN_DRM) << "No outputs, cannot render, will terminate now"; emit initFailed(); return; } // setup udevMonitor if (m_udevMonitor) { m_udevMonitor->filterSubsystemDevType("drm"); const int fd = m_udevMonitor->fd(); if (fd != -1) { QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { auto device = m_udevMonitor->getDevice(); if (!device) { return; } if (device->sysNum() != m_drmId) { return; } if (device->hasProperty("HOTPLUG", "1")) { qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device"; updateOutputs(); - m_cursorIndex = (m_cursorIndex + 1) % 2; updateCursor(); } } ); m_udevMonitor->enable(); } } setReady(true); - - initCursor(); } void DrmBackend::updateOutputs() { if (m_fd < 0) { return; } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd)); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } QVector connectedOutputs; QVector pendingConnectors; // split up connected connectors in already or not yet assigned ones for (DrmConnector *con : qAsConst(m_connectors)) { if (!con->isConnected()) { continue; } if (DrmOutput *o = findOutput(con->id())) { connectedOutputs << o; } else { pendingConnectors << con; } } // check for outputs which got removed auto it = m_outputs.begin(); while (it != m_outputs.end()) { if (connectedOutputs.contains(*it)) { it++; continue; } DrmOutput *removed = *it; it = m_outputs.erase(it); + m_enabledOutputs.removeOne(removed); emit outputRemoved(removed); delete removed; } // now check new connections for (DrmConnector *con : qAsConst(pendingConnectors)) { ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> connector(drmModeGetConnector(m_fd, con->id())); if (!connector) { continue; } if (connector->count_modes == 0) { continue; } bool outputDone = false; QVector encoders = con->encoders(); for (auto encId : qAsConst(encoders)) { ScopedDrmPointer<_drmModeEncoder, &drmModeFreeEncoder> encoder(drmModeGetEncoder(m_fd, encId)); if (!encoder) { continue; } for (DrmCrtc *crtc : qAsConst(m_crtcs)) { if (!(encoder->possible_crtcs & (1 << crtc->resIndex()))) { continue; } // check if crtc isn't used yet -- currently we don't allow multiple outputs on one crtc (cloned mode) auto it = std::find_if(connectedOutputs.constBegin(), connectedOutputs.constEnd(), [crtc] (DrmOutput *o) { return o->m_crtc == crtc; } ); if (it != connectedOutputs.constEnd()) { continue; } // we found a suitable encoder+crtc // TODO: we could avoid these lib drm calls if we store all struct data in DrmCrtc and DrmConnector in the beginning ScopedDrmPointer<_drmModeCrtc, &drmModeFreeCrtc> modeCrtc(drmModeGetCrtc(m_fd, crtc->id())); if (!modeCrtc) { continue; } DrmOutput *output = new DrmOutput(this); con->setOutput(output); output->m_conn = con; crtc->setOutput(output); output->m_crtc = crtc; connect(output, &DrmOutput::dpmsChanged, this, &DrmBackend::outputDpmsChanged); if (modeCrtc->mode_valid) { output->m_mode = modeCrtc->mode; } else { output->m_mode = connector->modes[0]; } qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name; if (!output->init(connector.data())) { qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id(); - con->setOutput(nullptr); - crtc->setOutput(nullptr); delete output; continue; } + if (!output->initCursor(m_cursorSize)) { + setSoftWareCursor(true); + } qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid(); connectedOutputs << output; emit outputAdded(output); outputDone = true; break; } if (outputDone) { break; } } } std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_conn->id() < b->m_conn->id(); }); m_outputs = connectedOutputs; + m_enabledOutputs = connectedOutputs; readOutputsConfiguration(); if (!m_outputs.isEmpty()) { emit screensQueried(); } } void DrmBackend::readOutputsConfiguration() { if (m_outputs.isEmpty()) { return; } const QByteArray uuid = generateOutputConfigurationUuid(); const auto outputGroup = kwinApp()->config()->group("DrmOutputs"); const auto configGroup = outputGroup.group(uuid); // default position goes from left to right QPoint pos(0, 0); for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { qCDebug(KWIN_DRM) << "Reading output configuration for [" << uuid << "] ["<< (*it)->uuid() << "]"; const auto outputConfig = configGroup.group((*it)->uuid()); (*it)->setGlobalPos(outputConfig.readEntry("Position", pos)); // TODO: add mode (*it)->setScale(outputConfig.readEntry("Scale", 1.0)); pos.setX(pos.x() + (*it)->geometry().width()); } } QByteArray DrmBackend::generateOutputConfigurationUuid() const { auto it = m_outputs.constBegin(); if (m_outputs.size() == 1) { // special case: one output return (*it)->uuid(); } QCryptographicHash hash(QCryptographicHash::Md5); for (; it != m_outputs.constEnd(); ++it) { hash.addData((*it)->uuid()); } return hash.result().toHex().left(10); } void DrmBackend::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { const auto changes = config->changes(); - for (auto it = changes.begin(); it != changes.end(); it++) { + bool countChanged = false; + //process all non-disabling changes + for (auto it = changes.begin(); it != changes.end(); it++) { KWayland::Server::OutputChangeSet *changeset = it.value(); auto drmoutput = findOutput(it.key()->uuid()); if (drmoutput == nullptr) { qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); - return; + continue; + } + if (changeset->enabledChanged() && changeset->enabled() == KWayland::Server::OutputDeviceInterface::Enablement::Enabled) { + drmoutput->setEnabled(true); + m_enabledOutputs << drmoutput; + emit outputAdded(drmoutput); + countChanged = true; } drmoutput->setChanges(changeset); } - emit screens()->changed(); + //process any disable requests + for (auto it = changes.begin(); it != changes.end(); it++) { + KWayland::Server::OutputChangeSet *changeset = it.value(); + if (changeset->enabledChanged() && changeset->enabled() == KWayland::Server::OutputDeviceInterface::Enablement::Disabled) { + if (m_enabledOutputs.count() == 1) { + qCWarning(KWIN_DRM) << "Not disabling final screen" << it.key()->uuid(); + continue; + } + auto drmoutput = findOutput(it.key()->uuid()); + if (drmoutput == nullptr) { + qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); + continue; + } + drmoutput->setEnabled(false); + m_enabledOutputs.removeOne(drmoutput); + emit outputRemoved(drmoutput); + countChanged = true; + } + } + + if (countChanged) { + emit screensQueried(); + } else { + emit screens()->changed(); + } // KCoreAddons needs kwayland's 2b3f9509ac1 to not crash if (KCoreAddons::version() >= QT_VERSION_CHECK(5, 39, 0)) { config->setApplied(); } } DrmOutput *DrmBackend::findOutput(quint32 connector) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) { return o->m_conn->id() == connector; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } DrmOutput *DrmBackend::findOutput(const QByteArray &uuid) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [uuid] (DrmOutput *o) { return o->m_uuid == uuid; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output) { if (!buffer || buffer->bufferId() == 0) { if (m_deleteBufferAfterPageFlip) { delete buffer; } return; } if (output->present(buffer)) { m_pageFlipsPending++; if (m_pageFlipsPending == 1 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } } else if (m_deleteBufferAfterPageFlip) { delete buffer; } } void DrmBackend::initCursor() { m_cursorEnabled = waylandServer()->seat()->hasPointer(); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasPointerChanged, this, [this] { m_cursorEnabled = waylandServer()->seat()->hasPointer(); if (usesSoftwareCursor()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { if (m_cursorEnabled) { - (*it)->showCursor(m_cursor[m_cursorIndex]); + (*it)->showCursor(); } else { (*it)->hideCursor(); } } } ); uint64_t capability = 0; QSize cursorSize; if (drmGetCap(m_fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { cursorSize.setWidth(capability); } else { cursorSize.setWidth(64); } if (drmGetCap(m_fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { cursorSize.setHeight(capability); } else { cursorSize.setHeight(64); } - auto createCursor = [this, cursorSize] (int index) { - m_cursor[index] = createBuffer(cursorSize); - if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { - return false; - } - m_cursor[index]->image()->fill(Qt::transparent); - return true; - }; - if (!createCursor(0) || !createCursor(1)) { - setSoftWareCursor(true); - return; - } + m_cursorSize = cursorSize; // now we have screens and can set cursors, so start tracking connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor); connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor); } void DrmBackend::setCursor() { - DrmDumbBuffer *c = m_cursor[m_cursorIndex]; - m_cursorIndex = (m_cursorIndex + 1) % 2; if (m_cursorEnabled) { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { - (*it)->showCursor(c); + (*it)->showCursor(); } } markCursorAsRendered(); } void DrmBackend::updateCursor() { if (usesSoftwareCursor()) { return; } if (isCursorHidden()) { return; } const QImage &cursorImage = softwareCursor(); if (cursorImage.isNull()) { doHideCursor(); return; } - QImage *c = m_cursor[m_cursorIndex]->image(); - c->fill(Qt::transparent); - QPainter p; - p.begin(c); - p.drawImage(QPoint(0, 0), cursorImage); - p.end(); + for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { + (*it)->updateCursor(); + } setCursor(); moveCursor(); } void DrmBackend::doShowCursor() { updateCursor(); } void DrmBackend::doHideCursor() { if (!m_cursorEnabled) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->hideCursor(); } } void DrmBackend::moveCursor() { if (!m_cursorEnabled || isCursorHidden()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->moveCursor(Cursor::pos()); } } Screens *DrmBackend::createScreens(QObject *parent) { return new DrmScreens(this, parent); } QPainterBackend *DrmBackend::createQPainterBackend() { m_deleteBufferAfterPageFlip = false; return new DrmQPainterBackend(this); } OpenGLBackend *DrmBackend::createOpenGLBackend() { #if HAVE_GBM m_deleteBufferAfterPageFlip = true; return new EglGbmBackend(this); #else return Platform::createOpenGLBackend(); #endif } DrmDumbBuffer *DrmBackend::createBuffer(const QSize &size) { - DrmDumbBuffer *b = new DrmDumbBuffer(this, size); + DrmDumbBuffer *b = new DrmDumbBuffer(m_fd, size); return b; } #if HAVE_GBM DrmSurfaceBuffer *DrmBackend::createBuffer(const std::shared_ptr &surface) { - DrmSurfaceBuffer *b = new DrmSurfaceBuffer(this, surface); + DrmSurfaceBuffer *b = new DrmSurfaceBuffer(m_fd, surface); return b; } #endif void DrmBackend::outputDpmsChanged() { - if (m_outputs.isEmpty()) { + if (m_enabledOutputs.isEmpty()) { return; } bool enabled = false; - for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { + for (auto it = m_enabledOutputs.constBegin(); it != m_enabledOutputs.constEnd(); ++it) { enabled = enabled || (*it)->isDpmsEnabled(); } setOutputsEnabled(enabled); } QVector DrmBackend::supportedCompositors() const { #if HAVE_GBM return QVector{OpenGLCompositing, QPainterCompositing}; #else return QVector{QPainterCompositing}; #endif } +int DrmBackend::gammaRampSize(int screen) const +{ + if (m_outputs.size() <= screen) { + return 0; + } + return m_outputs.at(screen)->m_crtc->getGammaRampSize(); +} + +bool DrmBackend::setGammaRamp(int screen, ColorCorrect::GammaRamp &gamma) +{ + if (m_outputs.size() <= screen) { + return false; + } + return m_outputs.at(screen)->m_crtc->setGammaRamp(gamma); +} + +QString DrmBackend::supportInformation() const +{ + QString supportInfo; + QDebug s(&supportInfo); + s.nospace(); + s << "Name: " << "DRM" << endl; + s << "Active: " << m_active << endl; + s << "Atomic Mode Setting: " << m_atomicModeSetting << endl; + return supportInfo; +} + } diff --git a/plugins/platforms/drm/drm_backend.h b/plugins/platforms/drm/drm_backend.h index e3563e996..13af3121c 100644 --- a/plugins/platforms/drm/drm_backend.h +++ b/plugins/platforms/drm/drm_backend.h @@ -1,180 +1,199 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_BACKEND_H #define KWIN_DRM_BACKEND_H #include "platform.h" #include "input.h" #include "drm_buffer.h" #if HAVE_GBM #include "drm_buffer_gbm.h" #endif #include "drm_inputeventfilter.h" #include "drm_pointer.h" #include #include #include #include #include #include struct gbm_bo; struct gbm_device; struct gbm_surface; namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; } } namespace KWin { +namespace ColorCorrect { +struct GammaRamp; +} + class Udev; class UdevMonitor; class DrmOutput; class DrmPlane; class DrmCrtc; class DrmConnector; class GbmSurface; class KWIN_EXPORT DrmBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "drm.json") public: explicit DrmBackend(QObject *parent = nullptr); virtual ~DrmBackend(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) override; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; OpenGLBackend* createOpenGLBackend() override; void init() override; DrmDumbBuffer *createBuffer(const QSize &size); #if HAVE_GBM DrmSurfaceBuffer *createBuffer(const std::shared_ptr &surface); #endif void present(DrmBuffer *buffer, DrmOutput *output); int fd() const { return m_fd; } QVector outputs() const { return m_outputs; } + QVector enabledOutputs() const { + return m_enabledOutputs; + } QVector planes() const { return m_planes; } QVector overlayPlanes() const { return m_overlayPlanes; } void outputWentOff(); void checkOutputsAreOn(); // QPainter reuses buffers bool deleteBufferAfterPageFlip() const { return m_deleteBufferAfterPageFlip; } // returns use of AMS, default is not/legacy bool atomicModeSetting() const { return m_atomicModeSetting; } void setGbmDevice(gbm_device *device) { m_gbmDevice = device; } gbm_device *gbmDevice() const { return m_gbmDevice; } + int gammaRampSize(int screen) const override; + bool setGammaRamp(int screen, ColorCorrect::GammaRamp &gamma) override; QVector supportedCompositors() const override; + QString supportInformation() const override; + public Q_SLOTS: void turnOutputsOn(); Q_SIGNALS: + /** + * Emitted whenever an output is removed/disabled + */ void outputRemoved(KWin::DrmOutput *output); + /** + * Emitted whenever an output is added/enabled + */ void outputAdded(KWin::DrmOutput *output); protected: void doHideCursor() override; void doShowCursor() override; private: static void pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data); void openDrm(); void activate(bool active); void reactivate(); void deactivate(); void updateOutputs(); void setCursor(); void updateCursor(); void moveCursor(); void initCursor(); void outputDpmsChanged(); void readOutputsConfiguration(); QByteArray generateOutputConfigurationUuid() const; DrmOutput *findOutput(quint32 connector); DrmOutput *findOutput(const QByteArray &uuid); QScopedPointer m_udev; QScopedPointer m_udevMonitor; int m_fd = -1; int m_drmId = 0; // all crtcs QVector m_crtcs; // all connectors QVector m_connectors; - // currently active output pipelines (planes + crtc + encoder + connector) + // active output pipelines (planes + crtc + encoder + connector) QVector m_outputs; - DrmDumbBuffer *m_cursor[2]; + // active and enabled pipelines (above + wl_output) + QVector m_enabledOutputs; + bool m_deleteBufferAfterPageFlip; bool m_atomicModeSetting = false; bool m_cursorEnabled = false; - int m_cursorIndex = 0; + QSize m_cursorSize; int m_pageFlipsPending = 0; bool m_active = false; // all available planes: primarys, cursors and overlays QVector m_planes; QVector m_overlayPlanes; QScopedPointer m_dpmsFilter; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; gbm_device *m_gbmDevice = nullptr; }; } #endif diff --git a/plugins/platforms/drm/drm_buffer.cpp b/plugins/platforms/drm/drm_buffer.cpp index a28c45942..b640c593c 100644 --- a/plugins/platforms/drm/drm_buffer.cpp +++ b/plugins/platforms/drm/drm_buffer.cpp @@ -1,107 +1,107 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "drm_buffer.h" -#include "drm_backend.h" #include "logging.h" // system #include #include // drm #include +#include namespace KWin { -DrmBuffer:: DrmBuffer(DrmBackend *backend) - : m_backend(backend) +DrmBuffer:: DrmBuffer(int fd) + : m_fd(fd) { } // DrmDumbBuffer -DrmDumbBuffer::DrmDumbBuffer(DrmBackend *backend, const QSize &size) - : DrmBuffer(backend) +DrmDumbBuffer::DrmDumbBuffer(int fd, const QSize &size) + : DrmBuffer(fd) { m_size = size; drm_mode_create_dumb createArgs; memset(&createArgs, 0, sizeof createArgs); createArgs.bpp = 32; createArgs.width = size.width(); createArgs.height = size.height(); - if (drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_CREATE_DUMB, &createArgs) != 0) { + if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &createArgs) != 0) { qCWarning(KWIN_DRM) << "DRM_IOCTL_MODE_CREATE_DUMB failed"; return; } m_handle = createArgs.handle; m_bufferSize = createArgs.size; m_stride = createArgs.pitch; - if (drmModeAddFB(m_backend->fd(), size.width(), size.height(), 24, 32, + if (drmModeAddFB(fd, size.width(), size.height(), 24, 32, m_stride, createArgs.handle, &m_bufferId) != 0) { qCWarning(KWIN_DRM) << "drmModeAddFB failed with errno" << errno; } } DrmDumbBuffer::~DrmDumbBuffer() { if (m_bufferId) { - drmModeRmFB(m_backend->fd(), m_bufferId); + drmModeRmFB(fd(), m_bufferId); } delete m_image; if (m_memory) { munmap(m_memory, m_bufferSize); } if (m_handle) { drm_mode_destroy_dumb destroyArgs; destroyArgs.handle = m_handle; - drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &destroyArgs); + drmIoctl(fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &destroyArgs); } } bool DrmDumbBuffer::needsModeChange(DrmBuffer *b) const { if (DrmDumbBuffer *db = dynamic_cast(b)) { return m_stride != db->stride(); } else { return true; } } bool DrmDumbBuffer::map(QImage::Format format) { if (!m_handle || !m_bufferId) { return false; } drm_mode_map_dumb mapArgs; memset(&mapArgs, 0, sizeof mapArgs); mapArgs.handle = m_handle; - if (drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_MAP_DUMB, &mapArgs) != 0) { + if (drmIoctl(fd(), DRM_IOCTL_MODE_MAP_DUMB, &mapArgs) != 0) { return false; } - void *address = mmap(nullptr, m_bufferSize, PROT_WRITE, MAP_SHARED, m_backend->fd(), mapArgs.offset); + void *address = mmap(nullptr, m_bufferSize, PROT_WRITE, MAP_SHARED, fd(), mapArgs.offset); if (address == MAP_FAILED) { return false; } m_memory = address; m_image = new QImage((uchar*)m_memory, m_size.width(), m_size.height(), m_stride, format); return !m_image->isNull(); } } diff --git a/plugins/platforms/drm/drm_buffer.h b/plugins/platforms/drm/drm_buffer.h index ab50f00ba..7af2ef913 100644 --- a/plugins/platforms/drm/drm_buffer.h +++ b/plugins/platforms/drm/drm_buffer.h @@ -1,86 +1,88 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_BUFFER_H #define KWIN_DRM_BUFFER_H #include #include namespace KWin { -class DrmBackend; - class DrmBuffer { public: - DrmBuffer(DrmBackend *backend); + DrmBuffer(int fd); virtual ~DrmBuffer() = default; virtual bool needsModeChange(DrmBuffer *b) const {Q_UNUSED(b) return false;} quint32 bufferId() const { return m_bufferId; } const QSize &size() const { return m_size; } virtual void releaseGbm() {} + int fd() const { + return m_fd; + } + protected: - DrmBackend *m_backend; quint32 m_bufferId = 0; QSize m_size; + int m_fd; }; class DrmDumbBuffer : public DrmBuffer { public: - DrmDumbBuffer(DrmBackend *backend, const QSize &size); + DrmDumbBuffer(int fd, const QSize &size); ~DrmDumbBuffer(); bool needsModeChange(DrmBuffer *b) const override; bool map(QImage::Format format = QImage::Format_RGB32); quint32 handle() const { return m_handle; } QImage *image() const { return m_image; } quint32 stride() const { return m_stride; } private: quint32 m_handle = 0; quint64 m_bufferSize = 0; void *m_memory = nullptr; QImage *m_image = nullptr; quint32 m_stride = 0; }; } #endif diff --git a/plugins/platforms/drm/drm_buffer_gbm.cpp b/plugins/platforms/drm/drm_buffer_gbm.cpp index 6959d1bd9..cbc0d3abd 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.cpp +++ b/plugins/platforms/drm/drm_buffer_gbm.cpp @@ -1,68 +1,68 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2017 Roman Gilg Copyright 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 "drm_backend.h" #include "drm_buffer_gbm.h" #include "gbm_surface.h" #include "logging.h" // system #include #include // drm #include +#include #include namespace KWin { // DrmSurfaceBuffer -DrmSurfaceBuffer::DrmSurfaceBuffer(DrmBackend *backend, const std::shared_ptr &surface) - : DrmBuffer(backend) +DrmSurfaceBuffer::DrmSurfaceBuffer(int fd, const std::shared_ptr &surface) + : DrmBuffer(fd) , m_surface(surface) { m_bo = m_surface->lockFrontBuffer(); if (!m_bo) { qCWarning(KWIN_DRM) << "Locking front buffer failed"; return; } m_size = QSize(gbm_bo_get_width(m_bo), gbm_bo_get_height(m_bo)); - if (drmModeAddFB(m_backend->fd(), m_size.width(), m_size.height(), 24, 32, gbm_bo_get_stride(m_bo), gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) { + if (drmModeAddFB(fd, m_size.width(), m_size.height(), 24, 32, gbm_bo_get_stride(m_bo), gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) { qCWarning(KWIN_DRM) << "drmModeAddFB failed"; } gbm_bo_set_user_data(m_bo, this, nullptr); } DrmSurfaceBuffer::~DrmSurfaceBuffer() { if (m_bufferId) { - drmModeRmFB(m_backend->fd(), m_bufferId); + drmModeRmFB(fd(), m_bufferId); } releaseGbm(); } void DrmSurfaceBuffer::releaseGbm() { m_surface->releaseBuffer(m_bo); m_bo = nullptr; } } diff --git a/plugins/platforms/drm/drm_buffer_gbm.h b/plugins/platforms/drm/drm_buffer_gbm.h index bdd1e94f9..b9b40e8e7 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.h +++ b/plugins/platforms/drm/drm_buffer_gbm.h @@ -1,68 +1,67 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2017 Roman Gilg Copyright 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_BUFFER_GBM_H #define KWIN_DRM_BUFFER_GBM_H #include "drm_buffer.h" #include struct gbm_bo; namespace KWin { -class DrmBackend; class GbmSurface; class DrmSurfaceBuffer : public DrmBuffer { public: - DrmSurfaceBuffer(DrmBackend *backend, const std::shared_ptr &surface); + DrmSurfaceBuffer(int fd, const std::shared_ptr &surface); ~DrmSurfaceBuffer(); bool needsModeChange(DrmBuffer *b) const override { if (DrmSurfaceBuffer *sb = dynamic_cast(b)) { return hasBo() != sb->hasBo(); } else { return true; } } bool hasBo() const { return m_bo != nullptr; } gbm_bo* getBo() const { return m_bo; } void releaseGbm() override; private: std::shared_ptr m_surface; gbm_bo *m_bo = nullptr; }; } #endif diff --git a/plugins/platforms/drm/drm_object.cpp b/plugins/platforms/drm/drm_object.cpp index 579b06b83..e67b581b5 100644 --- a/plugins/platforms/drm/drm_object.cpp +++ b/plugins/platforms/drm/drm_object.cpp @@ -1,147 +1,155 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg 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 "drm_object.h" -#include "drm_backend.h" #include "logging.h" namespace KWin { /* * Defintions for class DrmObject */ -DrmObject::DrmObject(uint32_t object_id, DrmBackend *backend) - : m_backend(backend) +DrmObject::DrmObject(uint32_t object_id, int fd) + : m_fd(fd) , m_id(object_id) { } DrmObject::~DrmObject() { foreach(Property* p, m_props) delete p; } +void DrmObject::setPropertyNames(QVector &&vector) +{ + m_propsNames = std::move(vector); + m_props.fill(nullptr, m_propsNames.size()); +} + void DrmObject::initProp(int n, drmModeObjectProperties *properties, QVector enumNames) { - m_props.resize(m_propsNames.size()); for (unsigned int i = 0; i < properties->count_props; ++i) { - drmModePropertyRes *prop = drmModeGetProperty(m_backend->fd(), properties->props[i]); + drmModePropertyRes *prop = drmModeGetProperty(fd(), properties->props[i]); if (!prop) { continue; } if (prop->name == m_propsNames[n]) { qCDebug(KWIN_DRM).nospace() << m_id << ": " << prop->name << "' (id " << prop->prop_id << "): " << properties->prop_values[i]; m_props[n] = new Property(prop, properties->prop_values[i], enumNames); } drmModeFreeProperty(prop); } } -bool DrmObject::atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value) +bool DrmObject::atomicAddProperty(drmModeAtomicReq *req, Property *property) { - if (drmModeAtomicAddProperty(req, m_id, m_props[prop]->propId(), value) <= 0) { - qCWarning(KWIN_DRM) << "Adding property" << m_propsNames[prop] << "to atomic commit failed for object" << this; + if (drmModeAtomicAddProperty(req, m_id, property->propId(), property->value()) <= 0) { + qCWarning(KWIN_DRM) << "Adding property" << property->name() << "to atomic commit failed for object" << this; return false; } return true; } bool DrmObject::atomicPopulate(drmModeAtomicReq *req) { bool ret = true; for (int i = 0; i < m_props.size(); i++) { - ret &= atomicAddProperty(req, i, m_props[i]->value()); + auto property = m_props.at(i); + if (!property) { + continue; + } + ret &= atomicAddProperty(req, property); } if (!ret) { qCWarning(KWIN_DRM) << "Failed to populate atomic plane" << m_id; return false; } return true; } /* * Defintions for struct Prop */ DrmObject::Property::Property(drmModePropertyRes *prop, uint64_t val, QVector enumNames) : m_propId(prop->prop_id) , m_propName(prop->name) , m_value(val) { if (!enumNames.isEmpty()) { qCDebug(KWIN_DRM) << m_propName << " has enums:" << enumNames; m_enumNames = enumNames; initEnumMap(prop); } } DrmObject::Property::~Property() = default; void DrmObject::Property::initEnumMap(drmModePropertyRes *prop) { - if (!(prop->flags & DRM_MODE_PROP_ENUM) || prop->count_enums < 1) { + if (!((prop->flags & DRM_MODE_PROP_ENUM) || (prop->flags & DRM_MODE_PROP_BITMASK)) || prop->count_enums < 1) { qCWarning(KWIN_DRM) << "Property '" << prop->name << "' ( id =" << m_propId << ") should be enum valued, but it is not."; return; } int nameCount = m_enumNames.size(); m_enumMap.resize(nameCount); qCDebug(KWIN_DRM).nospace() << "Test all " << prop->count_enums << " possible enums" <<":"; for (int i = 0; i < prop->count_enums; i++) { struct drm_mode_property_enum *en = &prop->enums[i]; int j = 0; while (QByteArray(en->name) != m_enumNames[j]) { j++; if (j == nameCount) { break; } } if (j == nameCount) { qCWarning(KWIN_DRM).nospace() << m_propName << " has unrecognized enum '" << en->name << "'"; } else { qCDebug(KWIN_DRM).nospace() << "Enum '" << en->name << "': runtime-value = " << en->value; m_enumMap[j] = en->value; } } if (KWIN_DRM().isDebugEnabled()) { for (int i = 0; i < m_enumMap.size(); i++) { if (m_value == m_enumMap[i]) { qCDebug(KWIN_DRM) << "=>" << m_propName << "with mapped enum value" << m_enumNames[i]; } } } } } diff --git a/plugins/platforms/drm/drm_object.h b/plugins/platforms/drm/drm_object.h index 6c46a6bcf..450dd491f 100644 --- a/plugins/platforms/drm/drm_object.h +++ b/plugins/platforms/drm/drm_object.h @@ -1,122 +1,136 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_OBJECT_H #define KWIN_DRM_OBJECT_H #include #include // drm #include namespace KWin { class DrmBackend; class DrmOutput; class DrmObject { public: // creates drm object by its id delivered by the kernel - DrmObject(uint32_t object_id, DrmBackend *backend); + DrmObject(uint32_t object_id, int fd); - virtual ~DrmObject() = 0; + virtual ~DrmObject(); virtual bool atomicInit() = 0; uint32_t id() const { return m_id; } DrmOutput *output() const { return m_output; } void setOutput(DrmOutput* output) { m_output = output; } - - uint32_t propId(int prop) { - return m_props[prop]->propId(); - } - uint64_t value(int prop) { - return m_props[prop]->value(); + + bool propHasEnum(int prop, uint64_t value) const { + auto property = m_props.at(prop); + return property ? property->hasEnum(value) : false; } void setValue(int prop, uint64_t new_value) { Q_ASSERT(prop < m_props.size()); - m_props[prop]->setValue(new_value); + auto property = m_props.at(prop); + if (property) { + property->setValue(new_value); + } + } + + int fd() const { + return m_fd; } virtual bool atomicPopulate(drmModeAtomicReq *req); protected: virtual bool initProps() = 0; // only derived classes know names and quantity of properties + void setPropertyNames(QVector &&vector); void initProp(int n, drmModeObjectProperties *properties, QVector enumNames = QVector(0)); - bool atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value); + class Property; + bool atomicAddProperty(drmModeAtomicReq *req, Property *property); - DrmBackend *m_backend; - const uint32_t m_id = 0; + int m_fd; + const uint32_t m_id; DrmOutput *m_output = nullptr; // for comparision with received name of DRM object - QVector m_propsNames; - class Property; QVector m_props; class Property { public: Property(drmModePropertyRes *prop, uint64_t val, QVector enumNames); virtual ~Property(); void initEnumMap(drmModePropertyRes *prop); uint64_t enumMap(int n) { return m_enumMap[n]; // TODO: test on index out of bounds? } + bool hasEnum(uint64_t value) const { + return m_enumMap.contains(value); + } - uint32_t propId() { + uint32_t propId() const { return m_propId; } - uint64_t value() { + uint64_t value() const { return m_value; } void setValue(uint64_t new_value) { m_value = new_value; } + const QByteArray &name() const { + return m_propName; + } private: uint32_t m_propId = 0; QByteArray m_propName; uint64_t m_value = 0; QVector m_enumMap; QVector m_enumNames; }; + +private: + QVector m_propsNames; }; } #endif diff --git a/plugins/platforms/drm/drm_object_connector.cpp b/plugins/platforms/drm/drm_object_connector.cpp index 444ce4eef..30b6e584a 100644 --- a/plugins/platforms/drm/drm_object_connector.cpp +++ b/plugins/platforms/drm/drm_object_connector.cpp @@ -1,81 +1,80 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg 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 "drm_object_connector.h" -#include "drm_backend.h" #include "drm_pointer.h" #include "logging.h" namespace KWin { -DrmConnector::DrmConnector(uint32_t connector_id, DrmBackend *backend) - : DrmObject(connector_id, backend) +DrmConnector::DrmConnector(uint32_t connector_id, int fd) + : DrmObject(connector_id, fd) { - ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> con(drmModeGetConnector(backend->fd(), connector_id)); + ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> con(drmModeGetConnector(fd, connector_id)); if (!con) { return; } for (int i = 0; i < con->count_encoders; ++i) { m_encoders << con->encoders[i]; } } DrmConnector::~DrmConnector() = default; bool DrmConnector::atomicInit() { qCDebug(KWIN_DRM) << "Creating connector" << m_id; if (!initProps()) { return false; } return true; } bool DrmConnector::initProps() { - m_propsNames = { + setPropertyNames( { QByteArrayLiteral("CRTC_ID"), - }; + }); - drmModeObjectProperties *properties = drmModeObjectGetProperties(m_backend->fd(), m_id, DRM_MODE_OBJECT_CONNECTOR); + drmModeObjectProperties *properties = drmModeObjectGetProperties(fd(), m_id, DRM_MODE_OBJECT_CONNECTOR); if (!properties) { qCWarning(KWIN_DRM) << "Failed to get properties for connector " << m_id ; return false; } int propCount = int(PropertyIndex::Count); for (int j = 0; j < propCount; ++j) { initProp(j, properties); } drmModeFreeObjectProperties(properties); return true; } bool DrmConnector::isConnected() { - ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> con(drmModeGetConnector(m_backend->fd(), m_id)); + ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> con(drmModeGetConnector(fd(), m_id)); if (!con) { return false; } return con->connection == DRM_MODE_CONNECTED; } } diff --git a/plugins/platforms/drm/drm_object_connector.h b/plugins/platforms/drm/drm_object_connector.h index 0f6fcdbd4..7cd240629 100644 --- a/plugins/platforms/drm/drm_object_connector.h +++ b/plugins/platforms/drm/drm_object_connector.h @@ -1,57 +1,57 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_OBJECT_CONNECTOR_H #define KWIN_DRM_OBJECT_CONNECTOR_H #include "drm_object.h" namespace KWin { class DrmConnector : public DrmObject { public: - DrmConnector(uint32_t connector_id, DrmBackend *backend); + DrmConnector(uint32_t connector_id, int fd); virtual ~DrmConnector(); bool atomicInit(); enum class PropertyIndex { CrtcId = 0, Count }; QVector encoders() { return m_encoders; } bool initProps(); bool isConnected(); private: QVector m_encoders; }; } #endif diff --git a/plugins/platforms/drm/drm_object_crtc.cpp b/plugins/platforms/drm/drm_object_crtc.cpp index 20b232ef8..3319e9150 100644 --- a/plugins/platforms/drm/drm_object_crtc.cpp +++ b/plugins/platforms/drm/drm_object_crtc.cpp @@ -1,106 +1,121 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg 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 "drm_object_crtc.h" #include "drm_backend.h" #include "drm_output.h" #include "drm_buffer.h" #include "logging.h" +#include namespace KWin { DrmCrtc::DrmCrtc(uint32_t crtc_id, DrmBackend *backend, int resIndex) - : DrmObject(crtc_id, backend), - m_resIndex(resIndex) + : DrmObject(crtc_id, backend->fd()), + m_resIndex(resIndex), + m_backend(backend) { + ScopedDrmPointer<_drmModeCrtc, &drmModeFreeCrtc> modeCrtc(drmModeGetCrtc(backend->fd(), crtc_id)); + if (modeCrtc) { + m_gammaRampSize = modeCrtc->gamma_size; + } } DrmCrtc::~DrmCrtc() { } bool DrmCrtc::atomicInit() { qCDebug(KWIN_DRM) << "Atomic init for CRTC:" << resIndex() << "id:" << m_id; if (!initProps()) { return false; } return true; } bool DrmCrtc::initProps() { - m_propsNames = { + setPropertyNames({ QByteArrayLiteral("MODE_ID"), QByteArrayLiteral("ACTIVE"), - }; + }); - drmModeObjectProperties *properties = drmModeObjectGetProperties(m_backend->fd(), m_id, DRM_MODE_OBJECT_CRTC); + drmModeObjectProperties *properties = drmModeObjectGetProperties(fd(), m_id, DRM_MODE_OBJECT_CRTC); if (!properties) { qCWarning(KWIN_DRM) << "Failed to get properties for crtc " << m_id ; return false; } int propCount = int(PropertyIndex::Count); for (int j = 0; j < propCount; ++j) { initProp(j, properties); } drmModeFreeObjectProperties(properties); return true; } void DrmCrtc::flipBuffer() { if (m_currentBuffer && m_backend->deleteBufferAfterPageFlip() && m_currentBuffer != m_nextBuffer) { delete m_currentBuffer; } m_currentBuffer = m_nextBuffer; m_nextBuffer = nullptr; delete m_blackBuffer; m_blackBuffer = nullptr; } bool DrmCrtc::blank() { + if (!m_output) { + return false; + } if (!m_blackBuffer) { DrmDumbBuffer *blackBuffer = m_backend->createBuffer(m_output->pixelSize()); if (!blackBuffer->map()) { delete blackBuffer; return false; } blackBuffer->image()->fill(Qt::black); m_blackBuffer = blackBuffer; } if (m_output->setModeLegacy(m_blackBuffer)) { if (m_currentBuffer && m_backend->deleteBufferAfterPageFlip()) { delete m_currentBuffer; delete m_nextBuffer; } m_currentBuffer = nullptr; m_nextBuffer = nullptr; return true; } return false; } +bool DrmCrtc::setGammaRamp(ColorCorrect::GammaRamp &gamma) { + bool isError = drmModeCrtcSetGamma(m_backend->fd(), m_id, gamma.size, + gamma.red, gamma.green, gamma.blue); + return !isError; +} + } diff --git a/plugins/platforms/drm/drm_object_crtc.h b/plugins/platforms/drm/drm_object_crtc.h index c5fc37d21..fce1c1366 100644 --- a/plugins/platforms/drm/drm_object_crtc.h +++ b/plugins/platforms/drm/drm_object_crtc.h @@ -1,77 +1,88 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_OBJECT_CRTC_H #define KWIN_DRM_OBJECT_CRTC_H #include "drm_object.h" namespace KWin { +namespace ColorCorrect { +struct GammaRamp; +} + class DrmBackend; class DrmBuffer; class DrmDumbBuffer; class DrmCrtc : public DrmObject { public: DrmCrtc(uint32_t crtc_id, DrmBackend *backend, int resIndex); virtual ~DrmCrtc(); bool atomicInit(); enum class PropertyIndex { ModeId = 0, Active, Count }; bool initProps(); int resIndex() const { return m_resIndex; } DrmBuffer *current() { return m_currentBuffer; } DrmBuffer *next() { return m_nextBuffer; } void setNext(DrmBuffer *buffer) { m_nextBuffer = buffer; } void flipBuffer(); bool blank(); + int getGammaRampSize() const { + return m_gammaRampSize; + } + bool setGammaRamp(ColorCorrect::GammaRamp &gamma); + private: int m_resIndex; + uint32_t m_gammaRampSize = 0; DrmBuffer *m_currentBuffer = nullptr; DrmBuffer *m_nextBuffer = nullptr; DrmDumbBuffer *m_blackBuffer = nullptr; + DrmBackend *m_backend; }; } #endif diff --git a/plugins/platforms/drm/drm_object_plane.cpp b/plugins/platforms/drm/drm_object_plane.cpp index 8c899a5b0..929d27d72 100644 --- a/plugins/platforms/drm/drm_object_plane.cpp +++ b/plugins/platforms/drm/drm_object_plane.cpp @@ -1,151 +1,200 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg 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 "drm_object_plane.h" -#include "drm_backend.h" #include "drm_buffer.h" #include "drm_pointer.h" #include "logging.h" namespace KWin { -DrmPlane::DrmPlane(uint32_t plane_id, DrmBackend *backend) - : DrmObject(plane_id, backend) +DrmPlane::DrmPlane(uint32_t plane_id, int fd) + : DrmObject(plane_id, fd) { } DrmPlane::~DrmPlane() { delete m_current; delete m_next; } bool DrmPlane::atomicInit() { qCDebug(KWIN_DRM) << "Atomic init for plane:" << m_id; - ScopedDrmPointer<_drmModePlane, &drmModeFreePlane> p(drmModeGetPlane(m_backend->fd(), m_id)); + ScopedDrmPointer<_drmModePlane, &drmModeFreePlane> p(drmModeGetPlane(fd(), m_id)); if (!p) { qCWarning(KWIN_DRM) << "Failed to get kernel plane" << m_id; return false; } m_possibleCrtcs = p->possible_crtcs; int count_formats = p->count_formats; m_formats.resize(count_formats); for (int i = 0; i < count_formats; i++) { m_formats[i] = p->formats[i]; } if (!initProps()) { return false; } return true; } bool DrmPlane::initProps() { - m_propsNames = { + setPropertyNames( { QByteArrayLiteral("type"), QByteArrayLiteral("SRC_X"), QByteArrayLiteral("SRC_Y"), QByteArrayLiteral("SRC_W"), QByteArrayLiteral("SRC_H"), QByteArrayLiteral("CRTC_X"), QByteArrayLiteral("CRTC_Y"), QByteArrayLiteral("CRTC_W"), QByteArrayLiteral("CRTC_H"), QByteArrayLiteral("FB_ID"), QByteArrayLiteral("CRTC_ID"), - }; + QByteArrayLiteral("rotation") + }); QVector typeNames = { QByteArrayLiteral("Primary"), QByteArrayLiteral("Cursor"), QByteArrayLiteral("Overlay"), }; - drmModeObjectProperties *properties = drmModeObjectGetProperties(m_backend->fd(), m_id, DRM_MODE_OBJECT_PLANE); + const QVector rotationNames{ + QByteArrayLiteral("rotate-0"), + QByteArrayLiteral("rotate-90"), + QByteArrayLiteral("rotate-180"), + QByteArrayLiteral("rotate-270"), + QByteArrayLiteral("reflect-x"), + QByteArrayLiteral("reflect-y") + }; + + drmModeObjectProperties *properties = drmModeObjectGetProperties(fd(), m_id, DRM_MODE_OBJECT_PLANE); if (!properties){ qCWarning(KWIN_DRM) << "Failed to get properties for plane " << m_id ; return false; } int propCount = int(PropertyIndex::Count); for (int j = 0; j < propCount; ++j) { if (j == int(PropertyIndex::Type)) { initProp(j, properties, typeNames); + } else if (j == int(PropertyIndex::Rotation)) { + initProp(j, properties, rotationNames); + m_supportedTransformations = Transformations(); + auto testTransform = [j, this] (uint64_t value, Transformation t) { + if (propHasEnum(j, value)) { + m_supportedTransformations |= t; + } + }; + testTransform(0, Transformation::Rotate0); + testTransform(1, Transformation::Rotate90); + testTransform(2, Transformation::Rotate180); + testTransform(3, Transformation::Rotate270); + testTransform(4, Transformation::ReflectX); + testTransform(5, Transformation::ReflectY); + qCDebug(KWIN_DRM) << "Supported Transformations: " << m_supportedTransformations << " on plane " << m_id; } else { initProp(j, properties); } } drmModeFreeObjectProperties(properties); return true; } DrmPlane::TypeIndex DrmPlane::type() { - uint64_t v = value(int(PropertyIndex::Type)); + auto property = m_props.at(int(PropertyIndex::Type)); + if (!property) { + return TypeIndex::Overlay; + } int typeCount = int(TypeIndex::Count); for (int i = 0; i < typeCount; i++) { - if (m_props[int(PropertyIndex::Type)]->enumMap(i) == v) { + if (property->enumMap(i) == property->value()) { return TypeIndex(i); } } return TypeIndex::Overlay; } -void DrmPlane::setNext(DrmBuffer *b){ - setValue(int(PropertyIndex::FbId), b ? b->bufferId() : 0); +void DrmPlane::setNext(DrmBuffer *b) +{ + if (auto property = m_props.at(int(PropertyIndex::FbId))) { + property->setValue(b ? b->bufferId() : 0); + } m_next = b; } +void DrmPlane::setTransformation(Transformations t) +{ + if (auto property = m_props.at(int(PropertyIndex::Rotation))) { + property->setValue(int(t)); + } +} + +DrmPlane::Transformations DrmPlane::transformation() +{ + if (auto property = m_props.at(int(PropertyIndex::Rotation))) { + return Transformations(int(property->value())); + } + return Transformations(Transformation::Rotate0); +} + bool DrmPlane::atomicPopulate(drmModeAtomicReq *req) { bool ret = true; for (int i = 1; i < m_props.size(); i++) { - ret &= atomicAddProperty(req, i, m_props[i]->value()); + auto property = m_props.at(i); + if (!property) { + continue; + } + ret &= atomicAddProperty(req, property); } if (!ret) { qCWarning(KWIN_DRM) << "Failed to populate atomic plane" << m_id; return false; } return true; } void DrmPlane::flipBuffer() { m_current = m_next; m_next = nullptr; } void DrmPlane::flipBufferWithDelete() { if (m_current != m_next) { delete m_current; } flipBuffer(); } } diff --git a/plugins/platforms/drm/drm_object_plane.h b/plugins/platforms/drm/drm_object_plane.h index 280546204..cd6f739c6 100644 --- a/plugins/platforms/drm/drm_object_plane.h +++ b/plugins/platforms/drm/drm_object_plane.h @@ -1,101 +1,122 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_OBJECT_PLANE_H #define KWIN_DRM_OBJECT_PLANE_H #include "drm_object.h" // drm #include namespace KWin { class DrmBuffer; class DrmPlane : public DrmObject { public: - DrmPlane(uint32_t plane_id, DrmBackend *backend); + DrmPlane(uint32_t plane_id, int fd); ~DrmPlane(); enum class PropertyIndex { Type = 0, SrcX, SrcY, SrcW, SrcH, CrtcX, CrtcY, CrtcW, CrtcH, FbId, CrtcId, + Rotation, Count }; enum class TypeIndex { Primary = 0, Cursor, Overlay, Count }; - + + enum class Transformation { + Rotate0 = 1 << 0, + Rotate90 = 1 << 1, + Rotate180 = 1 << 2, + Rotate270 = 1 << 3, + ReflectX = 1 << 4, + ReflectY = 1 << 5 + }; + Q_DECLARE_FLAGS(Transformations, Transformation); + bool atomicInit(); bool initProps(); TypeIndex type(); bool isCrtcSupported(int resIndex) const { return (m_possibleCrtcs & (1 << resIndex)); } QVector formats() const { return m_formats; } DrmBuffer *current() const { return m_current; } DrmBuffer *next() const { return m_next; } void setCurrent(DrmBuffer *b) { m_current = b; } void setNext(DrmBuffer *b); + void setTransformation(Transformations t); + Transformations transformation(); bool atomicPopulate(drmModeAtomicReq *req); void flipBuffer(); void flipBufferWithDelete(); + Transformations supportedTransformations() const { + return m_supportedTransformations; + } + private: DrmBuffer *m_current = nullptr; DrmBuffer *m_next = nullptr; // TODO: See weston drm_output_check_plane_format for future use of these member variables QVector m_formats; // Possible formats, which can be presented on this plane // TODO: when using overlay planes in the future: restrict possible screens / crtcs of planes uint32_t m_possibleCrtcs; + + Transformations m_supportedTransformations = Transformation::Rotate0; }; } +Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::DrmPlane::Transformations) + #endif diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp index cf6ef8a01..8a21bf56d 100644 --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -1,1000 +1,1298 @@ /******************************************************************** 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 "drm_output.h" #include "drm_backend.h" #include "drm_object_plane.h" #include "drm_object_crtc.h" #include "drm_object_connector.h" #include #include "composite.h" #include "logind.h" #include "logging.h" #include "main.h" +#include "orientation_sensor.h" #include "screens_drm.h" #include "wayland_server.h" // KWayland #include #include #include #include #include #include // KF5 #include #include #include // Qt +#include #include +#include // drm #include #include #include namespace KWin { DrmOutput::DrmOutput(DrmBackend *backend) : QObject() , m_backend(backend) { } DrmOutput::~DrmOutput() { hideCursor(); m_crtc->blank(); if (m_primaryPlane) { // TODO: when having multiple planes, also clean up these m_primaryPlane->setOutput(nullptr); if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); } m_primaryPlane->setCurrent(nullptr); } m_crtc->setOutput(nullptr); m_conn->setOutput(nullptr); delete m_waylandOutput.data(); delete m_waylandOutputDevice.data(); + delete m_cursor[0]; + delete m_cursor[1]; } void DrmOutput::releaseGbm() { if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } if (m_primaryPlane && m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } } void DrmOutput::hideCursor() { drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0); } void DrmOutput::showCursor(DrmDumbBuffer *c) { const QSize &s = c->size(); drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()); } +void DrmOutput::showCursor() +{ + showCursor(m_cursor[m_cursorIndex]); + if (m_hasNewCursor) { + m_cursorIndex = (m_cursorIndex + 1) % 2; + m_hasNewCursor = false; + } +} + +void DrmOutput::updateCursor() +{ + QImage cursorImage = m_backend->softwareCursor(); + if (cursorImage.isNull()) { + return; + } + m_hasNewCursor = true; + QImage *c = m_cursor[m_cursorIndex]->image(); + c->fill(Qt::transparent); + QPainter p; + p.begin(c); + if (m_orientation == Qt::InvertedLandscapeOrientation) { + QMatrix4x4 matrix; + matrix.translate(cursorImage.width() / 2.0, cursorImage.height() / 2.0); + matrix.rotate(180.0f, 0.0f, 0.0f, 1.0f); + matrix.translate(-cursorImage.width() / 2.0, -cursorImage.height() / 2.0); + p.setWorldTransform(matrix.toTransform()); + } + p.drawImage(QPoint(0, 0), cursorImage); + p.end(); +} + void DrmOutput::moveCursor(const QPoint &globalPos) { - const QPoint p = ((globalPos - m_globalPos) * m_scale) - m_backend->softwareCursorHotspot(); + QMatrix4x4 matrix; + QMatrix4x4 hotspotMatrix; + if (m_orientation == Qt::InvertedLandscapeOrientation) { + matrix.translate(pixelSize().width() /2.0, pixelSize().height() / 2.0); + matrix.rotate(180.0f, 0.0f, 0.0f, 1.0f); + matrix.translate(-pixelSize().width() /2.0, -pixelSize().height() / 2.0); + const auto cursorSize = m_backend->softwareCursor().size(); + hotspotMatrix.translate(cursorSize.width()/2.0, cursorSize.height()/2.0); + hotspotMatrix.rotate(180.0f, 0.0f, 0.0f, 1.0f); + hotspotMatrix.translate(-cursorSize.width()/2.0, -cursorSize.height()/2.0); + } + matrix.scale(m_scale); + matrix.translate(-m_globalPos.x(), -m_globalPos.y()); + const QPoint p = matrix.map(globalPos) - hotspotMatrix.map(m_backend->softwareCursorHotspot()); drmModeMoveCursor(m_backend->fd(), m_crtc->id(), p.x(), p.y()); } QSize DrmOutput::pixelSize() const { + if (m_orientation == Qt::PortraitOrientation || m_orientation == Qt::InvertedPortraitOrientation) { + return QSize(m_mode.vdisplay, m_mode.hdisplay); + } return QSize(m_mode.hdisplay, m_mode.vdisplay); } +QSize DrmOutput::physicalSize() const +{ + if (m_orientation == Qt::PortraitOrientation || m_orientation == Qt::InvertedPortraitOrientation) { + return QSize(m_physicalSize.height(), m_physicalSize.width()); + } + return m_physicalSize; +} + QRect DrmOutput::geometry() const { return QRect(m_globalPos, pixelSize() / scale()); } qreal DrmOutput::scale() const { return m_scale; } +void DrmOutput::setEnabled(bool enabled) +{ + if (enabled == isEnabled()) { + return; + } + if (enabled) { + setDpms(DpmsMode::On); + initOutput(); + } else { + setDpms(DpmsMode::Off); + delete m_waylandOutput.data(); + } + m_waylandOutputDevice->setEnabled(enabled ? + KWayland::Server::OutputDeviceInterface::Enablement::Enabled : KWayland::Server::OutputDeviceInterface::Enablement::Disabled); +} + +bool DrmOutput::isEnabled() const +{ + return !m_waylandOutput.isNull(); +} static KWayland::Server::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode) { using namespace KWayland::Server; switch (mode) { case DrmOutput::DpmsMode::On: return OutputInterface::DpmsMode::On; case DrmOutput::DpmsMode::Standby: return OutputInterface::DpmsMode::Standby; case DrmOutput::DpmsMode::Suspend: return OutputInterface::DpmsMode::Suspend; case DrmOutput::DpmsMode::Off: return OutputInterface::DpmsMode::Off; default: Q_UNREACHABLE(); } } static DrmOutput::DpmsMode fromWaylandDpmsMode(KWayland::Server::OutputInterface::DpmsMode wlMode) { using namespace KWayland::Server; switch (wlMode) { case OutputInterface::DpmsMode::On: return DrmOutput::DpmsMode::On; case OutputInterface::DpmsMode::Standby: return DrmOutput::DpmsMode::Standby; case OutputInterface::DpmsMode::Suspend: return DrmOutput::DpmsMode::Suspend; case OutputInterface::DpmsMode::Off: return DrmOutput::DpmsMode::Off; default: Q_UNREACHABLE(); } } static QHash s_connectorNames = { {DRM_MODE_CONNECTOR_Unknown, QByteArrayLiteral("Unknown")}, {DRM_MODE_CONNECTOR_VGA, QByteArrayLiteral("VGA")}, {DRM_MODE_CONNECTOR_DVII, QByteArrayLiteral("DVI-I")}, {DRM_MODE_CONNECTOR_DVID, QByteArrayLiteral("DVI-D")}, {DRM_MODE_CONNECTOR_DVIA, QByteArrayLiteral("DVI-A")}, {DRM_MODE_CONNECTOR_Composite, QByteArrayLiteral("Composite")}, {DRM_MODE_CONNECTOR_SVIDEO, QByteArrayLiteral("SVIDEO")}, {DRM_MODE_CONNECTOR_LVDS, QByteArrayLiteral("LVDS")}, {DRM_MODE_CONNECTOR_Component, QByteArrayLiteral("Component")}, {DRM_MODE_CONNECTOR_9PinDIN, QByteArrayLiteral("DIN")}, {DRM_MODE_CONNECTOR_DisplayPort, QByteArrayLiteral("DP")}, {DRM_MODE_CONNECTOR_HDMIA, QByteArrayLiteral("HDMI-A")}, {DRM_MODE_CONNECTOR_HDMIB, QByteArrayLiteral("HDMI-B")}, {DRM_MODE_CONNECTOR_TV, QByteArrayLiteral("TV")}, {DRM_MODE_CONNECTOR_eDP, QByteArrayLiteral("eDP")}, {DRM_MODE_CONNECTOR_VIRTUAL, QByteArrayLiteral("Virtual")}, {DRM_MODE_CONNECTOR_DSI, QByteArrayLiteral("DSI")} }; +namespace { +quint64 refreshRateForMode(_drmModeModeInfo *m) +{ + // Calculate higher precision (mHz) refresh rate + // logic based on Weston, see compositor-drm.c + quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; + if (m->flags & DRM_MODE_FLAG_INTERLACE) { + refreshRate *= 2; + } + if (m->flags & DRM_MODE_FLAG_DBLSCAN) { + refreshRate /= 2; + } + if (m->vscan > 1) { + refreshRate /= m->vscan; + } + return refreshRate; +} +} bool DrmOutput::init(drmModeConnector *connector) { initEdid(connector); initDpms(connector); initUuid(); if (m_backend->atomicModeSetting()) { if (!initPrimaryPlane()) { return false; } } else if (!m_crtc->blank()) { return false; } - setDpms(DpmsMode::On); + m_internal = connector->connector_type == DRM_MODE_CONNECTOR_LVDS || connector->connector_type == DRM_MODE_CONNECTOR_eDP; + + if (m_internal) { + connect(kwinApp(), &Application::screensCreated, this, + [this] { + connect(screens()->orientationSensor(), &OrientationSensor::orientationChanged, this, &DrmOutput::automaticRotation); + } + ); + } + + QSize physicalSize = !m_edid.physicalSize.isEmpty() ? m_edid.physicalSize : QSize(connector->mmWidth, connector->mmHeight); + // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 + // as this information is used to calculate DPI info, it's going to result in everything being huge + const QByteArray unknown = QByteArrayLiteral("unkown"); + KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId.isEmpty() ? unknown : m_edid.eisaId) + .group(m_edid.monitorName.isEmpty() ? unknown : m_edid.monitorName) + .group(m_edid.serialNumber.isEmpty() ? unknown : m_edid.serialNumber); + if (group.hasKey("PhysicalSize")) { + const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); + qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId << "/" << m_edid.monitorName << "/" << m_edid.serialNumber << " from " << physicalSize << "to " << overwriteSize; + physicalSize = overwriteSize; + } + m_physicalSize = physicalSize; + + initOutputDevice(connector); + + setEnabled(true); + return true; +} + +void DrmOutput::initUuid() +{ + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(QByteArray::number(m_conn->id())); + hash.addData(m_edid.eisaId); + hash.addData(m_edid.monitorName); + hash.addData(m_edid.serialNumber); + m_uuid = hash.result().toHex().left(10); +} + +void DrmOutput::initOutput() +{ + Q_ASSERT(m_waylandOutputDevice); if (!m_waylandOutput.isNull()) { delete m_waylandOutput.data(); m_waylandOutput.clear(); } m_waylandOutput = waylandServer()->display()->createOutput(); + connect(this, &DrmOutput::modeChanged, this, + [this] { + if (m_waylandOutput.isNull()) { + return; + } + m_waylandOutput->setCurrentMode(QSize(m_mode.hdisplay, m_mode.vdisplay), refreshRateForMode(&m_mode)); + } + ); + m_waylandOutput->setManufacturer(m_waylandOutputDevice->manufacturer()); + m_waylandOutput->setModel(m_waylandOutputDevice->model()); + m_waylandOutput->setPhysicalSize(m_physicalSize); + + // set dpms + if (!m_dpms.isNull()) { + m_waylandOutput->setDpmsSupported(true); + m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); + connect(m_waylandOutput.data(), &KWayland::Server::OutputInterface::dpmsModeRequested, this, + [this] (KWayland::Server::OutputInterface::DpmsMode mode) { + setDpms(fromWaylandDpmsMode(mode)); + }, Qt::QueuedConnection + ); + } + + for(const auto &mode: m_waylandOutputDevice->modes()) { + KWayland::Server::OutputInterface::ModeFlags flags; + if (mode.flags & KWayland::Server::OutputDeviceInterface::ModeFlag::Current) { + flags |= KWayland::Server::OutputInterface::ModeFlag::Current; + } + if (mode.flags & KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred) { + flags |= KWayland::Server::OutputInterface::ModeFlag::Preferred; + } + m_waylandOutput->addMode(mode.size, flags, mode.refreshRate); + } + + m_waylandOutput->create(); +} + +void DrmOutput::initOutputDevice(drmModeConnector *connector) +{ if (!m_waylandOutputDevice.isNull()) { delete m_waylandOutputDevice.data(); m_waylandOutputDevice.clear(); } m_waylandOutputDevice = waylandServer()->display()->createOutputDevice(); m_waylandOutputDevice->setUuid(m_uuid); if (!m_edid.eisaId.isEmpty()) { - m_waylandOutput->setManufacturer(QString::fromLatin1(m_edid.eisaId)); + m_waylandOutputDevice->setManufacturer(QString::fromLatin1(m_edid.eisaId)); } else { - m_waylandOutput->setManufacturer(i18n("unknown")); + m_waylandOutputDevice->setManufacturer(i18n("unknown")); } - m_waylandOutputDevice->setManufacturer(m_waylandOutput->manufacturer()); QString connectorName = s_connectorNames.value(connector->connector_type, QByteArrayLiteral("Unknown")); QString modelName; if (!m_edid.monitorName.isEmpty()) { QString model = QString::fromLatin1(m_edid.monitorName); if (!m_edid.serialNumber.isEmpty()) { model.append('/'); model.append(QString::fromLatin1(m_edid.serialNumber)); } modelName = model; } else if (!m_edid.serialNumber.isEmpty()) { modelName = QString::fromLatin1(m_edid.serialNumber); } else { modelName = i18n("unknown"); } - m_waylandOutput->setModel(connectorName + QStringLiteral("-") + QString::number(connector->connector_type_id) + QStringLiteral("-") + modelName); - m_waylandOutputDevice->setModel(m_waylandOutput->model()); - - QSize physicalSize = !m_edid.physicalSize.isEmpty() ? m_edid.physicalSize : QSize(connector->mmWidth, connector->mmHeight); - // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 - // as this information is used to calculate DPI info, it's going to result in everything being huge - const QByteArray unknown = QByteArrayLiteral("unkown"); - KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId.isEmpty() ? unknown : m_edid.eisaId) - .group(m_edid.monitorName.isEmpty() ? unknown : m_edid.monitorName) - .group(m_edid.serialNumber.isEmpty() ? unknown : m_edid.serialNumber); - if (group.hasKey("PhysicalSize")) { - const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); - qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId << "/" << m_edid.monitorName << "/" << m_edid.serialNumber << " from " << physicalSize << "to " << overwriteSize; - physicalSize = overwriteSize; - } - m_physicalSize = physicalSize; - m_waylandOutput->setPhysicalSize(physicalSize); - m_waylandOutputDevice->setPhysicalSize(physicalSize); + m_waylandOutputDevice->setModel(connectorName + QStringLiteral("-") + QString::number(connector->connector_type_id) + QStringLiteral("-") + modelName); + + m_waylandOutputDevice->setPhysicalSize(m_physicalSize); // read in mode information for (int i = 0; i < connector->count_modes; ++i) { - // TODO: in AMS here we could read and store for later every mode's blob_id // would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set - auto *m = &connector->modes[i]; - KWayland::Server::OutputInterface::ModeFlags flags; KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; if (isCurrentMode(m)) { - flags |= KWayland::Server::OutputInterface::ModeFlag::Current; deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; } if (m->type & DRM_MODE_TYPE_PREFERRED) { - flags |= KWayland::Server::OutputInterface::ModeFlag::Preferred; deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; } - // Calculate higher precision (mHz) refresh rate - // logic based on Weston, see compositor-drm.c - quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; - if (m->flags & DRM_MODE_FLAG_INTERLACE) { - refreshRate *= 2; - } - if (m->flags & DRM_MODE_FLAG_DBLSCAN) { - refreshRate /= 2; - } - if (m->vscan > 1) { - refreshRate /= m->vscan; - } - m_waylandOutput->addMode(QSize(m->hdisplay, m->vdisplay), flags, refreshRate); + const auto refreshRate = refreshRateForMode(m); KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = i; mode.size = QSize(m->hdisplay, m->vdisplay); mode.flags = deviceflags; mode.refreshRate = refreshRate; qCDebug(KWIN_DRM) << "Adding mode: " << i << mode.size; m_waylandOutputDevice->addMode(mode); } - - // set dpms - if (!m_dpms.isNull()) { - m_waylandOutput->setDpmsSupported(true); - m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); - connect(m_waylandOutput.data(), &KWayland::Server::OutputInterface::dpmsModeRequested, this, - [this] (KWayland::Server::OutputInterface::DpmsMode mode) { - setDpms(fromWaylandDpmsMode(mode)); - }, Qt::QueuedConnection - ); - } - - m_waylandOutput->create(); - qCDebug(KWIN_DRM) << "Created OutputDevice"; m_waylandOutputDevice->create(); - return true; -} - -void DrmOutput::initUuid() -{ - QCryptographicHash hash(QCryptographicHash::Md5); - hash.addData(QByteArray::number(m_conn->id())); - hash.addData(m_edid.eisaId); - hash.addData(m_edid.monitorName); - hash.addData(m_edid.serialNumber); - m_uuid = hash.result().toHex().left(10); } bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const { return mode->clock == m_mode.clock && mode->hdisplay == m_mode.hdisplay && mode->hsync_start == m_mode.hsync_start && mode->hsync_end == m_mode.hsync_end && mode->htotal == m_mode.htotal && mode->hskew == m_mode.hskew && mode->vdisplay == m_mode.vdisplay && mode->vsync_start == m_mode.vsync_start && mode->vsync_end == m_mode.vsync_end && mode->vtotal == m_mode.vtotal && mode->vscan == m_mode.vscan && mode->vrefresh == m_mode.vrefresh && mode->flags == m_mode.flags && mode->type == m_mode.type && qstrcmp(mode->name, m_mode.name) == 0; } static bool verifyEdidHeader(drmModePropertyBlobPtr edid) { const uint8_t *data = reinterpret_cast(edid->data); if (data[0] != 0x00) { return false; } for (int i = 1; i < 7; ++i) { if (data[i] != 0xFF) { return false; } } if (data[7] != 0x00) { return false; } return true; } static QByteArray extractEisaId(drmModePropertyBlobPtr edid) { /* * From EDID standard section 3.4: * The ID Manufacturer Name field, shown in Table 3.5, contains a 2-byte representation of the monitor's * manufacturer. This is the same as the EISA ID. It is based on compressed ASCII, “0001=A” ... “11010=Z”. * * The table: * | Byte | Bit | * | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * ---------------------------------------- * | 1 | 0)| (4| 3 | 2 | 1 | 0)| (4| 3 | * | | * | Character 1 | Char 2| * ---------------------------------------- * | 2 | 2 | 1 | 0)| (4| 3 | 2 | 1 | 0)| * | | Character2| Character 3 | * ---------------------------------------- **/ const uint8_t *data = reinterpret_cast(edid->data); static const uint offset = 0x8; char id[4]; if (data[offset] >> 7) { // bit at position 7 is not a 0 return QByteArray(); } // shift two bits to right, and with 7 right most bits id[0] = 'A' + ((data[offset] >> 2) & 0x1f) -1; // for first byte: take last two bits and shift them 3 to left (000xx000) // for second byte: shift 5 bits to right and take 3 right most bits (00000xxx) // or both together id[1] = 'A' + (((data[offset] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1; // take five right most bits id[2] = 'A' + (data[offset + 1] & 0x1f) - 1; id[3] = '\0'; return QByteArray(id); } static void extractMonitorDescriptorDescription(drmModePropertyBlobPtr blob, DrmOutput::Edid &edid) { // see section 3.10.3 const uint8_t *data = reinterpret_cast(blob->data); static const uint offset = 0x36; static const uint blockLength = 18; for (int i = 0; i < 5; ++i) { const uint co = offset + i * blockLength; // Flag = 0000h when block used as descriptor if (data[co] != 0) { continue; } if (data[co + 1] != 0) { continue; } // Reserved = 00h when block used as descriptor if (data[co + 2] != 0) { continue; } /* * FFh: Monitor Serial Number - Stored as ASCII, code page # 437, ≤ 13 bytes. * FEh: ASCII String - Stored as ASCII, code page # 437, ≤ 13 bytes. * FDh: Monitor range limits, binary coded * FCh: Monitor name, stored as ASCII, code page # 437 * FBh: Descriptor contains additional color point data * FAh: Descriptor contains additional Standard Timing Identifications * F9h - 11h: Currently undefined * 10h: Dummy descriptor, used to indicate that the descriptor space is unused * 0Fh - 00h: Descriptor defined by manufacturer. */ if (data[co + 3] == 0xfc && edid.monitorName.isEmpty()) { edid.monitorName = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); } if (data[co + 3] == 0xfe) { const QByteArray id = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); if (!id.isEmpty()) { edid.eisaId = id; } } if (data[co + 3] == 0xff) { edid.serialNumber = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); } } } static QByteArray extractSerialNumber(drmModePropertyBlobPtr edid) { // see section 3.4 const uint8_t *data = reinterpret_cast(edid->data); static const uint offset = 0x0C; /* * The ID serial number is a 32-bit serial number used to differentiate between individual instances of the same model * of monitor. Its use is optional. When used, the bit order for this field follows that shown in Table 3.6. The EDID * structure Version 1 Revision 1 and later offer a way to represent the serial number of the monitor as an ASCII string * in a separate descriptor block. */ uint32_t serialNumber = 0; serialNumber = (uint32_t) data[offset + 0]; serialNumber |= (uint32_t) data[offset + 1] << 8; serialNumber |= (uint32_t) data[offset + 2] << 16; serialNumber |= (uint32_t) data[offset + 3] << 24; if (serialNumber == 0) { return QByteArray(); } return QByteArray::number(serialNumber); } static QSize extractPhysicalSize(drmModePropertyBlobPtr edid) { const uint8_t *data = reinterpret_cast(edid->data); return QSize(data[0x15], data[0x16]) * 10; } void DrmOutput::initEdid(drmModeConnector *connector) { ScopedDrmPointer<_drmModePropertyBlob, &drmModeFreePropertyBlob> edid; for (int i = 0; i < connector->count_props; ++i) { ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if ((property->flags & DRM_MODE_PROP_BLOB) && qstrcmp(property->name, "EDID") == 0) { edid.reset(drmModeGetPropertyBlob(m_backend->fd(), connector->prop_values[i])); } } if (!edid) { return; } // for documentation see: http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf if (edid->length < 128) { return; } if (!verifyEdidHeader(edid.data())) { return; } m_edid.eisaId = extractEisaId(edid.data()); m_edid.serialNumber = extractSerialNumber(edid.data()); // parse monitor descriptor description extractMonitorDescriptorDescription(edid.data(), m_edid); m_edid.physicalSize = extractPhysicalSize(edid.data()); } bool DrmOutput::initPrimaryPlane() { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Primary) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_primaryPlane) { // Output already has a primary plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_primaryPlane = p; qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; return false; } bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Cursor) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_cursorPlane) { // Output already has a cursor plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_cursorPlane = p; qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } return false; } void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if (qstrcmp(property->name, "DPMS") == 0) { m_dpms.swap(property); break; } } } void DrmOutput::setDpms(DrmOutput::DpmsMode mode) { if (m_dpms.isNull()) { return; } if (mode == m_dpmsModePending) { qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; return; } m_dpmsModePending = mode; if (m_backend->atomicModeSetting()) { m_modesetRequested = true; if (mode == DpmsMode::On) { if (m_pageFlipPending) { m_pageFlipPending = false; Compositor::self()->bufferSwapComplete(); } dpmsOnHandler(); } else { m_dpmsAtomicOffPending = true; if (!m_pageFlipPending) { dpmsAtomicOff(); } } } else { if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(mode)) < 0) { m_dpmsModePending = m_dpmsMode; qCWarning(KWIN_DRM) << "Setting DPMS failed"; return; } if (mode == DpmsMode::On) { dpmsOnHandler(); } else { dpmsOffHandler(); } m_dpmsMode = m_dpmsModePending; } } void DrmOutput::dpmsOnHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On."; if (m_waylandOutput) { m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->checkOutputsAreOn(); if (!m_backend->atomicModeSetting()) { m_crtc->blank(); } if (Compositor *compositor = Compositor::self()) { compositor->addRepaintFull(); } } void DrmOutput::dpmsOffHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off."; if (m_waylandOutput) { m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->outputWentOff(); } QString DrmOutput::name() const { if (!m_waylandOutput) { return i18n("unknown"); } return QStringLiteral("%1 %2").arg(m_waylandOutput->manufacturer()).arg(m_waylandOutput->model()); } int DrmOutput::currentRefreshRate() const { if (!m_waylandOutput) { return 60000; } return m_waylandOutput->refreshRate(); } void DrmOutput::setGlobalPos(const QPoint &pos) { m_globalPos = pos; if (m_waylandOutput) { m_waylandOutput->setGlobalPosition(pos); } if (m_waylandOutputDevice) { m_waylandOutputDevice->setGlobalPosition(pos); } } void DrmOutput::setScale(qreal scale) { m_scale = scale; if (m_waylandOutput) { m_waylandOutput->setScale(scale); } if (m_waylandOutputDevice) { m_waylandOutputDevice->setScale(scale); } } void DrmOutput::setChanges(KWayland::Server::OutputChangeSet *changes) { m_changeset = changes; qCDebug(KWIN_DRM) << "set changes in DrmOutput"; commitChanges(); } bool DrmOutput::commitChanges() { Q_ASSERT(!m_waylandOutputDevice.isNull()); - Q_ASSERT(!m_waylandOutput.isNull()); if (m_changeset.isNull()) { qCDebug(KWIN_DRM) << "no changes"; // No changes to an output is an entirely valid thing return true; } - - if (m_changeset->enabledChanged()) { - qCDebug(KWIN_DRM) << "Setting enabled:"; - m_waylandOutputDevice->setEnabled(m_changeset->enabled()); - } + //enabledChanged is handled by drmbackend if (m_changeset->modeChanged()) { qCDebug(KWIN_DRM) << "Setting new mode:" << m_changeset->mode(); m_waylandOutputDevice->setCurrentMode(m_changeset->mode()); - // FIXME: implement for wl_output + updateMode(m_changeset->mode()); } if (m_changeset->transformChanged()) { qCDebug(KWIN_DRM) << "Server setting transform: " << (int)(m_changeset->transform()); - m_waylandOutputDevice->setTransform(m_changeset->transform()); - // FIXME: implement for wl_output + transform(m_changeset->transform()); } if (m_changeset->positionChanged()) { qCDebug(KWIN_DRM) << "Server setting position: " << m_changeset->position(); - m_waylandOutput->setGlobalPosition(m_changeset->position()); - m_waylandOutputDevice->setGlobalPosition(m_changeset->position()); setGlobalPos(m_changeset->position()); // may just work already! } if (m_changeset->scaleChanged()) { qCDebug(KWIN_DRM) << "Setting scale:" << m_changeset->scale(); setScale(m_changeset->scale()); } return true; } +void DrmOutput::transform(KWayland::Server::OutputDeviceInterface::Transform transform) +{ + m_waylandOutputDevice->setTransform(transform); + using KWayland::Server::OutputDeviceInterface; + using KWayland::Server::OutputInterface; + switch (transform) { + case OutputDeviceInterface::Transform::Normal: + if (m_primaryPlane) { + m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0); + } + if (m_waylandOutput) { + m_waylandOutput->setTransform(OutputInterface::Transform::Normal); + } + m_orientation = Qt::PrimaryOrientation; + break; + case OutputDeviceInterface::Transform::Rotated90: + if (m_primaryPlane) { + m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate90); + } + if (m_waylandOutput) { + m_waylandOutput->setTransform(OutputInterface::Transform::Rotated90); + } + m_orientation = Qt::PortraitOrientation; + break; + case OutputDeviceInterface::Transform::Rotated180: + if (m_primaryPlane) { + m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate180); + } + if (m_waylandOutput) { + m_waylandOutput->setTransform(OutputInterface::Transform::Rotated180); + } + m_orientation = Qt::InvertedLandscapeOrientation; + break; + case OutputDeviceInterface::Transform::Rotated270: + if (m_primaryPlane) { + m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate270); + } + if (m_waylandOutput) { + m_waylandOutput->setTransform(OutputInterface::Transform::Rotated270); + } + m_orientation = Qt::InvertedPortraitOrientation; + break; + case OutputDeviceInterface::Transform::Flipped: + // TODO: what is this exactly? + if (m_waylandOutput) { + m_waylandOutput->setTransform(OutputInterface::Transform::Flipped); + } + break; + case OutputDeviceInterface::Transform::Flipped90: + // TODO: what is this exactly? + if (m_waylandOutput) { + m_waylandOutput->setTransform(OutputInterface::Transform::Flipped90); + } + break; + case OutputDeviceInterface::Transform::Flipped180: + // TODO: what is this exactly? + if (m_waylandOutput) { + m_waylandOutput->setTransform(OutputInterface::Transform::Flipped180); + } + break; + case OutputDeviceInterface::Transform::Flipped270: + // TODO: what is this exactly? + if (m_waylandOutput) { + m_waylandOutput->setTransform(OutputInterface::Transform::Flipped270); + } + break; + } + m_modesetRequested = true; + // the cursor might need to get rotated + updateCursor(); + showCursor(); + emit modeChanged(); +} + +void DrmOutput::updateMode(int modeIndex) +{ + // get all modes on the connector + ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> connector(drmModeGetConnector(m_backend->fd(), m_conn->id())); + if (connector->count_modes <= modeIndex) { + // TODO: error? + return; + } + if (isCurrentMode(&connector->modes[modeIndex])) { + // nothing to do + return; + } + m_mode = connector->modes[modeIndex]; + m_modesetRequested = true; + emit modeChanged(); +} + void DrmOutput::pageFlipped() { m_pageFlipPending = false; if (!m_crtc) { return; } // Egl based surface buffers get destroyed, QPainter based dumb buffers not // TODO: split up DrmOutput in two for dumb and egl/gbm surface buffer compatible subclasses completely? if (m_backend->deleteBufferAfterPageFlip()) { if (m_backend->atomicModeSetting()) { if (!m_primaryPlane->next()) { // on manual vt switch // TODO: when we later use overlay planes it might happen, that we have a page flip with only // damage on one of these, and therefore the primary plane has no next buffer // -> Then we don't want to return here! if (m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } return; } for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBufferWithDelete(); } m_nextPlanesFlipList.clear(); } else { if (!m_crtc->next()) { // on manual vt switch if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } } m_crtc->flipBuffer(); } } else { if (m_backend->atomicModeSetting()){ for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBuffer(); } m_nextPlanesFlipList.clear(); } else { m_crtc->flipBuffer(); } m_crtc->flipBuffer(); } } bool DrmOutput::present(DrmBuffer *buffer) { if (m_backend->atomicModeSetting()) { return presentAtomically(buffer); } else { return presentLegacy(buffer); } } bool DrmOutput::dpmsAtomicOff() { m_dpmsAtomicOffPending = false; // TODO: With multiple planes: deactivate all of them here delete m_primaryPlane->next(); m_primaryPlane->setNext(nullptr); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting."; return false; } if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting."; return false; } m_nextPlanesFlipList.clear(); dpmsOffHandler(); return true; } bool DrmOutput::presentAtomically(DrmBuffer *buffer) { if (!LogindIntegration::self()->isActiveSession()) { qCWarning(KWIN_DRM) << "Logind session not active."; return false; } if (m_pageFlipPending) { qCWarning(KWIN_DRM) << "Page not yet flipped."; return false; } m_primaryPlane->setNext(buffer); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { //TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout? //TODO: Probably should undo setNext and reset the flip list qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present."; + // go back to previous state + if (m_lastWorkingState.valid) { + m_mode = m_lastWorkingState.mode; + m_orientation = m_lastWorkingState.orientation; + setGlobalPos(m_lastWorkingState.globalPos); + if (m_primaryPlane) { + m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations); + } + m_modesetRequested = true; + // the cursor might need to get rotated + updateCursor(); + showCursor(); + // TODO: forward to OutputInterface and OutputDeviceInterface + emit modeChanged(); + emit screens()->changed(); + } return false; } + const bool wasModeset = m_modesetRequested; if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present."; //TODO: Probably should undo setNext and reset the flip list return false; } + if (wasModeset) { + // store current mode set as new good state + m_lastWorkingState.mode = m_mode; + m_lastWorkingState.orientation = m_orientation; + m_lastWorkingState.globalPos = m_globalPos; + if (m_primaryPlane) { + m_lastWorkingState.planeTransformations = m_primaryPlane->transformation(); + } + m_lastWorkingState.valid = true; + } m_pageFlipPending = true; return true; } bool DrmOutput::presentLegacy(DrmBuffer *buffer) { if (m_crtc->next()) { return false; } if (!LogindIntegration::self()->isActiveSession()) { m_crtc->setNext(buffer); return false; } if (m_dpmsMode != DpmsMode::On) { return false; } // Do we need to set a new mode first? if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer)) { if (!setModeLegacy(buffer)) { return false; } } const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; if (ok) { m_crtc->setNext(buffer); } else { qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); } return ok; } bool DrmOutput::setModeLegacy(DrmBuffer *buffer) { uint32_t connId = m_conn->id(); if (drmModeSetCrtc(m_backend->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { return true; } else { qCWarning(KWIN_DRM) << "Mode setting failed"; return false; } } bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) { drmModeAtomicReq *req = drmModeAtomicAlloc(); auto errorHandler = [this, mode, req] () { if (mode == AtomicCommitMode::Test) { // TODO: when we later test overlay planes, make sure we change only the right stuff back } if (req) { drmModeAtomicFree(req); } if (m_dpmsMode != m_dpmsModePending) { qCWarning(KWIN_DRM) << "Setting DPMS failed"; m_dpmsModePending = m_dpmsMode; if (m_dpmsMode != DpmsMode::On) { dpmsOffHandler(); } } // TODO: see above, rework later for overlay planes! for (DrmPlane *p : m_nextPlanesFlipList) { p->setNext(nullptr); } m_nextPlanesFlipList.clear(); }; if (!req) { qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; errorHandler(); return false; } uint32_t flags = 0; // Do we need to set a new mode? if (m_modesetRequested) { if (m_dpmsModePending == DpmsMode::On) { if (drmModeCreatePropertyBlob(m_backend->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) { qCWarning(KWIN_DRM) << "Failed to create property blob"; errorHandler(); return false; } } if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){ qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset"; errorHandler(); return false; } flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } if (mode == AtomicCommitMode::Real) { if (m_dpmsModePending == DpmsMode::On) { if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { // TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10. flags |= DRM_MODE_ATOMIC_NONBLOCK; } flags |= DRM_MODE_PAGE_FLIP_EVENT; } } else { flags |= DRM_MODE_ATOMIC_TEST_ONLY; } bool ret = true; // TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order. for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) { DrmPlane *p = m_nextPlanesFlipList[i]; ret &= p->atomicPopulate(req); } if (!ret) { qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!"; errorHandler(); return false; } if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); errorHandler(); return false; } if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { qCDebug(KWIN_DRM) << "Atomic Modeset successful."; m_modesetRequested = false; m_dpmsMode = m_dpmsModePending; } drmModeAtomicFree(req); return true; } bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) { if (enable) { m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), m_mode.hdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), m_mode.vdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), m_mode.hdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), m_mode.vdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id()); } else { if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); delete m_primaryPlane->next(); } m_primaryPlane->setCurrent(nullptr); m_primaryPlane->setNext(nullptr); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), 0); } m_conn->setValue(int(DrmConnector::PropertyIndex::CrtcId), enable ? m_crtc->id() : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::ModeId), enable ? m_blobId : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::Active), enable); bool ret = true; ret &= m_conn->atomicPopulate(req); ret &= m_crtc->atomicPopulate(req); return ret; } +bool DrmOutput::initCursor(const QSize &cursorSize) +{ + auto createCursor = [this, cursorSize] (int index) { + m_cursor[index] = m_backend->createBuffer(cursorSize); + if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { + return false; + } + return true; + }; + if (!createCursor(0) || !createCursor(1)) { + return false; + } + return true; +} + +bool DrmOutput::supportsTransformations() const +{ + if (!m_primaryPlane) { + return false; + } + const auto transformations = m_primaryPlane->supportedTransformations(); + return transformations.testFlag(DrmPlane::Transformation::Rotate90) + || transformations.testFlag(DrmPlane::Transformation::Rotate180) + || transformations.testFlag(DrmPlane::Transformation::Rotate270); +} + +void DrmOutput::automaticRotation() +{ + if (!m_primaryPlane) { + return; + } + const auto supportedTransformations = m_primaryPlane->supportedTransformations(); + const auto requestedTransformation = screens()->orientationSensor()->orientation(); + using KWayland::Server::OutputDeviceInterface; + OutputDeviceInterface::Transform newTransformation = OutputDeviceInterface::Transform::Normal; + switch (requestedTransformation) { + case OrientationSensor::Orientation::TopUp: + newTransformation = OutputDeviceInterface::Transform::Normal; + break; + case OrientationSensor::Orientation::TopDown: + if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate180)) { + return; + } + newTransformation = OutputDeviceInterface::Transform::Rotated180; + break; + case OrientationSensor::Orientation::LeftUp: + if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate90)) { + return; + } + newTransformation = OutputDeviceInterface::Transform::Rotated90; + break; + case OrientationSensor::Orientation::RightUp: + if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate270)) { + return; + } + newTransformation = OutputDeviceInterface::Transform::Rotated270; + break; + case OrientationSensor::Orientation::FaceUp: + case OrientationSensor::Orientation::FaceDown: + case OrientationSensor::Orientation::Undefined: + // unsupported + return; + } + transform(newTransformation); + emit screens()->changed(); +} + } diff --git a/plugins/platforms/drm/drm_output.h b/plugins/platforms/drm/drm_output.h index 5925042ca..2f59678d4 100644 --- a/plugins/platforms/drm/drm_output.h +++ b/plugins/platforms/drm/drm_output.h @@ -1,175 +1,219 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_OUTPUT_H #define KWIN_DRM_OUTPUT_H #include "drm_pointer.h" #include "drm_object.h" +#include "drm_object_plane.h" #include #include #include #include #include #include +#include + namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; } } namespace KWin { class DrmBackend; class DrmBuffer; class DrmDumbBuffer; class DrmPlane; class DrmConnector; class DrmCrtc; class DrmOutput : public QObject { Q_OBJECT public: struct Edid { QByteArray eisaId; QByteArray monitorName; QByteArray serialNumber; QSize physicalSize; }; virtual ~DrmOutput(); void releaseGbm(); void showCursor(DrmDumbBuffer *buffer); + void showCursor(); void hideCursor(); + void updateCursor(); void moveCursor(const QPoint &globalPos); bool init(drmModeConnector *connector); bool present(DrmBuffer *buffer); void pageFlipped(); + /** + * Enable or disable the output. + * This differs from setDpms as it also + * removes the wl_output + * The default is on + */ + void setEnabled(bool enabled); + bool isEnabled() const; + /** * This sets the changes and tests them against the DRM output */ void setChanges(KWayland::Server::OutputChangeSet *changeset); bool commitChanges(); QSize pixelSize() const; qreal scale() const; /* * The geometry of this output in global compositor co-ordinates (i.e scaled) */ QRect geometry() const; QString name() const; int currentRefreshRate() const; // These values are defined by the kernel enum class DpmsMode { On = DRM_MODE_DPMS_ON, Standby = DRM_MODE_DPMS_STANDBY, Suspend = DRM_MODE_DPMS_SUSPEND, Off = DRM_MODE_DPMS_OFF }; void setDpms(DpmsMode mode); bool isDpmsEnabled() const { // We care for current as well as pending mode in order to allow first present in AMS. return m_dpmsModePending == DpmsMode::On; } QByteArray uuid() const { return m_uuid; } - QSize physicalSize() const { - return m_physicalSize; + QSize physicalSize() const; + + bool initCursor(const QSize &cursorSize); + + bool supportsTransformations() const; + + bool isInternal() const { + return m_internal; + } + + Qt::ScreenOrientation orientation() const { + return m_orientation; } Q_SIGNALS: void dpmsChanged(); + void modeChanged(); private: friend class RemoteAccessManager; friend class DrmBackend; friend class DrmCrtc; // TODO: For use of setModeLegacy. Remove later when we allow multiple connectors per crtc // and save the connector ids in the DrmCrtc instance. DrmOutput(DrmBackend *backend); bool presentAtomically(DrmBuffer *buffer); enum class AtomicCommitMode { Test, Real }; bool doAtomicCommit(AtomicCommitMode mode); bool presentLegacy(DrmBuffer *buffer); bool setModeLegacy(DrmBuffer *buffer); void initEdid(drmModeConnector *connector); void initDpms(drmModeConnector *connector); + void initOutputDevice(drmModeConnector *connector); + bool isCurrentMode(const drmModeModeInfo *mode) const; void initUuid(); void setGlobalPos(const QPoint &pos); void setScale(qreal scale); - + void initOutput(); bool initPrimaryPlane(); bool initCursorPlane(); void dpmsOnHandler(); void dpmsOffHandler(); bool dpmsAtomicOff(); bool atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable); + void updateMode(int modeIndex); + + void transform(KWayland::Server::OutputDeviceInterface::Transform transform); + void automaticRotation(); DrmBackend *m_backend; DrmConnector *m_conn = nullptr; DrmCrtc *m_crtc = nullptr; QPoint m_globalPos; qreal m_scale = 1; bool m_lastGbm = false; drmModeModeInfo m_mode; Edid m_edid; QPointer m_waylandOutput; QPointer m_waylandOutputDevice; QPointer m_changeset; KWin::ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> m_dpms; DpmsMode m_dpmsMode = DpmsMode::On; DpmsMode m_dpmsModePending = DpmsMode::On; QByteArray m_uuid; uint32_t m_blobId = 0; DrmPlane* m_primaryPlane = nullptr; DrmPlane* m_cursorPlane = nullptr; QVector m_nextPlanesFlipList; bool m_pageFlipPending = false; bool m_dpmsAtomicOffPending = false; bool m_modesetRequested = true; QSize m_physicalSize; + Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation; + + struct { + Qt::ScreenOrientation orientation; + drmModeModeInfo mode; + DrmPlane::Transformations planeTransformations; + QPoint globalPos; + bool valid = false; + } m_lastWorkingState; + DrmDumbBuffer *m_cursor[2] = {nullptr, nullptr}; + int m_cursorIndex = 0; + bool m_hasNewCursor = false; + bool m_internal = false; }; } Q_DECLARE_METATYPE(KWin::DrmOutput*) #endif diff --git a/plugins/platforms/drm/egl_gbm_backend.cpp b/plugins/platforms/drm/egl_gbm_backend.cpp index a8a772288..db5f4350f 100644 --- a/plugins/platforms/drm/egl_gbm_backend.cpp +++ b/plugins/platforms/drm/egl_gbm_backend.cpp @@ -1,368 +1,397 @@ /******************************************************************** 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 "drm_backend.h" #include "drm_output.h" #include "gbm_surface.h" #include "logging.h" #include "options.h" #include "screens.h" // kwin libs #include // Qt #include // system #include namespace KWin { EglGbmBackend::EglGbmBackend(DrmBackend *b) : AbstractEglBackend() , m_backend(b) { // Egl is always direct rendering setIsDirectRendering(true); setSyncsToVBlank(true); connect(m_backend, &DrmBackend::outputAdded, this, &EglGbmBackend::createOutput); connect(m_backend, &DrmBackend::outputRemoved, this, [this] (DrmOutput *output) { auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [output] (const Output &o) { return o.output == output; } ); if (it == m_outputs.end()) { return; } cleanupOutput(*it); m_outputs.erase(it); } ); } EglGbmBackend::~EglGbmBackend() { cleanup(); } void EglGbmBackend::cleanupSurfaces() { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { cleanupOutput(*it); } m_outputs.clear(); } void EglGbmBackend::cleanupOutput(const Output &o) { o.output->releaseGbm(); if (o.eglSurface != EGL_NO_SURFACE) { eglDestroySurface(eglDisplay(), o.eglSurface); } } 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) { if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_base")) || !hasClientExtension(QByteArrayLiteral("EGL_MESA_platform_gbm"))) { setFailed("EGL_EXT_platform_base and/or EGL_MESA_platform_gbm missing"); return false; } auto device = gbm_create_device(m_backend->fd()); if (!device) { setFailed("Could not create gbm device"); return false; } m_backend->setGbmDevice(device); display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, device, nullptr); } 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(); initBufferAge(); initWayland(); initRemotePresent(); } bool EglGbmBackend::initRenderingContext() { initBufferConfigs(); if (!createContext()) { return false; } const auto outputs = m_backend->outputs(); for (DrmOutput *drmOutput: outputs) { createOutput(drmOutput); } if (m_outputs.isEmpty()) { qCCritical(KWIN_DRM) << "Create Window Surfaces failed"; return false; } // set our first surface as the one for the abstract backend, just to make it happy setSurface(m_outputs.first().eglSurface); return makeContextCurrent(m_outputs.first()); } void EglGbmBackend::initRemotePresent() { if (!qEnvironmentVariableIsSet("KWIN_REMOTE")) return; qCDebug(KWIN_DRM) << "Support for remote access enabled"; m_remoteaccessManager.reset(new RemoteAccessManager); } -void EglGbmBackend::createOutput(DrmOutput *drmOutput) +bool EglGbmBackend::resetOutput(Output &o, DrmOutput *drmOutput) { - Output o; o.output = drmOutput; auto size = drmOutput->pixelSize(); - o.gbmSurface = std::make_shared(m_backend->gbmDevice(), size.width(), size.height(), + auto gbmSurface = std::make_shared(m_backend->gbmDevice(), size.width(), size.height(), GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); - if (!o.gbmSurface) { + if (!gbmSurface) { qCCritical(KWIN_DRM) << "Create gbm surface failed"; - return; + return false; } - o.eglSurface = eglCreatePlatformWindowSurfaceEXT(eglDisplay(), config(), (void *)(o.gbmSurface->surface()), nullptr); - if (o.eglSurface == EGL_NO_SURFACE) { + auto eglSurface = eglCreatePlatformWindowSurfaceEXT(eglDisplay(), config(), (void *)(gbmSurface->surface()), nullptr); + if (eglSurface == EGL_NO_SURFACE) { qCCritical(KWIN_DRM) << "Create Window Surface failed"; - o.gbmSurface.reset(); - return; + return false; + } else { + // destroy previous surface + if (o.eglSurface != EGL_NO_SURFACE) { + if (surface() == o.eglSurface) { + setSurface(eglSurface); + } + eglDestroySurface(eglDisplay(), o.eglSurface); + } + o.eglSurface = eglSurface; + o.gbmSurface = gbmSurface; + } + return true; +} + +void EglGbmBackend::createOutput(DrmOutput *drmOutput) +{ + Output o; + if (resetOutput(o, drmOutput)) { + connect(drmOutput, &DrmOutput::modeChanged, this, + [drmOutput, this] { + auto it = std::find_if(m_outputs.begin(), m_outputs.end(), + [drmOutput] (const auto &o) { + return o.output == drmOutput; + } + ); + if (it == m_outputs.end()) { + return; + } + resetOutput(*it, drmOutput); + } + ); + m_outputs << o; } - m_outputs << o; } bool EglGbmBackend::makeContextCurrent(const Output &output) { const EGLSurface surface = output.eglSurface; if (surface == EGL_NO_SURFACE) { return false; } if (eglMakeCurrent(eglDisplay(), surface, surface, context()) == EGL_FALSE) { qCCritical(KWIN_DRM) << "Make Context Current failed"; return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qCWarning(KWIN_DRM) << "Error occurred while creating context " << error; return false; } // TODO: ensure the viewport is set correctly each time const QSize &overall = screens()->size(); const QRect &v = output.output->geometry(); // TODO: are the values correct? qreal scale = output.output->scale(); - glViewport(-v.x() * scale, (v.height() - overall.height() - v.y()) * scale, + glViewport(-v.x() * scale, (v.height() - overall.height() + v.y()) * scale, overall.width() * scale, overall.height() * scale); return true; } 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) { qCCritical(KWIN_DRM) << "choose config failed"; return false; } if (count != 1) { qCCritical(KWIN_DRM) << "choose config did not return a config" << count; return false; } setConfig(configs[0]); return true; } void EglGbmBackend::present() { for (auto &o: m_outputs) { makeContextCurrent(o); presentOnOutput(o); } } void EglGbmBackend::presentOnOutput(EglGbmBackend::Output &o) { eglSwapBuffers(eglDisplay(), o.eglSurface); o.buffer = m_backend->createBuffer(o.gbmSurface); if(m_remoteaccessManager && gbm_surface_has_free_buffers(o.gbmSurface->surface())) { // GBM surface is released on page flip so // we should pass the buffer before it's presented m_remoteaccessManager->passBuffer(o.output, o.buffer); } m_backend->present(o.buffer, o.output); if (supportsBufferAge()) { eglQuerySurface(eglDisplay(), o.eglSurface, EGL_BUFFER_AGE_EXT, &o.bufferAge); } } 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() { startRenderTimer(); return QRegion(); } QRegion EglGbmBackend::prepareRenderingForScreen(int screenId) { const Output &o = m_outputs.at(screenId); makeContextCurrent(o); if (supportsBufferAge()) { QRegion region; // Note: An age of zero means the buffer contents are undefined if (o.bufferAge > 0 && o.bufferAge <= o.damageHistory.count()) { for (int i = 0; i < o.bufferAge - 1; i++) region |= o.damageHistory[i]; } else { region = o.output->geometry(); } return region; } return QRegion(); } void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { Q_UNUSED(renderedRegion) Q_UNUSED(damagedRegion) } void EglGbmBackend::endRenderingFrameForScreen(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion) { Output &o = m_outputs[screenId]; if (damagedRegion.intersected(o.output->geometry()).isEmpty() && screenId == 0) { // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. if (!renderedRegion.intersected(o.output->geometry()).isEmpty()) glFlush(); for (auto &o: m_outputs) { o.bufferAge = 1; } return; } presentOnOutput(o); // Save the damaged region to history // Note: damage history is only collected for the first screen. For any other screen full repaints // are triggered. This is due to a limitation in Scene::paintGenericScreen which resets the Toplevel's // repaint. So multiple calls to Scene::paintScreen as it's done in multi-output rendering only // have correct damage information for the first screen. If we try to track damage nevertheless, // it creates artifacts. So for the time being we work around the problem by only supporting buffer // age on the first output. To properly support buffer age on all outputs the rendering needs to // be refactored in general. if (supportsBufferAge() && screenId == 0) { if (o.damageHistory.count() > 10) { o.damageHistory.removeLast(); } o.damageHistory.prepend(damagedRegion.intersected(o.output->geometry())); } } bool EglGbmBackend::usesOverlayWindow() const { return false; } bool EglGbmBackend::perScreenRendering() const { return true; } /************************************************ * EglTexture ************************************************/ EglGbmTexture::EglGbmTexture(KWin::SceneOpenGLTexture *texture, EglGbmBackend *backend) : AbstractEglTexture(texture, backend) { } EglGbmTexture::~EglGbmTexture() = default; } // namespace diff --git a/plugins/platforms/drm/egl_gbm_backend.h b/plugins/platforms/drm/egl_gbm_backend.h index ab276237d..d9d7bcb01 100644 --- a/plugins/platforms/drm/egl_gbm_backend.h +++ b/plugins/platforms/drm/egl_gbm_backend.h @@ -1,99 +1,100 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_EGL_GBM_BACKEND_H #define KWIN_EGL_GBM_BACKEND_H #include "abstract_egl_backend.h" #include "remoteaccess_manager.h" #include struct gbm_surface; namespace KWin { class DrmBackend; class DrmBuffer; class DrmOutput; class GbmSurface; /** * @brief OpenGL Backend using Egl on a GBM surface. **/ class EglGbmBackend : public AbstractEglBackend { Q_OBJECT public: EglGbmBackend(DrmBackend *b); virtual ~EglGbmBackend(); void screenGeometryChanged(const QSize &size) override; SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override; QRegion prepareRenderingFrame() override; void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override; void endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion) override; bool usesOverlayWindow() const override; bool perScreenRendering() const override; QRegion prepareRenderingForScreen(int screenId) override; void init() override; protected: void present() override; void cleanupSurfaces() override; private: bool initializeEgl(); bool initBufferConfigs(); bool initRenderingContext(); void initRemotePresent(); struct Output { DrmOutput *output = nullptr; DrmBuffer *buffer = nullptr; std::shared_ptr gbmSurface; EGLSurface eglSurface = EGL_NO_SURFACE; int bufferAge = 0; /** * @brief The damage history for the past 10 frames. */ QList damageHistory; }; + bool resetOutput(Output &output, DrmOutput *drmOutput); bool makeContextCurrent(const Output &output); void presentOnOutput(Output &output); void cleanupOutput(const Output &output); void createOutput(DrmOutput *output); DrmBackend *m_backend; QVector m_outputs; QScopedPointer m_remoteaccessManager; friend class EglGbmTexture; }; /** * @brief Texture using an EGLImageKHR. **/ class EglGbmTexture : public AbstractEglTexture { public: virtual ~EglGbmTexture(); private: friend class EglGbmBackend; EglGbmTexture(SceneOpenGLTexture *texture, EglGbmBackend *backend); }; } // namespace #endif diff --git a/plugins/platforms/drm/scene_qpainter_drm_backend.cpp b/plugins/platforms/drm/scene_qpainter_drm_backend.cpp index bf5a000a3..49b4656dd 100644 --- a/plugins/platforms/drm/scene_qpainter_drm_backend.cpp +++ b/plugins/platforms/drm/scene_qpainter_drm_backend.cpp @@ -1,123 +1,144 @@ /******************************************************************** 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 "scene_qpainter_drm_backend.h" #include "drm_backend.h" #include "drm_output.h" #include "logind.h" namespace KWin { DrmQPainterBackend::DrmQPainterBackend(DrmBackend *backend) : QObject() , QPainterBackend() , m_backend(backend) { const auto outputs = m_backend->outputs(); for (auto output: outputs) { initOutput(output); } connect(m_backend, &DrmBackend::outputAdded, this, &DrmQPainterBackend::initOutput); connect(m_backend, &DrmBackend::outputRemoved, this, [this] (DrmOutput *o) { auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [o] (const Output &output) { return output.output == o; } ); if (it == m_outputs.end()) { return; } delete (*it).buffer[0]; delete (*it).buffer[1]; m_outputs.erase(it); } ); } DrmQPainterBackend::~DrmQPainterBackend() { for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { delete (*it).buffer[0]; delete (*it).buffer[1]; } } void DrmQPainterBackend::initOutput(DrmOutput *output) { Output o; auto initBuffer = [&o, output, this] (int index) { o.buffer[index] = m_backend->createBuffer(output->pixelSize()); o.buffer[index]->map(); o.buffer[index]->image()->fill(Qt::black); }; + connect(output, &DrmOutput::modeChanged, this, + [output, this] { + auto it = std::find_if(m_outputs.begin(), m_outputs.end(), + [output] (const auto &o) { + return o.output == output; + } + ); + if (it == m_outputs.end()) { + return; + } + delete (*it).buffer[0]; + delete (*it).buffer[1]; + auto initBuffer = [it, output, this] (int index) { + it->buffer[index] = m_backend->createBuffer(output->pixelSize()); + it->buffer[index]->map(); + it->buffer[index]->image()->fill(Qt::black); + }; + initBuffer(0); + initBuffer(1); + } + ); initBuffer(0); initBuffer(1); o.output = output; m_outputs << o; } QImage *DrmQPainterBackend::buffer() { return bufferForScreen(0); } QImage *DrmQPainterBackend::bufferForScreen(int screenId) { const Output &o = m_outputs.at(screenId); return o.buffer[o.index]->image(); } bool DrmQPainterBackend::needsFullRepaint() const { return true; } void DrmQPainterBackend::prepareRenderingFrame() { for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { (*it).index = ((*it).index + 1) % 2; } } void DrmQPainterBackend::present(int mask, const QRegion &damage) { Q_UNUSED(mask) Q_UNUSED(damage) if (!LogindIntegration::self()->isActiveSession()) { return; } for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { const Output &o = *it; m_backend->present(o.buffer[o.index], o.output); } } bool DrmQPainterBackend::usesOverlayWindow() const { return false; } bool DrmQPainterBackend::perScreenRendering() const { return true; } } diff --git a/plugins/platforms/drm/screens_drm.cpp b/plugins/platforms/drm/screens_drm.cpp index e87d84423..400046b30 100644 --- a/plugins/platforms/drm/screens_drm.cpp +++ b/plugins/platforms/drm/screens_drm.cpp @@ -1,125 +1,152 @@ /******************************************************************** 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 "screens_drm.h" #include "drm_backend.h" #include "drm_output.h" namespace KWin { DrmScreens::DrmScreens(DrmBackend *backend, QObject *parent) : Screens(parent) , m_backend(backend) { connect(backend, &DrmBackend::screensQueried, this, &DrmScreens::updateCount); connect(backend, &DrmBackend::screensQueried, this, &DrmScreens::changed); } DrmScreens::~DrmScreens() = default; void DrmScreens::init() { updateCount(); KWin::Screens::init(); emit changed(); } QRect DrmScreens::geometry(int screen) const { - const auto outputs = m_backend->outputs(); + const auto outputs = m_backend->enabledOutputs(); if (screen >= outputs.size()) { return QRect(); } return outputs.at(screen)->geometry(); } qreal DrmScreens::scale(int screen) const { - const auto outputs = m_backend->outputs(); + const auto outputs = m_backend->enabledOutputs(); if (screen >= outputs.size()) { return 1; } return outputs.at(screen)->scale(); } QSize DrmScreens::size(int screen) const { - const auto outputs = m_backend->outputs(); + const auto outputs = m_backend->enabledOutputs(); if (screen >= outputs.size()) { return QSize(); } return outputs.at(screen)->geometry().size(); } void DrmScreens::updateCount() { - setCount(m_backend->outputs().size()); + setCount(m_backend->enabledOutputs().size()); } int DrmScreens::number(const QPoint &pos) const { int bestScreen = 0; int minDistance = INT_MAX; - const auto outputs = m_backend->outputs(); + const auto outputs = m_backend->enabledOutputs(); for (int i = 0; i < outputs.size(); ++i) { const QRect &geo = outputs.at(i)->geometry(); if (geo.contains(pos)) { return i; } int distance = QPoint(geo.topLeft() - pos).manhattanLength(); distance = qMin(distance, QPoint(geo.topRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomLeft() - pos).manhattanLength()); if (distance < minDistance) { minDistance = distance; bestScreen = i; } } return bestScreen; } QString DrmScreens::name(int screen) const { - const auto outputs = m_backend->outputs(); + const auto outputs = m_backend->enabledOutputs(); if (screen >= outputs.size()) { return Screens::name(screen); } return outputs.at(screen)->name(); } float DrmScreens::refreshRate(int screen) const { - const auto outputs = m_backend->outputs(); + const auto outputs = m_backend->enabledOutputs(); if (screen >= outputs.size()) { return Screens::refreshRate(screen); } return outputs.at(screen)->currentRefreshRate() / 1000.0f; } QSizeF DrmScreens::physicalSize(int screen) const { - const auto outputs = m_backend->outputs(); + const auto outputs = m_backend->enabledOutputs(); if (screen >= outputs.size()) { return Screens::physicalSize(screen); } return outputs.at(screen)->physicalSize(); } +bool DrmScreens::isInternal(int screen) const +{ + const auto outputs = m_backend->enabledOutputs(); + if (screen >= outputs.size()) { + return false; + } + return outputs.at(screen)->isInternal(); +} + +bool DrmScreens::supportsTransformations(int screen) const +{ + const auto outputs = m_backend->enabledOutputs(); + if (screen >= outputs.size()) { + return false; + } + return outputs.at(screen)->supportsTransformations(); +} + +Qt::ScreenOrientation DrmScreens::orientation(int screen) const +{ + const auto outputs = m_backend->outputs(); + if (screen >= outputs.size()) { + return Qt::PrimaryOrientation; + } + return outputs.at(screen)->orientation(); +} + } diff --git a/plugins/platforms/drm/screens_drm.h b/plugins/platforms/drm/screens_drm.h index e7f8d961e..b3bf7e3a6 100644 --- a/plugins/platforms/drm/screens_drm.h +++ b/plugins/platforms/drm/screens_drm.h @@ -1,51 +1,54 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_SCREENS_DRM_H #define KWIN_SCREENS_DRM_H #include "screens.h" namespace KWin { class DrmBackend; class DrmScreens : public Screens { Q_OBJECT public: DrmScreens(DrmBackend *backend, QObject *parent = nullptr); virtual ~DrmScreens(); void init() override; QRect geometry(int screen) const override; int number(const QPoint &pos) const override; qreal scale(int screen) const override; QSize size(int screen) const override; void updateCount() override; QString name(int screen) const override; float refreshRate(int screen) const override; QSizeF physicalSize(int screen) const override; + bool isInternal(int screen) const override; + bool supportsTransformations(int screen) const override; + Qt::ScreenOrientation orientation(int screen) const override; private: DrmBackend *m_backend; }; } #endif diff --git a/plugins/platforms/fbdev/fbdev.json b/plugins/platforms/fbdev/fbdev.json index 9ff086a6d..b223aafcd 100644 --- a/plugins/platforms/fbdev/fbdev.json +++ b/plugins/platforms/fbdev/fbdev.json @@ -1,55 +1,55 @@ { "KPlugin": { "Description": "Render to framebuffer.", "Description[ca@valencia]": "Renderitza al «framebuffer».", "Description[ca]": "Renderitza al «framebuffer».", "Description[da]": "Render til framebuffer.", "Description[de]": "In Framebuffer rendern.", "Description[el]": "Αποτύπωση σε ενδιάμεση μνήμη πλαισίων.", "Description[es]": "Renderizar en el «framebuffer».", "Description[et]": "Renderdamine kaadripuhvris.", "Description[eu]": "Errendatu framebuffer batera.", - "Description[fi]": "Renderöi framebufferiin.", + "Description[fi]": "Hahmonna framebufferiin.", "Description[fr]": "Rendre sur le « framebuffer ».", "Description[gl]": "Renderizar no búfer de fotogramas.", "Description[hu]": "Renderelés framebufferbe.", "Description[it]": "Resa su framebuffer.", "Description[ko]": "프레임버퍼에 렌더링합니다.", "Description[nl]": "Naar framebuffer renderen.", "Description[nn]": "Teikn opp til biletbuffer.", "Description[pl]": "Wyświetlaj w buforze klatek.", "Description[pt]": "Desenhar no 'framebuffer'.", "Description[pt_BR]": "Renderizar no framebuffer.", "Description[ru]": "Отрисовка во фреймбуфер", "Description[sk]": "Renderovať do framebuffera.", "Description[sl]": "Izriši v medpomnilnik sličic.", "Description[sr@ijekavian]": "Рендеровање у кадробафер.", "Description[sr@ijekavianlatin]": "Renderovanje u kadrobafer.", "Description[sr@latin]": "Renderovanje u kadrobafer.", "Description[sr]": "Рендеровање у кадробафер.", "Description[sv]": "Återge i rambuffer.", "Description[tr]": "Çerçeve tamponuna gerçekle.", "Description[uk]": "Обробляти до буфера кадрів.", "Description[x-test]": "xxRender to framebuffer.xx", "Description[zh_CN]": "渲染到帧缓冲。", "Description[zh_TW]": "成像至 framebuffer。", "Id": "KWinWaylandFbdevBackend", "Name": "framebuffer", "Name[ca@valencia]": "Framebuffer", "Name[ca]": "Framebuffer", "Name[de]": "Framebuffer", "Name[el]": "ενδιάμεση μνήμη πλαισίων", "Name[nn]": "biletbuffer", "Name[pl]": "bufor klatek", "Name[pt]": "'Framebuffer'", "Name[sl]": "medpomnilnik sličic", "Name[sr@ijekavian]": "Кадробафер", "Name[sr@ijekavianlatin]": "Kadrobafer", "Name[sr@latin]": "Kadrobafer", "Name[sr]": "Кадробафер", "Name[sv]": "rambuffer", "Name[tr]": "çerçeve tampon", "Name[x-test]": "xxframebufferxx" }, "input": false } diff --git a/plugins/platforms/hwcomposer/hwcomposer.json b/plugins/platforms/hwcomposer/hwcomposer.json index de2a8e50b..61cb0fc08 100644 --- a/plugins/platforms/hwcomposer/hwcomposer.json +++ b/plugins/platforms/hwcomposer/hwcomposer.json @@ -1,47 +1,47 @@ { "KPlugin": { "Description": "Render through hwcomposer through libhybris.", "Description[ca@valencia]": "Renderitza mitjançant el «hwcomposer» mitjançant «libhybris».", "Description[ca]": "Renderitza mitjançant el «hwcomposer» mitjançant «libhybris».", "Description[da]": "Rendér igennem hwcomposer igennem libhybris.", "Description[de]": "In hwcomposer mit libhybris rendern.", "Description[el]": "Αποτύπωση μέσω hwcomposer μέσω libhybris.", "Description[es]": "Renderizar a través «hwcomposer» mediante «libhybris».", "Description[et]": "Renderdamine hwcomposeris libhybrise abil.", "Description[eu]": "Errendatu hwcomposer libhybris bidez erabiliz.", - "Description[fi]": "Renderöi hwcomposerin läpi käyttäen libhybristä.", + "Description[fi]": "Hahmonna hwcomposerin läpi libhybristä käyttäen.", "Description[fr]": "Rendre par le biais de « hwcomposer » via « libhybris ».", "Description[gl]": "Renderizar a través de hwcomposer a través de libhybris.", "Description[hu]": "Renderelés hwcomposerrel libhybrisen keresztül.", "Description[it]": "Resa tramite hwcomposer attraverso libhybris.", "Description[ko]": "libhybris를 통하여 hwcomposer로 렌더링합니다.", "Description[nl]": "Render via hwcomposer via libhybris.", "Description[nn]": "Teikn opp via hwcomposer gjennom libhybris.", "Description[pl]": "Wyświetlaj przez sprzętowy kompozytor przez libhybris.", "Description[pt]": "Desenhar através do Hwcomposer, usando a libhybris.", "Description[pt_BR]": "Renderizar através do hwcomposer e libhybris.", "Description[sk]": "Renderovať cez hwcomposer cez libhybris.", "Description[sl]": "Izriši preko hwcomposer-ja in libhybris.", "Description[sr@ijekavian]": "Рендеровање кроз ХВ‑композер кроз libhybris.", "Description[sr@ijekavianlatin]": "Renderovanje kroz HWcomposer kroz libhybris.", "Description[sr@latin]": "Renderovanje kroz HWcomposer kroz libhybris.", "Description[sr]": "Рендеровање кроз ХВ‑композер кроз libhybris.", "Description[sv]": "Återge via på hårdvarusammansättare via libhybris.", "Description[tr]": "libhybris aracılığıyla hwcomposer içinden gerçekle.", "Description[uk]": "Обробляти за допомогою апаратного засобу композиції через libhybris.", "Description[x-test]": "xxRender through hwcomposer through libhybris.xx", "Description[zh_CN]": "使用 libhybris 通过 hwcomposer 渲染。", "Description[zh_TW]": "透過 libhybris 成像到 hwcomposer。", "Id": "KWinWaylandHwcomposerBackend", "Name": "hwcomposer", "Name[pl]": "sprzętowy kompozytor", "Name[pt]": "Hwcomposer", "Name[sr@ijekavian]": "ХВ‑композер", "Name[sr@ijekavianlatin]": "HWcomposer", "Name[sr@latin]": "HWcomposer", "Name[sr]": "ХВ‑композер", "Name[sv]": "hårdvarusammansättare", "Name[x-test]": "xxhwcomposerxx" }, "input": false } diff --git a/plugins/platforms/hwcomposer/hwcomposer_backend.cpp b/plugins/platforms/hwcomposer/hwcomposer_backend.cpp index d871568d8..6cc024fa9 100644 --- a/plugins/platforms/hwcomposer/hwcomposer_backend.cpp +++ b/plugins/platforms/hwcomposer/hwcomposer_backend.cpp @@ -1,475 +1,485 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "egl_hwcomposer_backend.h" #include "hwcomposer_backend.h" #include "logging.h" #include "screens_hwcomposer.h" #include "composite.h" #include "input.h" #include "main.h" #include "wayland_server.h" // KWayland #include #include // Qt #include #include // hybris/android #include #include // linux #include // based on test_hwcomposer.c from libhybris project (Apache 2 licensed) namespace KWin { BacklightInputEventFilter::BacklightInputEventFilter(HwcomposerBackend *backend) : InputEventFilter() , m_backend(backend) { } BacklightInputEventFilter::~BacklightInputEventFilter() = default; bool BacklightInputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { Q_UNUSED(event) Q_UNUSED(nativeButton) if (!m_backend->isBacklightOff()) { return false; } toggleBacklight(); return true; } bool BacklightInputEventFilter::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) if (!m_backend->isBacklightOff()) { return false; } toggleBacklight(); return true; } bool BacklightInputEventFilter::keyEvent(QKeyEvent *event) { if (event->key() == Qt::Key_PowerOff && event->type() == QEvent::KeyRelease) { toggleBacklight(); return true; } return m_backend->isBacklightOff(); } bool BacklightInputEventFilter::touchDown(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(pos) Q_UNUSED(time) if (!m_backend->isBacklightOff()) { return false; } if (m_touchPoints.isEmpty()) { if (!m_doubleTapTimer.isValid()) { // this is the first tap m_doubleTapTimer.start(); } else { if (m_doubleTapTimer.elapsed() < qApp->doubleClickInterval()) { m_secondTap = true; } else { // took too long. Let's consider it a new click m_doubleTapTimer.restart(); } } } else { // not a double tap m_doubleTapTimer.invalidate(); m_secondTap = false; } m_touchPoints << id; return true; } bool BacklightInputEventFilter::touchUp(quint32 id, quint32 time) { Q_UNUSED(time) m_touchPoints.removeAll(id); if (!m_backend->isBacklightOff()) { return false; } if (m_touchPoints.isEmpty() && m_doubleTapTimer.isValid() && m_secondTap) { if (m_doubleTapTimer.elapsed() < qApp->doubleClickInterval()) { toggleBacklight(); } m_doubleTapTimer.invalidate(); m_secondTap = false; } return true; } bool BacklightInputEventFilter::touchMotion(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) return m_backend->isBacklightOff(); } void BacklightInputEventFilter::toggleBacklight() { // queued to not modify the list of event filters while filtering QMetaObject::invokeMethod(m_backend, "toggleBlankOutput", Qt::QueuedConnection); } HwcomposerBackend::HwcomposerBackend(QObject *parent) : Platform(parent) { if (!QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement/Actions/BrightnessControl"), QStringLiteral("org.kde.Solid.PowerManagement.Actions.BrightnessControl"), QStringLiteral("brightnessChanged"), this, SLOT(screenBrightnessChanged(int)))) { qCWarning(KWIN_HWCOMPOSER) << "Failed to connect to brightness control"; } handleOutputs(); } HwcomposerBackend::~HwcomposerBackend() { if (!m_outputBlank) { toggleBlankOutput(); } if (m_device) { hwc_close_1(m_device); } } KWayland::Server::OutputInterface* HwcomposerBackend::createOutput(hwc_composer_device_1_t *device) { uint32_t configs[5]; size_t numConfigs = 5; if (device->getDisplayConfigs(device, 0, configs, &numConfigs) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to get hwcomposer display configurations"; return nullptr; } int32_t attr_values[5]; uint32_t attributes[] = { HWC_DISPLAY_WIDTH, HWC_DISPLAY_HEIGHT, HWC_DISPLAY_DPI_X, HWC_DISPLAY_DPI_Y, HWC_DISPLAY_VSYNC_PERIOD , HWC_DISPLAY_NO_ATTRIBUTE }; device->getDisplayAttributes(device, 0, configs[0], attributes, attr_values); QSize pixel(attr_values[0], attr_values[1]); if (pixel.isEmpty()) { return nullptr; } using namespace KWayland::Server; OutputInterface *o = waylandServer()->display()->createOutput(waylandServer()->display()); o->addMode(pixel, OutputInterface::ModeFlag::Current | OutputInterface::ModeFlag::Preferred, (attr_values[4] == 0) ? 60000 : 10E11/attr_values[4]); if (attr_values[2] != 0 && attr_values[3] != 0) { static const qreal factor = 25.4; m_physicalSize = QSizeF(qreal(pixel.width() * 1000) / qreal(attr_values[2]) * factor, qreal(pixel.height() * 1000) / qreal(attr_values[3]) * factor); o->setPhysicalSize(m_physicalSize.toSize()); } else { // couldn't read physical size, assume 96 dpi o->setPhysicalSize(pixel / 3.8); } o->create(); return o; } void HwcomposerBackend::init() { hw_module_t *hwcModule = nullptr; if (hw_get_module(HWC_HARDWARE_MODULE_ID, (const hw_module_t **)&hwcModule) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to get hwcomposer module"; emit initFailed(); return; } hwc_composer_device_1_t *hwcDevice = nullptr; if (hwc_open_1(hwcModule, &hwcDevice) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to open hwcomposer device"; emit initFailed(); return; } // unblank, setPowerMode? m_device = hwcDevice; m_hwcVersion = m_device->common.version; if ((m_hwcVersion & 0xffff0000) == 0) { // Assume header version is always 1 uint32_t header_version = 1; // Legacy version encoding m_hwcVersion = (m_hwcVersion << 16) | header_version; } // register callbacks hwc_procs_t *procs = new hwc_procs_t; procs->invalidate = [] (const struct hwc_procs* procs) { Q_UNUSED(procs) }; procs->vsync = [] (const struct hwc_procs* procs, int disp, int64_t timestamp) { Q_UNUSED(procs) if (disp != 0) { return; } dynamic_cast(kwinApp()->platform())->wakeVSync(); }; procs->hotplug = [] (const struct hwc_procs* procs, int disp, int connected) { Q_UNUSED(procs) Q_UNUSED(disp) Q_UNUSED(connected) }; m_device->registerProcs(m_device, procs); initLights(); toggleBlankOutput(); m_filter.reset(new BacklightInputEventFilter(this)); input()->prependInputEventFilter(m_filter.data()); // get display configuration auto output = createOutput(hwcDevice); if (!output) { emit initFailed(); return; } m_displaySize = output->pixelSize(); m_refreshRate = output->refreshRate(); if (m_refreshRate != 0) { m_vsyncInterval = 1000000/m_refreshRate; } if (m_lights) { using namespace KWayland::Server; output->setDpmsSupported(true); auto updateDpms = [this, output] { output->setDpmsMode(m_outputBlank ? OutputInterface::DpmsMode::Off : OutputInterface::DpmsMode::On); }; updateDpms(); connect(this, &HwcomposerBackend::outputBlankChanged, this, updateDpms); connect(output, &OutputInterface::dpmsModeRequested, this, [this] (KWayland::Server::OutputInterface::DpmsMode mode) { if (mode == OutputInterface::DpmsMode::On) { if (m_outputBlank) { toggleBlankOutput(); } } else { if (!m_outputBlank) { toggleBlankOutput(); } } } ); } qCDebug(KWIN_HWCOMPOSER) << "Display size:" << m_displaySize; qCDebug(KWIN_HWCOMPOSER) << "Refresh rate:" << m_refreshRate; emit screensQueried(); setReady(true); } void HwcomposerBackend::initLights() { hw_module_t *lightsModule = nullptr; if (hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (const hw_module_t **)&lightsModule) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to get lights module"; return; } light_device_t *lightsDevice = nullptr; if (lightsModule->methods->open(lightsModule, LIGHT_ID_BACKLIGHT, (hw_device_t **)&lightsDevice) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to create lights device"; return; } m_lights = lightsDevice; } void HwcomposerBackend::toggleBlankOutput() { if (!m_device) { return; } m_outputBlank = !m_outputBlank; toggleScreenBrightness(); #if defined(HWC_DEVICE_API_VERSION_1_4) || defined(HWC_DEVICE_API_VERSION_1_5) if (m_hwcVersion > HWC_DEVICE_API_VERSION_1_3) m_device->setPowerMode(m_device, 0, m_outputBlank ? HWC_POWER_MODE_OFF : HWC_POWER_MODE_NORMAL); else #endif m_device->blank(m_device, 0, m_outputBlank ? 1 : 0); // only disable Vsync, enable happens after next frame rendered if (m_outputBlank) { enableVSync(false); } // enable/disable compositor repainting when blanked setOutputsEnabled(!m_outputBlank); if (Compositor *compositor = Compositor::self()) { if (!m_outputBlank) { compositor->addRepaintFull(); } } emit outputBlankChanged(); } void HwcomposerBackend::toggleScreenBrightness() { if (!m_lights) { return; } const int brightness = m_outputBlank ? 0 : m_oldScreenBrightness; struct light_state_t state; state.flashMode = LIGHT_FLASH_NONE; state.brightnessMode = BRIGHTNESS_MODE_USER; state.color = (int)((0xffU << 24) | (brightness << 16) | (brightness << 8) | brightness); m_lights->set_light(m_lights, &state); } void HwcomposerBackend::enableVSync(bool enable) { if (m_hasVsync == enable) { return; } const int result = m_device->eventControl(m_device, 0, HWC_EVENT_VSYNC, enable ? 1: 0); m_hasVsync = enable && (result == 0); } HwcomposerWindow *HwcomposerBackend::createSurface() { return new HwcomposerWindow(this); } Screens *HwcomposerBackend::createScreens(QObject *parent) { return new HwcomposerScreens(this, parent); } OpenGLBackend *HwcomposerBackend::createOpenGLBackend() { return new EglHwcomposerBackend(this); } void HwcomposerBackend::waitVSync() { if (!m_hasVsync) { return; } m_vsyncMutex.lock(); m_vsyncWaitCondition.wait(&m_vsyncMutex, m_vsyncInterval); m_vsyncMutex.unlock(); } void HwcomposerBackend::wakeVSync() { m_vsyncMutex.lock(); m_vsyncWaitCondition.wakeAll(); m_vsyncMutex.unlock(); } -static void initLayer(hwc_layer_1_t *layer, const hwc_rect_t &rect) +static void initLayer(hwc_layer_1_t *layer, const hwc_rect_t &rect, int layerCompositionType) { memset(layer, 0, sizeof(hwc_layer_1_t)); - layer->compositionType = HWC_FRAMEBUFFER; + layer->compositionType = layerCompositionType; layer->hints = 0; layer->flags = 0; layer->handle = 0; layer->transform = 0; layer->blending = HWC_BLENDING_NONE; +#ifdef HWC_DEVICE_API_VERSION_1_3 + layer->sourceCropf.top = 0.0f; + layer->sourceCropf.left = 0.0f; + layer->sourceCropf.bottom = (float) rect.bottom; + layer->sourceCropf.right = (float) rect.right; +#else layer->sourceCrop = rect; +#endif layer->displayFrame = rect; layer->visibleRegionScreen.numRects = 1; layer->visibleRegionScreen.rects = &layer->displayFrame; layer->acquireFenceFd = -1; layer->releaseFenceFd = -1; layer->planeAlpha = 0xFF; +#ifdef HWC_DEVICE_API_VERSION_1_5 + layer->surfaceDamage.numRects = 0; +#endif } HwcomposerWindow::HwcomposerWindow(HwcomposerBackend *backend) : HWComposerNativeWindow(backend->size().width(), backend->size().height(), HAL_PIXEL_FORMAT_RGBA_8888) , m_backend(backend) { setBufferCount(3); size_t size = sizeof(hwc_display_contents_1_t) + 2 * sizeof(hwc_layer_1_t); hwc_display_contents_1_t *list = (hwc_display_contents_1_t*)malloc(size); m_list = (hwc_display_contents_1_t**)malloc(HWC_NUM_DISPLAY_TYPES * sizeof(hwc_display_contents_1_t *)); for (int i = 0; i < HWC_NUM_DISPLAY_TYPES; ++i) { m_list[i] = nullptr; } // Assign buffer only to the first item, otherwise you get tearing // if passed the same to multiple places // see https://github.com/mer-hybris/qt5-qpa-hwcomposer-plugin/commit/f1d802151e8a4f5d10d60eb8de8e07552b93a34a m_list[0] = list; const hwc_rect_t rect = { 0, 0, m_backend->size().width(), m_backend->size().height() }; - initLayer(&list->hwLayers[0], rect); - initLayer(&list->hwLayers[1], rect); + initLayer(&list->hwLayers[0], rect, HWC_FRAMEBUFFER); + initLayer(&list->hwLayers[1], rect, HWC_FRAMEBUFFER_TARGET); list->retireFenceFd = -1; list->flags = HWC_GEOMETRY_CHANGED; list->numHwLayers = 2; } HwcomposerWindow::~HwcomposerWindow() { // TODO: cleanup } void HwcomposerWindow::present(HWComposerNativeWindowBuffer *buffer) { m_backend->waitVSync(); hwc_composer_device_1_t *device = m_backend->device(); auto fblayer = &m_list[0]->hwLayers[1]; fblayer->handle = buffer->handle; fblayer->acquireFenceFd = getFenceBufferFd(buffer); fblayer->releaseFenceFd = -1; int err = device->prepare(device, 1, m_list); assert(err == 0); err = device->set(device, 1, m_list); assert(err == 0); m_backend->enableVSync(true); setFenceBufferFd(buffer, fblayer->releaseFenceFd); if (m_list[0]->retireFenceFd != -1) { close(m_list[0]->retireFenceFd); m_list[0]->retireFenceFd = -1; } m_list[0]->flags = 0; } } diff --git a/plugins/platforms/virtual/virtual.json b/plugins/platforms/virtual/virtual.json index 594ce15c9..267292a4f 100644 --- a/plugins/platforms/virtual/virtual.json +++ b/plugins/platforms/virtual/virtual.json @@ -1,59 +1,60 @@ { "KPlugin": { "Description": "Render to a virtual framebuffer.", "Description[ca@valencia]": "Renderitza a un «framebuffer» virtual.", "Description[ca]": "Renderitza a un «framebuffer» virtual.", "Description[da]": "Rendér til en virtuel framebuffer.", "Description[de]": "In virtuellen Framebuffer rendern.", "Description[el]": "Αποτύπωση σε εικονική ενδιάμεση μνήμη πλαισίων.", "Description[es]": "Renderizar en un «framebuffer» virtual.", "Description[et]": "Renderdamine virtuaalses kaadripuhvris.", "Description[eu]": "Errendatu alegiazko framebuffer batera.", - "Description[fi]": "Renderöi virtuaaliseen framebufferiin.", + "Description[fi]": "Hahmonna virtuaaliseen framebufferiin.", "Description[fr]": "Rendre sur un « framebuffer » factice.", "Description[gl]": "Renderizar nun búfer de fotogramas virtual.", "Description[hu]": "Renderelés egy virtuális framebufferbe.", "Description[it]": "Resa su un framebuffer virtuale.", "Description[ko]": "가상 프레임버퍼에 렌더링합니다.", "Description[nl]": "Naar een virtuele framebuffer renderen.", "Description[nn]": "Teikn opp til virtuell biletbuffer.", "Description[pl]": "Wyświetlaj w wirtualnym buforze klatek.", "Description[pt]": "Desenhar num 'framebuffer' virtual.", "Description[pt_BR]": "Renderizar no framebuffer virtual.", "Description[ru]": "Отрисовка в виртуальный фреймбуфер", "Description[sk]": "Renderovať na virtuálny framebuffer.", "Description[sl]": "Izriši v navidezni medpomnilnik sličic.", "Description[sr@ijekavian]": "Рендеровање у виртуелни кадробафер.", "Description[sr@ijekavianlatin]": "Renderovanje u virtuelni kadrobafer.", "Description[sr@latin]": "Renderovanje u virtuelni kadrobafer.", "Description[sr]": "Рендеровање у виртуелни кадробафер.", "Description[sv]": "Återge i en virtuell rambuffer.", "Description[tr]": "Sanal bir çerçeve tamponuna gerçekle.", "Description[uk]": "Обробляти до віртуального буфера кадрів.", "Description[x-test]": "xxRender to a virtual framebuffer.xx", "Description[zh_CN]": "渲染到虚拟帧缓冲。", "Description[zh_TW]": "成像到虛擬影格緩衝區。", "Id": "KWinWaylandVirtualBackend", "Name": "virtual", "Name[ca@valencia]": "Virtual", "Name[ca]": "Virtual", + "Name[cs]": "virtuální", "Name[da]": "virtuel", "Name[de]": "Virtuell", "Name[el]": "εικονικό", "Name[eu]": "alegiazkoa", "Name[hu]": "virtuális", "Name[nl]": "virtueel", "Name[nn]": "virtuell", "Name[pl]": "wirtualne", "Name[sl]": "navidezno", "Name[sr@ijekavian]": "Виртуелно", "Name[sr@ijekavianlatin]": "Virtuelno", "Name[sr@latin]": "Virtuelno", "Name[sr]": "Виртуелно", "Name[sv]": "virtuell", "Name[tr]": "sanal", "Name[x-test]": "xxvirtualxx", "Name[zh_TW]": "虛擬" }, "input": true } diff --git a/plugins/platforms/virtual/virtual_backend.cpp b/plugins/platforms/virtual/virtual_backend.cpp index 1f2c384ae..d41cd6988 100644 --- a/plugins/platforms/virtual/virtual_backend.cpp +++ b/plugins/platforms/virtual/virtual_backend.cpp @@ -1,101 +1,112 @@ /******************************************************************** 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 "virtual_backend.h" #include "scene_qpainter_virtual_backend.h" #include "screens_virtual.h" #include "wayland_server.h" #include "egl_gbm_backend.h" // Qt #include // KWayland #include // system #include #include #include #if HAVE_GBM #include #endif +#include namespace KWin { VirtualBackend::VirtualBackend(QObject *parent) : Platform(parent) { if (qEnvironmentVariableIsSet("KWIN_WAYLAND_VIRTUAL_SCREENSHOTS")) { m_screenshotDir.reset(new QTemporaryDir); if (!m_screenshotDir->isValid()) { m_screenshotDir.reset(); } if (!m_screenshotDir.isNull()) { qDebug() << "Screenshots saved to: " << m_screenshotDir->path(); } } setSupportsPointerWarping(true); + setSupportsGammaControl(true); } VirtualBackend::~VirtualBackend() { #if HAVE_GBM if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } #endif if (m_drmFd != -1) { close(m_drmFd); } } void VirtualBackend::init() { setSoftWareCursor(true); m_size = initialWindowSize(); setReady(true); waylandServer()->seat()->setHasPointer(true); waylandServer()->seat()->setHasKeyboard(true); waylandServer()->seat()->setHasTouch(true); emit screensQueried(); } QString VirtualBackend::screenshotDirPath() const { if (m_screenshotDir.isNull()) { return QString(); } return m_screenshotDir->path(); } Screens *VirtualBackend::createScreens(QObject *parent) { return new VirtualScreens(this, parent); } QPainterBackend *VirtualBackend::createQPainterBackend() { return new VirtualQPainterBackend(this); } OpenGLBackend *VirtualBackend::createOpenGLBackend() { return new EglGbmBackend(this); } +int VirtualBackend::gammaRampSize(int screen) const { + return m_gammaSizes[screen]; +} + +bool VirtualBackend::setGammaRamp(int screen, ColorCorrect::GammaRamp &gamma) { + Q_UNUSED(gamma); + return m_gammaResults[screen]; +} + } diff --git a/plugins/platforms/virtual/virtual_backend.h b/plugins/platforms/virtual/virtual_backend.h index ea19b4f5a..6c2eae152 100644 --- a/plugins/platforms/virtual/virtual_backend.h +++ b/plugins/platforms/virtual/virtual_backend.h @@ -1,107 +1,119 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_VIRTUAL_BACKEND_H #define KWIN_VIRTUAL_BACKEND_H #include "platform.h" #include #include #include class QTemporaryDir; struct gbm_device; namespace KWin { +namespace ColorCorrect { +class Manager; +struct GammaRamp; +} + class KWIN_EXPORT VirtualBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "virtual.json") Q_PROPERTY(QSize size READ size NOTIFY sizeChanged) public: VirtualBackend(QObject *parent = nullptr); virtual ~VirtualBackend(); void init() override; QSize size() const { return m_size; } int outputCount() const { return m_outputCount; } qreal outputScale() const { return m_outputScale; } bool saveFrames() const { return !m_screenshotDir.isNull(); } QString screenshotDirPath() const; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend* createQPainterBackend() override; OpenGLBackend *createOpenGLBackend() override; Q_INVOKABLE void setOutputCount(int count) { m_outputCount = count; + m_gammaSizes = QVector(count, 200); + m_gammaResults = QVector(count, true); } Q_INVOKABLE void setOutputScale(qreal scale) { m_outputScale = scale; } int drmFd() const { return m_drmFd; } void setDrmFd(int fd) { m_drmFd = fd; } gbm_device *gbmDevice() const { return m_gbmDevice; } void setGbmDevice(gbm_device *device) { m_gbmDevice = device; } + virtual int gammaRampSize(int screen) const override; + virtual bool setGammaRamp(int screen, ColorCorrect::GammaRamp &gamma) override; QVector supportedCompositors() const override { return QVector{OpenGLCompositing, QPainterCompositing}; } Q_SIGNALS: void sizeChanged(); void outputGeometriesChanged(const QVector &geometries); private: QSize m_size; int m_outputCount = 1; qreal m_outputScale = 1; QScopedPointer m_screenshotDir; int m_drmFd = -1; gbm_device *m_gbmDevice = nullptr; + + QVector m_gammaSizes = QVector(1, 200); + QVector m_gammaResults = QVector(1, true); }; } #endif diff --git a/plugins/platforms/wayland/wayland.json b/plugins/platforms/wayland/wayland.json index d1d70b555..03c1f2aff 100644 --- a/plugins/platforms/wayland/wayland.json +++ b/plugins/platforms/wayland/wayland.json @@ -1,49 +1,49 @@ { "KPlugin": { "Description": "Render to a nested window on running Wayland compositor.", "Description[ca@valencia]": "Renderitza a una finestra imbricada en un compositor Wayland en execució.", "Description[ca]": "Renderitza a una finestra imbricada en un compositor Wayland en execució.", "Description[da]": "Rendér til et indlejret vindue på kørende Wayland-compositor.", "Description[de]": "In ein eingebettetes Fenster auf dem laufenden Wayland-Kompositor rendern.", "Description[el]": "Αποτύπωση σε εμφωλευμένο παράθυρο κατά την εκτέλεση του συνθέτη Wayland.", "Description[es]": "Renderizar en una ventana anidada en el compositor Wayland en ejecución.", "Description[et]": "Renderdamine töötava Waylandi komposiitori pesastatud aknas.", "Description[eu]": "Errendatu Wayland konposatzailean habiaratutako leiho batera.", - "Description[fi]": "Renderöi sisäkkäiseen ikkunaan, jota hallitsee Wayland-koostin.", + "Description[fi]": "Hahmonna sisäkkäiseen ikkunaan, jota hallitsee Wayland-koostin.", "Description[fr]": "Rendre sur une fenêtre imbriquée sur un compositeur Wayland en cours de fonctionnement.", "Description[gl]": "Renderizar unha xanela aniñada no compositor de Wayland en execución.", "Description[hu]": "Renderelés egy Wayland kompozitoron futó beágyazott ablakba.", "Description[it]": "Resa in una finestra nidificata su compositore Wayland in esecuzione.", "Description[ko]": "Wayland 컴포지터에서 실행 중인 창에 렌더링합니다.", "Description[nl]": "Render naar een genest venster in een werkende Wayland-compositor.", "Description[nn]": "Teikn opp til innebygd vindauge på køyrande Wayland-samansetjar.", "Description[pl]": "Wyświetlaj w zagnieżdżonym oknie w kompozytorze Wayland.", "Description[pt]": "Desenhar numa janela encadeada no compositor de Wayland em execução.", "Description[pt_BR]": "Renderizar uma janela encadeada no compositor Wayland em execução.", "Description[sk]": "Renderovať na vnorené okno na bežiaci kompozítor Wayland.", "Description[sl]": "Izriši v gnezdeno okno na upravljalniku skladnje Wayland.", "Description[sr@ijekavian]": "Рендеровање у угнежђени прозор на вејланд слагачу.", "Description[sr@ijekavianlatin]": "Renderovanje u ugnežđeni prozor na Wayland slagaču.", "Description[sr@latin]": "Renderovanje u ugnežđeni prozor na Wayland slagaču.", "Description[sr]": "Рендеровање у угнежђени прозор на вејланд слагачу.", "Description[sv]": "Återge till ett nästlat fönster på Wayland-sammansättare som kör.", "Description[tr]": "Wayland birleştirici çalıştıran iç içe geçmiş bir pencerede gerçekle.", "Description[uk]": "Обробляти у вкладене вікно запущеного засобу композиції Wayland.", "Description[x-test]": "xxRender to a nested window on running Wayland compositor.xx", "Description[zh_CN]": "渲染到 Wayland 混成器上的嵌套窗口中", "Description[zh_TW]": "成像到執行中的 Wayland 的巢狀視窗。", "Id": "KWinWaylandWaylandBackend", "Name": "wayland", "Name[ca@valencia]": "Wayland", "Name[ca]": "Wayland", "Name[de]": "Wayland", "Name[pt]": "Wayland", "Name[sr@ijekavian]": "Вејланд", "Name[sr@ijekavianlatin]": "Wayland", "Name[sr@latin]": "Wayland", "Name[sr]": "Вејланд", "Name[sv]": "Wayland", "Name[x-test]": "xxwaylandxx" }, "input": true } diff --git a/plugins/platforms/x11/standalone/CMakeLists.txt b/plugins/platforms/x11/standalone/CMakeLists.txt index ebea1553d..a1ec49608 100644 --- a/plugins/platforms/x11/standalone/CMakeLists.txt +++ b/plugins/platforms/x11/standalone/CMakeLists.txt @@ -1,43 +1,43 @@ set(X11PLATFORM_SOURCES edge.cpp logging.cpp x11cursor.cpp x11_platform.cpp screens_xrandr.cpp windowselector.cpp overlaywindow_x11.cpp screenedges_filter.cpp non_composited_outline.cpp x11_decoration_renderer.cpp xfixes_cursor_event_filter.cpp effects_x11.cpp effects_mouse_interception_x11_filter.cpp sync_filter.cpp ) if(X11_Xinput_FOUND) set(X11PLATFORM_SOURCES ${X11PLATFORM_SOURCES} xinputintegration.cpp) endif() if(HAVE_EPOXY_GLX) set(X11PLATFORM_SOURCES ${X11PLATFORM_SOURCES} glxbackend.cpp glx_context_attribute_builder.cpp) endif() include_directories(${CMAKE_SOURCE_DIR}/platformsupport/scenes/opengl) add_library(KWinX11Platform MODULE ${X11PLATFORM_SOURCES}) -target_link_libraries(KWinX11Platform eglx11common kwin kwinxrenderutils SceneOpenGLBackend Qt5::X11Extras XCB::CURSOR) +target_link_libraries(KWinX11Platform eglx11common kwin kwinxrenderutils SceneOpenGLBackend Qt5::X11Extras XCB::CURSOR KF5::Crash) if(X11_Xinput_FOUND) target_link_libraries(KWinX11Platform ${X11_Xinput_LIB}) endif() if(HAVE_DL_LIBRARY) target_link_libraries(KWinX11Platform ${DL_LIBRARY}) endif() install( TARGETS KWinX11Platform DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kwin.platforms/ ) diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp index ff22ec719..a2c570e59 100644 --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -1,935 +1,936 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2012 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "glxbackend.h" #include "logging.h" #include "glx_context_attribute_builder.h" // kwin #include "options.h" #include "overlaywindow.h" #include "composite.h" #include "platform.h" #include "scene.h" #include "screens.h" #include "xcbutils.h" #include "texture.h" // kwin libs #include #include #include // Qt #include #include #include // system #include #include #include #if HAVE_DL_LIBRARY #include #endif #include #ifndef XCB_GLX_BUFFER_SWAP_COMPLETE #define XCB_GLX_BUFFER_SWAP_COMPLETE 1 typedef struct xcb_glx_buffer_swap_complete_event_t { uint8_t response_type; /**< */ uint8_t pad0; /**< */ uint16_t sequence; /**< */ uint16_t event_type; /**< */ uint8_t pad1[2]; /**< */ xcb_glx_drawable_t drawable; /**< */ uint32_t ust_hi; /**< */ uint32_t ust_lo; /**< */ uint32_t msc_hi; /**< */ uint32_t msc_lo; /**< */ uint32_t sbc; /**< */ } xcb_glx_buffer_swap_complete_event_t; #endif #include #include namespace KWin { SwapEventFilter::SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glxDrawable) : X11EventFilter(Xcb::Extensions::self()->glxEventBase() + XCB_GLX_BUFFER_SWAP_COMPLETE), m_drawable(drawable), m_glxDrawable(glxDrawable) { } bool SwapEventFilter::event(xcb_generic_event_t *event) { xcb_glx_buffer_swap_complete_event_t *ev = reinterpret_cast(event); // The drawable field is the X drawable when the event was synthesized // by a WireToEvent handler, and the GLX drawable when the event was // received over the wire if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) { Compositor::self()->bufferSwapComplete(); return true; } return false; } // ----------------------------------------------------------------------- GlxBackend::GlxBackend(Display *display) : OpenGLBackend() , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) , window(None) , fbconfig(NULL) , glxWindow(None) , ctx(nullptr) , m_bufferAge(0) , haveSwapInterval(false) , m_x11Display(display) { } static bool gs_tripleBufferUndetected = true; static bool gs_tripleBufferNeedsDetection = false; GlxBackend::~GlxBackend() { if (isFailed()) { m_overlayWindow->destroy(); } // TODO: cleanup in error case // do cleanup after initBuffer() cleanupGL(); doneCurrent(); gs_tripleBufferUndetected = true; gs_tripleBufferNeedsDetection = false; if (ctx) glXDestroyContext(display(), ctx); if (glxWindow) glXDestroyWindow(display(), glxWindow); if (window) XDestroyWindow(display(), window); qDeleteAll(m_fbconfigHash); m_fbconfigHash.clear(); overlayWindow()->destroy(); delete m_overlayWindow; } typedef void (*glXFuncPtr)(); static glXFuncPtr getProcAddress(const char* name) { glXFuncPtr ret = nullptr; #if HAVE_EPOXY_GLX ret = glXGetProcAddress((const GLubyte*) name); #endif #if HAVE_DL_LIBRARY if (ret == nullptr) ret = (glXFuncPtr) dlsym(RTLD_DEFAULT, name); #endif return ret; } glXSwapIntervalMESA_func glXSwapIntervalMESA; void GlxBackend::init() { // Require at least GLX 1.3 if (!checkVersion()) { setFailed(QStringLiteral("Requires at least GLX 1.3")); return; } initExtensions(); // resolve glXSwapIntervalMESA if available if (hasExtension(QByteArrayLiteral("GLX_MESA_swap_control"))) { glXSwapIntervalMESA = (glXSwapIntervalMESA_func) getProcAddress("glXSwapIntervalMESA"); } else { glXSwapIntervalMESA = nullptr; } initVisualDepthHashTable(); if (!initBuffer()) { setFailed(QStringLiteral("Could not initialize the buffer")); return; } if (!initRenderingContext()) { setFailed(QStringLiteral("Could not initialize rendering context")); return; } // Initialize OpenGL GLPlatform *glPlatform = GLPlatform::instance(); glPlatform->detect(GlxPlatformInterface); options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen glPlatform->printResults(); initGL(&getProcAddress); // Check whether certain features are supported m_haveMESACopySubBuffer = hasExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer")); m_haveMESASwapControl = hasExtension(QByteArrayLiteral("GLX_MESA_swap_control")); m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control")); m_haveSGISwapControl = hasExtension(QByteArrayLiteral("GLX_SGI_swap_control")); // only enable Intel swap event if env variable is set, see BUG 342582 m_haveINTELSwapEvent = hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1"); if (m_haveINTELSwapEvent) { m_swapEventFilter = std::make_unique(window, glxWindow); glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); } haveSwapInterval = m_haveMESASwapControl || m_haveEXTSwapControl || m_haveSGISwapControl; setSupportsBufferAge(false); if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); if (useBufferAge != "0") setSupportsBufferAge(true); } setSyncsToVBlank(false); setBlocksForRetrace(false); haveWaitSync = false; gs_tripleBufferNeedsDetection = false; m_swapProfiler.init(); const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage; if (wantSync && glXIsDirect(display(), ctx)) { if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable setSwapInterval(1); setSyncsToVBlank(true); const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); if (!tripleBuffer.isEmpty()) { setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); gs_tripleBufferUndetected = false; } gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; } else if (hasExtension(QByteArrayLiteral("GLX_SGI_video_sync"))) { unsigned int sync; if (glXGetVideoSyncSGI(&sync) == 0 && glXWaitVideoSyncSGI(1, 0, &sync) == 0) { setSyncsToVBlank(true); setBlocksForRetrace(true); haveWaitSync = true; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken"; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported"; } else { // disable v-sync (if possible) setSwapInterval(0); } if (glPlatform->isVirtualBox()) { // VirtualBox does not support glxQueryDrawable // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension // and the GLPlatform has not been initialized at the moment when initGLX() is called. glXQueryDrawable = NULL; } setIsDirectRendering(bool(glXIsDirect(display(), ctx))); qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); } bool GlxBackend::checkVersion() { int major, minor; glXQueryVersion(display(), &major, &minor); return kVersionNumber(major, minor) >= kVersionNumber(1, 3); } void GlxBackend::initExtensions() { const QByteArray string = (const char *) glXQueryExtensionsString(display(), QX11Info::appScreen()); setExtensions(string.split(' ')); } bool GlxBackend::initRenderingContext() { const bool direct = true; // Use glXCreateContextAttribsARB() when it's available if (hasExtension(QByteArrayLiteral("GLX_ARB_create_context"))) { const bool have_robustness = hasExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness")); const bool haveVideoMemoryPurge = hasExtension(QByteArrayLiteral("GLX_NV_robustness_video_memory_purge")); std::vector candidates; if (options->glCoreProfile()) { if (have_robustness) { if (haveVideoMemoryPurge) { GlxContextAttributeBuilder purgeMemoryCore; purgeMemoryCore.setVersion(3, 1); purgeMemoryCore.setRobust(true); purgeMemoryCore.setResetOnVideoMemoryPurge(true); candidates.emplace_back(std::move(purgeMemoryCore)); } GlxContextAttributeBuilder robustCore; robustCore.setVersion(3, 1); robustCore.setRobust(true); candidates.emplace_back(std::move(robustCore)); } GlxContextAttributeBuilder core; core.setVersion(3, 1); candidates.emplace_back(std::move(core)); } else { if (have_robustness) { if (haveVideoMemoryPurge) { GlxContextAttributeBuilder purgeMemoryLegacy; purgeMemoryLegacy.setRobust(true); purgeMemoryLegacy.setResetOnVideoMemoryPurge(true); candidates.emplace_back(std::move(purgeMemoryLegacy)); } GlxContextAttributeBuilder robustLegacy; robustLegacy.setRobust(true); candidates.emplace_back(std::move(robustLegacy)); } GlxContextAttributeBuilder legacy; legacy.setVersion(2, 1); candidates.emplace_back(std::move(legacy)); } for (auto it = candidates.begin(); it != candidates.end(); it++) { const auto attribs = it->build(); ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, true, attribs.data()); if (ctx) { qCDebug(KWIN_X11STANDALONE) << "Created GLX context with attributes:" << &(*it); break; } } } if (!ctx) ctx = glXCreateNewContext(display(), fbconfig, GLX_RGBA_TYPE, NULL, direct); if (!ctx) { qCDebug(KWIN_X11STANDALONE) << "Failed to create an OpenGL context."; return false; } if (!glXMakeCurrent(display(), glxWindow, ctx)) { qCDebug(KWIN_X11STANDALONE) << "Failed to make the OpenGL context current."; glXDestroyContext(display(), ctx); ctx = 0; return false; } return true; } bool GlxBackend::initBuffer() { if (!initFbConfig()) return false; if (overlayWindow()->create()) { xcb_connection_t * const c = connection(); // Try to create double-buffered window in the overlay xcb_visualid_t visual; glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, (int *) &visual); if (!visual) { qCCritical(KWIN_X11STANDALONE) << "The GLXFBConfig does not have an associated X visual"; return false; } xcb_colormap_t colormap = xcb_generate_id(c); xcb_create_colormap(c, false, colormap, rootWindow(), visual); const QSize size = screens()->size(); window = xcb_generate_id(c); xcb_create_window(c, visualDepth(visual), window, overlayWindow()->window(), 0, 0, size.width(), size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual, XCB_CW_COLORMAP, &colormap); glxWindow = glXCreateWindow(display(), fbconfig, window, NULL); overlayWindow()->setup(window); } else { qCCritical(KWIN_X11STANDALONE) << "Failed to create overlay window"; return false; } return true; } bool GlxBackend::initFbConfig() { const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_ALPHA_SIZE, 0, GLX_DEPTH_SIZE, 0, GLX_STENCIL_SIZE, 0, GLX_CONFIG_CAVEAT, GLX_NONE, GLX_DOUBLEBUFFER, true, 0 }; // Try to find a double buffered configuration int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); struct FBConfig { GLXFBConfig config; int depth; int stencil; }; std::deque candidates; for (int i = 0; i < count; i++) { int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); candidates.emplace_back(FBConfig{configs[i], depth, stencil}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { fbconfig = candidates.front().config; int fbconfig_id, visual_id, red, green, blue, alpha, depth, stencil; glXGetFBConfigAttrib(display(), fbconfig, GLX_FBCONFIG_ID, &fbconfig_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, &visual_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), fbconfig, GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), fbconfig, GLX_BLUE_SIZE, &blue); glXGetFBConfigAttrib(display(), fbconfig, GLX_ALPHA_SIZE, &alpha); glXGetFBConfigAttrib(display(), fbconfig, GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil); qCDebug(KWIN_X11STANDALONE, "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d", fbconfig_id, visual_id, visualDepth(visual_id), red, green, blue, alpha, depth, stencil); } if (fbconfig == nullptr) { qCCritical(KWIN_X11STANDALONE) << "Failed to find a usable framebuffer configuration"; return false; } return true; } void GlxBackend::initVisualDepthHashTable() { const xcb_setup_t *setup = xcb_get_setup(connection()); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) { const int len = xcb_depth_visuals_length(depth.data); const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data); for (int i = 0; i < len; i++) m_visualDepthHash.insert(visuals[i].visual_id, depth.data->depth); } } } int GlxBackend::visualDepth(xcb_visualid_t visual) const { return m_visualDepthHash.value(visual); } static inline int bitCount(uint32_t mask) { #if defined(__GNUC__) return __builtin_popcount(mask); #else int count = 0; while (mask) { count += (mask & 1); mask >>= 1; } return count; #endif } FBConfigInfo *GlxBackend::infoForVisual(xcb_visualid_t visual) { auto it = m_fbconfigHash.constFind(visual); if (it != m_fbconfigHash.constEnd()) { return it.value(); } FBConfigInfo *info = new FBConfigInfo; m_fbconfigHash.insert(visual, info); info->fbconfig = nullptr; info->bind_texture_format = 0; info->texture_targets = 0; info->y_inverted = 0; info->mipmap = 0; const xcb_render_pictformat_t format = XRenderUtils::findPictFormat(visual); const xcb_render_directformat_t *direct = XRenderUtils::findPictFormatInfo(format); if (!direct) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a picture format for visual 0x" << hex << visual; return info; } const int red_bits = bitCount(direct->red_mask); const int green_bits = bitCount(direct->green_mask); const int blue_bits = bitCount(direct->blue_mask); const int alpha_bits = bitCount(direct->alpha_mask); const int depth = visualDepth(visual); const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits); const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_X_RENDERABLE, True, GLX_CONFIG_CAVEAT, int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst + GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, int(GLX_DONT_CARE), // The ARGB32 visual is marked sRGB capable in mesa/i965 GLX_BUFFER_SIZE, red_bits + green_bits + blue_bits + alpha_bits, GLX_RED_SIZE, red_bits, GLX_GREEN_SIZE, green_bits, GLX_BLUE_SIZE, blue_bits, GLX_ALPHA_SIZE, alpha_bits, GLX_STENCIL_SIZE, 0, GLX_DEPTH_SIZE, 0, 0 }; int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); if (count < 1) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a framebuffer configuration for visual 0x" << hex << visual; return info; } struct FBConfig { GLXFBConfig config; int depth; int stencil; int format; }; std::deque candidates; for (int i = 0; i < count; i++) { int red, green, blue; glXGetFBConfigAttrib(display(), configs[i], GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), configs[i], GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), configs[i], GLX_BLUE_SIZE, &blue); if (std::tie(red, green, blue) != rgb_sizes) continue; xcb_visualid_t visual; glXGetFBConfigAttrib(display(), configs[i], GLX_VISUAL_ID, (int *) &visual); if (visualDepth(visual) != depth) continue; int bind_rgb, bind_rgba; glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba); glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &bind_rgb); if (!bind_rgb && !bind_rgba) continue; int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); int texture_format; if (alpha_bits) texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT; else texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT; candidates.emplace_back(FBConfig{configs[i], depth, stencil, texture_format}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { const FBConfig &candidate = candidates.front(); int y_inverted, texture_targets; glXGetFBConfigAttrib(display(), candidate.config, GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets); glXGetFBConfigAttrib(display(), candidate.config, GLX_Y_INVERTED_EXT, &y_inverted); info->fbconfig = candidate.config; info->bind_texture_format = candidate.format; info->texture_targets = texture_targets; info->y_inverted = y_inverted; info->mipmap = 0; } if (info->fbconfig) { int fbc_id = 0; int visual_id = 0; glXGetFBConfigAttrib(display(), info->fbconfig, GLX_FBCONFIG_ID, &fbc_id); glXGetFBConfigAttrib(display(), info->fbconfig, GLX_VISUAL_ID, &visual_id); qCDebug(KWIN_X11STANDALONE).nospace() << "Using FBConfig 0x" << hex << fbc_id << " for visual 0x" << hex << visual_id; } return info; } void GlxBackend::setSwapInterval(int interval) { if (m_haveEXTSwapControl) glXSwapIntervalEXT(display(), glxWindow, interval); else if (m_haveMESASwapControl) glXSwapIntervalMESA(interval); else if (m_haveSGISwapControl) glXSwapIntervalSGI(interval); } void GlxBackend::waitSync() { // NOTE that vsync has no effect with indirect rendering if (haveWaitSync) { uint sync; #if 0 // TODO: why precisely is this important? // the sync counter /can/ perform multiple steps during glXGetVideoSync & glXWaitVideoSync // but this only leads to waiting for two frames??!? glXGetVideoSync(&sync); glXWaitVideoSync(2, (sync + 1) % 2, &sync); #else glXWaitVideoSyncSGI(1, 0, &sync); #endif } } void GlxBackend::present() { if (lastDamage().isEmpty()) return; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); if (fullRepaint) { if (m_haveINTELSwapEvent) Compositor::self()->aboutToSwapBuffers(); if (haveSwapInterval) { if (gs_tripleBufferNeedsDetection) { glXWaitGL(); m_swapProfiler.begin(); } glXSwapBuffers(display(), glxWindow); if (gs_tripleBufferNeedsDetection) { glXWaitGL(); if (char result = m_swapProfiler.end()) { gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; if (result == 'd' && GLPlatform::instance()->driver() == Driver_NVidia) { // TODO this is a workaround, we should get __GL_YIELD set before libGL checks it if (qstrcmp(qgetenv("__GL_YIELD"), "USLEEP")) { options->setGlPreferBufferSwap(0); setSwapInterval(0); result = 0; // hint proper behavior qCWarning(KWIN_X11STANDALONE) << "\nIt seems you are using the nvidia driver without triple buffering\n" "You must export __GL_YIELD=\"USLEEP\" to prevent large CPU overhead on synced swaps\n" "Preferably, enable the TripleBuffer Option in the xorg.conf Device\n" "For this reason, the tearing prevention has been disabled.\n" "See https://bugs.kde.org/show_bug.cgi?id=322060\n"; } } setBlocksForRetrace(result == 'd'); } } else if (blocksForRetrace()) { // at least the nvidia blob manages to swap async, ie. return immediately on double // buffering - what messes our timing calculation and leads to laggy behavior #346275 glXWaitGL(); } } else { waitSync(); glXSwapBuffers(display(), glxWindow); } if (supportsBufferAge()) { glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); } } else if (m_haveMESACopySubBuffer) { foreach (const QRect & r, lastDamage().rects()) { // convert to OpenGL coordinates int y = screenSize.height() - r.y() - r.height(); glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); } } else { // Copy Pixels (horribly slow on Mesa) glDrawBuffer(GL_FRONT); copyPixels(lastDamage()); glDrawBuffer(GL_BACK); } setLastDamage(QRegion()); if (!supportsBufferAge()) { glXWaitGL(); XFlush(display()); } } void GlxBackend::screenGeometryChanged(const QSize &size) { doneCurrent(); XMoveResizeWindow(display(), window, 0, 0, size.width(), size.height()); overlayWindow()->setup(window); Xcb::sync(); makeCurrent(); glViewport(0, 0, size.width(), size.height()); // The back buffer contents are now undefined m_bufferAge = 0; } SceneOpenGLTexturePrivate *GlxBackend::createBackendTexture(SceneOpenGLTexture *texture) { return new GlxTexture(texture, this); } QRegion GlxBackend::prepareRenderingFrame() { QRegion repaint; if (gs_tripleBufferNeedsDetection) { // the composite timer floors the repaint frequency. This can pollute our triple buffering // detection because the glXSwapBuffers call for the new frame has to wait until the pending // one scanned out. // So we compensate for that by waiting an extra milisecond to give the driver the chance to // fllush the buffer queue usleep(1000); } present(); if (supportsBufferAge()) repaint = accumulatedDamageHistory(m_bufferAge); startRenderTimer(); glXWaitX(); return repaint; } void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { if (damagedRegion.isEmpty()) { setLastDamage(QRegion()); // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. if (!renderedRegion.isEmpty()) glFlush(); m_bufferAge = 1; return; } setLastDamage(renderedRegion); if (!blocksForRetrace()) { // This also sets lastDamage to empty which prevents the frame from // being posted again when prepareRenderingFrame() is called. present(); } else { // Make sure that the GPU begins processing the command stream // now and not the next time prepareRenderingFrame() is called. glFlush(); } if (overlayWindow()->window()) // show the window only after the first pass, overlayWindow()->show(); // since that pass may take long // Save the damaged region to history if (supportsBufferAge()) addToDamageHistory(damagedRegion); } bool GlxBackend::makeCurrent() { if (QOpenGLContext *context = QOpenGLContext::currentContext()) { // Workaround to tell Qt that no QOpenGLContext is current context->doneCurrent(); } const bool current = glXMakeCurrent(display(), glxWindow, ctx); return current; } void GlxBackend::doneCurrent() { glXMakeCurrent(display(), None, nullptr); } OverlayWindow* GlxBackend::overlayWindow() { return m_overlayWindow; } bool GlxBackend::usesOverlayWindow() const { return true; } /******************************************************** * GlxTexture *******************************************************/ GlxTexture::GlxTexture(SceneOpenGLTexture *texture, GlxBackend *backend) : SceneOpenGLTexturePrivate() , q(texture) , m_backend(backend) , m_glxpixmap(None) { } GlxTexture::~GlxTexture() { if (m_glxpixmap != None) { if (!options->isGlStrictBinding()) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); } glXDestroyPixmap(display(), m_glxpixmap); m_glxpixmap = None; } } void GlxTexture::onDamage() { if (options->isGlStrictBinding() && m_glxpixmap) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, NULL); } GLTexturePrivate::onDamage(); } bool GlxTexture::loadTexture(xcb_pixmap_t pixmap, const QSize &size, xcb_visualid_t visual) { if (pixmap == XCB_NONE || size.isEmpty() || visual == XCB_NONE) return false; const FBConfigInfo *info = m_backend->infoForVisual(visual); if (!info || info->fbconfig == nullptr) return false; if (info->texture_targets & GLX_TEXTURE_2D_BIT_EXT) { m_target = GL_TEXTURE_2D; m_scale.setWidth(1.0f / m_size.width()); m_scale.setHeight(1.0f / m_size.height()); } else { assert(info->texture_targets & GLX_TEXTURE_RECTANGLE_BIT_EXT); m_target = GL_TEXTURE_RECTANGLE; m_scale.setWidth(1.0f); m_scale.setHeight(1.0f); } const int attrs[] = { GLX_TEXTURE_FORMAT_EXT, info->bind_texture_format, GLX_MIPMAP_TEXTURE_EXT, false, GLX_TEXTURE_TARGET_EXT, m_target == GL_TEXTURE_2D ? GLX_TEXTURE_2D_EXT : GLX_TEXTURE_RECTANGLE_EXT, 0 }; m_glxpixmap = glXCreatePixmap(display(), info->fbconfig, pixmap, attrs); m_size = size; m_yInverted = info->y_inverted ? true : false; m_canUseMipmaps = false; glGenTextures(1, &m_texture); q->setDirty(); q->setFilter(GL_NEAREST); glBindTexture(m_target, m_texture); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr); updateMatrix(); return true; } bool GlxTexture::loadTexture(WindowPixmap *pixmap) { Toplevel *t = pixmap->toplevel(); return loadTexture(pixmap->pixmap(), t->size(), t->visual()); } OpenGLBackend *GlxTexture::backend() { return m_backend; } } // namespace diff --git a/plugins/platforms/x11/standalone/x11_platform.cpp b/plugins/platforms/x11/standalone/x11_platform.cpp index 11af70ff1..806d8044d 100644 --- a/plugins/platforms/x11/standalone/x11_platform.cpp +++ b/plugins/platforms/x11/standalone/x11_platform.cpp @@ -1,434 +1,433 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "x11_platform.h" #include "x11cursor.h" #include "edge.h" #include "sync_filter.h" #include "windowselector.h" #include #include #if HAVE_EPOXY_GLX #include "glxbackend.h" #endif #if HAVE_X11_XINPUT #include "xinputintegration.h" #endif #include "abstract_client.h" #include "effects_x11.h" #include "eglonxbackend.h" #include "keyboard_input.h" #include "logging.h" #include "screens_xrandr.h" #include "screenedges_filter.h" #include "options.h" #include "overlaywindow_x11.h" #include "non_composited_outline.h" #include "workspace.h" #include "x11_decoration_renderer.h" #include #include #include +#include #include #include #include namespace KWin { X11StandalonePlatform::X11StandalonePlatform(QObject *parent) : Platform(parent) , m_x11Display(QX11Info::display()) { #if HAVE_X11_XINPUT if (!qEnvironmentVariableIsSet("KWIN_NO_XI2")) { m_xinputIntegration = new XInputIntegration(m_x11Display, this); m_xinputIntegration->init(); if (!m_xinputIntegration->hasXinput()) { delete m_xinputIntegration; m_xinputIntegration = nullptr; } else { connect(kwinApp(), &Application::workspaceCreated, m_xinputIntegration, &XInputIntegration::startListening); } } #endif connect(kwinApp(), &Application::workspaceCreated, this, [this] { if (Xcb::Extensions::self()->isSyncAvailable()) { m_syncFilter = std::make_unique(); } } ); } X11StandalonePlatform::~X11StandalonePlatform() { if (m_openGLFreezeProtectionThread) { m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); delete m_openGLFreezeProtectionThread; } - XRenderUtils::cleanup(); + if (isReady()) { + XRenderUtils::cleanup(); + } } void X11StandalonePlatform::init() { if (!QX11Info::isPlatformX11()) { emit initFailed(); return; } XRenderUtils::init(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); setReady(true); emit screensQueried(); } Screens *X11StandalonePlatform::createScreens(QObject *parent) { return new XRandRScreens(parent); } OpenGLBackend *X11StandalonePlatform::createOpenGLBackend() { switch (options->glPlatformInterface()) { #if HAVE_EPOXY_GLX case GlxPlatformInterface: if (hasGlx()) { return new GlxBackend(m_x11Display); } else { qCWarning(KWIN_X11STANDALONE) << "Glx not available, trying EGL instead."; // no break, needs fall-through -#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); -#endif } #endif case EglPlatformInterface: return new EglOnXBackend(m_x11Display); default: // no backend available return nullptr; } } Edge *X11StandalonePlatform::createScreenEdge(ScreenEdges *edges) { if (m_screenEdgesFilter.isNull()) { m_screenEdgesFilter.reset(new ScreenEdgesFilter); } return new WindowBasedEdge(edges); } void X11StandalonePlatform::createPlatformCursor(QObject *parent) { auto c = new X11Cursor(parent, m_xinputIntegration != nullptr); #if HAVE_X11_XINPUT if (m_xinputIntegration) { m_xinputIntegration->setCursor(c); // we know we have xkb already auto xkb = input()->keyboard()->xkb(); xkb->reconfigure(); } #endif } bool X11StandalonePlatform::requiresCompositing() const { return false; } bool X11StandalonePlatform::openGLCompositingIsBroken() const { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); return KConfigGroup(kwinApp()->config(), "Compositing").readEntry(unsafeKey, false); } QString X11StandalonePlatform::compositingNotPossibleReason() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return i18n("OpenGL compositing (the default) has crashed KWin in the past.
" "This was most likely due to a driver bug." "

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

" "

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

"); if (!Xcb::Extensions::self()->isCompositeAvailable() || !Xcb::Extensions::self()->isDamageAvailable()) { return i18n("Required X extensions (XComposite and XDamage) are not available."); } #if !defined( KWIN_HAVE_XRENDER_COMPOSITING ) if (!hasGlx()) return i18n("GLX/OpenGL are not available and only OpenGL support is compiled."); #else if (!(hasGlx() || (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()))) { return i18n("GLX/OpenGL and XRender/XFixes are not available."); } #endif return QString(); } bool X11StandalonePlatform::compositingPossible() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return false; if (!Xcb::Extensions::self()->isCompositeAvailable()) { qCDebug(KWIN_CORE) << "No composite extension available"; return false; } if (!Xcb::Extensions::self()->isDamageAvailable()) { qCDebug(KWIN_CORE) << "No damage extension available"; return false; } if (hasGlx()) return true; #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()) return true; #endif if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { return true; } else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { return true; } qCDebug(KWIN_CORE) << "No OpenGL or XRender/XFixes support"; return false; } bool X11StandalonePlatform::hasGlx() { return Xcb::Extensions::self()->hasGlx(); } void X11StandalonePlatform::createOpenGLSafePoint(OpenGLSafePoint safePoint) { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); auto group = KConfigGroup(kwinApp()->config(), "Compositing"); switch (safePoint) { case OpenGLSafePoint::PreInit: group.writeEntry(unsafeKey, true); group.sync(); // Deliberately continue with PreFrame -#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); -#endif case OpenGLSafePoint::PreFrame: if (m_openGLFreezeProtectionThread == nullptr) { Q_ASSERT(m_openGLFreezeProtection == nullptr); m_openGLFreezeProtectionThread = new QThread(this); m_openGLFreezeProtectionThread->setObjectName("FreezeDetector"); m_openGLFreezeProtectionThread->start(); m_openGLFreezeProtection = new QTimer; m_openGLFreezeProtection->setInterval(15000); m_openGLFreezeProtection->setSingleShot(true); m_openGLFreezeProtection->start(); + const QString configName = kwinApp()->config()->name(); m_openGLFreezeProtection->moveToThread(m_openGLFreezeProtectionThread); connect(m_openGLFreezeProtection, &QTimer::timeout, m_openGLFreezeProtection, - [] { + [configName] { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); - auto group = KConfigGroup(kwinApp()->config(), "Compositing"); + auto group = KConfigGroup(KSharedConfig::openConfig(configName), "Compositing"); group.writeEntry(unsafeKey, true); group.sync(); + KCrash::setDrKonqiEnabled(false); qFatal("Freeze in OpenGL initialization detected"); }, Qt::DirectConnection); } else { Q_ASSERT(m_openGLFreezeProtection); QMetaObject::invokeMethod(m_openGLFreezeProtection, "start", Qt::QueuedConnection); } break; case OpenGLSafePoint::PostInit: group.writeEntry(unsafeKey, false); group.sync(); // Deliberately continue with PostFrame -#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); -#endif case OpenGLSafePoint::PostFrame: QMetaObject::invokeMethod(m_openGLFreezeProtection, "stop", Qt::QueuedConnection); break; case OpenGLSafePoint::PostLastGuardedFrame: m_openGLFreezeProtection->deleteLater(); m_openGLFreezeProtection = nullptr; m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); delete m_openGLFreezeProtectionThread; m_openGLFreezeProtectionThread = nullptr; break; } } PlatformCursorImage X11StandalonePlatform::cursorImage() const { auto c = kwinApp()->x11Connection(); QScopedPointer cursor( xcb_xfixes_get_cursor_image_reply(c, xcb_xfixes_get_cursor_image_unchecked(c), nullptr)); if (cursor.isNull()) { return PlatformCursorImage(); } QImage qcursorimg((uchar *) xcb_xfixes_get_cursor_image_cursor_image(cursor.data()), cursor->width, cursor->height, QImage::Format_ARGB32_Premultiplied); // deep copy of image as the data is going to be freed return PlatformCursorImage(qcursorimg.copy(), QPoint(cursor->xhot, cursor->yhot)); } void X11StandalonePlatform::doHideCursor() { xcb_xfixes_hide_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } void X11StandalonePlatform::doShowCursor() { xcb_xfixes_show_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } void X11StandalonePlatform::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (m_windowSelector.isNull()) { m_windowSelector.reset(new WindowSelector); } m_windowSelector->start(callback, cursorName); } void X11StandalonePlatform::setupActionForGlobalAccel(QAction *action) { connect(action, &QAction::triggered, kwinApp(), [action] { QVariant timestamp = action->property("org.kde.kglobalaccel.activationTimestamp"); bool ok = false; const quint32 t = timestamp.toULongLong(&ok); if (ok) { kwinApp()->setX11Time(t); } }); } OverlayWindow *X11StandalonePlatform::createOverlayWindow() { return new OverlayWindowX11(); } /* Updates xTime(). This used to simply fetch current timestamp from the server, but that can cause xTime() to be newer than timestamp of events that are still in our events queue, thus e.g. making XSetInputFocus() caused by such event to be ignored. Therefore events queue is searched for first event with timestamp, and extra PropertyNotify is generated in order to make sure such event is found. */ void X11StandalonePlatform::updateXTime() { // NOTE: QX11Info::getTimestamp does not yet search the event queue as the old // solution did. This means there might be regressions currently. See the // documentation above on how it should be done properly. kwinApp()->setX11Time(QX11Info::getTimestamp(), Application::TimestampUpdate::Always); } OutlineVisual *X11StandalonePlatform::createOutline(Outline *outline) { // first try composited Outline auto ret = Platform::createOutline(outline); if (!ret) { ret = new NonCompositedOutlineVisual(outline); } return ret; } Decoration::Renderer *X11StandalonePlatform::createDecorationRenderer(Decoration::DecoratedClientImpl *client) { auto renderer = Platform::createDecorationRenderer(client); if (!renderer) { renderer = new Decoration::X11Renderer(client); } return renderer; } void X11StandalonePlatform::invertScreen() { using namespace Xcb::RandR; bool succeeded = false; if (Xcb::Extensions::self()->isRandrAvailable()) { const auto active_client = workspace()->activeClient(); ScreenResources res((active_client && active_client->window() != XCB_WINDOW_NONE) ? active_client->window() : rootWindow()); if (!res.isNull()) { for (int j = 0; j < res->num_crtcs; ++j) { auto crtc = res.crtcs()[j]; CrtcGamma gamma(crtc); if (gamma.isNull()) { continue; } if (gamma->size) { qCDebug(KWIN_CORE) << "inverting screen using xcb_randr_set_crtc_gamma"; const int half = gamma->size / 2 + 1; uint16_t *red = gamma.red(); uint16_t *green = gamma.green(); uint16_t *blue = gamma.blue(); for (int i = 0; i < half; ++i) { auto invert = [&gamma, i](uint16_t *ramp) { qSwap(ramp[i], ramp[gamma->size - 1 - i]); }; invert(red); invert(green); invert(blue); } xcb_randr_set_crtc_gamma(connection(), crtc, gamma->size, red, green, blue); succeeded = true; } } } } if (!succeeded) { Platform::invertScreen(); } } void X11StandalonePlatform::createEffectsHandler(Compositor *compositor, Scene *scene) { new EffectsHandlerImplX11(compositor, scene); } QVector X11StandalonePlatform::supportedCompositors() const { QVector compositors; #if HAVE_EPOXY_GLX compositors << OpenGLCompositing; #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING compositors << XRenderCompositing; #endif compositors << NoCompositing; return compositors; } } diff --git a/plugins/platforms/x11/windowed/egl_x11_backend.cpp b/plugins/platforms/x11/windowed/egl_x11_backend.cpp index d0e08525f..c9e649a71 100644 --- a/plugins/platforms/x11/windowed/egl_x11_backend.cpp +++ b/plugins/platforms/x11/windowed/egl_x11_backend.cpp @@ -1,121 +1,121 @@ /******************************************************************** 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_x11_backend.h" // kwin #include "screens.h" #include "x11windowed_backend.h" // kwin libs #include namespace KWin { EglX11Backend::EglX11Backend(X11WindowedBackend *backend) : EglOnXBackend(backend->connection(), backend->display(), backend->rootWindow(), backend->screenNumer(), XCB_WINDOW_NONE) , m_backend(backend) { setX11TextureFromPixmapSupported(false); } EglX11Backend::~EglX11Backend() = default; void EglX11Backend::cleanupSurfaces() { for (auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) { eglDestroySurface(eglDisplay(), *it); } } bool EglX11Backend::createSurfaces() { for (int i = 0; i < screens()->count(); ++i) { EGLSurface s = createSurface(m_backend->windowForScreen(i)); if (s == EGL_NO_SURFACE) { return false; } m_surfaces << s; } if (m_surfaces.isEmpty()) { return false; } setSurface(m_surfaces.first()); return true; } void EglX11Backend::present() { for (int i = 0; i < screens()->count(); ++i) { EGLSurface s = m_surfaces.at(i); makeContextCurrent(s); setupViewport(i); presentSurface(s, screens()->geometry(i), screens()->geometry(i)); } eglWaitGL(); xcb_flush(m_backend->connection()); } QRegion EglX11Backend::prepareRenderingFrame() { startRenderTimer(); return QRegion(); } void EglX11Backend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { Q_UNUSED(renderedRegion) Q_UNUSED(damagedRegion) } bool EglX11Backend::usesOverlayWindow() const { return false; } bool EglX11Backend::perScreenRendering() const { return true; } QRegion EglX11Backend::prepareRenderingForScreen(int screenId) { makeContextCurrent(m_surfaces.at(screenId)); setupViewport(screenId); return screens()->geometry(screenId); } void EglX11Backend::setupViewport(int screenId) { // TODO: ensure the viewport is set correctly each time const QSize &overall = screens()->size(); const QRect &v = screens()->geometry(screenId); // TODO: are the values correct? qreal scale = screens()->scale(screenId); - glViewport(-v.x(), v.height() - overall.height() - v.y(), overall.width() * scale, overall.height() * scale); + glViewport(-v.x(), v.height() - overall.height() + v.y(), overall.width() * scale, overall.height() * scale); } void EglX11Backend::endRenderingFrameForScreen(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion) { Q_UNUSED(damagedRegion) const QRect &outputGeometry = screens()->geometry(screenId); presentSurface(m_surfaces.at(screenId), renderedRegion, outputGeometry); } } // namespace diff --git a/plugins/platforms/x11/windowed/x11.json b/plugins/platforms/x11/windowed/x11.json index 5b706897d..ef901fa0b 100644 --- a/plugins/platforms/x11/windowed/x11.json +++ b/plugins/platforms/x11/windowed/x11.json @@ -1,51 +1,51 @@ { "KPlugin": { "Description": "Render to a nested window on X11 windowing system.", "Description[ca@valencia]": "Renderitza a una finestra imbricada en el sistema de finestres X11.", "Description[ca]": "Renderitza a una finestra imbricada en el sistema de finestres X11.", "Description[da]": "Rendér til et indlejret vindue på X11-vinduessystemet.", "Description[de]": "In ein eingebettetes Fenster auf X11-Fenstersystemen rendern.", "Description[el]": "Αποτύπωση σε εμφωλευμένο παράθυρο σε παραθυρικό σύστημα X11.", "Description[es]": "Renderizar en una ventana anidada en el sistema de ventanas X11.", "Description[et]": "Pesastatud akna renderdamine X11 aknasüsteemis.", "Description[eu]": "Errendatu X11 leiho sistemaren leiho habiaratu batera.", - "Description[fi]": "Renderöi sisäkkäiseen ikkunaan, joka on X11-ikkunointijärjestelmässä.", + "Description[fi]": "Hahmonna X11-ikkunointijärjestelmän sisäkkäiseen ikkunaan.", "Description[fr]": "Rendre sur une fenêtre imbriquée sur le système de fenêtrage X11.", "Description[gl]": "Renderizar unha xanela aniñada no sistema de xanelas X11.", "Description[hu]": "Renderelés egy X11 ablakkezelő rendszeren futó beágyazott ablakba.", "Description[it]": "Resa in una finestra nidificata su sistema di finestre X11.", "Description[ko]": "X11 창 시스템에서 실행 중인 창에 렌더링합니다.", "Description[nl]": "Render naar een genest venster in het X11 windowingsysteem.", "Description[nn]": "Teikn opp til innebygd vindauge på køyrande X11-system.", "Description[pl]": "Wyświetlaj w zagnieżdżonym oknie w systemie okien X11.", "Description[pt]": "Desenhar numa janela encadeada no sistema de janelas X11.", "Description[pt_BR]": "Renderizar uma janela encadeada no sistema de janelas X11.", "Description[ru]": "Отрисовка во вложенном окне оконной системы X11.", "Description[sk]": "Renderovať na vnorené okno na systém okien X11.", "Description[sl]": "Izriši v gnezdeno okno na okenskem sistemu X11.", "Description[sr@ijekavian]": "Рендеровање у угнежђени прозор на прозорском систему Икс11.", "Description[sr@ijekavianlatin]": "Renderovanje u ugnežđeni prozor na prozorskom sistemu X11.", "Description[sr@latin]": "Renderovanje u ugnežđeni prozor na prozorskom sistemu X11.", "Description[sr]": "Рендеровање у угнежђени прозор на прозорском систему Икс11.", "Description[sv]": "Återge till ett nästlat fönster på X11-fönstersystem.", "Description[tr]": "X11 pencere sisteminde iç içe geçmiş bir pencereye gerçekle.", "Description[uk]": "Обробляти у вкладене вікно системи керування вікнами X11.", "Description[x-test]": "xxRender to a nested window on X11 windowing system.xx", "Description[zh_CN]": "渲染到 X11 窗口系统上的嵌套窗口中", "Description[zh_TW]": "成像到 X11 視窗系統的巢狀視窗。", "Id": "KWinWaylandX11Backend", "Name": "x11", "Name[ca@valencia]": "X11", "Name[ca]": "X11", "Name[de]": "X11", "Name[ia]": "x", "Name[pt]": "X11", "Name[sr@ijekavian]": "Икс11", "Name[sr@ijekavianlatin]": "X11", "Name[sr@latin]": "X11", "Name[sr]": "Икс11", "Name[sv]": "X11", "Name[x-test]": "xxx11xx" }, "input": true } diff --git a/plugins/qpa/integration.cpp b/plugins/qpa/integration.cpp index dfde2d1dd..c59da310c 100644 --- a/plugins/qpa/integration.cpp +++ b/plugins/qpa/integration.cpp @@ -1,294 +1,288 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #define WL_EGL_PLATFORM 1 #include "integration.h" #include "platform.h" #include "backingstore.h" #include "nativeinterface.h" #include "platformcontextwayland.h" #include "screen.h" #include "sharingplatformcontext.h" #include "window.h" #include "../../virtualkeyboard.h" #include "../../main.h" #include "../../screens.h" #include "../../wayland_server.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#if QT_VERSION >= 0x050800 #include #include #include -#else -#include -#include -#include -#endif namespace KWin { namespace QPA { Integration::Integration() : QObject() , QPlatformIntegration() , m_fontDb(new QGenericUnixFontDatabase()) , m_nativeInterface(new NativeInterface(this)) , m_inputContext() { } Integration::~Integration() = default; bool Integration::hasCapability(Capability cap) const { switch (cap) { case ThreadedPixmaps: return true; case OpenGL: return true; case ThreadedOpenGL: return false; case BufferQueueingOpenGL: return false; case MultipleWindows: case NonFullScreenWindows: return true; case RasterGLSurface: return false; default: return QPlatformIntegration::hasCapability(cap); } } void Integration::initialize() { connect(kwinApp(), &Application::screensCreated, this, [this] { connect(screens(), &Screens::changed, this, &Integration::initScreens); initScreens(); } ); QPlatformIntegration::initialize(); auto dummyScreen = new Screen(-1); screenAdded(dummyScreen); m_screens << dummyScreen; m_inputContext.reset(QPlatformInputContextFactory::create(QStringLiteral("qtvirtualkeyboard"))); qunsetenv("QT_IM_MODULE"); if (!m_inputContext.isNull()) { connect(qApp, &QGuiApplication::focusObjectChanged, this, [this] { if (VirtualKeyboard::self() && qApp->focusObject() != VirtualKeyboard::self()) { m_inputContext->setFocusObject(VirtualKeyboard::self()); } } ); connect(kwinApp(), &Application::workspaceCreated, this, [this] { if (VirtualKeyboard::self()) { m_inputContext->setFocusObject(VirtualKeyboard::self()); } } ); connect(qApp->inputMethod(), &QInputMethod::visibleChanged, this, [this] { if (qApp->inputMethod()->isVisible()) { if (QWindow *w = VirtualKeyboard::self()->inputPanel()) { QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason); } } } ); } } QAbstractEventDispatcher *Integration::createEventDispatcher() const { return new QUnixEventDispatcherQPA; } QPlatformBackingStore *Integration::createPlatformBackingStore(QWindow *window) const { auto registry = waylandServer()->internalClientRegistry(); const auto shm = registry->interface(KWayland::Client::Registry::Interface::Shm); if (shm.name == 0u) { return nullptr; } return new BackingStore(window, registry->createShmPool(shm.name, shm.version, window)); } QPlatformWindow *Integration::createPlatformWindow(QWindow *window) const { auto c = compositor(); auto s = shell(); if (!s || !c) { return new QPlatformWindow(window); } else { // don't set window as parent, cause infinite recursion in PlasmaQuick::Dialog auto surface = c->createSurface(c); return new Window(window, surface, s->createSurface(surface, surface), this); } } QPlatformFontDatabase *Integration::fontDatabase() const { return m_fontDb; } QPlatformTheme *Integration::createPlatformTheme(const QString &name) const { return QGenericUnixTheme::createUnixTheme(name); } QStringList Integration::themeNames() const { if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { return QStringList({QStringLiteral("kde")}); } return QStringList({QLatin1String(QGenericUnixTheme::name)}); } QPlatformNativeInterface *Integration::nativeInterface() const { return m_nativeInterface; } QPlatformOpenGLContext *Integration::createPlatformOpenGLContext(QOpenGLContext *context) const { if (kwinApp()->platform()->supportsQpaContext()) { return new SharingPlatformContext(context); } if (kwinApp()->platform()->sceneEglDisplay() != EGL_NO_DISPLAY) { auto s = kwinApp()->platform()->sceneEglSurface(); if (s != EGL_NO_SURFACE) { // try a SharingPlatformContext with a created surface return new SharingPlatformContext(context, s, kwinApp()->platform()->sceneEglConfig()); } } if (m_eglDisplay == EGL_NO_DISPLAY) { const_cast(this)->initEgl(); } if (m_eglDisplay == EGL_NO_DISPLAY) { return nullptr; } return new PlatformContextWayland(context, const_cast(this)); } void Integration::initScreens() { QVector newScreens; for (int i = 0; i < screens()->count(); i++) { auto screen = new Screen(i); screenAdded(screen); newScreens << screen; } while (!m_screens.isEmpty()) { destroyScreen(m_screens.takeLast()); } m_screens = newScreens; } KWayland::Client::Compositor *Integration::compositor() const { if (!m_compositor) { using namespace KWayland::Client; auto registry = waylandServer()->internalClientRegistry(); const auto c = registry->interface(Registry::Interface::Compositor); if (c.name != 0u) { const_cast(this)->m_compositor = registry->createCompositor(c.name, c.version, registry); } } return m_compositor; } KWayland::Client::Shell *Integration::shell() const { if (!m_shell) { using namespace KWayland::Client; auto registry = waylandServer()->internalClientRegistry(); const auto s = registry->interface(Registry::Interface::Shell); if (s.name != 0u) { const_cast(this)->m_shell = registry->createShell(s.name, s.version, registry); } } return m_shell; } EGLDisplay Integration::eglDisplay() const { return m_eglDisplay; } void Integration::initEgl() { Q_ASSERT(m_eglDisplay == EGL_NO_DISPLAY); // This variant uses Wayland as the EGL platform qputenv("EGL_PLATFORM", "wayland"); m_eglDisplay = eglGetDisplay(waylandServer()->internalClientConection()->display()); if (m_eglDisplay == EGL_NO_DISPLAY) { return; } // call eglInitialize in a thread to not block QFuture future = QtConcurrent::run([this] () -> bool { EGLint major, minor; if (eglInitialize(m_eglDisplay, &major, &minor) == EGL_FALSE) { return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { return false; } return true; }); // TODO: make this better while (!future.isFinished()) { waylandServer()->internalClientConection()->flush(); QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } if (!future.result()) { eglTerminate(m_eglDisplay); m_eglDisplay = EGL_NO_DISPLAY; } } QPlatformInputContext *Integration::inputContext() const { return m_inputContext.data(); } } } diff --git a/plugins/scenes/opengl/CMakeLists.txt b/plugins/scenes/opengl/CMakeLists.txt index 47684dfe0..a01a26533 100644 --- a/plugins/scenes/opengl/CMakeLists.txt +++ b/plugins/scenes/opengl/CMakeLists.txt @@ -1,26 +1,27 @@ set(SCENE_OPENGL_SRCS scene_opengl.cpp) include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category( SCENE_OPENGL_SRCS HEADER logging.h IDENTIFIER KWIN_OPENGL CATEGORY_NAME kwin_scene_opengl DEFAULT_SEVERITY Critical ) add_library(KWinSceneOpenGL MODULE scene_opengl.cpp) +set_target_properties(KWinSceneOpenGL PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kwin.scenes/") target_link_libraries(KWinSceneOpenGL kwin SceneOpenGLBackend ) install( TARGETS KWinSceneOpenGL DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kwin.scenes/ ) diff --git a/plugins/scenes/opengl/opengl.json b/plugins/scenes/opengl/opengl.json index 531019c9e..d0bb2131e 100644 --- a/plugins/scenes/opengl/opengl.json +++ b/plugins/scenes/opengl/opengl.json @@ -1,30 +1,39 @@ { "CompositingType": 1, "KPlugin": { "Description": "KWin Compositor plugin rendering through OpenGL", "Description[ca@valencia]": "Connector del Compositor del KWin que renderitza a través de l'OpenGL", "Description[ca]": "Connector del Compositor del KWin que renderitza a través de l'OpenGL", + "Description[de]": "KWin-Compositor-Modul zum Rendern mit OpenGL", "Description[es]": "Complemento compositor de KWin renderizando mediante OpenGL", + "Description[fi]": "OpenGL:llä hahmontava KWin-koostajaliitännäinen", + "Description[fr]": "Module du compositeur KWin effectuant le rendu avec OpenGL", + "Description[gl]": "Complemento de compositor de KWin que renderiza a través de OpenGL.", "Description[it]": "Estensione del compositore di KWin per la resa tramite OpenGL", + "Description[ko]": "OpenGL로 렌더링하는 KWin 컴포지터 플러그인", "Description[nl]": "KWin-compositor-plug-in rendering via OpenGL", "Description[pl]": "Wtyczka kompozytora KWin wyświetlająca przez OpenGL", "Description[pt]": "'Plugin' de Composição do KWin com desenho via OpenGL", + "Description[pt_BR]": "Plugin do compositor KWin renderizando pelo OpenGL", + "Description[sk]": "Renderovací plugin kompozítora KWin cez OpenGL", + "Description[sl]": "Izrisovanje vstavka upravljalnika skladnje KWin preko OpenGL-ja", "Description[sr@ijekavian]": "К‑винов прикључак слагача за рендеровање кроз опенГЛ", "Description[sr@ijekavianlatin]": "KWinov priključak slagača za renderovanje kroz OpenGL", "Description[sr@latin]": "KWinov priključak slagača za renderovanje kroz OpenGL", "Description[sr]": "К‑винов прикључак слагача за рендеровање кроз опенГЛ", "Description[sv]": "Kwin sammansättningsinsticksprogram återger via OpenGL", + "Description[tr]": "OpenGL üzerinden gerçekleme yapan KWin Dizgici eklentisi", "Description[uk]": "Додаток засобу композиції KWin для обробки з використанням OpenGL", "Description[x-test]": "xxKWin Compositor plugin rendering through OpenGLxx", "Description[zh_CN]": "使用 OpenGL 渲染的 KWin 混成插件", "Id": "KWinSceneOpenGL", "Name": "SceneOpenGL", "Name[pl]": "OpenGL sceny", "Name[sr@ijekavian]": "ОпенГЛ-сцена", "Name[sr@ijekavianlatin]": "OpenGL-scena", "Name[sr@latin]": "OpenGL-scena", "Name[sr]": "ОпенГЛ-сцена", "Name[sv]": "Scen OpenGL", "Name[x-test]": "xxSceneOpenGLxx" } } diff --git a/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp index 35885df9a..9a02c26ee 100644 --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -1,2460 +1,2464 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009, 2010, 2011 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. Explicit command stream synchronization based on the sample implementation by James Jones , Copyright © 2011 NVIDIA Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "scene_opengl.h" #include "platform.h" #include "wayland_server.h" #include "platformsupport/scenes/opengl/texture.h" #include #include "utils.h" #include "client.h" #include "composite.h" #include "deleted.h" #include "effects.h" #include "lanczosfilter.h" #include "main.h" #include "overlaywindow.h" #include "screens.h" #include "cursor.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // HACK: workaround for libepoxy < 1.3 #ifndef GL_GUILTY_CONTEXT_RESET #define GL_GUILTY_CONTEXT_RESET 0x8253 #endif #ifndef GL_INNOCENT_CONTEXT_RESET #define GL_INNOCENT_CONTEXT_RESET 0x8254 #endif #ifndef GL_UNKNOWN_CONTEXT_RESET #define GL_UNKNOWN_CONTEXT_RESET 0x8255 #endif namespace KWin { extern int currentRefreshRate(); /** * SyncObject represents a fence used to synchronize operations in * the kwin command stream with operations in the X command stream. */ class SyncObject { public: enum State { Ready, TriggerSent, Waiting, Done, Resetting }; SyncObject(); ~SyncObject(); State state() const { return m_state; } void trigger(); void wait(); bool finish(); void reset(); void finishResetting(); private: State m_state; GLsync m_sync; xcb_sync_fence_t m_fence; xcb_get_input_focus_cookie_t m_reset_cookie; }; SyncObject::SyncObject() { m_state = Ready; xcb_connection_t * const c = connection(); m_fence = xcb_generate_id(c); xcb_sync_create_fence(c, rootWindow(), m_fence, false); xcb_flush(c); m_sync = glImportSyncEXT(GL_SYNC_X11_FENCE_EXT, m_fence, 0); } SyncObject::~SyncObject() { // If glDeleteSync is called before the xcb fence is signalled // the nvidia driver (the only one to implement GL_SYNC_X11_FENCE_EXT) // deadlocks waiting for the fence to be signalled. // To avoid this, make sure the fence is signalled before // deleting the sync. if (m_state == Resetting || m_state == Ready){ trigger(); // The flush is necessary! // The trigger command needs to be sent to the X server. xcb_flush(connection()); } xcb_sync_destroy_fence(connection(), m_fence); glDeleteSync(m_sync); if (m_state == Resetting) xcb_discard_reply(connection(), m_reset_cookie.sequence); } void SyncObject::trigger() { assert(m_state == Ready || m_state == Resetting); // Finish resetting the fence if necessary if (m_state == Resetting) finishResetting(); xcb_sync_trigger_fence(connection(), m_fence); m_state = TriggerSent; } void SyncObject::wait() { if (m_state != TriggerSent) return; glWaitSync(m_sync, 0, GL_TIMEOUT_IGNORED); m_state = Waiting; } bool SyncObject::finish() { if (m_state == Done) return true; // Note: It is possible that we never inserted a wait for the fence. // This can happen if we ended up not rendering the damaged // window because it is fully occluded. assert(m_state == TriggerSent || m_state == Waiting); // Check if the fence is signaled GLint value; glGetSynciv(m_sync, GL_SYNC_STATUS, 1, nullptr, &value); if (value != GL_SIGNALED) { qCDebug(KWIN_OPENGL) << "Waiting for X fence to finish"; // Wait for the fence to become signaled with a one second timeout const GLenum result = glClientWaitSync(m_sync, 0, 1000000000); switch (result) { case GL_TIMEOUT_EXPIRED: qCWarning(KWIN_OPENGL) << "Timeout while waiting for X fence"; return false; case GL_WAIT_FAILED: qCWarning(KWIN_OPENGL) << "glClientWaitSync() failed"; return false; } } m_state = Done; return true; } void SyncObject::reset() { assert(m_state == Done); xcb_connection_t * const c = connection(); // Send the reset request along with a sync request. // We use the cookie to ensure that the server has processed the reset // request before we trigger the fence and call glWaitSync(). // Otherwise there is a race condition between the reset finishing and // the glWaitSync() call. xcb_sync_reset_fence(c, m_fence); m_reset_cookie = xcb_get_input_focus(c); xcb_flush(c); m_state = Resetting; } void SyncObject::finishResetting() { assert(m_state == Resetting); free(xcb_get_input_focus_reply(connection(), m_reset_cookie, nullptr)); m_state = Ready; } // ----------------------------------------------------------------------- /** * SyncManager manages a set of fences used for explicit synchronization * with the X command stream. */ class SyncManager { public: enum { MaxFences = 4 }; SyncManager(); ~SyncManager(); SyncObject *nextFence(); bool updateFences(); private: std::array m_fences; int m_next; }; SyncManager::SyncManager() : m_next(0) { } SyncManager::~SyncManager() { } SyncObject *SyncManager::nextFence() { SyncObject *fence = &m_fences[m_next]; m_next = (m_next + 1) % MaxFences; return fence; } bool SyncManager::updateFences() { for (int i = 0; i < qMin(2, MaxFences - 1); i++) { const int index = (m_next + i) % MaxFences; SyncObject &fence = m_fences[index]; switch (fence.state()) { case SyncObject::Ready: break; case SyncObject::TriggerSent: case SyncObject::Waiting: if (!fence.finish()) return false; fence.reset(); break; // Should not happen in practice since we always reset the fence // after finishing it case SyncObject::Done: fence.reset(); break; case SyncObject::Resetting: fence.finishResetting(); break; } } return true; } // ----------------------------------------------------------------------- /************************************************ * SceneOpenGL ***********************************************/ SceneOpenGL::SceneOpenGL(OpenGLBackend *backend, QObject *parent) : Scene(parent) , init_ok(true) , m_backend(backend) , m_syncManager(nullptr) , m_currentFence(nullptr) { if (m_backend->isFailed()) { init_ok = false; return; } if (!viewportLimitsMatched(screens()->size())) return; // perform Scene specific checks GLPlatform *glPlatform = GLPlatform::instance(); if (!glPlatform->isGLES() && !hasGLExtension(QByteArrayLiteral("GL_ARB_texture_non_power_of_two")) && !hasGLExtension(QByteArrayLiteral("GL_ARB_texture_rectangle"))) { qCCritical(KWIN_OPENGL) << "GL_ARB_texture_non_power_of_two and GL_ARB_texture_rectangle missing"; init_ok = false; return; // error } if (glPlatform->isMesaDriver() && glPlatform->mesaVersion() < kVersionNumber(10, 0)) { qCCritical(KWIN_OPENGL) << "KWin requires at least Mesa 10.0 for OpenGL compositing."; init_ok = false; return; } if (!glPlatform->isGLES() && !m_backend->isSurfaceLessContext()) { glDrawBuffer(GL_BACK); } m_debug = qstrcmp(qgetenv("KWIN_GL_DEBUG"), "1") == 0; initDebugOutput(); // set strict binding if (options->isGlStrictBindingFollowsDriver()) { options->setGlStrictBinding(!glPlatform->supports(LooseBinding)); } bool haveSyncObjects = glPlatform->isGLES() ? hasGLVersion(3, 0) : hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync"); if (hasGLExtension("GL_EXT_x11_sync_object") && haveSyncObjects && kwinApp()->operationMode() == Application::OperationModeX11) { const QByteArray useExplicitSync = qgetenv("KWIN_EXPLICIT_SYNC"); if (useExplicitSync != "0") { qCDebug(KWIN_OPENGL) << "Initializing fences for synchronization with the X command stream"; m_syncManager = new SyncManager; } else { qCDebug(KWIN_OPENGL) << "Explicit synchronization with the X command stream disabled by environment variable"; } } } static SceneOpenGL *gs_debuggedScene = nullptr; SceneOpenGL::~SceneOpenGL() { // do cleanup after initBuffer() gs_debuggedScene = nullptr; SceneOpenGL::EffectFrame::cleanup(); if (init_ok) { delete m_syncManager; // backend might be still needed for a different scene delete m_backend; } } static void scheduleVboReInit() { if (!gs_debuggedScene) return; static QPointer timer; if (!timer) { delete timer; timer = new QTimer(gs_debuggedScene); timer->setSingleShot(true); QObject::connect(timer.data(), &QTimer::timeout, gs_debuggedScene, []() { GLVertexBuffer::cleanup(); GLVertexBuffer::initStatic(); }); } timer->start(250); } void SceneOpenGL::initDebugOutput() { const bool have_KHR_debug = hasGLExtension(QByteArrayLiteral("GL_KHR_debug")); const bool have_ARB_debug = hasGLExtension(QByteArrayLiteral("GL_ARB_debug_output")); if (!have_KHR_debug && !have_ARB_debug) return; if (!have_ARB_debug) { // if we don't have ARB debug, but only KHR debug we need to verify whether the context is a debug context // it should work without as well, but empirical tests show: no it doesn't if (GLPlatform::instance()->isGLES()) { if (!hasGLVersion(3, 2)) { // empirical data shows extension doesn't work return; } } else if (!hasGLVersion(3, 0)) { return; } // can only be queried with either OpenGL >= 3.0 or OpenGL ES of at least 3.1 GLint value = 0; glGetIntegerv(GL_CONTEXT_FLAGS, &value); if (!(value & GL_CONTEXT_FLAG_DEBUG_BIT)) { return; } } gs_debuggedScene = this; // Set the callback function auto callback = [](GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const GLvoid *userParam) { Q_UNUSED(source) Q_UNUSED(severity) Q_UNUSED(userParam) while (message[length] == '\n' || message[length] == '\r') --length; switch (type) { case GL_DEBUG_TYPE_ERROR: case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: qCWarning(KWIN_OPENGL, "%#x: %.*s", id, length, message); break; case GL_DEBUG_TYPE_OTHER: // at least the nvidia driver seems prone to end up with invalid VBOs after // transferring them between system heap and VRAM // so we re-init them whenever this happens (typically when switching VT, resuming // from STR and XRandR events - #344326 if (strstr(message, "Buffer detailed info:") && strstr(message, "has been updated")) scheduleVboReInit(); // fall through! for general message printing -#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); -#endif case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: case GL_DEBUG_TYPE_PORTABILITY: case GL_DEBUG_TYPE_PERFORMANCE: default: qCDebug(KWIN_OPENGL, "%#x: %.*s", id, length, message); break; } }; glDebugMessageCallback(callback, nullptr); // This state exists only in GL_KHR_debug if (have_KHR_debug) glEnable(GL_DEBUG_OUTPUT); #ifndef NDEBUG // Enable all debug messages glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); #else // Enable error messages glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, nullptr, GL_TRUE); glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, GL_DONT_CARE, 0, nullptr, GL_TRUE); #endif // Insert a test message const QByteArray message = QByteArrayLiteral("OpenGL debug output initialized"); glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, 0, GL_DEBUG_SEVERITY_LOW, message.length(), message.constData()); } SceneOpenGL *SceneOpenGL::createScene(QObject *parent) { OpenGLBackend *backend = kwinApp()->platform()->createOpenGLBackend(); if (!backend) { return nullptr; } if (!backend->isFailed()) { backend->init(); } if (backend->isFailed()) { delete backend; return NULL; } SceneOpenGL *scene = NULL; // first let's try an OpenGL 2 scene if (SceneOpenGL2::supported(backend)) { scene = new SceneOpenGL2(backend, parent); if (scene->initFailed()) { delete scene; scene = NULL; } else { return scene; } } if (!scene) { if (GLPlatform::instance()->recommendedCompositor() == XRenderCompositing) { qCCritical(KWIN_OPENGL) << "OpenGL driver recommends XRender based compositing. Falling back to XRender."; qCCritical(KWIN_OPENGL) << "To overwrite the detection use the environment variable KWIN_COMPOSE"; qCCritical(KWIN_OPENGL) << "For more information see http://community.kde.org/KWin/Environment_Variables#KWIN_COMPOSE"; } delete backend; } return scene; } OverlayWindow *SceneOpenGL::overlayWindow() { return m_backend->overlayWindow(); } bool SceneOpenGL::syncsToVBlank() const { return m_backend->syncsToVBlank(); } bool SceneOpenGL::blocksForRetrace() const { return m_backend->blocksForRetrace(); } void SceneOpenGL::idle() { m_backend->idle(); Scene::idle(); } bool SceneOpenGL::initFailed() const { return !init_ok; } void SceneOpenGL::handleGraphicsReset(GLenum status) { switch (status) { case GL_GUILTY_CONTEXT_RESET: qCDebug(KWIN_OPENGL) << "A graphics reset attributable to the current GL context occurred."; break; case GL_INNOCENT_CONTEXT_RESET: qCDebug(KWIN_OPENGL) << "A graphics reset not attributable to the current GL context occurred."; break; case GL_UNKNOWN_CONTEXT_RESET: qCDebug(KWIN_OPENGL) << "A graphics reset of an unknown cause occurred."; break; default: break; } QElapsedTimer timer; timer.start(); // Wait until the reset is completed or max 10 seconds while (timer.elapsed() < 10000 && glGetGraphicsResetStatus() != GL_NO_ERROR) usleep(50); qCDebug(KWIN_OPENGL) << "Attempting to reset compositing."; QMetaObject::invokeMethod(this, "resetCompositing", Qt::QueuedConnection); KNotification::event(QStringLiteral("graphicsreset"), i18n("Desktop effects were restarted due to a graphics reset")); } void SceneOpenGL::triggerFence() { if (m_syncManager) { m_currentFence = m_syncManager->nextFence(); m_currentFence->trigger(); } } void SceneOpenGL::insertWait() { if (m_currentFence && m_currentFence->state() != SyncObject::Waiting) { m_currentFence->wait(); } } /** * Render cursor texture in case hardware cursor is disabled. * Useful for screen recording apps or backends that can't do planes. */ void SceneOpenGL2::paintCursor() { // don't paint if we use hardware cursor if (!kwinApp()->platform()->usesSoftwareCursor()) { return; } // lazy init texture cursor only in case we need software rendering if (!m_cursorTexture) { auto updateCursorTexture = [this] { // don't paint if no image for cursor is set const QImage img = kwinApp()->platform()->softwareCursor(); if (img.isNull()) { return; } m_cursorTexture.reset(new GLTexture(img)); }; // init now updateCursorTexture(); // handle shape update on case cursor image changed connect(Cursor::self(), &Cursor::cursorChanged, this, updateCursorTexture); } // get cursor position in projection coordinates const QPoint cursorPos = Cursor::pos() - kwinApp()->platform()->softwareCursorHotspot(); const QRect cursorRect(0, 0, m_cursorTexture->width(), m_cursorTexture->height()); QMatrix4x4 mvp = m_projectionMatrix; mvp.translate(cursorPos.x(), cursorPos.y()); // handle transparence glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // paint texture in cursor offset m_cursorTexture->bind(); ShaderBinder binder(ShaderTrait::MapTexture); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_cursorTexture->render(QRegion(cursorRect), cursorRect); m_cursorTexture->unbind(); kwinApp()->platform()->markCursorAsRendered(); glDisable(GL_BLEND); } qint64 SceneOpenGL::paint(QRegion damage, ToplevelList toplevels) { // actually paint the frame, flushed with the NEXT frame createStackingOrder(toplevels); // After this call, updateRegion will contain the damaged region in the // back buffer. This is the region that needs to be posted to repair // the front buffer. It doesn't include the additional damage returned // by prepareRenderingFrame(). validRegion is the region that has been // repainted, and may be larger than updateRegion. QRegion updateRegion, validRegion; if (m_backend->perScreenRendering()) { // trigger start render timer m_backend->prepareRenderingFrame(); for (int i = 0; i < screens()->count(); ++i) { const QRect &geo = screens()->geometry(i); QRegion update; QRegion valid; // prepare rendering makes context current on the output QRegion repaint = m_backend->prepareRenderingForScreen(i); GLVertexBuffer::setVirtualScreenGeometry(geo); GLRenderTarget::setVirtualScreenGeometry(geo); + GLVertexBuffer::setVirtualScreenScale(screens()->scale(i)); GLRenderTarget::setVirtualScreenScale(screens()->scale(i)); const GLenum status = glGetGraphicsResetStatus(); if (status != GL_NO_ERROR) { handleGraphicsReset(status); return 0; } int mask = 0; updateProjectionMatrix(); paintScreen(&mask, damage.intersected(geo), repaint, &update, &valid, projectionMatrix(), geo); // call generic implementation paintCursor(); GLVertexBuffer::streamingBuffer()->endOfFrame(); m_backend->endRenderingFrameForScreen(i, valid, update); GLVertexBuffer::streamingBuffer()->framePosted(); } } else { m_backend->makeCurrent(); QRegion repaint = m_backend->prepareRenderingFrame(); const GLenum status = glGetGraphicsResetStatus(); if (status != GL_NO_ERROR) { handleGraphicsReset(status); return 0; } GLVertexBuffer::setVirtualScreenGeometry(screens()->geometry()); GLRenderTarget::setVirtualScreenGeometry(screens()->geometry()); + GLVertexBuffer::setVirtualScreenScale(1); GLRenderTarget::setVirtualScreenScale(1); int mask = 0; updateProjectionMatrix(); paintScreen(&mask, damage, repaint, &updateRegion, &validRegion, projectionMatrix()); // call generic implementation if (!GLPlatform::instance()->isGLES()) { const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); // copy dirty parts from front to backbuffer if (!m_backend->supportsBufferAge() && options->glPreferBufferSwap() == Options::CopyFrontBuffer && validRegion != displayRegion) { glReadBuffer(GL_FRONT); m_backend->copyPixels(displayRegion - validRegion); glReadBuffer(GL_BACK); validRegion = displayRegion; } } GLVertexBuffer::streamingBuffer()->endOfFrame(); m_backend->endRenderingFrame(validRegion, updateRegion); GLVertexBuffer::streamingBuffer()->framePosted(); } if (m_currentFence) { if (!m_syncManager->updateFences()) { qCDebug(KWIN_OPENGL) << "Aborting explicit synchronization with the X command stream."; qCDebug(KWIN_OPENGL) << "Future frames will be rendered unsynchronized."; delete m_syncManager; m_syncManager = nullptr; } m_currentFence = nullptr; } // do cleanup clearStackingOrder(); return m_backend->renderTime(); } QMatrix4x4 SceneOpenGL::transformation(int mask, const ScreenPaintData &data) const { QMatrix4x4 matrix; if (!(mask & PAINT_SCREEN_TRANSFORMED)) return matrix; matrix.translate(data.translation()); data.scale().applyTo(&matrix); if (data.rotationAngle() == 0.0) return matrix; // Apply the rotation // cannot use data.rotation->applyTo(&matrix) as QGraphicsRotation uses projectedRotate to map back to 2D matrix.translate(data.rotationOrigin()); const QVector3D axis = data.rotationAxis(); matrix.rotate(data.rotationAngle(), axis.x(), axis.y(), axis.z()); matrix.translate(-data.rotationOrigin()); return matrix; } void SceneOpenGL::paintBackground(QRegion region) { PaintClipper pc(region); if (!PaintClipper::clip()) { glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); return; } if (pc.clip() && pc.paintArea().isEmpty()) return; // no background to paint QVector verts; for (PaintClipper::Iterator iterator; !iterator.isDone(); iterator.next()) { QRect r = iterator.boundingRect(); verts << r.x() + r.width() << r.y(); verts << r.x() << r.y(); verts << r.x() << r.y() + r.height(); verts << r.x() << r.y() + r.height(); verts << r.x() + r.width() << r.y() + r.height(); verts << r.x() + r.width() << r.y(); } doPaintBackground(verts); } void SceneOpenGL::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) { if (m_backend->supportsBufferAge()) return; const QSize &screenSize = screens()->size(); if (options->glPreferBufferSwap() == Options::ExtendDamage) { // only Extend "large" repaints const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); uint damagedPixels = 0; const uint fullRepaintLimit = (opaqueFullscreen?0.49f:0.748f)*screenSize.width()*screenSize.height(); // 16:9 is 75% of 4:3 and 2.55:1 is 49.01% of 5:4 // (5:4 is the most square format and 2.55:1 is Cinemascope55 - the widest ever shot // movie aspect - two times ;-) It's a Fox format, though, so maybe we want to restrict // to 2.20:1 - Panavision - which has actually been used for interesting movies ...) // would be 57% of 5/4 - foreach (const QRect &r, region.rects()) { + for (const QRect &r : region) { // damagedPixels += r.width() * r.height(); // combined window damage test damagedPixels = r.width() * r.height(); // experimental single window damage testing if (damagedPixels > fullRepaintLimit) { region = displayRegion; return; } } } else if (options->glPreferBufferSwap() == Options::PaintFullScreen) { // forced full rePaint region = QRegion(0, 0, screenSize.width(), screenSize.height()); } } SceneOpenGLTexture *SceneOpenGL::createTexture() { return new SceneOpenGLTexture(m_backend); } bool SceneOpenGL::viewportLimitsMatched(const QSize &size) const { GLint limit[2]; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, limit); if (limit[0] < size.width() || limit[1] < size.height()) { QMetaObject::invokeMethod(Compositor::self(), "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, Compositor::AllReasonSuspend)); const QString message = i18n("

OpenGL desktop effects not possible

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

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

OpenGL desktop effects might be unusable

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

" "Consider to suspend compositing, switch to the XRender backend " "or lower the resolution to %1x%1." , limit[0]); const QString details = i18n("The demanded resolution exceeds the GL_MAX_TEXTURE_SIZE " "limitation of your GPU, thus windows of that size cannot be " "assigned to textures and will be entirely black.
" "Also this limit will often be a performance level barrier despite " "below GL_MAX_VIEWPORT_DIMS, because the driver might fall back to " "software rendering in this case."); const int oldTimeout = QDBusConnection::sessionBus().interface()->timeout(); QDBusConnection::sessionBus().interface()->setTimeout(500); if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.kwinCompositingDialog")).value()) { QDBusInterface dialog( QStringLiteral("org.kde.kwinCompositingDialog"), QStringLiteral("/CompositorSettings"), QStringLiteral("org.kde.kwinCompositingDialog") ); dialog.asyncCall(QStringLiteral("warn"), message, details, QStringLiteral("kwin_dialogsrc:max_tex_warning")); } else { const QString args = QLatin1String("warn ") + QString::fromUtf8(message.toLocal8Bit().toBase64()) + QLatin1String(" details ") + QString::fromUtf8(details.toLocal8Bit().toBase64()) + QLatin1String(" dontagain kwin_dialogsrc:max_tex_warning"); KProcess::startDetached(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("kwincompositing") << QStringLiteral("--args") << args); } QDBusConnection::sessionBus().interface()->setTimeout(oldTimeout); } return true; } void SceneOpenGL::screenGeometryChanged(const QSize &size) { if (!viewportLimitsMatched(size)) return; Scene::screenGeometryChanged(size); glViewport(0,0, size.width(), size.height()); m_backend->screenGeometryChanged(size); GLRenderTarget::setVirtualScreenSize(size); } void SceneOpenGL::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { const QRect r = region.boundingRect(); glEnable(GL_SCISSOR_TEST); glScissor(r.x(), screens()->size().height() - r.y() - r.height(), r.width(), r.height()); KWin::Scene::paintDesktop(desktop, mask, region, data); glDisable(GL_SCISSOR_TEST); } bool SceneOpenGL::makeOpenGLContextCurrent() { return m_backend->makeCurrent(); } void SceneOpenGL::doneOpenGLContextCurrent() { m_backend->doneCurrent(); } Scene::EffectFrame *SceneOpenGL::createEffectFrame(EffectFrameImpl *frame) { return new SceneOpenGL::EffectFrame(frame, this); } Shadow *SceneOpenGL::createShadow(Toplevel *toplevel) { return new SceneOpenGLShadow(toplevel); } Decoration::Renderer *SceneOpenGL::createDecorationRenderer(Decoration::DecoratedClientImpl *impl) { return new SceneOpenGLDecorationRenderer(impl); } bool SceneOpenGL::animationsSupported() const { return !GLPlatform::instance()->isSoftwareEmulation(); } QVector SceneOpenGL::openGLPlatformInterfaceExtensions() const { return m_backend->extensions().toVector(); } //**************************************** // SceneOpenGL2 //**************************************** bool SceneOpenGL2::supported(OpenGLBackend *backend) { const QByteArray forceEnv = qgetenv("KWIN_COMPOSE"); if (!forceEnv.isEmpty()) { if (qstrcmp(forceEnv, "O2") == 0 || qstrcmp(forceEnv, "O2ES") == 0) { qCDebug(KWIN_OPENGL) << "OpenGL 2 compositing enforced by environment variable"; return true; } else { // OpenGL 2 disabled by environment variable return false; } } if (!backend->isDirectRendering()) { return false; } if (GLPlatform::instance()->recommendedCompositor() < OpenGL2Compositing) { qCDebug(KWIN_OPENGL) << "Driver does not recommend OpenGL 2 compositing"; return false; } return true; } SceneOpenGL2::SceneOpenGL2(OpenGLBackend *backend, QObject *parent) : SceneOpenGL(backend, parent) , m_lanczosFilter(NULL) { if (!init_ok) { // base ctor already failed return; } // We only support the OpenGL 2+ shader API, not GL_ARB_shader_objects if (!hasGLVersion(2, 0)) { qCDebug(KWIN_OPENGL) << "OpenGL 2.0 is not supported"; init_ok = false; return; } const QSize &s = screens()->size(); GLRenderTarget::setVirtualScreenSize(s); GLRenderTarget::setVirtualScreenGeometry(screens()->geometry()); // push one shader on the stack so that one is always bound ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); if (checkGLError("Init")) { qCCritical(KWIN_OPENGL) << "OpenGL 2 compositing setup failed"; init_ok = false; return; // error } // It is not legal to not have a vertex array object bound in a core context if (!GLPlatform::instance()->isGLES() && hasGLExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) { glGenVertexArrays(1, &vao); glBindVertexArray(vao); } if (!ShaderManager::instance()->selfTest()) { qCCritical(KWIN_OPENGL) << "ShaderManager self test failed"; init_ok = false; return; } qCDebug(KWIN_OPENGL) << "OpenGL 2 compositing successfully initialized"; init_ok = true; } SceneOpenGL2::~SceneOpenGL2() { } QMatrix4x4 SceneOpenGL2::createProjectionMatrix() const { // Create a perspective projection with a 60° field-of-view, // and an aspect ratio of 1.0. const float fovY = 60.0f; const float aspect = 1.0f; const float zNear = 0.1f; const float zFar = 100.0f; const float yMax = zNear * std::tan(fovY * M_PI / 360.0f); const float yMin = -yMax; const float xMin = yMin * aspect; const float xMax = yMax * aspect; QMatrix4x4 projection; projection.frustum(xMin, xMax, yMin, yMax, zNear, zFar); // Create a second matrix that transforms screen coordinates // to world coordinates. const float scaleFactor = 1.1 * std::tan(fovY * M_PI / 360.0f) / yMax; const QSize size = screens()->size(); QMatrix4x4 matrix; matrix.translate(xMin * scaleFactor, yMax * scaleFactor, -1.1); matrix.scale( (xMax - xMin) * scaleFactor / size.width(), -(yMax - yMin) * scaleFactor / size.height(), 0.001); // Combine the matrices return projection * matrix; } void SceneOpenGL2::updateProjectionMatrix() { m_projectionMatrix = createProjectionMatrix(); } void SceneOpenGL2::paintSimpleScreen(int mask, QRegion region) { m_screenProjectionMatrix = m_projectionMatrix; Scene::paintSimpleScreen(mask, region); } void SceneOpenGL2::paintGenericScreen(int mask, ScreenPaintData data) { const QMatrix4x4 screenMatrix = transformation(mask, data); m_screenProjectionMatrix = m_projectionMatrix * screenMatrix; Scene::paintGenericScreen(mask, data); } void SceneOpenGL2::doPaintBackground(const QVector< float >& vertices) { GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setUseColor(true); vbo->setData(vertices.count() / 2, 2, vertices.data(), NULL); ShaderBinder binder(ShaderTrait::UniformColor); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, m_projectionMatrix); vbo->render(GL_TRIANGLES); } Scene::Window *SceneOpenGL2::createWindow(Toplevel *t) { SceneOpenGL2Window *w = new SceneOpenGL2Window(t); w->setScene(this); return w; } void SceneOpenGL2::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (waylandServer() && waylandServer()->isScreenLocked() && !w->window()->isLockScreen() && !w->window()->isInputMethod()) { return; } performPaintWindow(w, mask, region, data); } void SceneOpenGL2::performPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (mask & PAINT_WINDOW_LANCZOS) { if (!m_lanczosFilter) { m_lanczosFilter = new LanczosFilter(this); // reset the lanczos filter when the screen gets resized // it will get created next paint connect(screens(), &Screens::changed, this, [this]() { makeOpenGLContextCurrent(); delete m_lanczosFilter; m_lanczosFilter = NULL; }); } m_lanczosFilter->performPaint(w, mask, region, data); } else w->sceneWindow()->performPaint(mask, region, data); } //**************************************** // SceneOpenGL::Window //**************************************** SceneOpenGL::Window::Window(Toplevel* c) : Scene::Window(c) , m_scene(NULL) { } SceneOpenGL::Window::~Window() { } static SceneOpenGLTexture *s_frameTexture = NULL; // Bind the window pixmap to an OpenGL texture. bool SceneOpenGL::Window::bindTexture() { s_frameTexture = NULL; OpenGLWindowPixmap *pixmap = windowPixmap(); if (!pixmap) { return false; } s_frameTexture = pixmap->texture(); if (pixmap->isDiscarded()) { return !pixmap->texture()->isNull(); } if (!window()->damage().isEmpty()) m_scene->insertWait(); return pixmap->bind(); } QMatrix4x4 SceneOpenGL::Window::transformation(int mask, const WindowPaintData &data) const { QMatrix4x4 matrix; matrix.translate(x(), y()); if (!(mask & PAINT_WINDOW_TRANSFORMED)) return matrix; matrix.translate(data.translation()); data.scale().applyTo(&matrix); if (data.rotationAngle() == 0.0) return matrix; // Apply the rotation // cannot use data.rotation.applyTo(&matrix) as QGraphicsRotation uses projectedRotate to map back to 2D matrix.translate(data.rotationOrigin()); const QVector3D axis = data.rotationAxis(); matrix.rotate(data.rotationAngle(), axis.x(), axis.y(), axis.z()); matrix.translate(-data.rotationOrigin()); return matrix; } bool SceneOpenGL::Window::beginRenderWindow(int mask, const QRegion ®ion, WindowPaintData &data) { if (region.isEmpty()) return false; m_hardwareClipping = region != infiniteRegion() && (mask & PAINT_WINDOW_TRANSFORMED) && !(mask & PAINT_SCREEN_TRANSFORMED); if (region != infiniteRegion() && !m_hardwareClipping) { WindowQuadList quads; quads.reserve(data.quads.count()); const QRegion filterRegion = region.translated(-x(), -y()); // split all quads in bounding rect with the actual rects in the region foreach (const WindowQuad &quad, data.quads) { - foreach (const QRect &r, filterRegion.rects()) { + for (const QRect &r : filterRegion) { const QRectF rf(r); const QRectF quadRect(QPointF(quad.left(), quad.top()), QPointF(quad.right(), quad.bottom())); const QRectF &intersected = rf.intersected(quadRect); if (intersected.isValid()) { if (quadRect == intersected) { // case 1: completely contains, include and do not check other rects quads << quad; break; } // case 2: intersection quads << quad.makeSubQuad(intersected.left(), intersected.top(), intersected.right(), intersected.bottom()); } } } data.quads = quads; } if (data.quads.isEmpty()) return false; if (!bindTexture() || !s_frameTexture) { return false; } if (m_hardwareClipping) { glEnable(GL_SCISSOR_TEST); } // Update the texture filter if (options->glSmoothScale() != 0 && (mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) filter = ImageFilterGood; else filter = ImageFilterFast; s_frameTexture->setFilter(filter == ImageFilterGood ? GL_LINEAR : GL_NEAREST); const GLVertexAttrib attribs[] = { { VA_Position, 2, GL_FLOAT, offsetof(GLVertex2D, position) }, { VA_TexCoord, 2, GL_FLOAT, offsetof(GLVertex2D, texcoord) }, }; GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setAttribLayout(attribs, 2, sizeof(GLVertex2D)); return true; } void SceneOpenGL::Window::endRenderWindow() { if (m_hardwareClipping) { glDisable(GL_SCISSOR_TEST); } } GLTexture *SceneOpenGL::Window::getDecorationTexture() const { if (AbstractClient *client = dynamic_cast(toplevel)) { if (client->noBorder()) { return nullptr; } if (!client->isDecorated()) { return nullptr; } if (SceneOpenGLDecorationRenderer *renderer = static_cast(client->decoratedClient()->renderer())) { renderer->render(); return renderer->texture(); } } else if (toplevel->isDeleted()) { Deleted *deleted = static_cast(toplevel); if (!deleted->wasClient() || deleted->noBorder()) { return nullptr; } if (const SceneOpenGLDecorationRenderer *renderer = static_cast(deleted->decorationRenderer())) { return renderer->texture(); } } return nullptr; } WindowPixmap* SceneOpenGL::Window::createWindowPixmap() { return new OpenGLWindowPixmap(this, m_scene); } //*************************************** // SceneOpenGL2Window //*************************************** SceneOpenGL2Window::SceneOpenGL2Window(Toplevel *c) : SceneOpenGL::Window(c) , m_blendingEnabled(false) { } SceneOpenGL2Window::~SceneOpenGL2Window() { } QVector4D SceneOpenGL2Window::modulate(float opacity, float brightness) const { const float a = opacity; const float rgb = opacity * brightness; return QVector4D(rgb, rgb, rgb, a); } void SceneOpenGL2Window::setBlendEnabled(bool enabled) { if (enabled && !m_blendingEnabled) glEnable(GL_BLEND); else if (!enabled && m_blendingEnabled) glDisable(GL_BLEND); m_blendingEnabled = enabled; } void SceneOpenGL2Window::setupLeafNodes(LeafNode *nodes, const WindowQuadList *quads, const WindowPaintData &data) { if (!quads[ShadowLeaf].isEmpty()) { nodes[ShadowLeaf].texture = static_cast(m_shadow)->shadowTexture(); nodes[ShadowLeaf].opacity = data.opacity(); nodes[ShadowLeaf].hasAlpha = true; nodes[ShadowLeaf].coordinateType = NormalizedCoordinates; } if (!quads[DecorationLeaf].isEmpty()) { nodes[DecorationLeaf].texture = getDecorationTexture(); nodes[DecorationLeaf].opacity = data.opacity(); nodes[DecorationLeaf].hasAlpha = true; nodes[DecorationLeaf].coordinateType = UnnormalizedCoordinates; } nodes[ContentLeaf].texture = s_frameTexture; nodes[ContentLeaf].hasAlpha = !isOpaque(); // TODO: ARGB crsoofading is atm. a hack, playing on opacities for two dumb SrcOver operations // Should be a shader if (data.crossFadeProgress() != 1.0 && (data.opacity() < 0.95 || toplevel->hasAlpha())) { const float opacity = 1.0 - data.crossFadeProgress(); nodes[ContentLeaf].opacity = data.opacity() * (1 - pow(opacity, 1.0f + 2.0f * data.opacity())); } else { nodes[ContentLeaf].opacity = data.opacity(); } nodes[ContentLeaf].coordinateType = UnnormalizedCoordinates; if (data.crossFadeProgress() != 1.0) { OpenGLWindowPixmap *previous = previousWindowPixmap(); nodes[PreviousContentLeaf].texture = previous ? previous->texture() : NULL; nodes[PreviousContentLeaf].hasAlpha = !isOpaque(); nodes[PreviousContentLeaf].opacity = data.opacity() * (1.0 - data.crossFadeProgress()); nodes[PreviousContentLeaf].coordinateType = NormalizedCoordinates; } } QMatrix4x4 SceneOpenGL2Window::modelViewProjectionMatrix(int mask, const WindowPaintData &data) const { SceneOpenGL2 *scene = static_cast(m_scene); const QMatrix4x4 pMatrix = data.projectionMatrix(); const QMatrix4x4 mvMatrix = data.modelViewMatrix(); // An effect may want to override the default projection matrix in some cases, // such as when it is rendering a window on a render target that doesn't have // the same dimensions as the default framebuffer. // // Note that the screen transformation is not applied here. if (!pMatrix.isIdentity()) return pMatrix * mvMatrix; // If an effect has specified a model-view matrix, we multiply that matrix // with the default projection matrix. If the effect hasn't specified a // model-view matrix, mvMatrix will be the identity matrix. if (mask & Scene::PAINT_SCREEN_TRANSFORMED) return scene->screenProjectionMatrix() * mvMatrix; return scene->projectionMatrix() * mvMatrix; } -static void renderSubSurface(GLShader *shader, const QMatrix4x4 &mvp, const QMatrix4x4 &windowMatrix, OpenGLWindowPixmap *pixmap) +static void renderSubSurface(GLShader *shader, const QMatrix4x4 &mvp, const QMatrix4x4 &windowMatrix, OpenGLWindowPixmap *pixmap, const QRegion ®ion, bool hardwareClipping) { QMatrix4x4 newWindowMatrix = windowMatrix; newWindowMatrix.translate(pixmap->subSurface()->position().x(), pixmap->subSurface()->position().y()); qreal scale = 1.0; if (pixmap->surface()) { scale = pixmap->surface()->scale(); } if (!pixmap->texture()->isNull()) { // render this texture shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp * newWindowMatrix); auto texture = pixmap->texture(); texture->bind(); - texture->render(QRegion(), QRect(0, 0, texture->width() / scale, texture->height() / scale)); + texture->render(region, QRect(0, 0, texture->width() / scale, texture->height() / scale), hardwareClipping); texture->unbind(); } const auto &children = pixmap->children(); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } - renderSubSurface(shader, mvp, newWindowMatrix, static_cast(pixmap)); + renderSubSurface(shader, mvp, newWindowMatrix, static_cast(pixmap), region, hardwareClipping); } } void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!beginRenderWindow(mask, region, data)) return; QMatrix4x4 windowMatrix = transformation(mask, data); const QMatrix4x4 modelViewProjection = modelViewProjectionMatrix(mask, data); const QMatrix4x4 mvpMatrix = modelViewProjection * windowMatrix; GLShader *shader = data.shader; if (!shader) { ShaderTraits traits = ShaderTrait::MapTexture; if (data.opacity() != 1.0 || data.brightness() != 1.0 || data.crossFadeProgress() != 1.0) traits |= ShaderTrait::Modulate; if (data.saturation() != 1.0) traits |= ShaderTrait::AdjustSaturation; shader = ShaderManager::instance()->pushShader(traits); } shader->setUniform(GLShader::ModelViewProjectionMatrix, mvpMatrix); shader->setUniform(GLShader::Saturation, data.saturation()); const GLenum filter = (mask & (Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_SCREEN_TRANSFORMED)) && options->glSmoothScale() != 0 ? GL_LINEAR : GL_NEAREST; WindowQuadList quads[LeafCount]; // Split the quads into separate lists for each type foreach (const WindowQuad &quad, data.quads) { switch (quad.type()) { case WindowQuadDecoration: quads[DecorationLeaf].append(quad); continue; case WindowQuadContents: quads[ContentLeaf].append(quad); continue; case WindowQuadShadow: quads[ShadowLeaf].append(quad); continue; default: continue; } } if (data.crossFadeProgress() != 1.0) { OpenGLWindowPixmap *previous = previousWindowPixmap(); if (previous) { const QRect &oldGeometry = previous->contentsRect(); for (const WindowQuad &quad : quads[ContentLeaf]) { // we need to create new window quads with normalize texture coordinates // normal quads divide the x/y position by width/height. This would not work as the texture // is larger than the visible content in case of a decorated Client resulting in garbage being shown. // So we calculate the normalized texture coordinate in the Client's new content space and map it to // the previous Client's content space. WindowQuad newQuad(WindowQuadContents); for (int i = 0; i < 4; ++i) { const qreal xFactor = qreal(quad[i].textureX() - toplevel->clientPos().x())/qreal(toplevel->clientSize().width()); const qreal yFactor = qreal(quad[i].textureY() - toplevel->clientPos().y())/qreal(toplevel->clientSize().height()); WindowVertex vertex(quad[i].x(), quad[i].y(), (xFactor * oldGeometry.width() + oldGeometry.x())/qreal(previous->size().width()), (yFactor * oldGeometry.height() + oldGeometry.y())/qreal(previous->size().height())); newQuad[i] = vertex; } quads[PreviousContentLeaf].append(newQuad); } } } const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads(); const GLenum primitiveType = indexedQuads ? GL_QUADS : GL_TRIANGLES; const int verticesPerQuad = indexedQuads ? 4 : 6; const size_t size = verticesPerQuad * (quads[0].count() + quads[1].count() + quads[2].count() + quads[3].count()) * sizeof(GLVertex2D); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); GLVertex2D *map = (GLVertex2D *) vbo->map(size); LeafNode nodes[LeafCount]; setupLeafNodes(nodes, quads, data); for (int i = 0, v = 0; i < LeafCount; i++) { if (quads[i].isEmpty() || !nodes[i].texture) continue; nodes[i].firstVertex = v; nodes[i].vertexCount = quads[i].count() * verticesPerQuad; const QMatrix4x4 matrix = nodes[i].texture->matrix(nodes[i].coordinateType); quads[i].makeInterleavedArrays(primitiveType, &map[v], matrix); v += quads[i].count() * verticesPerQuad; } vbo->unmap(); vbo->bindArrays(); // Make sure the blend function is set up correctly in case we will be doing blending glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); float opacity = -1.0; for (int i = 0; i < LeafCount; i++) { if (nodes[i].vertexCount == 0) continue; setBlendEnabled(nodes[i].hasAlpha || nodes[i].opacity < 1.0); if (opacity != nodes[i].opacity) { shader->setUniform(GLShader::ModulationConstant, modulate(nodes[i].opacity, data.brightness())); opacity = nodes[i].opacity; } nodes[i].texture->setFilter(filter); nodes[i].texture->setWrapMode(GL_CLAMP_TO_EDGE); nodes[i].texture->bind(); vbo->draw(region, primitiveType, nodes[i].firstVertex, nodes[i].vertexCount, m_hardwareClipping); } vbo->unbindArrays(); setBlendEnabled(false); // render sub-surfaces auto wp = windowPixmap(); const auto &children = wp ? wp->children() : QVector(); windowMatrix.translate(toplevel->clientPos().x(), toplevel->clientPos().y()); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } - renderSubSurface(shader, modelViewProjection, windowMatrix, static_cast(pixmap)); + renderSubSurface(shader, modelViewProjection, windowMatrix, static_cast(pixmap), region, m_hardwareClipping); } if (!data.shader) ShaderManager::instance()->popShader(); endRenderWindow(); } //**************************************** // OpenGLWindowPixmap //**************************************** OpenGLWindowPixmap::OpenGLWindowPixmap(Scene::Window *window, SceneOpenGL* scene) : WindowPixmap(window) , m_texture(scene->createTexture()) , m_scene(scene) { } OpenGLWindowPixmap::OpenGLWindowPixmap(const QPointer &subSurface, WindowPixmap *parent, SceneOpenGL *scene) : WindowPixmap(subSurface, parent) , m_texture(scene->createTexture()) , m_scene(scene) { } OpenGLWindowPixmap::~OpenGLWindowPixmap() { } bool OpenGLWindowPixmap::bind() { if (!m_texture->isNull()) { // always call updateBuffer to get the sub-surface tree updated if (subSurface().isNull() && !toplevel()->damage().isEmpty()) { updateBuffer(); } auto s = surface(); if (s && !s->trackedDamage().isEmpty()) { m_texture->updateFromPixmap(this); // mipmaps need to be updated m_texture->setDirty(); } if (subSurface().isNull()) { toplevel()->resetDamage(); } // also bind all children for (auto it = children().constBegin(); it != children().constEnd(); ++it) { static_cast(*it)->bind(); } return true; } // also bind all children, needs to be done before checking isValid // as there might be valid children to render, see https://bugreports.qt.io/browse/QTBUG-52192 if (subSurface().isNull()) { updateBuffer(); } for (auto it = children().constBegin(); it != children().constEnd(); ++it) { static_cast(*it)->bind(); } if (!isValid()) { return false; } bool success = m_texture->load(this); if (success) { if (subSurface().isNull()) { toplevel()->resetDamage(); } } else qCDebug(KWIN_OPENGL) << "Failed to bind window"; return success; } WindowPixmap *OpenGLWindowPixmap::createChild(const QPointer &subSurface) { return new OpenGLWindowPixmap(subSurface, this, m_scene); } bool OpenGLWindowPixmap::isValid() const { if (!m_texture->isNull()) { return true; } return WindowPixmap::isValid(); } //**************************************** // SceneOpenGL::EffectFrame //**************************************** GLTexture* SceneOpenGL::EffectFrame::m_unstyledTexture = NULL; QPixmap* SceneOpenGL::EffectFrame::m_unstyledPixmap = NULL; SceneOpenGL::EffectFrame::EffectFrame(EffectFrameImpl* frame, SceneOpenGL *scene) : Scene::EffectFrame(frame) , m_texture(NULL) , m_textTexture(NULL) , m_oldTextTexture(NULL) , m_textPixmap(NULL) , m_iconTexture(NULL) , m_oldIconTexture(NULL) , m_selectionTexture(NULL) , m_unstyledVBO(NULL) , m_scene(scene) { if (m_effectFrame->style() == EffectFrameUnstyled && !m_unstyledTexture) { updateUnstyledTexture(); } } SceneOpenGL::EffectFrame::~EffectFrame() { delete m_texture; delete m_textTexture; delete m_textPixmap; delete m_oldTextTexture; delete m_iconTexture; delete m_oldIconTexture; delete m_selectionTexture; delete m_unstyledVBO; } void SceneOpenGL::EffectFrame::free() { glFlush(); delete m_texture; m_texture = NULL; delete m_textTexture; m_textTexture = NULL; delete m_textPixmap; m_textPixmap = NULL; delete m_iconTexture; m_iconTexture = NULL; delete m_selectionTexture; m_selectionTexture = NULL; delete m_unstyledVBO; m_unstyledVBO = NULL; delete m_oldIconTexture; m_oldIconTexture = NULL; delete m_oldTextTexture; m_oldTextTexture = NULL; } void SceneOpenGL::EffectFrame::freeIconFrame() { delete m_iconTexture; m_iconTexture = NULL; } void SceneOpenGL::EffectFrame::freeTextFrame() { delete m_textTexture; m_textTexture = NULL; delete m_textPixmap; m_textPixmap = NULL; } void SceneOpenGL::EffectFrame::freeSelection() { delete m_selectionTexture; m_selectionTexture = NULL; } void SceneOpenGL::EffectFrame::crossFadeIcon() { delete m_oldIconTexture; m_oldIconTexture = m_iconTexture; m_iconTexture = NULL; } void SceneOpenGL::EffectFrame::crossFadeText() { delete m_oldTextTexture; m_oldTextTexture = m_textTexture; m_textTexture = NULL; } void SceneOpenGL::EffectFrame::render(QRegion region, double opacity, double frameOpacity) { if (m_effectFrame->geometry().isEmpty()) return; // Nothing to display region = infiniteRegion(); // TODO: Old region doesn't seem to work with OpenGL GLShader* shader = m_effectFrame->shader(); if (!shader) { shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::Modulate); } else if (shader) { ShaderManager::instance()->pushShader(shader); } if (shader) { shader->setUniform(GLShader::ModulationConstant, QVector4D(1.0, 1.0, 1.0, 1.0)); shader->setUniform(GLShader::Saturation, 1.0f); } const QMatrix4x4 projection = m_scene->projectionMatrix(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { if (!m_unstyledVBO) { m_unstyledVBO = new GLVertexBuffer(GLVertexBuffer::Static); QRect area = m_effectFrame->geometry(); area.moveTo(0, 0); area.adjust(-5, -5, 5, 5); const int roundness = 5; QVector verts, texCoords; verts.reserve(84); texCoords.reserve(84); // top left verts << area.left() << area.top(); texCoords << 0.0f << 0.0f; verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; // top verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; // top right verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() << area.top(); texCoords << 1.0f << 0.0f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; verts << area.right() << area.top(); texCoords << 1.0f << 0.0f; // bottom left verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.left() << area.bottom(); texCoords << 0.0f << 1.0f; verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.left() << area.bottom(); texCoords << 0.0f << 1.0f; verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; // bottom verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; // bottom right verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() << area.bottom(); texCoords << 1.0f << 1.0f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; // center verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; m_unstyledVBO->setData(verts.count() / 2, 2, verts.data(), texCoords.data()); } if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_unstyledTexture->bind(); const QPoint pt = m_effectFrame->geometry().topLeft(); QMatrix4x4 mvp(projection); mvp.translate(pt.x(), pt.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_unstyledVBO->render(region, GL_TRIANGLES); m_unstyledTexture->unbind(); } else if (m_effectFrame->style() == EffectFrameStyled) { if (!m_texture) // Lazy creation updateTexture(); if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_texture->bind(); qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry const QRect rect = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); QMatrix4x4 mvp(projection); mvp.translate(rect.x(), rect.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_texture->render(region, rect); m_texture->unbind(); } if (!m_effectFrame->selection().isNull()) { if (!m_selectionTexture) { // Lazy creation QPixmap pixmap = m_effectFrame->selectionFrame().framePixmap(); if (!pixmap.isNull()) m_selectionTexture = new GLTexture(pixmap); } if (m_selectionTexture) { if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } QMatrix4x4 mvp(projection); mvp.translate(m_effectFrame->selection().x(), m_effectFrame->selection().y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); m_selectionTexture->bind(); m_selectionTexture->render(region, m_effectFrame->selection()); m_selectionTexture->unbind(); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } } // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); QMatrix4x4 mvp(projection); mvp.translate(topLeft.x(), topLeft.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); if (m_effectFrame->isCrossFade() && m_oldIconTexture) { if (shader) { const float a = opacity * (1.0 - m_effectFrame->crossFadeProgress()); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_oldIconTexture->bind(); m_oldIconTexture->render(region, QRect(topLeft, m_effectFrame->iconSize())); m_oldIconTexture->unbind(); if (shader) { const float a = opacity * m_effectFrame->crossFadeProgress(); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } } else { if (shader) { const QVector4D constant(opacity, opacity, opacity, opacity); shader->setUniform(GLShader::ModulationConstant, constant); } } if (!m_iconTexture) { // lazy creation m_iconTexture = new GLTexture(m_effectFrame->icon().pixmap(m_effectFrame->iconSize())); } m_iconTexture->bind(); m_iconTexture->render(region, QRect(topLeft, m_effectFrame->iconSize())); m_iconTexture->unbind(); } // Render text if (!m_effectFrame->text().isEmpty()) { QMatrix4x4 mvp(projection); mvp.translate(m_effectFrame->geometry().x(), m_effectFrame->geometry().y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); if (m_effectFrame->isCrossFade() && m_oldTextTexture) { if (shader) { const float a = opacity * (1.0 - m_effectFrame->crossFadeProgress()); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_oldTextTexture->bind(); m_oldTextTexture->render(region, m_effectFrame->geometry()); m_oldTextTexture->unbind(); if (shader) { const float a = opacity * m_effectFrame->crossFadeProgress(); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } } else { if (shader) { const QVector4D constant(opacity, opacity, opacity, opacity); shader->setUniform(GLShader::ModulationConstant, constant); } } if (!m_textTexture) // Lazy creation updateTextTexture(); if (m_textTexture) { m_textTexture->bind(); m_textTexture->render(region, m_effectFrame->geometry()); m_textTexture->unbind(); } } if (shader) { ShaderManager::instance()->popShader(); } glDisable(GL_BLEND); } void SceneOpenGL::EffectFrame::updateTexture() { delete m_texture; m_texture = 0L; if (m_effectFrame->style() == EffectFrameStyled) { QPixmap pixmap = m_effectFrame->frame().framePixmap(); m_texture = new GLTexture(pixmap); } } void SceneOpenGL::EffectFrame::updateTextTexture() { delete m_textTexture; m_textTexture = 0L; delete m_textPixmap; m_textPixmap = 0L; if (m_effectFrame->text().isEmpty()) return; // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) rect.setLeft(m_effectFrame->iconSize().width()); // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->font()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } m_textPixmap = new QPixmap(m_effectFrame->geometry().size()); m_textPixmap->fill(Qt::transparent); QPainter p(m_textPixmap); p.setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) p.setPen(m_effectFrame->styledTextColor()); else // TODO: What about no frame? Custom color setting required p.setPen(Qt::white); p.drawText(rect, m_effectFrame->alignment(), text); p.end(); m_textTexture = new GLTexture(*m_textPixmap); } void SceneOpenGL::EffectFrame::updateUnstyledTexture() { delete m_unstyledTexture; m_unstyledTexture = 0L; delete m_unstyledPixmap; m_unstyledPixmap = 0L; // Based off circle() from kwinxrenderutils.cpp #define CS 8 m_unstyledPixmap = new QPixmap(2 * CS, 2 * CS); m_unstyledPixmap->fill(Qt::transparent); QPainter p(m_unstyledPixmap); p.setRenderHint(QPainter::Antialiasing); p.setPen(Qt::NoPen); p.setBrush(Qt::black); p.drawEllipse(m_unstyledPixmap->rect()); p.end(); #undef CS m_unstyledTexture = new GLTexture(*m_unstyledPixmap); } void SceneOpenGL::EffectFrame::cleanup() { delete m_unstyledTexture; m_unstyledTexture = NULL; delete m_unstyledPixmap; m_unstyledPixmap = NULL; } //**************************************** // SceneOpenGL::Shadow //**************************************** class DecorationShadowTextureCache { public: ~DecorationShadowTextureCache(); DecorationShadowTextureCache(const DecorationShadowTextureCache&) = delete; static DecorationShadowTextureCache &instance(); void unregister(SceneOpenGLShadow *shadow); QSharedPointer getTexture(SceneOpenGLShadow *shadow); private: DecorationShadowTextureCache() = default; struct Data { QSharedPointer texture; QVector shadows; }; QHash m_cache; }; DecorationShadowTextureCache &DecorationShadowTextureCache::instance() { static DecorationShadowTextureCache s_instance; return s_instance; } DecorationShadowTextureCache::~DecorationShadowTextureCache() { Q_ASSERT(m_cache.isEmpty()); } void DecorationShadowTextureCache::unregister(SceneOpenGLShadow *shadow) { auto it = m_cache.begin(); while (it != m_cache.end()) { auto &d = it.value(); // check whether the Vector of Shadows contains our shadow and remove all of them auto glIt = d.shadows.begin(); while (glIt != d.shadows.end()) { if (*glIt == shadow) { glIt = d.shadows.erase(glIt); } else { glIt++; } } // if there are no shadows any more we can erase the cache entry if (d.shadows.isEmpty()) { it = m_cache.erase(it); } else { it++; } } } QSharedPointer DecorationShadowTextureCache::getTexture(SceneOpenGLShadow *shadow) { Q_ASSERT(shadow->hasDecorationShadow()); unregister(shadow); const auto &decoShadow = shadow->decorationShadow(); Q_ASSERT(!decoShadow.isNull()); auto it = m_cache.find(decoShadow.data()); if (it != m_cache.end()) { Q_ASSERT(!it.value().shadows.contains(shadow)); it.value().shadows << shadow; return it.value().texture; } Data d; d.shadows << shadow; d.texture = QSharedPointer::create(shadow->decorationShadowImage()); m_cache.insert(decoShadow.data(), d); return d.texture; } SceneOpenGLShadow::SceneOpenGLShadow(Toplevel *toplevel) : Shadow(toplevel) { } SceneOpenGLShadow::~SceneOpenGLShadow() { if (effects) { effects->makeOpenGLContextCurrent(); DecorationShadowTextureCache::instance().unregister(this); m_texture.reset(); } } void SceneOpenGLShadow::buildQuads() { // prepare window quads m_shadowQuads.clear(); const QSizeF top(elementSize(ShadowElementTop)); const QSizeF topRight(elementSize(ShadowElementTopRight)); const QSizeF right(elementSize(ShadowElementRight)); const QSizeF bottomRight(elementSize(ShadowElementBottomRight)); const QSizeF bottom(elementSize(ShadowElementBottom)); const QSizeF bottomLeft(elementSize(ShadowElementBottomLeft)); const QSizeF left(elementSize(ShadowElementLeft)); const QSizeF topLeft(elementSize(ShadowElementTopLeft)); if ((left.width() - leftOffset() > topLevel()->width()) || (right.width() - rightOffset() > topLevel()->width()) || (top.height() - topOffset() > topLevel()->height()) || (bottom.height() - bottomOffset() > topLevel()->height())) { // if our shadow is bigger than the window, we don't render the shadow setShadowRegion(QRegion()); return; } const QRectF outerRect(QPointF(-leftOffset(), -topOffset()), QPointF(topLevel()->width() + rightOffset(), topLevel()->height() + bottomOffset())); const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + std::max(top.width(), bottom.width()) + std::max({topRight.width(), right.width(), bottomRight.width()}); const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + std::max(left.height(), right.height()) + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()}); qreal tx1(0.0), tx2(0.0), ty1(0.0), ty2(0.0); tx2 = topLeft.width()/width; ty2 = topLeft.height()/height; WindowQuad topLeftQuad(WindowQuadShadow); topLeftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.y(), tx1, ty1); topLeftQuad[ 1 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y(), tx2, ty1); topLeftQuad[ 2 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y() + topLeft.height(), tx2, ty2); topLeftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.y() + topLeft.height(), tx1, ty2); m_shadowQuads.append(topLeftQuad); tx1 = tx2; tx2 = (topLeft.width() + top.width())/width; ty2 = top.height()/height; WindowQuad topQuad(WindowQuadShadow); topQuad[ 0 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y(), tx1, ty1); topQuad[ 1 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y(), tx2, ty1); topQuad[ 2 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y() + top.height(),tx2, ty2); topQuad[ 3 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y() + top.height(), tx1, ty2); m_shadowQuads.append(topQuad); tx1 = tx2; tx2 = 1.0; ty2 = topRight.height()/height; WindowQuad topRightQuad(WindowQuadShadow); topRightQuad[ 0 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y(), tx1, ty1); topRightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.y(), tx2, ty1); topRightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.y() + topRight.height(), tx2, ty2); topRightQuad[ 3 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y() + topRight.height(), tx1, ty2); m_shadowQuads.append(topRightQuad); tx1 = (width - right.width())/width; ty1 = topRight.height()/height; ty2 = (topRight.height() + right.height())/height; WindowQuad rightQuad(WindowQuadShadow); rightQuad[ 0 ] = WindowVertex(outerRect.right() - right.width(), outerRect.y() + topRight.height(), tx1, ty1); rightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.y() + topRight.height(), tx2, ty1); rightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.bottom() - bottomRight.height(), tx2, ty2); rightQuad[ 3 ] = WindowVertex(outerRect.right() - right.width(), outerRect.bottom() - bottomRight.height(), tx1, ty2); m_shadowQuads.append(rightQuad); tx1 = (width - bottomRight.width())/width; ty1 = ty2; ty2 = 1.0; WindowQuad bottomRightQuad(WindowQuadShadow); bottomRightQuad[ 0 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom() - bottomRight.height(), tx1, ty1); bottomRightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.bottom() - bottomRight.height(), tx2, ty1); bottomRightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.bottom(), tx2, ty2); bottomRightQuad[ 3 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomRightQuad); tx2 = tx1; tx1 = bottomLeft.width()/width; ty1 = (height - bottom.height())/height; WindowQuad bottomQuad(WindowQuadShadow); bottomQuad[ 0 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom() - bottom.height(), tx1, ty1); bottomQuad[ 1 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom() - bottom.height(), tx2, ty1); bottomQuad[ 2 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom(), tx2, ty2); bottomQuad[ 3 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomQuad); tx1 = 0.0; tx2 = bottomLeft.width()/width; ty1 = (height - bottomLeft.height())/height; WindowQuad bottomLeftQuad(WindowQuadShadow); bottomLeftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.bottom() - bottomLeft.height(), tx1, ty1); bottomLeftQuad[ 1 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom() - bottomLeft.height(), tx2, ty1); bottomLeftQuad[ 2 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom(), tx2, ty2); bottomLeftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomLeftQuad); tx2 = left.width()/width; ty2 = ty1; ty1 = topLeft.height()/height; WindowQuad leftQuad(WindowQuadShadow); leftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.y() + topLeft.height(), tx1, ty1); leftQuad[ 1 ] = WindowVertex(outerRect.x() + left.width(), outerRect.y() + topLeft.height(), tx2, ty1); leftQuad[ 2 ] = WindowVertex(outerRect.x() + left.width(), outerRect.bottom() - bottomLeft.height(), tx2, ty2); leftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.bottom() - bottomLeft.height(), tx1, ty2); m_shadowQuads.append(leftQuad); } bool SceneOpenGLShadow::prepareBackend() { if (hasDecorationShadow()) { // simplifies a lot by going directly to effects->makeOpenGLContextCurrent(); m_texture = DecorationShadowTextureCache::instance().getTexture(this); return true; } const QSize top(shadowPixmap(ShadowElementTop).size()); const QSize topRight(shadowPixmap(ShadowElementTopRight).size()); const QSize right(shadowPixmap(ShadowElementRight).size()); const QSize bottom(shadowPixmap(ShadowElementBottom).size()); const QSize bottomLeft(shadowPixmap(ShadowElementBottomLeft).size()); const QSize left(shadowPixmap(ShadowElementLeft).size()); const QSize topLeft(shadowPixmap(ShadowElementTopLeft).size()); const QSize bottomRight(shadowPixmap(ShadowElementBottomRight).size()); const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + std::max(top.width(), bottom.width()) + std::max({topRight.width(), right.width(), bottomRight.width()}); const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + std::max(left.height(), right.height()) + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()}); if (width == 0 || height == 0) { return false; } QImage image(width, height, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter p; p.begin(&image); p.drawPixmap(0, 0, shadowPixmap(ShadowElementTopLeft)); p.drawPixmap(topLeft.width(), 0, shadowPixmap(ShadowElementTop)); p.drawPixmap(topLeft.width() + top.width(), 0, shadowPixmap(ShadowElementTopRight)); p.drawPixmap(0, topLeft.height(), shadowPixmap(ShadowElementLeft)); p.drawPixmap(width - right.width(), topRight.height(), shadowPixmap(ShadowElementRight)); p.drawPixmap(0, topLeft.height() + left.height(), shadowPixmap(ShadowElementBottomLeft)); p.drawPixmap(bottomLeft.width(), height - bottom.height(), shadowPixmap(ShadowElementBottom)); p.drawPixmap(bottomLeft.width() + bottom.width(), topRight.height() + right.height(), shadowPixmap(ShadowElementBottomRight)); p.end(); // Check if the image is alpha-only in practice, and if so convert it to an 8-bpp format if (!GLPlatform::instance()->isGLES() && GLTexture::supportsSwizzle() && GLTexture::supportsFormatRG()) { QImage alphaImage(image.size(), QImage::Format_Indexed8); // Change to Format_Alpha8 w/ Qt 5.5 bool alphaOnly = true; for (ptrdiff_t y = 0; alphaOnly && y < image.height(); y++) { const uint32_t * const src = reinterpret_cast(image.scanLine(y)); uint8_t * const dst = reinterpret_cast(alphaImage.scanLine(y)); for (ptrdiff_t x = 0; x < image.width(); x++) { if (src[x] & 0x00ffffff) alphaOnly = false; dst[x] = qAlpha(src[x]); } } if (alphaOnly) { image = alphaImage; } } effects->makeOpenGLContextCurrent(); m_texture = QSharedPointer::create(image); if (m_texture->internalFormat() == GL_R8) { // Swizzle red to alpha and all other channels to zero m_texture->bind(); m_texture->setSwizzle(GL_ZERO, GL_ZERO, GL_ZERO, GL_RED); } return true; } SceneOpenGLDecorationRenderer::SceneOpenGLDecorationRenderer(Decoration::DecoratedClientImpl *client) : Renderer(client) , m_texture() { connect(this, &Renderer::renderScheduled, client->client(), static_cast(&AbstractClient::addRepaint)); } SceneOpenGLDecorationRenderer::~SceneOpenGLDecorationRenderer() = default; // Rotates the given source rect 90° counter-clockwise, // and flips it vertically static QImage rotate(const QImage &srcImage, const QRect &srcRect) { - QImage image(srcRect.height(), srcRect.width(), srcImage.format()); + auto dpr = srcImage.devicePixelRatio(); + QImage image(srcRect.height() * dpr, srcRect.width() * dpr, srcImage.format()); + image.setDevicePixelRatio(dpr); + const QPoint srcPoint(srcRect.x() * dpr, srcRect.y() * dpr); const uint32_t *src = reinterpret_cast(srcImage.bits()); uint32_t *dst = reinterpret_cast(image.bits()); for (int x = 0; x < image.width(); x++) { - const uint32_t *s = src + (srcRect.y() + x) * srcImage.width() + srcRect.x(); + const uint32_t *s = src + (srcPoint.y() + x) * srcImage.width() + srcPoint.x(); uint32_t *d = dst + x; for (int y = 0; y < image.height(); y++) { *d = s[y]; d += image.width(); } } return image; } void SceneOpenGLDecorationRenderer::render() { const QRegion scheduled = getScheduled(); - if (scheduled.isEmpty()) { + const bool dirty = areImageSizesDirty(); + if (scheduled.isEmpty() && !dirty) { return; } - const bool dirty = areImageSizesDirty(); if (dirty) { resizeTexture(); resetImageSizesDirty(); } if (!m_texture) { // for invalid sizes we get no texture, see BUG 361551 return; } QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); const QRect geometry = dirty ? QRect(QPoint(0, 0), client()->client()->geometry().size()) : scheduled.boundingRect(); auto renderPart = [this](const QRect &geo, const QRect &partRect, const QPoint &offset, bool rotated = false) { if (geo.isNull()) { return; } QImage image = renderToImage(geo); if (rotated) { // TODO: get this done directly when rendering to the image image = rotate(image, QRect(geo.topLeft() - partRect.topLeft(), geo.size())); } - m_texture->update(image, geo.topLeft() - partRect.topLeft() + offset); + m_texture->update(image, (geo.topLeft() - partRect.topLeft() + offset) * image.devicePixelRatio()); }; renderPart(left.intersected(geometry), left, QPoint(0, top.height() + bottom.height() + 2), true); renderPart(top.intersected(geometry), top, QPoint(0, 0)); renderPart(right.intersected(geometry), right, QPoint(0, top.height() + bottom.height() + left.width() + 3), true); renderPart(bottom.intersected(geometry), bottom, QPoint(0, top.height() + 1)); } static int align(int value, int align) { return (value + align - 1) & ~(align - 1); } void SceneOpenGLDecorationRenderer::resizeTexture() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); QSize size; size.rwidth() = qMax(qMax(top.width(), bottom.width()), qMax(left.height(), right.height())); size.rheight() = top.height() + bottom.height() + left.width() + right.width() + 3; size.rwidth() = align(size.width(), 128); + size *= client()->client()->screenScale(); if (m_texture && m_texture->size() == size) return; if (!size.isEmpty()) { m_texture.reset(new GLTexture(GL_RGBA8, size.width(), size.height())); m_texture->setYInverted(true); m_texture->setWrapMode(GL_CLAMP_TO_EDGE); m_texture->clear(); } else { m_texture.reset(); } } void SceneOpenGLDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } OpenGLFactory::OpenGLFactory(QObject *parent) : SceneFactory(parent) { } OpenGLFactory::~OpenGLFactory() = default; Scene *OpenGLFactory::create(QObject *parent) const { qCDebug(KWIN_OPENGL) << "Initializing OpenGL compositing"; // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes: if (kwinApp()->platform()->openGLCompositingIsBroken()) { qCWarning(KWIN_OPENGL) << "KWin has detected that your OpenGL library is unsafe to use"; return nullptr; } kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreInit); auto s = SceneOpenGL::createScene(parent); kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostInit); if (s && s->initFailed()) { delete s; return nullptr; } return s; } } // namespace diff --git a/plugins/scenes/qpainter/CMakeLists.txt b/plugins/scenes/qpainter/CMakeLists.txt index 34b03e730..24f0d9c54 100644 --- a/plugins/scenes/qpainter/CMakeLists.txt +++ b/plugins/scenes/qpainter/CMakeLists.txt @@ -1,14 +1,15 @@ set(SCENE_QPAINTER_SRCS scene_qpainter.cpp) add_library(KWinSceneQPainter MODULE scene_qpainter.cpp) +set_target_properties(KWinSceneQPainter PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kwin.scenes/") target_link_libraries(KWinSceneQPainter kwin SceneQPainterBackend ) install( TARGETS KWinSceneQPainter DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kwin.scenes/ ) diff --git a/plugins/scenes/qpainter/qpainter.json b/plugins/scenes/qpainter/qpainter.json index 3ef9e6a5c..d2c1179ef 100644 --- a/plugins/scenes/qpainter/qpainter.json +++ b/plugins/scenes/qpainter/qpainter.json @@ -1,33 +1,41 @@ { "CompositingType": 4, "KPlugin": { "Description": "KWin Compositor plugin rendering through QPainter", "Description[ca@valencia]": "Connector del Compositor del KWin que renderitza a través del QPainter", "Description[ca]": "Connector del Compositor del KWin que renderitza a través del QPainter", + "Description[da]": "KWin-compositorplugin som renderer igennem QPainter", + "Description[de]": "KWin-Compositor-Modul zum Rendern mit QPainter", "Description[el]": "Αποτύπωση πρσοθέτου συνθέτη KWin μέσω QPainter", "Description[es]": "Complemento compositor de KWin renderizando mediante QPainter", + "Description[fi]": "QPainterillä hahmontava KWin-koostajaliitännäinen", "Description[fr]": "Module du compositeur KWin effectuant le rendu avec QPainter", + "Description[gl]": "Complemento de compositor de KWin que renderiza a través de QPainter.", "Description[it]": "Estensione del compositore di KWin per la resa tramite QPainter", + "Description[ko]": "QPainter로 렌더링하는 KWin 컴포지터 플러그인", "Description[nl]": "KWin-compositor-plug-in rendering via QPainter", "Description[pl]": "Wtyczka kompozytora KWin wyświetlająca przez QPainter", "Description[pt]": "'Plugin' de Composição do KWin com desenho via QPainter", "Description[pt_BR]": "Plugin do compositor KWin renderizando pelo QPainter", + "Description[sk]": "Renderovací plugin kompozítora KWin cez QPainter", + "Description[sl]": "Izrisovanje vstavka upravljalnika skladnje KWin preko QPainter-ja", "Description[sr@ijekavian]": "К‑винов прикључак слагача за рендеровање кроз QPainter", "Description[sr@ijekavianlatin]": "KWinov priključak slagača za renderovanje kroz QPainter", "Description[sr@latin]": "KWinov priključak slagača za renderovanje kroz QPainter", "Description[sr]": "К‑винов прикључак слагача за рендеровање кроз QPainter", "Description[sv]": "Kwin sammansättningsinsticksprogram återger via QPainter", + "Description[tr]": "QPainter üzerinden KWin Dizgici eklentisi oluşturma", "Description[uk]": "Додаток засобу композиції KWin для обробки з використанням QPainter", "Description[x-test]": "xxKWin Compositor plugin rendering through QPainterxx", "Description[zh_CN]": "使用 QPainter 渲染的 KWin 合成插件", "Id": "KWinSceneQPainter", "Name": "SceneQPainter", "Name[pl]": "QPainter sceny", "Name[sr@ijekavian]": "QPainter-сцена", "Name[sr@ijekavianlatin]": "QPainter-scena", "Name[sr@latin]": "QPainter-scena", "Name[sr]": "QPainter-сцена", "Name[sv]": "Scen QPainter", "Name[x-test]": "xxSceneQPainterxx" } } diff --git a/plugins/scenes/qpainter/scene_qpainter.cpp b/plugins/scenes/qpainter/scene_qpainter.cpp index 92e2b4c53..69017c828 100644 --- a/plugins/scenes/qpainter/scene_qpainter.cpp +++ b/plugins/scenes/qpainter/scene_qpainter.cpp @@ -1,666 +1,674 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "scene_qpainter.h" // KWin #include "client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "effects.h" #include "main.h" #include "screens.h" #include "toplevel.h" #include "platform.h" #include "wayland_server.h" #include #include #include #include "decorations/decoratedclient.h" // Qt #include #include #include namespace KWin { //**************************************** // SceneQPainter //**************************************** SceneQPainter *SceneQPainter::createScene(QObject *parent) { QScopedPointer backend(kwinApp()->platform()->createQPainterBackend()); if (backend.isNull()) { return nullptr; } if (backend->isFailed()) { return NULL; } return new SceneQPainter(backend.take(), parent); } SceneQPainter::SceneQPainter(QPainterBackend *backend, QObject *parent) : Scene(parent) , m_backend(backend) , m_painter(new QPainter()) { } SceneQPainter::~SceneQPainter() { } CompositingType SceneQPainter::compositingType() const { return QPainterCompositing; } bool SceneQPainter::initFailed() const { return false; } void SceneQPainter::paintGenericScreen(int mask, ScreenPaintData data) { m_painter->save(); m_painter->translate(data.xTranslation(), data.yTranslation()); m_painter->scale(data.xScale(), data.yScale()); Scene::paintGenericScreen(mask, data); m_painter->restore(); } qint64 SceneQPainter::paint(QRegion damage, ToplevelList toplevels) { QElapsedTimer renderTimer; renderTimer.start(); createStackingOrder(toplevels); int mask = 0; m_backend->prepareRenderingFrame(); if (m_backend->perScreenRendering()) { const bool needsFullRepaint = m_backend->needsFullRepaint(); if (needsFullRepaint) { mask |= Scene::PAINT_SCREEN_BACKGROUND_FIRST; damage = screens()->geometry(); } QRegion overallUpdate; for (int i = 0; i < screens()->count(); ++i) { const QRect geometry = screens()->geometry(i); QImage *buffer = m_backend->bufferForScreen(i); if (!buffer || buffer->isNull()) { continue; } m_painter->begin(buffer); m_painter->save(); m_painter->setWindow(geometry); QRegion updateRegion, validRegion; paintScreen(&mask, damage.intersected(geometry), QRegion(), &updateRegion, &validRegion); overallUpdate = overallUpdate.united(updateRegion); paintCursor(); m_painter->restore(); m_painter->end(); } m_backend->showOverlay(); m_backend->present(mask, overallUpdate); } else { m_painter->begin(m_backend->buffer()); m_painter->setClipping(true); m_painter->setClipRegion(damage); if (m_backend->needsFullRepaint()) { mask |= Scene::PAINT_SCREEN_BACKGROUND_FIRST; damage = screens()->geometry(); } QRegion updateRegion, validRegion; paintScreen(&mask, damage, QRegion(), &updateRegion, &validRegion); paintCursor(); m_backend->showOverlay(); m_painter->end(); m_backend->present(mask, updateRegion); } // do cleanup clearStackingOrder(); emit frameRendered(); return renderTimer.nsecsElapsed(); } void SceneQPainter::paintBackground(QRegion region) { m_painter->setBrush(Qt::black); m_painter->drawRects(region.rects()); } void SceneQPainter::paintCursor() { if (!kwinApp()->platform()->usesSoftwareCursor()) { return; } const QImage img = kwinApp()->platform()->softwareCursor(); if (img.isNull()) { return; } const QPoint cursorPos = Cursor::pos(); const QPoint hotspot = kwinApp()->platform()->softwareCursorHotspot(); m_painter->drawImage(cursorPos - hotspot, img); kwinApp()->platform()->markCursorAsRendered(); } Scene::Window *SceneQPainter::createWindow(Toplevel *toplevel) { return new SceneQPainter::Window(this, toplevel); } Scene::EffectFrame *SceneQPainter::createEffectFrame(EffectFrameImpl *frame) { return new QPainterEffectFrame(frame, this); } Shadow *SceneQPainter::createShadow(Toplevel *toplevel) { return new SceneQPainterShadow(toplevel); } void SceneQPainter::screenGeometryChanged(const QSize &size) { Scene::screenGeometryChanged(size); m_backend->screenGeometryChanged(size); } QImage *SceneQPainter::qpainterRenderBuffer() const { return m_backend->buffer(); } //**************************************** // SceneQPainter::Window //**************************************** SceneQPainter::Window::Window(SceneQPainter *scene, Toplevel *c) : Scene::Window(c) , m_scene(scene) { } SceneQPainter::Window::~Window() { discardShape(); } static void paintSubSurface(QPainter *painter, const QPoint &pos, QPainterWindowPixmap *pixmap) { QPoint p = pos; if (!pixmap->subSurface().isNull()) { p += pixmap->subSurface()->position(); } painter->drawImage(QRect(pos, pixmap->size()), pixmap->image()); const auto &children = pixmap->children(); for (auto it = children.begin(); it != children.end(); ++it) { auto pixmap = static_cast(*it); if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } paintSubSurface(painter, p, pixmap); } } void SceneQPainter::Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!(mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) region &= toplevel->visibleRect(); if (region.isEmpty()) return; QPainterWindowPixmap *pixmap = windowPixmap(); if (!pixmap || !pixmap->isValid()) { return; } if (!toplevel->damage().isEmpty()) { pixmap->updateBuffer(); toplevel->resetDamage(); } QPainter *scenePainter = m_scene->scenePainter(); QPainter *painter = scenePainter; painter->save(); painter->setClipRegion(region); painter->setClipping(true); painter->translate(x(), y()); if (mask & PAINT_WINDOW_TRANSFORMED) { painter->translate(data.xTranslation(), data.yTranslation()); painter->scale(data.xScale(), data.yScale()); } const bool opaque = qFuzzyCompare(1.0, data.opacity()); QImage tempImage; QPainter tempPainter; if (!opaque) { // need a temp render target which we later on blit to the screen tempImage = QImage(toplevel->visibleRect().size(), QImage::Format_ARGB32_Premultiplied); tempImage.fill(Qt::transparent); tempPainter.begin(&tempImage); tempPainter.save(); tempPainter.translate(toplevel->geometry().topLeft() - toplevel->visibleRect().topLeft()); painter = &tempPainter; } renderShadow(painter); renderWindowDecorations(painter); // render content const QRect target = QRect(toplevel->clientPos(), toplevel->clientSize()); QSize srcSize = pixmap->image().size(); if (pixmap->surface() && pixmap->surface()->scale() == 1 && srcSize != toplevel->clientSize()) { // special case for XWayland windows srcSize = toplevel->clientSize(); } const QRect src = QRect(toplevel->clientPos() + toplevel->clientContentPos(), srcSize); painter->drawImage(target, pixmap->image(), src); // render subsurfaces const auto &children = pixmap->children(); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } paintSubSurface(painter, toplevel->clientPos(), static_cast(pixmap)); } if (!opaque) { tempPainter.restore(); tempPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); QColor translucent(Qt::transparent); translucent.setAlphaF(data.opacity()); tempPainter.fillRect(QRect(QPoint(0, 0), toplevel->visibleRect().size()), translucent); tempPainter.end(); painter = scenePainter; painter->drawImage(toplevel->visibleRect().topLeft() - toplevel->geometry().topLeft(), tempImage); } painter->restore(); } void SceneQPainter::Window::renderShadow(QPainter* painter) { if (!toplevel->shadow()) { return; } SceneQPainterShadow *shadow = static_cast(toplevel->shadow()); const QPixmap &topLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopLeft); const QPixmap &top = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTop); const QPixmap &topRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopRight); const QPixmap &bottomLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomLeft); const QPixmap &bottom = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottom); const QPixmap &bottomRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomRight); const QPixmap &left = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementLeft); const QPixmap &right = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementRight); const int leftOffset = shadow->leftOffset(); const int topOffset = shadow->topOffset(); const int rightOffset = shadow->rightOffset(); const int bottomOffset = shadow->bottomOffset(); // top left painter->drawPixmap(-leftOffset, -topOffset, topLeft); // top right painter->drawPixmap(toplevel->width() - topRight.width() + rightOffset, -topOffset, topRight); // bottom left painter->drawPixmap(-leftOffset, toplevel->height() - bottomLeft.height() + bottomOffset, bottomLeft); // bottom right painter->drawPixmap(toplevel->width() - bottomRight.width() + rightOffset, toplevel->height() - bottomRight.height() + bottomOffset, bottomRight); // top painter->drawPixmap(topLeft.width() - leftOffset, -topOffset, toplevel->width() - topLeft.width() - topRight.width() + leftOffset + rightOffset, top.height(), top); // left painter->drawPixmap(-leftOffset, topLeft.height() - topOffset, left.width(), toplevel->height() - topLeft.height() - bottomLeft.height() + topOffset + bottomOffset, left); // right painter->drawPixmap(toplevel->width() - right.width() + rightOffset, topRight.height() - topOffset, right.width(), toplevel->height() - topRight.height() - bottomRight.height() + topOffset + bottomOffset, right); // bottom painter->drawPixmap(bottomLeft.width() - leftOffset, toplevel->height() - bottom.height() + bottomOffset, toplevel->width() - bottomLeft.width() - bottomRight.width() + leftOffset + rightOffset, bottom.height(), bottom); } void SceneQPainter::Window::renderWindowDecorations(QPainter *painter) { // TODO: custom decoration opacity AbstractClient *client = dynamic_cast(toplevel); Deleted *deleted = dynamic_cast(toplevel); if (!client && !deleted) { return; } bool noBorder = true; const SceneQPainterDecorationRenderer *renderer = nullptr; QRect dtr, dlr, drr, dbr; if (client && !client->noBorder()) { if (client->isDecorated()) { if (SceneQPainterDecorationRenderer *r = static_cast(client->decoratedClient()->renderer())) { r->render(); renderer = r; } } client->layoutDecorationRects(dlr, dtr, drr, dbr); noBorder = false; } else if (deleted && !deleted->noBorder()) { noBorder = false; deleted->layoutDecorationRects(dlr, dtr, drr, dbr); renderer = static_cast(deleted->decorationRenderer()); } if (noBorder || !renderer) { return; } painter->drawImage(dtr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Top)); painter->drawImage(dlr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Left)); painter->drawImage(drr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Right)); painter->drawImage(dbr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Bottom)); } WindowPixmap *SceneQPainter::Window::createWindowPixmap() { return new QPainterWindowPixmap(this); } Decoration::Renderer *SceneQPainter::createDecorationRenderer(Decoration::DecoratedClientImpl *impl) { return new SceneQPainterDecorationRenderer(impl); } //**************************************** // QPainterWindowPixmap //**************************************** QPainterWindowPixmap::QPainterWindowPixmap(Scene::Window *window) : WindowPixmap(window) { } QPainterWindowPixmap::QPainterWindowPixmap(const QPointer &subSurface, WindowPixmap *parent) : WindowPixmap(subSurface, parent) { } QPainterWindowPixmap::~QPainterWindowPixmap() { } void QPainterWindowPixmap::create() { if (isValid()) { return; } KWin::WindowPixmap::create(); if (!isValid()) { return; } // performing deep copy, this could probably be improved m_image = buffer()->data().copy(); if (auto s = surface()) { s->resetTrackedDamage(); } } WindowPixmap *QPainterWindowPixmap::createChild(const QPointer &subSurface) { return new QPainterWindowPixmap(subSurface, this); } void QPainterWindowPixmap::updateBuffer() { const auto oldBuffer = buffer(); WindowPixmap::updateBuffer(); const auto &b = buffer(); if (b.isNull()) { m_image = QImage(); return; } if (b == oldBuffer) { return; } // perform deep copy m_image = b->data().copy(); if (auto s = surface()) { s->resetTrackedDamage(); } } bool QPainterWindowPixmap::isValid() const { if (!m_image.isNull()) { return true; } return WindowPixmap::isValid(); } QPainterEffectFrame::QPainterEffectFrame(EffectFrameImpl *frame, SceneQPainter *scene) : Scene::EffectFrame(frame) , m_scene(scene) { } QPainterEffectFrame::~QPainterEffectFrame() { } void QPainterEffectFrame::render(QRegion region, double opacity, double frameOpacity) { Q_UNUSED(region) Q_UNUSED(opacity) // TODO: adjust opacity if (m_effectFrame->geometry().isEmpty()) { return; // Nothing to display } QPainter *painter = m_scene->scenePainter(); // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { painter->save(); painter->setPen(Qt::NoPen); QColor color(Qt::black); color.setAlphaF(frameOpacity); painter->setBrush(color); painter->setRenderHint(QPainter::Antialiasing); painter->drawRoundedRect(m_effectFrame->geometry().adjusted(-5, -5, 5, 5), 5.0, 5.0); painter->restore(); } else if (m_effectFrame->style() == EffectFrameStyled) { qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry QRect geom = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); painter->drawPixmap(geom, m_effectFrame->frame().framePixmap()); } if (!m_effectFrame->selection().isNull()) { painter->drawPixmap(m_effectFrame->selection(), m_effectFrame->selectionFrame().framePixmap()); } // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { const QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); const QRect geom = QRect(topLeft, m_effectFrame->iconSize()); painter->drawPixmap(geom, m_effectFrame->icon().pixmap(m_effectFrame->iconSize())); } // Render text if (!m_effectFrame->text().isEmpty()) { // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { rect.setLeft(m_effectFrame->iconSize().width()); } // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->text()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } painter->save(); painter->setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) { painter->setPen(m_effectFrame->styledTextColor()); } else { // TODO: What about no frame? Custom color setting required painter->setPen(Qt::white); } painter->drawText(rect.translated(m_effectFrame->geometry().topLeft()), m_effectFrame->alignment(), text); painter->restore(); } } //**************************************** // QPainterShadow //**************************************** SceneQPainterShadow::SceneQPainterShadow(Toplevel* toplevel) : Shadow(toplevel) { } SceneQPainterShadow::~SceneQPainterShadow() { } bool SceneQPainterShadow::prepareBackend() { if (hasDecorationShadow()) { // TODO: implement for QPainter return false; } return true; } //**************************************** // QPainterDecorationRenderer //**************************************** SceneQPainterDecorationRenderer::SceneQPainterDecorationRenderer(Decoration::DecoratedClientImpl *client) : Renderer(client) { connect(this, &Renderer::renderScheduled, client->client(), static_cast(&AbstractClient::addRepaint)); } SceneQPainterDecorationRenderer::~SceneQPainterDecorationRenderer() = default; QImage SceneQPainterDecorationRenderer::image(SceneQPainterDecorationRenderer::DecorationPart part) const { Q_ASSERT(part != DecorationPart::Count); return m_images[int(part)]; } void SceneQPainterDecorationRenderer::render() { const QRegion scheduled = getScheduled(); if (scheduled.isEmpty()) { return; } if (areImageSizesDirty()) { resizeImages(); resetImageSizesDirty(); } - const QRect top(QPoint(0, 0), m_images[int(DecorationPart::Top)].size()); - const QRect left(QPoint(0, top.height()), m_images[int(DecorationPart::Left)].size()); - const QRect right(QPoint(top.width() - m_images[int(DecorationPart::Right)].size().width(), top.height()), m_images[int(DecorationPart::Right)].size()); - const QRect bottom(QPoint(0, left.y() + left.height()), m_images[int(DecorationPart::Bottom)].size()); + auto imageSize = [this](DecorationPart part) { + return m_images[int(part)].size() / m_images[int(part)].devicePixelRatio(); + }; + + const QRect top(QPoint(0, 0), imageSize(DecorationPart::Top)); + const QRect left(QPoint(0, top.height()), imageSize(DecorationPart::Left)); + const QRect right(QPoint(top.width() - imageSize(DecorationPart::Right).width(), top.height()), imageSize(DecorationPart::Right)); + const QRect bottom(QPoint(0, left.y() + left.height()), imageSize(DecorationPart::Bottom)); const QRect geometry = scheduled.boundingRect(); auto renderPart = [this](const QRect &rect, const QRect &partRect, int index) { if (rect.isEmpty()) { return; } QPainter painter(&m_images[index]); painter.setRenderHint(QPainter::Antialiasing); - painter.setWindow(partRect); + painter.setWindow(QRect(partRect.topLeft(), partRect.size() * m_images[index].devicePixelRatio())); painter.setClipRect(rect); painter.save(); // clear existing part painter.setCompositionMode(QPainter::CompositionMode_Source); painter.fillRect(rect, Qt::transparent); painter.restore(); client()->decoration()->paint(&painter, rect); }; renderPart(left.intersected(geometry), left, int(DecorationPart::Left)); renderPart(top.intersected(geometry), top, int(DecorationPart::Top)); renderPart(right.intersected(geometry), right, int(DecorationPart::Right)); renderPart(bottom.intersected(geometry), bottom, int(DecorationPart::Bottom)); } void SceneQPainterDecorationRenderer::resizeImages() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); auto checkAndCreate = [this](int index, const QSize &size) { - if (m_images[index].size() != size) { - m_images[index] = QImage(size, QImage::Format_ARGB32_Premultiplied); + auto dpr = client()->client()->screenScale(); + if (m_images[index].size() != size * dpr || + m_images[index].devicePixelRatio() != dpr) + { + m_images[index] = QImage(size * dpr, QImage::Format_ARGB32_Premultiplied); + m_images[index].setDevicePixelRatio(dpr); m_images[index].fill(Qt::transparent); } }; checkAndCreate(int(DecorationPart::Left), left.size()); checkAndCreate(int(DecorationPart::Right), right.size()); checkAndCreate(int(DecorationPart::Top), top.size()); checkAndCreate(int(DecorationPart::Bottom), bottom.size()); } void SceneQPainterDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } QPainterFactory::QPainterFactory(QObject *parent) : SceneFactory(parent) { } QPainterFactory::~QPainterFactory() = default; Scene *QPainterFactory::create(QObject *parent) const { auto s = SceneQPainter::createScene(parent); if (s && s->initFailed()) { delete s; s = nullptr; } return s; } } // KWin diff --git a/plugins/scenes/xrender/CMakeLists.txt b/plugins/scenes/xrender/CMakeLists.txt index 61ecea5ea..2f16fc4cd 100644 --- a/plugins/scenes/xrender/CMakeLists.txt +++ b/plugins/scenes/xrender/CMakeLists.txt @@ -1,26 +1,27 @@ set(SCENE_XRENDER_SRCS scene_xrender.cpp) include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category( SCENE_XRENDER_SRCS HEADER logging.h IDENTIFIER KWIN_XRENDER CATEGORY_NAME kwin_scene_xrender DEFAULT_SEVERITY Critical ) add_library(KWinSceneXRender MODULE ${SCENE_XRENDER_SRCS}) +set_target_properties(KWinSceneXRender PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kwin.scenes/") target_link_libraries(KWinSceneXRender kwin kwinxrenderutils ) install( TARGETS KWinSceneXRender DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kwin.scenes/ ) diff --git a/plugins/scenes/xrender/scene_xrender.cpp b/plugins/scenes/xrender/scene_xrender.cpp index df1702699..7e0864a07 100644 --- a/plugins/scenes/xrender/scene_xrender.cpp +++ b/plugins/scenes/xrender/scene_xrender.cpp @@ -1,1325 +1,1326 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Fredrik Höglund Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "scene_xrender.h" #include "utils.h" #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include "logging.h" #include "toplevel.h" #include "client.h" #include "composite.h" #include "deleted.h" #include "effects.h" #include "main.h" #include "overlaywindow.h" #include "platform.h" #include "screens.h" #include "xcbutils.h" #include "kwinxrenderutils.h" #include "decorations/decoratedclient.h" #include #include #include #include namespace KWin { ScreenPaintData SceneXrender::screen_paint; #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) #define FIXED_TO_DOUBLE(f) ((double) ((f) / 65536.0)) //**************************************** // XRenderBackend //**************************************** XRenderBackend::XRenderBackend() : m_buffer(XCB_RENDER_PICTURE_NONE) , m_failed(false) { if (!Xcb::Extensions::self()->isRenderAvailable()) { setFailed("No XRender extension available"); return; } if (!Xcb::Extensions::self()->isFixesRegionAvailable()) { setFailed("No XFixes v3+ extension available"); return; } } XRenderBackend::~XRenderBackend() { if (m_buffer) { xcb_render_free_picture(connection(), m_buffer); } } OverlayWindow* XRenderBackend::overlayWindow() { return NULL; } void XRenderBackend::showOverlay() { } void XRenderBackend::setBuffer(xcb_render_picture_t buffer) { if (m_buffer != XCB_RENDER_PICTURE_NONE) { xcb_render_free_picture(connection(), m_buffer); } m_buffer = buffer; } void XRenderBackend::setFailed(const QString& reason) { qCCritical(KWIN_XRENDER) << "Creating the XRender backend failed: " << reason; m_failed = true; } void XRenderBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) } //**************************************** // X11XRenderBackend //**************************************** X11XRenderBackend::X11XRenderBackend() : XRenderBackend() , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) , m_front(XCB_RENDER_PICTURE_NONE) , m_format(0) { init(true); } X11XRenderBackend::~X11XRenderBackend() { if (m_front) { xcb_render_free_picture(connection(), m_front); } m_overlayWindow->destroy(); } OverlayWindow* X11XRenderBackend::overlayWindow() { return m_overlayWindow.data(); } void X11XRenderBackend::showOverlay() { if (m_overlayWindow->window()) // show the window only after the first pass, since m_overlayWindow->show(); // that pass may take long } void X11XRenderBackend::init(bool createOverlay) { if (m_front != XCB_RENDER_PICTURE_NONE) xcb_render_free_picture(connection(), m_front); bool haveOverlay = createOverlay ? m_overlayWindow->create() : (m_overlayWindow->window() != XCB_WINDOW_NONE); if (haveOverlay) { m_overlayWindow->setup(XCB_WINDOW_NONE); ScopedCPointer attribs(xcb_get_window_attributes_reply(connection(), xcb_get_window_attributes_unchecked(connection(), m_overlayWindow->window()), NULL)); if (!attribs) { setFailed("Failed getting window attributes for overlay window"); return; } m_format = XRenderUtils::findPictFormat(attribs->visual); if (m_format == 0) { setFailed("Failed to find XRender format for overlay window"); return; } m_front = xcb_generate_id(connection()); xcb_render_create_picture(connection(), m_front, m_overlayWindow->window(), m_format, 0, NULL); } else { // create XRender picture for the root window m_format = XRenderUtils::findPictFormat(defaultScreen()->root_visual); if (m_format == 0) { setFailed("Failed to find XRender format for root window"); return; // error } m_front = xcb_generate_id(connection()); const uint32_t values[] = {XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS}; xcb_render_create_picture(connection(), m_front, rootWindow(), m_format, XCB_RENDER_CP_SUBWINDOW_MODE, values); } createBuffer(); } void X11XRenderBackend::createBuffer() { xcb_pixmap_t pixmap = xcb_generate_id(connection()); const auto displaySize = screens()->displaySize(); xcb_create_pixmap(connection(), Xcb::defaultDepth(), pixmap, rootWindow(), displaySize.width(), displaySize.height()); xcb_render_picture_t b = xcb_generate_id(connection()); xcb_render_create_picture(connection(), b, pixmap, m_format, 0, NULL); xcb_free_pixmap(connection(), pixmap); // The picture owns the pixmap now setBuffer(b); } void X11XRenderBackend::present(int mask, const QRegion &damage) { const auto displaySize = screens()->displaySize(); if (mask & Scene::PAINT_SCREEN_REGION) { // Use the damage region as the clip region for the root window XFixesRegion frontRegion(damage); xcb_xfixes_set_picture_clip_region(connection(), m_front, frontRegion, 0, 0); // copy composed buffer to the root window xcb_xfixes_set_picture_clip_region(connection(), buffer(), XCB_XFIXES_REGION_NONE, 0, 0); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_SRC, buffer(), XCB_RENDER_PICTURE_NONE, m_front, 0, 0, 0, 0, 0, 0, displaySize.width(), displaySize.height()); xcb_xfixes_set_picture_clip_region(connection(), m_front, XCB_XFIXES_REGION_NONE, 0, 0); xcb_flush(connection()); } else { // copy composed buffer to the root window xcb_render_composite(connection(), XCB_RENDER_PICT_OP_SRC, buffer(), XCB_RENDER_PICTURE_NONE, m_front, 0, 0, 0, 0, 0, 0, displaySize.width(), displaySize.height()); xcb_flush(connection()); } } void X11XRenderBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) init(false); } bool X11XRenderBackend::usesOverlayWindow() const { return true; } //**************************************** // SceneXrender //**************************************** SceneXrender* SceneXrender::createScene(QObject *parent) { QScopedPointer backend; backend.reset(new X11XRenderBackend); if (backend->isFailed()) { return NULL; } return new SceneXrender(backend.take(), parent); } SceneXrender::SceneXrender(XRenderBackend *backend, QObject *parent) : Scene(parent) , m_backend(backend) { } SceneXrender::~SceneXrender() { SceneXrender::Window::cleanup(); SceneXrender::EffectFrame::cleanup(); } bool SceneXrender::initFailed() const { return false; } // the entry point for painting qint64 SceneXrender::paint(QRegion damage, ToplevelList toplevels) { QElapsedTimer renderTimer; renderTimer.start(); createStackingOrder(toplevels); int mask = 0; QRegion updateRegion, validRegion; paintScreen(&mask, damage, QRegion(), &updateRegion, &validRegion); m_backend->showOverlay(); m_backend->present(mask, updateRegion); // do cleanup clearStackingOrder(); return renderTimer.nsecsElapsed(); } void SceneXrender::paintGenericScreen(int mask, ScreenPaintData data) { screen_paint = data; // save, transformations will be done when painting windows Scene::paintGenericScreen(mask, data); } void SceneXrender::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { PaintClipper::push(region); KWin::Scene::paintDesktop(desktop, mask, region, data); PaintClipper::pop(region); } // fill the screen background void SceneXrender::paintBackground(QRegion region) { xcb_render_color_t col = { 0, 0, 0, 0xffff }; // black const QVector &rects = Xcb::regionToRects(region); xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, xrenderBufferPicture(), col, rects.count(), rects.data()); } Scene::Window *SceneXrender::createWindow(Toplevel *toplevel) { return new Window(toplevel, this); } Scene::EffectFrame *SceneXrender::createEffectFrame(EffectFrameImpl *frame) { return new SceneXrender::EffectFrame(frame); } Shadow *SceneXrender::createShadow(Toplevel *toplevel) { return new SceneXRenderShadow(toplevel); } Decoration::Renderer *SceneXrender::createDecorationRenderer(Decoration::DecoratedClientImpl* client) { return new SceneXRenderDecorationRenderer(client); } //**************************************** // SceneXrender::Window //**************************************** XRenderPicture *SceneXrender::Window::s_tempPicture = 0; QRect SceneXrender::Window::temp_visibleRect; XRenderPicture *SceneXrender::Window::s_fadeAlphaPicture = nullptr; SceneXrender::Window::Window(Toplevel* c, SceneXrender *scene) : Scene::Window(c) , m_scene(scene) , format(XRenderUtils::findPictFormat(c->visual())) { } SceneXrender::Window::~Window() { discardShape(); } void SceneXrender::Window::cleanup() { delete s_tempPicture; s_tempPicture = NULL; delete s_fadeAlphaPicture; s_fadeAlphaPicture = nullptr; } // Maps window coordinates to screen coordinates QRect SceneXrender::Window::mapToScreen(int mask, const WindowPaintData &data, const QRect &rect) const { QRect r = rect; if (mask & PAINT_WINDOW_TRANSFORMED) { // Apply the window transformation r.moveTo(r.x() * data.xScale() + data.xTranslation(), r.y() * data.yScale() + data.yTranslation()); r.setWidth(r.width() * data.xScale()); r.setHeight(r.height() * data.yScale()); } // Move the rectangle to the screen position r.translate(x(), y()); if (mask & PAINT_SCREEN_TRANSFORMED) { // Apply the screen transformation r.moveTo(r.x() * screen_paint.xScale() + screen_paint.xTranslation(), r.y() * screen_paint.yScale() + screen_paint.yTranslation()); r.setWidth(r.width() * screen_paint.xScale()); r.setHeight(r.height() * screen_paint.yScale()); } return r; } // Maps window coordinates to screen coordinates QPoint SceneXrender::Window::mapToScreen(int mask, const WindowPaintData &data, const QPoint &point) const { QPoint pt = point; if (mask & PAINT_WINDOW_TRANSFORMED) { // Apply the window transformation pt.rx() = pt.x() * data.xScale() + data.xTranslation(); pt.ry() = pt.y() * data.yScale() + data.yTranslation(); } // Move the point to the screen position pt += QPoint(x(), y()); if (mask & PAINT_SCREEN_TRANSFORMED) { // Apply the screen transformation pt.rx() = pt.x() * screen_paint.xScale() + screen_paint.xTranslation(); pt.ry() = pt.y() * screen_paint.yScale() + screen_paint.yTranslation(); } return pt; } void SceneXrender::Window::prepareTempPixmap() { const QSize oldSize = temp_visibleRect.size(); temp_visibleRect = toplevel->visibleRect().translated(-toplevel->pos()); if (s_tempPicture && (oldSize.width() < temp_visibleRect.width() || oldSize.height() < temp_visibleRect.height())) { delete s_tempPicture; s_tempPicture = NULL; scene_setXRenderOffscreenTarget(0); // invalidate, better crash than cause weird results for developers } if (!s_tempPicture) { xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 32, pix, rootWindow(), temp_visibleRect.width(), temp_visibleRect.height()); s_tempPicture = new XRenderPicture(pix, 32); xcb_free_pixmap(connection(), pix); } const xcb_render_color_t transparent = {0, 0, 0, 0}; const xcb_rectangle_t rect = {0, 0, uint16_t(temp_visibleRect.width()), uint16_t(temp_visibleRect.height())}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_tempPicture, transparent, 1, &rect); } // paint the window void SceneXrender::Window::performPaint(int mask, QRegion region, WindowPaintData data) { setTransformedShape(QRegion()); // maybe nothing will be painted // check if there is something to paint bool opaque = isOpaque() && qFuzzyCompare(data.opacity(), 1.0); /* HACK: It seems this causes painting glitches, disable temporarily if (( mask & PAINT_WINDOW_OPAQUE ) ^ ( mask & PAINT_WINDOW_TRANSLUCENT )) { // We are only painting either opaque OR translucent windows, not both if ( mask & PAINT_WINDOW_OPAQUE && !opaque ) return; // Only painting opaque and window is translucent if ( mask & PAINT_WINDOW_TRANSLUCENT && opaque ) return; // Only painting translucent and window is opaque }*/ // Intersect the clip region with the rectangle the window occupies on the screen if (!(mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) region &= toplevel->visibleRect(); if (region.isEmpty()) return; XRenderWindowPixmap *pixmap = windowPixmap(); if (!pixmap || !pixmap->isValid()) { return; } xcb_render_picture_t pic = pixmap->picture(); if (pic == XCB_RENDER_PICTURE_NONE) // The render format can be null for GL and/or Xv visuals return; toplevel->resetDamage(); // set picture filter if (options->isXrenderSmoothScale()) { // only when forced, it's slow if (mask & PAINT_WINDOW_TRANSFORMED) filter = ImageFilterGood; else if (mask & PAINT_SCREEN_TRANSFORMED) filter = ImageFilterGood; else filter = ImageFilterFast; } else filter = ImageFilterFast; // do required transformations const QRect wr = mapToScreen(mask, data, QRect(0, 0, width(), height())); QRect cr = QRect(toplevel->clientPos(), toplevel->clientSize()); // Client rect (in the window) qreal xscale = 1; qreal yscale = 1; bool scaled = false; Client *client = dynamic_cast(toplevel); Deleted *deleted = dynamic_cast(toplevel); const QRect decorationRect = toplevel->decorationRect(); if (((client && !client->noBorder()) || (deleted && !deleted->noBorder())) && true) { // decorated client transformed_shape = decorationRect; if (toplevel->shape()) { // "xeyes" + decoration transformed_shape -= cr; transformed_shape += shape(); } } else { transformed_shape = shape(); } if (toplevel->hasShadow()) transformed_shape |= toplevel->shadow()->shadowRegion(); xcb_render_transform_t xform = { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; static const xcb_render_transform_t identity = { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; if (mask & PAINT_WINDOW_TRANSFORMED) { xscale = data.xScale(); yscale = data.yScale(); } if (mask & PAINT_SCREEN_TRANSFORMED) { xscale *= screen_paint.xScale(); yscale *= screen_paint.yScale(); } if (!qFuzzyCompare(xscale, 1.0) || !qFuzzyCompare(yscale, 1.0)) { scaled = true; xform.matrix11 = DOUBLE_TO_FIXED(1.0 / xscale); xform.matrix22 = DOUBLE_TO_FIXED(1.0 / yscale); // transform the shape for clipping in paintTransformedScreen() QVector rects = transformed_shape.rects(); for (int i = 0; i < rects.count(); ++i) { QRect& r = rects[ i ]; r.setRect(qRound(r.x() * xscale), qRound(r.y() * yscale), qRound(r.width() * xscale), qRound(r.height() * yscale)); } transformed_shape.setRects(rects.constData(), rects.count()); } transformed_shape.translate(mapToScreen(mask, data, QPoint(0, 0))); PaintClipper pcreg(region); // clip by the region to paint PaintClipper pc(transformed_shape); // clip by window's shape const bool wantShadow = m_shadow && !m_shadow->shadowRegion().isEmpty(); // In order to obtain a pixel perfect rescaling // we need to blit the window content togheter with // decorations in a temporary pixmap and scale // the temporary pixmap at the end. // We should do this only if there is scaling and // the window has border // This solves a number of glitches and on top of this // it optimizes painting quite a bit const bool blitInTempPixmap = xRenderOffscreen() || (data.crossFadeProgress() < 1.0 && !opaque) || (scaled && (wantShadow || (client && !client->noBorder()) || (deleted && !deleted->noBorder()))); xcb_render_picture_t renderTarget = m_scene->xrenderBufferPicture(); if (blitInTempPixmap) { if (scene_xRenderOffscreenTarget()) { temp_visibleRect = toplevel->visibleRect().translated(-toplevel->pos()); renderTarget = *scene_xRenderOffscreenTarget(); } else { prepareTempPixmap(); renderTarget = *s_tempPicture; } } else { xcb_render_set_picture_transform(connection(), pic, xform); if (filter == ImageFilterGood) { setPictureFilter(pic, KWin::Scene::ImageFilterGood); } //BEGIN OF STUPID RADEON HACK // This is needed to avoid hitting a fallback in the radeon driver. // The Render specification states that sampling pixels outside the // source picture results in alpha=0 pixels. This can be achieved by // setting the border color to transparent black, but since the border // color has the same format as the texture, it only works when the // texture has an alpha channel. So the driver falls back to software // when the repeat mode is RepeatNone, the picture has a non-identity // transformation matrix, and doesn't have an alpha channel. // Since we only scale the picture, we can work around this by setting // the repeat mode to RepeatPad. if (!window()->hasAlpha()) { const uint32_t values[] = {XCB_RENDER_REPEAT_PAD}; xcb_render_change_picture(connection(), pic, XCB_RENDER_CP_REPEAT, values); } //END OF STUPID RADEON HACK } #define MAP_RECT_TO_TARGET(_RECT_) \ if (blitInTempPixmap) _RECT_.translate(-temp_visibleRect.topLeft()); else _RECT_ = mapToScreen(mask, data, _RECT_) //BEGIN deco preparations bool noBorder = true; xcb_render_picture_t left = XCB_RENDER_PICTURE_NONE; xcb_render_picture_t top = XCB_RENDER_PICTURE_NONE; xcb_render_picture_t right = XCB_RENDER_PICTURE_NONE; xcb_render_picture_t bottom = XCB_RENDER_PICTURE_NONE; QRect dtr, dlr, drr, dbr; const SceneXRenderDecorationRenderer *renderer = nullptr; if (client) { if (client && !client->noBorder()) { if (client->isDecorated()) { SceneXRenderDecorationRenderer *r = static_cast(client->decoratedClient()->renderer()); if (r) { r->render(); renderer = r; } } noBorder = client->noBorder(); client->layoutDecorationRects(dlr, dtr, drr, dbr); } } if (deleted && !deleted->noBorder()) { renderer = static_cast(deleted->decorationRenderer()); noBorder = deleted->noBorder(); deleted->layoutDecorationRects(dlr, dtr, drr, dbr); } if (renderer) { left = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Left); top = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Top); right = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Right); bottom = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Bottom); } if (!noBorder) { MAP_RECT_TO_TARGET(dtr); MAP_RECT_TO_TARGET(dlr); MAP_RECT_TO_TARGET(drr); MAP_RECT_TO_TARGET(dbr); } //END deco preparations //BEGIN shadow preparations QRect stlr, str, strr, srr, sbrr, sbr, sblr, slr; SceneXRenderShadow* m_xrenderShadow = static_cast(m_shadow); if (wantShadow) { m_xrenderShadow->layoutShadowRects(str, strr, srr, sbrr, sbr, sblr, slr, stlr); MAP_RECT_TO_TARGET(stlr); MAP_RECT_TO_TARGET(str); MAP_RECT_TO_TARGET(strr); MAP_RECT_TO_TARGET(srr); MAP_RECT_TO_TARGET(sbrr); MAP_RECT_TO_TARGET(sbr); MAP_RECT_TO_TARGET(sblr); MAP_RECT_TO_TARGET(slr); } //BEGIN end preparations //BEGIN client preparations QRect dr = cr; if (blitInTempPixmap) { dr.translate(-temp_visibleRect.topLeft()); } else { dr = mapToScreen(mask, data, dr); // Destination rect if (scaled) { cr.moveLeft(cr.x() * xscale); cr.moveTop(cr.y() * yscale); } } const int clientRenderOp = (opaque || blitInTempPixmap) ? XCB_RENDER_PICT_OP_SRC : XCB_RENDER_PICT_OP_OVER; //END client preparations #undef MAP_RECT_TO_TARGET for (PaintClipper::Iterator iterator; !iterator.isDone(); iterator.next()) { #define RENDER_SHADOW_TILE(_TILE_, _RECT_) \ xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, m_xrenderShadow->picture(SceneXRenderShadow::ShadowElement##_TILE_), \ shadowAlpha, renderTarget, 0, 0, 0, 0, _RECT_.x(), _RECT_.y(), _RECT_.width(), _RECT_.height()) //shadow if (wantShadow) { xcb_render_picture_t shadowAlpha = XCB_RENDER_PICTURE_NONE; if (!opaque) { shadowAlpha = xRenderBlendPicture(data.opacity()); } RENDER_SHADOW_TILE(TopLeft, stlr); RENDER_SHADOW_TILE(Top, str); RENDER_SHADOW_TILE(TopRight, strr); RENDER_SHADOW_TILE(Left, slr); RENDER_SHADOW_TILE(Right, srr); RENDER_SHADOW_TILE(BottomLeft, sblr); RENDER_SHADOW_TILE(Bottom, sbr); RENDER_SHADOW_TILE(BottomRight, sbrr); } #undef RENDER_SHADOW_TILE // Paint the window contents if (!(client && client->isShade())) { xcb_render_picture_t clientAlpha = XCB_RENDER_PICTURE_NONE; if (!opaque) { clientAlpha = xRenderBlendPicture(data.opacity()); } xcb_render_composite(connection(), clientRenderOp, pic, clientAlpha, renderTarget, cr.x(), cr.y(), 0, 0, dr.x(), dr.y(), dr.width(), dr.height()); if (data.crossFadeProgress() < 1.0 && data.crossFadeProgress() > 0.0) { XRenderWindowPixmap *previous = previousWindowPixmap(); if (previous && previous != pixmap) { static xcb_render_color_t cFadeColor = {0, 0, 0, 0}; cFadeColor.alpha = uint16_t((1.0 - data.crossFadeProgress()) * 0xffff); if (!s_fadeAlphaPicture) { s_fadeAlphaPicture = new XRenderPicture(xRenderFill(cFadeColor)); } else { xcb_rectangle_t rect = {0, 0, 1, 1}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_fadeAlphaPicture, cFadeColor , 1, &rect); } if (previous->size() != pixmap->size()) { xcb_render_transform_t xform2 = { DOUBLE_TO_FIXED(FIXED_TO_DOUBLE(xform.matrix11) * previous->size().width() / pixmap->size().width()), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(FIXED_TO_DOUBLE(xform.matrix22) * previous->size().height() / pixmap->size().height()), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; xcb_render_set_picture_transform(connection(), previous->picture(), xform2); } xcb_render_composite(connection(), opaque ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_ATOP, previous->picture(), *s_fadeAlphaPicture, renderTarget, cr.x(), cr.y(), 0, 0, dr.x(), dr.y(), dr.width(), dr.height()); if (previous->size() != pixmap->size()) { xcb_render_set_picture_transform(connection(), previous->picture(), identity); } } } if (!opaque) transformed_shape = QRegion(); } if (client || deleted) { if (!noBorder) { xcb_render_picture_t decorationAlpha = xRenderBlendPicture(data.opacity()); auto renderDeco = [decorationAlpha, renderTarget](xcb_render_picture_t deco, const QRect &rect) { if (deco == XCB_RENDER_PICTURE_NONE) { return; } xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, deco, decorationAlpha, renderTarget, 0, 0, 0, 0, rect.x(), rect.y(), rect.width(), rect.height()); }; renderDeco(top, dtr); renderDeco(left, dlr); renderDeco(right, drr); renderDeco(bottom, dbr); } } if (data.brightness() != 1.0) { // fake brightness change by overlaying black const float alpha = (1 - data.brightness()) * data.opacity(); xcb_rectangle_t rect; if (blitInTempPixmap) { rect.x = -temp_visibleRect.left(); rect.y = -temp_visibleRect.top(); rect.width = width(); rect.height = height(); } else { rect.x = wr.x(); rect.y = wr.y(); rect.width = wr.width(); rect.height = wr.height(); } xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_OVER, renderTarget, preMultiply(data.brightness() < 1.0 ? QColor(0,0,0,255*alpha) : QColor(255,255,255,-alpha*255)), 1, &rect); } if (blitInTempPixmap) { const QRect r = mapToScreen(mask, data, temp_visibleRect); xcb_render_set_picture_transform(connection(), *s_tempPicture, xform); setPictureFilter(*s_tempPicture, filter); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *s_tempPicture, XCB_RENDER_PICTURE_NONE, m_scene->xrenderBufferPicture(), 0, 0, 0, 0, r.x(), r.y(), r.width(), r.height()); xcb_render_set_picture_transform(connection(), *s_tempPicture, identity); } } if (scaled && !blitInTempPixmap) { xcb_render_set_picture_transform(connection(), pic, identity); if (filter == ImageFilterGood) setPictureFilter(pic, KWin::Scene::ImageFilterFast); if (!window()->hasAlpha()) { const uint32_t values[] = {XCB_RENDER_REPEAT_NONE}; xcb_render_change_picture(connection(), pic, XCB_RENDER_CP_REPEAT, values); } } if (xRenderOffscreen()) scene_setXRenderOffscreenTarget(*s_tempPicture); } void SceneXrender::Window::setPictureFilter(xcb_render_picture_t pic, Scene::ImageFilterType filter) { QByteArray filterName; switch (filter) { case KWin::Scene::ImageFilterFast: filterName = QByteArray("fast"); break; case KWin::Scene::ImageFilterGood: filterName = QByteArray("good"); break; } xcb_render_set_picture_filter(connection(), pic, filterName.length(), filterName.constData(), 0, NULL); } WindowPixmap* SceneXrender::Window::createWindowPixmap() { return new XRenderWindowPixmap(this, format); } void SceneXrender::screenGeometryChanged(const QSize &size) { Scene::screenGeometryChanged(size); m_backend->screenGeometryChanged(size); } //**************************************** // XRenderWindowPixmap //**************************************** XRenderWindowPixmap::XRenderWindowPixmap(Scene::Window *window, xcb_render_pictformat_t format) : WindowPixmap(window) , m_picture(XCB_RENDER_PICTURE_NONE) , m_format(format) { } XRenderWindowPixmap::~XRenderWindowPixmap() { if (m_picture != XCB_RENDER_PICTURE_NONE) { xcb_render_free_picture(connection(), m_picture); } } void XRenderWindowPixmap::create() { if (isValid()) { return; } KWin::WindowPixmap::create(); if (!isValid()) { return; } m_picture = xcb_generate_id(connection()); xcb_render_create_picture(connection(), m_picture, pixmap(), m_format, 0, NULL); } //**************************************** // SceneXrender::EffectFrame //**************************************** XRenderPicture *SceneXrender::EffectFrame::s_effectFrameCircle = NULL; SceneXrender::EffectFrame::EffectFrame(EffectFrameImpl* frame) : Scene::EffectFrame(frame) { m_picture = NULL; m_textPicture = NULL; m_iconPicture = NULL; m_selectionPicture = NULL; } SceneXrender::EffectFrame::~EffectFrame() { delete m_picture; delete m_textPicture; delete m_iconPicture; delete m_selectionPicture; } void SceneXrender::EffectFrame::cleanup() { delete s_effectFrameCircle; s_effectFrameCircle = NULL; } void SceneXrender::EffectFrame::free() { delete m_picture; m_picture = NULL; delete m_textPicture; m_textPicture = NULL; delete m_iconPicture; m_iconPicture = NULL; delete m_selectionPicture; m_selectionPicture = NULL; } void SceneXrender::EffectFrame::freeIconFrame() { delete m_iconPicture; m_iconPicture = NULL; } void SceneXrender::EffectFrame::freeTextFrame() { delete m_textPicture; m_textPicture = NULL; } void SceneXrender::EffectFrame::freeSelection() { delete m_selectionPicture; m_selectionPicture = NULL; } void SceneXrender::EffectFrame::crossFadeIcon() { // TODO: implement me } void SceneXrender::EffectFrame::crossFadeText() { // TODO: implement me } void SceneXrender::EffectFrame::render(QRegion region, double opacity, double frameOpacity) { Q_UNUSED(region) if (m_effectFrame->geometry().isEmpty()) { return; // Nothing to display } // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { renderUnstyled(effects->xrenderBufferPicture(), m_effectFrame->geometry(), opacity * frameOpacity); } else if (m_effectFrame->style() == EffectFrameStyled) { if (!m_picture) { // Lazy creation updatePicture(); } if (m_picture) { qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry QRect geom = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_picture, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height()); } } if (!m_effectFrame->selection().isNull()) { if (!m_selectionPicture) { // Lazy creation const QPixmap pix = m_effectFrame->selectionFrame().framePixmap(); if (!pix.isNull()) // don't try if there's no content m_selectionPicture = new XRenderPicture(m_effectFrame->selectionFrame().framePixmap().toImage()); } if (m_selectionPicture) { const QRect geom = m_effectFrame->selection(); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_selectionPicture, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height()); } } XRenderPicture fill = xRenderBlendPicture(opacity); // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); if (!m_iconPicture) // lazy creation m_iconPicture = new XRenderPicture(m_effectFrame->icon().pixmap(m_effectFrame->iconSize()).toImage()); QRect geom = QRect(topLeft, m_effectFrame->iconSize()); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_iconPicture, fill, effects->xrenderBufferPicture(), 0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height()); } // Render text if (!m_effectFrame->text().isEmpty()) { if (!m_textPicture) { // Lazy creation updateTextPicture(); } if (m_textPicture) { xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_textPicture, fill, effects->xrenderBufferPicture(), 0, 0, 0, 0, m_effectFrame->geometry().x(), m_effectFrame->geometry().y(), m_effectFrame->geometry().width(), m_effectFrame->geometry().height()); } } } void SceneXrender::EffectFrame::renderUnstyled(xcb_render_picture_t pict, const QRect &rect, qreal opacity) { const int roundness = 5; const QRect area = rect.adjusted(-roundness, -roundness, roundness, roundness); xcb_rectangle_t rects[3]; // center rects[0].x = area.left(); rects[0].y = area.top() + roundness; rects[0].width = area.width(); rects[0].height = area.height() - roundness * 2; // top rects[1].x = area.left() + roundness; rects[1].y = area.top(); rects[1].width = area.width() - roundness * 2; rects[1].height = roundness; // bottom rects[2].x = area.left() + roundness; rects[2].y = area.top() + area.height() - roundness; rects[2].width = area.width() - roundness * 2; rects[2].height = roundness; xcb_render_color_t color = {0, 0, 0, uint16_t(opacity * 0xffff)}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_OVER, pict, color, 3, rects); if (!s_effectFrameCircle) { // create the circle const int diameter = roundness * 2; xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 32, pix, rootWindow(), diameter, diameter); s_effectFrameCircle = new XRenderPicture(pix, 32); xcb_free_pixmap(connection(), pix); // clear it with transparent xcb_rectangle_t xrect = {0, 0, diameter, diameter}; xcb_render_color_t tranparent = {0, 0, 0, 0}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_effectFrameCircle, tranparent, 1, &xrect); static const int num_segments = 80; static const qreal theta = 2 * M_PI / qreal(num_segments); static const qreal c = qCos(theta); //precalculate the sine and cosine static const qreal s = qSin(theta); qreal t; qreal x = roundness;//we start at angle = 0 qreal y = 0; QVector points; xcb_render_pointfix_t point; point.x = DOUBLE_TO_FIXED(roundness); point.y = DOUBLE_TO_FIXED(roundness); points << point; for (int ii = 0; ii <= num_segments; ++ii) { point.x = DOUBLE_TO_FIXED(x + roundness); point.y = DOUBLE_TO_FIXED(y + roundness); points << point; //apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } XRenderPicture fill = xRenderFill(Qt::black); xcb_render_tri_fan(connection(), XCB_RENDER_PICT_OP_OVER, fill, *s_effectFrameCircle, 0, 0, 0, points.count(), points.constData()); } // TODO: merge alpha mask with SceneXrender::Window::alphaMask // alpha mask xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 8, pix, rootWindow(), 1, 1); XRenderPicture alphaMask(pix, 8); xcb_free_pixmap(connection(), pix); const uint32_t values[] = {true}; xcb_render_change_picture(connection(), alphaMask, XCB_RENDER_CP_REPEAT, values); color.alpha = int(opacity * 0xffff); xcb_rectangle_t xrect = {0, 0, 1, 1}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, alphaMask, color, 1, &xrect); // TODO: replace by lambda #define RENDER_CIRCLE(srcX, srcY, destX, destY) \ xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *s_effectFrameCircle, alphaMask, \ pict, srcX, srcY, 0, 0, destX, destY, roundness, roundness) RENDER_CIRCLE(0, 0, area.left(), area.top()); RENDER_CIRCLE(0, roundness, area.left(), area.top() + area.height() - roundness); RENDER_CIRCLE(roundness, 0, area.left() + area.width() - roundness, area.top()); RENDER_CIRCLE(roundness, roundness, area.left() + area.width() - roundness, area.top() + area.height() - roundness); #undef RENDER_CIRCLE } void SceneXrender::EffectFrame::updatePicture() { delete m_picture; m_picture = 0L; if (m_effectFrame->style() == EffectFrameStyled) { const QPixmap pix = m_effectFrame->frame().framePixmap(); if (!pix.isNull()) m_picture = new XRenderPicture(pix.toImage()); } } void SceneXrender::EffectFrame::updateTextPicture() { // Mostly copied from SceneOpenGL::EffectFrame::updateTextTexture() above delete m_textPicture; m_textPicture = 0L; if (m_effectFrame->text().isEmpty()) { return; } // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { rect.setLeft(m_effectFrame->iconSize().width()); } // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->text()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } QPixmap pixmap(m_effectFrame->geometry().size()); pixmap.fill(Qt::transparent); QPainter p(&pixmap); p.setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) { p.setPen(m_effectFrame->styledTextColor()); } else { // TODO: What about no frame? Custom color setting required p.setPen(Qt::white); } p.drawText(rect, m_effectFrame->alignment(), text); p.end(); m_textPicture = new XRenderPicture(pixmap.toImage()); } SceneXRenderShadow::SceneXRenderShadow(Toplevel *toplevel) :Shadow(toplevel) { for (int i=0; iclient(), static_cast(&AbstractClient::addRepaint)); for (int i = 0; i < int(DecorationPart::Count); ++i) { m_pixmaps[i] = XCB_PIXMAP_NONE; m_pictures[i] = nullptr; } } SceneXRenderDecorationRenderer::~SceneXRenderDecorationRenderer() { for (int i = 0; i < int(DecorationPart::Count); ++i) { if (m_pixmaps[i] != XCB_PIXMAP_NONE) { xcb_free_pixmap(connection(), m_pixmaps[i]); } delete m_pictures[i]; } if (m_gc != 0) { xcb_free_gc(connection(), m_gc); } } void SceneXRenderDecorationRenderer::render() { QRegion scheduled = getScheduled(); if (scheduled.isEmpty()) { return; } if (areImageSizesDirty()) { resizePixmaps(); resetImageSizesDirty(); scheduled = client()->client()->decorationRect(); } const QRect top(QPoint(0, 0), m_sizes[int(DecorationPart::Top)]); const QRect left(QPoint(0, top.height()), m_sizes[int(DecorationPart::Left)]); const QRect right(QPoint(top.width() - m_sizes[int(DecorationPart::Right)].width(), top.height()), m_sizes[int(DecorationPart::Right)]); const QRect bottom(QPoint(0, left.y() + left.height()), m_sizes[int(DecorationPart::Bottom)]); xcb_connection_t *c = connection(); if (m_gc == 0) { m_gc = xcb_generate_id(connection()); xcb_create_gc(c, m_gc, m_pixmaps[int(DecorationPart::Top)], 0, nullptr); } auto renderPart = [this, c](const QRect &geo, const QPoint &offset, int index) { if (geo.isNull()) { return; } QImage image = renderToImage(geo); + Q_ASSERT(image.devicePixelRatio() == 1); xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, m_pixmaps[index], m_gc, image.width(), image.height(), geo.x() - offset.x(), geo.y() - offset.y(), 0, 32, image.byteCount(), image.constBits()); }; const QRect geometry = scheduled.boundingRect(); renderPart(left.intersected(geometry), left.topLeft(), int(DecorationPart::Left)); renderPart(top.intersected(geometry), top.topLeft(), int(DecorationPart::Top)); renderPart(right.intersected(geometry), right.topLeft(), int(DecorationPart::Right)); renderPart(bottom.intersected(geometry), bottom.topLeft(), int(DecorationPart::Bottom)); xcb_flush(c); } void SceneXRenderDecorationRenderer::resizePixmaps() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); xcb_connection_t *c = connection(); auto checkAndCreate = [this, c](int border, const QRect &rect) { const QSize size = rect.size(); if (m_sizes[border] != size) { m_sizes[border] = size; if (m_pixmaps[border] != XCB_PIXMAP_NONE) { xcb_free_pixmap(c, m_pixmaps[border]); } delete m_pictures[border]; if (!size.isEmpty()) { m_pixmaps[border] = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 32, m_pixmaps[border], rootWindow(), size.width(), size.height()); m_pictures[border] = new XRenderPicture(m_pixmaps[border], 32); } else { m_pixmaps[border] = XCB_PIXMAP_NONE; m_pictures[border] = nullptr; } } if (!m_pictures[border]) { return; } // fill transparent xcb_rectangle_t r = {0, 0, uint16_t(size.width()), uint16_t(size.height())}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *m_pictures[border], preMultiply(Qt::transparent), 1, &r); }; checkAndCreate(int(DecorationPart::Left), left); checkAndCreate(int(DecorationPart::Top), top); checkAndCreate(int(DecorationPart::Right), right); checkAndCreate(int(DecorationPart::Bottom), bottom); } xcb_render_picture_t SceneXRenderDecorationRenderer::picture(SceneXRenderDecorationRenderer::DecorationPart part) const { Q_ASSERT(part != DecorationPart::Count); XRenderPicture *picture = m_pictures[int(part)]; if (!picture) { return XCB_RENDER_PICTURE_NONE; } return *picture; } void SceneXRenderDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } #undef DOUBLE_TO_FIXED #undef FIXED_TO_DOUBLE XRenderFactory::XRenderFactory(QObject *parent) : SceneFactory(parent) { } XRenderFactory::~XRenderFactory() = default; Scene *XRenderFactory::create(QObject *parent) const { auto s = SceneXrender::createScene(parent); if (s && s->initFailed()) { delete s; s = nullptr; } return s; } } // namespace #endif void KWin::SceneXrender::paintCursor() { } diff --git a/plugins/scenes/xrender/xrender.json b/plugins/scenes/xrender/xrender.json index 44a08c188..1d603655b 100644 --- a/plugins/scenes/xrender/xrender.json +++ b/plugins/scenes/xrender/xrender.json @@ -1,33 +1,41 @@ { "CompositingType": 2, "KPlugin": { "Description": "KWin Compositor plugin rendering through XRender", - "Description[ca@valencia]": "Connector del Compositor del KWin que renderitza a través de l'XRender", - "Description[ca]": "Connector del Compositor del KWin que renderitza a través de l'XRender", + "Description[ca@valencia]": "Connector del Compositor del KWin que renderitza a través del XRender", + "Description[ca]": "Connector del Compositor del KWin que renderitza a través del XRender", + "Description[da]": "KWin-compositorplugin som renderer igennem XRender", + "Description[de]": "KWin-Compositor-Modul zum Rendern mit XRender", "Description[el]": "Αποτύπωση πρσοθέτου συνθέτη KWin μέσω XRender", "Description[es]": "Complemento compositor de KWin renderizando mediante XRender", + "Description[fi]": "XRenderillä hahmontava KWin-koostajaliitännäinen", "Description[fr]": "Module du compositeur KWin effectuant le rendu avec XRender", + "Description[gl]": "Complemento de compositor de KWin que renderiza a través de XRender.", "Description[it]": "Estensione del compositore di KWin per la resa tramite XRender", + "Description[ko]": "XRender로 렌더링하는 KWin 컴포지터 플러그인", "Description[nl]": "KWin-compositor-plug-in rendering via XRender", "Description[pl]": "Wtyczka kompozytora KWin wyświetlająca przez XRender", "Description[pt]": "'Plugin' de Composição do KWin com desenho via XRender", "Description[pt_BR]": "Plugin do compositor KWin renderizando pelo XRender", + "Description[sk]": "Renderovací plugin kompozítora KWin cez XRender", + "Description[sl]": "Izrisovanje vstavka upravljalnika skladnje KWin preko XRender-ja", "Description[sr@ijekavian]": "К‑винов прикључак слагача за рендеровање кроз Икс‑рендер", "Description[sr@ijekavianlatin]": "KWinov priključak slagača za renderovanje kroz XRender", "Description[sr@latin]": "KWinov priključak slagača za renderovanje kroz XRender", "Description[sr]": "К‑винов прикључак слагача за рендеровање кроз Икс‑рендер", "Description[sv]": "Kwin sammansättningsinsticksprogram återger via XRender", + "Description[tr]": "XRender üzerinden KWin Dizgici eklentisi oluşturma", "Description[uk]": "Додаток засобу композиції KWin для обробки з використанням XRender", "Description[x-test]": "xxKWin Compositor plugin rendering through XRenderxx", "Description[zh_CN]": "使用 XRender 渲染的 KWin 合成插件", "Id": "KWinSceneXRender", "Name": "SceneXRender", "Name[pl]": "XRender sceny", "Name[sr@ijekavian]": "Икс‑рендер-сцена", "Name[sr@ijekavianlatin]": "XRender-scena", "Name[sr@latin]": "XRender-scena", "Name[sr]": "Икс‑рендер-сцена", "Name[sv]": "Scen XRender", "Name[x-test]": "xxSceneXRenderxx" } } diff --git a/scene.cpp b/scene.cpp index 36a459ad6..a12e97429 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1,1146 +1,1150 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* The base class for compositing, implementing shared functionality between the OpenGL and XRender backends. Design: When compositing is turned on, XComposite extension is used to redirect drawing of windows to pixmaps and XDamage extension is used to get informed about damage (changes) to window contents. This code is mostly in composite.cpp . Compositor::performCompositing() starts one painting pass. Painting is done by painting the screen, which in turn paints every window. Painting can be affected using effects, which are chained. E.g. painting a screen means that actually paintScreen() of the first effect is called, which possibly does modifications and calls next effect's paintScreen() and so on, until Scene::finalPaintScreen() is called. There are 3 phases of every paint (not necessarily done together): The pre-paint phase, the paint phase and the post-paint phase. The pre-paint phase is used to find out about how the painting will be actually done (i.e. what the effects will do). For example when only a part of the screen needs to be updated and no effect will do any transformation it is possible to use an optimized paint function. How the painting will be done is controlled by the mask argument, see PAINT_WINDOW_* and PAINT_SCREEN_* flags in scene.h . For example an effect that decides to paint a normal windows as translucent will need to modify the mask in its prePaintWindow() to include the PAINT_WINDOW_TRANSLUCENT flag. The paintWindow() function will then get the mask with this flag turned on and will also paint using transparency. The paint pass does the actual painting, based on the information collected using the pre-paint pass. After running through the effects' paintScreen() either paintGenericScreen() or optimized paintSimpleScreen() are called. Those call paintWindow() on windows (not necessarily all), possibly using clipping to optimize performance and calling paintWindow() first with only PAINT_WINDOW_OPAQUE to paint the opaque parts and then later with PAINT_WINDOW_TRANSLUCENT to paint the transparent parts. Function paintWindow() again goes through effects' paintWindow() until finalPaintWindow() is called, which calls the window's performPaint() to do the actual painting. The post-paint can be used for cleanups and is also used for scheduling repaints during the next painting pass for animations. Effects wanting to repaint certain parts can manually damage them during post-paint and repaint of these parts will be done during the next paint pass. */ #include "scene.h" #include #include #include "client.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "screens.h" #include "shadow.h" #include "wayland_server.h" #include "thumbnailitem.h" #include #include #include namespace KWin { //**************************************** // Scene //**************************************** Scene::Scene(QObject *parent) : QObject(parent) { last_time.invalidate(); // Initialize the timer } Scene::~Scene() { foreach (Window *w, m_windows) { delete w; } } // returns mask and possibly modified region void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint, QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection, const QRect &outputGeometry) { const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); *mask = (damage == displayRegion) ? 0 : PAINT_SCREEN_REGION; updateTimeDiff(); // preparation step static_cast(effects)->startPaint(); QRegion region = damage; ScreenPrePaintData pdata; pdata.mask = *mask; pdata.paint = region; effects->prePaintScreen(pdata, time_diff); *mask = pdata.mask; region = pdata.paint; if (*mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) { // Region painting is not possible with transformations, // because screen damage doesn't match transformed positions. *mask &= ~PAINT_SCREEN_REGION; region = infiniteRegion(); } else if (*mask & PAINT_SCREEN_REGION) { // make sure not to go outside visible screen region &= displayRegion; } else { // whole screen, not transformed, force region to be full region = displayRegion; } painted_region = region; repaint_region = repaint; if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) { paintBackground(region); } ScreenPaintData data(projection, outputGeometry); effects->paintScreen(*mask, region, data); foreach (Window *w, stacking_order) { effects->postPaintWindow(effectWindow(w)); } effects->postPaintScreen(); // make sure not to go outside of the screen area *updateRegion = damaged_region; *validRegion = (region | painted_region) & displayRegion; repaint_region = QRegion(); damaged_region = QRegion(); // make sure all clipping is restored Q_ASSERT(!PaintClipper::clip()); } // Compute time since the last painting pass. void Scene::updateTimeDiff() { if (!last_time.isValid()) { // Painting has been idle (optimized out) for some time, // which means time_diff would be huge and would break animations. // Simply set it to one (zero would mean no change at all and could // cause problems). time_diff = 1; last_time.start(); } else time_diff = last_time.restart(); if (time_diff < 0) // check time rollback time_diff = 1; } // Painting pass is optimized away. void Scene::idle() { // Don't break time since last paint for the next pass. last_time.invalidate(); } // the function that'll be eventually called by paintScreen() above void Scene::finalPaintScreen(int mask, QRegion region, ScreenPaintData& data) { if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) paintGenericScreen(mask, data); else paintSimpleScreen(mask, region); } // The generic painting code that can handle even transformations. // It simply paints bottom-to-top. void Scene::paintGenericScreen(int orig_mask, ScreenPaintData) { if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { paintBackground(infiniteRegion()); } QList< Phase2Data > phase2; foreach (Window * w, stacking_order) { // bottom to top Toplevel* topw = w->window(); // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. topw->resetRepaints(); WindowPrePaintData data; data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); w->resetPaintingEnabled(); data.paint = infiniteRegion(); // no clipping, so doesn't really matter data.clip = QRegion(); data.quads = w->buildQuads(); // preparation step effects->prePaintWindow(effectWindow(w), data, time_diff); #ifndef NDEBUG if (data.quads.isTransformed()) { qFatal("Pre-paint calls are not allowed to transform quads!"); } #endif if (!w->isPaintingEnabled()) { continue; } phase2.append(Phase2Data(w, infiniteRegion(), data.clip, data.mask, data.quads)); } foreach (const Phase2Data & d, phase2) { paintWindow(d.window, d.mask, d.region, d.quads); } const QSize &screenSize = screens()->size(); damaged_region = QRegion(0, 0, screenSize.width(), screenSize.height()); } // The optimized case without any transformations at all. // It can paint only the requested region and can use clipping // to reduce painting and improve performance. void Scene::paintSimpleScreen(int orig_mask, QRegion region) { assert((orig_mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) == 0); QList< QPair< Window*, Phase2Data > > phase2data; QRegion dirtyArea = region; bool opaqueFullscreen(false); for (int i = 0; // do prePaintWindow bottom to top i < stacking_order.count(); ++i) { Window* w = stacking_order[ i ]; Toplevel* topw = w->window(); WindowPrePaintData data; data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); w->resetPaintingEnabled(); data.paint = region; data.paint |= topw->repaints(); // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. topw->resetRepaints(); // Clip out the decoration for opaque windows; the decoration is drawn in the second pass opaqueFullscreen = false; // TODO: do we care about unmanged windows here (maybe input windows?) if (w->isOpaque()) { AbstractClient *c = dynamic_cast(topw); if (c) { opaqueFullscreen = c->isFullScreen(); } Client *cc = dynamic_cast(c); // the window is fully opaque if (cc && cc->decorationHasAlpha()) { // decoration uses alpha channel, so we may not exclude it in clipping data.clip = w->clientShape().translated(w->x(), w->y()); } else { // decoration is fully opaque if (c && c->isShade()) { data.clip = QRegion(); } else { data.clip = w->shape().translated(w->x(), w->y()); } } } else if (topw->hasAlpha() && topw->opacity() == 1.0) { // the window is partially opaque data.clip = (w->clientShape() & topw->opaqueRegion().translated(topw->clientPos())).translated(w->x(), w->y()); } else { data.clip = QRegion(); } data.quads = w->buildQuads(); // preparation step effects->prePaintWindow(effectWindow(w), data, time_diff); #ifndef NDEBUG if (data.quads.isTransformed()) { qFatal("Pre-paint calls are not allowed to transform quads!"); } #endif if (!w->isPaintingEnabled()) { continue; } dirtyArea |= data.paint; // Schedule the window for painting phase2data.append(QPair< Window*, Phase2Data >(w,Phase2Data(w, data.paint, data.clip, data.mask, data.quads))); } // Save the part of the repaint region that's exclusively rendered to // bring a reused back buffer up to date. Then union the dirty region // with the repaint region. const QRegion repaintClip = repaint_region - dirtyArea; dirtyArea |= repaint_region; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); bool fullRepaint(dirtyArea == displayRegion); // spare some expensive region operations if (!fullRepaint) { extendPaintRegion(dirtyArea, opaqueFullscreen); fullRepaint = (dirtyArea == displayRegion); } QRegion allclips, upperTranslucentDamage; upperTranslucentDamage = repaint_region; // This is the occlusion culling pass for (int i = phase2data.count() - 1; i >= 0; --i) { QPair< Window*, Phase2Data > *entry = &phase2data[i]; Phase2Data *data = &entry->second; if (fullRepaint) data->region = displayRegion; else data->region |= upperTranslucentDamage; // subtract the parts which will possibly been drawn as part of // a higher opaque window data->region -= allclips; // Here we rely on WindowPrePaintData::setTranslucent() to remove // the clip if needed. if (!data->clip.isEmpty() && !(data->mask & PAINT_WINDOW_TRANSFORMED)) { // clip away the opaque regions for all windows below this one allclips |= data->clip; // extend the translucent damage for windows below this by remaining (translucent) regions if (!fullRepaint) upperTranslucentDamage |= data->region - data->clip; } else if (!fullRepaint) { upperTranslucentDamage |= data->region; } } QRegion paintedArea; // Fill any areas of the root window not covered by opaque windows if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { paintedArea = dirtyArea - allclips; paintBackground(paintedArea); } // Now walk the list bottom to top and draw the windows. for (int i = 0; i < phase2data.count(); ++i) { Phase2Data *data = &phase2data[i].second; // add all regions which have been drawn so far paintedArea |= data->region; data->region = paintedArea; paintWindow(data->window, data->mask, data->region, data->quads); } if (fullRepaint) { painted_region = displayRegion; damaged_region = displayRegion; } else { painted_region |= paintedArea; // Clip the repainted region from the damaged region. // It's important that we don't add the union of the damaged region // and the repainted region to the damage history. Otherwise the // repaint region will grow with every frame until it eventually // covers the whole back buffer, at which point we're always doing // full repaints. damaged_region = paintedArea - repaintClip; } } void Scene::windowAdded(Toplevel *c) { assert(!m_windows.contains(c)); Scene::Window *w = createWindow(c); m_windows[ c ] = w; connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(windowGeometryShapeChanged(KWin::Toplevel*))); connect(c, SIGNAL(windowClosed(KWin::Toplevel*,KWin::Deleted*)), SLOT(windowClosed(KWin::Toplevel*,KWin::Deleted*))); //A change of scale won't affect the geometry in compositor co-ordinates, but will affect the window quads. if (c->surface()) { connect(c->surface(), &KWayland::Server::SurfaceInterface::scaleChanged, this, std::bind(&Scene::windowGeometryShapeChanged, this, c)); } + connect(c, &Toplevel::screenScaleChanged, std::bind(&Scene::windowGeometryShapeChanged, this, c)); c->effectWindow()->setSceneWindow(w); c->getShadow(); w->updateShadow(c->shadow()); } void Scene::windowClosed(Toplevel *c, Deleted *deleted) { assert(m_windows.contains(c)); if (deleted != NULL) { // replace c with deleted Window* w = m_windows.take(c); w->updateToplevel(deleted); if (w->shadow()) { w->shadow()->setToplevel(deleted); } m_windows[ deleted ] = w; } else { delete m_windows.take(c); c->effectWindow()->setSceneWindow(NULL); } } void Scene::windowDeleted(Deleted *c) { assert(m_windows.contains(c)); delete m_windows.take(c); c->effectWindow()->setSceneWindow(NULL); } void Scene::windowGeometryShapeChanged(Toplevel *c) { if (!m_windows.contains(c)) // this is ok, shape is not valid by default return; Window *w = m_windows[ c ]; w->discardShape(); } void Scene::createStackingOrder(ToplevelList toplevels) { // TODO: cache the stacking_order in case it has not changed foreach (Toplevel *c, toplevels) { assert(m_windows.contains(c)); stacking_order.append(m_windows[ c ]); } } void Scene::clearStackingOrder() { stacking_order.clear(); } static Scene::Window *s_recursionCheck = NULL; void Scene::paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads) { // no painting outside visible screen (and no transformations) const QSize &screenSize = screens()->size(); region &= QRect(0, 0, screenSize.width(), screenSize.height()); if (region.isEmpty()) // completely clipped return; if (w->window()->isDeleted() && w->window()->skipsCloseAnimation()) { // should not get painted return; } if (s_recursionCheck == w) { return; } WindowPaintData data(w->window()->effectWindow(), screenProjectionMatrix()); data.quads = quads; effects->paintWindow(effectWindow(w), mask, region, data); // paint thumbnails on top of window paintWindowThumbnails(w, region, data.opacity(), data.brightness(), data.saturation()); // and desktop thumbnails paintDesktopThumbnails(w); } static void adjustClipRegion(AbstractThumbnailItem *item, QRegion &clippingRegion) { if (item->clip() && item->clipTo()) { // the x/y positions of the parent item are not correct. The margins are added, though the size seems fine // that's why we have to get the offset by inspecting the anchors properties QQuickItem *parentItem = item->clipTo(); QPointF offset; QVariant anchors = parentItem->property("anchors"); if (anchors.isValid()) { if (QObject *anchorsObject = anchors.value()) { offset.setX(anchorsObject->property("leftMargin").toReal()); offset.setY(anchorsObject->property("topMargin").toReal()); } } QRectF rect = QRectF(parentItem->position() - offset, QSizeF(parentItem->width(), parentItem->height())); if (QQuickItem *p = parentItem->parentItem()) { rect = p->mapRectToScene(rect); } clippingRegion &= rect.adjusted(0,0,-1,-1).translated(item->window()->position()).toRect(); } } void Scene::paintWindowThumbnails(Scene::Window *w, QRegion region, qreal opacity, qreal brightness, qreal saturation) { EffectWindowImpl *wImpl = static_cast(effectWindow(w)); for (QHash >::const_iterator it = wImpl->thumbnails().constBegin(); it != wImpl->thumbnails().constEnd(); ++it) { if (it.value().isNull()) { continue; } WindowThumbnailItem *item = it.key(); if (!item->isVisible()) { continue; } EffectWindowImpl *thumb = it.value().data(); WindowPaintData thumbData(thumb, screenProjectionMatrix()); thumbData.setOpacity(opacity); thumbData.setBrightness(brightness * item->brightness()); thumbData.setSaturation(saturation * item->saturation()); const QRect visualThumbRect(thumb->expandedGeometry()); QSizeF size = QSizeF(visualThumbRect.size()); size.scale(QSizeF(item->width(), item->height()), Qt::KeepAspectRatio); if (size.width() > visualThumbRect.width() || size.height() > visualThumbRect.height()) { size = QSizeF(visualThumbRect.size()); } thumbData.setXScale(size.width() / static_cast(visualThumbRect.width())); thumbData.setYScale(size.height() / static_cast(visualThumbRect.height())); if (!item->window()) { continue; } const QPointF point = item->mapToScene(item->position()); qreal x = point.x() + w->x() + (item->width() - size.width())/2; qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; x -= thumb->x(); y -= thumb->y(); // compensate shadow topleft padding x += (thumb->x()-visualThumbRect.x())*thumbData.xScale(); y += (thumb->y()-visualThumbRect.y())*thumbData.yScale(); thumbData.setXTranslation(x); thumbData.setYTranslation(y); int thumbMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS; if (thumbData.opacity() == 1.0) { thumbMask |= PAINT_WINDOW_OPAQUE; } else { thumbMask |= PAINT_WINDOW_TRANSLUCENT; } QRegion clippingRegion = region; clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); adjustClipRegion(item, clippingRegion); effects->drawWindow(thumb, thumbMask, clippingRegion, thumbData); } } void Scene::paintDesktopThumbnails(Scene::Window *w) { EffectWindowImpl *wImpl = static_cast(effectWindow(w)); for (QList::const_iterator it = wImpl->desktopThumbnails().constBegin(); it != wImpl->desktopThumbnails().constEnd(); ++it) { DesktopThumbnailItem *item = *it; if (!item->isVisible()) { continue; } if (!item->window()) { continue; } s_recursionCheck = w; ScreenPaintData data; const QSize &screenSize = screens()->size(); QSize size = screenSize; size.scale(item->width(), item->height(), Qt::KeepAspectRatio); data *= QVector2D(size.width() / double(screenSize.width()), size.height() / double(screenSize.height())); const QPointF point = item->mapToScene(item->position()); const qreal x = point.x() + w->x() + (item->width() - size.width())/2; const qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; const QRect region = QRect(x, y, item->width(), item->height()); QRegion clippingRegion = region; clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); adjustClipRegion(item, clippingRegion); data += QPointF(x, y); const int desktopMask = PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; paintDesktop(item->desktop(), desktopMask, clippingRegion, data); s_recursionCheck = NULL; } } void Scene::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { static_cast(effects)->paintDesktop(desktop, mask, region, data); } // the function that'll be eventually called by paintWindow() above void Scene::finalPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { effects->drawWindow(w, mask, region, data); } // will be eventually called from drawWindow() void Scene::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (waylandServer() && waylandServer()->isScreenLocked() && !w->window()->isLockScreen() && !w->window()->isInputMethod()) { return; } w->sceneWindow()->performPaint(mask, region, data); } void Scene::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) { Q_UNUSED(region); Q_UNUSED(opaqueFullscreen); } bool Scene::blocksForRetrace() const { return false; } bool Scene::syncsToVBlank() const { return false; } void Scene::screenGeometryChanged(const QSize &size) { if (!overlayWindow()) { return; } overlayWindow()->resize(size); } bool Scene::makeOpenGLContextCurrent() { return false; } void Scene::doneOpenGLContextCurrent() { } void Scene::triggerFence() { } QMatrix4x4 Scene::screenProjectionMatrix() const { return QMatrix4x4(); } xcb_render_picture_t Scene::xrenderBufferPicture() const { return XCB_RENDER_PICTURE_NONE; } QPainter *Scene::scenePainter() const { return nullptr; } QImage *Scene::qpainterRenderBuffer() const { return nullptr; } QVector Scene::openGLPlatformInterfaceExtensions() const { return QVector{}; } //**************************************** // Scene::Window //**************************************** Scene::Window::Window(Toplevel * c) : toplevel(c) , filter(ImageFilterFast) , m_shadow(NULL) , m_currentPixmap() , m_previousPixmap() , m_referencePixmapCounter(0) , disable_painting(0) , shape_valid(false) , cached_quad_list(NULL) { } Scene::Window::~Window() { delete m_shadow; } void Scene::Window::referencePreviousPixmap() { if (!m_previousPixmap.isNull() && m_previousPixmap->isDiscarded()) { m_referencePixmapCounter++; } } void Scene::Window::unreferencePreviousPixmap() { if (m_previousPixmap.isNull() || !m_previousPixmap->isDiscarded()) { return; } m_referencePixmapCounter--; if (m_referencePixmapCounter == 0) { m_previousPixmap.reset(); } } void Scene::Window::pixmapDiscarded() { if (!m_currentPixmap.isNull()) { if (m_currentPixmap->isValid()) { m_previousPixmap.reset(m_currentPixmap.take()); m_previousPixmap->markAsDiscarded(); } else { m_currentPixmap.reset(); } } } void Scene::Window::discardShape() { // it is created on-demand and cached, simply // reset the flag shape_valid = false; cached_quad_list.reset(); } // Find out the shape of the window using the XShape extension // or if shape is not set then simply it's the window geometry. const QRegion &Scene::Window::shape() const { if (!shape_valid) { if (toplevel->shape()) { auto cookie = xcb_shape_get_rectangles_unchecked(connection(), toplevel->frameId(), XCB_SHAPE_SK_BOUNDING); ScopedCPointer reply(xcb_shape_get_rectangles_reply(connection(), cookie, nullptr)); if (!reply.isNull()) { shape_region = QRegion(); auto *rects = xcb_shape_get_rectangles_rectangles(reply.data()); for (int i = 0; i < xcb_shape_get_rectangles_rectangles_length(reply.data()); ++i) shape_region += QRegion(rects[ i ].x, rects[ i ].y, rects[ i ].width, rects[ i ].height); // make sure the shape is sane (X is async, maybe even XShape is broken) shape_region &= QRegion(0, 0, width(), height()); } else shape_region = QRegion(); } else shape_region = QRegion(0, 0, width(), height()); shape_valid = true; } return shape_region; } QRegion Scene::Window::clientShape() const { if (AbstractClient *c = dynamic_cast< AbstractClient * > (toplevel)) { if (c->isShade()) return QRegion(); } // TODO: cache const QRegion r = shape() & QRect(toplevel->clientPos(), toplevel->clientSize()); return r.isEmpty() ? QRegion() : r; } bool Scene::Window::isVisible() const { if (toplevel->isDeleted()) return false; if (!toplevel->isOnCurrentDesktop()) return false; if (!toplevel->isOnCurrentActivity()) return false; if (AbstractClient *c = dynamic_cast(toplevel)) return c->isShown(true); return true; // Unmanaged is always visible } bool Scene::Window::isOpaque() const { return toplevel->opacity() == 1.0 && !toplevel->hasAlpha(); } bool Scene::Window::isPaintingEnabled() const { return !disable_painting; } void Scene::Window::resetPaintingEnabled() { disable_painting = 0; if (toplevel->isDeleted()) disable_painting |= PAINT_DISABLED_BY_DELETE; if (static_cast(effects)->isDesktopRendering()) { if (!toplevel->isOnDesktop(static_cast(effects)->currentRenderedDesktop())) { disable_painting |= PAINT_DISABLED_BY_DESKTOP; } } else { if (!toplevel->isOnCurrentDesktop()) disable_painting |= PAINT_DISABLED_BY_DESKTOP; } if (!toplevel->isOnCurrentActivity()) disable_painting |= PAINT_DISABLED_BY_ACTIVITY; if (AbstractClient *c = dynamic_cast(toplevel)) { if (c->isMinimized()) disable_painting |= PAINT_DISABLED_BY_MINIMIZE; if (c->tabGroup() && c != c->tabGroup()->current()) disable_painting |= PAINT_DISABLED_BY_TAB_GROUP; if (c->isHiddenInternal()) { disable_painting |= PAINT_DISABLED; } } } void Scene::Window::enablePainting(int reason) { disable_painting &= ~reason; } void Scene::Window::disablePainting(int reason) { disable_painting |= reason; } WindowQuadList Scene::Window::buildQuads(bool force) const { if (cached_quad_list != NULL && !force) return *cached_quad_list; WindowQuadList ret; qreal scale = 1.0; if (toplevel->surface()) { scale = toplevel->surface()->scale(); } if (toplevel->clientPos() == QPoint(0, 0) && toplevel->clientSize() == toplevel->decorationRect().size()) ret = makeQuads(WindowQuadContents, shape(), QPoint(0,0), scale); // has no decoration else { AbstractClient *client = dynamic_cast(toplevel); QRegion contents = clientShape(); QRegion center = toplevel->transparentRect(); QRegion decoration = (client ? QRegion(client->decorationRect()) : shape()) - center; + qreal decorationScale = 1.0; ret = makeQuads(WindowQuadContents, contents, toplevel->clientContentPos(), scale); - QRect rects[4]; bool isShadedClient = false; if (client) { client->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]); + decorationScale = client->screenScale(); isShadedClient = client->isShade() || center.isEmpty(); } if (isShadedClient) { const QRect bounding = rects[0] | rects[1] | rects[2] | rects[3]; - ret += makeDecorationQuads(rects, bounding); + ret += makeDecorationQuads(rects, bounding, decorationScale); } else { - ret += makeDecorationQuads(rects, decoration); + ret += makeDecorationQuads(rects, decoration, decorationScale); } } if (m_shadow && toplevel->wantsShadowToBeRendered()) { ret << m_shadow->shadowQuads(); } effects->buildQuads(toplevel->effectWindow(), ret); cached_quad_list.reset(new WindowQuadList(ret)); return ret; } -WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QRegion ®ion) const +WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QRegion ®ion, qreal textureScale) const { WindowQuadList list; const QPoint offsets[4] = { QPoint(-rects[0].x() + rects[1].height() + rects[3].height() + 2, -rects[0].y()), // Left QPoint(-rects[1].x(), -rects[1].y()), // Top QPoint(-rects[2].x() + rects[1].height() + rects[3].height() + rects[0].width() + 3, -rects[2].y()), // Right QPoint(-rects[3].x(), -rects[3].y() + rects[1].height() + 1) // Bottom }; const Qt::Orientation orientations[4] = { Qt::Vertical, // Left Qt::Horizontal, // Top Qt::Vertical, // Right Qt::Horizontal, // Bottom }; for (int i = 0; i < 4; i++) { - foreach (const QRect &r, (region & rects[i]).rects()) { + const QRegion intersectedRegion = (region & rects[i]); + for (const QRect &r : intersectedRegion) { if (!r.isValid()) continue; const bool swap = orientations[i] == Qt::Vertical; const int x0 = r.x(); const int y0 = r.y(); const int x1 = r.x() + r.width(); const int y1 = r.y() + r.height(); - const int u0 = x0 + offsets[i].x(); - const int v0 = y0 + offsets[i].y(); - const int u1 = x1 + offsets[i].x(); - const int v1 = y1 + offsets[i].y(); + const int u0 = (x0 + offsets[i].x()) * textureScale; + const int v0 = (y0 + offsets[i].y()) * textureScale; + const int u1 = (x1 + offsets[i].x()) * textureScale; + const int v1 = (y1 + offsets[i].y()) * textureScale; WindowQuad quad(WindowQuadDecoration); quad.setUVAxisSwapped(swap); if (swap) { quad[0] = WindowVertex(x0, y0, v0, u0); // Top-left quad[1] = WindowVertex(x1, y0, v0, u1); // Top-right quad[2] = WindowVertex(x1, y1, v1, u1); // Bottom-right quad[3] = WindowVertex(x0, y1, v1, u0); // Bottom-left } else { quad[0] = WindowVertex(x0, y0, u0, v0); // Top-left quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left } list.append(quad); } } return list; } WindowQuadList Scene::Window::makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset, qreal scale) const { WindowQuadList ret; - foreach (const QRect & r, reg.rects()) { + ret.reserve(reg.rectCount()); + for (const QRect &r : reg) { WindowQuad quad(type); // TODO asi mam spatne pravy dolni roh - bud tady, nebo v jinych castech quad[ 0 ] = WindowVertex(QPointF(r.x(), r.y()), QPointF(r.x() + textureOffset.x(), r.y() + textureOffset.y()) * scale); quad[ 1 ] = WindowVertex(QPointF(r.x() + r.width(), r.y()), QPointF(r.x() + r.width() + textureOffset.x(), r.y() + textureOffset.y()) * scale); quad[ 2 ] = WindowVertex(QPointF(r.x() + r.width(), r.y() + r.height()), QPointF(r.x() + r.width() + textureOffset.x(), r.y() + r.height() + textureOffset.y()) * scale); quad[ 3 ] = WindowVertex(QPointF(r.x(), r.y() + r.height()), QPointF(r.x() + textureOffset.x(), r.y() + r.height() + textureOffset.y()) * scale); ret.append(quad); } return ret; } void Scene::Window::updateShadow(Shadow* shadow) { if (m_shadow == shadow) { return; } delete m_shadow; m_shadow = shadow; } //**************************************** // WindowPixmap //**************************************** WindowPixmap::WindowPixmap(Scene::Window *window) : m_window(window) , m_pixmap(XCB_PIXMAP_NONE) , m_discarded(false) { } WindowPixmap::WindowPixmap(const QPointer &subSurface, WindowPixmap *parent) : m_window(parent->m_window) , m_pixmap(XCB_PIXMAP_NONE) , m_discarded(false) , m_parent(parent) , m_subSurface(subSurface) { } WindowPixmap::~WindowPixmap() { if (m_pixmap != XCB_WINDOW_NONE) { xcb_free_pixmap(connection(), m_pixmap); } if (m_buffer) { using namespace KWayland::Server; QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); } } void WindowPixmap::create() { if (isValid() || toplevel()->isDeleted()) { return; } // always update from Buffer on Wayland, don't try using XPixmap if (kwinApp()->shouldUseWaylandForCompositing()) { // use Buffer updateBuffer(); if ((m_buffer || !m_fbo.isNull()) && m_subSurface.isNull()) { m_window->unreferencePreviousPixmap(); } return; } XServerGrabber grabber; xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_void_cookie_t namePixmapCookie = xcb_composite_name_window_pixmap_checked(connection(), toplevel()->frameId(), pix); Xcb::WindowAttributes windowAttributes(toplevel()->frameId()); Xcb::WindowGeometry windowGeometry(toplevel()->frameId()); if (xcb_generic_error_t *error = xcb_request_check(connection(), namePixmapCookie)) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << error->error_code; free(error); return; } // check that the received pixmap is valid and actually matches what we // know about the window (i.e. size) if (!windowAttributes || windowAttributes->map_state != XCB_MAP_STATE_VIEWABLE) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << this; xcb_free_pixmap(connection(), pix); return; } if (!windowGeometry || windowGeometry->width != toplevel()->width() || windowGeometry->height != toplevel()->height()) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << this; xcb_free_pixmap(connection(), pix); return; } m_pixmap = pix; m_pixmapSize = QSize(toplevel()->width(), toplevel()->height()); m_contentsRect = QRect(toplevel()->clientPos(), toplevel()->clientSize()); m_window->unreferencePreviousPixmap(); } WindowPixmap *WindowPixmap::createChild(const QPointer &subSurface) { Q_UNUSED(subSurface) return nullptr; } bool WindowPixmap::isValid() const { if (!m_buffer.isNull() || !m_fbo.isNull()) { return true; } return m_pixmap != XCB_PIXMAP_NONE; } void WindowPixmap::updateBuffer() { using namespace KWayland::Server; if (SurfaceInterface *s = surface()) { QVector oldTree = m_children; QVector children; using namespace KWayland::Server; const auto subSurfaces = s->childSubSurfaces(); for (const auto &subSurface : subSurfaces) { if (subSurface.isNull()) { continue; } auto it = std::find_if(oldTree.begin(), oldTree.end(), [subSurface] (WindowPixmap *p) { return p->m_subSurface == subSurface; }); if (it != oldTree.end()) { children << *it; (*it)->updateBuffer(); oldTree.erase(it); } else { WindowPixmap *p = createChild(subSurface); if (p) { p->create(); children << p; } } } setChildren(children); qDeleteAll(oldTree); if (auto b = s->buffer()) { if (b == m_buffer) { // no change return; } if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); } m_buffer = b; m_buffer->ref(); QObject::connect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); } else if (m_subSurface) { if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); m_buffer.clear(); } } else { // might be an internal window const auto &fbo = toplevel()->internalFramebufferObject(); if (!fbo.isNull()) { m_fbo = fbo; } } } else { if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); m_buffer.clear(); } } } KWayland::Server::SurfaceInterface *WindowPixmap::surface() const { if (!m_subSurface.isNull()) { return m_subSurface->surface().data(); } else { return toplevel()->surface(); } } //**************************************** // Scene::EffectFrame //**************************************** Scene::EffectFrame::EffectFrame(EffectFrameImpl* frame) : m_effectFrame(frame) { } Scene::EffectFrame::~EffectFrame() { } SceneFactory::SceneFactory(QObject *parent) : QObject(parent) { } SceneFactory::~SceneFactory() { } } // namespace diff --git a/scene.h b/scene.h index 8f3b9615f..f7c67c5ca 100644 --- a/scene.h +++ b/scene.h @@ -1,682 +1,682 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_SCENE_H #define KWIN_SCENE_H #include "toplevel.h" #include "utils.h" #include "kwineffects.h" #include #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class BufferInterface; class SubSurfaceInterface; } } namespace KWin { namespace Decoration { class DecoratedClientImpl; class Renderer; } class AbstractThumbnailItem; class Deleted; class EffectFrameImpl; class EffectWindowImpl; class OverlayWindow; class Shadow; class WindowPixmap; // The base class for compositing backends. class KWIN_EXPORT Scene : public QObject { Q_OBJECT public: explicit Scene(QObject *parent = nullptr); virtual ~Scene() = 0; class EffectFrame; class Window; // Returns true if the ctor failed to properly initialize. virtual bool initFailed() const = 0; virtual CompositingType compositingType() const = 0; virtual bool hasPendingFlush() const { return false; } // Repaints the given screen areas, windows provides the stacking order. // The entry point for the main part of the painting pass. // returns the time since the last vblank signal - if there's one // ie. "what of this frame is lost to painting" virtual qint64 paint(QRegion damage, ToplevelList windows) = 0; // Notification function - KWin core informs about changes. // Used to mainly discard cached data. // a new window has been created void windowAdded(Toplevel*); /** * @brief Creates the Scene backend of an EffectFrame. * * @param frame The EffectFrame this Scene::EffectFrame belongs to. */ virtual Scene::EffectFrame *createEffectFrame(EffectFrameImpl *frame) = 0; /** * @brief Creates the Scene specific Shadow subclass. * * An implementing class has to create a proper instance. It is not allowed to * return @c null. * * @param toplevel The Toplevel for which the Shadow needs to be created. */ virtual Shadow *createShadow(Toplevel *toplevel) = 0; /** * Method invoked when the screen geometry is changed. * Reimplementing classes should also invoke the parent method * as it takes care of resizing the overlay window. * @param size The new screen geometry size **/ virtual void screenGeometryChanged(const QSize &size); // Flags controlling how painting is done. enum { // Window (or at least part of it) will be painted opaque. PAINT_WINDOW_OPAQUE = 1 << 0, // Window (or at least part of it) will be painted translucent. PAINT_WINDOW_TRANSLUCENT = 1 << 1, // Window will be painted with transformed geometry. PAINT_WINDOW_TRANSFORMED = 1 << 2, // Paint only a region of the screen (can be optimized, cannot // be used together with TRANSFORMED flags). PAINT_SCREEN_REGION = 1 << 3, // Whole screen will be painted with transformed geometry. PAINT_SCREEN_TRANSFORMED = 1 << 4, // At least one window will be painted with transformed geometry. PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS = 1 << 5, // Clear whole background as the very first step, without optimizing it PAINT_SCREEN_BACKGROUND_FIRST = 1 << 6, // PAINT_DECORATION_ONLY = 1 << 7 has been removed // Window will be painted with a lanczos filter. PAINT_WINDOW_LANCZOS = 1 << 8 // PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_WITHOUT_FULL_REPAINTS = 1 << 9 has been removed }; // types of filtering available enum ImageFilterType { ImageFilterFast, ImageFilterGood }; // there's nothing to paint (adjust time_diff later) virtual void idle(); virtual bool blocksForRetrace() const; virtual bool syncsToVBlank() const; virtual OverlayWindow* overlayWindow() = 0; virtual bool makeOpenGLContextCurrent(); virtual void doneOpenGLContextCurrent(); virtual QMatrix4x4 screenProjectionMatrix() const; /** * Whether the Scene uses an X11 overlay window to perform compositing. */ virtual bool usesOverlayWindow() const = 0; virtual void triggerFence(); virtual Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *) = 0; /** * Whether the Scene is able to drive animations. * This is used as a hint to the effects system which effects can be supported. * If the Scene performs software rendering it is supposed to return @c false, * if rendering is hardware accelerated it should return @c true. **/ virtual bool animationsSupported() const = 0; /** * The render buffer used by an XRender based compositor scene. * Default implementation returns XCB_RENDER_PICTURE_NONE **/ virtual xcb_render_picture_t xrenderBufferPicture() const; /** * The QPainter used by a QPainter based compositor scene. * Default implementation returns @c nullptr; **/ virtual QPainter *scenePainter() const; /** * The render buffer used by a QPainter based compositor. * Default implementation returns @c nullptr. **/ virtual QImage *qpainterRenderBuffer() const; /** * The backend specific extensions (e.g. EGL/GLX extensions). * * Not the OpenGL (ES) extension! * * Default implementation returns empty list **/ virtual QVector openGLPlatformInterfaceExtensions() const; Q_SIGNALS: void frameRendered(); void resetCompositing(); public Q_SLOTS: // a window has been destroyed void windowDeleted(KWin::Deleted*); // shape/size of a window changed void windowGeometryShapeChanged(KWin::Toplevel* c); // a window has been closed void windowClosed(KWin::Toplevel* c, KWin::Deleted* deleted); protected: virtual Window *createWindow(Toplevel *toplevel) = 0; void createStackingOrder(ToplevelList toplevels); void clearStackingOrder(); // shared implementation, starts painting the screen void paintScreen(int *mask, const QRegion &damage, const QRegion &repaint, QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection = QMatrix4x4(), const QRect &outputGeometry = QRect()); // Render cursor texture in case hardware cursor is disabled/non-applicable virtual void paintCursor() = 0; friend class EffectsHandlerImpl; // called after all effects had their paintScreen() called void finalPaintScreen(int mask, QRegion region, ScreenPaintData& data); // shared implementation of painting the screen in the generic // (unoptimized) way virtual void paintGenericScreen(int mask, ScreenPaintData data); // shared implementation of painting the screen in an optimized way virtual void paintSimpleScreen(int mask, QRegion region); // paint the background (not the desktop background - the whole background) virtual void paintBackground(QRegion region) = 0; // called after all effects had their paintWindow() called void finalPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); // shared implementation, starts painting the window virtual void paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads); // called after all effects had their drawWindow() called virtual void finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); // let the scene decide whether it's better to paint more of the screen, eg. in order to allow a buffer swap // the default is NOOP virtual void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen); virtual void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data); // compute time since the last repaint void updateTimeDiff(); // saved data for 2nd pass of optimized screen painting struct Phase2Data { Phase2Data(Window* w, QRegion r, QRegion c, int m, const WindowQuadList& q) : window(w), region(r), clip(c), mask(m), quads(q) {} Phase2Data() { window = 0; mask = 0; } Window* window; QRegion region; QRegion clip; int mask; WindowQuadList quads; }; // The region which actually has been painted by paintScreen() and should be // copied from the buffer to the screen. I.e. the region returned from Scene::paintScreen(). // Since prePaintWindow() can extend areas to paint, these changes would have to propagate // up all the way from paintSimpleScreen() up to paintScreen(), so save them here rather // than propagate them up in arguments. QRegion painted_region; // Additional damage that needs to be repaired to bring a reused back buffer up to date QRegion repaint_region; // The dirty region before it was unioned with repaint_region QRegion damaged_region; // time since last repaint int time_diff; QElapsedTimer last_time; private: void paintWindowThumbnails(Scene::Window *w, QRegion region, qreal opacity, qreal brightness, qreal saturation); void paintDesktopThumbnails(Scene::Window *w); QHash< Toplevel*, Window* > m_windows; // windows in their stacking order QVector< Window* > stacking_order; }; /** * Factory class to create a Scene. Needs to be implemented by the plugins. **/ class KWIN_EXPORT SceneFactory : public QObject { Q_OBJECT public: virtual ~SceneFactory(); /** * @returns The created Scene, may be @c nullptr. **/ virtual Scene *create(QObject *parent = nullptr) const = 0; protected: explicit SceneFactory(QObject *parent); }; // The base class for windows representations in composite backends class Scene::Window { public: Window(Toplevel* c); virtual ~Window(); // perform the actual painting of the window virtual void performPaint(int mask, QRegion region, WindowPaintData data) = 0; // do any cleanup needed when the window's composite pixmap is discarded void pixmapDiscarded(); int x() const; int y() const; int width() const; int height() const; QRect geometry() const; QPoint pos() const; QSize size() const; QRect rect() const; // access to the internal window class // TODO eventually get rid of this Toplevel* window() const; // should the window be painted bool isPaintingEnabled() const; void resetPaintingEnabled(); // Flags explaining why painting should be disabled enum { // Window will not be painted PAINT_DISABLED = 1 << 0, // Window will not be painted because it is deleted PAINT_DISABLED_BY_DELETE = 1 << 1, // Window will not be painted because of which desktop it's on PAINT_DISABLED_BY_DESKTOP = 1 << 2, // Window will not be painted because it is minimized PAINT_DISABLED_BY_MINIMIZE = 1 << 3, // Window will not be painted because it is not the active window in a client group PAINT_DISABLED_BY_TAB_GROUP = 1 << 4, // Window will not be painted because it's not on the current activity PAINT_DISABLED_BY_ACTIVITY = 1 << 5 }; void enablePainting(int reason); void disablePainting(int reason); // is the window visible at all bool isVisible() const; // is the window fully opaque bool isOpaque() const; // shape of the window const QRegion &shape() const; QRegion clientShape() const; void discardShape(); void updateToplevel(Toplevel* c); // creates initial quad list for the window virtual WindowQuadList buildQuads(bool force = false) const; void updateShadow(Shadow* shadow); const Shadow* shadow() const; Shadow* shadow(); void referencePreviousPixmap(); void unreferencePreviousPixmap(); protected: WindowQuadList makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset = QPoint(0, 0), qreal textureScale = 1.0) const; - WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion ®ion) const; + WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion ®ion, qreal textureScale = 1.0) const; /** * @brief Returns the WindowPixmap for this Window. * * If the WindowPixmap does not yet exist, this method will invoke @link createWindowPixmap. * If the WindowPixmap is not valid it tries to create it, in case this succeeds the WindowPixmap is * returned. In case it fails, the previous (and still valid) WindowPixmap is returned. * * Note: this method can return @c NULL as there might neither be a valid previous nor current WindowPixmap * around. * * The WindowPixmap gets casted to the type passed in as a template parameter. That way this class does not * need to know the actual WindowPixmap subclass used by the concrete Scene implementations. * * @return The WindowPixmap casted to T* or @c NULL if there is no valid window pixmap. */ template T *windowPixmap(); template T *previousWindowPixmap(); /** * @brief Factory method to create a WindowPixmap. * * The inheriting classes need to implement this method to create a new instance of their WindowPixmap subclass. * Note: do not use @link WindowPixmap::create on the created instance. The Scene will take care of that. */ virtual WindowPixmap *createWindowPixmap() = 0; Toplevel* toplevel; ImageFilterType filter; Shadow *m_shadow; private: QScopedPointer m_currentPixmap; QScopedPointer m_previousPixmap; int m_referencePixmapCounter; int disable_painting; mutable QRegion shape_region; mutable bool shape_valid; mutable QScopedPointer cached_quad_list; Q_DISABLE_COPY(Window) }; /** * @brief Wrapper for a pixmap of the @link Scene::Window. * * This class encapsulates the functionality to get the pixmap for a window. When initialized the pixmap is not yet * mapped to the window and @link isValid will return @c false. The pixmap mapping to the window can be established * through @link create. If it succeeds @link isValid will return @c true, otherwise it will keep in the non valid * state and it can be tried to create the pixmap mapping again (e.g. in the next frame). * * This class is not intended to be updated when the pixmap is no longer valid due to e.g. resizing the window. * Instead a new instance of this class should be instantiated. The idea behind this is that a valid pixmap does not * get destroyed, but can continue to be used. To indicate that a newer pixmap should in generally be around, one can * use @link markAsDiscarded. * * This class is intended to be inherited for the needs of the compositor backends which need further mapping from * the native pixmap to the respective rendering format. */ class KWIN_EXPORT WindowPixmap { public: virtual ~WindowPixmap(); /** * @brief Tries to create the mapping between the Window and the pixmap. * * In case this method succeeds in creating the pixmap for the window, @link isValid will return @c true otherwise * @c false. * * Inheriting classes should re-implement this method in case they need to add further functionality for mapping the * native pixmap to the rendering format. */ virtual void create(); /** * @return @c true if the pixmap has been created and is valid, @c false otherwise */ virtual bool isValid() const; /** * @return The native X11 pixmap handle */ xcb_pixmap_t pixmap() const; /** * @return The Wayland BufferInterface for this WindowPixmap. **/ QPointer buffer() const; const QSharedPointer &fbo() const; /** * @brief Whether this WindowPixmap is considered as discarded. This means the window has changed in a way that a new * WindowPixmap should have been created already. * * @return @c true if this WindowPixmap is considered as discarded, @c false otherwise. * @see markAsDiscarded */ bool isDiscarded() const; /** * @brief Marks this WindowPixmap as discarded. From now on @link isDiscarded will return @c true. This method should * only be used by the Window when it changes in a way that a new pixmap is required. * * @see isDiscarded */ void markAsDiscarded(); /** * The size of the pixmap. */ const QSize &size() const; /** * The geometry of the Client's content inside the pixmap. In case of a decorated Client the * pixmap also contains the decoration which is not rendered into this pixmap, though. This * contentsRect tells where inside the complete pixmap the real content is. */ const QRect &contentsRect() const; /** * @brief Returns the Toplevel this WindowPixmap belongs to. * Note: the Toplevel can change over the lifetime of the WindowPixmap in case the Toplevel is copied to Deleted. */ Toplevel *toplevel() const; /** * @returns the parent WindowPixmap in the sub-surface tree **/ WindowPixmap *parent() const { return m_parent; } /** * @returns the current sub-surface tree **/ QVector children() const { return m_children; } /** * @returns the subsurface this WindowPixmap is for if it is not for a root window **/ QPointer subSurface() const { return m_subSurface; } /** * @returns the surface this WindowPixmap references, might be @c null. **/ KWayland::Server::SurfaceInterface *surface() const; protected: explicit WindowPixmap(Scene::Window *window); explicit WindowPixmap(const QPointer &subSurface, WindowPixmap *parent); virtual WindowPixmap *createChild(const QPointer &subSurface); /** * @return The Window this WindowPixmap belongs to */ Scene::Window *window(); /** * Should be called by the implementing subclasses when the Wayland Buffer changed and needs * updating. **/ virtual void updateBuffer(); /** * Sets the sub-surface tree to @p children. **/ void setChildren(const QVector &children) { m_children = children; } private: Scene::Window *m_window; xcb_pixmap_t m_pixmap; QSize m_pixmapSize; bool m_discarded; QRect m_contentsRect; QPointer m_buffer; QSharedPointer m_fbo; WindowPixmap *m_parent = nullptr; QVector m_children; QPointer m_subSurface; }; class Scene::EffectFrame { public: EffectFrame(EffectFrameImpl* frame); virtual ~EffectFrame(); virtual void render(QRegion region, double opacity, double frameOpacity) = 0; virtual void free() = 0; virtual void freeIconFrame() = 0; virtual void freeTextFrame() = 0; virtual void freeSelection() = 0; virtual void crossFadeIcon() = 0; virtual void crossFadeText() = 0; protected: EffectFrameImpl* m_effectFrame; }; inline int Scene::Window::x() const { return toplevel->x(); } inline int Scene::Window::y() const { return toplevel->y(); } inline int Scene::Window::width() const { return toplevel->width(); } inline int Scene::Window::height() const { return toplevel->height(); } inline QRect Scene::Window::geometry() const { return toplevel->geometry(); } inline QSize Scene::Window::size() const { return toplevel->size(); } inline QPoint Scene::Window::pos() const { return toplevel->pos(); } inline QRect Scene::Window::rect() const { return toplevel->rect(); } inline Toplevel* Scene::Window::window() const { return toplevel; } inline void Scene::Window::updateToplevel(Toplevel* c) { toplevel = c; } inline const Shadow* Scene::Window::shadow() const { return m_shadow; } inline Shadow* Scene::Window::shadow() { return m_shadow; } inline QPointer WindowPixmap::buffer() const { return m_buffer; } inline const QSharedPointer &WindowPixmap::fbo() const { return m_fbo; } template inline T* Scene::Window::windowPixmap() { if (m_currentPixmap.isNull()) { m_currentPixmap.reset(createWindowPixmap()); } if (m_currentPixmap->isValid()) { return static_cast(m_currentPixmap.data()); } m_currentPixmap->create(); if (m_currentPixmap->isValid()) { return static_cast(m_currentPixmap.data()); } else { return static_cast(m_previousPixmap.data()); } } template inline T* Scene::Window::previousWindowPixmap() { return static_cast(m_previousPixmap.data()); } inline Toplevel* WindowPixmap::toplevel() const { return m_window->window(); } inline xcb_pixmap_t WindowPixmap::pixmap() const { return m_pixmap; } inline bool WindowPixmap::isDiscarded() const { return m_discarded; } inline void WindowPixmap::markAsDiscarded() { m_discarded = true; m_window->referencePreviousPixmap(); } inline const QRect &WindowPixmap::contentsRect() const { return m_contentsRect; } inline const QSize &WindowPixmap::size() const { return m_pixmapSize; } } // namespace Q_DECLARE_INTERFACE(KWin::SceneFactory, "org.kde.kwin.Scene") #endif diff --git a/screens.cpp b/screens.cpp index 5bf3b9939..d28f75ce1 100644 --- a/screens.cpp +++ b/screens.cpp @@ -1,269 +1,308 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "screens.h" #include #include #include "cursor.h" +#include "orientation_sensor.h" #include "utils.h" #include "settings.h" #include #include #include "platform.h" #include "wayland_server.h" #ifdef KWIN_UNIT_TEST #include #endif namespace KWin { Screens *Screens::s_self = nullptr; Screens *Screens::create(QObject *parent) { Q_ASSERT(!s_self); #ifdef KWIN_UNIT_TEST s_self = new MockScreens(parent); #else s_self = kwinApp()->platform()->createScreens(parent); #endif Q_ASSERT(s_self); s_self->init(); return s_self; } Screens::Screens(QObject *parent) : QObject(parent) , m_count(0) , m_current(0) , m_currentFollowsMouse(false) , m_changedTimer(new QTimer(this)) + , m_orientationSensor(new OrientationSensor(this)) { + connect(this, &Screens::changed, this, + [this] { + int internalIndex = -1; + for (int i = 0; i < m_count; i++) { + if (isInternal(i)) { + internalIndex = i; + break; + } + } + m_orientationSensor->setEnabled(internalIndex != -1 && supportsTransformations(internalIndex)); + } + ); } Screens::~Screens() { s_self = NULL; } void Screens::init() { m_changedTimer->setSingleShot(true); m_changedTimer->setInterval(100); connect(m_changedTimer, SIGNAL(timeout()), SLOT(updateCount())); connect(m_changedTimer, SIGNAL(timeout()), SIGNAL(changed())); connect(this, &Screens::countChanged, this, &Screens::changed, Qt::QueuedConnection); connect(this, &Screens::changed, this, &Screens::updateSize); connect(this, &Screens::sizeChanged, this, &Screens::geometryChanged); Settings settings; settings.setDefaults(); m_currentFollowsMouse = settings.activeMouseScreen(); } QString Screens::name(int screen) const { Q_UNUSED(screen) qCWarning(KWIN_CORE, "%s::name(int screen) is a stub, please reimplement it!", metaObject()->className()); return QLatin1String("DUMMY"); } float Screens::refreshRate(int screen) const { Q_UNUSED(screen) qCWarning(KWIN_CORE, "%s::refreshRate(int screen) is a stub, please reimplement it!", metaObject()->className()); return 60.0f; } qreal Screens::scale(int screen) const { Q_UNUSED(screen) - qCWarning(KWIN_CORE, "%s::scale(qreal screen) is a stub, please reimplement it!", metaObject()->className()); return 1; } void Screens::reconfigure() { if (!m_config) { return; } Settings settings(m_config); settings.read(); setCurrentFollowsMouse(settings.activeMouseScreen()); } void Screens::updateSize() { QRect bounding; for (int i = 0; i < count(); ++i) { bounding = bounding.united(geometry(i)); } if (m_boundingSize != bounding.size()) { m_boundingSize = bounding.size(); emit sizeChanged(); } } void Screens::setCount(int count) { if (m_count == count) { return; } const int previous = m_count; m_count = count; emit countChanged(previous, count); } void Screens::setCurrent(int current) { if (m_current == current) { return; } m_current = current; emit currentChanged(); } void Screens::setCurrent(const QPoint &pos) { setCurrent(number(pos)); } void Screens::setCurrent(const AbstractClient *c) { if (!c->isActive()) { return; } if (!c->isOnScreen(m_current)) { setCurrent(c->screen()); } } void Screens::setCurrentFollowsMouse(bool follows) { if (m_currentFollowsMouse == follows) { return; } m_currentFollowsMouse = follows; } int Screens::current() const { if (m_currentFollowsMouse) { return number(Cursor::pos()); } AbstractClient *client = Workspace::self()->activeClient(); if (client && !client->isOnScreen(m_current)) { return client->screen(); } return m_current; } int Screens::intersecting(const QRect &r) const { int cnt = 0; for (int i = 0; i < count(); ++i) { if (geometry(i).intersects(r)) { ++cnt; } } return cnt; } QSize Screens::displaySize() const { return size(); } QSizeF Screens::physicalSize(int screen) const { return QSizeF(size(screen)) / 3.8; } +bool Screens::isInternal(int screen) const +{ + Q_UNUSED(screen) + return false; +} + +bool Screens::supportsTransformations(int screen) const +{ + Q_UNUSED(screen) + return false; +} + +Qt::ScreenOrientation Screens::orientation(int screen) const +{ + Q_UNUSED(screen) + return Qt::PrimaryOrientation; +} + +void Screens::setConfig(KSharedConfig::Ptr config) +{ + m_config = config; + if (m_orientationSensor) { + m_orientationSensor->setConfig(config); + } +} + BasicScreens::BasicScreens(Platform *backend, QObject *parent) : Screens(parent) , m_backend(backend) { } BasicScreens::~BasicScreens() = default; void BasicScreens::init() { updateCount(); KWin::Screens::init(); #ifndef KWIN_UNIT_TEST connect(m_backend, &Platform::screenSizeChanged, this, &BasicScreens::startChangedTimer); #endif emit changed(); } QRect BasicScreens::geometry(int screen) const { if (screen < m_geometries.count()) { return m_geometries.at(screen); } return QRect(); } QSize BasicScreens::size(int screen) const { if (screen < m_geometries.count()) { return m_geometries.at(screen).size(); } return QSize(); } qreal BasicScreens::scale(int screen) const { if (screen < m_scales.count()) { return m_scales.at(screen); } return 1; } void BasicScreens::updateCount() { m_geometries = m_backend->screenGeometries(); m_scales = m_backend->screenScales(); setCount(m_geometries.count()); } int BasicScreens::number(const QPoint &pos) const { int bestScreen = 0; int minDistance = INT_MAX; for (int i = 0; i < m_geometries.count(); ++i) { const QRect &geo = m_geometries.at(i); if (geo.contains(pos)) { return i; } int distance = QPoint(geo.topLeft() - pos).manhattanLength(); distance = qMin(distance, QPoint(geo.topRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomLeft() - pos).manhattanLength()); if (distance < minDistance) { minDistance = distance; bestScreen = i; } } return bestScreen; } } // namespace diff --git a/screens.h b/screens.h index 1fbc2e6f3..4fda86fb9 100644 --- a/screens.h +++ b/screens.h @@ -1,244 +1,264 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_SCREENS_H #define KWIN_SCREENS_H // KWin includes #include // KDE includes #include #include // Qt includes #include #include #include #include namespace KWin { class AbstractClient; class Platform; +class OrientationSensor; class KWIN_EXPORT Screens : public QObject { Q_OBJECT Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged) Q_PROPERTY(int current READ current WRITE setCurrent NOTIFY currentChanged) Q_PROPERTY(bool currentFollowsMouse READ isCurrentFollowsMouse WRITE setCurrentFollowsMouse) public: virtual ~Screens(); /** * @internal **/ void setConfig(KSharedConfig::Ptr config); int count() const; int current() const; void setCurrent(int current); /** * Called e.g. when a user clicks on a window, set current screen to be the screen * where the click occurred */ void setCurrent(const QPoint &pos); /** * Check whether a client moved completely out of what's considered the current screen, * if yes, set a new active screen. */ void setCurrent(const AbstractClient *c); bool isCurrentFollowsMouse() const; void setCurrentFollowsMouse(bool follows); virtual QRect geometry(int screen) const = 0; /** * The bounding geometry of all screens combined. Overlapping areas * are not counted multiple times. * @see geometryChanged() **/ QRect geometry() const; /** * The output name of the screen (usually eg. LVDS-1, VGA-0 or DVI-I-1 etc.) */ virtual QString name(int screen) const; /** * @returns current refreshrate of the @p screen. **/ virtual float refreshRate(int screen) const; /** * @returns size of the @p screen. * * To get the size of all screens combined use size(). * @see size() **/ virtual QSize size(int screen) const = 0; /* * The output scale for this display, for use by high DPI displays */ virtual qreal scale(int screen) const; /** * The bounding size of all screens combined. Overlapping areas * are not counted multiple times. * * @see geometry() * @see sizeChanged() **/ QSize size() const; virtual int number(const QPoint &pos) const = 0; inline bool isChanging() { return m_changedTimer->isActive(); } int intersecting(const QRect &r) const; /** * The virtual bounding size of all screens combined. * The default implementation returns the same as @link{size} and that is the * method which should be preferred. * * This method is only for cases where the platform specific implementation needs * to support different virtual sizes like on X11 with XRandR panning. * * @see size **/ virtual QSize displaySize() const; /** * The physical size of @p screen in mm. * Default implementation returns a size derived from 96 DPI. **/ virtual QSizeF physicalSize(int screen) const; + /** + * @returns @c true if the @p screen is connected through an internal display (e.g. LVDS). + * Default implementation returns @c false. + **/ + virtual bool isInternal(int screen) const; + + /** + * @returns @c true if the @p screen can be rotated. + * Default implementation returns @c false + **/ + virtual bool supportsTransformations(int screen) const; + + virtual Qt::ScreenOrientation orientation(int screen) const; + + /** + * Provides access to the OrientationSensor. The OrientationSensor is controlled by the + * base implementation. The implementing subclass can use this to get notifications about + * changes of the orientation and current orientation. There is no need to enable/disable it, + * that is done by the base implementation + **/ + OrientationSensor *orientationSensor() const { + return m_orientationSensor; + } + public Q_SLOTS: void reconfigure(); Q_SIGNALS: void countChanged(int previousCount, int newCount); /** * Emitted whenever the screens are changed either count or geometry. **/ void changed(); void currentChanged(); /** * Emitted when the geometry of all screens combined changes. * Not emitted when the geometry of an individual screen changes. * @see geometry() **/ void geometryChanged(); /** * Emitted when the size of all screens combined changes. * Not emitted when the size of an individual screen changes. * @see size() **/ void sizeChanged(); protected Q_SLOTS: void setCount(int count); void startChangedTimer(); virtual void updateCount() = 0; protected: /** * Called once the singleton instance has been created. * Any initialization code should go into this method. Overriding classes have to call * the base implementation first. **/ virtual void init(); private Q_SLOTS: void updateSize(); private: int m_count; int m_current; bool m_currentFollowsMouse; QTimer *m_changedTimer; KSharedConfig::Ptr m_config; QSize m_boundingSize; + OrientationSensor *m_orientationSensor; KWIN_SINGLETON(Screens) }; /** * @brief A base implementation for backends with just a (nested) window **/ class KWIN_EXPORT BasicScreens : public Screens { Q_OBJECT public: BasicScreens(Platform *backend, QObject *parent = nullptr); virtual ~BasicScreens(); void init() override; QRect geometry(int screen) const override; int number(const QPoint &pos) const override; QSize size(int screen) const override; qreal scale(int screen) const override; void updateCount() override; private: Platform *m_backend; QVector m_geometries; QVector m_scales; }; -inline -void Screens::setConfig(KSharedConfig::Ptr config) -{ - m_config = config; -} - inline int Screens::count() const { return m_count; } inline bool Screens::isCurrentFollowsMouse() const { return m_currentFollowsMouse; } inline void Screens::startChangedTimer() { m_changedTimer->start(); } inline QSize Screens::size() const { return m_boundingSize; } inline QRect Screens::geometry() const { return QRect(QPoint(0,0), size()); } inline Screens *screens() { return Screens::self(); } } #endif // KWIN_SCREENS_H diff --git a/scripts/enforcedeco/metadata.desktop b/scripts/enforcedeco/metadata.desktop index ed61f015b..b411036fb 100644 --- a/scripts/enforcedeco/metadata.desktop +++ b/scripts/enforcedeco/metadata.desktop @@ -1,97 +1,97 @@ [Desktop Entry] Name=Enforces Window Decorations on GTK+ window Name[bs]=Forsira dekoracije prozora u GTK+ prozoru Name[ca]=Força la decoració de les finestres a les finestres GTK+ Name[ca@valencia]=Força la decoració de les finestres a les finestres GTK+ Name[cs]=Vynutí dekoraci oken na okně GTK+ Name[da]=Gennemtvinger vinduesdekorationer på GTK+-vinduer Name[de]=Erzwingt Fensterdekorationen für „GTK+“-Fenster Name[el]=Εξαναγκασμός διακοσμήσεων παραθύρου σε παράθυρο GTK+ Name[en_GB]=Enforces Window Decorations on GTK+ window Name[es]=Fuerza decoraciones en las ventanas de GTK+ Name[et]=Akna dekoratsioonide kehtestamine GTK+ aknas Name[eu]=GTK+ leihoengan leiho-apaingarriak behartzen ditu Name[fi]=Pakottaa GTK+-ikkunoiden ikkunakehykset Name[fr]=Forcer les décorations de fenêtres avec les fenêtres GTK+ Name[gl]=Aplica a decoración ás xanelas de GTK+ Name[he]=אכוף עיצוב מסגרת חלון לחלונות GTK Name[hu]=Ablakdekorációk kényszerítése GTK+ ablakokra Name[ia]=Applica Decorationes de fenestra sur fenestra GTK+ Name[id]=Memaksa Dekorasi Jendela pada jendela GTK+ Name[it]=Forza le decorazioni delle finestre GTK+ Name[ja]=GTK+ ウインドウのウィンドウの飾りを強制する Name[ko]=GTK+ 창 장식 강제 사용 Name[lt]=Forsuoja lango dekoracijas GTK+ lange Name[nb]=Påtvinger vinduspynt på GTK+-vindu Name[nds]=Verdwingt dat Opfladusen ok vun GTK+-Finstern Name[nl]=Dwingt vensterdecoraties af op GTK+ venster Name[nn]=Tvingar vindagspynt på GTK+-vindauge Name[pl]=Wymusza wygląd okien na oknach GTK+ Name[pt]=Forçar as Decorações das Janelas nas janelas em GTK+ Name[pt_BR]=Forçar as decorações das janelas em janelas GTK+ Name[ru]=Применение выбранного оформления к окнам GTK+ Name[sk]=Vynúti dekorácie okien na GTK+ okne Name[sl]=Vsili okraske oken na oknih GTK+ Name[sr]=Наметање декорација прозора на ГТК+ прозор Name[sr@ijekavian]=Наметање декорација прозора на ГТК+ прозор Name[sr@ijekavianlatin]=Nametanje dekoracija prozora na GTK+ prozor Name[sr@latin]=Nametanje dekoracija prozora na GTK+ prozor Name[sv]=Tvingar fönsterdekorationer på GTK+ fönster Name[tr]=GTK+ penceresinde Pencere Süslemelerini Yürütür Name[uk]=Примусове декорування вікон для програм на основі GTK+ Name[x-test]=xxEnforces Window Decorations on GTK+ windowxx Name[zh_CN]=强制对 GTK+ 窗口使用窗口装饰 Name[zh_TW]=強制 GTK+ 視窗的視窗裝飾 Comment=Fixes functional deficits for client side decorated windows. Comment[bs]=Popravlja funkcionalne nedostatke klijentskih dekoracija prozora Comment[ca]=Corregeix els dèficits funcionals amb les finestres decorades a la banda del client. Comment[ca@valencia]=Corregeix els dèficits funcionals amb les finestres decorades a la banda del client. Comment[da]=Løser funktionelle mangler ved vinduer med dekorationer på klientsiden. Comment[de]=Behebt funktionale Defizite von Fenstern, die durch Klient-Programme dekoriert werden. Comment[el]=Διόρθωση λειτουργικών ελαττωμάτων στις διακοσμήσεις παραθύρων στην πλευρά του πελάτη. Comment[en_GB]=Fixes functional deficits for client side decorated windows. Comment[es]=Fija deficiencias funcionales en ventanas decoradas en el lado cliente. Comment[et]=Kliendipoolsete dekoratsioonidega akende funktsionaalsete puudujääkide parandamine. Comment[eu]=Gabezia funtzionala konpontzen die bezeroen aldean apaindutako leihoei Comment[fi]=Korjaa asiakkaan kehystämien ikkunoiden toiminnallisia puutteita. Comment[fr]=Corrige les défaillances des fenêtres avec décorations gérées par le client. Comment[gl]=Soluciona problemas funcionais de xanelas decoradas no lado do cliente. Comment[hu]=Javítja a funkcionális hibákat a kliens oldalon dekorált ablakoknál. Comment[id]=Memperbaiki defisit fungsional untuk dekorasi jendela pada sisi klien. Comment[it]=Corregge i problemi funzionali delle finestre con decorazione lato client. Comment[ko]=클라이언트에서 장식되는 창의 기능 오류를 수정합니다. Comment[nb]=Retter opp manglende funksjonalitet for vinduer som er dekorert fra klientsiden. Comment[nds]=Richt dat, wenn vun den Client dekoreert Finstern wiss Könen nich hebbt Comment[nl]=Repareert functionele onvolkomenheden voor gedecoreerde vensters aan de zijde van de client. Comment[nn]=Reparerer vindaugspyntmanglar for vindauge som vert dekorerte av klient. Comment[pl]=Naprawia niedobory funkcjonalne dla okien wystrojonych po stronie klienta. Comment[pt]=Fixa as deficiências funcionais para as janelas decoradas pelo cliente. Comment[pt_BR]=Fixa as deficiências funcionais para as janelas decoradas pelo cliente. Comment[ru]=Исправляет функциональные недостатки окон с собственным оформлением Comment[sk]=Opraví funkčné deficity pre okná dekorované na klientskej strane. Comment[sl]=Odpravi pomanjkanja zmožnosti za okna, ki so okrašena s strani odjemalca. -Comment[sr]=Исправља функционалне недостатке прозора декорисаних при клијенту. -Comment[sr@ijekavian]=Исправља функционалне недостатке прозора декорисаних при клијенту. -Comment[sr@ijekavianlatin]=Ispravlja funkcionalne nedostatke prozora dekorisanih pri klijentu. -Comment[sr@latin]=Ispravlja funkcionalne nedostatke prozora dekorisanih pri klijentu. +Comment[sr]=Исправља функционалне недостатке прозора декорисаних при клијенту +Comment[sr@ijekavian]=Исправља функционалне недостатке прозора декорисаних при клијенту +Comment[sr@ijekavianlatin]=Ispravlja funkcionalne nedostatke prozora dekorisanih pri klijentu +Comment[sr@latin]=Ispravlja funkcionalne nedostatke prozora dekorisanih pri klijentu Comment[sv]=Fixar funktionella svagheter för fönster dekorerade på klientsidan Comment[tr]=İstemci tarafı donatılmış pencereler için, işlevsel açıkları düzeltir. Comment[uk]=Виправляє недостатність функціональних можливостей для декорованих вікон на боці клієнта. Comment[x-test]=xxFixes functional deficits for client side decorated windows.xx Comment[zh_CN]=修复客户端窗口装饰的功能缺失。 Comment[zh_TW]=修正客戶端裝飾視窗功能不足的問題。 Icon=preferences-system-windows-script-enforcedeco X-Plasma-API=javascript X-Plasma-MainScript=code/main.js X-KDE-PluginInfo-Author=Martin Gräßlin X-KDE-PluginInfo-Email=mgraesslin@kde.org X-KDE-PluginInfo-Name=enforcedeco X-KDE-PluginInfo-Version=1.0 X-KDE-PluginInfo-EnabledByDefault=true X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL X-KDE-ServiceTypes=KWin/Script Type=Service diff --git a/scripts/minimizeall/metadata.desktop b/scripts/minimizeall/metadata.desktop index a40d56d3c..1fa1d346c 100644 --- a/scripts/minimizeall/metadata.desktop +++ b/scripts/minimizeall/metadata.desktop @@ -1,93 +1,78 @@ [Desktop Entry] Name=MinimizeAll Name[ca]=MinimitzaTot Name[ca@valencia]=MinimitzaTot Name[cs]=MinimalizovatVše Name[da]=Minimér alle Name[de]=Alle minimieren Name[el]=ΕλαχιστοποίησηΌλων Name[en_GB]=MinimiseAll Name[es]=Maximizar todo Name[et]=Kõigi minimeerimine Name[eu]=Minimizatu guztiak Name[fi]=Pienennä kaikki Name[fr]=Tout réduire Name[gl]=Minimizalo todo Name[he]=מזער הכל Name[hu]=Összes minimalizálása Name[ia]=MaximizaOmne Name[it]=Minimizza tutto Name[ja]=すべて最小化 Name[ko]=모두최소화 Name[lt]=Sumažinti visus Name[nb]=Minimer alt Name[nl]=Alles-minimaliseren Name[nn]=Minimer alle Name[pl]=MinimalizujWszystko Name[pt]=Maximização Total Name[pt_BR]=Minimizar tudo Name[ru]=Сворачивание всех окон Name[sk]=Minimalizovať všetko Name[sl]=Skrči vse Name[sr]=Минимизовање свега Name[sr@ijekavian]=Минимизовање свега Name[sr@ijekavianlatin]=Minimizovanje svega Name[sr@latin]=Minimizovanje svega Name[sv]=Minimera alla Name[tr]=Tümünü simge durumuna küçült Name[uk]=Мінімізувати усі Name[x-test]=xxMinimizeAllxx Name[zh_CN]=最小化所有 Name[zh_TW]=全部最小化 -Comment=Adds a shortcut to minimize all windows or unminimize all such way minimized windows -Comment[ca]=Afegeix una drecera per a minimitzar totes les finestres o desminimitzar-les si estan minimitzades -Comment[ca@valencia]=Afig una drecera per a minimitzar totes les finestres o desminimitzar-les si estan minimitzades -Comment[da]=Tilføjer en genvej til at minimere alle vinduer eller afminimere alle vinduer der er minimeret på den måde -Comment[de]=Fügt einen Kurzbefehl hinzu, um alle Fenster zu minimieren oder alle auf diese Art minimierten Fenster wieder anzuzeigen -Comment[el]=Προσθήκη συντόμευσης για την ελαχιστοποίηση όλων των παραθύρων ή την αναίρεση της ελαχιστοποίησης που έγινε με αυτόν τον τρόπο -Comment[en_GB]=Adds a shortcut to minimise all windows or unminimise all such way minimised windows -Comment[es]=Añade un acceso rápido para minimizar todas la ventanas o restaurar todas las minimizadas de este modo -Comment[et]=Kiirtoimingu lisamine kõigi akende minimeerimiseks või sel moel minimeeritud akende suuruse taastamiseks -Comment[eu]=Erantsi lasterbide bat leiho guztiak minimizatzeko edo hala dauden leiho guztiak leheneratzeko -Comment[fi]=Lisää pikanäppäimen, joka pienentää kaikki ikkunat tai palauttaa kaikki samalla tavalla pienennetyt ikkunat -Comment[fr]=Ajoute un raccourci pour réduire toutes les fenêtres ou afficher toutes les fenêtres réduites -Comment[gl]=Engade un atallo para minimizar todas as xanelas e restauralas de novo. -Comment[he]=מוסיף קיצור דרך למיעור כל החלונות או הגדלת כל החלונות אם הם ממוזערים -Comment[hu]=Gyorsgomb az összes ablak minimalizálásához vagy az így minimalizált ablakok visszaállításához -Comment[it]=Aggiunge una scorciatoia per minimizzare tutte le finestre o ripristinare le finestre così minimizzate -Comment[ko]=모든 창을 최소화하고 복원하는 단축키 추가 -Comment[nb]=Legger til en snarvei for å minimere alle vinduer, eller oppheve slik minimering -Comment[nl]=Voegt een sneltoets toe om alle vensters te minimaliseren of maakt de minimalisatie van deze ongedaan -Comment[nn]=Legg til ein snarveg for å minimera alle vindauge, eller oppheva slik minimering -Comment[pl]=Dodaje skrót do zminimalizowania wszystkich okien lub działania odwrotnego dla wszystkich w ten sposób zminimalizowanych okien -Comment[pt]=Adiciona um atalho para minimizar todas as janelas ou retirá-las desse estado -Comment[pt_BR]=Adiciona um atalho para minimizar todas as janelas ou retirá-las desse estado -Comment[ru]=Добавляет глобальную комбинацию клавиш для сворачивания и восстановления из свёрнутого всех окон -Comment[sk]=Pridá skratku na minimalizáciu všetkých okien alebo obnoví všetky takto minimalizované okná -Comment[sl]=Doda bližnjico za skrčenje vseh oken ali pa povečanje tako skrčenih oken -Comment[sr]=Додаје пречицу за минимизовање свих прозора или обнављање овако минимизованих прозора -Comment[sr@ijekavian]=Додаје пречицу за минимизовање свих прозора или обнављање овако минимизованих прозора -Comment[sr@ijekavianlatin]=Dodaje prečicu za minimizovanje svih prozora ili obnavljanje ovako minimizovanih prozora -Comment[sr@latin]=Dodaje prečicu za minimizovanje svih prozora ili obnavljanje ovako minimizovanih prozora -Comment[sv]=Lägger till en genväg för att minimera alla fönster eller återställa alla fönster som minimerats på detta sätt -Comment[tr]=Tüm pencereleri simge durumuna küçültmek ve geri almak için bir kısayol ekler -Comment[uk]=Додає кнопку для мінімізації усіх вікон або скасування такої мінімізації для усіх вікон -Comment[x-test]=xxAdds a shortcut to minimize all windows or unminimize all such way minimized windowsxx -Comment[zh_CN]=添加快捷键来最小化所有窗口或者取消最小化用此方式最小化的窗口 -Comment[zh_TW]=新增捷徑將所有視窗最小化或恢復原狀 +Comment=Adds a shortcut to minimize and restore all windows +Comment[ca]=Afegeix una drecera per a minimitzar i restaurar totes les finestres +Comment[ca@valencia]=Afig una drecera per a minimitzar i restaurar totes les finestres +Comment[de]=Fügt einen Kurzbefehl hinzu, um alle Fenster zu minimieren oder wieder anzuzeigen +Comment[en_GB]=Adds a shortcut to minimise and restore all windows +Comment[es]=Añade un acceso rápido para minimizar y restaurar todas la ventanas +Comment[gl]=Engade un atallo para minimizar e restaurar todas as xanelas. +Comment[it]=Aggiunge una scorciatoia per minimizzare e ripristinare tutte le finestre +Comment[nl]=Voegt een sneltoets toe om alle vensters te minimaliseren en te herstellen +Comment[nn]=Legg til snarvegar for å minimera og gjenoppretta alle vindauge +Comment[pl]=Dodaje skrót do zminimalizowania i przywracania wszystkich okien +Comment[pt]=Adiciona um atalho para minimizar ou repor todas as janelas +Comment[sl]=Doda bližnjico za skrčenje ali povečanje vseh oken +Comment[sr]=Додаје пречицу за минимизовање и обнављање свих прозора +Comment[sr@ijekavian]=Додаје пречицу за минимизовање и обнављање свих прозора +Comment[sr@ijekavianlatin]=Dodaje prečicu za minimizovanje i obnavljanje svih prozora +Comment[sr@latin]=Dodaje prečicu za minimizovanje i obnavljanje svih prozora +Comment[sv]=Lägger till en genväg för att minimera och återställa alla fönster +Comment[uk]=Додає скорочення мінімізації усіх вікон або скасування такої мінімізації для усіх вікон +Comment[x-test]=xxAdds a shortcut to minimize and restore all windowsxx +Comment[zh_CN]=添加最小化与恢复所有窗口的快捷方式 Icon=preferences-system-windows-script-test Type=Service X-Plasma-API=javascript X-Plasma-MainScript=code/main.js X-KWin-Border-Activate=true X-KDE-PluginInfo-Author=Thomas Lübking X-KDE-PluginInfo-Email=thomas.luebking@gmail.com X-KDE-PluginInfo-Name=minimizeall X-KDE-PluginInfo-Version=1.0 X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL X-KDE-ServiceTypes=KWin/Script X-KDE-PluginKeyword=minimizeall diff --git a/scripts/synchronizeskipswitcher/metadata.desktop b/scripts/synchronizeskipswitcher/metadata.desktop index b5daced90..b227fad91 100644 --- a/scripts/synchronizeskipswitcher/metadata.desktop +++ b/scripts/synchronizeskipswitcher/metadata.desktop @@ -1,104 +1,104 @@ [Desktop Entry] Name=Synchronize Skip Switcher with Taskbar Name[bs]=Uskladite Skip Switcher s trake -Name[ca]=Sincronitza el «Ignora el commutador» amb la barra de tasques -Name[ca@valencia]=Sincronitza el «Ignora el commutador» amb la barra de tasques +Name[ca]=Sincronitza «Ignora el commutador» amb la barra de tasques +Name[ca@valencia]=Sincronitza «Ignora el commutador» amb la barra de tasques Name[cs]=Synchronizovat přepínač přeskakování s pruhem úloh Name[da]=Synkronisér skip-skifter med opgavelinjen Name[de]=Fensterwechsler mit Fensterleiste abgleichen Name[el]=Συγχρονισμός εναλλάκτη παράβλεψης με τη γραμμή εργασιών Name[en_GB]=Synchronize Skip Switcher with Taskbar Name[es]=Sincronizar «Omitir cambiador» con la barra de tareas Name[et]=Vahelejätmise lülitaja sünkroonimine tegumiribaga Name[eu]=Sinkronizatu Saltatu kommutadorea ataza-barrarekin Name[fi]=Synkronoi tehtävävaihdon ohitus tehtäväpalkin kanssa Name[fr]=Synchroniser le sélecteur de fenêtres à ignorer avec la barre de tâches Name[gl]=Sincronizar o ignorar o alternador coa barra de tarefas Name[hu]=Skip-váltó szinkronizálása a feladatsávval Name[ia]=Synchronisa Commutator de salto con Barra de carga -Name[id]=Sinkronisasi Pengganti Lewatkan dengan Batang Tugas +Name[id]=Sinkronisasi Pengganti Lewatkan dengan Bilah Tugas Name[it]=Sincronizza Salta barra delle applicazioni con la barra delle applicazioni Name[kk]=Терезе көрсету күйін ауыстырғышты тапсырма панелімен қадамдастыру Name[km]=ធ្វើ​សមកាលម្ម​​ដោយ​រំលង​ឧបករណ៍​ប្ដូរ​​ជា​មួយ​របារ​ភារកិច្ច Name[ko]=작업 표시줄과 건너뛸 창 동기화 Name[mr]=कार्यपट्टी बरोबर स्किप बदलणाऱ्याचे समन्वय करा Name[nb]=Synkroniser Hopp over bytter med Oppgavelinja Name[nds]=Bi't Wesseln övergahn un Programmbalken synkroniseren Name[nl]=Overslaanschakelaar met taakbalk synchroniseren Name[nn]=Synkroniser «hopp over»-byte med oppgåvelinja Name[pa]=ਛੱਡਣ ਸਵਿੱਚਰ ਨੂੰ ਟਾਸਕਬਾਰ ਨਾਲ ਮਿਲਾਉ Name[pl]=Synchronizuj pomijanie przełącznika z paskiem zadań Name[pt]=Sincronizar o 'Ignorar o Selector' com a Barra de Tarefas Name[pt_BR]=Sincronizar o 'Ignorar seletor' com a barra de tarefas Name[ru]=Синхронизация переключателя пропусков с панелью задач Name[sk]=Synchronizovať Preskočiť prepínač s taskbarom Name[sl]=Uskladi preskok preklapljanja z opravilno vrstico Name[sr]=Синхронизовано прескакање мењача са траком задатака Name[sr@ijekavian]=Синхронизовано прескакање мењача са траком задатака Name[sr@ijekavianlatin]=Sinhronizovano preskakanje menjača sa trakom zadataka Name[sr@latin]=Sinhronizovano preskakanje menjača sa trakom zadataka Name[sv]=Synkronisera överhoppning av aktivitetsraden med fönsterbyte Name[tr]=Görev Çubuğu ile Atlama Seçiciyi Eşzamanla Name[uk]=Синхронізувати виключення вікон у перемикачі з панеллю задач Name[x-test]=xxSynchronize Skip Switcher with Taskbarxx Name[zh_CN]=和任务栏同步跳过切换器设置 Name[zh_TW]=將工作列中設定要忽略的視窗也套用在視窗切換器 Comment=Hides all windows marked as Skip Taskbar to be also excluded from window switchers (e.g. Alt+Tab) Comment[bs]=Skriva sve prozorie označene kao Skip traka da će također biti isključeni iz prebacivanja prozora(npr. Alt + Tab) Comment[ca]=Oculta totes les finestres marcades com a «Ignora la barra de tasques» que també s'excloguin del commutador de finestres (p. ex. Alt+Tab) Comment[ca@valencia]=Oculta totes les finestres marcades com a «Ignora la barra de tasques» que també s'excloguin del commutador de finestres (p. ex. Alt+Tab) Comment[cs]=Skryje všechna okna označená k přeskočení v pruhu úloh také pro přepínače oken (např. Alt+Tab) Comment[da]=Skjuler alle vinduer der er markeret som "Skip opgavelinje", så de også udelades fra vinduesskiftere (f.eks. Alt+Tab) Comment[de]=Alle Fenster, die so eingestellt sind, dass sie nicht in der Fensterleiste angezeigt werden, werden ebenfalls aus den Fensterwechslern (z. B. Alt+Tab) ausgeschlossen. Comment[el]=Αποκρύπτει όλα τα παράθυρα που είναι σημειωμένα ως παράβλεψη γραμμής εργασιών για να αποκλειστούν και από εναλλάκτες παραθύρων (π.χ. Alt+Tab) Comment[en_GB]=Hides all windows marked as Skip Taskbar to be also excluded from window switchers (e.g. Alt+Tab) Comment[es]=Oculta todas las ventanas marcadas como «Omitir barra de tareas» para que también sean excluidas de los selectores de ventana (por ejemplo, Alt+Tab) Comment[et]=Kõigi tegumiribalt väljajäetavaks märgitud akende peitmine, et neid ei kaasataks ka aknavahetajatesse (nt Alt+Tab) Comment[eu]='Saltatu ataza-barra' gisa markatuta dauden leiho guztiak ezkutatzen ditu leiho-kommutadoretik kanpo ere uzteko (adib., Alt+Tab) Comment[fi]=Piilottaa kaikki tehtäväpalkin ohittavat ikkunat myös tehtävävaihdosta (esim. Alt+sarkain) Comment[fr]=Cache toutes les fenêtres marquées comme « À ignorer » dans la barre des tâches pour les exclure du sélecteur de fenêtres (c'est-à-dire « Alt »+ « Tab ») Comment[gl]=Agocha todas as xanelas que deban ser ignoradas pola barra de tarefas tamén dos alternadores de xanelas (p.ex. Alt+Tab) Comment[he]=מסתיר את כל החלונות המסומנים לדילוג על שורת המשימות לא להיות גם במחליף חלונות (כגון Alt+Tab) Comment[hu]=A Skip feladatsávként megjelölt összes ablak elrejtése, kivéve az ablakváltókat (például Alt+Tab) Comment[ia]=Cela omne fenestras marcate como barra de cargas de salto pro anque esser excludite ex commutatores de fenestra (p.ex.Alt+Tab) -Comment[id]=Sembunyikan semua jendela bertanda Lewatkan Batang Tugas untuk dikecualikan dari pengganti jendela (misalnya Alt+Tab) +Comment[id]=Sembunyikan semua jendela bertanda Lewatkan Bilah Tugas untuk dikecualikan dari pengganti jendela (misalnya Alt+Tab) Comment[it]=Nascondi tutte le finestre marcate come Salta barra delle applicazioni che sono escluse anche dallo scambiafinestre (es. Alt+Tab) Comment[kk]=Көрсетілмейтін деп белгіленген бүкіл терезелерін терезе ауыстырғышында да (мысалы, Alt-Tab дегенде) көрсетпеу Comment[km]=លាក់​បង្អួច​ទាំងអស់​ដែល​បាន​សម្គាល់​ថា​រំលង​របារ​ភារកិច្ច ក៏​ត្រូវ​បាន​ដក​ចេញ​ពី​​ឧបករណ៍​ប្ដូរ​បង្អួច (ឧ. Alt+Tab) Comment[ko]=작업 표시줄에서 건너뛰도록 표시한 창을 창 전환기에서도 표시하지 않음 (예: Alt+Tab) Comment[mr]=स्किप कार्यपट्टीत निवडलेल्या सर्व चौकटी चौकटी बदलणाऱ्यापासून (उदा. Alt+Tab) लपवितो Comment[nb]=Skjuler alle vinduer merket med Hopp over Oppgavelinja så de heller ikke blir tatt med i vindusbyttere (f.eks. Alt+TAB) Comment[nds]=Finstern mit de Instellen, se dukt nich op den Programmbalken op, warrt bi't Finsterwesseln versteken (a.B. bi Alt+Tab) Comment[nl]=Verbergt alle vensters gemarkeerd als Taakbalkoverslaan om ook te worden uitgesloten van vensterschakelaars (bijv. Alt+Tab) Comment[nn]=Alle vindauge som ikkje høyrer til «oppgåvehandsamaren» vert utelatne frå «vindaugsbytaren» (eksempelvis «Alt + Tab») Comment[pl]=Ukrywa wszystkie okna oznaczone jako "pomiń pasek zadań", tak aby były one wykluczone także z układów przełączania okien (np. Alt+Tab) Comment[pt]=Esconde todas as janelas marcadas como 'Ignorar a Barra de Tarefas' para também serem excluídas dos selectores de janelas (p.ex., Alt+Tab) Comment[pt_BR]=Oculta todas as janelas marcadas como 'Ignorar a barra de tarefas' para também serem excluídas dos seletores de janelas (p.ex., Alt+Tab) Comment[ru]=Скрывает все окна, не показываемые на панели задач, чтобы исключить их также из переключателей окон (например, Alt+Tab) Comment[sk]=Skryje všetky okná označené ako Preskočiť taskbar na ich vylúčenie z prepínačov okien (napr. Alt+Tab) Comment[sl]=Vsa okna, ki so označena z »Preskoči opravilno vrstico«, bodo izločena tudi iz preklapljanja med okni (npr. z Alt+Tab) Comment[sr]=Прозори са задатим скривањем на траци задатака такође бивају прескакани у мењачима прозора (нпр. на Alt-Tab) Comment[sr@ijekavian]=Прозори са задатим скривањем на траци задатака такође бивају прескакани у мењачима прозора (нпр. на Alt-Tab) Comment[sr@ijekavianlatin]=Prozori sa zadatim skrivanjem na traci zadataka takođe bivaju preskakani u menjačima prozora (npr. na Alt-Tab) Comment[sr@latin]=Prozori sa zadatim skrivanjem na traci zadataka takođe bivaju preskakani u menjačima prozora (npr. na Alt-Tab) Comment[sv]=Dölj alla fönster som är markerade att hoppa över aktivitetsraden så att de också utesluts vid fönsterbyte (t.ex. Alt+Tabulator) Comment[tr]=Görev Çubuğunu Atla olarak seçilen tüm pencereleri gizler, ayrıca pencere seçiciler tarafından hariç tutulanları da gizler (Alt+Tab gibi) Comment[uk]=Приховує всі вікна, позначені як такі, що не показуються на панелі задач, так, щоб їх не було показано у списках перемикання вікон (наприклад Alt+Tab) Comment[vi]=Ẩn tất cả cửa sổ được đánh dấu "Bỏ qua trên thanh tác vụ" khỏi trình chuyển đổi cửa sổ (VD: Alt+Tab) Comment[x-test]=xxHides all windows marked as Skip Taskbar to be also excluded from window switchers (e.g. Alt+Tab)xx Comment[zh_CN]=所有标记为跳过任务栏的窗口也从窗口切换器 (例如Alt+Tab) 中排除 Comment[zh_TW]=工作列中設定要忽略的視窗,在視窗切換器(如 Alt+Tab 切換)切換時也忽略 Icon=preferences-system-windows-script-synchronizeskipswitcher X-Plasma-API=javascript X-Plasma-MainScript=code/main.js X-KDE-PluginInfo-Author=Martin Gräßlin X-KDE-PluginInfo-Email=mgraesslin@kde.org X-KDE-PluginInfo-Name=synchronizeskipswitcher X-KDE-PluginInfo-Version=1.0 X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL X-KDE-ServiceTypes=KWin/Script Type=Service diff --git a/shell_client.cpp b/shell_client.cpp index 6d6d3d53e..5266e082f 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -1,1641 +1,1692 @@ /******************************************************************** 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 "shell_client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "placement.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "virtualdesktops.h" #include "workspace.h" #include "screens.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include + #include #include #include #include #include #include using namespace KWayland::Server; -static const QByteArray s_schemePropertyName = QByteArrayLiteral("KDE_COLOR_SCHEME_PATH"); -static const QByteArray s_appMenuServiceNamePropertyName = QByteArrayLiteral("KDE_APPMENU_SERVICE_NAME"); -static const QByteArray s_appMenuObjectPathPropertyName = QByteArrayLiteral("KDE_APPMENU_OBJECT_PATH"); static const QByteArray s_skipClosePropertyName = QByteArrayLiteral("KWIN_SKIP_CLOSE_ANIMATION"); namespace KWin { ShellClient::ShellClient(ShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(surface) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(nullptr) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::ShellClient(XdgShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(surface) , m_xdgShellPopup(nullptr) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::ShellClient(XdgShellPopupInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(surface) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::~ShellClient() = default; template void ShellClient::initSurface(T *shellSurface) { m_caption = shellSurface->title().simplified(); // delay till end of init QTimer::singleShot(0, this, &ShellClient::updateCaption); connect(shellSurface, &T::destroyed, this, &ShellClient::destroyClient); connect(shellSurface, &T::titleChanged, this, [this] (const QString &s) { const auto oldSuffix = m_captionSuffix; m_caption = s.simplified(); updateCaption(); if (m_captionSuffix == oldSuffix) { // don't emit caption change twice // it already got emitted by the changing suffix emit captionChanged(); } } ); connect(shellSurface, &T::moveRequested, this, [this] { // TODO: check the seat and serial performMouseCommand(Options::MouseMove, Cursor::pos()); } ); setResourceClass(shellSurface->windowClass()); setDesktopFileName(shellSurface->windowClass()); connect(shellSurface, &T::windowClassChanged, this, [this] (const QByteArray &windowClass) { setResourceClass(windowClass); setDesktopFileName(windowClass); } ); connect(shellSurface, &T::resizeRequested, this, [this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) { // TODO: check the seat and serial Q_UNUSED(seat) Q_UNUSED(serial) if (!isResizable() || isShade()) { return; } if (isMoveResize()) { finishMoveResize(false); } setMoveResizePointerButtonDown(true); setMoveOffset(Cursor::pos() - pos()); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); auto toPosition = [edges] { Position pos = PositionCenter; if (edges.testFlag(Qt::TopEdge)) { pos = PositionTop; } else if (edges.testFlag(Qt::BottomEdge)) { pos = PositionBottom; } if (edges.testFlag(Qt::LeftEdge)) { pos = Position(pos | PositionLeft); } else if (edges.testFlag(Qt::RightEdge)) { pos = Position(pos | PositionRight); } return pos; }; setMoveResizePointerMode(toPosition()); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); } ); connect(shellSurface, &T::maximizedChanged, this, [this] (bool maximized) { if (m_shellSurface && isFullScreen()) { // ignore for wl_shell - there it is mutual exclusive and messes with the geometry return; } maximize(maximized ? MaximizeFull : MaximizeRestore); } ); // TODO: consider output! connect(shellSurface, &T::fullscreenChanged, this, &ShellClient::clientFullScreenChanged); connect(shellSurface, &T::transientForChanged, this, &ShellClient::setTransient); connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateClientOutputs); connect(screens(), &Screens::changed, this, &ShellClient::updateClientOutputs); if (!m_internal) { setupWindowRules(false); } } void ShellClient::init() { connect(this, &ShellClient::desktopFileNameChanged, this, &ShellClient::updateIcon); findInternalWindow(); createWindowId(); setupCompositing(); SurfaceInterface *s = surface(); Q_ASSERT(s); if (s->buffer()) { setReadyForPainting(); if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } m_unmapped = false; m_clientSize = s->size(); } else { ready_for_painting = false; } if (m_internalWindow) { updateInternalWindowGeometry(); updateDecoration(true); } else { doSetGeometry(QRect(QPoint(0, 0), m_clientSize)); } if (waylandServer()->inputMethodConnection() == s->client()) { m_windowType = NET::OnScreenDisplay; } connect(s, &SurfaceInterface::sizeChanged, this, [this] { m_clientSize = surface()->size(); doSetGeometry(QRect(geom.topLeft(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } ); connect(s, &SurfaceInterface::unmapped, this, &ShellClient::unmap); connect(s, &SurfaceInterface::unbound, this, &ShellClient::destroyClient); connect(s, &SurfaceInterface::destroyed, this, &ShellClient::destroyClient); if (m_shellSurface) { initSurface(m_shellSurface); auto setPopup = [this] { // TODO: verify grab serial m_hasPopupGrab = m_shellSurface->isPopup(); }; connect(m_shellSurface, &ShellSurfaceInterface::popupChanged, this, setPopup); setPopup(); } else if (m_xdgShellSurface) { initSurface(m_xdgShellSurface); auto global = static_cast(m_xdgShellSurface->global()); connect(global, &XdgShellInterface::pingDelayed, this, [this](qint32 serial) { auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); setUnresponsive(true); } }); connect(global, &XdgShellInterface::pingTimeout, this, [this](qint32 serial) { auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { if (it.value() == PingReason::CloseWindow) { qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption(); //for internal windows, killing the window will delete this QPointer guard(this); killWindow(); if (!guard) { return; } } m_pingSerials.erase(it); } }); connect(global, &XdgShellInterface::pongReceived, this, [this](qint32 serial){ auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { setUnresponsive(false); m_pingSerials.erase(it); } }); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowMenuRequested, this, [this] (SeatInterface *seat, quint32 serial, const QPoint &surfacePos) { // TODO: check serial on seat Q_UNUSED(seat) Q_UNUSED(serial) performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); } ); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::minimizeRequested, this, [this] { performMouseCommand(Options::MouseMinimize, Cursor::pos()); } ); auto configure = [this] { if (m_closing) { return; } m_xdgShellSurface->configure(xdgSurfaceStates()); }; configure(); connect(this, &AbstractClient::activeChanged, this, configure); connect(this, &AbstractClient::clientStartUserMovedResized, this, configure); connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure); } else if (m_xdgShellPopup) { connect(m_xdgShellPopup, &XdgShellPopupInterface::grabRequested, this, [this](SeatInterface *seat, quint32 serial) { Q_UNUSED(seat) Q_UNUSED(serial) //TODO - should check the parent had focus m_hasPopupGrab = true; }); QRect position = QRect(m_xdgShellPopup->transientOffset(), m_xdgShellPopup->initialSize()); m_xdgShellPopup->configure(position); connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &ShellClient::destroyClient); } // set initial desktop setDesktop(rules()->checkDesktop(m_internal ? int(NET::OnAllDesktops) : VirtualDesktopManager::self()->current(), true)); // TODO: merge in checks from Client::manage? if (rules()->checkMinimize(false, true)) { minimize(true); // No animation } + setSkipTaskbar(rules()->checkSkipTaskbar(m_plasmaShellSurface ? m_plasmaShellSurface->skipTaskbar() : false, true)); + setSkipPager(rules()->checkSkipPager(false, true)); + setSkipSwitcher(rules()->checkSkipSwitcher(false, true)); + setKeepAbove(rules()->checkKeepAbove(false, true)); + setKeepBelow(rules()->checkKeepBelow(false, true)); + setShortcut(rules()->checkShortcut(QString(), true)); // setup shadow integration getShadow(); connect(s, &SurfaceInterface::shadowChanged, this, &Toplevel::getShadow); connect(waylandServer(), &WaylandServer::foreignTransientChanged, this, [this](KWayland::Server::SurfaceInterface *child) { if (child == surface()) { setTransient(); } }); setTransient(); // check whether we have a ServerSideDecoration if (ServerSideDecorationInterface *deco = ServerSideDecorationInterface::get(s)) { installServerSideDecoration(deco); } AbstractClient::updateColorScheme(QString()); - updateApplicationMenu(); if (!m_internal) { discardTemporaryRules(); applyWindowRules(); // Just in case RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules updateWindowRules(Rules::All); // Was blocked while !isManaged() } } void ShellClient::destroyClient() { m_closing = true; Deleted *del = nullptr; if (workspace()) { del = Deleted::create(this); } emit windowClosed(this, del); destroyWindowManagementInterface(); destroyDecoration(); if (workspace()) { StackingUpdatesBlocker blocker(workspace()); if (transientFor()) { transientFor()->removeTransient(this); } for (auto it = transients().constBegin(); it != transients().constEnd();) { if ((*it)->transientFor() == this) { removeTransient(*it); it = transients().constBegin(); // restart, just in case something more has changed with the list } else { ++it; } } } waylandServer()->removeClient(this); if (del) { del->unrefWindow(); } m_shellSurface = nullptr; m_xdgShellSurface = nullptr; m_xdgShellPopup = nullptr; deleteClient(this); } void ShellClient::deleteClient(ShellClient *c) { delete c; } QStringList ShellClient::activities() const { // TODO: implement return QStringList(); } QPoint ShellClient::clientContentPos() const { return -1 * clientPos(); } QSize ShellClient::clientSize() const { return m_clientSize; } void ShellClient::debug(QDebug &stream) const { // TODO: implement Q_UNUSED(stream) } Layer ShellClient::layerForDock() const { if (m_plasmaShellSurface) { switch (m_plasmaShellSurface->panelBehavior()) { case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: return NormalLayer; case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: return AboveLayer; case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: return DockLayer; default: Q_UNREACHABLE(); break; } } return AbstractClient::layerForDock(); } QRect ShellClient::transparentRect() const { // TODO: implement return QRect(); } NET::WindowType ShellClient::windowType(bool direct, int supported_types) const { // TODO: implement Q_UNUSED(direct) Q_UNUSED(supported_types) return m_windowType; } double ShellClient::opacity() const { return m_opacity; } void ShellClient::setOpacity(double opacity) { const qreal newOpacity = qBound(0.0, opacity, 1.0); if (newOpacity == m_opacity) { return; } const qreal oldOpacity = m_opacity; m_opacity = newOpacity; addRepaintFull(); emit opacityChanged(this, oldOpacity); } void ShellClient::addDamage(const QRegion &damage) { auto s = surface(); if (s->size().isValid()) { m_clientSize = s->size(); QPoint position = geom.topLeft(); if (m_positionAfterResize.isValid()) { addLayerRepaint(geometry()); position = m_positionAfterResize.point(); m_positionAfterResize.clear(); } doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } markAsMapped(); setDepth((s->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24); repaints_region += damage.translated(clientPos()); Toplevel::addDamage(damage); } void ShellClient::setInternalFramebufferObject(const QSharedPointer &fbo) { if (fbo.isNull()) { unmap(); return; } //Kwin currently scales internal windows to 1, so this is currently always correct //when that changes, this needs adjusting m_clientSize = fbo->size(); markAsMapped(); doSetGeometry(QRect(geom.topLeft(), m_clientSize)); Toplevel::setInternalFramebufferObject(fbo); Toplevel::addDamage(QRegion(0, 0, width(), height())); } void ShellClient::markAsMapped() { if (!m_unmapped) { return; } m_unmapped = false; if (!ready_for_painting) { setReadyForPainting(); } else { addRepaintFull(); emit windowShown(this); } if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } updateShowOnScreenEdge(); } void ShellClient::createDecoration(const QRect &oldGeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { GeometryUpdatesBlocker blocker(this); RequestGeometryBlocker requestBlocker(this); QRect oldgeom = geometry(); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); } setDecoration(decoration); // TODO: ensure the new geometry still fits into the client area (e.g. maximized windows) doSetGeometry(QRect(oldGeom.topLeft(), m_clientSize + (decoration ? QSize(decoration->borderLeft() + decoration->borderRight(), decoration->borderBottom() + decoration->borderTop()) : QSize()))); emit geometryShapeChanged(this, oldGeom); } void ShellClient::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); if (m_serverDecoration && isDecorated()) { m_serverDecoration->setMode(KWayland::Server::ServerSideDecorationManagerInterface::Mode::Server); } getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); blockGeometryUpdates(false); } void ShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { - Q_UNUSED(force) + if (areGeometryUpdatesBlocked()) { + // when the GeometryUpdateBlocker exits the current geom is passed to setGeometry + // thus we need to set it here. + geom = QRect(x, y, w, h); + if (pendingGeometryUpdate() == PendingGeometryForced) + {} // maximum, nothing needed + else if (force == ForceGeometrySet) + setPendingGeometryUpdate(PendingGeometryForced); + else + setPendingGeometryUpdate(PendingGeometryNormal); + return; + } + if (pendingGeometryUpdate() != PendingGeometryNone) { + // reset geometry to the one before blocking, so that we can compare properly + geom = geometryBeforeUpdateBlocking(); + } // TODO: better merge with Client's implementation - if (QSize(w, h) == geom.size()) { + if (QSize(w, h) == geom.size() && !m_positionAfterResize.isValid()) { // size didn't change, update directly doSetGeometry(QRect(x, y, w, h)); } else { // size did change, Client needs to provide a new buffer requestGeometry(QRect(x, y, w, h)); } } void ShellClient::doSetGeometry(const QRect &rect) { if (geom == rect && pendingGeometryUpdate() == PendingGeometryNone) { return; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } - const QRect old = geom; geom = rect; if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) { // use first valid geometry as restore geometry m_geomMaximizeRestore = geom; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } syncGeometryToInternalWindow(); if (hasStrut()) { workspace()->updateClientArea(); } + const auto old = geometryBeforeUpdateBlocking(); + updateGeometryBeforeUpdateBlocking(); emit geometryShapeChanged(this, old); if (isResize()) { performMoveResize(); } } void ShellClient::doMove(int x, int y) { Q_UNUSED(x) Q_UNUSED(y) syncGeometryToInternalWindow(); } void ShellClient::syncGeometryToInternalWindow() { if (!m_internalWindow) { return; } const QRect windowRect = QRect(geom.topLeft() + QPoint(borderLeft(), borderTop()), geom.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom())); if (m_internalWindow->geometry() != windowRect) { // delay to end of cycle to prevent freeze, see BUG 384441 QTimer::singleShot(0, m_internalWindow, std::bind(static_cast(&QWindow::setGeometry), m_internalWindow, windowRect)); } } QByteArray ShellClient::windowRole() const { return QByteArray(); } -bool ShellClient::belongsToSameApplication(const AbstractClient *other, bool active_hack) const +bool ShellClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const { - Q_UNUSED(active_hack) + if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) { + if (other->desktopFileName() == desktopFileName()) { + return true; + } + } if (auto s = other->surface()) { return s->client() == surface()->client(); } return false; } void ShellClient::blockActivityUpdates(bool b) { Q_UNUSED(b) } void ShellClient::updateCaption() { const QString oldSuffix = m_captionSuffix; const auto shortcut = shortcutCaptionSuffix(); m_captionSuffix = shortcut; if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { int i = 2; do { m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>'); i++; } while (findClientWithSameCaption()); } if (m_captionSuffix != oldSuffix) { emit captionChanged(); } } void ShellClient::closeWindow() { if (m_xdgShellSurface && isCloseable()) { m_xdgShellSurface->close(); const qint32 pingSerial = static_cast(m_xdgShellSurface->global())->ping(m_xdgShellSurface); m_pingSerials.insert(pingSerial, PingReason::CloseWindow); } else if (m_qtExtendedSurface && isCloseable()) { m_qtExtendedSurface->close(); } else if (m_internalWindow) { m_internalWindow->hide(); } } AbstractClient *ShellClient::findModal(bool allow_itself) { Q_UNUSED(allow_itself) return nullptr; } bool ShellClient::isCloseable() const { if (m_windowType == NET::Desktop || m_windowType == NET::Dock) { return false; } if (m_xdgShellSurface) { return true; } if (m_internal) { return true; } return m_qtExtendedSurface ? true : false; } -bool ShellClient::isFullScreenable() const -{ - return false; -} - bool ShellClient::isFullScreen() const { return m_fullScreen; } bool ShellClient::isMaximizable() const { if (m_internal) { return false; } return true; } bool ShellClient::isMinimizable() const { if (m_internal) { return false; } return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal); } bool ShellClient::isMovable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isMovableAcrossScreens() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isResizable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isShown(bool shaded_is_shown) const { Q_UNUSED(shaded_is_shown) return !m_closing && !m_unmapped && !isMinimized() && !m_hidden; } void ShellClient::hideClient(bool hide) { if (m_hidden == hide) { return; } m_hidden = hide; if (hide) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); } else { emit windowShown(this); } } static bool changeMaximizeRecursion = false; void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) { if (changeMaximizeRecursion) { return; } if (!isResizable()) { return; } const QRect clientArea = isElectricBorderMaximizing() ? workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()) : workspace()->clientArea(MaximizeArea, this); MaximizeMode oldMode = m_maximizeMode; StackingUpdatesBlocker blocker(workspace()); RequestGeometryBlocker geometryBlocker(this); // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeVertical); if (horizontal) m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeHorizontal); } // TODO: add more checks as in Client // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_maximizeMode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((m_maximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(m_maximizeMode & MaximizeVertical); } if ((m_maximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(m_maximizeMode & MaximizeHorizontal); } if ((m_maximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { emit c->maximizedChanged(m_maximizeMode & MaximizeFull); } changeMaximizeRecursion = false; } if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry changeMaximizeRecursion = true; setNoBorder(rules()->checkNoBorder(m_maximizeMode == MaximizeFull)); changeMaximizeRecursion = false; } // Conditional quick tiling exit points const auto oldQuickTileMode = quickTileMode(); if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { if (oldMode == MaximizeFull && !clientArea.contains(m_geomMaximizeRestore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually } else if ((oldMode == MaximizeVertical && m_maximizeMode == MaximizeRestore) || (oldMode == MaximizeFull && m_maximizeMode == MaximizeHorizontal)) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry } } // TODO: check rules if (m_maximizeMode == MaximizeFull) { m_geomMaximizeRestore = geometry(); // TODO: Client has more checks if (options->electricBorderMaximize()) { updateQuickTileMode(QuickTileFlag::Maximize); } else { updateQuickTileMode(QuickTileFlag::None); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } - requestGeometry(workspace()->clientArea(MaximizeArea, this)); + setGeometry(workspace()->clientArea(MaximizeArea, this)); workspace()->raiseClient(this); } else { if (m_maximizeMode == MaximizeRestore) { updateQuickTileMode(QuickTileFlag::None); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } if (m_geomMaximizeRestore.isValid()) { - requestGeometry(m_geomMaximizeRestore); + setGeometry(m_geomMaximizeRestore); } else { - requestGeometry(workspace()->clientArea(PlacementArea, this)); + setGeometry(workspace()->clientArea(PlacementArea, this)); } } } MaximizeMode ShellClient::maximizeMode() const { return m_maximizeMode; } bool ShellClient::noBorder() const { if (isInternal()) { return m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup); } if (m_serverDecoration) { if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return m_userNoBorder || isFullScreen(); } } return true; } void ShellClient::setFullScreen(bool set, bool user) { - Q_UNUSED(set) - Q_UNUSED(user) + if (!isFullScreen() && !set) + return; + if (user && !userCanSetFullScreen()) + return; + set = rules()->checkFullScreen(set && !isSpecialWindow()); + setShade(ShadeNone); + bool was_fs = isFullScreen(); + if (was_fs) + workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event + else + m_geomFsRestore = geometry(); + m_fullScreen = set; + if (was_fs == isFullScreen()) + return; + if (set) { + untab(); + workspace()->raiseClient(this); + } + RequestGeometryBlocker requestBlocker(this); + StackingUpdatesBlocker blocker1(workspace()); + GeometryUpdatesBlocker blocker2(this); + workspace()->updateClientLayer(this); // active fullscreens get different layer + updateDecoration(false, false); + if (isFullScreen()) { + setGeometry(workspace()->clientArea(FullScreenArea, this)); + } else { + if (!m_geomFsRestore.isNull()) { + int currentScreen = screen(); + setGeometry(QRect(m_geomFsRestore.topLeft(), adjustedSize(m_geomFsRestore.size()))); + if( currentScreen != screen()) + workspace()->sendClientToScreen( this, currentScreen ); + } else { + // does this ever happen? + setGeometry(workspace()->clientArea(MaximizeArea, this)); + } + } + updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); + + if (was_fs != isFullScreen()) { + emit fullScreenChanged(); + } } void ShellClient::setNoBorder(bool set) { if (!userCanSetNoBorder()) { return; } set = rules()->checkNoBorder(set); if (m_userNoBorder == set) { return; } m_userNoBorder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void ShellClient::setOnAllActivities(bool set) { Q_UNUSED(set) } void ShellClient::takeFocus() { if (rules()->checkAcceptFocus(wantsInput())) { if (m_xdgShellSurface) { const qint32 pingSerial = static_cast(m_xdgShellSurface->global())->ping(m_xdgShellSurface); m_pingSerials.insert(pingSerial, PingReason::FocusWindow); } setActive(true); } bool breakShowingDesktop = !keepAbove() && !isOnScreenDisplay(); if (breakShowingDesktop) { // check that it doesn't belong to the desktop const auto &clients = waylandServer()->clients(); for (auto c: clients) { - if (!belongsToSameApplication(c, false)) { + if (!belongsToSameApplication(c, SameApplicationChecks())) { continue; } if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } void ShellClient::doSetActive() { if (!isActive()) { return; } StackingUpdatesBlocker blocker(workspace()); workspace()->focusToNull(); } bool ShellClient::userCanSetFullScreen() const { + if (m_xdgShellSurface) { + return true; + } return false; } bool ShellClient::userCanSetNoBorder() const { if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return !isFullScreen() && !isShade() && !tabGroup(); } if (m_internal) { return !m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup); } return false; } bool ShellClient::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus()); } bool ShellClient::acceptsFocus() const { if (isInternal()) { return false; } if (waylandServer()->inputMethodConnection() == surface()->client()) { return false; } if (m_plasmaShellSurface) { if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification) { return false; } } if (m_closing) { // a closing window does not accept focus return false; } if (m_unmapped) { // an unmapped window does not accept focus return false; } if (m_shellSurface) { if (m_shellSurface->isPopup()) { return false; } return m_shellSurface->acceptsKeyboardFocus(); } if (m_xdgShellSurface) { // TODO: proper return true; } return false; } void ShellClient::createWindowId() { if (m_internalWindow) { m_windowId = m_internalWindow->winId(); } else { m_windowId = waylandServer()->createWindowId(surface()); } } void ShellClient::findInternalWindow() { if (surface()->client() != waylandServer()->internalConnection()) { return; } const QWindowList windows = kwinApp()->topLevelWindows(); for (QWindow *w: windows) { auto s = KWayland::Client::Surface::fromWindow(w); if (!s) { continue; } if (s->id() != surface()->id()) { continue; } m_internalWindow = w; m_internalWindowFlags = m_internalWindow->flags(); connect(m_internalWindow, &QWindow::xChanged, this, &ShellClient::updateInternalWindowGeometry); connect(m_internalWindow, &QWindow::yChanged, this, &ShellClient::updateInternalWindowGeometry); connect(m_internalWindow, &QWindow::destroyed, this, [this] { m_internalWindow = nullptr; }); connect(m_internalWindow, &QWindow::opacityChanged, this, &ShellClient::setOpacity); // Try reading the window type from the QWindow. PlasmaCore.Dialog provides a dynamic type property // let's check whether it exists, if it does it's our window type const QVariant windowType = m_internalWindow->property("type"); if (!windowType.isNull()) { m_windowType = static_cast(windowType.toInt()); } setOpacity(m_internalWindow->opacity()); // skip close animation support setSkipCloseAnimation(m_internalWindow->property(s_skipClosePropertyName).toBool()); m_internalWindow->installEventFilter(this); return; } } void ShellClient::updateInternalWindowGeometry() { if (!m_internalWindow) { return; } doSetGeometry(QRect(m_internalWindow->geometry().topLeft() - QPoint(borderLeft(), borderTop()), m_internalWindow->geometry().size() + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } pid_t ShellClient::pid() const { return surface()->client()->processId(); } bool ShellClient::isInternal() const { return m_internal; } bool ShellClient::isLockScreen() const { if (m_internalWindow) { return m_internalWindow->property("org_kde_ksld_emergency").toBool(); } return surface()->client() == waylandServer()->screenLockerClientConnection(); } bool ShellClient::isInputMethod() const { if (m_internal && m_internalWindow) { return m_internalWindow->property("__kwin_input_method").toBool(); } return surface()->client() == waylandServer()->inputMethodConnection(); } void ShellClient::requestGeometry(const QRect &rect) { if (m_requestGeometryBlockCounter != 0) { m_blockedRequestGeometry = rect; return; } m_positionAfterResize.setPoint(rect.topLeft()); const QSize size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); if (m_shellSurface) { m_shellSurface->requestSize(size); } if (m_xdgShellSurface) { m_xdgShellSurface->configure(xdgSurfaceStates(), size); } if (m_xdgShellPopup) { auto parent = transientFor(); if (parent) { const QPoint globalClientContentPos = parent->geometry().topLeft() + parent->clientPos(); const QPoint relativeOffset = rect.topLeft() -globalClientContentPos; m_xdgShellPopup->configure(QRect(relativeOffset, rect.size())); } } m_blockedRequestGeometry = QRect(); if (m_internal) { m_internalWindow->setGeometry(QRect(rect.topLeft() + QPoint(borderLeft(), borderTop()), rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } } void ShellClient::clientFullScreenChanged(bool fullScreen) { - RequestGeometryBlocker requestBlocker(this); - StackingUpdatesBlocker blocker(workspace()); - - const bool emitSignal = m_fullScreen != fullScreen; - m_fullScreen = fullScreen; - updateDecoration(false, false); - - workspace()->updateClientLayer(this); // active fullscreens get different layer - - if (fullScreen) { - m_geomFsRestore = geometry(); - requestGeometry(workspace()->clientArea(FullScreenArea, this)); - workspace()->raiseClient(this); - } else { - if (m_geomFsRestore.isValid()) { - requestGeometry(m_geomFsRestore); - } else { - requestGeometry(workspace()->clientArea(MaximizeArea, this)); - } - } - if (emitSignal) { - emit fullScreenChanged(); - } + setFullScreen(fullScreen, false); } void ShellClient::resizeWithChecks(int w, int h, ForceGeometry_t force) { Q_UNUSED(force) QRect area = workspace()->clientArea(WorkArea, this); // don't allow growing larger than workarea if (w > area.width()) { w = area.width(); } if (h > area.height()) { h = area.height(); } if (m_shellSurface) { m_shellSurface->requestSize(QSize(w, h)); } if (m_xdgShellSurface) { m_xdgShellSurface->configure(xdgSurfaceStates(), QSize(w, h)); } if (m_internal) { m_internalWindow->setGeometry(QRect(pos() + QPoint(borderLeft(), borderTop()), QSize(w, h) - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } } void ShellClient::unmap() { m_unmapped = true; destroyWindowManagementInterface(); if (Workspace::self()) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } emit windowHidden(this); } void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface) { m_plasmaShellSurface = surface; auto updatePosition = [this, surface] { doSetGeometry(QRect(surface->position(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); }; auto updateRole = [this, surface] { NET::WindowType type = NET::Unknown; switch (surface->role()) { case PlasmaShellSurfaceInterface::Role::Desktop: type = NET::Desktop; break; case PlasmaShellSurfaceInterface::Role::Panel: type = NET::Dock; break; case PlasmaShellSurfaceInterface::Role::OnScreenDisplay: type = NET::OnScreenDisplay; break; case PlasmaShellSurfaceInterface::Role::Notification: type = NET::Notification; break; case PlasmaShellSurfaceInterface::Role::ToolTip: type = NET::Tooltip; break; case PlasmaShellSurfaceInterface::Role::Normal: default: type = NET::Normal; break; } if (type != m_windowType) { m_windowType = type; if (m_windowType == NET::Desktop || type == NET::Dock || type == NET::OnScreenDisplay || type == NET::Notification || type == NET::Tooltip) { setOnAllDesktops(true); } workspace()->updateClientArea(); } }; connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [this] { updateShowOnScreenEdge(); workspace()->updateClientArea(); } ); connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, [this] { hideClient(true); m_plasmaShellSurface->hideAutoHidingPanel(); updateShowOnScreenEdge(); } ); connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, [this] { hideClient(false); ScreenEdges::self()->reserve(this, ElectricNone); m_plasmaShellSurface->showAutoHidingPanel(); } ); updatePosition(); updateRole(); updateShowOnScreenEdge(); connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateShowOnScreenEdge); setSkipTaskbar(surface->skipTaskbar()); connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { setSkipTaskbar(m_plasmaShellSurface->skipTaskbar()); }); } void ShellClient::updateShowOnScreenEdge() { if (!ScreenEdges::self()) { return; } if (m_unmapped || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { ScreenEdges::self()->reserve(this, ElectricNone); return; } if ((m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && m_hidden) || m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) { // screen edge API requires an edge, thus we need to figure out which edge the window borders Qt::Edges edges; for (int i = 0; i < screens()->count(); i++) { const auto &screenGeo = screens()->geometry(i); if (screenGeo.x() == geom.x()) { edges |= Qt::LeftEdge; } if (screenGeo.x() + screenGeo.width() == geom.x() + geom.width()) { edges |= Qt::RightEdge; } if (screenGeo.y() == geom.y()) { edges |= Qt::TopEdge; } if (screenGeo.y() + screenGeo.height() == geom.y() + geom.height()) { edges |= Qt::BottomEdge; } } // a panel might border multiple screen edges. E.g. a horizontal panel at the bottom will // also border the left and right edge // let's remove such cases if (edges.testFlag(Qt::LeftEdge) && edges.testFlag(Qt::RightEdge)) { edges = edges & (~(Qt::LeftEdge | Qt::RightEdge)); } if (edges.testFlag(Qt::TopEdge) && edges.testFlag(Qt::BottomEdge)) { edges = edges & (~(Qt::TopEdge | Qt::BottomEdge)); } // it's still possible that a panel borders two edges, e.g. bottom and left // in that case the one which is sharing more with the edge wins auto check = [this](Qt::Edges edges, Qt::Edge horiz, Qt::Edge vert) { if (edges.testFlag(horiz) && edges.testFlag(vert)) { if (geom.width() >= geom.height()) { return edges & ~horiz; } else { return edges & ~vert; } } return edges; }; edges = check(edges, Qt::LeftEdge, Qt::TopEdge); edges = check(edges, Qt::LeftEdge, Qt::BottomEdge); edges = check(edges, Qt::RightEdge, Qt::TopEdge); edges = check(edges, Qt::RightEdge, Qt::BottomEdge); ElectricBorder border = ElectricNone; if (edges.testFlag(Qt::LeftEdge)) { border = ElectricLeft; } if (edges.testFlag(Qt::RightEdge)) { border = ElectricRight; } if (edges.testFlag(Qt::TopEdge)) { border = ElectricTop; } if (edges.testFlag(Qt::BottomEdge)) { border = ElectricBottom; } ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } bool ShellClient::isInitialPositionSet() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->isPositionSet(); } return false; } void ShellClient::installQtExtendedSurface(QtExtendedSurfaceInterface *surface) { m_qtExtendedSurface = surface; connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::raiseRequested, this, [this]() { workspace()->raiseClientRequest(this); }); connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::lowerRequested, this, [this]() { workspace()->lowerClientRequest(this); }); m_qtExtendedSurface->installEventFilter(this); } +void ShellClient::installAppMenu(AppMenuInterface *menu) +{ + m_appMenuInterface = menu; + + auto updateMenu = [this](AppMenuInterface::InterfaceAddress address) { + updateApplicationMenuServiceName(address.serviceName); + updateApplicationMenuObjectPath(address.objectPath); + }; + connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, [=](AppMenuInterface::InterfaceAddress address) { + updateMenu(address); + }); + updateMenu(menu->address()); +} + +void ShellClient::installPalette(ServerSideDecorationPaletteInterface *palette) +{ + m_paletteInterface = palette; + + auto updatePalette = [this](const QString &palette) { + AbstractClient::updateColorScheme(rules()->checkDecoColor(palette)); + }; + connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged, this, [=](const QString &palette) { + updatePalette(palette); + }); + connect(m_paletteInterface, &QObject::destroyed, this, [=]() { + updatePalette(QString()); + }); + updatePalette(palette->palette()); +} + + bool ShellClient::eventFilter(QObject *watched, QEvent *event) { - if (watched == m_qtExtendedSurface.data() && event->type() == QEvent::DynamicPropertyChange) { - QDynamicPropertyChangeEvent *pe = static_cast(event); - if (pe->propertyName() == s_schemePropertyName) { - AbstractClient::updateColorScheme(rules()->checkDecoColor(m_qtExtendedSurface->property(pe->propertyName().constData()).toString())); - } else if (pe->propertyName() == s_appMenuServiceNamePropertyName) { - updateApplicationMenuServiceName(m_qtExtendedSurface->property(pe->propertyName().constData()).toString()); - } else if (pe->propertyName() == s_appMenuObjectPathPropertyName) { - updateApplicationMenuObjectPath(m_qtExtendedSurface->property(pe->propertyName().constData()).toString()); - } - } if (watched == m_internalWindow && event->type() == QEvent::DynamicPropertyChange) { QDynamicPropertyChangeEvent *pe = static_cast(event); if (pe->propertyName() == s_skipClosePropertyName) { setSkipCloseAnimation(m_internalWindow->property(s_skipClosePropertyName).toBool()); } } return false; } void ShellClient::updateColorScheme() { - if (m_qtExtendedSurface) { - AbstractClient::updateColorScheme(rules()->checkDecoColor(m_qtExtendedSurface->property(s_schemePropertyName.constData()).toString())); + if (m_paletteInterface) { + AbstractClient::updateColorScheme(rules()->checkDecoColor(m_paletteInterface->palette())); } else { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString())); } } bool ShellClient::hasStrut() const { if (!isShown(true)) { return false; } if (!m_plasmaShellSurface) { return false; } if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { return false; } return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible; } void ShellClient::updateIcon() { const QString waylandIconName = QStringLiteral("wayland"); const QString dfIconName = iconFromDesktopFile(); const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName; if (iconName == icon().name()) { return; } setIcon(QIcon::fromTheme(iconName)); } bool ShellClient::isTransient() const { return m_transient; } void ShellClient::setTransient() { SurfaceInterface *s = nullptr; if (m_shellSurface) { s = m_shellSurface->transientFor().data(); } if (m_xdgShellSurface) { if (auto transient = m_xdgShellSurface->transientFor().data()) { s = transient->surface(); } } if (m_xdgShellPopup) { s = m_xdgShellPopup->transientFor().data(); } if (!s) { s = waylandServer()->findForeignTransientForSurface(surface()); } auto t = waylandServer()->findClient(s); if (t != transientFor()) { // remove from main client if (transientFor()) transientFor()->removeTransient(this); setTransientFor(t); if (t) { t->addTransient(this); } } m_transient = (s != nullptr); } bool ShellClient::hasTransientPlacementHint() const { return isTransient() && transientFor() != nullptr; } QPoint ShellClient::transientPlacementHint() const { if (m_shellSurface) { return m_shellSurface->transientOffset(); } if (m_xdgShellPopup) { return m_xdgShellPopup->transientOffset(); } return QPoint(); } bool ShellClient::isWaitingForMoveResizeSync() const { return m_positionAfterResize.isValid(); } void ShellClient::doResizeSync() { requestGeometry(moveResizeGeometry()); } QMatrix4x4 ShellClient::inputTransformation() const { QMatrix4x4 m = Toplevel::inputTransformation(); m.translate(-borderLeft(), -borderTop()); return m; } void ShellClient::installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *deco) { if (m_serverDecoration == deco) { return; } m_serverDecoration = deco; connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, [this] { m_serverDecoration = nullptr; if (m_closing || !Workspace::self()) { return; } if (!m_unmapped) { // maybe delay to next event cycle in case the ShellClient is getting destroyed, too updateDecoration(true); } } ); if (!m_unmapped) { updateDecoration(true); } connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, [this] (ServerSideDecorationManagerInterface::Mode mode) { const bool changed = mode != m_serverDecoration->mode(); - // always acknowledge the requested mode - m_serverDecoration->setMode(mode); if (changed && !m_unmapped) { updateDecoration(false); } } ); } bool ShellClient::shouldExposeToWindowManagement() { if (isInternal()) { return false; } if (isLockScreen()) { return false; } if (m_xdgShellPopup) { return false; } if (m_shellSurface) { if (m_shellSurface->isTransient() && !m_shellSurface->acceptsKeyboardFocus()) { return false; } } return true; } KWayland::Server::XdgShellSurfaceInterface::States ShellClient::xdgSurfaceStates() const { XdgShellSurfaceInterface::States states; if (isActive()) { states |= XdgShellSurfaceInterface::State::Activated; } if (isFullScreen()) { states |= XdgShellSurfaceInterface::State::Fullscreen; } if (maximizeMode() == MaximizeMode::MaximizeFull) { states |= XdgShellSurfaceInterface::State::Maximized; } if (isResize()) { states |= XdgShellSurfaceInterface::State::Resizing; } return states; } void ShellClient::doMinimize() { if (isMinimized()) { workspace()->clientHidden(this); } else { emit windowShown(this); } } bool ShellClient::setupCompositing() { if (m_compositingSetup) { return true; } m_compositingSetup = Toplevel::setupCompositing(); return m_compositingSetup; } void ShellClient::finishCompositing(ReleaseReason releaseReason) { m_compositingSetup = false; Toplevel::finishCompositing(releaseReason); } void ShellClient::placeIn(QRect &area) { Placement::self()->place(this, area); setGeometryRestore(geometry()); } void ShellClient::showOnScreenEdge() { if (!m_plasmaShellSurface || m_unmapped) { return; } hideClient(false); workspace()->raiseClient(this); if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) { m_plasmaShellSurface->showAutoHidingPanel(); } } bool ShellClient::dockWantsInput() const { if (m_plasmaShellSurface) { if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) { return m_plasmaShellSurface->panelTakesFocus(); } } return false; } void ShellClient::killWindow() { if (isInternal()) { return; } if (!surface()) { return; } auto c = surface()->client(); if (c->processId() == getpid() || c->processId() == 0) { c->destroy(); return; } ::kill(c->processId(), SIGTERM); // give it time to terminate and only if terminate fails, try destroy Wayland connection QTimer::singleShot(5000, c, &ClientConnection::destroy); } -void ShellClient::updateApplicationMenu() -{ - if (m_qtExtendedSurface) { - updateApplicationMenuServiceName(m_qtExtendedSurface->property(s_appMenuObjectPathPropertyName).toString()); - updateApplicationMenuObjectPath(m_qtExtendedSurface->property(s_appMenuServiceNamePropertyName).toString()); - } -} - bool ShellClient::hasPopupGrab() const { return m_hasPopupGrab; } void ShellClient::popupDone() { if (m_shellSurface) { m_shellSurface->popupDone(); } if (m_xdgShellPopup) { m_xdgShellPopup->popupDone(); } } void ShellClient::updateClientOutputs() { QVector clientOutputs; const auto outputs = waylandServer()->display()->outputs(); for (OutputInterface* output: qAsConst(outputs)) { const QRect outputGeom(output->globalPosition(), output->pixelSize() / output->scale()); if (geometry().intersects(outputGeom)) { clientOutputs << output; } } surface()->setOutputs(clientOutputs); } } diff --git a/shell_client.h b/shell_client.h index 0538e3f2f..3a22acbcf 100644 --- a/shell_client.h +++ b/shell_client.h @@ -1,265 +1,268 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_SHELL_CLIENT_H #define KWIN_SHELL_CLIENT_H #include "abstract_client.h" #include namespace KWayland { namespace Server { class ShellSurfaceInterface; class ServerSideDecorationInterface; +class ServerSideDecorationPaletteInterface; +class AppMenuInterface; class PlasmaShellSurfaceInterface; class QtExtendedSurfaceInterface; } } namespace KWin { /** * @brief The reason for which the server pinged a client surface */ enum class PingReason { CloseWindow = 0, FocusWindow }; class KWIN_EXPORT ShellClient : public AbstractClient { Q_OBJECT public: ShellClient(KWayland::Server::ShellSurfaceInterface *surface); ShellClient(KWayland::Server::XdgShellSurfaceInterface *surface); ShellClient(KWayland::Server::XdgShellPopupInterface *surface); virtual ~ShellClient(); bool eventFilter(QObject *watched, QEvent *event) override; QStringList activities() const override; QPoint clientContentPos() const override; QSize clientSize() const override; QRect transparentRect() const override; NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; void debug(QDebug &stream) const override; double opacity() const override; void setOpacity(double opacity) override; QByteArray windowRole() const override; KWayland::Server::ShellSurfaceInterface *shellSurface() const { return m_shellSurface; } void blockActivityUpdates(bool b = true) override; QString captionNormal() const override { return m_caption; } QString captionSuffix() const override { return m_captionSuffix; } void closeWindow() override; AbstractClient *findModal(bool allow_itself = false) override; bool isCloseable() const override; - bool isFullScreenable() const override; bool isFullScreen() const override; bool isMaximizable() const override; bool isMinimizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isResizable() const override; bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override { return m_unmapped || m_hidden; } void hideClient(bool hide) override; MaximizeMode maximizeMode() const override; QRect geometryRestore() const override { return m_geomMaximizeRestore; } bool noBorder() const override; void setFullScreen(bool set, bool user = true) override; void setNoBorder(bool set) override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void setOnAllActivities(bool set) override; void takeFocus() override; bool userCanSetFullScreen() const override; bool userCanSetNoBorder() const override; bool wantsInput() const override; bool dockWantsInput() const override; using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; bool hasStrut() const override; void setInternalFramebufferObject(const QSharedPointer &fbo) override; quint32 windowId() const override { return m_windowId; } /** * The process for this client. * Note that processes started by kwin will share its process id. * @since 5.11 * @returns the process if for this client. **/ pid_t pid() const override; bool isInternal() const; bool isLockScreen() const override; bool isInputMethod() const override; QWindow *internalWindow() const { return m_internalWindow; } void installPlasmaShellSurface(KWayland::Server::PlasmaShellSurfaceInterface *surface); void installQtExtendedSurface(KWayland::Server::QtExtendedSurfaceInterface *surface); void installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *decoration); + void installAppMenu(KWayland::Server::AppMenuInterface *appmenu); + void installPalette(KWayland::Server::ServerSideDecorationPaletteInterface *palette); bool isInitialPositionSet() const override; bool isTransient() const override; bool hasTransientPlacementHint() const override; QPoint transientPlacementHint() const override; QMatrix4x4 inputTransformation() const override; bool setupCompositing() override; void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void showOnScreenEdge() override; void killWindow() override; // TODO: const-ref void placeIn(QRect &area); - void updateApplicationMenu(); - bool hasPopupGrab() const override; void popupDone() override; void updateColorScheme() override; protected: void addDamage(const QRegion &damage) override; - bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const override; + bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const override; void doSetActive() override; Layer layerForDock() const override; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; void setGeometryRestore(const QRect &geo) override { m_geomMaximizeRestore = geo; } void doResizeSync() override; bool isWaitingForMoveResizeSync() const override; bool acceptsFocus() const override; void doMinimize() override; void doMove(int x, int y) override; void updateCaption() override; private Q_SLOTS: void clientFullScreenChanged(bool fullScreen); private: void init(); template void initSurface(T *shellSurface); void requestGeometry(const QRect &rect); void doSetGeometry(const QRect &rect); void createDecoration(const QRect &oldgeom); void destroyClient(); void unmap(); void createWindowId(); void findInternalWindow(); void updateInternalWindowGeometry(); void syncGeometryToInternalWindow(); void updateIcon(); void markAsMapped(); void setTransient(); bool shouldExposeToWindowManagement(); void updateClientOutputs(); KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const; void updateShowOnScreenEdge(); static void deleteClient(ShellClient *c); KWayland::Server::ShellSurfaceInterface *m_shellSurface; KWayland::Server::XdgShellSurfaceInterface *m_xdgShellSurface; KWayland::Server::XdgShellPopupInterface *m_xdgShellPopup; QSize m_clientSize; ClearablePoint m_positionAfterResize; // co-ordinates saved from a requestGeometry call, real geometry will be updated after the next damage event when the client has resized QRect m_geomFsRestore; //size and position of the window before it was set to fullscreen bool m_closing = false; quint32 m_windowId = 0; QWindow *m_internalWindow = nullptr; Qt::WindowFlags m_internalWindowFlags = Qt::WindowFlags(); bool m_unmapped = true; MaximizeMode m_maximizeMode = MaximizeRestore; QRect m_geomMaximizeRestore; // size and position of the window before it was set to maximize NET::WindowType m_windowType = NET::Normal; QPointer m_plasmaShellSurface; QPointer m_qtExtendedSurface; + QPointer m_appMenuInterface; + QPointer m_paletteInterface; KWayland::Server::ServerSideDecorationInterface *m_serverDecoration = nullptr; bool m_userNoBorder = false; bool m_fullScreen = false; bool m_transient = false; bool m_hidden = false; bool m_internal; bool m_hasPopupGrab = false; qreal m_opacity = 1.0; class RequestGeometryBlocker { public: RequestGeometryBlocker(ShellClient *client) : m_client(client) { m_client->m_requestGeometryBlockCounter++; } ~RequestGeometryBlocker() { m_client->m_requestGeometryBlockCounter--; if (m_client->m_requestGeometryBlockCounter == 0) { if (m_client->m_blockedRequestGeometry.isValid()) { m_client->requestGeometry(m_client->m_blockedRequestGeometry); } } } private: ShellClient *m_client; }; friend class RequestGeometryBlocker; int m_requestGeometryBlockCounter = 0; QRect m_blockedRequestGeometry; QString m_caption; QString m_captionSuffix; QHash m_pingSerials; bool m_compositingSetup = false; }; } Q_DECLARE_METATYPE(KWin::ShellClient*) #endif diff --git a/tabbox/clientmodel.cpp b/tabbox/clientmodel.cpp index d060c09c5..79bb6a6f7 100644 --- a/tabbox/clientmodel.cpp +++ b/tabbox/clientmodel.cpp @@ -1,262 +1,263 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2009 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 . *********************************************************************/ // own #include "clientmodel.h" // tabbox #include "tabboxconfig.h" #include "tabboxhandler.h" // Qt #include // TODO: remove with Qt 5, only for HTML escaping the caption #include #include // other #include namespace KWin { namespace TabBox { ClientModel::ClientModel(QObject* parent) : QAbstractItemModel(parent) { QHash roles; roles[CaptionRole] = "caption"; roles[DesktopNameRole] = "desktopName"; roles[MinimizedRole] = "minimized"; roles[WIdRole] = "windowId"; roles[CloseableRole] = "closeable"; roles[IconRole] = "icon"; setRoleNames(roles); } ClientModel::~ClientModel() { } QVariant ClientModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); if (m_clientList.isEmpty()) { return QVariant(); } int clientIndex = index.row(); if (clientIndex >= m_clientList.count()) return QVariant(); QSharedPointer client = m_clientList[ clientIndex ].toStrongRef(); if (!client) { return QVariant(); } switch(role) { case Qt::DisplayRole: case CaptionRole: { QString caption = client->caption(); if (Qt::mightBeRichText(caption)) { caption = Qt::escape(caption); } return caption; } case ClientRole: return qVariantFromValue((void*)client.data()); case DesktopNameRole: { return tabBox->desktopName(client.data()); } case WIdRole: return qulonglong(client->window()); case MinimizedRole: return client->isMinimized(); case CloseableRole: //clients that claim to be first are not closeable return client->isCloseable() && !client->isFirstInTabBox(); case IconRole: return client->icon(); default: return QVariant(); } } QString ClientModel::longestCaption() const { QString caption; foreach (const QWeakPointer &clientPointer, m_clientList) { QSharedPointer client = clientPointer.toStrongRef(); if (!client) { continue; } if (client->caption().size() > caption.size()) { caption = client->caption(); } } return caption; } int ClientModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } int ClientModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return m_clientList.count(); } QModelIndex ClientModel::parent(const QModelIndex& child) const { Q_UNUSED(child) return QModelIndex(); } QModelIndex ClientModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || column != 0 || parent.isValid()) { return QModelIndex(); } int index = row * columnCount(); if (index >= m_clientList.count() && !m_clientList.isEmpty()) return QModelIndex(); return createIndex(row, 0); } QModelIndex ClientModel::index(QWeakPointer client) const { if (!m_clientList.contains(client)) return QModelIndex(); int index = m_clientList.indexOf(client); int row = index / columnCount(); int column = index % columnCount(); return createIndex(row, column); } void ClientModel::createClientList(bool partialReset) { createClientList(tabBox->currentDesktop(), partialReset); } void ClientModel::createClientList(int desktop, bool partialReset) { TabBoxClient* start = tabBox->activeClient().toStrongRef().data(); // TODO: new clients are not added at correct position if (partialReset && !m_clientList.isEmpty()) { QSharedPointer firstClient = m_clientList.first().toStrongRef(); if (firstClient) { start = firstClient.data(); } } m_clientList.clear(); QList< QWeakPointer< TabBoxClient > > stickyClients; switch(tabBox->config().clientSwitchingMode()) { case TabBoxConfig::FocusChainSwitching: { TabBoxClient* c = start; if (!tabBox->isInFocusChain(c)) { QSharedPointer firstClient = tabBox->firstClientFocusChain().toStrongRef(); if (firstClient) { c = firstClient.data(); } } TabBoxClient* stop = c; do { QWeakPointer add = tabBox->clientToAddToList(c, desktop); if (!add.isNull()) { m_clientList += add; if (add.data()->isFirstInTabBox()) { stickyClients << add; } } c = tabBox->nextClientFocusChain(c).data(); } while (c && c != stop); break; } case TabBoxConfig::StackingOrderSwitching: { // TODO: needs improvement TabBoxClientList stacking = tabBox->stackingOrder(); TabBoxClient* c = stacking.first().data(); TabBoxClient* stop = c; int index = 0; while (c) { QWeakPointer add = tabBox->clientToAddToList(c, desktop); if (!add.isNull()) { if (start == add.data()) { m_clientList.removeAll(add); m_clientList.prepend(add); } else m_clientList += add; if (add.data()->isFirstInTabBox()) { stickyClients << add; } } if (index >= stacking.size() - 1) { c = nullptr; } else { c = stacking[++index].data(); } if (c == stop) break; } break; } } foreach (const QWeakPointer< TabBoxClient > &c, stickyClients) { m_clientList.removeAll(c); m_clientList.prepend(c); } - if (tabBox->config().showDesktopMode() == TabBoxConfig::ShowDesktopClient || m_clientList.isEmpty()) { + if (tabBox->config().clientApplicationsMode() != TabBoxConfig::AllWindowsCurrentApplication + && (tabBox->config().showDesktopMode() == TabBoxConfig::ShowDesktopClient || m_clientList.isEmpty())) { QWeakPointer desktopClient = tabBox->desktopClient(); if (!desktopClient.isNull()) m_clientList.append(desktopClient); } reset(); } void ClientModel::close(int i) { QModelIndex ind = index(i, 0); if (!ind.isValid()) { return; } QSharedPointer client = m_clientList.at(i).toStrongRef(); if (client) { client->close(); } } void ClientModel::activate(int i) { QModelIndex ind = index(i, 0); if (!ind.isValid()) { return; } tabBox->setCurrentIndex(ind); tabBox->activateAndClose(); } } // namespace Tabbox } // namespace KWin diff --git a/tabbox/tabbox.cpp b/tabbox/tabbox.cpp index f397d791f..42651c6be 100644 --- a/tabbox/tabbox.cpp +++ b/tabbox/tabbox.cpp @@ -1,1612 +1,1612 @@ /******************************************************************** 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) 2009 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ //#define QT_CLEAN_NAMESPACE // own #include "tabbox.h" // tabbox #include "tabbox/clientmodel.h" #include "tabbox/desktopmodel.h" #include "tabbox/tabboxconfig.h" #include "tabbox/desktopchain.h" #include "tabbox/tabbox_logging.h" #include "tabbox/x11_filter.h" // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "client.h" #include "effects.h" #include "input.h" #include "keyboard_input.h" #include "focuschain.h" #include "screenedge.h" #include "screens.h" #include "unmanaged.h" #include "virtualdesktops.h" #include "workspace.h" #include "xcbutils.h" // Qt #include #include // KDE #include #include #include #include #include // X11 #include #include // xcb #include // specify externals before namespace namespace KWin { namespace TabBox { TabBoxHandlerImpl::TabBoxHandlerImpl(TabBox* tabBox) : TabBoxHandler(tabBox) , m_tabBox(tabBox) , m_desktopFocusChain(new DesktopChainManager(this)) { // connects for DesktopFocusChainManager VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(vds, SIGNAL(countChanged(uint,uint)), m_desktopFocusChain, SLOT(resize(uint,uint))); connect(vds, SIGNAL(currentChanged(uint,uint)), m_desktopFocusChain, SLOT(addDesktop(uint,uint))); #ifdef KWIN_BUILD_ACTIVITIES if (Activities::self()) { connect(Activities::self(), SIGNAL(currentChanged(QString)), m_desktopFocusChain, SLOT(useChain(QString))); } #endif } TabBoxHandlerImpl::~TabBoxHandlerImpl() { } int TabBoxHandlerImpl::activeScreen() const { return screens()->current(); } int TabBoxHandlerImpl::currentDesktop() const { return VirtualDesktopManager::self()->current(); } QString TabBoxHandlerImpl::desktopName(TabBoxClient* client) const { if (TabBoxClientImpl* c = static_cast< TabBoxClientImpl* >(client)) { if (!c->client()->isOnAllDesktops()) return VirtualDesktopManager::self()->name(c->client()->desktop()); } return VirtualDesktopManager::self()->name(VirtualDesktopManager::self()->current()); } QString TabBoxHandlerImpl::desktopName(int desktop) const { return VirtualDesktopManager::self()->name(desktop); } QWeakPointer TabBoxHandlerImpl::nextClientFocusChain(TabBoxClient* client) const { if (TabBoxClientImpl* c = static_cast< TabBoxClientImpl* >(client)) { auto next = FocusChain::self()->nextMostRecentlyUsed(c->client()); if (next) return next->tabBoxClient(); } return QWeakPointer(); } QWeakPointer< TabBoxClient > TabBoxHandlerImpl::firstClientFocusChain() const { if (auto c = FocusChain::self()->firstMostRecentlyUsed()) { return QWeakPointer(c->tabBoxClient()); } else { return QWeakPointer(); } } bool TabBoxHandlerImpl::isInFocusChain(TabBoxClient *client) const { if (TabBoxClientImpl *c = static_cast(client)) { return FocusChain::self()->contains(c->client()); } return false; } int TabBoxHandlerImpl::nextDesktopFocusChain(int desktop) const { return m_desktopFocusChain->next(desktop); } int TabBoxHandlerImpl::numberOfDesktops() const { return VirtualDesktopManager::self()->count(); } QWeakPointer TabBoxHandlerImpl::activeClient() const { if (Workspace::self()->activeClient()) return Workspace::self()->activeClient()->tabBoxClient(); else return QWeakPointer(); } bool TabBoxHandlerImpl::checkDesktop(TabBoxClient* client, int desktop) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); switch (config().clientDesktopMode()) { case TabBoxConfig::AllDesktopsClients: return true; case TabBoxConfig::ExcludeCurrentDesktopClients: return !current->isOnDesktop(desktop); default: // TabBoxConfig::OnlyCurrentDesktopClients return current->isOnDesktop(desktop); } } bool TabBoxHandlerImpl::checkActivity(TabBoxClient* client) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); switch (config().clientActivitiesMode()) { case TabBoxConfig::AllActivitiesClients: return true; case TabBoxConfig::ExcludeCurrentActivityClients: return !current->isOnCurrentActivity(); default: // TabBoxConfig::OnlyCurrentActivityClients return current->isOnCurrentActivity(); } } bool TabBoxHandlerImpl::checkApplications(TabBoxClient* client) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); TabBoxClientImpl* c; QListIterator< QWeakPointer > i(clientList()); switch (config().clientApplicationsMode()) { case TabBoxConfig::OneWindowPerApplication: // check if the list already contains an entry of this application while (i.hasNext()) { QSharedPointer client = i.next().toStrongRef(); if (!client) { continue; } if ((c = dynamic_cast< TabBoxClientImpl* >(client.data()))) { - if (AbstractClient::belongToSameApplication(c->client(), current)) { + if (AbstractClient::belongToSameApplication(c->client(), current, AbstractClient::SameApplicationCheck::AllowCrossProcesses)) { return false; } } } return true; case TabBoxConfig::AllWindowsCurrentApplication: { QSharedPointer pointer = tabBox->activeClient().toStrongRef(); if (!pointer) { return false; } if ((c = dynamic_cast< TabBoxClientImpl* >(pointer.data()))) { - if (AbstractClient::belongToSameApplication(c->client(), current)) { + if (AbstractClient::belongToSameApplication(c->client(), current, AbstractClient::SameApplicationCheck::AllowCrossProcesses)) { return true; } } return false; } default: // TabBoxConfig::AllWindowsAllApplications return true; } } bool TabBoxHandlerImpl::checkMinimized(TabBoxClient* client) const { switch (config().clientMinimizedMode()) { case TabBoxConfig::ExcludeMinimizedClients: return !client->isMinimized(); case TabBoxConfig::OnlyMinimizedClients: return client->isMinimized(); default: // TabBoxConfig::IgnoreMinimizedStatus return true; } } bool TabBoxHandlerImpl::checkMultiScreen(TabBoxClient* client) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); switch (config().clientMultiScreenMode()) { case TabBoxConfig::IgnoreMultiScreen: return true; case TabBoxConfig::ExcludeCurrentScreenClients: return current->screen() != screens()->current(); default: // TabBoxConfig::OnlyCurrentScreenClients return current->screen() == screens()->current(); } } QWeakPointer TabBoxHandlerImpl::clientToAddToList(TabBoxClient* client, int desktop) const { if (!client) { return QWeakPointer(); } AbstractClient* ret = nullptr; AbstractClient* current = (static_cast< TabBoxClientImpl* >(client))->client(); bool addClient = checkDesktop(client, desktop) && checkActivity(client) && checkApplications(client) && checkMinimized(client) && checkMultiScreen(client); addClient = addClient && current->wantsTabFocus() && !current->skipSwitcher(); if (addClient) { // don't add windows that have modal dialogs AbstractClient* modal = current->findModal(); if (modal == nullptr || modal == current) ret = current; else if (!clientList().contains(modal->tabBoxClient())) ret = modal; else { // nothing } } if (ret) return ret->tabBoxClient(); else return QWeakPointer(); } TabBoxClientList TabBoxHandlerImpl::stackingOrder() const { ToplevelList stacking = Workspace::self()->stackingOrder(); TabBoxClientList ret; foreach (Toplevel *toplevel, stacking) { if (auto client = qobject_cast(toplevel)) { ret.append(client->tabBoxClient()); } } return ret; } bool TabBoxHandlerImpl::isKWinCompositing() const { return Workspace::self()->compositing(); } void TabBoxHandlerImpl::raiseClient(TabBoxClient* c) const { Workspace::self()->raiseClient(static_cast(c)->client()); } void TabBoxHandlerImpl::restack(TabBoxClient *c, TabBoxClient *under) { Workspace::self()->restack(static_cast(c)->client(), static_cast(under)->client(), true); } void TabBoxHandlerImpl::elevateClient(TabBoxClient *c, QWindow *tabbox, bool b) const { auto cl = static_cast(c)->client(); cl->elevate(b); if (Toplevel *w = Workspace::self()->findInternal(tabbox)) w->elevate(b); } void TabBoxHandlerImpl::shadeClient(TabBoxClient *c, bool b) const { Client *cl = dynamic_cast(static_cast(c)->client()); if (!cl) { // shading is X11 specific return; } cl->cancelShadeHoverTimer(); // stop core shading action if (!b && cl->shadeMode() == ShadeNormal) cl->setShade(ShadeHover); else if (b && cl->shadeMode() == ShadeHover) cl->setShade(ShadeNormal); } QWeakPointer TabBoxHandlerImpl::desktopClient() const { foreach (Toplevel *toplevel, Workspace::self()->stackingOrder()) { auto client = qobject_cast(toplevel); if (client && client->isDesktop() && client->isOnCurrentDesktop() && client->screen() == screens()->current()) { return client->tabBoxClient(); } } return QWeakPointer(); } void TabBoxHandlerImpl::activateAndClose() { m_tabBox->accept(); } void TabBoxHandlerImpl::highlightWindows(TabBoxClient *window, QWindow *controller) { if (!effects) { return; } QVector windows; if (window) { windows << static_cast(window)->client()->effectWindow(); } if (auto t = Workspace::self()->findToplevel(controller)) { windows << t->effectWindow(); } static_cast(effects)->highlightWindows(windows); } bool TabBoxHandlerImpl::noModifierGrab() const { return m_tabBox->noModifierGrab(); } /********************************************************* * TabBoxClientImpl *********************************************************/ TabBoxClientImpl::TabBoxClientImpl(AbstractClient *client) : TabBoxClient() , m_client(client) { } TabBoxClientImpl::~TabBoxClientImpl() { } QString TabBoxClientImpl::caption() const { if (m_client->isDesktop()) return i18nc("Special entry in alt+tab list for minimizing all windows", "Show Desktop"); return m_client->caption(); } QIcon TabBoxClientImpl::icon() const { if (m_client->isDesktop()) { return QIcon::fromTheme(QStringLiteral("user-desktop")); } return m_client->icon(); } WId TabBoxClientImpl::window() const { return m_client->windowId(); } bool TabBoxClientImpl::isMinimized() const { return m_client->isMinimized(); } int TabBoxClientImpl::x() const { return m_client->x(); } int TabBoxClientImpl::y() const { return m_client->y(); } int TabBoxClientImpl::width() const { return m_client->width(); } int TabBoxClientImpl::height() const { return m_client->height(); } bool TabBoxClientImpl::isCloseable() const { return m_client->isCloseable(); } void TabBoxClientImpl::close() { m_client->closeWindow(); } bool TabBoxClientImpl::isFirstInTabBox() const { return m_client->isFirstInTabBox(); } /********************************************************* * TabBox *********************************************************/ TabBox *TabBox::s_self = nullptr; TabBox *TabBox::create(QObject *parent) { Q_ASSERT(!s_self); s_self = new TabBox(parent); return s_self; } TabBox::TabBox(QObject *parent) : QObject(parent) , m_displayRefcount(0) , m_desktopGrab(false) , m_tabGrab(false) , m_noModifierGrab(false) , m_forcedGlobalMouseGrab(false) , m_ready(false) { m_isShown = false; m_defaultConfig = TabBoxConfig(); m_defaultConfig.setTabBoxMode(TabBoxConfig::ClientTabBox); m_defaultConfig.setClientDesktopMode(TabBoxConfig::OnlyCurrentDesktopClients); m_defaultConfig.setClientActivitiesMode(TabBoxConfig::OnlyCurrentActivityClients); m_defaultConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsAllApplications); m_defaultConfig.setClientMinimizedMode(TabBoxConfig::IgnoreMinimizedStatus); m_defaultConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_defaultConfig.setClientMultiScreenMode(TabBoxConfig::IgnoreMultiScreen); m_defaultConfig.setClientSwitchingMode(TabBoxConfig::FocusChainSwitching); m_alternativeConfig = TabBoxConfig(); m_alternativeConfig.setTabBoxMode(TabBoxConfig::ClientTabBox); m_alternativeConfig.setClientDesktopMode(TabBoxConfig::AllDesktopsClients); m_alternativeConfig.setClientActivitiesMode(TabBoxConfig::OnlyCurrentActivityClients); m_alternativeConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsAllApplications); m_alternativeConfig.setClientMinimizedMode(TabBoxConfig::IgnoreMinimizedStatus); m_alternativeConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_alternativeConfig.setClientMultiScreenMode(TabBoxConfig::IgnoreMultiScreen); m_alternativeConfig.setClientSwitchingMode(TabBoxConfig::FocusChainSwitching); m_defaultCurrentApplicationConfig = m_defaultConfig; m_defaultCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_alternativeCurrentApplicationConfig = m_alternativeConfig; m_alternativeCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_desktopConfig = TabBoxConfig(); m_desktopConfig.setTabBoxMode(TabBoxConfig::DesktopTabBox); m_desktopConfig.setShowTabBox(true); m_desktopConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_desktopConfig.setDesktopSwitchingMode(TabBoxConfig::MostRecentlyUsedDesktopSwitching); m_desktopListConfig = TabBoxConfig(); m_desktopListConfig.setTabBoxMode(TabBoxConfig::DesktopTabBox); m_desktopListConfig.setShowTabBox(true); m_desktopListConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_desktopListConfig.setDesktopSwitchingMode(TabBoxConfig::StaticDesktopSwitching); m_tabBox = new TabBoxHandlerImpl(this); QTimer::singleShot(0, this, SLOT(handlerReady())); m_tabBoxMode = TabBoxDesktopMode; // init variables connect(&m_delayedShowTimer, SIGNAL(timeout()), this, SLOT(show())); connect(Workspace::self(), SIGNAL(configChanged()), this, SLOT(reconfigure())); } TabBox::~TabBox() { s_self = nullptr; } void TabBox::handlerReady() { m_tabBox->setConfig(m_defaultConfig); reconfigure(); m_ready = true; } template void TabBox::key(const char *actionName, Slot slot, const QKeySequence &shortcut) { QAction *a = new QAction(this); a->setProperty("componentName", QStringLiteral(KWIN_NAME)); a->setObjectName(QString::fromUtf8(actionName)); a->setText(i18n(actionName)); KGlobalAccel::self()->setShortcut(a, QList() << shortcut); input()->registerShortcut(shortcut, a, TabBox::self(), slot); auto cuts = KGlobalAccel::self()->shortcut(a); globalShortcutChanged(a, cuts.isEmpty() ? QKeySequence() : cuts.first()); } static const char s_windows[] = I18N_NOOP("Walk Through Windows"); static const char s_windowsRev[] = I18N_NOOP("Walk Through Windows (Reverse)"); static const char s_windowsAlt[] = I18N_NOOP("Walk Through Windows Alternative"); static const char s_windowsAltRev[] = I18N_NOOP("Walk Through Windows Alternative (Reverse)"); static const char s_app[] = I18N_NOOP("Walk Through Windows of Current Application"); static const char s_appRev[] = I18N_NOOP("Walk Through Windows of Current Application (Reverse)"); static const char s_appAlt[] = I18N_NOOP("Walk Through Windows of Current Application Alternative"); static const char s_appAltRev[] = I18N_NOOP("Walk Through Windows of Current Application Alternative (Reverse)"); static const char s_desktops[] = I18N_NOOP("Walk Through Desktops"); static const char s_desktopsRev[] = I18N_NOOP("Walk Through Desktops (Reverse)"); static const char s_desktopList[] = I18N_NOOP("Walk Through Desktop List"); static const char s_desktopListRev[] = I18N_NOOP("Walk Through Desktop List (Reverse)"); void TabBox::initShortcuts() { key(s_windows, &TabBox::slotWalkThroughWindows, Qt::ALT + Qt::Key_Tab); key(s_windowsRev, &TabBox::slotWalkBackThroughWindows, Qt::ALT + Qt::SHIFT + Qt::Key_Backtab); key(s_app, &TabBox::slotWalkThroughCurrentAppWindows, Qt::ALT + Qt::Key_QuoteLeft); key(s_appRev, &TabBox::slotWalkBackThroughCurrentAppWindows, Qt::ALT + Qt::Key_AsciiTilde); key(s_windowsAlt, &TabBox::slotWalkThroughWindowsAlternative); key(s_windowsAltRev, &TabBox::slotWalkBackThroughWindowsAlternative); key(s_appAlt, &TabBox::slotWalkThroughCurrentAppWindowsAlternative); key(s_appAltRev, &TabBox::slotWalkBackThroughCurrentAppWindowsAlternative); key(s_desktops, &TabBox::slotWalkThroughDesktops); key(s_desktopsRev, &TabBox::slotWalkBackThroughDesktops); key(s_desktopList, &TabBox::slotWalkThroughDesktopList); key(s_desktopListRev, &TabBox::slotWalkBackThroughDesktopList); connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &TabBox::globalShortcutChanged); } void TabBox::globalShortcutChanged(QAction *action, const QKeySequence &seq) { if (qstrcmp(qPrintable(action->objectName()), s_windows) == 0) { m_cutWalkThroughWindows = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_windowsRev) == 0) { m_cutWalkThroughWindowsReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_app) == 0) { m_cutWalkThroughCurrentAppWindows = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_appRev) == 0) { m_cutWalkThroughCurrentAppWindowsReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_windowsAlt) == 0) { m_cutWalkThroughWindowsAlternative = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_windowsAltRev) == 0) { m_cutWalkThroughWindowsAlternativeReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_appAlt) == 0) { m_cutWalkThroughCurrentAppWindowsAlternative = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_appAltRev) == 0) { m_cutWalkThroughCurrentAppWindowsAlternativeReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktops) == 0) { m_cutWalkThroughDesktops = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktopsRev) == 0) { m_cutWalkThroughDesktopsReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktopList) == 0) { m_cutWalkThroughDesktopList = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktopListRev) == 0) { m_cutWalkThroughDesktopListReverse = seq; } } /*! Sets the current mode to \a mode, either TabBoxDesktopListMode or TabBoxWindowsMode \sa mode() */ void TabBox::setMode(TabBoxMode mode) { m_tabBoxMode = mode; switch(mode) { case TabBoxWindowsMode: m_tabBox->setConfig(m_defaultConfig); break; case TabBoxWindowsAlternativeMode: m_tabBox->setConfig(m_alternativeConfig); break; case TabBoxCurrentAppWindowsMode: m_tabBox->setConfig(m_defaultCurrentApplicationConfig); break; case TabBoxCurrentAppWindowsAlternativeMode: m_tabBox->setConfig(m_alternativeCurrentApplicationConfig); break; case TabBoxDesktopMode: m_tabBox->setConfig(m_desktopConfig); break; case TabBoxDesktopListMode: m_tabBox->setConfig(m_desktopListConfig); break; } } /*! Resets the tab box to display the active client in TabBoxWindowsMode, or the current desktop in TabBoxDesktopListMode */ void TabBox::reset(bool partial_reset) { switch(m_tabBox->config().tabBoxMode()) { case TabBoxConfig::ClientTabBox: m_tabBox->createModel(partial_reset); if (!partial_reset) { if (Workspace::self()->activeClient()) setCurrentClient(Workspace::self()->activeClient()); // it's possible that the active client is not part of the model // in that case the index is invalid if (!m_tabBox->currentIndex().isValid()) setCurrentIndex(m_tabBox->first()); } else { if (!m_tabBox->currentIndex().isValid() || !m_tabBox->client(m_tabBox->currentIndex())) setCurrentIndex(m_tabBox->first()); } break; case TabBoxConfig::DesktopTabBox: m_tabBox->createModel(); if (!partial_reset) setCurrentDesktop(VirtualDesktopManager::self()->current()); break; } emit tabBoxUpdated(); } /*! Shows the next or previous item, depending on \a next */ void TabBox::nextPrev(bool next) { setCurrentIndex(m_tabBox->nextPrev(next), false); emit tabBoxUpdated(); } /*! Returns the currently displayed client ( only works in TabBoxWindowsMode ). Returns 0 if no client is displayed. */ AbstractClient* TabBox::currentClient() { if (TabBoxClientImpl* client = static_cast< TabBoxClientImpl* >(m_tabBox->client(m_tabBox->currentIndex()))) { if (!Workspace::self()->hasClient(client->client())) return nullptr; return client->client(); } else return nullptr; } /*! Returns the list of clients potentially displayed ( only works in TabBoxWindowsMode ). Returns an empty list if no clients are available. */ QList TabBox::currentClientList() { TabBoxClientList list = m_tabBox->clientList(); QList ret; foreach (const QWeakPointer &clientPointer, list) { QSharedPointer client = clientPointer.toStrongRef(); if (!client) continue; if (const TabBoxClientImpl* c = static_cast< const TabBoxClientImpl* >(client.data())) ret.append(c->client()); } return ret; } /*! Returns the currently displayed virtual desktop ( only works in TabBoxDesktopListMode ) Returns -1 if no desktop is displayed. */ int TabBox::currentDesktop() { return m_tabBox->desktop(m_tabBox->currentIndex()); } /*! Returns the list of desktops potentially displayed ( only works in TabBoxDesktopListMode ) Returns an empty list if no are available. */ QList< int > TabBox::currentDesktopList() { return m_tabBox->desktopList(); } /*! Change the currently selected client, and notify the effects. \sa setCurrentDesktop() */ void TabBox::setCurrentClient(AbstractClient *newClient) { setCurrentIndex(m_tabBox->index(newClient->tabBoxClient())); } /*! Change the currently selected desktop, and notify the effects. \sa setCurrentClient() */ void TabBox::setCurrentDesktop(int newDesktop) { setCurrentIndex(m_tabBox->desktopIndex(newDesktop)); } void TabBox::setCurrentIndex(QModelIndex index, bool notifyEffects) { if (!index.isValid()) return; m_tabBox->setCurrentIndex(index); if (notifyEffects) { emit tabBoxUpdated(); } } /*! Notify effects that the tab box is being shown, and only display the default tab box QFrame if no effect has referenced the tab box. */ void TabBox::show() { emit tabBoxAdded(m_tabBoxMode); if (isDisplayed()) { m_isShown = false; return; } workspace()->setShowingDesktop(false); reference(); m_isShown = true; m_tabBox->show(); } /*! Notify effects that the tab box is being hidden. */ void TabBox::hide(bool abort) { m_delayedShowTimer.stop(); if (m_isShown) { m_isShown = false; unreference(); } emit tabBoxClosed(); if (isDisplayed()) qCDebug(KWIN_TABBOX) << "Tab box was not properly closed by an effect"; m_tabBox->hide(abort); if (kwinApp()->x11Connection()) { Xcb::sync(); } } void TabBox::reconfigure() { KSharedConfigPtr c = kwinApp()->config(); KConfigGroup config = c->group("TabBox"); loadConfig(c->group("TabBox"), m_defaultConfig); loadConfig(c->group("TabBoxAlternative"), m_alternativeConfig); m_defaultCurrentApplicationConfig = m_defaultConfig; m_defaultCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_alternativeCurrentApplicationConfig = m_alternativeConfig; m_alternativeCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_tabBox->setConfig(m_defaultConfig); m_delayShow = config.readEntry("ShowDelay", true); m_delayShowTime = config.readEntry("DelayTime", 90); const QString defaultDesktopLayout = QStringLiteral("org.kde.breeze.desktop"); m_desktopConfig.setLayoutName(config.readEntry("DesktopLayout", defaultDesktopLayout)); m_desktopListConfig.setLayoutName(config.readEntry("DesktopListLayout", defaultDesktopLayout)); QList *borders = &m_borderActivate; QString borderConfig = QStringLiteral("BorderActivate"); for (int i = 0; i < 2; ++i) { foreach (ElectricBorder border, *borders) { ScreenEdges::self()->unreserve(border, this); } borders->clear(); QStringList list = config.readEntry(borderConfig, QStringList()); foreach (const QString &s, list) { bool ok; const int i = s.toInt(&ok); if (!ok) continue; borders->append(ElectricBorder(i)); ScreenEdges::self()->reserve(ElectricBorder(i), this, "toggle"); } borders = &m_borderAlternativeActivate; borderConfig = QStringLiteral("BorderAlternativeActivate"); } auto touchConfig = [this, config] (const QString &key, QHash &actions, TabBoxMode mode, const QStringList &defaults = QStringList{}) { // fist erase old config for (auto it = actions.begin(); it != actions.end(); ) { delete it.value(); it = actions.erase(it); } // now new config const QStringList list = config.readEntry(key, defaults); for (const auto &s : list) { bool ok; const int i = s.toInt(&ok); if (!ok) { continue; } QAction *a = new QAction(this); connect(a, &QAction::triggered, this, std::bind(&TabBox::toggleMode, this, mode)); ScreenEdges::self()->reserveTouch(ElectricBorder(i), a); actions.insert(ElectricBorder(i), a); } }; touchConfig(QStringLiteral("TouchBorderActivate"), m_touchActivate, TabBoxWindowsMode, QStringList{QString::number(int(ElectricLeft))}); touchConfig(QStringLiteral("TouchBorderAlternativeActivate"), m_touchAlternativeActivate, TabBoxWindowsAlternativeMode); } void TabBox::loadConfig(const KConfigGroup& config, TabBoxConfig& tabBoxConfig) { tabBoxConfig.setClientDesktopMode(TabBoxConfig::ClientDesktopMode( config.readEntry("DesktopMode", TabBoxConfig::defaultDesktopMode()))); tabBoxConfig.setClientActivitiesMode(TabBoxConfig::ClientActivitiesMode( config.readEntry("ActivitiesMode", TabBoxConfig::defaultActivitiesMode()))); tabBoxConfig.setClientApplicationsMode(TabBoxConfig::ClientApplicationsMode( config.readEntry("ApplicationsMode", TabBoxConfig::defaultApplicationsMode()))); tabBoxConfig.setClientMinimizedMode(TabBoxConfig::ClientMinimizedMode( config.readEntry("MinimizedMode", TabBoxConfig::defaultMinimizedMode()))); tabBoxConfig.setShowDesktopMode(TabBoxConfig::ShowDesktopMode( config.readEntry("ShowDesktopMode", TabBoxConfig::defaultShowDesktopMode()))); tabBoxConfig.setClientMultiScreenMode(TabBoxConfig::ClientMultiScreenMode( config.readEntry("MultiScreenMode", TabBoxConfig::defaultMultiScreenMode()))); tabBoxConfig.setClientSwitchingMode(TabBoxConfig::ClientSwitchingMode( config.readEntry("SwitchingMode", TabBoxConfig::defaultSwitchingMode()))); tabBoxConfig.setShowTabBox(config.readEntry("ShowTabBox", TabBoxConfig::defaultShowTabBox())); tabBoxConfig.setHighlightWindows(config.readEntry("HighlightWindows", TabBoxConfig::defaultHighlightWindow())); tabBoxConfig.setLayoutName(config.readEntry("LayoutName", TabBoxConfig::defaultLayoutName())); } /*! Rikkus: please document! (Matthias) Ok, here's the docs :) You call delayedShow() instead of show() directly. If the 'ShowDelay' setting is false, show() is simply called. Otherwise, we start a timer for the delay given in the settings and only do a show() when it times out. This means that you can alt-tab between windows and you don't see the tab box immediately. Not only does this make alt-tabbing faster, it gives less 'flicker' to the eyes. You don't need to see the tab box if you're just quickly switching between 2 or 3 windows. It seems to work quite nicely. */ void TabBox::delayedShow() { if (isDisplayed() || m_delayedShowTimer.isActive()) // already called show - no need to call it twice return; if (!m_delayShowTime) { show(); return; } m_delayedShowTimer.setSingleShot(true); m_delayedShowTimer.start(m_delayShowTime); } bool TabBox::handleMouseEvent(QMouseEvent *event) { if (!m_isShown && isDisplayed()) { // tabbox has been replaced, check effects if (effects && static_cast(effects)->checkInputWindowEvent(event)) { return true; } } switch (event->type()) { case QEvent::MouseMove: if (!m_tabBox->containsPos(event->globalPos())) { // filter out all events which are not on the TabBox window. // We don't want windows to react on the mouse events return true; } return false; case QEvent::MouseButtonPress: if ((!m_isShown && isDisplayed()) || !m_tabBox->containsPos(event->globalPos())) { close(); // click outside closes tab return true; } // fall through case QEvent::MouseButtonRelease: default: // we do not filter it out, the intenal filter takes care return false; } return false; } bool TabBox::handleWheelEvent(QWheelEvent *event) { if (!m_isShown && isDisplayed()) { // tabbox has been replaced, check effects if (effects && static_cast(effects)->checkInputWindowEvent(event)) { return true; } } if (event->angleDelta().y() == 0) { return false; } const QModelIndex index = m_tabBox->nextPrev(event->angleDelta().y() > 0); if (index.isValid()) { setCurrentIndex(index); } return true; } void TabBox::grabbedKeyEvent(QKeyEvent* event) { emit tabBoxKeyEvent(event); if (!m_isShown && isDisplayed()) { // tabbox has been replaced, check effects return; } if (m_noModifierGrab) { if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Space) { accept(); return; } } m_tabBox->grabbedKeyEvent(event); } struct KeySymbolsDeleter { static inline void cleanup(xcb_key_symbols_t *symbols) { xcb_key_symbols_free(symbols); } }; /*! Handles alt-tab / control-tab */ static bool areKeySymXsDepressed(bool bAll, const uint keySyms[], int nKeySyms) { qCDebug(KWIN_TABBOX) << "areKeySymXsDepressed: " << (bAll ? "all of " : "any of ") << nKeySyms; Xcb::QueryKeymap keys; QScopedPointer symbols(xcb_key_symbols_alloc(connection())); if (symbols.isNull() || !keys) { return false; } const auto keymap = keys->keys; for (int iKeySym = 0; iKeySym < nKeySyms; iKeySym++) { uint keySymX = keySyms[ iKeySym ]; xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(symbols.data(), keySymX); if (!keyCodes) { continue; } xcb_keycode_t keyCodeX = keyCodes[0]; free(keyCodes); if (keyCodeX == XCB_NO_SYMBOL) { continue; } int i = keyCodeX / 8; char mask = 1 << (keyCodeX - (i * 8)); // Abort if bad index value, if (i < 0 || i >= 32) return false; qCDebug(KWIN_TABBOX) << iKeySym << ": keySymX=0x" << QString::number(keySymX, 16) << " i=" << i << " mask=0x" << QString::number(mask, 16) << " keymap[i]=0x" << QString::number(keymap[i], 16); // If ALL keys passed need to be depressed, if (bAll) { if ((keymap[i] & mask) == 0) return false; } else { // If we are looking for ANY key press, and this key is depressed, if (keymap[i] & mask) return true; } } // If we were looking for ANY key press, then none was found, return false, // If we were looking for ALL key presses, then all were found, return true. return bAll; } static bool areModKeysDepressedX11(const QKeySequence &seq) { uint rgKeySyms[10]; int nKeySyms = 0; int mod = seq[seq.count()-1] & Qt::KeyboardModifierMask; if (mod & Qt::SHIFT) { rgKeySyms[nKeySyms++] = XK_Shift_L; rgKeySyms[nKeySyms++] = XK_Shift_R; } if (mod & Qt::CTRL) { rgKeySyms[nKeySyms++] = XK_Control_L; rgKeySyms[nKeySyms++] = XK_Control_R; } if (mod & Qt::ALT) { rgKeySyms[nKeySyms++] = XK_Alt_L; rgKeySyms[nKeySyms++] = XK_Alt_R; } if (mod & Qt::META) { // It would take some code to determine whether the Win key // is associated with Super or Meta, so check for both. // See bug #140023 for details. rgKeySyms[nKeySyms++] = XK_Super_L; rgKeySyms[nKeySyms++] = XK_Super_R; rgKeySyms[nKeySyms++] = XK_Meta_L; rgKeySyms[nKeySyms++] = XK_Meta_R; } return areKeySymXsDepressed(false, rgKeySyms, nKeySyms); } static bool areModKeysDepressedWayland(const QKeySequence &seq) { const int mod = seq[seq.count()-1] & Qt::KeyboardModifierMask; const Qt::KeyboardModifiers mods = input()->modifiersRelevantForGlobalShortcuts(); if ((mod & Qt::SHIFT) && mods.testFlag(Qt::ShiftModifier)) { return true; } if ((mod & Qt::CTRL) && mods.testFlag(Qt::ControlModifier)) { return true; } if ((mod & Qt::ALT) && mods.testFlag(Qt::AltModifier)) { return true; } if ((mod & Qt::META) && mods.testFlag(Qt::MetaModifier)) { return true; } return false; } static bool areModKeysDepressed(const QKeySequence& seq) { if (seq.isEmpty()) return false; if (kwinApp()->shouldUseWaylandForCompositing()) { return areModKeysDepressedWayland(seq); } else { return areModKeysDepressedX11(seq); } } void TabBox::navigatingThroughWindows(bool forward, const QKeySequence &shortcut, TabBoxMode mode) { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (!options->focusPolicyIsReasonable()) { //ungrabXKeyboard(); // need that because of accelerator raw mode // CDE style raise / lower CDEWalkThroughWindows(forward); } else { if (areModKeysDepressed(shortcut)) { if (startKDEWalkThroughWindows(mode)) KDEWalkThroughWindows(forward); } else // if the shortcut has no modifiers, don't show the tabbox, // don't grab, but simply go to the next window KDEOneStepThroughWindows(forward, mode); } } void TabBox::slotWalkThroughWindows() { navigatingThroughWindows(true, m_cutWalkThroughWindows, TabBoxWindowsMode); } void TabBox::slotWalkBackThroughWindows() { navigatingThroughWindows(false, m_cutWalkThroughWindowsReverse, TabBoxWindowsMode); } void TabBox::slotWalkThroughWindowsAlternative() { navigatingThroughWindows(true, m_cutWalkThroughWindowsAlternative, TabBoxWindowsAlternativeMode); } void TabBox::slotWalkBackThroughWindowsAlternative() { navigatingThroughWindows(false, m_cutWalkThroughWindowsAlternativeReverse, TabBoxWindowsAlternativeMode); } void TabBox::slotWalkThroughCurrentAppWindows() { navigatingThroughWindows(true, m_cutWalkThroughCurrentAppWindows, TabBoxCurrentAppWindowsMode); } void TabBox::slotWalkBackThroughCurrentAppWindows() { navigatingThroughWindows(false, m_cutWalkThroughCurrentAppWindowsReverse, TabBoxCurrentAppWindowsMode); } void TabBox::slotWalkThroughCurrentAppWindowsAlternative() { navigatingThroughWindows(true, m_cutWalkThroughCurrentAppWindowsAlternative, TabBoxCurrentAppWindowsAlternativeMode); } void TabBox::slotWalkBackThroughCurrentAppWindowsAlternative() { navigatingThroughWindows(false, m_cutWalkThroughCurrentAppWindowsAlternativeReverse, TabBoxCurrentAppWindowsAlternativeMode); } void TabBox::slotWalkThroughDesktops() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktops)) { if (startWalkThroughDesktops()) walkThroughDesktops(true); } else { oneStepThroughDesktops(true); } } void TabBox::slotWalkBackThroughDesktops() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktopsReverse)) { if (startWalkThroughDesktops()) walkThroughDesktops(false); } else { oneStepThroughDesktops(false); } } void TabBox::slotWalkThroughDesktopList() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktopList)) { if (startWalkThroughDesktopList()) walkThroughDesktops(true); } else { oneStepThroughDesktopList(true); } } void TabBox::slotWalkBackThroughDesktopList() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktopListReverse)) { if (startWalkThroughDesktopList()) walkThroughDesktops(false); } else { oneStepThroughDesktopList(false); } } void TabBox::shadeActivate(AbstractClient *c) { if ((c->shadeMode() == ShadeNormal || c->shadeMode() == ShadeHover) && options->isShadeHover()) c->setShade(ShadeActivated); } bool TabBox::toggle(ElectricBorder eb) { if (m_borderAlternativeActivate.contains(eb)) { return toggleMode(TabBoxWindowsAlternativeMode); } else { return toggleMode(TabBoxWindowsMode); } } bool TabBox::toggleMode(TabBoxMode mode) { if (!options->focusPolicyIsReasonable()) return false; // not supported. if (isDisplayed()) { accept(); return true; } if (!establishTabBoxGrab()) return false; m_noModifierGrab = m_tabGrab = true; setMode(mode); reset(); show(); return true; } bool TabBox::startKDEWalkThroughWindows(TabBoxMode mode) { if (!establishTabBoxGrab()) return false; m_tabGrab = true; m_noModifierGrab = false; setMode(mode); reset(); return true; } bool TabBox::startWalkThroughDesktops(TabBoxMode mode) { if (!establishTabBoxGrab()) return false; m_desktopGrab = true; m_noModifierGrab = false; setMode(mode); reset(); return true; } bool TabBox::startWalkThroughDesktops() { return startWalkThroughDesktops(TabBoxDesktopMode); } bool TabBox::startWalkThroughDesktopList() { return startWalkThroughDesktops(TabBoxDesktopListMode); } void TabBox::KDEWalkThroughWindows(bool forward) { nextPrev(forward); delayedShow(); } void TabBox::walkThroughDesktops(bool forward) { nextPrev(forward); delayedShow(); } void TabBox::CDEWalkThroughWindows(bool forward) { AbstractClient* c = nullptr; // this function find the first suitable client for unreasonable focus // policies - the topmost one, with some exceptions (can't be keepabove/below, // otherwise it gets stuck on them) // Q_ASSERT(Workspace::self()->block_stacking_updates == 0); for (int i = Workspace::self()->stackingOrder().size() - 1; i >= 0 ; --i) { auto it = qobject_cast(Workspace::self()->stackingOrder().at(i)); if (it && it->isOnCurrentActivity() && it->isOnCurrentDesktop() && !it->isSpecialWindow() && it->isShown(false) && it->wantsTabFocus() && !it->keepAbove() && !it->keepBelow()) { c = it; break; } } AbstractClient* nc = c; bool options_traverse_all; { KConfigGroup group(kwinApp()->config(), "TabBox"); options_traverse_all = group.readEntry("TraverseAll", false); } AbstractClient* firstClient = nullptr; do { nc = forward ? nextClientStatic(nc) : previousClientStatic(nc); if (!firstClient) { // When we see our first client for the second time, // it's time to stop. firstClient = nc; } else if (nc == firstClient) { // No candidates found. nc = nullptr; break; } } while (nc && nc != c && ((!options_traverse_all && !nc->isOnDesktop(currentDesktop())) || nc->isMinimized() || !nc->wantsTabFocus() || nc->keepAbove() || nc->keepBelow() || !nc->isOnCurrentActivity())); if (nc) { if (c && c != nc) Workspace::self()->lowerClient(c); if (options->focusPolicyIsReasonable()) { Workspace::self()->activateClient(nc); shadeActivate(nc); } else { if (!nc->isOnDesktop(currentDesktop())) setCurrentDesktop(nc->desktop()); Workspace::self()->raiseClient(nc); } } } void TabBox::KDEOneStepThroughWindows(bool forward, TabBoxMode mode) { setMode(mode); reset(); nextPrev(forward); if (AbstractClient* c = currentClient()) { Workspace::self()->activateClient(c); shadeActivate(c); } } void TabBox::oneStepThroughDesktops(bool forward, TabBoxMode mode) { setMode(mode); reset(); nextPrev(forward); if (currentDesktop() != -1) setCurrentDesktop(currentDesktop()); } void TabBox::oneStepThroughDesktops(bool forward) { oneStepThroughDesktops(forward, TabBoxDesktopMode); } void TabBox::oneStepThroughDesktopList(bool forward) { oneStepThroughDesktops(forward, TabBoxDesktopListMode); } /*! Handles holding alt-tab / control-tab */ void TabBox::keyPress(int keyQt) { enum Direction { Backward = -1, Steady = 0, Forward = 1 }; Direction direction(Steady); auto contains = [](const QKeySequence &shortcut, int key) -> bool { for (int i = 0; i < shortcut.count(); ++i) { if (shortcut[i] == key) { return true; } } return false; }; // tests whether a shortcut matches and handles pitfalls on ShiftKey invocation auto directionFor = [keyQt, contains](const QKeySequence &forward, const QKeySequence &backward) -> Direction { if (contains(forward, keyQt)) return Forward; if (contains(backward, keyQt)) return Backward; if (!(keyQt & Qt::ShiftModifier)) return Steady; // Before testing the unshifted key (Ctrl+A vs. Ctrl+Shift+a etc.), see whether this is +Shift+Tab // and check that against +Shift+Backtab (as well) Qt::KeyboardModifiers mods = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier|Qt::MetaModifier|Qt::KeypadModifier|Qt::GroupSwitchModifier; mods &= keyQt; if ((keyQt & ~mods) == Qt::Key_Tab) { if (contains(forward, mods | Qt::Key_Backtab)) return Forward; if (contains(backward, mods | Qt::Key_Backtab)) return Backward; } // if the shortcuts do not match, try matching again after filtering the shift key from keyQt // it is needed to handle correctly the ALT+~ shorcut for example as it is coded as ALT+SHIFT+~ in keyQt if (contains(forward, keyQt & ~Qt::ShiftModifier)) return Forward; if (contains(backward, keyQt & ~Qt::ShiftModifier)) return Backward; return Steady; }; if (m_tabGrab) { static const int ModeCount = 4; static const TabBoxMode modes[ModeCount] = { TabBoxWindowsMode, TabBoxWindowsAlternativeMode, TabBoxCurrentAppWindowsMode, TabBoxCurrentAppWindowsAlternativeMode }; static const QKeySequence cuts[2*ModeCount] = { // forward m_cutWalkThroughWindows, m_cutWalkThroughWindowsAlternative, m_cutWalkThroughCurrentAppWindows, m_cutWalkThroughCurrentAppWindowsAlternative, // backward m_cutWalkThroughWindowsReverse, m_cutWalkThroughWindowsAlternativeReverse, m_cutWalkThroughCurrentAppWindowsReverse, m_cutWalkThroughCurrentAppWindowsAlternativeReverse }; bool testedCurrent = false; // in case of collision, prefer to stay in the current mode int i = 0, j = 0; while (true) { if (!testedCurrent && modes[i] != mode()) { ++j; i = (i+1) % ModeCount; continue; } if (testedCurrent && modes[i] == mode()) { break; } testedCurrent = true; direction = directionFor(cuts[i], cuts[i+ModeCount]); if (direction != Steady) { if (modes[i] != mode()) { accept(false); setMode(modes[i]); auto replayWithChangedTabboxMode = [this, direction]() { reset(); nextPrev(direction == Forward); }; QTimer::singleShot(50, this, replayWithChangedTabboxMode); } break; } else if (++j > ModeCount) { // guarding counter for invalid modes qCDebug(KWIN_TABBOX) << "Invalid TabBoxMode"; return; } i = (i+1) % ModeCount; } if (direction != Steady) { qCDebug(KWIN_TABBOX) << "== " << cuts[i].toString() << " or " << cuts[i+ModeCount].toString(); KDEWalkThroughWindows(direction == Forward); } } else if (m_desktopGrab) { direction = directionFor(m_cutWalkThroughDesktops, m_cutWalkThroughDesktopsReverse); if (direction == Steady) direction = directionFor(m_cutWalkThroughDesktopList, m_cutWalkThroughDesktopListReverse); if (direction != Steady) walkThroughDesktops(direction == Forward); } if (m_desktopGrab || m_tabGrab) { if (((keyQt & ~Qt::KeyboardModifierMask) == Qt::Key_Escape) && direction == Steady) { // if Escape is part of the shortcut, don't cancel close(true); } else if (direction == Steady) { QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, keyQt & ~Qt::KeyboardModifierMask, Qt::NoModifier); grabbedKeyEvent(event); } } } void TabBox::close(bool abort) { if (isGrabbed()) { removeTabBoxGrab(); } hide(abort); m_tabGrab = false; m_desktopGrab = false; m_noModifierGrab = false; } void TabBox::accept(bool closeTabBox) { AbstractClient *c = currentClient(); if (closeTabBox) close(); if (c) { Workspace::self()->activateClient(c); shadeActivate(c); if (c->isDesktop()) Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop()); } } void TabBox::modifiersReleased() { if (m_noModifierGrab) { return; } if (m_tabGrab) { bool old_control_grab = m_desktopGrab; accept(); m_desktopGrab = old_control_grab; } if (m_desktopGrab) { bool old_tab_grab = m_tabGrab; int desktop = currentDesktop(); close(); m_tabGrab = old_tab_grab; if (desktop != -1) { setCurrentDesktop(desktop); VirtualDesktopManager::self()->setCurrent(desktop); } } } int TabBox::nextDesktopStatic(int iDesktop) const { DesktopNext functor; return functor(iDesktop, true); } int TabBox::previousDesktopStatic(int iDesktop) const { DesktopPrevious functor; return functor(iDesktop, true); } /*! auxiliary functions to travers all clients according to the static order. Useful for the CDE-style Alt-tab feature. */ AbstractClient* TabBox::nextClientStatic(AbstractClient* c) const { const auto &list = Workspace::self()->allClientList(); if (!c || list.isEmpty()) return 0; int pos = list.indexOf(c); if (pos == -1) return list.first(); ++pos; if (pos == list.count()) return list.first(); return list.at(pos); } /*! auxiliary functions to travers all clients according to the static order. Useful for the CDE-style Alt-tab feature. */ AbstractClient* TabBox::previousClientStatic(AbstractClient* c) const { const auto &list = Workspace::self()->allClientList(); if (!c || list.isEmpty()) return 0; int pos = list.indexOf(c); if (pos == -1) return list.last(); if (pos == 0) return list.last(); --pos; return list.at(pos); } bool TabBox::establishTabBoxGrab() { if (kwinApp()->shouldUseWaylandForCompositing()) { m_forcedGlobalMouseGrab = true; return true; } updateXTime(); if (!grabXKeyboard()) return false; // Don't try to establish a global mouse grab using XGrabPointer, as that would prevent // using Alt+Tab while DND (#44972). However force passive grabs on all windows // in order to catch MouseRelease events and close the tabbox (#67416). // All clients already have passive grabs in their wrapper windows, so check only // the active client, which may not have it. assert(!m_forcedGlobalMouseGrab); m_forcedGlobalMouseGrab = true; if (Workspace::self()->activeClient() != nullptr) Workspace::self()->activeClient()->updateMouseGrab(); m_x11EventFilter.reset(new X11Filter); return true; } void TabBox::removeTabBoxGrab() { if (kwinApp()->shouldUseWaylandForCompositing()) { m_forcedGlobalMouseGrab = false; return; } updateXTime(); ungrabXKeyboard(); assert(m_forcedGlobalMouseGrab); m_forcedGlobalMouseGrab = false; if (Workspace::self()->activeClient() != nullptr) Workspace::self()->activeClient()->updateMouseGrab(); m_x11EventFilter.reset(); } } // namespace TabBox } // namespace diff --git a/tabletmodemanager.cpp b/tabletmodemanager.cpp new file mode 100644 index 000000000..d668ad151 --- /dev/null +++ b/tabletmodemanager.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2018 Marco Martin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "tabletmodemanager.h" +#include "input.h" +#include "input_event.h" +#include "input_event_spy.h" + +#if HAVE_INPUT +#include "libinput/device.h" +#endif + +#include + +using namespace KWin; + +KWIN_SINGLETON_FACTORY_VARIABLE(TabletModeManager, s_manager) + +class KWin::TabletModeInputEventSpy : public InputEventSpy +{ +public: + explicit TabletModeInputEventSpy(TabletModeManager *parent); + + void switchEvent(SwitchEvent *event) override; +private: + TabletModeManager *m_parent; +}; + +TabletModeInputEventSpy::TabletModeInputEventSpy(TabletModeManager *parent) + : m_parent(parent) +{ +} + +void TabletModeInputEventSpy::switchEvent(SwitchEvent *event) +{ + if (!event->device()->isTabletModeSwitch()) { + return; + } + + switch (event->state()) { + case SwitchEvent::State::Off: + m_parent->setIsTablet(false); + break; + case SwitchEvent::State::On: + m_parent->setIsTablet(true); + break; + default: + Q_UNREACHABLE(); + } +} + + + +TabletModeManager::TabletModeManager(QObject *parent) + : QObject(parent), + m_spy(new TabletModeInputEventSpy(this)) +{ + input()->installInputEventSpy(m_spy); + + QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin"), + QStringLiteral("org.kde.KWin.TabletModeManager"), + this, + QDBusConnection::ExportAllProperties | QDBusConnection::ExportAllSignals + ); + + connect(input(), &InputRedirection::hasTabletModeSwitchChanged, this, &TabletModeManager::tabletModeAvailableChanged); +} + +bool TabletModeManager::isTabletModeAvailable() const +{ + return input()->hasTabletModeSwitch(); +} + +bool TabletModeManager::isTablet() const +{ + return m_isTabletMode; +} + +void TabletModeManager::setIsTablet(bool tablet) +{ + if (m_isTabletMode == tablet) { + return; + } + + m_isTabletMode = tablet; + emit tabletModeChanged(tablet); +} diff --git a/tabletmodemanager.h b/tabletmodemanager.h new file mode 100644 index 000000000..83ee27cfc --- /dev/null +++ b/tabletmodemanager.h @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Marco Martin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef KWIN_TABLETMODEMANAGER_H +#define KWIN_TABLETMODEMANAGER_H + +#include +#include + +namespace KWin { + +class TabletModeInputEventSpy; + +class TabletModeManager : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.TabletModeManager") + //assuming such a switch is not pluggable for now + Q_PROPERTY(bool tabletModeAvailable READ isTabletModeAvailable NOTIFY tabletModeAvailableChanged) + Q_PROPERTY(bool tabletMode READ isTablet NOTIFY tabletModeChanged) + +public: + ~TabletModeManager() = default; + + bool isTabletModeAvailable() const; + + bool isTablet() const; + void setIsTablet(bool tablet); + +Q_SIGNALS: + void tabletModeAvailableChanged(bool available); + void tabletModeChanged(bool tabletMode); + +private: + bool m_tabletModeAvailable = false; + bool m_isTabletMode = false; + TabletModeInputEventSpy *m_spy; + KWIN_SINGLETON_VARIABLE(TabletModeManager, s_manager) +}; +} + +#endif // KWIN_TABLETMODEMANAGER_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 55f6c51ce..48f307c77 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,42 +1,54 @@ if (XCB_ICCCM_FOUND) set(normalhintsbasesizetest_SRCS normalhintsbasesizetest.cpp) add_executable(normalhintsbasesizetest ${normalhintsbasesizetest_SRCS}) target_link_libraries(normalhintsbasesizetest XCB::XCB XCB::ICCCM KF5::WindowSystem) endif() # next target set(screenedgeshowtest_SRCS screenedgeshowtest.cpp) add_executable(screenedgeshowtest ${screenedgeshowtest_SRCS}) target_link_libraries(screenedgeshowtest Qt5::Widgets Qt5::X11Extras KF5::ConfigCore KF5::WindowSystem KF5::WaylandClient ${XCB_XCB_LIBRARY}) if (KF5Wayland_FOUND) add_definitions(-DSOURCE_DIR="${KWIN_SOURCE_DIR}") set(waylandclienttest_SRCS waylandclienttest.cpp ) add_executable(waylandclienttest ${waylandclienttest_SRCS}) target_link_libraries(waylandclienttest Qt5::Core Qt5::Gui KF5::WaylandClient) endif() if (HAVE_INPUT) set(libinputtest_SRCS libinputtest.cpp ${KWIN_SOURCE_DIR}/libinput/context.cpp ${KWIN_SOURCE_DIR}/libinput/connection.cpp ${KWIN_SOURCE_DIR}/libinput/device.cpp ${KWIN_SOURCE_DIR}/libinput/events.cpp ${KWIN_SOURCE_DIR}/libinput/libinput_logging.cpp ${KWIN_SOURCE_DIR}/logind.cpp ${KWIN_SOURCE_DIR}/udev.cpp ) add_executable(libinputtest ${libinputtest_SRCS}) add_definitions(-DKWIN_BUILD_TESTING) target_link_libraries(libinputtest Qt5::Core Qt5::DBus Libinput::Libinput ${UDEV_LIBS} KF5::ConfigCore KF5::GlobalAccel KF5::WindowSystem) endif() add_executable(x11shadowreader x11shadowreader.cpp) target_link_libraries(x11shadowreader XCB::XCB Qt5::Widgets Qt5::X11Extras KF5::ConfigCore KF5::WindowSystem) add_executable(pointergestures pointergesturestest.cpp) add_definitions(-DDIR="${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(pointergestures Qt5::Gui Qt5::Quick KF5::WaylandClient) + +add_executable(cursorhotspottest cursorhotspottest.cpp) +target_link_libraries(cursorhotspottest Qt5::Widgets) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +set( orientationtest_SRCS + orientationtest.cpp + ../orientation_sensor.cpp +) +qt5_add_dbus_adaptor( orientationtest_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../org.kde.kwin.OrientationSensor.xml ${CMAKE_CURRENT_SOURCE_DIR}/../orientation_sensor.h KWin::OrientationSensor) +add_executable(orientationtest ${orientationtest_SRCS}) +target_link_libraries(orientationtest Qt5::DBus Qt5::Widgets Qt5::Sensors KF5::ConfigCore KF5::Notifications KF5::I18n) diff --git a/tests/cursorhotspottest.cpp b/tests/cursorhotspottest.cpp new file mode 100644 index 000000000..efc3916e3 --- /dev/null +++ b/tests/cursorhotspottest.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2017 Martin Flöser + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +#include +#include +#include +#include +#include + +class MouseCursorWidget : public QWidget +{ + Q_OBJECT +public: + explicit MouseCursorWidget(); + ~MouseCursorWidget(); + +protected: + void paintEvent(QPaintEvent * event) override; + void mouseMoveEvent(QMouseEvent * event) override; + void keyPressEvent(QKeyEvent * event) override; + +private: + QPoint m_cursorPos; + QCursor m_cursors[5]; + int m_cursorIndex = 0; +}; + +namespace { +QCursor createCenterHotspotCursor() +{ + QPixmap cursor(64, 64); + cursor.fill(Qt::transparent); + QPainter p(&cursor); + p.setPen(Qt::black); + const QPoint center = cursor.rect().center(); + p.drawLine(0, center.y(), center.x()-1, center.y()); + p.drawLine(center.x() + 1, center.y(), cursor.width(), center.y()); + p.drawLine(center.x(), 0, center.x(), center.y() -1); + p.drawLine(center.x(), center.y() + 1, center.x(), cursor.height()); + return QCursor(cursor, 31, 31); +} + +QCursor createTopLeftHotspotCursor() +{ + QPixmap cursor(64, 64); + cursor.fill(Qt::transparent); + QPainter p(&cursor); + p.setPen(Qt::black); + p.drawLine(0, 1, 0, cursor.height()); + p.drawLine(1, 0, cursor.width(), 0); + return QCursor(cursor, 0, 0); +} + +QCursor createTopRightHotspotCursor() +{ + QPixmap cursor(64, 64); + cursor.fill(Qt::transparent); + QPainter p(&cursor); + p.setPen(Qt::black); + p.drawLine(cursor.width() -1, 1, cursor.width() -1, cursor.height()); + p.drawLine(0, 0, cursor.width() -2, 0); + return QCursor(cursor, 63, 0); +} + +QCursor createButtomRightHotspotCursor() +{ + QPixmap cursor(64, 64); + cursor.fill(Qt::transparent); + QPainter p(&cursor); + p.setPen(Qt::black); + p.drawLine(cursor.width() -1, 0, cursor.width() -1, cursor.height() - 2); + p.drawLine(0, cursor.height()-1, cursor.width() -2, cursor.height()-1); + return QCursor(cursor, 63, 63); +} + +QCursor createButtomLeftHotspotCursor() +{ + QPixmap cursor(64, 64); + cursor.fill(Qt::transparent); + QPainter p(&cursor); + p.setPen(Qt::black); + p.drawLine(0, 0, 0, cursor.height() - 2); + p.drawLine(1, cursor.height()-1, cursor.width(), cursor.height()-1); + return QCursor(cursor, 0, 63); +} + +} + +MouseCursorWidget::MouseCursorWidget() + : QWidget() +{ + setMouseTracking(true); + // create cursors + m_cursors[0] = createCenterHotspotCursor(); + m_cursors[1] = createTopLeftHotspotCursor(); + m_cursors[2] = createTopRightHotspotCursor(); + m_cursors[3] = createButtomRightHotspotCursor(); + m_cursors[4] = createButtomLeftHotspotCursor(); + + setCursor(m_cursors[m_cursorIndex]); +} + +MouseCursorWidget::~MouseCursorWidget() = default; + +void MouseCursorWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter p(this); + p.fillRect(0, 0, width(), height(), Qt::white); + if (geometry().contains(m_cursorPos)) { + p.setPen(Qt::red); + p.drawPoint(m_cursorPos); + } +} + +void MouseCursorWidget::mouseMoveEvent(QMouseEvent *event) +{ + m_cursorPos = event->pos(); + update(); +} + +void MouseCursorWidget::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Space) { + m_cursorIndex = (m_cursorIndex + 1) % 5; + setCursor(m_cursors[m_cursorIndex]); + } +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + MouseCursorWidget widget; + widget.show(); + + return app.exec(); +} + +#include "cursorhotspottest.moc" diff --git a/tests/orientationtest.cpp b/tests/orientationtest.cpp new file mode 100644 index 000000000..722043c47 --- /dev/null +++ b/tests/orientationtest.cpp @@ -0,0 +1,62 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 "../orientation_sensor.h" +#include +#include + +using KWin::OrientationSensor; + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + OrientationSensor sensor; + QObject::connect(&sensor, &OrientationSensor::orientationChanged, + [&sensor] { + const auto orientation = sensor.orientation(); + switch (orientation) { + case OrientationSensor::Orientation::Undefined: + qDebug() << "Undefined"; + break; + case OrientationSensor::Orientation::TopUp: + qDebug() << "TopUp"; + break; + case OrientationSensor::Orientation::TopDown: + qDebug() << "TopDown"; + break; + case OrientationSensor::Orientation::LeftUp: + qDebug() << "LeftUp"; + break; + case OrientationSensor::Orientation::RightUp: + qDebug() << "RightUp"; + break; + case OrientationSensor::Orientation::FaceUp: + qDebug() << "FaceUp"; + break; + case OrientationSensor::Orientation::FaceDown: + qDebug() << "FaceDown"; + break; + } + } + ); + sensor.setEnabled(true); + + return app.exec(); +} diff --git a/toplevel.cpp b/toplevel.cpp index aec48615c..ab04f6817 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -1,547 +1,557 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "toplevel.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client.h" #include "client_machine.h" #include "effects.h" #include "screens.h" #include "shadow.h" #include "xcbutils.h" #include #include namespace KWin { Toplevel::Toplevel() : m_visual(XCB_NONE) , bit_depth(24) , info(NULL) , ready_for_painting(true) , m_isDamaged(false) , m_client() , damage_handle(None) , is_shape(false) , effect_window(NULL) , m_clientMachine(new ClientMachine(this)) , wmClientLeaderWin(0) , m_damageReplyPending(false) , m_screen(0) , m_skipCloseAnimation(false) { connect(this, SIGNAL(damaged(KWin::Toplevel*,QRect)), SIGNAL(needsRepaint())); connect(screens(), SIGNAL(changed()), SLOT(checkScreen())); connect(screens(), SIGNAL(countChanged(int,int)), SLOT(checkScreen())); setupCheckScreenConnection(); } Toplevel::~Toplevel() { assert(damage_handle == None); delete info; } QDebug& operator<<(QDebug& stream, const Toplevel* cl) { if (cl == NULL) return stream << "\'NULL\'"; cl->debug(stream); return stream; } QDebug& operator<<(QDebug& stream, const ToplevelList& list) { stream << "LIST:("; bool first = true; for (ToplevelList::ConstIterator it = list.begin(); it != list.end(); ++it) { if (!first) stream << ":"; first = false; stream << *it; } stream << ")"; return stream; } QRect Toplevel::decorationRect() const { return rect(); } void Toplevel::detectShape(Window id) { const bool wasShape = is_shape; is_shape = Xcb::Extensions::self()->hasShape(id); if (wasShape != is_shape) { emit shapedChanged(); } } // used only by Deleted::copy() void Toplevel::copyToDeleted(Toplevel* c) { geom = c->geom; m_visual = c->m_visual; bit_depth = c->bit_depth; info = c->info; m_client.reset(c->m_client, false); ready_for_painting = c->ready_for_painting; damage_handle = None; damage_region = c->damage_region; repaints_region = c->repaints_region; is_shape = c->is_shape; effect_window = c->effect_window; if (effect_window != NULL) effect_window->setWindow(this); resource_name = c->resourceName(); resource_class = c->resourceClass(); m_clientMachine = c->m_clientMachine; m_clientMachine->setParent(this); wmClientLeaderWin = c->wmClientLeader(); opaque_region = c->opaqueRegion(); m_screen = c->m_screen; m_skipCloseAnimation = c->m_skipCloseAnimation; m_internalFBO = c->m_internalFBO; } // before being deleted, remove references to everything that's now // owner by Deleted void Toplevel::disownDataPassedToDeleted() { info = NULL; } QRect Toplevel::visibleRect() const { QRect r = decorationRect(); if (hasShadow() && !shadow()->shadowRegion().isEmpty()) { r |= shadow()->shadowRegion().boundingRect(); } return r.translated(geometry().topLeft()); } Xcb::Property Toplevel::fetchWmClientLeader() const { return Xcb::Property(false, window(), atoms->wm_client_leader, XCB_ATOM_WINDOW, 0, 10000); } void Toplevel::readWmClientLeader(Xcb::Property &prop) { wmClientLeaderWin = prop.value(window()); } void Toplevel::getWmClientLeader() { auto prop = fetchWmClientLeader(); readWmClientLeader(prop); } /*! Returns sessionId for this client, taken either from its window or from the leader window. */ QByteArray Toplevel::sessionId() const { QByteArray result = Xcb::StringProperty(window(), atoms->sm_client_id); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin != window()) result = Xcb::StringProperty(wmClientLeaderWin, atoms->sm_client_id); return result; } /*! Returns command property for this client, taken either from its window or from the leader window. */ QByteArray Toplevel::wmCommand() { QByteArray result = Xcb::StringProperty(window(), XCB_ATOM_WM_COMMAND); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin != window()) result = Xcb::StringProperty(wmClientLeaderWin, XCB_ATOM_WM_COMMAND); result.replace(0, ' '); return result; } void Toplevel::getWmClientMachine() { m_clientMachine->resolve(window(), wmClientLeader()); } /*! Returns client machine for this client, taken either from its window or from the leader window. */ QByteArray Toplevel::wmClientMachine(bool use_localhost) const { if (!m_clientMachine) { // this should never happen return QByteArray(); } if (use_localhost && m_clientMachine->isLocal()) { // special name for the local machine (localhost) return ClientMachine::localhost(); } return m_clientMachine->hostName(); } /*! Returns client leader window for this client. Returns the client window itself if no leader window is defined. */ Window Toplevel::wmClientLeader() const { if (wmClientLeaderWin) return wmClientLeaderWin; return window(); } void Toplevel::getResourceClass() { setResourceClass(QByteArray(info->windowClassName()).toLower(), QByteArray(info->windowClassClass()).toLower()); } void Toplevel::setResourceClass(const QByteArray &name, const QByteArray &className) { resource_name = name; resource_class = className; emit windowClassChanged(); } double Toplevel::opacity() const { if (info->opacity() == 0xffffffff) return 1.0; return info->opacity() * 1.0 / 0xffffffff; } void Toplevel::setOpacity(double new_opacity) { double old_opacity = opacity(); new_opacity = qBound(0.0, new_opacity, 1.0); if (old_opacity == new_opacity) return; info->setOpacity(static_cast< unsigned long >(new_opacity * 0xffffffff)); if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } } void Toplevel::setReadyForPainting() { if (!ready_for_painting) { ready_for_painting = true; if (compositing()) { addRepaintFull(); emit windowShown(this); if (Client *cl = dynamic_cast(this)) { if (cl->tabGroup() && cl->tabGroup()->current() == cl) cl->tabGroup()->setCurrent(cl, true); } } } } void Toplevel::deleteEffectWindow() { delete effect_window; effect_window = NULL; } void Toplevel::checkScreen() { if (screens()->count() == 1) { if (m_screen != 0) { m_screen = 0; emit screenChanged(); } - return; + } else { + const int s = screens()->number(geometry().center()); + if (s != m_screen) { + m_screen = s; + emit screenChanged(); + } } - const int s = screens()->number(geometry().center()); - if (s != m_screen) { - m_screen = s; - emit screenChanged(); + qreal newScale = screens()->scale(m_screen); + if (newScale != m_screenScale) { + m_screenScale = newScale; + emit screenScaleChanged(); } } void Toplevel::setupCheckScreenConnection() { connect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(checkScreen())); connect(this, SIGNAL(geometryChanged()), SLOT(checkScreen())); checkScreen(); } void Toplevel::removeCheckScreenConnection() { disconnect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), this, SLOT(checkScreen())); disconnect(this, SIGNAL(geometryChanged()), this, SLOT(checkScreen())); } int Toplevel::screen() const { return m_screen; } +qreal Toplevel::screenScale() const +{ + return m_screenScale; +} + bool Toplevel::isOnScreen(int screen) const { return screens()->geometry(screen).intersects(geometry()); } bool Toplevel::isOnActiveScreen() const { return isOnScreen(screens()->current()); } void Toplevel::getShadow() { QRect dirtyRect; // old & new shadow region const QRect oldVisibleRect = visibleRect(); if (hasShadow()) { dirtyRect = shadow()->shadowRegion().boundingRect(); effectWindow()->sceneWindow()->shadow()->updateShadow(); } else { Shadow::createShadow(this); } if (hasShadow()) dirtyRect |= shadow()->shadowRegion().boundingRect(); if (oldVisibleRect != visibleRect()) emit paddingChanged(this, oldVisibleRect); if (dirtyRect.isValid()) { dirtyRect.translate(pos()); addLayerRepaint(dirtyRect); } } bool Toplevel::hasShadow() const { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow() != NULL; } return false; } Shadow *Toplevel::shadow() { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { return NULL; } } const Shadow *Toplevel::shadow() const { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { return NULL; } } bool Toplevel::wantsShadowToBeRendered() const { return true; } void Toplevel::getWmOpaqueRegion() { const auto rects = info->opaqueRegion(); QRegion new_opaque_region; for (const auto &r : rects) { new_opaque_region += QRect(r.pos.x, r.pos.y, r.size.width, r.size.height); } opaque_region = new_opaque_region; } bool Toplevel::isClient() const { return false; } bool Toplevel::isDeleted() const { return false; } bool Toplevel::isOnCurrentActivity() const { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return true; } return isOnActivity(Activities::self()->current()); #else return true; #endif } void Toplevel::elevate(bool elevate) { if (!effectWindow()) { return; } effectWindow()->elevate(elevate); addWorkspaceRepaint(visibleRect()); } pid_t Toplevel::pid() const { return info->pid(); } xcb_window_t Toplevel::frameId() const { return m_client; } Xcb::Property Toplevel::fetchSkipCloseAnimation() const { return Xcb::Property(false, window(), atoms->kde_skip_close_animation, XCB_ATOM_CARDINAL, 0, 1); } void Toplevel::readSkipCloseAnimation(Xcb::Property &property) { setSkipCloseAnimation(property.toBool()); } void Toplevel::getSkipCloseAnimation() { Xcb::Property property = fetchSkipCloseAnimation(); readSkipCloseAnimation(property); } bool Toplevel::skipsCloseAnimation() const { return m_skipCloseAnimation; } void Toplevel::setSkipCloseAnimation(bool set) { if (set == m_skipCloseAnimation) { return; } m_skipCloseAnimation = set; emit skipCloseAnimationChanged(); } void Toplevel::setSurface(KWayland::Server::SurfaceInterface *surface) { if (m_surface == surface) { return; } using namespace KWayland::Server; if (m_surface) { disconnect(m_surface, &SurfaceInterface::damaged, this, &Toplevel::addDamage); disconnect(m_surface, &SurfaceInterface::sizeChanged, this, &Toplevel::discardWindowPixmap); } m_surface = surface; connect(m_surface, &SurfaceInterface::damaged, this, &Toplevel::addDamage); connect(m_surface, &SurfaceInterface::sizeChanged, this, &Toplevel::discardWindowPixmap); connect(m_surface, &SurfaceInterface::subSurfaceTreeChanged, this, [this] { // TODO improve to only update actual visual area if (ready_for_painting) { addDamageFull(); m_isDamaged = true; } } ); connect(m_surface, &SurfaceInterface::destroyed, this, [this] { m_surface = nullptr; } ); emit surfaceChanged(); } void Toplevel::addDamage(const QRegion &damage) { m_isDamaged = true; damage_region += damage; for (const QRect &r : damage.rects()) { emit damaged(this, r); } } QByteArray Toplevel::windowRole() const { return QByteArray(info->windowRole()); } void Toplevel::setDepth(int depth) { if (bit_depth == depth) { return; } const bool oldAlpha = hasAlpha(); bit_depth = depth; if (oldAlpha != hasAlpha()) { emit hasAlphaChanged(); } } QRegion Toplevel::inputShape() const { if (m_surface) { return m_surface->input(); } else { // TODO: maybe also for X11? return QRegion(); } } void Toplevel::setInternalFramebufferObject(const QSharedPointer &fbo) { if (m_internalFBO != fbo) { discardWindowPixmap(); m_internalFBO = fbo; } setDepth(32); } QMatrix4x4 Toplevel::inputTransformation() const { QMatrix4x4 m; m.translate(-x(), -y()); return m; } quint32 Toplevel::windowId() const { return window(); } QRect Toplevel::inputGeometry() const { return geometry(); } } // namespace diff --git a/toplevel.h b/toplevel.h index 18775470a..77ad278bd 100644 --- a/toplevel.h +++ b/toplevel.h @@ -1,841 +1,855 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_TOPLEVEL_H #define KWIN_TOPLEVEL_H // kwin #include "input.h" #include "utils.h" #include "virtualdesktops.h" #include "xcbutils.h" // KDE #include // Qt #include #include // xcb #include #include // XLib #include #include // system #include // c++ #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class SurfaceInterface; } } namespace KWin { class ClientMachine; class EffectWindowImpl; class Shadow; /** * Enum to describe the reason why a Toplevel has to be released. */ enum class ReleaseReason { Release, ///< Normal Release after e.g. an Unmap notify event (window still valid) Destroyed, ///< Release after an Destroy notify event (window no longer valid) KWinShutsDown ///< Release on KWin Shutdown (window still valid) }; class KWIN_EXPORT Toplevel : public QObject { Q_OBJECT Q_PROPERTY(bool alpha READ hasAlpha NOTIFY hasAlphaChanged) Q_PROPERTY(qulonglong frameId READ frameId) Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged) Q_PROPERTY(QRect visibleRect READ visibleRect) Q_PROPERTY(int height READ height) Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) Q_PROPERTY(QPoint pos READ pos) Q_PROPERTY(int screen READ screen NOTIFY screenChanged) Q_PROPERTY(QSize size READ size) Q_PROPERTY(int width READ width) Q_PROPERTY(qulonglong windowId READ windowId CONSTANT) Q_PROPERTY(int x READ x) Q_PROPERTY(int y READ y) Q_PROPERTY(int desktop READ desktop) /** * Whether the window is on all desktops. That is desktop is -1. **/ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops) Q_PROPERTY(QRect rect READ rect) Q_PROPERTY(QPoint clientPos READ clientPos) Q_PROPERTY(QSize clientSize READ clientSize) Q_PROPERTY(QByteArray resourceName READ resourceName NOTIFY windowClassChanged) Q_PROPERTY(QByteArray resourceClass READ resourceClass NOTIFY windowClassChanged) Q_PROPERTY(QByteArray windowRole READ windowRole NOTIFY windowRoleChanged) /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool desktopWindow READ isDesktop) /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dock READ isDock) /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool toolbar READ isToolbar) /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool menu READ isMenu) /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool normalWindow READ isNormalWindow) /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dialog READ isDialog) /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool splash READ isSplash) /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool utility READ isUtility) /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dropdownMenu READ isDropdownMenu) /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool popupMenu READ isPopupMenu) /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool tooltip READ isTooltip) /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool notification READ isNotification) /** * Returns whether the window is an On Screen Display. */ Q_PROPERTY(bool onScreenDisplay READ isOnScreenDisplay) /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool comboBox READ isComboBox) /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dndIcon READ isDNDIcon) /** * Returns the NETWM window type * See http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(int windowType READ windowType) Q_PROPERTY(QStringList activities READ activities NOTIFY activitiesChanged) /** * Whether this Toplevel is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). **/ Q_PROPERTY(bool managed READ isClient CONSTANT) /** * Whether this Toplevel represents an already deleted window and only kept for the compositor for animations. **/ Q_PROPERTY(bool deleted READ isDeleted CONSTANT) /** * Whether the window has an own shape **/ Q_PROPERTY(bool shaped READ shape NOTIFY shapedChanged) /** * Whether the window does not want to be animated on window close. * There are legit reasons for this like a screenshot application which does not want it's * window being captured. **/ Q_PROPERTY(bool skipsCloseAnimation READ skipsCloseAnimation WRITE setSkipCloseAnimation NOTIFY skipCloseAnimationChanged) /** * The Id of the Wayland Surface associated with this Toplevel. * On X11 only setups the value is @c 0. **/ Q_PROPERTY(quint32 surfaceId READ surfaceId NOTIFY surfaceIdChanged) /** * Interface to the Wayland Surface. * Relevant only in Wayland, in X11 it will be nullptr */ Q_PROPERTY(KWayland::Server::SurfaceInterface *surface READ surface) public: explicit Toplevel(); virtual xcb_window_t frameId() const; xcb_window_t window() const; /** * @return a unique identifier for the Toplevel. On X11 same as @link {window} **/ virtual quint32 windowId() const; QRect geometry() const; /** * The geometry of the Toplevel which accepts input events. This might be larger * than the actual geometry, e.g. to support resizing outside the window. * * Default implementation returns same as geometry. **/ virtual QRect inputGeometry() const; QSize size() const; QPoint pos() const; QRect rect() const; int x() const; int y() const; int width() const; int height() const; bool isOnScreen(int screen) const; // true if it's at least partially there bool isOnActiveScreen() const; int screen() const; // the screen where the center is + /** + * The scale of the screen this window is currently on + * @Note: The buffer scale can be different. + * @since 5.12 + */ + qreal screenScale() const; // virtual QPoint clientPos() const = 0; // inside of geometry() /** * Describes how the client's content maps to the window geometry including the frame. * The default implementation is a 1:1 mapping meaning the frame is part of the content. **/ virtual QPoint clientContentPos() const; virtual QSize clientSize() const = 0; virtual QRect visibleRect() const; // the area the window occupies on the screen virtual QRect decorationRect() const; // rect including the decoration shadows virtual QRect transparentRect() const = 0; virtual bool isClient() const; virtual bool isDeleted() const; // prefer isXXX() instead // 0 for supported types means default for managed/unmanaged types virtual NET::WindowType windowType(bool direct = false, int supported_types = 0) const = 0; bool hasNETSupport() const; bool isDesktop() const; bool isDock() const; bool isToolbar() const; bool isMenu() const; bool isNormalWindow() const; // normal as in 'NET::Normal or NET::Unknown non-transient' bool isDialog() const; bool isSplash() const; bool isUtility() const; bool isDropdownMenu() const; bool isPopupMenu() const; // a context popup, not dropdown, not torn-off bool isTooltip() const; bool isNotification() const; bool isOnScreenDisplay() const; bool isComboBox() const; bool isDNDIcon() const; virtual bool isLockScreen() const; virtual bool isInputMethod() const; /** * Returns the virtual desktop within the workspace() the client window * is located in, 0 if it isn't located on any special desktop (not mapped yet), * or NET::OnAllDesktops. Do not use desktop() directly, use * isOnDesktop() instead. */ virtual int desktop() const = 0; virtual QStringList activities() const = 0; bool isOnDesktop(int d) const; bool isOnActivity(const QString &activity) const; bool isOnCurrentDesktop() const; bool isOnCurrentActivity() const; bool isOnAllDesktops() const; bool isOnAllActivities() const; virtual QByteArray windowRole() const; QByteArray sessionId() const; QByteArray resourceName() const; QByteArray resourceClass() const; QByteArray wmCommand(); QByteArray wmClientMachine(bool use_localhost) const; const ClientMachine *clientMachine() const; Window wmClientLeader() const; virtual pid_t pid() const; static bool resourceMatch(const Toplevel* c1, const Toplevel* c2); bool readyForPainting() const; // true if the window has been already painted its contents xcb_visualid_t visual() const; bool shape() const; QRegion inputShape() const; virtual void setOpacity(double opacity); virtual double opacity() const; int depth() const; bool hasAlpha() const; virtual bool setupCompositing(); virtual void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release); Q_INVOKABLE void addRepaint(const QRect& r); Q_INVOKABLE void addRepaint(const QRegion& r); Q_INVOKABLE void addRepaint(int x, int y, int w, int h); Q_INVOKABLE void addLayerRepaint(const QRect& r); Q_INVOKABLE void addLayerRepaint(const QRegion& r); Q_INVOKABLE void addLayerRepaint(int x, int y, int w, int h); Q_INVOKABLE virtual void addRepaintFull(); // these call workspace->addRepaint(), but first transform the damage if needed void addWorkspaceRepaint(const QRect& r); void addWorkspaceRepaint(int x, int y, int w, int h); QRegion repaints() const; void resetRepaints(); QRegion damage() const; void resetDamage(); EffectWindowImpl* effectWindow(); const EffectWindowImpl* effectWindow() const; /** * Window will be temporarily painted as if being at the top of the stack. * Only available if Compositor is active, if not active, this method is a no-op. **/ void elevate(bool elevate); /** * @returns Whether the Toplevel has a Shadow or not * @see shadow **/ bool hasShadow() const; /** * Returns the pointer to the Toplevel's Shadow. A Shadow * is only available if Compositing is enabled and the corresponding X window * has the Shadow property set. * If a shadow is available @link hasShadow returns @c true. * @returns The Shadow belonging to this Toplevel, may be @c NULL. * @see hasShadow **/ const Shadow *shadow() const; Shadow *shadow(); /** * Updates the Shadow associated with this Toplevel from X11 Property. * Call this method when the Property changes or Compositing is started. **/ void getShadow(); /** * Whether the Toplevel currently wants the shadow to be rendered. Default * implementation always returns @c true. **/ virtual bool wantsShadowToBeRendered() const; /** * This method returns the area that the Toplevel window reports to be opaque. * It is supposed to only provide valuable information if @link hasAlpha is @c true . * @see hasAlpha **/ const QRegion& opaqueRegion() const; virtual Layer layer() const = 0; /** * Resets the damage state and sends a request for the damage region. * A call to this function must be followed by a call to getDamageRegionReply(), * or the reply will be leaked. * * Returns true if the window was damaged, and false otherwise. */ bool resetAndFetchDamage(); /** * Gets the reply from a previous call to resetAndFetchDamage(). * Calling this function is a no-op if there is no pending reply. * Call damage() to return the fetched region. */ void getDamageRegionReply(); bool skipsCloseAnimation() const; void setSkipCloseAnimation(bool set); quint32 surfaceId() const; KWayland::Server::SurfaceInterface *surface() const; void setSurface(KWayland::Server::SurfaceInterface *surface); virtual void setInternalFramebufferObject(const QSharedPointer &fbo); const QSharedPointer &internalFramebufferObject() const; /** * @returns Transformation to map from global to window coordinates. * * Default implementation returns a translation on negative pos(). * @see pos **/ virtual QMatrix4x4 inputTransformation() const; /** * The window has a popup grab. This means that when it got mapped the * parent window had an implicit (pointer) grab. * * Normally this is only relevant for transient windows. * * Once the popup grab ends (e.g. pointer press outside of any Toplevel of * the client), the method popupDone should be invoked. * * The default implementation returns @c false. * @see popupDone * @since 5.10 **/ virtual bool hasPopupGrab() const { return false; } /** * This method should be invoked for Toplevels with a popup grab when * the grab ends. * * The default implementation does nothing. * @see hasPopupGrab * @since 5.10 **/ virtual void popupDone() {}; /** * @brief Finds the Toplevel matching the condition expressed in @p func in @p list. * * The method is templated to operate on either a list of Toplevels or on a list of * a subclass type of Toplevel. * @param list The list to search in * @param func The condition function (compare std::find_if) * @return T* The found Toplevel or @c null if there is no matching Toplevel */ template static T *findInList(const QList &list, std::function func); Q_SIGNALS: void opacityChanged(KWin::Toplevel* toplevel, qreal oldOpacity); void damaged(KWin::Toplevel* toplevel, const QRect& damage); void geometryChanged(); void geometryShapeChanged(KWin::Toplevel* toplevel, const QRect& old); void paddingChanged(KWin::Toplevel* toplevel, const QRect& old); void windowClosed(KWin::Toplevel* toplevel, KWin::Deleted* deleted); void windowShown(KWin::Toplevel* toplevel); void windowHidden(KWin::Toplevel* toplevel); /** * Signal emitted when the window's shape state changed. That is if it did not have a shape * and received one or if the shape was withdrawn. Think of Chromium enabling/disabling KWin's * decoration. **/ void shapedChanged(); /** * Emitted whenever the state changes in a way, that the Compositor should * schedule a repaint of the scene. **/ void needsRepaint(); void activitiesChanged(KWin::Toplevel* toplevel); /** * Emitted whenever the Toplevel's screen changes. This can happen either in consequence to * a screen being removed/added or if the Toplevel's geometry changes. * @since 4.11 **/ void screenChanged(); void skipCloseAnimationChanged(); /** * Emitted whenever the window role of the window changes. * @since 5.0 **/ void windowRoleChanged(); /** * Emitted whenever the window class name or resource name of the window changes. * @since 5.0 **/ void windowClassChanged(); /** * Emitted when a Wayland Surface gets associated with this Toplevel. * @since 5.3 **/ void surfaceIdChanged(quint32); /** * @since 5.4 **/ void hasAlphaChanged(); /** * Emitted whenever the Surface for this Toplevel changes. **/ void surfaceChanged(); + /* + * Emitted when the client's screen changes onto a screen of a different scale + * or the screen we're on changes + * @since 5.12 + */ + void screenScaleChanged(); + protected Q_SLOTS: /** * Checks whether the screen number for this Toplevel changed and updates if needed. * Any method changing the geometry of the Toplevel should call this method. **/ void checkScreen(); void setupCheckScreenConnection(); void removeCheckScreenConnection(); void setReadyForPainting(); protected: virtual ~Toplevel(); void setWindowHandles(xcb_window_t client); void detectShape(Window id); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e); virtual void damageNotifyEvent(); virtual void clientMessageEvent(xcb_client_message_event_t *e); void discardWindowPixmap(); void addDamageFull(); virtual void addDamage(const QRegion &damage); Xcb::Property fetchWmClientLeader() const; void readWmClientLeader(Xcb::Property &p); void getWmClientLeader(); void getWmClientMachine(); /** * @returns Whether there is a compositor and it is active. **/ bool compositing() const; /** * This function fetches the opaque region from this Toplevel. * Will only be called on corresponding property changes and for initialization. **/ void getWmOpaqueRegion(); void getResourceClass(); void setResourceClass(const QByteArray &name, const QByteArray &className = QByteArray()); Xcb::Property fetchSkipCloseAnimation() const; void readSkipCloseAnimation(Xcb::Property &prop); void getSkipCloseAnimation(); virtual void debug(QDebug& stream) const = 0; void copyToDeleted(Toplevel* c); void disownDataPassedToDeleted(); friend QDebug& operator<<(QDebug& stream, const Toplevel*); void deleteEffectWindow(); void setDepth(int depth); QRect geom; xcb_visualid_t m_visual; int bit_depth; NETWinInfo* info; bool ready_for_painting; QRegion repaints_region; // updating, repaint just requires repaint of that area QRegion layer_repaints_region; protected: bool m_isDamaged; private: // when adding new data members, check also copyToDeleted() Xcb::Window m_client; xcb_damage_damage_t damage_handle; QRegion damage_region; // damage is really damaged window (XDamage) and texture needs bool is_shape; EffectWindowImpl* effect_window; QByteArray resource_name; QByteArray resource_class; ClientMachine *m_clientMachine; WId wmClientLeaderWin; bool m_damageReplyPending; QRegion opaque_region; xcb_xfixes_fetch_region_cookie_t m_regionCookie; int m_screen; bool m_skipCloseAnimation; quint32 m_surfaceId = 0; KWayland::Server::SurfaceInterface *m_surface = nullptr; /** * An FBO object KWin internal windows might render to. **/ QSharedPointer m_internalFBO; // when adding new data members, check also copyToDeleted() + qreal m_screenScale = 1.0; }; inline xcb_window_t Toplevel::window() const { return m_client; } inline void Toplevel::setWindowHandles(xcb_window_t w) { assert(!m_client.isValid() && w != XCB_WINDOW_NONE); m_client.reset(w, false); } inline QRect Toplevel::geometry() const { return geom; } inline QSize Toplevel::size() const { return geom.size(); } inline QPoint Toplevel::pos() const { return geom.topLeft(); } inline int Toplevel::x() const { return geom.x(); } inline int Toplevel::y() const { return geom.y(); } inline int Toplevel::width() const { return geom.width(); } inline int Toplevel::height() const { return geom.height(); } inline QRect Toplevel::rect() const { return QRect(0, 0, width(), height()); } inline bool Toplevel::readyForPainting() const { return ready_for_painting; } inline xcb_visualid_t Toplevel::visual() const { return m_visual; } inline bool Toplevel::isDesktop() const { return windowType() == NET::Desktop; } inline bool Toplevel::isDock() const { return windowType() == NET::Dock; } inline bool Toplevel::isMenu() const { return windowType() == NET::Menu; } inline bool Toplevel::isToolbar() const { return windowType() == NET::Toolbar; } inline bool Toplevel::isSplash() const { return windowType() == NET::Splash; } inline bool Toplevel::isUtility() const { return windowType() == NET::Utility; } inline bool Toplevel::isDialog() const { return windowType() == NET::Dialog; } inline bool Toplevel::isNormalWindow() const { return windowType() == NET::Normal; } inline bool Toplevel::isDropdownMenu() const { return windowType() == NET::DropdownMenu; } inline bool Toplevel::isPopupMenu() const { return windowType() == NET::PopupMenu; } inline bool Toplevel::isTooltip() const { return windowType() == NET::Tooltip; } inline bool Toplevel::isNotification() const { return windowType() == NET::Notification; } inline bool Toplevel::isOnScreenDisplay() const { return windowType() == NET::OnScreenDisplay; } inline bool Toplevel::isComboBox() const { return windowType() == NET::ComboBox; } inline bool Toplevel::isDNDIcon() const { return windowType() == NET::DNDIcon; } inline bool Toplevel::isLockScreen() const { return false; } inline bool Toplevel::isInputMethod() const { return false; } inline QRegion Toplevel::damage() const { return damage_region; } inline QRegion Toplevel::repaints() const { return repaints_region.translated(pos()) | layer_repaints_region; } inline bool Toplevel::shape() const { return is_shape; } inline int Toplevel::depth() const { return bit_depth; } inline bool Toplevel::hasAlpha() const { return depth() == 32; } inline const QRegion& Toplevel::opaqueRegion() const { return opaque_region; } inline EffectWindowImpl* Toplevel::effectWindow() { return effect_window; } inline const EffectWindowImpl* Toplevel::effectWindow() const { return effect_window; } inline bool Toplevel::isOnAllDesktops() const { return desktop() == NET::OnAllDesktops; } inline bool Toplevel::isOnAllActivities() const { return activities().isEmpty(); } inline bool Toplevel::isOnDesktop(int d) const { return desktop() == d || /*desk == 0 ||*/ isOnAllDesktops(); } inline bool Toplevel::isOnActivity(const QString &activity) const { return activities().isEmpty() || activities().contains(activity); } inline bool Toplevel::isOnCurrentDesktop() const { return isOnDesktop(VirtualDesktopManager::self()->current()); } inline QByteArray Toplevel::resourceName() const { return resource_name; // it is always lowercase } inline QByteArray Toplevel::resourceClass() const { return resource_class; // it is always lowercase } inline const ClientMachine *Toplevel::clientMachine() const { return m_clientMachine; } inline quint32 Toplevel::surfaceId() const { return m_surfaceId; } inline KWayland::Server::SurfaceInterface *Toplevel::surface() const { return m_surface; } inline const QSharedPointer &Toplevel::internalFramebufferObject() const { return m_internalFBO; } inline QPoint Toplevel::clientContentPos() const { return QPoint(0, 0); } template inline T *Toplevel::findInList(const QList &list, std::function func) { static_assert(std::is_base_of::value, "U must be derived from T"); const auto it = std::find_if(list.begin(), list.end(), func); if (it == list.end()) { return nullptr; } return *it; } QDebug& operator<<(QDebug& stream, const Toplevel*); QDebug& operator<<(QDebug& stream, const ToplevelList&); } // namespace Q_DECLARE_METATYPE(KWin::Toplevel*) #endif diff --git a/wayland_server.cpp b/wayland_server.cpp index 5a5851a52..78ab4c332 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -1,716 +1,765 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "wayland_server.h" #include "client.h" #include "platform.h" #include "composite.h" +#include "idle_inhibition.h" #include "screens.h" #include "shell_client.h" #include "workspace.h" // Client #include #include #include #include #include // Server +#include #include #include +#include #include #include #include +#include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include // Qt #include #include // system #include #include #include //screenlocker #include using namespace KWayland::Server; namespace KWin { KWIN_SINGLETON_FACTORY(WaylandServer) WaylandServer::WaylandServer(QObject *parent) : QObject(parent) { qRegisterMetaType(); connect(kwinApp(), &Application::screensCreated, this, &WaylandServer::initOutputs); connect(kwinApp(), &Application::x11ConnectionChanged, this, &WaylandServer::setupX11ClipboardSync); } WaylandServer::~WaylandServer() { destroyInputMethodConnection(); } void WaylandServer::destroyInternalConnection() { emit terminatingInternalClientConnection(); if (m_internalConnection.client) { // delete all connections hold by plugins like e.g. widget style const auto connections = KWayland::Client::ConnectionThread::connections(); for (auto c : connections) { if (c == m_internalConnection.client) { continue; } emit c->connectionDied(); } delete m_internalConnection.registry; delete m_internalConnection.shm; dispatch(); m_internalConnection.client->deleteLater(); m_internalConnection.clientThread->quit(); m_internalConnection.clientThread->wait(); delete m_internalConnection.clientThread; m_internalConnection.client = nullptr; m_internalConnection.server->destroy(); m_internalConnection.server = nullptr; } } void WaylandServer::terminateClientConnections() { destroyInternalConnection(); destroyInputMethodConnection(); if (m_display) { const auto connections = m_display->connections(); for (auto it = connections.begin(); it != connections.end(); ++it) { (*it)->destroy(); } } } template void WaylandServer::createSurface(T *surface) { if (!Workspace::self()) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() == m_xwayland.client) { // skip Xwayland clients, those are created using standard X11 way return; } if (surface->client() == m_screenLockerClientConnection) { ScreenLocker::KSldApp::self()->lockScreenShown(); } auto client = new ShellClient(surface); auto it = std::find_if(m_plasmaShellSurfaces.begin(), m_plasmaShellSurfaces.end(), [client] (PlasmaShellSurfaceInterface *surface) { return client->surface() == surface->surface(); } ); if (it != m_plasmaShellSurfaces.end()) { client->installPlasmaShellSurface(*it); m_plasmaShellSurfaces.erase(it); } + if (auto menu = m_appMenuManager->appMenuForSurface(surface->surface())) { + client->installAppMenu(menu); + } + if (auto palette = m_paletteManager->paletteForSurface(surface->surface())) { + client->installPalette(palette); + } if (client->isInternal()) { m_internalClients << client; } else { m_clients << client; } if (client->readyForPainting()) { emit shellClientAdded(client); } else { connect(client, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); } //not directly connected as the connection is tied to client instead of this connect(m_XdgForeign, &KWayland::Server::XdgForeignInterface::transientChanged, client, [this](KWayland::Server::SurfaceInterface *child) { emit foreignTransientChanged(child); }); } bool WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags) { m_initFlags = flags; m_display = new KWayland::Server::Display(this); if (!socketName.isNull() && !socketName.isEmpty()) { m_display->setSocketName(QString::fromUtf8(socketName)); } m_display->start(); if (!m_display->isRunning()) { return false; } m_compositor = m_display->createCompositor(m_display); m_compositor->create(); connect(m_compositor, &CompositorInterface::surfaceCreated, this, [this] (SurfaceInterface *surface) { // check whether we have a Toplevel with the Surface's id Workspace *ws = Workspace::self(); if (!ws) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() != xWaylandConnection()) { // setting surface is only relevat for Xwayland clients return; } auto check = [surface] (const Toplevel *t) { return t->surfaceId() == surface->id(); }; if (Toplevel *t = ws->findToplevel(check)) { t->setSurface(surface); } } ); m_shell = m_display->createShell(m_display); m_shell->create(); connect(m_shell, &ShellInterface::surfaceCreated, this, &WaylandServer::createSurface); m_xdgShell = m_display->createXdgShell(XdgShellInterfaceVersion::UnstableV5, m_display); m_xdgShell->create(); connect(m_xdgShell, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); // TODO: verify seat and serial connect(m_xdgShell, &XdgShellInterface::popupCreated, this, &WaylandServer::createSurface); m_xdgShell6 = m_display->createXdgShell(XdgShellInterfaceVersion::UnstableV6, m_display); m_xdgShell6->create(); connect(m_xdgShell6, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); connect(m_xdgShell6, &XdgShellInterface::xdgPopupCreated, this, &WaylandServer::createSurface); m_display->createShm(); m_seat = m_display->createSeat(m_display); m_seat->create(); m_display->createPointerGestures(PointerGesturesInterfaceVersion::UnstableV1, m_display)->create(); m_display->createPointerConstraints(PointerConstraintsInterfaceVersion::UnstableV1, m_display)->create(); auto ddm = m_display->createDataDeviceManager(m_display); ddm->create(); connect(ddm, &DataDeviceManagerInterface::dataDeviceCreated, this, [this] (DataDeviceInterface *ddi) { if (ddi->client() == m_xclipbaordSync.client && m_xclipbaordSync.client != nullptr) { m_xclipbaordSync.ddi = QPointer(ddi); connect(m_xclipbaordSync.ddi.data(), &DataDeviceInterface::selectionChanged, this, [this] { // testing whether the active client inherits Client // it would be better to test for the keyboard focus, but we might get a clipboard update // when the Client is already active, but no Surface is created yet. if (workspace()->activeClient() && workspace()->activeClient()->inherits("KWin::Client")) { m_seat->setSelection(m_xclipbaordSync.ddi.data()); } } ); } } ); - m_display->createIdle(m_display)->create(); + m_idle = m_display->createIdle(m_display); + m_idle->create(); + auto idleInhibition = new IdleInhibition(m_idle); + connect(this, &WaylandServer::shellClientAdded, idleInhibition, &IdleInhibition::registerShellClient); + m_display->createIdleInhibitManager(IdleInhibitManagerInterfaceVersion::UnstableV1, m_display)->create(); m_plasmaShell = m_display->createPlasmaShell(m_display); m_plasmaShell->create(); connect(m_plasmaShell, &PlasmaShellInterface::surfaceCreated, [this] (PlasmaShellSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installPlasmaShellSurface(surface); } else { m_plasmaShellSurfaces << surface; connect(surface, &QObject::destroyed, this, [this, surface] { m_plasmaShellSurfaces.removeOne(surface); } ); } } ); + + m_qtExtendedSurface = m_display->createQtSurfaceExtension(m_display); m_qtExtendedSurface->create(); connect(m_qtExtendedSurface, &QtSurfaceExtensionInterface::surfaceCreated, [this] (QtExtendedSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installQtExtendedSurface(surface); } } ); + m_appMenuManager = m_display->createAppMenuManagerInterface(m_display); + m_appMenuManager->create(); + connect(m_appMenuManager, &AppMenuManagerInterface::appMenuCreated, + [this] (AppMenuInterface *appMenu) { + if (ShellClient *client = findClient(appMenu->surface())) { + client->installAppMenu(appMenu); + } + } + ); + m_paletteManager = m_display->createServerSideDecorationPaletteManager(m_display); + m_paletteManager->create(); + connect(m_paletteManager, &ServerSideDecorationPaletteManagerInterface::paletteCreated, + [this] (ServerSideDecorationPaletteInterface *palette) { + if (ShellClient *client = findClient(palette->surface())) { + client->installPalette(palette); + } + } + ); + m_windowManagement = m_display->createPlasmaWindowManagement(m_display); m_windowManagement->create(); m_windowManagement->setShowingDesktopState(PlasmaWindowManagementInterface::ShowingDesktopState::Disabled); connect(m_windowManagement, &PlasmaWindowManagementInterface::requestChangeShowingDesktop, this, [] (PlasmaWindowManagementInterface::ShowingDesktopState state) { if (!workspace()) { return; } bool set = false; switch (state) { case PlasmaWindowManagementInterface::ShowingDesktopState::Disabled: set = false; break; case PlasmaWindowManagementInterface::ShowingDesktopState::Enabled: set = true; break; default: Q_UNREACHABLE(); break; } if (set == workspace()->showingDesktop()) { return; } workspace()->setShowingDesktop(set); } ); auto shadowManager = m_display->createShadowManager(m_display); shadowManager->create(); m_display->createDpmsManager(m_display)->create(); m_decorationManager = m_display->createServerSideDecorationManager(m_display); connect(m_decorationManager, &ServerSideDecorationManagerInterface::decorationCreated, this, [this] (ServerSideDecorationInterface *deco) { if (ShellClient *c = findClient(deco->surface())) { c->installServerSideDecoration(deco); } + connect(deco, &ServerSideDecorationInterface::modeRequested, this, + [this, deco] (ServerSideDecorationManagerInterface::Mode mode) { + // always acknowledge the requested mode + deco->setMode(mode); + } + ); } ); m_decorationManager->create(); m_outputManagement = m_display->createOutputManagement(m_display); connect(m_outputManagement, &OutputManagementInterface::configurationChangeRequested, this, [this](KWayland::Server::OutputConfigurationInterface *config) { kwinApp()->platform()->configurationChangeRequested(config); }); m_outputManagement->create(); m_display->createSubCompositor(m_display)->create(); m_XdgForeign = m_display->createXdgForeignInterface(m_display); m_XdgForeign->create(); return true; } SurfaceInterface *WaylandServer::findForeignTransientForSurface(SurfaceInterface *surface) { return m_XdgForeign->transientFor(surface); } void WaylandServer::shellClientShown(Toplevel *t) { ShellClient *c = dynamic_cast(t); if (!c) { qCWarning(KWIN_CORE) << "Failed to cast a Toplevel which is supposed to be a ShellClient to ShellClient"; return; } disconnect(c, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); emit shellClientAdded(c); } void WaylandServer::initWorkspace() { if (m_windowManagement) { connect(workspace(), &Workspace::showingDesktopChanged, this, [this] (bool set) { using namespace KWayland::Server; m_windowManagement->setShowingDesktopState(set ? PlasmaWindowManagementInterface::ShowingDesktopState::Enabled : PlasmaWindowManagementInterface::ShowingDesktopState::Disabled ); } ); } if (hasScreenLockerIntegration()) { if (m_internalConnection.interfacesAnnounced) { initScreenLocker(); } else { connect(m_internalConnection.registry, &KWayland::Client::Registry::interfacesAnnounced, this, &WaylandServer::initScreenLocker); } } else { emit initialized(); } } void WaylandServer::initScreenLocker() { ScreenLocker::KSldApp::self(); ScreenLocker::KSldApp::self()->setWaylandDisplay(m_display); ScreenLocker::KSldApp::self()->setGreeterEnvironment(kwinApp()->processStartupEnvironment()); ScreenLocker::KSldApp::self()->initialize(); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::greeterClientConnectionChanged, this, [this] () { m_screenLockerClientConnection = ScreenLocker::KSldApp::self()->greeterClientConnection(); } ); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::unlocked, this, [this] () { m_screenLockerClientConnection = nullptr; } ); if (m_initFlags.testFlag(InitalizationFlag::LockScreen)) { ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); } emit initialized(); } void WaylandServer::initOutputs() { if (kwinApp()->platform()->handlesOutputs()) { return; } syncOutputsToWayland(); connect(screens(), &Screens::changed, this, [this] { // when screens change we need to sync this to Wayland. // Unfortunately we don't have much information and cannot properly match a KWin screen // to a Wayland screen. // Thus we just recreate all outputs and delete the old ones const auto outputs = m_display->outputs(); syncOutputsToWayland(); qDeleteAll(outputs); } ); } void WaylandServer::syncOutputsToWayland() { Screens *s = screens(); Q_ASSERT(s); for (int i = 0; i < s->count(); ++i) { OutputInterface *output = m_display->createOutput(m_display); output->setScale(s->scale(i)); const QRect &geo = s->geometry(i); output->setGlobalPosition(geo.topLeft()); output->setPhysicalSize(s->physicalSize(i).toSize()); output->addMode(geo.size()); output->create(); } } WaylandServer::SocketPairConnection WaylandServer::createConnection() { SocketPairConnection ret; int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return ret; } ret.connection = m_display->createClient(sx[0]); ret.fd = sx[1]; return ret; } int WaylandServer::createXWaylandConnection() { const auto socket = createConnection(); if (!socket.connection) { return -1; } m_xwayland.client = socket.connection; m_xwayland.destroyConnection = connect(m_xwayland.client, &KWayland::Server::ClientConnection::disconnected, this, [] { qFatal("Xwayland Connection died"); } ); return socket.fd; } void WaylandServer::destroyXWaylandConnection() { if (!m_xwayland.client) { return; } // first terminate the clipboard sync if (m_xclipbaordSync.process) { m_xclipbaordSync.process->terminate(); } disconnect(m_xwayland.destroyConnection); m_xwayland.client->destroy(); m_xwayland.client = nullptr; } int WaylandServer::createInputMethodConnection() { const auto socket = createConnection(); if (!socket.connection) { return -1; } m_inputMethodServerConnection = socket.connection; return socket.fd; } void WaylandServer::destroyInputMethodConnection() { if (!m_inputMethodServerConnection) { return; } m_inputMethodServerConnection->destroy(); m_inputMethodServerConnection = nullptr; } int WaylandServer::createXclipboardSyncConnection() { const auto socket = createConnection(); if (!socket.connection) { return -1; } m_xclipbaordSync.client = socket.connection; return socket.fd; } void WaylandServer::setupX11ClipboardSync() { if (m_xclipbaordSync.process) { return; } int socket = dup(createXclipboardSyncConnection()); if (socket == -1) { delete m_xclipbaordSync.client; m_xclipbaordSync.client = nullptr; return; } if (socket >= 0) { QProcessEnvironment environment = kwinApp()->processStartupEnvironment(); environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); environment.insert(QStringLiteral("DISPLAY"), QString::fromUtf8(qgetenv("DISPLAY"))); environment.remove("WAYLAND_DISPLAY"); m_xclipbaordSync.process = new Process(this); m_xclipbaordSync.process->setProcessChannelMode(QProcess::ForwardedErrorChannel); auto finishedSignal = static_cast(&QProcess::finished); connect(m_xclipbaordSync.process, finishedSignal, this, [this] { m_xclipbaordSync.process->deleteLater(); m_xclipbaordSync.process = nullptr; m_xclipbaordSync.ddi.clear(); m_xclipbaordSync.client->destroy(); m_xclipbaordSync.client = nullptr; // TODO: restart } ); m_xclipbaordSync.process->setProcessEnvironment(environment); m_xclipbaordSync.process->start(QStringLiteral(KWIN_XCLIPBOARD_SYNC_BIN)); } } void WaylandServer::createInternalConnection() { const auto socket = createConnection(); if (!socket.connection) { return; } m_internalConnection.server = socket.connection; using namespace KWayland::Client; m_internalConnection.client = new ConnectionThread(); m_internalConnection.client->setSocketFd(socket.fd); m_internalConnection.clientThread = new QThread; m_internalConnection.client->moveToThread(m_internalConnection.clientThread); m_internalConnection.clientThread->start(); connect(m_internalConnection.client, &ConnectionThread::connected, this, [this] { Registry *registry = new Registry(this); EventQueue *eventQueue = new EventQueue(this); eventQueue->setup(m_internalConnection.client); registry->setEventQueue(eventQueue); registry->create(m_internalConnection.client); m_internalConnection.registry = registry; connect(registry, &Registry::shmAnnounced, this, [this] (quint32 name, quint32 version) { m_internalConnection.shm = m_internalConnection.registry->createShmPool(name, version, this); } ); connect(registry, &Registry::interfacesAnnounced, this, [this] { m_internalConnection.interfacesAnnounced = true; } ); registry->setup(); } ); m_internalConnection.client->initConnection(); } void WaylandServer::removeClient(ShellClient *c) { m_clients.removeAll(c); m_internalClients.removeAll(c); emit shellClientRemoved(c); } void WaylandServer::dispatch() { if (!m_display) { return; } if (m_internalConnection.server) { m_internalConnection.server->flush(); } m_display->dispatchEvents(0); } static ShellClient *findClientInList(const QList &clients, quint32 id) { auto it = std::find_if(clients.begin(), clients.end(), [id] (ShellClient *c) { return c->windowId() == id; } ); if (it == clients.end()) { return nullptr; } return *it; } static ShellClient *findClientInList(const QList &clients, KWayland::Server::SurfaceInterface *surface) { auto it = std::find_if(clients.begin(), clients.end(), [surface] (ShellClient *c) { return c->surface() == surface; } ); if (it == clients.end()) { return nullptr; } return *it; } ShellClient *WaylandServer::findClient(quint32 id) const { if (id == 0) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, id)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, id)) { return c; } return nullptr; } ShellClient *WaylandServer::findClient(SurfaceInterface *surface) const { if (!surface) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, surface)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, surface)) { return c; } return nullptr; } AbstractClient *WaylandServer::findAbstractClient(SurfaceInterface *surface) const { return findClient(surface); } ShellClient *WaylandServer::findClient(QWindow *w) const { if (!w) { return nullptr; } auto it = std::find_if(m_internalClients.constBegin(), m_internalClients.constEnd(), [w] (const ShellClient *c) { return c->internalWindow() == w; } ); if (it != m_internalClients.constEnd()) { return *it; } return nullptr; } quint32 WaylandServer::createWindowId(SurfaceInterface *surface) { auto it = m_clientIds.constFind(surface->client()); quint16 clientId = 0; if (it != m_clientIds.constEnd()) { clientId = it.value(); } else { clientId = createClientId(surface->client()); } Q_ASSERT(clientId != 0); quint32 id = clientId; // TODO: this does not prevent that two surfaces of same client get same id id = (id << 16) | (surface->id() & 0xFFFF); if (findClient(id)) { qCWarning(KWIN_CORE) << "Invalid client windowId generated:" << id; return 0; } return id; } quint16 WaylandServer::createClientId(ClientConnection *c) { auto ids = m_clientIds.values().toSet(); quint16 id = 1; if (!ids.isEmpty()) { for (quint16 i = ids.count() + 1; i >= 1 ; i--) { if (!ids.contains(i)) { id = i; break; } } } Q_ASSERT(!ids.contains(id)); m_clientIds.insert(c, id); connect(c, &ClientConnection::disconnected, this, [this] (ClientConnection *c) { m_clientIds.remove(c); } ); return id; } bool WaylandServer::isScreenLocked() const { if (!hasScreenLockerIntegration()) { return false; } return ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::Locked || ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::AcquiringLock; } bool WaylandServer::hasScreenLockerIntegration() const { return !m_initFlags.testFlag(InitalizationFlag::NoLockScreenIntegration); } +void WaylandServer::simulateUserActivity() +{ + if (m_idle) { + m_idle->simulateUserActivity(); + } +} + } diff --git a/wayland_server.h b/wayland_server.h index d272d9ec2..03ba8c862 100644 --- a/wayland_server.h +++ b/wayland_server.h @@ -1,259 +1,267 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_WAYLAND_SERVER_H #define KWIN_WAYLAND_SERVER_H #include #include #include class QThread; class QProcess; class QWindow; namespace KWayland { namespace Client { class ConnectionThread; class Registry; class ShmPool; class Surface; } namespace Server { +class AppMenuManagerInterface; class ClientConnection; class CompositorInterface; class Display; class DataDeviceInterface; +class IdleInterface; class ShellInterface; class SeatInterface; class ServerSideDecorationManagerInterface; +class ServerSideDecorationPaletteManagerInterface; class SurfaceInterface; class OutputInterface; class PlasmaShellInterface; class PlasmaShellSurfaceInterface; class PlasmaWindowManagementInterface; class QtSurfaceExtensionInterface; class OutputManagementInterface; class OutputConfigurationInterface; class XdgShellInterface; class XdgForeignInterface; } } namespace KWin { class ShellClient; class AbstractClient; class Toplevel; class KWIN_EXPORT WaylandServer : public QObject { Q_OBJECT public: enum class InitalizationFlag { NoOptions = 0x0, LockScreen = 0x1, NoLockScreenIntegration = 0x2 }; Q_DECLARE_FLAGS(InitalizationFlags, InitalizationFlag) virtual ~WaylandServer(); bool init(const QByteArray &socketName = QByteArray(), InitalizationFlags flags = InitalizationFlag::NoOptions); void terminateClientConnections(); KWayland::Server::Display *display() { return m_display; } KWayland::Server::CompositorInterface *compositor() { return m_compositor; } KWayland::Server::SeatInterface *seat() { return m_seat; } KWayland::Server::ShellInterface *shell() { return m_shell; } KWayland::Server::PlasmaWindowManagementInterface *windowManagement() { return m_windowManagement; } KWayland::Server::ServerSideDecorationManagerInterface *decorationManager() const { return m_decorationManager; } QList clients() const { return m_clients; } QList internalClients() const { return m_internalClients; } void removeClient(ShellClient *c); ShellClient *findClient(quint32 id) const; ShellClient *findClient(KWayland::Server::SurfaceInterface *surface) const; AbstractClient *findAbstractClient(KWayland::Server::SurfaceInterface *surface) const; ShellClient *findClient(QWindow *w) const; /** * return a transient parent of a surface imported with the foreign protocol, if any */ KWayland::Server::SurfaceInterface *findForeignTransientForSurface(KWayland::Server::SurfaceInterface *surface); /** * @returns file descriptor for Xwayland to connect to. **/ int createXWaylandConnection(); void destroyXWaylandConnection(); /** * @returns file descriptor to the input method server's socket. **/ int createInputMethodConnection(); void destroyInputMethodConnection(); int createXclipboardSyncConnection(); /** * @returns true if screen is locked. **/ bool isScreenLocked() const; /** * @returns whether integration with KScreenLocker is available. **/ bool hasScreenLockerIntegration() const; void createInternalConnection(); void initWorkspace(); KWayland::Server::ClientConnection *xWaylandConnection() const { return m_xwayland.client; } KWayland::Server::ClientConnection *inputMethodConnection() const { return m_inputMethodServerConnection; } KWayland::Server::ClientConnection *internalConnection() const { return m_internalConnection.server; } KWayland::Server::ClientConnection *screenLockerClientConnection() const { return m_screenLockerClientConnection; } QPointer xclipboardSyncDataDevice() const { return m_xclipbaordSync.ddi; } KWayland::Client::ShmPool *internalShmPool() { return m_internalConnection.shm; } KWayland::Client::ConnectionThread *internalClientConection() { return m_internalConnection.client; } KWayland::Client::Registry *internalClientRegistry() { return m_internalConnection.registry; } void dispatch(); quint32 createWindowId(KWayland::Server::SurfaceInterface *surface); /** * Struct containing information for a created Wayland connection through a * socketpair. **/ struct SocketPairConnection { /** * ServerSide Connection **/ KWayland::Server::ClientConnection *connection = nullptr; /** * client-side file descriptor for the socket **/ int fd = -1; }; /** * Creates a Wayland connection using a socket pair. **/ SocketPairConnection createConnection(); + void simulateUserActivity(); + Q_SIGNALS: void shellClientAdded(KWin::ShellClient*); void shellClientRemoved(KWin::ShellClient*); void terminatingInternalClientConnection(); void initialized(); void foreignTransientChanged(KWayland::Server::SurfaceInterface *child); private: void setupX11ClipboardSync(); void shellClientShown(Toplevel *t); void initOutputs(); void syncOutputsToWayland(); quint16 createClientId(KWayland::Server::ClientConnection *c); void destroyInternalConnection(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); template void createSurface(T *surface); void initScreenLocker(); KWayland::Server::Display *m_display = nullptr; KWayland::Server::CompositorInterface *m_compositor = nullptr; KWayland::Server::SeatInterface *m_seat = nullptr; KWayland::Server::ShellInterface *m_shell = nullptr; KWayland::Server::XdgShellInterface *m_xdgShell = nullptr; KWayland::Server::XdgShellInterface *m_xdgShell6 = nullptr; KWayland::Server::PlasmaShellInterface *m_plasmaShell = nullptr; KWayland::Server::PlasmaWindowManagementInterface *m_windowManagement = nullptr; KWayland::Server::QtSurfaceExtensionInterface *m_qtExtendedSurface = nullptr; KWayland::Server::ServerSideDecorationManagerInterface *m_decorationManager = nullptr; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; + KWayland::Server::AppMenuManagerInterface *m_appMenuManager = nullptr; + KWayland::Server::ServerSideDecorationPaletteManagerInterface *m_paletteManager = nullptr; + KWayland::Server::IdleInterface *m_idle = nullptr; struct { KWayland::Server::ClientConnection *client = nullptr; QMetaObject::Connection destroyConnection; } m_xwayland; KWayland::Server::ClientConnection *m_inputMethodServerConnection = nullptr; KWayland::Server::ClientConnection *m_screenLockerClientConnection = nullptr; struct { KWayland::Server::ClientConnection *server = nullptr; KWayland::Client::ConnectionThread *client = nullptr; QThread *clientThread = nullptr; KWayland::Client::Registry *registry = nullptr; KWayland::Client::ShmPool *shm = nullptr; bool interfacesAnnounced = false; } m_internalConnection; struct { QProcess *process = nullptr; KWayland::Server::ClientConnection *client = nullptr; QPointer ddi; } m_xclipbaordSync; KWayland::Server::XdgForeignInterface *m_XdgForeign = nullptr; QList m_clients; QList m_internalClients; QHash m_clientIds; InitalizationFlags m_initFlags; QVector m_plasmaShellSurfaces; KWIN_SINGLETON(WaylandServer) }; inline WaylandServer *waylandServer() { return WaylandServer::self(); } } // namespace KWin #endif diff --git a/workspace.cpp b/workspace.cpp index 7bb57a1bc..02ccf5aaa 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,1750 +1,1759 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "workspace.h" // kwin libs #include // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "appmenu.h" #include "atoms.h" #include "client.h" #include "composite.h" #include "cursor.h" #include "dbusinterface.h" #include "deleted.h" #include "effects.h" #include "focuschain.h" #include "group.h" #include "input.h" #include "logind.h" #include "moving_client_x11_filter.h" #include "killwindow.h" #include "netinfo.h" #include "outline.h" #include "placement.h" #include "rules.h" #include "screenedge.h" #include "screens.h" +#include "platform.h" #include "scripting/scripting.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "unmanaged.h" #include "useractions.h" #include "virtualdesktops.h" #include "shell_client.h" #include "was_user_interaction_x11_filter.h" #include "wayland_server.h" #include "xcbutils.h" #include "main.h" #include "decorations/decorationbridge.h" // KDE #include #include #include #include // Qt #include namespace KWin { extern int screen_number; extern bool is_multihead; ColorMapper::ColorMapper(QObject *parent) : QObject(parent) , m_default(defaultScreen()->default_colormap) , m_installed(defaultScreen()->default_colormap) { } ColorMapper::~ColorMapper() { } void ColorMapper::update() { xcb_colormap_t cmap = m_default; if (Client *c = dynamic_cast(Workspace::self()->activeClient())) { if (c->colormap() != XCB_COLORMAP_NONE) { cmap = c->colormap(); } } if (cmap != m_installed) { xcb_install_colormap(connection(), cmap); m_installed = cmap; } } Workspace* Workspace::_self = 0; Workspace::Workspace(const QString &sessionKey) : QObject(0) , m_compositor(NULL) // Unsorted , active_popup(NULL) , active_popup_client(NULL) , m_initialDesktop(1) , active_client(0) , last_active_client(0) , most_recently_raised(0) , movingClient(0) , delayfocus_client(0) , force_restacking(false) , showing_desktop(false) , was_user_interaction(false) , session_saving(false) , block_focus(0) , m_userActionsMenu(new UserActionsMenu(this)) , client_keys_dialog(NULL) , client_keys_client(NULL) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) , startup(0) , set_active_client_recursion(0) , block_stacking_updates(0) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); ApplicationMenu::create(this); _self = this; #ifdef KWIN_BUILD_ACTIVITIES Activities *activities = nullptr; if (kwinApp()->usesKActivities()) { activities = Activities::create(this); } if (activities) { connect(activities, SIGNAL(currentChanged(QString)), SLOT(updateCurrentActivity(QString))); } #endif // PluginMgr needs access to the config file, so we need to wait for it for finishing reparseConfigFuture.waitForFinished(); options->loadConfig(); options->loadCompositingConfig(false); delayFocusTimer = 0; if (!sessionKey.isEmpty()) loadSessionInfo(sessionKey); connect(qApp, &QGuiApplication::commitDataRequest, this, &Workspace::commitData); connect(qApp, &QGuiApplication::saveStateRequest, this, &Workspace::saveState); RuleBook::create(this)->load(); ScreenEdges::create(this); // VirtualDesktopManager needs to be created prior to init shortcuts // and prior to TabBox, due to TabBox connecting to signals // actual initialization happens in init() VirtualDesktopManager::create(this); #ifdef KWIN_BUILD_TABBOX // need to create the tabbox before compositing scene is setup TabBox::TabBox::create(this); #endif if (Compositor::self()) { m_compositor = Compositor::self(); } else { m_compositor = Compositor::create(this); } connect(this, &Workspace::currentDesktopChanged, m_compositor, &Compositor::addRepaintFull); connect(m_compositor, &QObject::destroyed, this, [this] { m_compositor = nullptr; }); auto decorationBridge = Decoration::DecorationBridge::create(this); decorationBridge->init(); connect(this, &Workspace::configChanged, decorationBridge, &Decoration::DecorationBridge::reconfigure); new DBusInterface(this); Outline::create(this); initShortcuts(); init(); } void Workspace::init() { KSharedConfigPtr config = kwinApp()->config(); kwinApp()->createScreens(); Screens *screens = Screens::self(); // get screen support connect(screens, SIGNAL(changed()), SLOT(desktopResized())); screens->setConfig(config); screens->reconfigure(); connect(options, SIGNAL(configChanged()), screens, SLOT(reconfigure())); ScreenEdges *screenEdges = ScreenEdges::self(); screenEdges->setConfig(config); screenEdges->init(); connect(options, SIGNAL(configChanged()), screenEdges, SLOT(reconfigure())); connect(VirtualDesktopManager::self(), SIGNAL(layoutChanged(int,int)), screenEdges, SLOT(updateLayout())); connect(this, &Workspace::clientActivated, screenEdges, &ScreenEdges::checkBlocking); FocusChain *focusChain = FocusChain::create(this); connect(this, &Workspace::clientRemoved, focusChain, &FocusChain::remove); connect(this, &Workspace::clientActivated, focusChain, &FocusChain::setActiveClient); connect(VirtualDesktopManager::self(), SIGNAL(countChanged(uint,uint)), focusChain, SLOT(resize(uint,uint))); connect(VirtualDesktopManager::self(), SIGNAL(currentChanged(uint,uint)), focusChain, SLOT(setCurrentDesktop(uint,uint))); connect(options, SIGNAL(separateScreenFocusChanged(bool)), focusChain, SLOT(setSeparateScreenFocus(bool))); focusChain->setSeparateScreenFocus(options->isSeparateScreenFocus()); // create VirtualDesktopManager and perform dependency injection VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(vds, SIGNAL(desktopsRemoved(uint)), SLOT(moveClientsFromRemovedDesktops())); connect(vds, SIGNAL(countChanged(uint,uint)), SLOT(slotDesktopCountChanged(uint,uint))); connect(vds, SIGNAL(currentChanged(uint,uint)), SLOT(slotCurrentDesktopChanged(uint,uint))); vds->setNavigationWrappingAround(options->isRollOverDesktops()); connect(options, SIGNAL(rollOverDesktopsChanged(bool)), vds, SLOT(setNavigationWrappingAround(bool))); vds->setConfig(config); // Now we know how many desktops we'll have, thus we initialize the positioning object Placement::create(this); // positioning object needs to be created before the virtual desktops are loaded. vds->load(); vds->updateLayout(); if (!VirtualDesktopManager::self()->setCurrent(m_initialDesktop)) VirtualDesktopManager::self()->setCurrent(1); reconfigureTimer.setSingleShot(true); updateToolWindowsTimer.setSingleShot(true); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows())); // TODO: do we really need to reconfigure everything when fonts change? // maybe just reconfigure the decorations? Move this into libkdecoration? QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KDEPlatformTheme"), QStringLiteral("org.kde.KDEPlatformTheme"), QStringLiteral("refreshFonts"), this, SLOT(reconfigure())); active_client = NULL; initWithX11(); Scripting::create(this); if (auto w = waylandServer()) { connect(w, &WaylandServer::shellClientAdded, this, [this] (ShellClient *c) { setupClientConnections(c); c->updateDecoration(false); updateClientLayer(c); if (!c->isInternal()) { QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop()); bool placementDone = false; if (c->isInitialPositionSet()) { placementDone = true; } if (c->isFullScreen()) { placementDone = true; } if (!placementDone) { c->placeIn(area); } m_allClients.append(c); if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order } markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); if (c->wantsInput() && !c->isMinimized()) { activateClient(c); } connect(c, &ShellClient::windowShown, this, [this, c] { updateClientLayer(c); // TODO: when else should we send the client through placement? if (c->hasTransientPlacementHint()) { QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop()); c->placeIn(area); } markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); if (c->wantsInput()) { activateClient(c); } } ); connect(c, &ShellClient::windowHidden, this, [this] { markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); } ); } ); connect(w, &WaylandServer::shellClientRemoved, this, [this] (ShellClient *c) { m_allClients.removeAll(c); if (c == most_recently_raised) { most_recently_raised = nullptr; } if (c == delayfocus_client) { cancelDelayFocus(); } if (c == last_active_client) { last_active_client = nullptr; } if (client_keys_client == c) { setupWindowShortcutDone(false); } if (!c->shortcut().isEmpty()) { c->setShortcut(QString()); // Remove from client_keys } clientHidden(c); emit clientRemoved(c); markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); } ); } // SELI TODO: This won't work with unreasonable focus policies, // and maybe in rare cases also if the selected client doesn't // want focus workspaceInit = false; // broadcast that Workspace is ready, but first process all events. QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection); // TODO: ungrabXServer() } void Workspace::initWithX11() { if (!kwinApp()->x11Connection()) { connect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initWithX11, Qt::UniqueConnection); return; } disconnect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initWithX11); atoms->retrieveHelpers(); // first initialize the extensions Xcb::Extensions::self(); ColorMapper *colormaps = new ColorMapper(this); connect(this, &Workspace::clientActivated, colormaps, &ColorMapper::update); // Call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this); // Select windowmanager privileges selectWmInputEventMask(); // Compatibility int32_t data = 1; xcb_change_property(connection(), XCB_PROP_MODE_APPEND, rootWindow(), atoms->kwin_running, atoms->kwin_running, 32, 1, &data); if (kwinApp()->operationMode() == Application::OperationModeX11) { m_wasUserInteractionFilter.reset(new WasUserInteractionX11Filter); m_movingClientFilter.reset(new MovingClientX11Filter); } updateXTime(); // Needed for proper initialization of user_time in Client ctor const uint32_t nullFocusValues[] = {true}; m_nullFocus.reset(new Xcb::Window(QRect(-1, -1, 1, 1), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, nullFocusValues)); m_nullFocus->map(); RootInfo *rootInfo = RootInfo::create(); const auto vds = VirtualDesktopManager::self(); vds->setRootInfo(rootInfo); // load again to sync to RootInfo, see BUG 385260 vds->load(); vds->updateRootInfo(); // TODO: only in X11 mode // Extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info(connection(), NET::ActiveWindow | NET::CurrentDesktop); if (!qApp->isSessionRestored()) m_initialDesktop = client_info.currentDesktop(); if (!VirtualDesktopManager::self()->setCurrent(m_initialDesktop)) VirtualDesktopManager::self()->setCurrent(1); // TODO: better value rootInfo->setActiveWindow(None); focusToNull(); if (!qApp->isSessionRestored()) ++block_focus; // Because it will be set below { // Begin updates blocker block StackingUpdatesBlocker blocker(this); Xcb::Tree tree(rootWindow()); xcb_window_t *wins = xcb_query_tree_children(tree.data()); QVector windowAttributes(tree->children_len); QVector windowGeometries(tree->children_len); // Request the attributes and geometries of all toplevel windows for (int i = 0; i < tree->children_len; i++) { windowAttributes[i] = Xcb::WindowAttributes(wins[i]); windowGeometries[i] = Xcb::WindowGeometry(wins[i]); } // Get the replies for (int i = 0; i < tree->children_len; i++) { Xcb::WindowAttributes attr(windowAttributes.at(i)); if (attr.isNull()) { continue; } if (attr->override_redirect) { if (attr->map_state == XCB_MAP_STATE_VIEWABLE && attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY) // ### This will request the attributes again createUnmanaged(wins[i]); } else if (attr->map_state != XCB_MAP_STATE_UNMAPPED) { if (Application::wasCrash()) { fixPositionAfterCrash(wins[i], windowGeometries.at(i).data()); } // ### This will request the attributes again createClient(wins[i], true); } } // Propagate clients, will really happen at the end of the updates blocker block updateStackingOrder(true); saveOldScreenSizes(); updateClientArea(); // NETWM spec says we have to set it to (0,0) if we don't support it NETPoint* viewports = new NETPoint[VirtualDesktopManager::self()->count()]; rootInfo->setDesktopViewport(VirtualDesktopManager::self()->count(), *viewports); delete[] viewports; QRect geom; for (int i = 0; i < screens()->count(); i++) { geom |= screens()->geometry(i); } NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry(desktop_geometry); setShowingDesktop(false); } // End updates blocker block // TODO: only on X11? AbstractClient* new_active_client = nullptr; if (!qApp->isSessionRestored()) { --block_focus; new_active_client = findClient(Predicate::WindowMatch, client_info.activeWindow()); } if (new_active_client == NULL && activeClient() == NULL && should_get_focus.count() == 0) { // No client activated in manage() if (new_active_client == NULL) new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->current(), -1); if (new_active_client == NULL && !desktops.isEmpty()) new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); } if (new_active_client != NULL) activateClient(new_active_client); } Workspace::~Workspace() { blockStackingUpdates(true); // TODO: grabXServer(); // Use stacking_order, so that kwin --replace keeps stacking order const ToplevelList stack = stacking_order; // "mutex" the stackingorder, since anything trying to access it from now on will find // many dangeling pointers and crash stacking_order.clear(); for (ToplevelList::const_iterator it = stack.constBegin(), end = stack.constEnd(); it != end; ++it) { Client *c = qobject_cast(const_cast(*it)); if (!c) { continue; } // Only release the window c->releaseWindow(true); // No removeClient() is called, it does more than just removing. // However, remove from some lists to e.g. prevent performTransiencyCheck() // from crashing. clients.removeAll(c); m_allClients.removeAll(c); desktops.removeAll(c); } Client::cleanupX11(); for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(ReleaseReason::KWinShutsDown); if (auto c = kwinApp()->x11Connection()) { xcb_delete_property(c, kwinApp()->x11RootWindow(), atoms->kwin_running); } for (auto it = deleted.begin(); it != deleted.end();) { emit deletedRemoved(*it); it = deleted.erase(it); } delete RuleBook::self(); kwinApp()->config()->sync(); RootInfo::destroy(); delete startup; delete Placement::self(); delete client_keys_dialog; foreach (SessionInfo * s, session) delete s; // TODO: ungrabXServer(); Xcb::Extensions::destroy(); _self = 0; } void Workspace::setupClientConnections(AbstractClient *c) { connect(c, &Toplevel::needsRepaint, m_compositor, &Compositor::scheduleRepaint); connect(c, &AbstractClient::desktopPresenceChanged, this, &Workspace::desktopPresenceChanged); connect(c, &AbstractClient::minimizedChanged, this, std::bind(&Workspace::clientMinimizedChanged, this, c)); } Client* Workspace::createClient(xcb_window_t w, bool is_mapped) { StackingUpdatesBlocker blocker(this); Client* c = new Client(); setupClientConnections(c); connect(c, SIGNAL(blockingCompositingChanged(KWin::Client*)), m_compositor, SLOT(updateCompositeBlocking(KWin::Client*))); connect(c, SIGNAL(clientFullScreenSet(KWin::Client*,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking())); if (!c->manage(w, is_mapped)) { Client::deleteClient(c); return NULL; } addClient(c); return c; } Unmanaged* Workspace::createUnmanaged(xcb_window_t w) { if (m_compositor && m_compositor->checkForOverlayWindow(w)) return NULL; Unmanaged* c = new Unmanaged(); if (!c->track(w)) { Unmanaged::deleteUnmanaged(c); return NULL; } connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); addUnmanaged(c); emit unmanagedAdded(c); return c; } void Workspace::addClient(Client* c) { Group* grp = findGroup(c->window()); emit clientAdded(c); if (grp != NULL) grp->gotLeader(c); if (c->isDesktop()) { desktops.append(c); if (active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) requestFocus(c); // TODO: Make sure desktop is active after startup if there's no other window active } else { FocusChain::self()->update(c, FocusChain::Update); clients.append(c); m_allClients.append(c); } if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order markXStackingOrderAsDirty(); updateClientArea(); // This cannot be in manage(), because the client got added only now updateClientLayer(c); if (c->isDesktop()) { raiseClient(c); // If there's no active client, make this desktop the active one if (activeClient() == NULL && should_get_focus.count() == 0) activateClient(findDesktop(true, VirtualDesktopManager::self()->current())); } c->checkActiveModal(); checkTransients(c->window()); // SELI TODO: Does this really belong here? updateStackingOrder(true); // Propagate new client if (c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows(true); #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isDisplayed()) TabBox::TabBox::self()->reset(true); #endif } void Workspace::addUnmanaged(Unmanaged* c) { unmanaged.append(c); markXStackingOrderAsDirty(); } /** * Destroys the client \a c */ void Workspace::removeClient(Client* c) { emit clientRemoved(c); if (c == active_popup_client) closeActivePopup(); if (m_userActionsMenu->isMenuClient(c)) { m_userActionsMenu->close(); } c->untab(QRect(), true); if (client_keys_client == c) setupWindowShortcutDone(false); if (!c->shortcut().isEmpty()) { c->setShortcut(QString()); // Remove from client_keys clientShortcutUpdated(c); // Needed, since this is otherwise delayed by setShortcut() and wouldn't run } #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox->isDisplayed() && tabBox->currentClient() == c) tabBox->nextPrev(true); #endif Q_ASSERT(clients.contains(c) || desktops.contains(c)); // TODO: if marked client is removed, notify the marked list clients.removeAll(c); m_allClients.removeAll(c); desktops.removeAll(c); markXStackingOrderAsDirty(); attention_chain.removeAll(c); Group* group = findGroup(c->window()); if (group != NULL) group->lostLeader(); if (c == most_recently_raised) most_recently_raised = 0; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) last_active_client = 0; if (c == delayfocus_client) cancelDelayFocus(); updateStackingOrder(true); #ifdef KWIN_BUILD_TABBOX if (tabBox->isDisplayed()) tabBox->reset(true); #endif updateClientArea(); } void Workspace::removeUnmanaged(Unmanaged* c) { assert(unmanaged.contains(c)); unmanaged.removeAll(c); emit unmanagedRemoved(c); markXStackingOrderAsDirty(); } void Workspace::addDeleted(Deleted* c, Toplevel *orig) { assert(!deleted.contains(c)); deleted.append(c); const int unconstraintedIndex = unconstrained_stacking_order.indexOf(orig); if (unconstraintedIndex != -1) { unconstrained_stacking_order.replace(unconstraintedIndex, c); } else { unconstrained_stacking_order.append(c); } const int index = stacking_order.indexOf(orig); if (index != -1) { stacking_order.replace(index, c); } else { stacking_order.append(c); } markXStackingOrderAsDirty(); connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); } void Workspace::removeDeleted(Deleted* c) { assert(deleted.contains(c)); emit deletedRemoved(c); deleted.removeAll(c); unconstrained_stacking_order.removeAll(c); stacking_order.removeAll(c); markXStackingOrderAsDirty(); if (c->wasClient() && m_compositor) { m_compositor->updateCompositeBlocking(); } } void Workspace::updateToolWindows(bool also_hide) { // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?) if (!options->isHideUtilityWindowsForInactive()) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) if (!(*it)->tabGroup() || (*it)->tabGroup()->current() == *it) (*it)->hideClient(false); return; } const Group* group = NULL; const Client* client = dynamic_cast(active_client); // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow // will be shown; if a group transient is group, all tools in the group will be shown while (client != NULL) { if (!client->isTransient()) break; if (client->groupTransient()) { group = client->group(); break; } client = dynamic_cast(client->transientFor()); } // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0, // I.e. if it's not up to date // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet? ClientList to_show, to_hide; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (c->isUtility() || c->isMenu() || c->isToolbar()) { bool show = true; if (!c->isTransient()) { if (c->group()->members().count() == 1) // Has its own group, keep always visible show = true; else if (client != NULL && c->group() == client->group()) show = true; else show = false; } else { if (group != NULL && c->group() == group) show = true; else if (client != NULL && client->hasTransient(c, true)) show = true; else show = false; } if (!show && also_hide) { const auto mainclients = c->mainClients(); // Don't hide utility windows which are standalone(?) or // have e.g. kicker as mainwindow if (mainclients.isEmpty()) show = true; for (auto it2 = mainclients.constBegin(); it2 != mainclients.constEnd(); ++it2) { if ((*it2)->isSpecialWindow()) show = true; } if (!show) to_hide.append(c); } if (show) to_show.append(c); } } // First show new ones, then hide for (int i = to_show.size() - 1; i >= 0; --i) // From topmost // TODO: Since this is in stacking order, the order of taskbar entries changes :( to_show.at(i)->hideClient(false); if (also_hide) { for (ClientList::ConstIterator it = to_hide.constBegin(); it != to_hide.constEnd(); ++it) // From bottommost (*it)->hideClient(true); updateToolWindowsTimer.stop(); } else // setActiveClient() is after called with NULL client, quickly followed // by setting a new client, which would result in flickering resetUpdateToolWindowsTimer(); } void Workspace::resetUpdateToolWindowsTimer() { updateToolWindowsTimer.start(200); } void Workspace::slotUpdateToolWindows() { updateToolWindows(true); } void Workspace::slotReloadConfig() { reconfigure(); } void Workspace::reconfigure() { reconfigureTimer.start(200); } /** * Reread settings */ void Workspace::slotReconfigure() { qCDebug(KWIN_CORE) << "Workspace::slotReconfigure()"; reconfigureTimer.stop(); bool borderlessMaximizedWindows = options->borderlessMaximizedWindows(); kwinApp()->config()->reparseConfiguration(); options->updateSettings(); emit configChanged(); m_userActionsMenu->discard(); updateToolWindows(true); RuleBook::self()->load(); for (auto it = m_allClients.begin(); it != m_allClients.end(); ++it) { (*it)->setupWindowRules(true); (*it)->applyWindowRules(); RuleBook::self()->discardUsed(*it, false); } if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() && !options->borderlessMaximizedWindows()) { // in case borderless maximized windows option changed and new option // is to have borders, we need to unset the borders for all maximized windows - for (ClientList::Iterator it = clients.begin(); - it != clients.end(); + for (auto it = m_allClients.begin(); + it != m_allClients.end(); ++it) { if ((*it)->maximizeMode() == MaximizeFull) (*it)->checkNoBorder(); } } } void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop) { closeActivePopup(); ++block_focus; StackingUpdatesBlocker blocker(this); updateClientVisibilityOnDesktopChange(newDesktop); // Restore the focus on this desktop --block_focus; activateClientOnNewDesktop(newDesktop); emit currentDesktopChanged(oldDesktop, movingClient); } void Workspace::updateClientVisibilityOnDesktopChange(uint newDesktop) { for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) { (c)->updateVisibility(); } } // Now propagate the change, after hiding, before showing if (rootInfo()) { rootInfo()->setCurrentDesktop(VirtualDesktopManager::self()->current()); } if (movingClient && !movingClient->isOnDesktop(newDesktop)) { movingClient->setDesktop(newDesktop); } for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnDesktop(newDesktop) && c->isOnCurrentActivity()) c->updateVisibility(); } if (showingDesktop()) // Do this only after desktop change to avoid flicker setShowingDesktop(false); } void Workspace::activateClientOnNewDesktop(uint desktop) { AbstractClient* c = NULL; if (options->focusPolicyIsReasonable()) { c = findClientToActivateOnDesktop(desktop); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, desktop); if (c != active_client) setActiveClient(NULL); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, desktop)); else focusToNull(); } AbstractClient *Workspace::findClientToActivateOnDesktop(uint desktop) { if (movingClient != NULL && active_client == movingClient && FocusChain::self()->contains(active_client, desktop) && active_client->isShown(true) && active_client->isOnCurrentDesktop()) { // A requestFocus call will fail, as the client is already active return active_client; } // from actiavtion.cpp if (options->isNextFocusPrefersMouse()) { ToplevelList::const_iterator it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { Client *client = qobject_cast(*(--it)); if (!client) { continue; } if (!(client->isShown(false) && client->isOnDesktop(desktop) && client->isOnCurrentActivity() && client->isOnActiveScreen())) continue; if (client->geometry().contains(Cursor::pos())) { if (!client->isDesktop()) return client; break; // unconditional break - we do not pass the focus to some client below an unusable one } } } return FocusChain::self()->getForActivation(desktop); } /** * Updates the current activity when it changes * do *not* call this directly; it does not set the activity. * * Shows/Hides windows according to the stacking order */ void Workspace::updateCurrentActivity(const QString &new_activity) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } //closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date StackingUpdatesBlocker blocker(this); // Optimized Desktop switching: unmapping done from back to front // mapping done from front to back => less exposure events //Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop)); for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) { c->updateVisibility(); } } // Now propagate the change, after hiding, before showing //rootInfo->setCurrentDesktop( currentDesktop() ); /* TODO someday enable dragging windows to other activities if ( movingClient && !movingClient->isOnDesktop( new_desktop )) { movingClient->setDesktop( new_desktop ); */ for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnActivity(new_activity)) c->updateVisibility(); } //FIXME not sure if I should do this either if (showingDesktop()) // Do this only after desktop change to avoid flicker setShowingDesktop(false); // Restore the focus on this desktop --block_focus; AbstractClient* c = 0; //FIXME below here is a lot of focuschain stuff, probably all wrong now if (options->focusPolicyIsReasonable()) { // Search in focus chain c = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current()); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, VirtualDesktopManager::self()->current()); if (c != active_client) setActiveClient(NULL); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, VirtualDesktopManager::self()->current())); else focusToNull(); // Not for the very first time, only if something changed and there are more than 1 desktops //if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) // static_cast( effects )->desktopChanged( old_desktop ); if (compositing() && m_compositor) m_compositor->addRepaintFull(); #else Q_UNUSED(new_activity) #endif } void Workspace::moveClientsFromRemovedDesktops() { for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) { if (!(*it)->isOnAllDesktops() && (*it)->desktop() > static_cast(VirtualDesktopManager::self()->count())) sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true); } } void Workspace::slotDesktopCountChanged(uint previousCount, uint newCount) { Q_UNUSED(previousCount) Placement::self()->reinitCascading(0); resetClientAreas(newCount); } void Workspace::resetClientAreas(uint desktopCount) { // Make it +1, so that it can be accessed as [1..numberofdesktops] workarea.clear(); workarea.resize(desktopCount + 1); restrictedmovearea.clear(); restrictedmovearea.resize(desktopCount + 1); screenarea.clear(); updateClientArea(true); } void Workspace::selectWmInputEventMask() { uint32_t presentMask = 0; Xcb::WindowAttributes attr(rootWindow()); if (!attr.isNull()) { presentMask = attr->your_event_mask; } Xcb::selectInput(rootWindow(), presentMask | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_FOCUS_CHANGE | // For NotifyDetailNone XCB_EVENT_MASK_EXPOSURE ); } /** * Sends client \a c to desktop \a desk. * * Takes care of transients as well. */ void Workspace::sendClientToDesktop(AbstractClient* c, int desk, bool dont_activate) { if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast(VirtualDesktopManager::self()->count())) return; int old_desktop = c->desktop(); bool was_on_desktop = c->isOnDesktop(desk) || c->isOnAllDesktops(); c->setDesktop(desk); if (c->desktop() != desk) // No change or desktop forced return; desk = c->desktop(); // Client did range checking if (c->isOnDesktop(VirtualDesktopManager::self()->current())) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_desktop && // for stickyness changes !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); c->checkWorkspacePosition( QRect(), old_desktop ); if (Client *client = dynamic_cast(c)) { // TODO: adjust transients for non-X11 auto transients_stacking_order = ensureStackingOrder(client->transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToDesktop(*it, desk, dont_activate); } updateClientArea(); } /** * checks whether the X Window with the input focus is on our X11 screen * if the window cannot be determined or inspected, resturn depends on whether there's actually * more than one screen * * this is NOT in any way related to XRandR multiscreen * */ extern bool is_multihead; // main.cpp bool Workspace::isOnCurrentHead() { if (!is_multihead) { return true; } Xcb::CurrentInput currentInput; if (currentInput.window() == XCB_WINDOW_NONE) { return !is_multihead; } Xcb::WindowGeometry geometry(currentInput.window()); if (geometry.isNull()) { // should not happen return !is_multihead; } return rootWindow() == geometry->root; } void Workspace::sendClientToScreen(AbstractClient* c, int screen) { c->sendToScreen(screen); } void Workspace::sendPingToWindow(xcb_window_t window, xcb_timestamp_t timestamp) { if (rootInfo()) { rootInfo()->sendPing(window, timestamp); } } /** * Delayed focus functions */ void Workspace::delayFocus() { requestFocus(delayfocus_client); cancelDelayFocus(); } void Workspace::requestDelayFocus(AbstractClient* c) { delayfocus_client = c; delete delayFocusTimer; delayFocusTimer = new QTimer(this); connect(delayFocusTimer, SIGNAL(timeout()), this, SLOT(delayFocus())); delayFocusTimer->setSingleShot(true); delayFocusTimer->start(options->delayFocusInterval()); } void Workspace::cancelDelayFocus() { delete delayFocusTimer; delayFocusTimer = 0; } bool Workspace::checkStartupNotification(xcb_window_t w, KStartupInfoId &id, KStartupInfoData &data) { return startup->checkStartup(w, id, data) == KStartupInfo::Match; } /** * Puts the focus on a dummy window * Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { if (m_nullFocus) { m_nullFocus->focus(); } } void Workspace::setShowingDesktop(bool showing) { const bool changed = showing != showing_desktop; if (rootInfo() && changed) { rootInfo()->setShowingDesktop(showing); } showing_desktop = showing; AbstractClient *topDesk = nullptr; { // for the blocker RAII StackingUpdatesBlocker blocker(this); // updateLayer & lowerClient would invalidate stacking_order for (int i = stacking_order.count() - 1; i > -1; --i) { AbstractClient *c = qobject_cast(stacking_order.at(i)); if (c && c->isOnCurrentDesktop()) { if (c->isDock()) { c->updateLayer(); } else if (c->isDesktop() && c->isShown(true)) { c->updateLayer(); lowerClient(c); if (!topDesk) topDesk = c; if (Client *client = qobject_cast(c)) { foreach (Client *cm, client->group()->members()) { cm->updateLayer(); } } } } } } // ~StackingUpdatesBlocker if (showing_desktop && topDesk) { requestFocus(topDesk); } else if (!showing_desktop && changed) { const auto client = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current()); if (client) { activateClient(client); } } if (changed) emit showingDesktopChanged(showing); } void Workspace::disableGlobalShortcutsForClient(bool disable) { if (global_shortcuts_disabled_for_client == disable) return; QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/kglobalaccel"), QStringLiteral("org.kde.KGlobalAccel"), QStringLiteral("blockGlobalShortcuts")); message.setArguments(QList() << disable); QDBusConnection::sessionBus().asyncCall(message); global_shortcuts_disabled_for_client = disable; // Update also Alt+LMB actions etc. for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateMouseGrab(); } QString Workspace::supportInformation() const { QString support; const QString yes = QStringLiteral("yes\n"); const QString no = QStringLiteral("no\n"); support.append(ki18nc("Introductory text shown in the support information.", "KWin Support Information:\n" "The following information should be used when requesting support on e.g. http://forum.kde.org.\n" "It provides information about the currently running instance, which options are used,\n" "what OpenGL driver and which effects are running.\n" "Please post the information provided underneath this introductory text to a paste bin service\n" "like http://paste.kde.org instead of pasting into support threads.\n").toString()); support.append(QStringLiteral("\n==========================\n\n")); // all following strings are intended for support. They need to be pasted to e.g forums.kde.org // it is expected that the support will happen in English language or that the people providing // help understand English. Because of that all texts are not translated support.append(QStringLiteral("Version\n")); support.append(QStringLiteral("=======\n")); support.append(QStringLiteral("KWin version: ")); support.append(QStringLiteral(KWIN_VERSION_STRING)); support.append(QStringLiteral("\n")); support.append(QStringLiteral("Qt Version: ")); support.append(QString::fromUtf8(qVersion())); support.append(QStringLiteral("\n")); support.append(QStringLiteral("Qt compile version: %1\n").arg(QStringLiteral(QT_VERSION_STR))); support.append(QStringLiteral("XCB compile version: %1\n\n").arg(QStringLiteral(XCB_VERSION_STRING))); support.append(QStringLiteral("Operation Mode: ")); switch (kwinApp()->operationMode()) { case Application::OperationModeX11: support.append(QStringLiteral("X11 only")); break; case Application::OperationModeWaylandOnly: support.append(QStringLiteral("Wayland Only")); break; case Application::OperationModeXwayland: support.append(QStringLiteral("Xwayland")); break; } support.append(QStringLiteral("\n\n")); support.append(QStringLiteral("Build Options\n")); support.append(QStringLiteral("=============\n")); support.append(QStringLiteral("KWIN_BUILD_DECORATIONS: ")); #ifdef KWIN_BUILD_DECORATIONS support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("KWIN_BUILD_TABBOX: ")); #ifdef KWIN_BUILD_TABBOX support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("KWIN_BUILD_ACTIVITIES: ")); #ifdef KWIN_BUILD_ACTIVITIES support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_INPUT: ")); #if HAVE_INPUT support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_DRM: ")); #if HAVE_DRM support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_GBM: ")); #if HAVE_GBM support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_X11_XCB: ")); #if HAVE_X11_XCB support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_EPOXY_GLX: ")); #if HAVE_EPOXY_GLX support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_WAYLAND_EGL: ")); #if HAVE_WAYLAND_EGL support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("\n")); - support.append(QStringLiteral("X11\n")); - support.append(QStringLiteral("===\n")); - auto x11setup = xcb_get_setup(connection()); - support.append(QStringLiteral("Vendor: %1\n").arg(QString::fromUtf8(QByteArray::fromRawData(xcb_setup_vendor(x11setup), xcb_setup_vendor_length(x11setup))))); - support.append(QStringLiteral("Vendor Release: %1\n").arg(x11setup->release_number)); - support.append(QStringLiteral("Protocol Version/Revision: %1/%2\n").arg(x11setup->protocol_major_version).arg(x11setup->protocol_minor_version)); - const auto extensions = Xcb::Extensions::self()->extensions(); - for (const auto &e : extensions) { - support.append(QStringLiteral("%1: %2; Version: 0x%3\n").arg(QString::fromUtf8(e.name)) - .arg(e.present ? yes.trimmed() : no.trimmed()) - .arg(QString::number(e.version, 16))); + if (auto c = kwinApp()->x11Connection()) { + support.append(QStringLiteral("X11\n")); + support.append(QStringLiteral("===\n")); + auto x11setup = xcb_get_setup(c); + support.append(QStringLiteral("Vendor: %1\n").arg(QString::fromUtf8(QByteArray::fromRawData(xcb_setup_vendor(x11setup), xcb_setup_vendor_length(x11setup))))); + support.append(QStringLiteral("Vendor Release: %1\n").arg(x11setup->release_number)); + support.append(QStringLiteral("Protocol Version/Revision: %1/%2\n").arg(x11setup->protocol_major_version).arg(x11setup->protocol_minor_version)); + const auto extensions = Xcb::Extensions::self()->extensions(); + for (const auto &e : extensions) { + support.append(QStringLiteral("%1: %2; Version: 0x%3\n").arg(QString::fromUtf8(e.name)) + .arg(e.present ? yes.trimmed() : no.trimmed()) + .arg(QString::number(e.version, 16))); + } + support.append(QStringLiteral("\n")); } - support.append(QStringLiteral("\n")); if (auto bridge = Decoration::DecorationBridge::self()) { support.append(QStringLiteral("Decoration\n")); support.append(QStringLiteral("==========\n")); support.append(bridge->supportInformation()); support.append(QStringLiteral("\n")); } + support.append(QStringLiteral("Platform\n")); + support.append(QStringLiteral("==========\n")); + support.append(kwinApp()->platform()->supportInformation()); + support.append(QStringLiteral("\n")); + support.append(QStringLiteral("Options\n")); support.append(QStringLiteral("=======\n")); const QMetaObject *metaOptions = options->metaObject(); auto printProperty = [] (const QVariant &variant) { if (variant.type() == QVariant::Size) { const QSize &s = variant.toSize(); return QStringLiteral("%1x%2").arg(QString::number(s.width())).arg(QString::number(s.height())); } if (QLatin1String(variant.typeName()) == QLatin1String("KWin::OpenGLPlatformInterface") || QLatin1String(variant.typeName()) == QLatin1String("KWin::Options::WindowOperation")) { return QString::number(variant.toInt()); } return variant.toString(); }; for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (QLatin1String(property.name()) == QLatin1String("objectName")) { continue; } support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(options->property(property.name())))); } support.append(QStringLiteral("\nScreen Edges\n")); support.append(QStringLiteral( "============\n")); const QMetaObject *metaScreenEdges = ScreenEdges::self()->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaScreenEdges->property(i); if (QLatin1String(property.name()) == QLatin1String("objectName")) { continue; } support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(ScreenEdges::self()->property(property.name())))); } support.append(QStringLiteral("\nScreens\n")); support.append(QStringLiteral( "=======\n")); support.append(QStringLiteral("Multi-Head: ")); if (is_multihead) { support.append(QStringLiteral("yes\n")); support.append(QStringLiteral("Head: %1\n").arg(screen_number)); } else { support.append(QStringLiteral("no\n")); } support.append(QStringLiteral("Active screen follows mouse: ")); if (screens()->isCurrentFollowsMouse()) support.append(QStringLiteral(" yes\n")); else support.append(QStringLiteral(" no\n")); support.append(QStringLiteral("Number of Screens: %1\n\n").arg(screens()->count())); for (int i=0; icount(); ++i) { const QRect geo = screens()->geometry(i); support.append(QStringLiteral("Screen %1:\n").arg(i)); - support.append(QStringLiteral("---------\n").arg(i)); + support.append(QStringLiteral("---------\n")); support.append(QStringLiteral("Name: %1\n").arg(screens()->name(i))); support.append(QStringLiteral("Geometry: %1,%2,%3x%4\n") .arg(geo.x()) .arg(geo.y()) .arg(geo.width()) .arg(geo.height())); + support.append(QStringLiteral("Scale: %1\n").arg(screens()->scale(i))); support.append(QStringLiteral("Refresh Rate: %1\n\n").arg(screens()->refreshRate(i))); } support.append(QStringLiteral("\nCompositing\n")); support.append(QStringLiteral( "===========\n")); if (effects) { support.append(QStringLiteral("Compositing is active\n")); switch (effects->compositingType()) { case OpenGL2Compositing: case OpenGLCompositing: { GLPlatform *platform = GLPlatform::instance(); if (platform->isGLES()) { support.append(QStringLiteral("Compositing Type: OpenGL ES 2.0\n")); } else { support.append(QStringLiteral("Compositing Type: OpenGL\n")); } support.append(QStringLiteral("OpenGL vendor string: ") + QString::fromUtf8(platform->glVendorString()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL renderer string: ") + QString::fromUtf8(platform->glRendererString()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL version string: ") + QString::fromUtf8(platform->glVersionString()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL platform interface: ")); switch (platform->platformInterface()) { case GlxPlatformInterface: support.append(QStringLiteral("GLX")); break; case EglPlatformInterface: support.append(QStringLiteral("EGL")); break; default: support.append(QStringLiteral("UNKNOWN")); } support.append(QStringLiteral("\n")); if (platform->supports(LimitedGLSL) || platform->supports(GLSL)) support.append(QStringLiteral("OpenGL shading language version string: ") + QString::fromUtf8(platform->glShadingLanguageVersionString()) + QStringLiteral("\n")); support.append(QStringLiteral("Driver: ") + GLPlatform::driverToString(platform->driver()) + QStringLiteral("\n")); if (!platform->isMesaDriver()) support.append(QStringLiteral("Driver version: ") + GLPlatform::versionToString(platform->driverVersion()) + QStringLiteral("\n")); support.append(QStringLiteral("GPU class: ") + GLPlatform::chipClassToString(platform->chipClass()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL version: ") + GLPlatform::versionToString(platform->glVersion()) + QStringLiteral("\n")); if (platform->supports(LimitedGLSL) || platform->supports(GLSL)) support.append(QStringLiteral("GLSL version: ") + GLPlatform::versionToString(platform->glslVersion()) + QStringLiteral("\n")); if (platform->isMesaDriver()) support.append(QStringLiteral("Mesa version: ") + GLPlatform::versionToString(platform->mesaVersion()) + QStringLiteral("\n")); if (platform->serverVersion() > 0) support.append(QStringLiteral("X server version: ") + GLPlatform::versionToString(platform->serverVersion()) + QStringLiteral("\n")); if (platform->kernelVersion() > 0) support.append(QStringLiteral("Linux kernel version: ") + GLPlatform::versionToString(platform->kernelVersion()) + QStringLiteral("\n")); support.append(QStringLiteral("Direct rendering: ")); support.append(QStringLiteral("Requires strict binding: ")); if (!platform->isLooseBinding()) { support.append(QStringLiteral("yes\n")); } else { support.append(QStringLiteral("no\n")); } support.append(QStringLiteral("GLSL shaders: ")); if (platform->supports(GLSL)) { if (platform->supports(LimitedGLSL)) { support.append(QStringLiteral(" limited\n")); } else { support.append(QStringLiteral(" yes\n")); } } else { support.append(QStringLiteral(" no\n")); } support.append(QStringLiteral("Texture NPOT support: ")); if (platform->supports(TextureNPOT)) { if (platform->supports(LimitedNPOT)) { support.append(QStringLiteral(" limited\n")); } else { support.append(QStringLiteral(" yes\n")); } } else { support.append(QStringLiteral(" no\n")); } support.append(QStringLiteral("Virtual Machine: ")); if (platform->isVirtualMachine()) { support.append(QStringLiteral(" yes\n")); } else { support.append(QStringLiteral(" no\n")); } support.append(QStringLiteral("OpenGL 2 Shaders are used\n")); support.append(QStringLiteral("Painting blocks for vertical retrace: ")); if (m_compositor->scene()->blocksForRetrace()) support.append(QStringLiteral(" yes\n")); else support.append(QStringLiteral(" no\n")); break; } case XRenderCompositing: support.append(QStringLiteral("Compositing Type: XRender\n")); break; case QPainterCompositing: support.append("Compositing Type: QPainter\n"); break; case NoCompositing: default: support.append(QStringLiteral("Something is really broken, neither OpenGL nor XRender is used")); } support.append(QStringLiteral("\nLoaded Effects:\n")); support.append(QStringLiteral( "---------------\n")); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(effect + QStringLiteral("\n")); } support.append(QStringLiteral("\nCurrently Active Effects:\n")); support.append(QStringLiteral( "-------------------------\n")); foreach (const QString &effect, static_cast(effects)->activeEffects()) { support.append(effect + QStringLiteral("\n")); } support.append(QStringLiteral("\nEffect Settings:\n")); support.append(QStringLiteral( "----------------\n")); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(static_cast(effects)->supportInformation(effect)); support.append(QStringLiteral("\n")); } } else { support.append(QStringLiteral("Compositing is not active\n")); } return support; } Client *Workspace::findClient(std::function func) const { if (Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } return nullptr; } AbstractClient *Workspace::findAbstractClient(std::function func) const { if (AbstractClient *ret = Toplevel::findInList(m_allClients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } if (waylandServer()) { if (AbstractClient *ret = Toplevel::findInList(waylandServer()->internalClients(), func)) { return ret; } } return nullptr; } Unmanaged *Workspace::findUnmanaged(std::function func) const { return Toplevel::findInList(unmanaged, func); } Unmanaged *Workspace::findUnmanaged(xcb_window_t w) const { return findUnmanaged([w](const Unmanaged *u) { return u->window() == w; }); } Client *Workspace::findClient(Predicate predicate, xcb_window_t w) const { switch (predicate) { case Predicate::WindowMatch: return findClient([w](const Client *c) { return c->window() == w; }); case Predicate::WrapperIdMatch: return findClient([w](const Client *c) { return c->wrapperId() == w; }); case Predicate::FrameIdMatch: return findClient([w](const Client *c) { return c->frameId() == w; }); case Predicate::InputIdMatch: return findClient([w](const Client *c) { return c->inputId() == w; }); } return nullptr; } Toplevel *Workspace::findToplevel(std::function func) const { if (Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } if (Unmanaged *ret = Toplevel::findInList(unmanaged, func)) { return ret; } return nullptr; } Toplevel *Workspace::findToplevel(QWindow *w) const { if (!w) { return nullptr; } if (waylandServer()) { if (auto c = waylandServer()->findClient(w)) { return c; } } return findUnmanaged(w->winId()); } bool Workspace::hasClient(const AbstractClient *c) { if (auto cc = dynamic_cast(c)) { return hasClient(cc); } else { return findAbstractClient([c](const AbstractClient *test) { return test == c; }) != nullptr; } return false; } void Workspace::forEachAbstractClient(std::function< void (AbstractClient*) > func) { std::for_each(m_allClients.constBegin(), m_allClients.constEnd(), func); std::for_each(desktops.constBegin(), desktops.constEnd(), func); } Toplevel *Workspace::findInternal(QWindow *w) const { if (!w) { return nullptr; } if (kwinApp()->operationMode() == Application::OperationModeX11) { return findUnmanaged(w->winId()); } else { return waylandServer()->findClient(w); } } void Workspace::markXStackingOrderAsDirty() { m_xStackingDirty = true; if (kwinApp()->x11Connection()) { m_xStackingQueryTree.reset(new Xcb::Tree(kwinApp()->x11RootWindow())); } } void Workspace::setWasUserInteraction() { if (was_user_interaction) { return; } was_user_interaction = true; // might be called from within the filter, so delay till we now the filter returned QTimer::singleShot(0, this, [this] { m_wasUserInteractionFilter.reset(); } ); } } // namespace diff --git a/xkb.cpp b/xkb.cpp index 74db4410f..150978cda 100644 --- a/xkb.cpp +++ b/xkb.cpp @@ -1,520 +1,553 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016, 2017 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 "xkb.h" #include "xkb_qt_mapping.h" #include "utils.h" // frameworks #include // KWayland #include // Qt #include #include // xkbcommon #include #include #include // system #include #include Q_LOGGING_CATEGORY(KWIN_XKB, "kwin_xkbcommon", QtCriticalMsg) namespace KWin { static void xkbLogHandler(xkb_context *context, xkb_log_level priority, const char *format, va_list args) { Q_UNUSED(context) char buf[1024]; if (std::vsnprintf(buf, 1023, format, args) <= 0) { return; } switch (priority) { case XKB_LOG_LEVEL_DEBUG: qCDebug(KWIN_XKB) << "XKB:" << buf; break; case XKB_LOG_LEVEL_INFO: qCInfo(KWIN_XKB) << "XKB:" << buf; break; case XKB_LOG_LEVEL_WARNING: qCWarning(KWIN_XKB) << "XKB:" << buf; break; case XKB_LOG_LEVEL_ERROR: case XKB_LOG_LEVEL_CRITICAL: default: qCCritical(KWIN_XKB) << "XKB:" << buf; break; } } Xkb::Xkb(QObject *parent) : QObject(parent) , m_context(xkb_context_new(static_cast(0))) , m_keymap(NULL) , m_state(NULL) , m_shiftModifier(0) , m_capsModifier(0) , m_controlModifier(0) , m_altModifier(0) , m_metaModifier(0) , m_numLock(0) , m_capsLock(0) , m_scrollLock(0) , m_modifiers(Qt::NoModifier) , m_consumedModifiers(Qt::NoModifier) , m_keysym(XKB_KEY_NoSymbol) , m_leds() { qRegisterMetaType(); if (!m_context) { qCDebug(KWIN_XKB) << "Could not create xkb context"; } else { xkb_context_set_log_level(m_context, XKB_LOG_LEVEL_DEBUG); xkb_context_set_log_fn(m_context, &xkbLogHandler); // get locale as described in xkbcommon doc // cannot use QLocale as it drops the modifier part QByteArray locale = qgetenv("LC_ALL"); if (locale.isEmpty()) { locale = qgetenv("LC_CTYPE"); } if (locale.isEmpty()) { locale = qgetenv("LANG"); } if (locale.isEmpty()) { locale = QByteArrayLiteral("C"); } m_compose.table = xkb_compose_table_new_from_locale(m_context, locale.constData(), XKB_COMPOSE_COMPILE_NO_FLAGS); if (m_compose.table) { m_compose.state = xkb_compose_state_new(m_compose.table, XKB_COMPOSE_STATE_NO_FLAGS); } } } Xkb::~Xkb() { xkb_compose_state_unref(m_compose.state); xkb_compose_table_unref(m_compose.table); xkb_state_unref(m_state); xkb_keymap_unref(m_keymap); xkb_context_unref(m_context); } void Xkb::reconfigure() { if (!m_context) { return; } xkb_keymap *keymap = nullptr; if (!qEnvironmentVariableIsSet("KWIN_XKB_DEFAULT_KEYMAP")) { keymap = loadKeymapFromConfig(); } if (!keymap) { qCDebug(KWIN_XKB) << "Could not create xkb keymap from configuration"; keymap = loadDefaultKeymap(); } if (keymap) { updateKeymap(keymap); } else { qCDebug(KWIN_XKB) << "Could not create default xkb keymap"; } } +static bool stringIsEmptyOrNull(const char *str) +{ + return str == nullptr || str[0] == '\0'; +} + +/** + * libxkbcommon uses secure_getenv to read the XKB_DEFAULT_* variables. + * As kwin_wayland may have the CAP_SET_NICE capability, it returns nullptr + * so we need to do it ourselves (see xkb_context_sanitize_rule_names). +**/ +static void applyEnvironmentRules(xkb_rule_names &ruleNames) +{ + if (stringIsEmptyOrNull(ruleNames.rules)) { + ruleNames.rules = getenv("XKB_DEFAULT_RULES"); + } + + if (stringIsEmptyOrNull(ruleNames.model)) { + ruleNames.model = getenv("XKB_DEFAULT_MODEL"); + } + + if (stringIsEmptyOrNull(ruleNames.layout)) { + ruleNames.layout = getenv("XKB_DEFAULT_LAYOUT"); + ruleNames.variant = getenv("XKB_DEFAULT_VARIANT"); + } + + if (ruleNames.options == nullptr) { + ruleNames.options = getenv("XKB_DEFAULT_OPTIONS"); + } +} + xkb_keymap *Xkb::loadKeymapFromConfig() { // load config if (!m_config) { return nullptr; } const KConfigGroup config = m_config->group("Layout"); const QByteArray model = config.readEntry("Model", "pc104").toLocal8Bit(); const QByteArray layout = config.readEntry("LayoutList", "").toLocal8Bit(); const QByteArray options = config.readEntry("Options", "").toLocal8Bit(); xkb_rule_names ruleNames = { .rules = nullptr, .model = model.constData(), .layout = layout.constData(), .variant = nullptr, .options = options.constData() }; + applyEnvironmentRules(ruleNames); return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS); } xkb_keymap *Xkb::loadDefaultKeymap() { - return xkb_keymap_new_from_names(m_context, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS); + xkb_rule_names ruleNames = {}; + applyEnvironmentRules(ruleNames); + return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS); } void Xkb::installKeymap(int fd, uint32_t size) { if (!m_context) { return; } char *map = reinterpret_cast(mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0)); if (map == MAP_FAILED) { return; } xkb_keymap *keymap = xkb_keymap_new_from_string(m_context, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_MAP_COMPILE_PLACEHOLDER); munmap(map, size); if (!keymap) { qCDebug(KWIN_XKB) << "Could not map keymap from file"; return; } updateKeymap(keymap); } void Xkb::updateKeymap(xkb_keymap *keymap) { Q_ASSERT(keymap); xkb_state *state = xkb_state_new(keymap); if (!state) { qCDebug(KWIN_XKB) << "Could not create XKB state"; xkb_keymap_unref(keymap); return; } // now release the old ones xkb_state_unref(m_state); xkb_keymap_unref(m_keymap); m_keymap = keymap; m_state = state; m_shiftModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_SHIFT); m_capsModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CAPS); m_controlModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CTRL); m_altModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_ALT); m_metaModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_LOGO); m_numLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_NUM); m_capsLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_CAPS); m_scrollLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_SCROLL); m_currentLayout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE); m_modifierState.depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)); m_modifierState.latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)); m_modifierState.locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)); createKeymapFile(); forwardModifiers(); } void Xkb::createKeymapFile() { if (!m_seat) { return; } // TODO: uninstall keymap on server? if (!m_keymap) { return; } ScopedCPointer keymapString(xkb_keymap_get_as_string(m_keymap, XKB_KEYMAP_FORMAT_TEXT_V1)); if (keymapString.isNull()) { return; } const uint size = qstrlen(keymapString.data()) + 1; QTemporaryFile *tmp = new QTemporaryFile(this); if (!tmp->open()) { delete tmp; return; } unlink(tmp->fileName().toUtf8().constData()); if (!tmp->resize(size)) { delete tmp; return; } uchar *address = tmp->map(0, size); if (!address) { return; } if (qstrncpy(reinterpret_cast(address), keymapString.data(), size) == nullptr) { delete tmp; return; } m_seat->setKeymap(tmp->handle(), size); } void Xkb::updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!m_keymap || !m_state) { return; } xkb_state_update_mask(m_state, modsDepressed, modsLatched, modsLocked, 0, 0, group); updateModifiers(); forwardModifiers(); } void Xkb::updateKey(uint32_t key, InputRedirection::KeyboardKeyState state) { if (!m_keymap || !m_state) { return; } xkb_state_update_key(m_state, key + 8, static_cast(state)); if (state == InputRedirection::KeyboardKeyPressed) { const auto sym = toKeysym(key); if (m_compose.state && xkb_compose_state_feed(m_compose.state, sym) == XKB_COMPOSE_FEED_ACCEPTED) { switch (xkb_compose_state_get_status(m_compose.state)) { case XKB_COMPOSE_NOTHING: m_keysym = sym; break; case XKB_COMPOSE_COMPOSED: m_keysym = xkb_compose_state_get_one_sym(m_compose.state); break; default: m_keysym = XKB_KEY_NoSymbol; break; } } else { m_keysym = sym; } } updateModifiers(); updateConsumedModifiers(key); } void Xkb::updateModifiers() { Qt::KeyboardModifiers mods = Qt::NoModifier; if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1 || xkb_state_mod_index_is_active(m_state, m_capsModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::ShiftModifier; } if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::AltModifier; } if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::ControlModifier; } if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::MetaModifier; } m_modifiers = mods; // update LEDs LEDs leds; if (xkb_state_led_index_is_active(m_state, m_numLock) == 1) { leds = leds | LED::NumLock; } if (xkb_state_led_index_is_active(m_state, m_capsLock) == 1) { leds = leds | LED::CapsLock; } if (xkb_state_led_index_is_active(m_state, m_scrollLock) == 1) { leds = leds | LED::ScrollLock; } if (m_leds != leds) { m_leds = leds; emit ledsChanged(m_leds); } m_currentLayout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE); m_modifierState.depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)); m_modifierState.latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)); m_modifierState.locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)); } void Xkb::forwardModifiers() { if (!m_seat) { return; } m_seat->updateKeyboardModifiers(m_modifierState.depressed, m_modifierState.latched, m_modifierState.locked, m_currentLayout); } QString Xkb::layoutName() const { return layoutName(m_currentLayout); } QString Xkb::layoutName(xkb_layout_index_t layout) const { if (!m_keymap) { return QString{}; } return QString::fromLocal8Bit(xkb_keymap_layout_get_name(m_keymap, layout)); } QMap Xkb::layoutNames() const { QMap layouts; const auto size = m_keymap ? xkb_keymap_num_layouts(m_keymap) : 0u; for (xkb_layout_index_t i = 0; i < size; i++) { layouts.insert(i, layoutName(i)); } return layouts; } void Xkb::updateConsumedModifiers(uint32_t key) { Qt::KeyboardModifiers mods = Qt::NoModifier; if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_shiftModifier, XKB_CONSUMED_MODE_GTK) == 1) { mods |= Qt::ShiftModifier; } if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_altModifier, XKB_CONSUMED_MODE_GTK) == 1) { mods |= Qt::AltModifier; } if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_controlModifier, XKB_CONSUMED_MODE_GTK) == 1) { mods |= Qt::ControlModifier; } if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_metaModifier, XKB_CONSUMED_MODE_GTK) == 1) { mods |= Qt::MetaModifier; } m_consumedModifiers = mods; } Qt::KeyboardModifiers Xkb::modifiersRelevantForGlobalShortcuts() const { if (!m_state) { return Qt::NoModifier; } Qt::KeyboardModifiers mods = Qt::NoModifier; if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::ShiftModifier; } if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::AltModifier; } if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::ControlModifier; } if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::MetaModifier; } Qt::KeyboardModifiers consumedMods = m_consumedModifiers; if ((mods & Qt::ShiftModifier) && (consumedMods == Qt::ShiftModifier)) { // test whether current keysym is a letter // in that case the shift should be removed from the consumed modifiers again // otherwise it would not be possible to trigger e.g. Shift+W as a shortcut // see BUG: 370341 if (QChar(toQtKey(m_keysym)).isLetter()) { consumedMods = Qt::KeyboardModifiers(); } } return mods & ~consumedMods; } xkb_keysym_t Xkb::toKeysym(uint32_t key) { if (!m_state) { return XKB_KEY_NoSymbol; } return xkb_state_key_get_one_sym(m_state, key + 8); } QString Xkb::toString(xkb_keysym_t keysym) { if (!m_state || keysym == XKB_KEY_NoSymbol) { return QString(); } QByteArray byteArray(7, 0); int ok = xkb_keysym_to_utf8(keysym, byteArray.data(), byteArray.size()); if (ok == -1 || ok == 0) { return QString(); } return QString::fromUtf8(byteArray.constData()); } Qt::Key Xkb::toQtKey(xkb_keysym_t keysym) const { return xkbToQtKey(keysym); } xkb_keysym_t Xkb::fromQtKey(Qt::Key key, Qt::KeyboardModifiers mods) const { return qtKeyToXkb(key, mods); } xkb_keysym_t Xkb::fromKeyEvent(QKeyEvent *event) const { xkb_keysym_t sym = xkb_keysym_from_name(event->text().toUtf8().constData(), XKB_KEYSYM_NO_FLAGS); if (sym == XKB_KEY_NoSymbol) { // mapping from text failed, try mapping through KKeyServer sym = fromQtKey(Qt::Key(event->key() & ~Qt::KeyboardModifierMask), event->modifiers()); } return sym; } bool Xkb::shouldKeyRepeat(quint32 key) const { if (!m_keymap) { return false; } return xkb_keymap_key_repeats(m_keymap, key + 8) != 0; } void Xkb::switchToNextLayout() { if (!m_keymap || !m_state) { return; } const xkb_layout_index_t numLayouts = xkb_keymap_num_layouts(m_keymap); const xkb_layout_index_t nextLayout = (xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE) + 1) % numLayouts; switchToLayout(nextLayout); } void Xkb::switchToPreviousLayout() { if (!m_keymap || !m_state) { return; } const xkb_layout_index_t previousLayout = m_currentLayout == 0 ? numberOfLayouts() - 1 : m_currentLayout -1; switchToLayout(previousLayout); } void Xkb::switchToLayout(xkb_layout_index_t layout) { if (!m_keymap || !m_state) { return; } if (layout >= numberOfLayouts()) { return; } const xkb_mod_mask_t depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)); const xkb_mod_mask_t latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)); const xkb_mod_mask_t locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)); xkb_state_update_mask(m_state, depressed, latched, locked, 0, 0, layout); updateModifiers(); forwardModifiers(); } quint32 Xkb::numberOfLayouts() const { if (!m_keymap) { return 0; } return xkb_keymap_num_layouts(m_keymap); } void Xkb::setSeat(KWayland::Server::SeatInterface *seat) { m_seat = QPointer(seat); } }