diff --git a/CMakeLists.txt b/CMakeLists.txt index 863e2378e..bf19c4448 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,765 +1,764 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(KWIN) set(PROJECT_VERSION "5.17.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.12.0") set(KF5_MIN_VERSION "5.62.0") set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) find_package(ECM 5.38 REQUIRED NO_MODULE) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(GenerateExportHeader) # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Concurrent Core DBus Quick QuickWidgets Script Sensors UiTools Widgets X11Extras ) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" TYPE OPTIONAL ) add_feature_info("Qt5Test" Qt5Test_FOUND "Required for building tests") if (NOT Qt5Test_FOUND) set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") endif() include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDEClangFormat) include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0 -DQT_USE_QSTRINGBUILDER -DQT_NO_URL_CAST_FROM_STRING) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt5Multimedia QUIET) set_package_properties(Qt5Multimedia PROPERTIES PURPOSE "Runtime-only dependency for effect video playback" TYPE RUNTIME ) # required frameworks by Core find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Config ConfigWidgets CoreAddons Crash GlobalAccel I18n IconThemes IdleTime Init Notifications Package Plasma Wayland WidgetsAddons WindowSystem ) # required frameworks by config modules find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Declarative KCMUtils KIO NewStuff Service TextWidgets XmlGui ) find_package(Threads) set_package_properties(Threads PROPERTIES PURPOSE "Needed for VirtualTerminal support in KWin Wayland" TYPE REQUIRED ) # optional frameworks find_package(KF5Activities ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Activities PROPERTIES PURPOSE "Enable building of KWin with kactivities support" TYPE OPTIONAL ) add_feature_info("KF5Activities" KF5Activities_FOUND "Enable building of KWin with kactivities support") find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5DocTools PROPERTIES PURPOSE "Enable building documentation" TYPE OPTIONAL ) add_feature_info("KF5DocTools" KF5DocTools_FOUND "Enable building documentation") find_package(KF5Kirigami2 ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "A QtQuick based components set" PURPOSE "Required at runtime for Virtual desktop KCM and the virtual keyboard" TYPE RUNTIME ) find_package(KDecoration2 5.13.0 CONFIG REQUIRED) find_package(KScreenLocker CONFIG REQUIRED) set_package_properties(KScreenLocker PROPERTIES TYPE REQUIRED PURPOSE "For screenlocker integration in kwin_wayland" ) find_package(Breeze 5.9.0 CONFIG) set_package_properties(Breeze PROPERTIES TYPE OPTIONAL PURPOSE "For setting the default window decoration plugin" ) if (${Breeze_FOUND}) if (${BREEZE_WITH_KDECORATION}) set(HAVE_BREEZE_DECO true) else() set(HAVE_BREEZE_DECO FALSE) endif() else() set(HAVE_BREEZE_DECO FALSE) endif() add_feature_info("Breeze-Decoration" HAVE_BREEZE_DECO "Default decoration plugin Breeze") find_package(EGL) set_package_properties(EGL PROPERTIES TYPE RUNTIME PURPOSE "Required to build KWin with EGL support" ) find_package(epoxy) set_package_properties(epoxy PROPERTIES DESCRIPTION "libepoxy" URL "https://github.com/anholt/libepoxy" TYPE REQUIRED PURPOSE "OpenGL dispatch library" ) set(HAVE_DL_LIBRARY FALSE) if (epoxy_HAS_GLX) find_library(DL_LIBRARY dl) if (DL_LIBRARY) set(HAVE_DL_LIBRARY TRUE) endif() endif() find_package(Wayland 1.2 REQUIRED COMPONENTS Cursor OPTIONAL_COMPONENTS Egl) set_package_properties(Wayland PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) add_feature_info("Wayland::EGL" Wayland_Egl_FOUND "Enable building of Wayland backend and QPA with EGL support.") set(HAVE_WAYLAND_EGL FALSE) if (Wayland_Egl_FOUND) set(HAVE_WAYLAND_EGL TRUE) endif() find_package(XKB 0.7.0) set_package_properties(XKB PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) find_package(Libinput 1.9) set_package_properties(Libinput PROPERTIES TYPE REQUIRED PURPOSE "Required for input handling on Wayland.") find_package(UDev) set_package_properties(UDev PROPERTIES URL "https://www.freedesktop.org/wiki/Software/systemd/" DESCRIPTION "Linux device library." TYPE REQUIRED PURPOSE "Required for input handling on Wayland." ) find_package(Libdrm 2.4.62) set_package_properties(Libdrm PROPERTIES TYPE OPTIONAL PURPOSE "Required for drm output on Wayland.") set(HAVE_DRM FALSE) if (Libdrm_FOUND) set(HAVE_DRM TRUE) endif() find_package(gbm) set_package_properties(gbm PROPERTIES TYPE OPTIONAL PURPOSE "Required for egl output of drm backend.") set(HAVE_GBM FALSE) if (HAVE_DRM AND gbm_FOUND) set(HAVE_GBM TRUE) endif() option(KWIN_BUILD_EGL_STREAM_BACKEND "Enable building of EGLStream based DRM backend" ON) if (HAVE_DRM AND KWIN_BUILD_EGL_STREAM_BACKEND) set(HAVE_EGL_STREAMS TRUE) endif() find_package(libhybris) set_package_properties(libhybris PROPERTIES TYPE OPTIONAL PURPOSE "Required for libhybris backend") set(HAVE_LIBHYBRIS ${libhybris_FOUND}) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "https://www.x.org" TYPE REQUIRED ) add_feature_info("XInput" X11_Xinput_FOUND "Required for poll-free mouse cursor updates") set(HAVE_X11_XINPUT ${X11_Xinput_FOUND}) # All the required XCB components find_package(XCB 1.10 REQUIRED COMPONENTS COMPOSITE CURSOR DAMAGE GLX ICCCM IMAGE KEYSYMS RANDR RENDER SHAPE SHM SYNC XCB XFIXES ) set_package_properties(XCB PROPERTIES TYPE REQUIRED) # and the optional XCB dependencies if (XCB_ICCCM_VERSION VERSION_LESS "0.4") set(XCB_ICCCM_FOUND FALSE) endif() add_feature_info("XCB-ICCCM" XCB_ICCCM_FOUND "Required for building test applications for KWin") find_package(X11_XCB) set_package_properties(X11_XCB PROPERTIES PURPOSE "Required for building X11 windowed backend of kwin_wayland" TYPE OPTIONAL ) # dependencies for QPA plugin find_package(Qt5FontDatabaseSupport REQUIRED) find_package(Qt5ThemeSupport REQUIRED) find_package(Qt5EventDispatcherSupport REQUIRED) find_package(Freetype REQUIRED) set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine" URL "https://www.freetype.org" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Fontconfig REQUIRED) set_package_properties(Fontconfig PROPERTIES TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Xwayland) set_package_properties(Xwayland PROPERTIES URL "https://x.org" DESCRIPTION "Xwayland X server" TYPE RUNTIME PURPOSE "Needed for running kwin_wayland" ) find_package(Libcap) set_package_properties(Libcap PROPERTIES TYPE OPTIONAL PURPOSE "Needed for running kwin_wayland with real-time scheduling policy" ) set(HAVE_LIBCAP ${Libcap_FOUND}) include(ECMQMLModules) ecm_find_qmlmodule(QtQuick 2.3) ecm_find_qmlmodule(QtQuick.Controls 1.2) ecm_find_qmlmodule(QtQuick.Layouts 1.3) ecm_find_qmlmodule(QtQuick.VirtualKeyboard 2.1) ecm_find_qmlmodule(QtQuick.Window 2.1) ecm_find_qmlmodule(QtMultimedia 5.0) ecm_find_qmlmodule(org.kde.kquickcontrolsaddons 2.0) ecm_find_qmlmodule(org.kde.plasma.core 2.0) ecm_find_qmlmodule(org.kde.plasma.components 2.0) ########### configure tests ############### include(CMakeDependentOption) option(KWIN_BUILD_DECORATIONS "Enable building of KWin decorations." ON) option(KWIN_BUILD_KCMS "Enable building of KWin configuration modules." ON) option(KWIN_BUILD_TABBOX "Enable building of KWin Tabbox functionality" ON) option(KWIN_BUILD_XRENDER_COMPOSITING "Enable building of KWin with XRender Compositing support" ON) cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "KF5Activities_FOUND" OFF) # Binary name of KWin set(KWIN_NAME "kwin") set(KWIN_INTERNAL_NAME_X11 "kwin_x11") set(KWIN_INTERNAL_NAME_WAYLAND "kwin_wayland") set(KWIN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # KWIN_HAVE_XRENDER_COMPOSITING - whether XRender-based compositing support is available: may be disabled if (KWIN_BUILD_XRENDER_COMPOSITING) set(KWIN_HAVE_XRENDER_COMPOSITING 1) endif() include_directories(${XKB_INCLUDE_DIR}) include_directories(${epoxy_INCLUDE_DIR}) set(HAVE_EPOXY_GLX ${epoxy_HAS_GLX}) # for things that are also used by kwin libraries configure_file(libkwineffects/kwinconfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects/kwinconfig.h ) # for kwin internal things set(HAVE_X11_XCB ${X11_XCB_FOUND}) include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckSymbolExists) check_include_files(unistd.h HAVE_UNISTD_H) check_include_files(malloc.h HAVE_MALLOC_H) check_include_file("sys/prctl.h" HAVE_SYS_PRCTL_H) check_symbol_exists(PR_SET_DUMPABLE "sys/prctl.h" HAVE_PR_SET_DUMPABLE) check_symbol_exists(PR_SET_PDEATHSIG "sys/prctl.h" HAVE_PR_SET_PDEATHSIG) check_include_file("sys/procctl.h" HAVE_SYS_PROCCTL_H) check_symbol_exists(PROC_TRACE_CTL "sys/procctl.h" HAVE_PROC_TRACE_CTL) if (HAVE_PR_SET_DUMPABLE OR HAVE_PROC_TRACE_CTL) set(CAN_DISABLE_PTRACE TRUE) endif() add_feature_info("prctl/procctl tracing control" CAN_DISABLE_PTRACE "Required for disallowing ptrace on kwin_wayland process") check_include_file("sys/sysmacros.h" HAVE_SYS_SYSMACROS_H) check_include_file("linux/vt.h" HAVE_LINUX_VT_H) add_feature_info("linux/vt.h" HAVE_LINUX_VT_H "Required for virtual terminal support under wayland") check_include_file("linux/fb.h" HAVE_LINUX_FB_H) add_feature_info("linux/fb.h" HAVE_LINUX_FB_H "Required for the fbdev backend") check_symbol_exists(SCHED_RESET_ON_FORK "sched.h" HAVE_SCHED_RESET_ON_FORK) add_feature_info("SCHED_RESET_ON_FORK" HAVE_SCHED_RESET_ON_FORK "Required for running kwin_wayland with real-time scheduling") configure_file(config-kwin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.h) ########### global ############### set(kwin_effects_dbus_xml ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.kwin.Effects.xml) include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/libkwineffects ${CMAKE_CURRENT_SOURCE_DIR}/effects ${CMAKE_CURRENT_SOURCE_DIR}/tabbox ${CMAKE_CURRENT_SOURCE_DIR}/platformsupport ) add_subdirectory(libkwineffects) if (KWIN_BUILD_KCMS) add_subdirectory(kcmkwin) endif() add_subdirectory(data) add_subdirectory(effects) add_subdirectory(scripts) add_subdirectory(tabbox) add_subdirectory(scripting) add_subdirectory(helpers) ########### next target ############### set(kwin_KDEINIT_SRCS abstract_client.cpp abstract_opengl_context_attribute_builder.cpp abstract_output.cpp abstract_wayland_output.cpp activation.cpp appmenu.cpp atoms.cpp client_machine.cpp colorcorrection/clockskewnotifier.cpp colorcorrection/clockskewnotifierengine.cpp colorcorrection/colorcorrectdbusinterface.cpp colorcorrection/manager.cpp colorcorrection/suncalc.cpp composite.cpp cursor.cpp dbusinterface.cpp debug_console.cpp decorations/decoratedclient.cpp decorations/decorationbridge.cpp decorations/decorationpalette.cpp decorations/decorationrenderer.cpp decorations/decorations_logging.cpp decorations/settings.cpp deleted.cpp effectloader.cpp effects.cpp egl_context_attribute_builder.cpp events.cpp focuschain.cpp - geometry.cpp geometrytip.cpp gestures.cpp globalshortcuts.cpp group.cpp idle_inhibition.cpp input.cpp input_event.cpp input_event_spy.cpp internal_client.cpp keyboard_input.cpp keyboard_layout.cpp keyboard_layout_switching.cpp keyboard_repeat.cpp killwindow.cpp layers.cpp libinput/connection.cpp libinput/context.cpp libinput/device.cpp libinput/events.cpp libinput/libinput_logging.cpp linux_dmabuf.cpp logind.cpp main.cpp modifier_only_shortcuts.cpp moving_client_x11_filter.cpp netinfo.cpp onscreennotification.cpp options.cpp orientation_sensor.cpp osd.cpp outline.cpp outputscreens.cpp overlaywindow.cpp placement.cpp platform.cpp pointer_input.cpp popup_input_filter.cpp rootinfo_filter.cpp rules.cpp scene.cpp screenedge.cpp screenlockerwatcher.cpp screens.cpp scripting/dbuscall.cpp scripting/meta.cpp scripting/screenedgeitem.cpp scripting/scriptedeffect.cpp scripting/scripting.cpp scripting/scripting_logging.cpp scripting/scripting_model.cpp scripting/scriptingutils.cpp scripting/timer.cpp scripting/workspace_wrapper.cpp shadow.cpp sm.cpp thumbnailitem.cpp toplevel.cpp touch_hide_cursor_spy.cpp tablet_input.cpp touch_input.cpp udev.cpp unmanaged.cpp useractions.cpp utils.cpp virtualdesktops.cpp virtualdesktopsdbustypes.cpp virtualkeyboard.cpp virtualkeyboard_dbus.cpp was_user_interaction_x11_filter.cpp wayland_cursor_theme.cpp wayland_server.cpp window_property_notify_x11_filter.cpp workspace.cpp x11client.cpp x11eventfilter.cpp xcbutils.cpp xdgshellclient.cpp xkb.cpp xwl/xwayland_interface.cpp ) if (CMAKE_SYSTEM_NAME MATCHES "Linux") set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} colorcorrection/clockskewnotifierengine_linux.cpp ) endif() include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category(kwin_KDEINIT_SRCS HEADER colorcorrect_logging.h IDENTIFIER KWIN_COLORCORRECTION CATEGORY_NAME kwin_colorcorrection DEFAULT_SEVERITY Critical ) if (KWIN_BUILD_TABBOX) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} tabbox/clientmodel.cpp tabbox/desktopchain.cpp tabbox/desktopmodel.cpp tabbox/switcheritem.cpp tabbox/tabbox.cpp tabbox/tabbox_logging.cpp tabbox/tabboxconfig.cpp tabbox/tabboxhandler.cpp tabbox/x11_filter.cpp ) endif() if (KWIN_BUILD_ACTIVITIES) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} activities.cpp ) endif() if (HAVE_LINUX_VT_H) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} virtual_terminal.cpp ) endif() kconfig_add_kcfg_files(kwin_KDEINIT_SRCS settings.kcfgc) kconfig_add_kcfg_files(kwin_KDEINIT_SRCS colorcorrection/colorcorrect_settings.kcfgc) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.KWin.xml dbusinterface.h KWin::DBusInterface) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.kwin.Compositing.xml dbusinterface.h KWin::CompositorDBusInterface) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.kwin.ColorCorrect.xml colorcorrection/colorcorrectdbusinterface.h KWin::ColorCorrect::ColorCorrectDBusInterface) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.kwin.OrientationSensor.xml orientation_sensor.h KWin::OrientationSensor) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.KWin.VirtualDesktopManager.xml dbusinterface.h KWin::VirtualDesktopManagerDBusInterface) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.KWin.Session.xml sm.h KWin::SessionManager) qt5_add_dbus_interface(kwin_KDEINIT_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/kf5_org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_dbus_interface(kwin_KDEINIT_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/org.kde.screensaver.xml kscreenlocker_interface) qt5_add_dbus_interface(kwin_KDEINIT_SRCS org.kde.kappmenu.xml appmenu_interface) ki18n_wrap_ui(kwin_KDEINIT_SRCS debug_console.ui shortcutdialog.ui ) ########### target link libraries ############### set(kwin_OWN_LIBS kwineffects kwin4_effect_builtins ) set(kwin_QT_LIBS Qt5::Concurrent Qt5::DBus Qt5::Quick Qt5::Script Qt5::Sensors ) set(kwin_KDE_LIBS KF5::ConfigCore KF5::ConfigWidgets KF5::CoreAddons KF5::GlobalAccel KF5::GlobalAccelPrivate KF5::I18n KF5::Notifications KF5::Package KF5::Plasma KF5::QuickAddons KF5::WindowSystem KDecoration2::KDecoration KDecoration2::KDecoration2Private PW::KScreenLocker ) set(kwin_XLIB_LIBS ${X11_ICE_LIB} ${X11_SM_LIB} ${X11_X11_LIB} ) set(kwin_XCB_LIBS XCB::COMPOSITE XCB::DAMAGE XCB::GLX XCB::ICCCM XCB::KEYSYMS XCB::RANDR XCB::RENDER XCB::SHAPE XCB::SHM XCB::SYNC XCB::XCB XCB::XFIXES ) set(kwin_WAYLAND_LIBS KF5::WaylandClient KF5::WaylandServer Wayland::Cursor XKB::XKB ${CMAKE_THREAD_LIBS_INIT} ) if (KWIN_BUILD_ACTIVITIES) set(kwin_KDE_LIBS ${kwin_KDE_LIBS} KF5::Activities) endif() set(kwinLibs ${kwin_OWN_LIBS} ${kwin_QT_LIBS} ${kwin_KDE_LIBS} ${kwin_XLIB_LIBS} ${kwin_XCB_LIBS} ${kwin_WAYLAND_LIBS} ${UDEV_LIBS} Libinput::Libinput ) add_library(kwin SHARED ${kwin_KDEINIT_SRCS}) set_target_properties(kwin PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) target_link_libraries(kwin ${kwinLibs}) generate_export_header(kwin EXPORT_FILE_NAME kwin_export.h) target_link_libraries(kwin kwinglutils ${epoxy_LIBRARY}) kf5_add_kdeinit_executable(kwin_x11 main_x11.cpp) target_link_libraries(kdeinit_kwin_x11 kwin KF5::Crash Qt5::X11Extras) install(TARGETS kwin ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) install(TARGETS kdeinit_kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS}) set(kwin_XWAYLAND_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/xwl/clipboard.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/databridge.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/dnd.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_wl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_x.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection_source.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/transfer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/xwayland.cpp ) include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category(kwin_XWAYLAND_SRCS HEADER xwayland_logging.h IDENTIFIER KWIN_XWL CATEGORY_NAME kwin_xwl DEFAULT_SEVERITY Critical ) set(kwin_WAYLAND_SRCS main_wayland.cpp tabletmodemanager.cpp ) add_executable(kwin_wayland ${kwin_WAYLAND_SRCS} ${kwin_XWAYLAND_SRCS}) target_link_libraries(kwin_wayland kwin KF5::Crash) if (HAVE_LIBCAP) target_link_libraries(kwin_wayland ${Libcap_LIBRARIES}) endif() install(TARGETS kwin_wayland ${INSTALL_TARGETS_DEFAULT_ARGS}) if (HAVE_LIBCAP) install( CODE "execute_process( COMMAND ${SETCAP_EXECUTABLE} CAP_SYS_NICE=+ep \$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/kwin_wayland)" ) endif() add_subdirectory(platformsupport) add_subdirectory(plugins) ########### install files ############### install(FILES kwin.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}.kcfg) install(FILES colorcorrection/colorcorrect_settings.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}_colorcorrect.kcfg) install(FILES kwin.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR} RENAME ${KWIN_NAME}.notifyrc) install( FILES org.kde.KWin.VirtualDesktopManager.xml org.kde.KWin.xml org.kde.kwin.ColorCorrect.xml org.kde.kwin.Compositing.xml org.kde.kwin.Effects.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kwin_export.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel) # Install the KWin/Script service type install(FILES scripting/kwinscript.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) add_subdirectory(qml) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() if (KF5DocTools_FOUND) add_subdirectory(doc) endif() add_subdirectory(kconf_update) # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) include(CMakePackageConfigHelpers) set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KWinDBusInterface") configure_package_config_file(KWinDBusInterfaceConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake" PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR}) diff --git a/abstract_client.cpp b/abstract_client.cpp index aa5b3c4d5..8100900d3 100644 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1,2050 +1,3145 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin Copyright (C) 2019 Vlad Zahorodnii 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 "useractions.h" #include "workspace.h" #include "wayland_server.h" #include #include #include #include #include namespace KWin { +static inline int sign(int v) +{ + return (v > 0) - (v < 0); +} + 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); // If the user manually moved the window, don't restore it after the keyboard closes connect(this, &AbstractClient::clientFinishUserMovedResized, this, [this] () { m_keyboardGeometryRestore = QRect(); }); connect(this, qOverload(&AbstractClient::clientMaximizedStateChanged), this, [this] () { m_keyboardGeometryRestore = QRect(); }); connect(this, &AbstractClient::fullScreenChanged, this, [this] () { m_keyboardGeometryRestore = QRect(); }); // replace on-screen-display on size changes connect(this, &AbstractClient::geometryShapeChanged, this, [this] (Toplevel *c, const QRect &old) { Q_UNUSED(c) if (isOnScreenDisplay() && !frameGeometry().isEmpty() && old.size() != frameGeometry().size() && !isInitialPositionSet()) { GeometryUpdatesBlocker blocker(this); const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); Placement::self()->place(this, area); setGeometryRestore(frameGeometry()); } } ); connect(this, &AbstractClient::paddingChanged, this, [this]() { m_visibleRectBeforeGeometryUpdate = visibleRect(); }); connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] { emit hasApplicationMenuChanged(hasApplicationMenu()); }); } AbstractClient::~AbstractClient() { Q_ASSERT(m_blockGeometryUpdates == 0); Q_ASSERT(m_decoration.decoration == nullptr); } void AbstractClient::updateMouseGrab() { } bool AbstractClient::belongToSameApplication(const AbstractClient *c1, const AbstractClient *c2, SameApplicationChecks checks) { return c1->belongsToSameApplication(c2, checks); } bool AbstractClient::isTransient() const { return false; } void AbstractClient::setClientShown(bool shown) { Q_UNUSED(shown) } MaximizeMode AbstractClient::requestedMaximizeMode() const { return maximizeMode(); } 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; doSetSkipSwitcher(); updateWindowRules(Rules::SkipSwitcher); emit skipSwitcherChanged(); } void AbstractClient::setSkipPager(bool b) { b = rules()->checkSkipPager(b); if (b == skipPager()) return; m_skipPager = b; doSetSkipPager(); 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::doSetSkipSwitcher() { } 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 : nullptr); 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 (isInternal()) return UnmanagedLayer; 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 (isCriticalNotification()) return CriticalNotificationLayer; 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(), NET::KeepAbove); return; } m_keepAbove = b; if (info) { info->setState(keepAbove() ? NET::KeepAbove : NET::States(), 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(), NET::KeepBelow); return; } m_keepBelow = b; if (info) { info->setState(keepBelow() ? NET::KeepBelow : NET::States(), 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() || isCriticalNotification(); } 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(), 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)); QVector desktops; if (desktop != NET::OnAllDesktops) { desktops << VirtualDesktopManager::self()->desktopForX11Id(desktop); } setDesktops(desktops); } void AbstractClient::setDesktops(QVector desktops) { //on x11 we can have only one desktop at a time if (kwinApp()->operationMode() == Application::OperationModeX11 && desktops.size() > 1) { desktops = QVector({desktops.last()}); } if (desktops == m_desktops) { return; } int was_desk = AbstractClient::desktop(); const bool wasOnCurrentDesktop = isOnCurrentDesktop() && was_desk >= 0; m_desktops = desktops; if (windowManagementInterface()) { if (m_desktops.isEmpty()) { windowManagementInterface()->setOnAllDesktops(true); } else { windowManagementInterface()->setOnAllDesktops(false); auto currentDesktops = windowManagementInterface()->plasmaVirtualDesktops(); for (auto desktop: m_desktops) { if (!currentDesktops.contains(desktop->id())) { windowManagementInterface()->addPlasmaVirtualDesktop(desktop->id()); } else { currentDesktops.removeOne(desktop->id()); } } for (auto desktopId: currentDesktops) { windowManagementInterface()->removePlasmaVirtualDesktop(desktopId); } } } 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)->setDesktops(desktops); 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->setDesktops(desktops); } doSetDesktop(desktop(), was_desk); FocusChain::self()->update(this, FocusChain::MakeFirst); updateWindowRules(Rules::Desktop); emit desktopChanged(); if (wasOnCurrentDesktop != isOnCurrentDesktop()) emit desktopPresenceChanged(this, was_desk); emit x11DesktopIdsChanged(); } void AbstractClient::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) } void AbstractClient::enterDesktop(VirtualDesktop *virtualDesktop) { if (m_desktops.contains(virtualDesktop)) { return; } auto desktops = m_desktops; desktops.append(virtualDesktop); setDesktops(desktops); } void AbstractClient::leaveDesktop(VirtualDesktop *virtualDesktop) { QVector currentDesktops; if (m_desktops.isEmpty()) { currentDesktops = VirtualDesktopManager::self()->desktops(); } else { currentDesktops = m_desktops; } if (!currentDesktops.contains(virtualDesktop)) { return; } auto desktops = currentDesktops; desktops.removeOne(virtualDesktop); setDesktops(desktops); } void AbstractClient::setOnAllDesktops(bool b) { if ((b && isOnAllDesktops()) || (!b && !isOnAllDesktops())) return; if (b) setDesktop(NET::OnAllDesktops); else setDesktop(VirtualDesktopManager::self()->current()); } QVector AbstractClient::x11DesktopIds() const { const auto desks = desktops(); QVector x11Ids; x11Ids.reserve(desks.count()); std::transform(desks.constBegin(), desks.constEnd(), std::back_inserter(x11Ids), [] (const VirtualDesktop *vd) { return vd->x11DesktopNumber(); } ); return x11Ids; } 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(NET::States(), 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()); emit colorSchemeChanged(); } } 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 (frameGeometry().right() > area.right() && width() <= area.width()) tx = area.right() - width() + 1; if (frameGeometry().bottom() > area.bottom() && height() <= area.height()) ty = area.bottom() - height() + 1; if (!area.contains(frameGeometry().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::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()) + setFrameGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet); + else + setFrameGeometry(frameGeometry(), NormalGeometrySet); + m_pendingGeometryUpdate = PendingGeometryNone; + } + } + } +} + +void AbstractClient::maximize(MaximizeMode m) +{ + setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); +} + +void AbstractClient::setMaximize(bool vertically, bool horizontally) +{ + // changeMaximize() flips the state, so change from set->flip + const MaximizeMode oldMode = maximizeMode(); + changeMaximize( + oldMode & MaximizeHorizontal ? !horizontally : horizontally, + oldMode & MaximizeVertical ? !vertically : vertically, + false); + const MaximizeMode newMode = maximizeMode(); + if (oldMode != newMode) { + emit clientMaximizedStateChanged(this, newMode); + emit clientMaximizedStateChanged(this, vertically, horizontally); + } +} + +void AbstractClient::move(int x, int y, ForceGeometry_t force) +{ + // resuming geometry updates is handled only in setGeometry() + Q_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 && m_frameGeometry.topLeft() == p) + return; + m_frameGeometry.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(); + emit geometryChanged(); +} + +bool AbstractClient::startMoveResize() +{ + Q_ASSERT(!isMoveResize()); + Q_ASSERT(QWidget::keyboardGrabber() == nullptr); + Q_ASSERT(QWidget::mouseGrabber() == nullptr); + stopDelayedMoveResize(); + if (QApplication::activePopupWidget() != nullptr) + return false; // popups have grab + if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens())) + return false; + if (!doStartMoveResize()) { + return false; + } + + invalidateDecorationDoubleClickTimer(); + + setMoveResize(true); + workspace()->setMoveResizeClient(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(frameGeometry()); // "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(frameGeometry()); + emit quickTileModeChanged(); + } + + updateHaveResizeEffect(); + updateInitialMoveResizeGeometry(); + checkUnrestrictedMoveResize(); + emit clientStartUserMovedResized(this); + if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) + ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal); + return true; +} + +void AbstractClient::finishMoveResize(bool cancel) +{ + GeometryUpdatesBlocker blocker(this); + const bool wasResize = isResize(); // store across leaveMoveResize + leaveMoveResize(); + + if (cancel) + setFrameGeometry(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(restoreH, restoreV, false); + } + } + setFrameGeometry(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(frameGeometry().x()); + geom_restore.setWidth(frameGeometry().width()); + } + if (!(maximizeMode() & MaximizeVertical)) { + geom_restore.setY(frameGeometry().y()); + geom_restore.setHeight(frameGeometry().height()); + } + setGeometryRestore(geom_restore); + } +// FRAME update(); + + emit clientFinishUserMovedResized(this); +} + +// 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]() { + Q_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::updateMoveResize(const QPointF ¤tGlobalCursor) { handleMoveResize(pos(), currentGlobalCursor.toPoint()); } +void AbstractClient::handleMoveResize(const QPoint &local, const QPoint &global) +{ + const QRect oldGeo = frameGeometry(); + handleMoveResize(local.x(), local.y(), global.x(), global.y()); + if (!isFullScreen() && isMove()) { + if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != frameGeometry()) { + 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()); + } + } +} + +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; + for (const QRect &rect : availableArea) { + 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()) { + Q_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; + for (const QRect &rect : availableArea) { + 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; + for (const QRect &r : strut) { + 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 AbstractClient::performMoveResize() +{ + const QRect &moveResizeGeom = moveResizeGeometry(); + if (isMove() || (isResize() && !haveResizeEffect())) { + setFrameGeometry(moveResizeGeom); + } + doPerformMoveResize(); + if (isResize()) + addRepaintFull(); + positionGeometryTip(); + emit clientStepUserMovedResized(this, moveResizeGeom); +} + 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->setSkipSwitcher(skipSwitcher()); w->setPid(pid()); w->setShadeable(isShadeable()); w->setShaded(isShade()); w->setResizable(isResizable()); w->setMovable(isMovable()); w->setVirtualDesktopChangeable(true); // FIXME Matches X11Client::actionSupported(), but both should be implemented. w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); w->setGeometry(frameGeometry()); connect(this, &AbstractClient::skipTaskbarChanged, w, [w, this] { w->setSkipTaskbar(skipTaskbar()); } ); connect(this, &AbstractClient::skipSwitcherChanged, w, [w, this] { w->setSkipSwitcher(skipSwitcher()); } ); connect(this, &AbstractClient::captionChanged, w, [w, this] { w->setTitle(caption()); }); 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(frameGeometry()); } ); connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); }); connect(w, &PlasmaWindowInterface::moveRequested, this, [this] { Cursor::setPos(frameGeometry().center()); performMouseCommand(Options::MouseMove, Cursor::pos()); } ); connect(w, &PlasmaWindowInterface::resizeRequested, this, [this] { Cursor::setPos(frameGeometry().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); } ); for (const auto vd : m_desktops) { w->addPlasmaVirtualDesktop(vd->id()); } //this is only for the legacy connect(this, &AbstractClient::desktopChanged, w, [w, this] { if (isOnAllDesktops()) { w->setOnAllDesktops(true); return; } w->setVirtualDesktop(desktop() - 1); w->setOnAllDesktops(false); } ); //Plasma Virtual desktop management //show/hide when the window enters/exits from desktop connect(w, &PlasmaWindowInterface::enterPlasmaVirtualDesktopRequested, this, [this] (const QString &desktopId) { VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId.toUtf8()); if (vd) { enterDesktop(vd); } } ); connect(w, &PlasmaWindowInterface::enterNewPlasmaVirtualDesktopRequested, this, [this] () { VirtualDesktopManager::self()->setCount(VirtualDesktopManager::self()->count() + 1); enterDesktop(VirtualDesktopManager::self()->desktops().last()); } ); connect(w, &PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested, this, [this] (const QString &desktopId) { VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId.toUtf8()); if (vd) { leaveDesktop(vd); } } ); 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) { auto 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->frameGeometry().intersects(frameGeometry())); } } 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::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::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; } QRect AbstractClient::transientPlacement(const QRect &bounds) const { Q_UNUSED(bounds); Q_UNREACHABLE(); return QRect(); } 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) { Q_ASSERT(!m_transients.contains(cl)); Q_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()|| 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; } QRect AbstractClient::bufferGeometryBeforeUpdateBlocking() const { return m_bufferGeometryBeforeUpdateBlocking; } QRect AbstractClient::frameGeometryBeforeUpdateBlocking() const { return m_frameGeometryBeforeUpdateBlocking; } void AbstractClient::updateGeometryBeforeUpdateBlocking() { m_bufferGeometryBeforeUpdateBlocking = bufferGeometry(); m_frameGeometryBeforeUpdateBlocking = frameGeometry(); } void AbstractClient::doMove(int, int) { } void AbstractClient::updateInitialMoveResizeGeometry() { m_moveResize.initialGeometry = frameGeometry(); m_moveResize.geometry = m_moveResize.initialGeometry; m_moveResize.startScreen = screen(); } void AbstractClient::updateCursor() { Position m = moveResizePointerMode(); if (!isResizable() || isShade()) m = PositionCenter; CursorShape c = Qt::ArrowCursor; switch(m) { case PositionTopLeft: c = KWin::ExtendedCursor::SizeNorthWest; break; case PositionBottomRight: c = KWin::ExtendedCursor::SizeSouthEast; break; case PositionBottomLeft: c = KWin::ExtendedCursor::SizeSouthWest; break; case PositionTopRight: c = KWin::ExtendedCursor::SizeNorthEast; break; case PositionTop: c = KWin::ExtendedCursor::SizeNorth; break; case PositionBottom: c = KWin::ExtendedCursor::SizeSouth; break; case PositionLeft: c = KWin::ExtendedCursor::SizeWest; break; case PositionRight: c = KWin::ExtendedCursor::SizeEast; 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()->setMoveResizeClient(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: setMoveResizePointerButtonDown(false); finishMoveResize(false); updateCursor(); break; case Qt::Key_Escape: setMoveResizePointerButtonDown(false); finishMoveResize(true); 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 { 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::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(); } QRect AbstractClient::virtualKeyboardGeometry() const { return m_virtualKeyboardGeometry; } void AbstractClient::setVirtualKeyboardGeometry(const QRect &geo) { // No keyboard anymore if (geo.isEmpty() && !m_keyboardGeometryRestore.isEmpty()) { setFrameGeometry(m_keyboardGeometryRestore); m_keyboardGeometryRestore = QRect(); } else if (geo.isEmpty()) { return; // The keyboard has just been opened (rather than resized) save client geometry for a restore } else if (m_keyboardGeometryRestore.isEmpty()) { m_keyboardGeometryRestore = frameGeometry(); } m_virtualKeyboardGeometry = geo; // Don't resize Desktop and fullscreen windows if (isFullScreen() || isDesktop()) { return; } if (!geo.intersects(m_keyboardGeometryRestore)) { return; } const QRect availableArea = workspace()->clientArea(MaximizeArea, this); QRect newWindowGeometry = m_keyboardGeometryRestore; newWindowGeometry.moveBottom(geo.top()); newWindowGeometry.setTop(qMax(newWindowGeometry.top(), availableArea.top())); setFrameGeometry(newWindowGeometry); } bool AbstractClient::dockWantsInput() const { return false; } void AbstractClient::setDesktopFileName(QByteArray name) { name = rules()->checkDesktopFile(name).toUtf8(); if (name == m_desktopFileName) { return; } m_desktopFileName = name; updateWindowRules(Rules::DesktopFile); 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); } bool AbstractClient::groupTransient() const { return false; } const Group *AbstractClient::group() const { return nullptr; } Group *AbstractClient::group() { return nullptr; } bool AbstractClient::isInternal() const { return false; } bool AbstractClient::supportsWindowRules() const { return true; } QMargins AbstractClient::frameMargins() const { return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom()); } QPoint AbstractClient::framePosToClientPos(const QPoint &point) const { return point + QPoint(borderLeft(), borderTop()); } QPoint AbstractClient::clientPosToFramePos(const QPoint &point) const { return point - QPoint(borderLeft(), borderTop()); } QSize AbstractClient::frameSizeToClientSize(const QSize &size) const { const int width = size.width() - borderLeft() - borderRight(); const int height = size.height() - borderTop() - borderBottom(); return QSize(width, height); } QSize AbstractClient::clientSizeToFrameSize(const QSize &size) const { const int width = size.width() + borderLeft() + borderRight(); const int height = size.height() + borderTop() + borderBottom(); return QSize(width, height); } QRect AbstractClient::frameRectToClientRect(const QRect &rect) const { const QPoint position = framePosToClientPos(rect.topLeft()); const QSize size = frameSizeToClientSize(rect.size()); return QRect(position, size); } QRect AbstractClient::clientRectToFrameRect(const QRect &rect) const { const QPoint position = clientPosToFramePos(rect.topLeft()); const QSize size = clientSizeToFrameSize(rect.size()); return QRect(position, size); } +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 window. + if (!isResizable()) { + return; + } + + workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event + + GeometryUpdatesBlocker blocker(this); + + if (mode == QuickTileMode(QuickTileFlag::Maximize)) { + 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 (frameGeometry().top() != clientArea.top()) { + QRect r(frameGeometry()); + r.moveTop(clientArea.top()); + setFrameGeometry(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) { + + if (mode != QuickTileMode(QuickTileFlag::None)) { + // 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); + + setFrameGeometry(electricBorderMaximizeGeometry(keyboard ? frameGeometry().center() : Cursor::pos(), desktop()), geom_mode); + // Store the mode change + m_quickTileMode = mode; + } else { + m_quickTileMode = mode; + setMaximize(false, false); + } + + emit quickTileModeChanged(); + + return; + } + + if (mode != QuickTileMode(QuickTileFlag::None)) { + QPoint whichScreen = keyboard ? frameGeometry().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 + setFrameGeometry(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(frameGeometry()); + } + + 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); + setFrameGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode); + } + + // Store the mode change + m_quickTileMode = mode; + } + + if (mode == QuickTileMode(QuickTileFlag::None)) { + 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(frameGeometry()); + // decorations may turn off some borders when tiled + const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; + setFrameGeometry(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 = frameGeometry(); + 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); + setFrameGeometry(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(frameGeometry()); + + checkWorkspacePosition(oldGeom); + + // re-align geom_restore to constrained geometry + setGeometryRestore(frameGeometry()); + + // 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); +} + +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 = frameGeometry(); + 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 (frameGeometry() != area) + setFrameGeometry(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 = frameGeometry(); + checkOffscreenPosition(&geom, screenArea); + setFrameGeometry(geom); + return; + } + + if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { + setFrameGeometry(electricBorderMaximizeGeometry(frameGeometry().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; + if( workspace()->inUpdateClientArea()) { + // we need to find the screen area as it was before the change + oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight()); + 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); + } + const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height + const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.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(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height + const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.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 + + for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) { + QRect rect = r & oldGeomTall; + if (!rect.isEmpty()) + oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); + } + for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) { + QRect rect = r & oldGeomWide; + if (!rect.isEmpty()) + oldRightMax = qMin(oldRightMax, rect.x()); + } + for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) { + QRect rect = r & oldGeomTall; + if (!rect.isEmpty()) + oldBottomMax = qMin(oldBottomMax, rect.y()); + } + for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) { + QRect rect = r & oldGeomWide; + if (!rect.isEmpty()) + oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); + } + + // These 4 compute new bounds + for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaTop)) { + QRect rect = r & newGeomTall; + if (!rect.isEmpty()) + topMax = qMax(topMax, rect.y() + rect.height()); + } + for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaRight)) { + QRect rect = r & newGeomWide; + if (!rect.isEmpty()) + rightMax = qMin(rightMax, rect.x()); + } + for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaBottom)) { + QRect rect = r & newGeomTall; + if (!rect.isEmpty()) + bottomMax = qMin(bottomMax, rect.y()); + } + for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaLeft)) { + 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 != frameGeometry()) + setFrameGeometry(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); + } +} + +QSize AbstractClient::adjustedSize(const QSize& frame, Sizemode mode) const +{ + // first, get the window size for the given frame size s + QSize wsize = frameSizeToClientSize(frame); + 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 X11Client::setGeometry() +QSize AbstractClient::adjustedSize() const +{ + return sizeForClientSize(clientSize()); +} + } diff --git a/geometry.cpp b/geometry.cpp deleted file mode 100644 index cfd7e8bc6..000000000 --- a/geometry.cpp +++ /dev/null @@ -1,3406 +0,0 @@ -/******************************************************************** - 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 "x11client.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 "internal_client.h" -#include -#include -#include - -#include "outline.h" -#include "xdgshellclient.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 @c 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). - * - * @see 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 (auto 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 completely - 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 completely - if (!geo.isEmpty()) { - new_sareas[(*it)->desktop()][ iS ] = geo; - } - } - } - } - if (waylandServer()) { - auto updateStrutsForWaylandClient = [&] (XdgShellClient *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->frameGeometry())) { - return margins; - } - // figure out which areas of the overall screen setup it borders - const bool left = c->frameGeometry().left() == geometry.left(); - const bool right = c->frameGeometry().right() == geometry.right(); - const bool top = c->frameGeometry().top() == geometry.top(); - const bool bottom = c->frameGeometry().bottom() == geometry.bottom(); - const bool horizontal = c->frameGeometry().width() >= c->frameGeometry().height(); - if (left && ((!top && !bottom) || !horizontal)) { - margins.setLeft(c->frameGeometry().width()); - } - if (right && ((!top && !bottom) || !horizontal)) { - margins.setRight(c->frameGeometry().width()); - } - if (top && ((!left && !right) || horizontal)) { - margins.setTop(c->frameGeometry().height()); - } - if (bottom && ((!left && !right) || horizontal)) { - margins.setBottom(c->frameGeometry().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->frameGeometry(), 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); - } - } -#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(); - - 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 Screens::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->frameGeometry().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->frameGeometry(); - 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->frameGeometry(); - QMargins frameMargins = c->frameMargins(); - - // snap to titlebar / snap to window borders on inner screen edges - AbstractClient::Position titlePos = c->titlebarPosition(); - if (frameMargins.left() && (titlePos == AbstractClient::PositionLeft || (c->maximizeMode() & MaximizeHorizontal) || - screens()->intersecting(geo.translated(maxRect.x() - (frameMargins.left() + geo.x()), 0)) > 1)) { - frameMargins.setLeft(0); - } - if (frameMargins.right() && (titlePos == AbstractClient::PositionRight || (c->maximizeMode() & MaximizeHorizontal) || - screens()->intersecting(geo.translated(maxRect.right() + frameMargins.right() - geo.right(), 0)) > 1)) { - frameMargins.setRight(0); - } - if (frameMargins.top() && (titlePos == AbstractClient::PositionTop || (c->maximizeMode() & MaximizeVertical) || - screens()->intersecting(geo.translated(0, maxRect.y() - (frameMargins.top() + geo.y()))) > 1)) { - frameMargins.setTop(0); - } - if (frameMargins.bottom() && (titlePos == AbstractClient::PositionBottom || (c->maximizeMode() & MaximizeVertical) || - screens()->intersecting(geo.translated(0, maxRect.bottom() + frameMargins.bottom() - geo.bottom())) > 1)) { - frameMargins.setBottom(0); - } - if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) { - deltaX = xmin - cx; - nx = xmin - frameMargins.left(); - } - if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) { - deltaX = rx - xmax; - nx = xmax - cw + frameMargins.right(); - } - - if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) { - deltaY = ymin - cy; - ny = ymin - frameMargins.top(); - } - if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) { - deltaY = ry - ymax; - ny = ymax - ch + frameMargins.bottom(); - } - } - - // 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)->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 AbstractClient::PositionBottomRight: - SNAP_WINDOW_BOTTOM - SNAP_WINDOW_RIGHT - SNAP_WINDOW_C_BOTTOM - SNAP_WINDOW_C_RIGHT - break; - case AbstractClient::PositionRight: - SNAP_WINDOW_RIGHT - SNAP_WINDOW_C_RIGHT - break; - case AbstractClient::PositionBottom: - SNAP_WINDOW_BOTTOM - SNAP_WINDOW_C_BOTTOM - break; - case AbstractClient::PositionTopLeft: - SNAP_WINDOW_TOP - SNAP_WINDOW_LEFT - SNAP_WINDOW_C_TOP - SNAP_WINDOW_C_LEFT - break; - case AbstractClient::PositionLeft: - SNAP_WINDOW_LEFT - SNAP_WINDOW_C_LEFT - break; - case AbstractClient::PositionTop: - SNAP_WINDOW_TOP - SNAP_WINDOW_C_TOP - break; - case AbstractClient::PositionTopRight: - SNAP_WINDOW_TOP - SNAP_WINDOW_RIGHT - SNAP_WINDOW_C_TOP - SNAP_WINDOW_C_RIGHT - break; - case AbstractClient::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 or resized by the user. - */ -void Workspace::setMoveResizeClient(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, NET::Properties2()); - 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 X11Client::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 X11Client::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 X11Client::strutRect(StrutArea area) const -{ - Q_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 X11Client::strutRects() const -{ - StrutRects region; - region += strutRect(StrutAreaTop); - region += strutRect(StrutAreaRight); - region += strutRect(StrutAreaBottom); - region += strutRect(StrutAreaLeft); - return region; -} - -bool X11Client::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 X11Client::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 = frameGeometry(); - 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 (frameGeometry() != area) - setFrameGeometry(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 = frameGeometry(); - checkOffscreenPosition(&geom, screenArea); - setFrameGeometry(geom); - return; - } - - if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { - setFrameGeometry(electricBorderMaximizeGeometry(frameGeometry().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; - if( workspace()->inUpdateClientArea()) { - // we need to find the screen area as it was before the change - oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight()); - 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); - } - const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height - const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.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(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height - const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.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 - - for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) { - QRect rect = r & oldGeomTall; - if (!rect.isEmpty()) - oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); - } - for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) { - QRect rect = r & oldGeomWide; - if (!rect.isEmpty()) - oldRightMax = qMin(oldRightMax, rect.x()); - } - for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) { - QRect rect = r & oldGeomTall; - if (!rect.isEmpty()) - oldBottomMax = qMin(oldBottomMax, rect.y()); - } - for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) { - QRect rect = r & oldGeomWide; - if (!rect.isEmpty()) - oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); - } - - // These 4 compute new bounds - for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaTop)) { - QRect rect = r & newGeomTall; - if (!rect.isEmpty()) - topMax = qMax(topMax, rect.y() + rect.height()); - } - for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaRight)) { - QRect rect = r & newGeomWide; - if (!rect.isEmpty()) - rightMax = qMin(rightMax, rect.x()); - } - for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaBottom)) { - QRect rect = r & newGeomTall; - if (!rect.isEmpty()) - bottomMax = qMin(bottomMax, rect.y()); - } - for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaLeft)) { - 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 != frameGeometry()) - setFrameGeometry(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); - } -} - -QSize AbstractClient::adjustedSize(const QSize& frame, Sizemode mode) const -{ - // first, get the window size for the given frame size s - QSize wsize = frameSizeToClientSize(frame); - 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 X11Client::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 X11Client::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 = minSize(); - QSize max_size = 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; - } - - QSize size(w, h); - if (!noframe) { - size = clientSizeToFrameSize(size); - } - return rules()->checkSize(size); -} - -/** - * Gets the client's normal WM hints and reconfigures itself respectively. - */ -void X11Client::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); - } - if (isManaged()) { - // update to match restrictions - QSize new_size = adjustedSize(); - if (new_size != size() && !isFullScreen()) { - QRect origClientGeometry = m_clientGeometry; - 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 X11Client::minSize() const -{ - return rules()->checkMinSize(m_geometryHints.minSize()); -} - -QSize X11Client::maxSize() const -{ - return rules()->checkMaxSize(m_geometryHints.maxSize()); -} - -QSize X11Client::basicUnit() const -{ - return m_geometryHints.resizeIncrements(); -} - -/** - * Auxiliary function to inform the client about the current window - * configuration. - */ -void X11Client::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 = m_clientGeometry.x(); - c.y = m_clientGeometry.y(); - c.width = m_clientGeometry.width(); - c.height = m_clientGeometry.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()); -} - -QPoint X11Client::gravityAdjustment(xcb_gravity_t gravity) const -{ - int dx = 0; - int dy = 0; - - // dx, dy specify how the client window moves to make space for the frame. - // In general we have to compute the reference point and from that figure - // out how much we need to shift the client, however given that we ignore - // the border width attribute and the extents of the server-side decoration - // are known in advance, we can simplify the math quite a bit and express - // the required window gravity adjustment in terms of border sizes. - switch(gravity) { - case XCB_GRAVITY_NORTH_WEST: // move down right - default: - dx = borderLeft(); - dy = borderTop(); - break; - case XCB_GRAVITY_NORTH: // move right - dx = 0; - dy = borderTop(); - break; - case XCB_GRAVITY_NORTH_EAST: // move down left - dx = -borderRight(); - dy = borderTop(); - break; - case XCB_GRAVITY_WEST: // move right - dx = borderLeft(); - dy = 0; - break; - case XCB_GRAVITY_CENTER: - dx = (borderLeft() - borderRight()) / 2; - dy = (borderTop() - borderBottom()) / 2; - break; - case XCB_GRAVITY_STATIC: // don't move - dx = 0; - dy = 0; - break; - case XCB_GRAVITY_EAST: // move left - dx = -borderRight(); - dy = 0; - break; - case XCB_GRAVITY_SOUTH_WEST: // move up right - dx = borderLeft() ; - dy = -borderBottom(); - break; - case XCB_GRAVITY_SOUTH: // move up - dx = 0; - dy = -borderBottom(); - break; - case XCB_GRAVITY_SOUTH_EAST: // move up left - dx = -borderRight(); - dy = -borderBottom(); - break; - } - - return QPoint(dx, dy); -} - -const QPoint X11Client::calculateGravitation(bool invert) const -{ - const QPoint adjustment = gravityAdjustment(m_geometryHints.windowGravity()); - - // translate from client movement to frame movement - const int dx = adjustment.x() - borderLeft(); - const int dy = adjustment.y() - borderTop(); - - if (!invert) - return QPoint(x() + dx, y() + dy); - else - return QPoint(x() - dx, y() - dy); -} - -void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool) -{ - const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; - const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; - const int configureGeometryMask = configurePositionMask | configureSizeMask; - - // "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 & configureGeometryMask) << - 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 &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT); - if (maximizeMode() == MaximizeHorizontal) - value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH); - if (!(value_mask & configureGeometryMask)) { - 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 & configureGeometryMask); - - if (gravity == 0) // default (nonsense) value for the argument - gravity = m_geometryHints.windowGravity(); - if (value_mask & configurePositionMask) { - QPoint new_pos = framePosToClientPos(pos()); - new_pos -= gravityAdjustment(xcb_gravity_t(gravity)); - if (value_mask & XCB_CONFIG_WINDOW_X) { - new_pos.setX(rx); - } - if (value_mask & XCB_CONFIG_WINDOW_Y) { - 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() == m_clientGeometry.x() && new_pos.y() == m_clientGeometry.y() - && gravity == XCB_GRAVITY_NORTH_WEST && !from_tool) { - new_pos.setX(x()); - new_pos.setY(y()); - } - new_pos += gravityAdjustment(xcb_gravity_t(gravity)); - new_pos = clientPosToFramePos(new_pos); - - int nw = clientSize().width(); - int nh = clientSize().height(); - if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { - nw = rw; - } - if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { - 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 = m_clientGeometry; - GeometryUpdatesBlocker blocker(this); - move(new_pos); - plainResize(ns); - 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 - // X11Client::adjustedClientArea() - if (hasStrut()) - workspace() -> updateClientArea(); - } - - if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) { // pure resize - int nw = clientSize().width(); - int nh = clientSize().height(); - if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { - nw = rw; - } - if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { - nh = rh; - } - QSize ns = sizeForClientSize(QSize(nw, nh)); - - if (ns != size()) { // don't restore if some app sets its own size again - QRect origClientGeometry = m_clientGeometry; - GeometryUpdatesBlocker blocker(this); - resizeWithChecks(ns, xcb_gravity_t(gravity)); - 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 = frameGeometry(); - // 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 X11Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force) -{ - Q_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 XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move - default: - break; - case XCB_GRAVITY_NORTH: // middle of top border doesn't move - newx = (newx + width() / 2) - (w / 2); - break; - case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move - newx = newx + width() - w; - break; - case XCB_GRAVITY_WEST: // middle of left border doesn't move - newy = (newy + height() / 2) - (h / 2); - break; - case XCB_GRAVITY_CENTER: // middle point doesn't move - newx = (newx + width() / 2) - (w / 2); - newy = (newy + height() / 2) - (h / 2); - break; - case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move - // since decoration doesn't change, equal to NorthWestGravity - break; - case XCB_GRAVITY_EAST: // // middle of right border doesn't move - newx = newx + width() - w; - newy = (newy + height() / 2) - (h / 2); - break; - case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move - newy = newy + height() - h; - break; - case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move - newx = (newx + width() / 2) - (w / 2); - newy = newy + height() - h; - break; - case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move - newx = newx + width() - w; - newy = newy + height() - h; - break; - } - setFrameGeometry(newx, newy, w, h, force); -} - -// _NET_MOVERESIZE_WINDOW -void X11Client::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 |= XCB_CONFIG_WINDOW_X; - } - if (flags & (1 << 9)) { - value_mask |= XCB_CONFIG_WINDOW_Y; - } - if (flags & (1 << 10)) { - value_mask |= XCB_CONFIG_WINDOW_WIDTH; - } - if (flags & (1 << 11)) { - value_mask |= XCB_CONFIG_WINDOW_HEIGHT; - } - configureRequest(value_mask, x, y, width, height, gravity, true); -} - -bool X11Client::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; -} - -bool X11Client::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; -} - -bool X11Client::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 = minSize(); - QSize max = maxSize(); - return min.width() < max.width() || min.height() < max.height(); -} - -bool X11Client::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 X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t force) -{ - // this code is also duplicated in X11Client::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 X11Client::clientSize() - - QRect frameGeometry(x, y, w, h); - QRect bufferGeometry; - - if (shade_geometry_change) - ; // nothing - else if (isShade()) { - if (frameGeometry.height() == borderTop() + borderBottom()) { - qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; - } else { - m_clientGeometry = frameRectToClientRect(frameGeometry); - frameGeometry.setHeight(borderTop() + borderBottom()); - } - } else { - m_clientGeometry = frameRectToClientRect(frameGeometry); - } - if (isDecorated()) { - bufferGeometry = frameGeometry; - } else { - bufferGeometry = m_clientGeometry; - } - if (!areGeometryUpdatesBlocked() && frameGeometry != rules()->checkGeometry(frameGeometry)) { - qCDebug(KWIN_CORE) << "forced geometry fail:" << frameGeometry << ":" << rules()->checkGeometry(frameGeometry); - } - m_frameGeometry = frameGeometry; - if (force == NormalGeometrySet && m_bufferGeometry == bufferGeometry && pendingGeometryUpdate() == PendingGeometryNone) { - return; - } - m_bufferGeometry = bufferGeometry; - if (areGeometryUpdatesBlocked()) { - if (pendingGeometryUpdate() == PendingGeometryForced) - {} // maximum, nothing needed - else if (force == ForceGeometrySet) - setPendingGeometryUpdate(PendingGeometryForced); - else - setPendingGeometryUpdate(PendingGeometryNormal); - return; - } - updateServerGeometry(); - 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 the buffer size is changed. - if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) { - discardWindowPixmap(); - } - emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); - addRepaintDuringGeometryUpdates(); - updateGeometryBeforeUpdateBlocking(); - // TODO: this signal is emitted too often - emit geometryChanged(); -} - -void X11Client::plainResize(int w, int h, ForceGeometry_t force) -{ - QSize frameSize(w, h); - QSize bufferSize; - - // this code is also duplicated in X11Client::setGeometry(), and it's also commented there - if (shade_geometry_change) - ; // nothing - else if (isShade()) { - if (frameSize.height() == borderTop() + borderBottom()) { - qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; - } else { - m_clientGeometry.setSize(frameSizeToClientSize(frameSize)); - frameSize.setHeight(borderTop() + borderBottom()); - } - } else { - m_clientGeometry.setSize(frameSizeToClientSize(frameSize)); - } - if (isDecorated()) { - bufferSize = frameSize; - } else { - bufferSize = m_clientGeometry.size(); - } - if (!areGeometryUpdatesBlocked() && frameSize != rules()->checkSize(frameSize)) { - qCDebug(KWIN_CORE) << "forced size fail:" << frameSize << ":" << rules()->checkSize(frameSize); - } - m_frameGeometry.setSize(frameSize); - // resuming geometry updates is handled only in setGeometry() - Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); - if (force == NormalGeometrySet && m_bufferGeometry.size() == bufferSize) { - return; - } - m_bufferGeometry.setSize(bufferSize); - if (areGeometryUpdatesBlocked()) { - if (pendingGeometryUpdate() == PendingGeometryForced) - {} // maximum, nothing needed - else if (force == ForceGeometrySet) - setPendingGeometryUpdate(PendingGeometryForced); - else - setPendingGeometryUpdate(PendingGeometryNormal); - return; - } - updateServerGeometry(); - updateWindowRules(Rules::Position|Rules::Size); - screens()->setCurrent(this); - workspace()->updateStackingOrder(); - if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) { - discardWindowPixmap(); - } - emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); - addRepaintDuringGeometryUpdates(); - updateGeometryBeforeUpdateBlocking(); - // TODO: this signal is emitted too often - emit geometryChanged(); -} - -void X11Client::updateServerGeometry() -{ - if (m_frame.geometry().size() != m_bufferGeometry.size() || pendingGeometryUpdate() == PendingGeometryForced) { - resizeDecoration(); - m_frame.setGeometry(m_bufferGeometry); - 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(m_bufferGeometry.topLeft()); // sendSyntheticConfigureNotify() on finish shall be sufficient - } - } else { - m_frame.move(m_bufferGeometry.topLeft()); - sendSyntheticConfigureNotify(); - } - // Unconditionally move the input window: it won't affect rendering - m_decoInputExtent.move(pos() + inputPos()); - } -} - -/** - * 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() - Q_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 && m_frameGeometry.topLeft() == p) - return; - m_frameGeometry.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(); - emit geometryChanged(); -} - -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()) - setFrameGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet); - else - setFrameGeometry(frameGeometry(), NormalGeometrySet); - m_pendingGeometryUpdate = PendingGeometryNone; - } - } - } -} - -void AbstractClient::maximize(MaximizeMode m) -{ - setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); -} - -void AbstractClient::setMaximize(bool vertically, bool horizontally) -{ - // changeMaximize() flips the state, so change from set->flip - const MaximizeMode oldMode = maximizeMode(); - changeMaximize( - oldMode & MaximizeHorizontal ? !horizontally : horizontally, - oldMode & MaximizeVertical ? !vertically : vertically, - false); - const MaximizeMode newMode = maximizeMode(); - if (oldMode != newMode) { - emit clientMaximizedStateChanged(this, newMode); - emit clientMaximizedStateChanged(this, vertically, horizontally); - } - -} - -static bool changeMaximizeRecursion = false; -void X11Client::changeMaximize(bool horizontal, bool vertical, 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); - - // 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 { - setFrameGeometry(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)); - setFrameGeometry(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 { - setFrameGeometry(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)); - setFrameGeometry(r, geom_mode); - } - info->setState(NET::MaxHoriz, NET::Max); - break; - } - - case MaximizeRestore: { - QRect restore = frameGeometry(); - // 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 = frameGeometry(); - 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)); - } - setFrameGeometry(restore, geom_mode); - if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen - Placement::self()->place(this, clientArea); - info->setState(NET::States(), 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())); - } - setFrameGeometry(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; - } - - updateAllowedActions(); - updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size); - emit quickTileModeChanged(); -} - -bool X11Client::userCanSetFullScreen() const -{ - if (!isFullScreenable()) { - return false; - } - return isNormalWindow() || isDialog(); -} - -void X11Client::setFullScreen(bool set, bool user) -{ - set = rules()->checkFullScreen(set); - - const bool wasFullscreen = isFullScreen(); - if (wasFullscreen == set) { - return; - } - if (user && !userCanSetFullScreen()) { - return; - } - - setShade(ShadeNone); - - if (wasFullscreen) { - workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event - } else { - geom_fs_restore = frameGeometry(); - } - - if (set) { - m_fullscreenMode = FullScreenNormal; - workspace()->raiseClient(this); - } else { - m_fullscreenMode = FullScreenNone; - } - - StackingUpdatesBlocker blocker1(workspace()); - GeometryUpdatesBlocker blocker2(this); - - // active fullscreens get different layer - workspace()->updateClientLayer(this); - - info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen); - updateDecoration(false, false); - - if (set) { - if (info->fullscreenMonitors().isSet()) { - setFrameGeometry(fullscreenMonitorsArea(info->fullscreenMonitors())); - } else { - setFrameGeometry(workspace()->clientArea(FullScreenArea, this)); - } - } else { - Q_ASSERT(!geom_fs_restore.isNull()); - const int currentScreen = screen(); - setFrameGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size()))); - if(currentScreen != screen()) { - workspace()->sendClientToScreen(this, currentScreen); - } - } - - updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size); - emit clientFullScreenSet(this, set, user); - emit fullScreenChanged(); -} - - -void X11Client::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()) - setFrameGeometry(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 X11Client::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; -} - -static GeometryTip* geometryTip = nullptr; - -void X11Client::positionGeometryTip() -{ - Q_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() -{ - Q_ASSERT(!isMoveResize()); - Q_ASSERT(QWidget::keyboardGrabber() == nullptr); - Q_ASSERT(QWidget::mouseGrabber() == nullptr); - stopDelayedMoveResize(); - if (QApplication::activePopupWidget() != nullptr) - return false; // popups have grab - if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens())) - return false; - if (!doStartMoveResize()) { - return false; - } - - invalidateDecorationDoubleClickTimer(); - - setMoveResize(true); - workspace()->setMoveResizeClient(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(frameGeometry()); // "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(frameGeometry()); - emit quickTileModeChanged(); - } - - updateHaveResizeEffect(); - updateInitialMoveResizeGeometry(); - checkUnrestrictedMoveResize(); - emit clientStartUserMovedResized(this); - if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) - ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal); - return true; -} - -bool X11Client::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* - // (https://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, nullptr, 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, nullptr)); - 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) -{ - GeometryUpdatesBlocker blocker(this); - const bool wasResize = isResize(); // store across leaveMoveResize - leaveMoveResize(); - - if (cancel) - setFrameGeometry(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(restoreH, restoreV, false); - } - } - setFrameGeometry(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(frameGeometry().x()); - geom_restore.setWidth(frameGeometry().width()); - } - if (!(maximizeMode() & MaximizeVertical)) { - geom_restore.setY(frameGeometry().y()); - geom_restore.setHeight(frameGeometry().height()); - } - setGeometryRestore(geom_restore); - } -// FRAME update(); - - emit clientFinishUserMovedResized(this); -} - -void X11Client::leaveMoveResize() -{ - if (needsXWindowMove) { - // Do the deferred move - m_frame.move(m_bufferGeometry.topLeft()); - needsXWindowMove = false; - } - if (!isResize()) - sendSyntheticConfigureNotify(); // tell the client about it's new final position - if (geometryTip) { - geometryTip->hide(); - delete geometryTip; - geometryTip = nullptr; - } - 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 = nullptr; - 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]() { - Q_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 = frameGeometry(); - handleMoveResize(local.x(), local.y(), global.x(), global.y()); - if (!isFullScreen() && isMove()) { - if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != frameGeometry()) { - 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 X11Client::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; - for (const QRect &rect : availableArea) { - 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()) { - Q_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; - for (const QRect &rect : availableArea) { - 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; - for (const QRect &r : strut) { - 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 X11Client::doResizeSync() -{ - if (!syncRequest.timeout) { - syncRequest.timeout = new QTimer(this); - connect(syncRequest.timeout, &QTimer::timeout, this, &X11Client::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 moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry()); - m_client.setGeometry(0, 0, moveResizeClientGeometry.width(), moveResizeClientGeometry.height()); -} - -void AbstractClient::performMoveResize() -{ - const QRect &moveResizeGeom = moveResizeGeometry(); - if (isMove() || (isResize() && !haveResizeEffect())) { - setFrameGeometry(moveResizeGeom); - } - doPerformMoveResize(); - if (isResize()) - addRepaintFull(); - positionGeometryTip(); - emit clientStepUserMovedResized(this, moveResizeGeom); -} - -void X11Client::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 window. - if (!isResizable()) { - return; - } - - workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event - - GeometryUpdatesBlocker blocker(this); - - if (mode == QuickTileMode(QuickTileFlag::Maximize)) { - 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 (frameGeometry().top() != clientArea.top()) { - QRect r(frameGeometry()); - r.moveTop(clientArea.top()); - setFrameGeometry(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) { - - if (mode != QuickTileMode(QuickTileFlag::None)) { - // 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); - - setFrameGeometry(electricBorderMaximizeGeometry(keyboard ? frameGeometry().center() : Cursor::pos(), desktop()), geom_mode); - // Store the mode change - m_quickTileMode = mode; - } else { - m_quickTileMode = mode; - setMaximize(false, false); - } - - emit quickTileModeChanged(); - - return; - } - - if (mode != QuickTileMode(QuickTileFlag::None)) { - QPoint whichScreen = keyboard ? frameGeometry().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 - setFrameGeometry(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(frameGeometry()); - } - - 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); - setFrameGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode); - } - - // Store the mode change - m_quickTileMode = mode; - } - - if (mode == QuickTileMode(QuickTileFlag::None)) { - 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(frameGeometry()); - // decorations may turn off some borders when tiled - const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; - setFrameGeometry(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 = frameGeometry(); - 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); - setFrameGeometry(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(frameGeometry()); - - checkWorkspacePosition(oldGeom); - - // re-align geom_restore to constrained geometry - setGeometryRestore(frameGeometry()); - - // 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/workspace.cpp b/workspace.cpp index f434be5fa..21f9a11f6 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,1941 +1,2781 @@ /******************************************************************** 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) 2019 Vlad Zahorodnii 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 "x11client.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 "internal_client.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 "xdgshellclient.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 (X11Client *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 = nullptr; Workspace::Workspace(const QString &sessionKey) : QObject(nullptr) , m_compositor(nullptr) // Unsorted , active_popup(nullptr) , active_popup_client(nullptr) , m_initialDesktop(1) , active_client(nullptr) , last_active_client(nullptr) , most_recently_raised(nullptr) , movingClient(nullptr) , delayfocus_client(nullptr) , force_restacking(false) , showing_desktop(false) , was_user_interaction(false) , block_focus(0) , m_userActionsMenu(new UserActionsMenu(this)) , client_keys_dialog(nullptr) , client_keys_client(nullptr) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) , startup(nullptr) , set_active_client_recursion(0) , block_stacking_updates(0) , m_sessionManager(new SessionManager(this)) { // 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 = nullptr; if (!sessionKey.isEmpty()) loadSessionInfo(sessionKey); 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); //dbus interface new VirtualDesktopManagerDBusInterface(VirtualDesktopManager::self()); #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 { Q_ASSERT(kwinApp()->operationMode() == Application::OperationMode::OperationModeX11); m_compositor = X11Compositor::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, &VirtualDesktopManager::desktopRemoved, this, [this](KWin::VirtualDesktop *desktop) { //Wayland if (kwinApp()->operationMode() == Application::OperationModeWaylandOnly || kwinApp()->operationMode() == Application::OperationModeXwayland) { for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) { if (!(*it)->desktops().contains(desktop)) { continue; } if ((*it)->desktops().count() > 1) { (*it)->leaveDesktop(desktop); } else { sendClientToDesktop(*it, qMin(desktop->x11DesktopNumber(), VirtualDesktopManager::self()->count()), true); } } //X11 } else { 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); } } } } ); 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(); //makes sure any autogenerated id is saved, necessary as in case of xwayland, load will be called 2 times // load is needed to be called again when starting xwayalnd to sync to RootInfo, see BUG 385260 vds->save(); 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 = nullptr; initWithX11(); Scripting::create(this); if (auto w = waylandServer()) { connect(w, &WaylandServer::shellClientAdded, this, [this] (XdgShellClient *c) { setupClientConnections(c); c->updateDecoration(false); updateClientLayer(c); if (!c->isInternal()) { const QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop()); bool placementDone = false; if (c->isInitialPositionSet()) { placementDone = true; } if (c->isFullScreen()) { placementDone = true; } if (c->maximizeMode() == MaximizeMode::MaximizeFull) { placementDone = true; } if (c->rules()->checkPosition(invalidPoint, true) != invalidPoint) { 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); } updateTabbox(); connect(c, &XdgShellClient::windowShown, this, [this, c] { updateClientLayer(c); // TODO: when else should we send the client through placement? if (c->hasTransientPlacementHint()) { const QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop()); c->placeIn(area); } markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); if (c->wantsInput()) { activateClient(c); } } ); connect(c, &XdgShellClient::windowHidden, this, [this] { // TODO: update tabbox if it's displayed markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); } ); } ); connect(w, &WaylandServer::shellClientRemoved, this, [this] (XdgShellClient *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(); updateTabbox(); } ); } // 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(); rootInfo->setCurrentDesktop(vds->currentDesktop()->x11DesktopNumber()); // 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(); vds->setCurrent(m_initialDesktop); } // 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 == nullptr && activeClient() == nullptr && should_get_focus.count() == 0) { // No client activated in manage() if (new_active_client == nullptr) new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->current(), -1); if (new_active_client == nullptr && !desktops.isEmpty()) new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); } if (new_active_client != nullptr) activateClient(new_active_client); } Workspace::~Workspace() { blockStackingUpdates(true); // TODO: grabXServer(); // Use stacking_order, so that kwin --replace keeps stacking order const QList 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 (auto it = stack.constBegin(), end = stack.constEnd(); it != end; ++it) { X11Client *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); } X11Client::cleanupX11(); if (waylandServer()) { const QList shellClients = waylandServer()->clients(); for (XdgShellClient *shellClient : shellClients) { shellClient->destroyClient(); } } for (auto it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(ReleaseReason::KWinShutsDown); for (InternalClient *client : m_internalClients) { client->destroyClient(); } 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 = nullptr; } 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)); } X11Client *Workspace::createClient(xcb_window_t w, bool is_mapped) { StackingUpdatesBlocker blocker(this); X11Client *c = new X11Client(); setupClientConnections(c); if (X11Compositor *compositor = X11Compositor::self()) { connect(c, &X11Client::blockingCompositingChanged, compositor, &X11Compositor::updateClientCompositeBlocking); } connect(c, SIGNAL(clientFullScreenSet(KWin::X11Client *,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking())); if (!c->manage(w, is_mapped)) { X11Client::deleteClient(c); return nullptr; } addClient(c); return c; } Unmanaged* Workspace::createUnmanaged(xcb_window_t w) { if (X11Compositor *compositor = X11Compositor::self()) { if (compositor->checkForOverlayWindow(w)) { return nullptr; } } Unmanaged* c = new Unmanaged(); if (!c->track(w)) { Unmanaged::deleteUnmanaged(c); return nullptr; } connect(c, &Unmanaged::needsRepaint, m_compositor, &Compositor::scheduleRepaint); addUnmanaged(c); emit unmanagedAdded(c); return c; } void Workspace::addClient(X11Client *c) { Group* grp = findGroup(c->window()); emit clientAdded(c); if (grp != nullptr) grp->gotLeader(c); if (c->isDesktop()) { desktops.append(c); if (active_client == nullptr && 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() == nullptr && 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); updateTabbox(); } void Workspace::addUnmanaged(Unmanaged* c) { unmanaged.append(c); markXStackingOrderAsDirty(); } /** * Destroys the client \a c */ void Workspace::removeClient(X11Client *c) { if (c == active_popup_client) closeActivePopup(); if (m_userActionsMenu->isMenuClient(c)) { m_userActionsMenu->close(); } 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 } 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 != nullptr) group->lostLeader(); if (c == most_recently_raised) most_recently_raised = nullptr; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) last_active_client = nullptr; if (c == delayfocus_client) cancelDelayFocus(); emit clientRemoved(c); updateStackingOrder(true); updateClientArea(); updateTabbox(); } void Workspace::removeUnmanaged(Unmanaged* c) { Q_ASSERT(unmanaged.contains(c)); unmanaged.removeAll(c); emit unmanagedRemoved(c); markXStackingOrderAsDirty(); } void Workspace::addDeleted(Deleted* c, Toplevel *orig) { Q_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, &Deleted::needsRepaint, m_compositor, &Compositor::scheduleRepaint); } void Workspace::removeDeleted(Deleted* c) { Q_ASSERT(deleted.contains(c)); emit deletedRemoved(c); deleted.removeAll(c); unconstrained_stacking_order.removeAll(c); stacking_order.removeAll(c); markXStackingOrderAsDirty(); if (!c->wasClient()) { return; } if (X11Compositor *compositor = X11Compositor::self()) { compositor->updateClientCompositeBlocking(); } } 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 (auto it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->hideClient(false); return; } const Group* group = nullptr; auto client = 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 != nullptr) { if (!client->isTransient()) break; if (client->groupTransient()) { group = client->group(); break; } client = 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? QVector to_show, to_hide; for (auto it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { auto c = qobject_cast(*it); if (!c) { continue; } if (c->isUtility() || c->isMenu() || c->isToolbar()) { bool show = true; if (!c->isTransient()) { if (!c->group() || c->group()->members().count() == 1) // Has its own group, keep always visible show = true; else if (client != nullptr && c->group() == client->group()) show = true; else show = false; } else { if (group != nullptr && c->group() == group) show = true; else if (client != nullptr && 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 (auto 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 (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 (auto it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { X11Client *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) { X11Client *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 = nullptr; 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 == nullptr && !desktops.isEmpty()) c = findDesktop(true, desktop); if (c != active_client) setActiveClient(nullptr); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, desktop)); else focusToNull(); } AbstractClient *Workspace::findClientToActivateOnDesktop(uint desktop) { if (movingClient != nullptr && 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()) { auto it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { X11Client *client = qobject_cast(*(--it)); if (!client) { continue; } if (!(client->isShown(false) && client->isOnDesktop(desktop) && client->isOnCurrentActivity() && client->isOnActiveScreen())) continue; if (client->frameGeometry().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 (auto it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { X11Client *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) { X11Client *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 = nullptr; //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 == nullptr && !desktops.isEmpty()) c = findDesktop(true, VirtualDesktopManager::self()->current()); if (c != active_client) setActiveClient(nullptr); 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::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 ); auto transients_stacking_order = ensureStackingOrder(c->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 = nullptr; } 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 (auto group = c->group()) { foreach (X11Client *cm, 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 (auto 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. https://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 https://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_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_EGL_STREAMS: ")); #if HAVE_EGL_STREAMS 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")); 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")); } 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")); 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; } X11Client *Workspace::findClient(std::function func) const { if (X11Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (X11Client *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 (X11Client *ret = Toplevel::findInList(desktops, func)) { return ret; } if (InternalClient *ret = Toplevel::findInList(m_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; }); } X11Client *Workspace::findClient(Predicate predicate, xcb_window_t w) const { switch (predicate) { case Predicate::WindowMatch: return findClient([w](const X11Client *c) { return c->window() == w; }); case Predicate::WrapperIdMatch: return findClient([w](const X11Client *c) { return c->wrapperId() == w; }); case Predicate::FrameIdMatch: return findClient([w](const X11Client *c) { return c->frameId() == w; }); case Predicate::InputIdMatch: return findClient([w](const X11Client *c) { return c->inputId() == w; }); } return nullptr; } Toplevel *Workspace::findToplevel(std::function func) const { if (X11Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (X11Client *ret = Toplevel::findInList(desktops, func)) { return ret; } if (Unmanaged *ret = Toplevel::findInList(unmanaged, func)) { return ret; } if (InternalClient *ret = Toplevel::findInList(m_internalClients, func)) { return ret; } return nullptr; } void Workspace::forEachToplevel(std::function func) { std::for_each(m_allClients.constBegin(), m_allClients.constEnd(), func); std::for_each(desktops.constBegin(), desktops.constEnd(), func); std::for_each(deleted.constBegin(), deleted.constEnd(), func); std::for_each(unmanaged.constBegin(), unmanaged.constEnd(), func); std::for_each(m_internalClients.constBegin(), m_internalClients.constEnd(), func); } 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); std::for_each(m_internalClients.constBegin(), m_internalClients.constEnd(), func); } Toplevel *Workspace::findInternal(QWindow *w) const { if (!w) { return nullptr; } if (kwinApp()->operationMode() == Application::OperationModeX11) { return findUnmanaged(w->winId()); } for (InternalClient *client : m_internalClients) { if (client->internalWindow() == w) { return client; } } return nullptr; } bool Workspace::compositing() const { return m_compositor && m_compositor->scene(); } 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(); } ); } void Workspace::updateTabbox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox->isDisplayed()) { tabBox->reset(true); } #endif } void Workspace::addInternalClient(InternalClient *client) { m_internalClients.append(client); setupClientConnections(client); client->updateLayer(); if (client->isDecorated()) { client->keepInArea(clientArea(FullScreenArea, client)); } markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); emit internalClientAdded(client); } void Workspace::removeInternalClient(InternalClient *client) { m_internalClients.removeOne(client); markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); emit internalClientRemoved(client); } Group* Workspace::findGroup(xcb_window_t leader) const { Q_ASSERT(leader != XCB_WINDOW_NONE); for (auto it = groups.constBegin(); it != groups.constEnd(); ++it) if ((*it)->leader() == leader) return *it; return nullptr; } // Client is group transient, but has no group set. Try to find // group with windows with the same client leader. Group* Workspace::findClientLeaderGroup(const X11Client *c) const { Group* ret = nullptr; for (auto it = clients.constBegin(); it != clients.constEnd(); ++it) { if (*it == c) continue; if ((*it)->wmClientLeader() == c->wmClientLeader()) { if (ret == nullptr || 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. QList old_group = (*it)->group()->members(); // old_group autodeletes when being empty for (int pos = 0; pos < old_group.count(); ++pos) { X11Client *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 (auto it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->checkTransient(w); } +/** + * 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 @c 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). + * + * @see 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 (auto 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 completely + 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 completely + if (!geo.isEmpty()) { + new_sareas[(*it)->desktop()][ iS ] = geo; + } + } + } + } + if (waylandServer()) { + auto updateStrutsForWaylandClient = [&] (XdgShellClient *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->frameGeometry())) { + return margins; + } + // figure out which areas of the overall screen setup it borders + const bool left = c->frameGeometry().left() == geometry.left(); + const bool right = c->frameGeometry().right() == geometry.right(); + const bool top = c->frameGeometry().top() == geometry.top(); + const bool bottom = c->frameGeometry().bottom() == geometry.bottom(); + const bool horizontal = c->frameGeometry().width() >= c->frameGeometry().height(); + if (left && ((!top && !bottom) || !horizontal)) { + margins.setLeft(c->frameGeometry().width()); + } + if (right && ((!top && !bottom) || !horizontal)) { + margins.setRight(c->frameGeometry().width()); + } + if (top && ((!left && !right) || horizontal)) { + margins.setTop(c->frameGeometry().height()); + } + if (bottom && ((!left && !right) || horizontal)) { + margins.setBottom(c->frameGeometry().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->frameGeometry(), 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); + } + } +#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(); + + 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 Screens::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->frameGeometry().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->frameGeometry(); + 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->frameGeometry(); + QMargins frameMargins = c->frameMargins(); + + // snap to titlebar / snap to window borders on inner screen edges + AbstractClient::Position titlePos = c->titlebarPosition(); + if (frameMargins.left() && (titlePos == AbstractClient::PositionLeft || (c->maximizeMode() & MaximizeHorizontal) || + screens()->intersecting(geo.translated(maxRect.x() - (frameMargins.left() + geo.x()), 0)) > 1)) { + frameMargins.setLeft(0); + } + if (frameMargins.right() && (titlePos == AbstractClient::PositionRight || (c->maximizeMode() & MaximizeHorizontal) || + screens()->intersecting(geo.translated(maxRect.right() + frameMargins.right() - geo.right(), 0)) > 1)) { + frameMargins.setRight(0); + } + if (frameMargins.top() && (titlePos == AbstractClient::PositionTop || (c->maximizeMode() & MaximizeVertical) || + screens()->intersecting(geo.translated(0, maxRect.y() - (frameMargins.top() + geo.y()))) > 1)) { + frameMargins.setTop(0); + } + if (frameMargins.bottom() && (titlePos == AbstractClient::PositionBottom || (c->maximizeMode() & MaximizeVertical) || + screens()->intersecting(geo.translated(0, maxRect.bottom() + frameMargins.bottom() - geo.bottom())) > 1)) { + frameMargins.setBottom(0); + } + if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) { + deltaX = xmin - cx; + nx = xmin - frameMargins.left(); + } + if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) { + deltaX = rx - xmax; + nx = xmax - cw + frameMargins.right(); + } + + if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) { + deltaY = ymin - cy; + ny = ymin - frameMargins.top(); + } + if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) { + deltaY = ry - ymax; + ny = ymax - ch + frameMargins.bottom(); + } + } + + // 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)->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 AbstractClient::PositionBottomRight: + SNAP_WINDOW_BOTTOM + SNAP_WINDOW_RIGHT + SNAP_WINDOW_C_BOTTOM + SNAP_WINDOW_C_RIGHT + break; + case AbstractClient::PositionRight: + SNAP_WINDOW_RIGHT + SNAP_WINDOW_C_RIGHT + break; + case AbstractClient::PositionBottom: + SNAP_WINDOW_BOTTOM + SNAP_WINDOW_C_BOTTOM + break; + case AbstractClient::PositionTopLeft: + SNAP_WINDOW_TOP + SNAP_WINDOW_LEFT + SNAP_WINDOW_C_TOP + SNAP_WINDOW_C_LEFT + break; + case AbstractClient::PositionLeft: + SNAP_WINDOW_LEFT + SNAP_WINDOW_C_LEFT + break; + case AbstractClient::PositionTop: + SNAP_WINDOW_TOP + SNAP_WINDOW_C_TOP + break; + case AbstractClient::PositionTopRight: + SNAP_WINDOW_TOP + SNAP_WINDOW_RIGHT + SNAP_WINDOW_C_TOP + SNAP_WINDOW_C_RIGHT + break; + case AbstractClient::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 or resized by the user. + */ +void Workspace::setMoveResizeClient(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, NET::Properties2()); + 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); + } +} + } // namespace diff --git a/x11client.cpp b/x11client.cpp index 30e62711f..3e645e84b 100644 --- a/x11client.cpp +++ b/x11client.cpp @@ -1,3546 +1,4947 @@ /******************************************************************** 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 "x11client.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 "effects.h" #include "focuschain.h" +#include "geometrytip.h" #include "group.h" #include "netinfo.h" #include "screens.h" #include "shadow.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "workspace.h" #include "screenedge.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include // KDE #include #include #include #include // Qt #include #include #include #include #include #include #include // xcb #include // system #include // c++ #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; // window types that are supported as normal windows (i.e. KWin actually manages them) const NET::WindowTypes SUPPORTED_MANAGED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask /*| NET::OverrideMask*/ | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask | NET::NotificationMask | NET::OnScreenDisplayMask | NET::CriticalNotificationMask; // 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 x11client.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(). */ X11Client::X11Client() : 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(nullptr) , m_motif(atoms->motif_wm_hints) , blocks_compositing(false) , shadeHoverTimer(nullptr) , m_colormap(XCB_COLORMAP_NONE) , in_group(nullptr) , ping_timer(nullptr) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet , allowed_actions() , shade_geometry_change(false) , sm_stacking_order(-1) , activitiesDefined(false) , sessionActivityOverride(false) , needsXWindowMove(false) , m_decoInputExtent() , m_focusOutTimer(nullptr) { // TODO: Do all as initialization syncRequest.counter = syncRequest.alarm = XCB_NONE; syncRequest.timeout = syncRequest.failsafeTimeout = nullptr; syncRequest.lastTimestamp = xTime(); syncRequest.isPending = false; // Set the initial mapping state mapping_state = Withdrawn; info = nullptr; shade_mode = ShadeNone; deleting = false; m_fullscreenMode = 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 m_frameGeometry = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) connect(clientMachine(), &ClientMachine::localhostChanged, this, &X11Client::updateCaption); connect(options, &Options::condensedTitleChanged, this, &X11Client::updateCaption); connect(this, &X11Client::moveResizeCursorChanged, this, [this] (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. */ X11Client::~X11Client() { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } if (syncRequest.alarm != XCB_NONE) xcb_sync_destroy_alarm(connection(), syncRequest.alarm); Q_ASSERT(!isMoveResize()); Q_ASSERT(m_client == XCB_WINDOW_NONE); Q_ASSERT(m_wrapper == XCB_WINDOW_NONE); Q_ASSERT(m_frame == XCB_WINDOW_NONE); Q_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 X11Client::deleteClient(X11Client *c) { delete c; } /** * Releases the window. The client has done its job and the window is still existing. */ void X11Client::releaseWindow(bool on_shutdown) { Q_ASSERT(!deleting); deleting = true; #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox->isDisplayed() && tabBox->currentClient() == this) { tabBox->nextPrev(true); } #endif destroyWindowManagementInterface(); Deleted* del = nullptr; 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 (https://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) grabXServer(); exportMappingState(XCB_ICCCM_WM_STATE_WITHDRAWN); 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(NET::States(), info->state()); // Reset all state flags } 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(), m_bufferGeometry.x(), m_bufferGeometry.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(); 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 X11Client::destroyClient() { Q_ASSERT(!deleting); deleting = true; #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) { tabBox->nextPrev(true); } #endif 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(); unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry disownDataPassedToDeleted(); del->unrefWindow(); deleteClient(this); } /** * Manages the clients. This means handling the very first maprequest: * reparenting, initial geometry, initial state, placement, etc. * Returns false if KWin is not going to manage this window. */ bool X11Client::manage(xcb_window_t w, bool isMapped) { StackingUpdatesBlocker stacking_blocker(workspace()); Xcb::WindowAttributes attr(w); Xcb::WindowGeometry windowGeometry(w); if (attr.isNull() || windowGeometry.isNull()) { return false; } // From this place on, manage() must not return false blockGeometryUpdates(); setPendingGeometryUpdate(PendingGeometryForced); // Force update when finishing with geometry changes embedClient(w, attr->visual, attr->colormap, windowGeometry->depth); m_visual = attr->visual; bit_depth = windowGeometry->depth; // SELI TODO: Order all these things in some sane manner const NET::Properties properties = NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMIconName; const NET::Properties2 properties2 = NET::WM2BlockCompositing | NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2UserTime | NET::WM2StartupId | NET::WM2ExtendedStrut | NET::WM2Opacity | NET::WM2FullscreenMonitors | NET::WM2FrameOverlap | NET::WM2GroupLeader | NET::WM2Urgency | NET::WM2Input | NET::WM2Protocols | NET::WM2InitialMappingState | NET::WM2IconPixmap | NET::WM2OpaqueRegion | NET::WM2DesktopFileName | NET::WM2GTKFrameExtents; auto wmClientLeaderCookie = fetchWmClientLeader(); auto skipCloseAnimationCookie = fetchSkipCloseAnimation(); auto showOnScreenEdgeCookie = fetchShowOnScreenEdge(); auto colorSchemeCookie = fetchColorScheme(); auto firstInTabBoxCookie = fetchFirstInTabBox(); auto transientCookie = fetchTransient(); auto activitiesCookie = fetchActivities(); auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName(); auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath(); m_geometryHints.init(window()); m_motif.init(window()); info = new WinInfo(this, m_client, rootWindow(), properties, properties2); if (isDesktop() && bit_depth == 32) { // force desktop windows to be opaque. It's a desktop after all, there is no window below bit_depth = 24; } // If it's already mapped, ignore hint bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic); m_colormap = attr->colormap; getResourceClass(); readWmClientLeader(wmClientLeaderCookie); getWmClientMachine(); getSyncCounter(); // First only read the caption text, so that setupWindowRules() can use it for matching, // and only then really set the caption using setCaption(), which checks for duplicates etc. // and also relies on rules already existing cap_normal = readName(); setupWindowRules(false); setCaption(cap_normal, true); connect(this, &X11Client::windowClassChanged, this, &X11Client::evaluateWindowRules); if (Xcb::Extensions::self()->isShapeAvailable()) xcb_shape_select_input(connection(), window(), true); detectShape(window()); detectNoBorder(); fetchIconicName(); setClientFrameExtents(info->gtkFrameExtents()); // Needs to be done before readTransient() because of reading the group checkGroup(); updateUrgency(); updateAllowedActions(); // Group affects isMinimizable() setModal((info->state() & NET::Modal) != 0); // Needs to be valid before handling groups readTransientProperty(transientCookie); setDesktopFileName(rules()->checkDesktopFile(QByteArray(info->desktopFileName()), true).toUtf8()); getIcons(); connect(this, &X11Client::desktopFileNameChanged, this, &X11Client::getIcons); m_geometryHints.read(); getMotifHints(); getWmOpaqueRegion(); readSkipCloseAnimation(skipCloseAnimationCookie); // TODO: Try to obey all state information from info->state() setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0); setSkipPager((info->state() & NET::SkipPager) != 0); setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0); readFirstInTabBox(firstInTabBoxCookie); setupCompositing(); KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); // Make sure that the input window is created before we update the stacking order updateInputWindow(); workspace()->updateClientLayer(this); SessionInfo* session = workspace()->takeSessionInfo(this); if (session) { init_minimize = session->minimized; noborder = session->noBorder; } setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true)); init_minimize = rules()->checkMinimize(init_minimize, !isMapped); noborder = rules()->checkNoBorder(noborder, !isMapped); readActivities(activitiesCookie); // Initial desktop placement int desk = 0; if (session) { desk = session->desktop; if (session->onAllDesktops) desk = NET::OnAllDesktops; setOnActivities(session->activities); } else { // If this window is transient, ensure that it is opened on the // same window as its parent. this is necessary when an application // starts up on a different desktop than is currently displayed if (isTransient()) { auto mainclients = mainClients(); bool on_current = false; bool on_all = false; AbstractClient* maincl = nullptr; // This is slightly duplicated from Placement::placeOnMainWindow() for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) { if (mainclients.count() > 1 && // A group-transient (*it)->isSpecialWindow() && // Don't consider toolbars etc when placing !(info->state() & NET::Modal)) // except when it's modal (blocks specials as well) continue; maincl = *it; if ((*it)->isOnCurrentDesktop()) on_current = true; if ((*it)->isOnAllDesktops()) on_all = true; } if (on_all) desk = NET::OnAllDesktops; else if (on_current) desk = VirtualDesktopManager::self()->current(); else if (maincl != nullptr) desk = maincl->desktop(); if (maincl) setOnActivities(maincl->activities()); } else { // a transient shall appear on its leader and not drag that around if (info->desktop()) desk = info->desktop(); // Window had the initial desktop property, force it if (desktop() == 0 && asn_valid && asn_data.desktop() != 0) desk = asn_data.desktop(); } #ifdef KWIN_BUILD_ACTIVITIES if (Activities::self() && !isMapped && !noborder && isNormalWindow() && !activitiesDefined) { //a new, regular window, when we're not recovering from a crash, //and it hasn't got an activity. let's try giving it the current one. //TODO: decide whether to keep this before the 4.6 release //TODO: if we are keeping it (at least as an option), replace noborder checking //with a public API for setting windows to be on all activities. //something like KWindowSystem::setOnAllActivities or //KActivityConsumer::setOnAllActivities setOnActivity(Activities::self()->current(), true); } #endif } if (desk == 0) // Assume window wants to be visible on the current desktop desk = isDesktop() ? static_cast(NET::OnAllDesktops) : VirtualDesktopManager::self()->current(); desk = rules()->checkDesktop(desk, !isMapped); if (desk != NET::OnAllDesktops) // Do range check desk = qBound(1, desk, static_cast(VirtualDesktopManager::self()->count())); setDesktop(desk); info->setDesktop(desk); workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO //onAllDesktopsChange(); // Decoration doesn't exist here yet QString activitiesList; activitiesList = rules()->checkActivity(activitiesList, !isMapped); if (!activitiesList.isEmpty()) setOnActivities(activitiesList.split(QStringLiteral(","))); QRect geom(windowGeometry.rect()); bool placementDone = false; if (session) geom = session->geometry; QRect area; bool partial_keep_in_area = isMapped || session; if (isMapped || session) { area = workspace()->clientArea(FullArea, geom.center(), desktop()); checkOffscreenPosition(&geom, area); } else { int screen = asn_data.xinerama() == -1 ? screens()->current() : asn_data.xinerama(); screen = rules()->checkScreen(screen, !isMapped); area = workspace()->clientArea(PlacementArea, screens()->geometry(screen).center(), desktop()); } if (isDesktop()) // KWin doesn't manage desktop windows placementDone = true; bool usePosition = false; if (isMapped || session || placementDone) placementDone = true; // Use geometry else if (isTransient() && !isUtility() && !isDialog() && !isSplash()) usePosition = true; else if (isTransient() && !hasNETSupport()) usePosition = true; else if (isDialog() && hasNETSupport()) { // If the dialog is actually non-NETWM transient window, don't try to apply placement to it, // it breaks with too many things (xmms, display) if (mainClients().count() >= 1) { #if 1 // #78082 - Ok, it seems there are after all some cases when an application has a good // reason to specify a position for its dialog. Too bad other WMs have never bothered // with placement for dialogs, so apps always specify positions for their dialogs, // including such silly positions like always centered on the screen or under mouse. // Using ignoring requested position in window-specific settings helps, and now // there's also _NET_WM_FULL_PLACEMENT. usePosition = true; #else ; // Force using placement policy #endif } else usePosition = true; } else if (isSplash()) ; // Force using placement policy else usePosition = true; if (!rules()->checkIgnoreGeometry(!usePosition, true)) { if (m_geometryHints.hasPosition()) { placementDone = true; // Disobey xinerama placement option for now (#70943) area = workspace()->clientArea(PlacementArea, geom.center(), desktop()); } } if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) placementDone = false; // Weird, do not trust. if (placementDone) { QPoint position = geom.topLeft(); // Session contains the position of the frame geometry before gravitating. if (!session) { position = clientPosToFramePos(position); } move(position); } // Create client group if the window will have a decoration bool dontKeepInArea = false; readColorScheme(colorSchemeCookie); readApplicationMenuServiceName(applicationMenuServiceNameCookie); readApplicationMenuObjectPath(applicationMenuObjectPathCookie); updateDecoration(false); // Also gravitates // TODO: Is CentralGravity right here, when resizing is done after gravitating? plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped)); QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped); if (forced_pos != invalidPoint) { move(forced_pos); placementDone = true; // Don't keep inside workarea if the window has specially configured position partial_keep_in_area = true; area = workspace()->clientArea(FullArea, geom.center(), desktop()); } if (!placementDone) { // Placement needs to be after setting size Placement::self()->place(this, area); // The client may have been moved to another screen, update placement area. area = workspace()->clientArea(PlacementArea, this); dontKeepInArea = true; placementDone = true; } // bugs #285967, #286146, #183694 // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully) // Maximization for oversized windows must happen NOW. // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained // to the combo of all screen MINUS all struts on the edges // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well. if (!session) { // has a better handling of this geom_restore = frameGeometry(); // Remember restore geometry if (isMaximizable() && (width() >= area.width() || height() >= area.height())) { // Window is too large for the screen, maximize in the // directions necessary const QSize ss = workspace()->clientArea(ScreenArea, area.center(), desktop()).size(); const QRect fsa = workspace()->clientArea(FullArea, geom.center(), desktop()); const QSize cs = clientSize(); int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0); if (width() >= area.width()) pseudo_max |= MaximizeHorizontal; if (height() >= area.height()) pseudo_max |= MaximizeVertical; // heuristics: // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen) // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an // attempt for maximization, but just constrain the size (the window simply wants to be bigger) // NOTICE // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller // than the workspace") but gtk / gimp seems to store it's size including the decoration, // thus a former maximized window wil become non-maximized bool keepInFsArea = false; if (width() < fsa.width() && (cs.width() > ss.width()+1)) { pseudo_max &= ~MaximizeHorizontal; keepInFsArea = true; } if (height() < fsa.height() && (cs.height() > ss.height()+1)) { pseudo_max &= ~MaximizeVertical; keepInFsArea = true; } if (pseudo_max != MaximizeRestore) { maximize((MaximizeMode)pseudo_max); // from now on, care about maxmode, since the maximization call will override mode for fix aspects dontKeepInArea |= (max_mode == MaximizeFull); geom_restore = QRect(); // Use placement when unmaximizing ... if (!(max_mode & MaximizeVertical)) { geom_restore.setY(y()); // ...but only for horizontal direction geom_restore.setHeight(height()); } if (!(max_mode & MaximizeHorizontal)) { geom_restore.setX(x()); // ...but only for vertical direction geom_restore.setWidth(width()); } } if (keepInFsArea) keepInArea(fsa, partial_keep_in_area); } } if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea) keepInArea(area, partial_keep_in_area); updateShape(); // CT: Extra check for stupid jdk 1.3.1. But should make sense in general // if client has initial state set to Iconic and is transient with a parent // window that is not Iconic, set init_state to Normal if (init_minimize && isTransient()) { auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) init_minimize = false; // SELI TODO: Even e.g. for NET::Utility? } // If a dialog is shown for minimized window, minimize it too if (!init_minimize && isTransient() && mainClients().count() > 0 && workspace()->sessionManager()->state() != SessionState::Saving) { bool visible_parent = false; // Use allMainClients(), to include also main clients of group transients // that have been optimized out in X11Client::checkGroupTransients() auto mainclients = allMainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) visible_parent = true; if (!visible_parent) { init_minimize = true; demandAttention(); } } if (init_minimize) minimize(true); // No animation // Other settings from the previous session if (session) { // Session restored windows are not considered to be new windows WRT rules, // I.e. obey only forcing rules setKeepAbove(session->keepAbove); setKeepBelow(session->keepBelow); setOriginalSkipTaskbar(session->skipTaskbar); setSkipPager(session->skipPager); setSkipSwitcher(session->skipSwitcher); setShade(session->shaded ? ShadeNormal : ShadeNone); setOpacity(session->opacity); geom_restore = session->restore; if (session->maximized != MaximizeRestore) { maximize(MaximizeMode(session->maximized)); } if (session->fullscreen != FullScreenNone) { setFullScreen(true, false); geom_fs_restore = session->fsrestore; } checkOffscreenPosition(&geom_restore, area); checkOffscreenPosition(&geom_fs_restore, area); } else { // Window may want to be maximized // done after checking that the window isn't larger than the workarea, so that // the restore geometry from the checks above takes precedence, and window // isn't restored larger than the workarea MaximizeMode maxmode = static_cast( ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0)); MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped); // Either hints were set to maximize, or is forced to maximize, // or is forced to non-maximize and hints were set to maximize if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore) maximize(forced_maxmode); // Read other initial states setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped)); setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped)); setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped)); setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped)); setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped)); setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped)); if (info->state() & NET::DemandsAttention) demandAttention(); if (info->state() & NET::Modal) setModal(true); setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false); } updateAllowedActions(true); // Set initial user time directly m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session); group()->updateUserTime(m_userTime); // And do what X11Client::updateUserTime() does // This should avoid flicker, because real restacking is done // only after manage() finishes because of blocking, but the window is shown sooner m_frame.lower(); if (session && session->stackingOrder != -1) { sm_stacking_order = session->stackingOrder; workspace()->restoreSessionStackingOrder(this); } if (compositing()) // Sending ConfigureNotify is done when setting mapping state below, // Getting the first sync response means window is ready for compositing sendSyncRequest(); else ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393 if (isShown(true)) { bool allow; if (session) allow = session->active && (!workspace()->wasUserInteraction() || workspace()->activeClient() == nullptr || workspace()->activeClient()->isDesktop()); else allow = workspace()->allowClientActivation(this, userTime(), false); const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving; // If session saving, force showing new windows (i.e. "save file?" dialogs etc.) // also force if activation is allowed if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || isSessionSaving )) VirtualDesktopManager::self()->setCurrent( desktop()); // If the window is on an inactive activity during session saving, temporarily force it to show. if( !isMapped && !session && isSessionSaving && !isOnCurrentActivity()) { setSessionActivityOverride( true ); foreach( AbstractClient* c, mainClients()) { if (X11Client *mc = dynamic_cast(c)) { mc->setSessionActivityOverride(true); } } } if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0)) workspace()->restackClientUnderActive(this); updateVisibility(); if (!isMapped) { if (allow && isOnCurrentDesktop()) { if (!isSpecialWindow()) if (options->focusPolicyIsReasonable() && wantsTabFocus()) workspace()->requestFocus(this); } else if (!session && !isSpecialWindow()) demandAttention(); } } else updateVisibility(); Q_ASSERT(mapping_state != Withdrawn); m_managed = true; blockGeometryUpdates(false); if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) { // No known user time, set something old m_userTime = xTime() - 1000000; if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) // Let's be paranoid m_userTime = xTime() - 1000000 + 10; } //sendSyntheticConfigureNotify(); // Done when setting mapping state delete session; discardTemporaryRules(); applyWindowRules(); // Just in case RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules updateWindowRules(Rules::All); // Was blocked while !isManaged() setBlockingCompositing(info->isBlockingCompositing()); readShowOnScreenEdge(showOnScreenEdgeCookie); // Forward all opacity values to the frame in case there'll be other CM running. connect(Compositor::self(), &Compositor::compositingToggled, this, [this](bool active) { if (active) { return; } if (opacity() == 1.0) { return; } NETWinInfo info(connection(), frameId(), rootWindow(), NET::Properties(), NET::Properties2()); info.setOpacity(static_cast(opacity() * 0xffffffff)); } ); // TODO: there's a small problem here - isManaged() depends on the mapping state, // but this client is not yet in Workspace's client list at this point, will // be only done in addClient() emit clientManaging(this); return true; } // Called only from manage() void X11Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth) { Q_ASSERT(m_client == XCB_WINDOW_NONE); Q_ASSERT(frameId() == XCB_WINDOW_NONE); Q_ASSERT(m_wrapper == XCB_WINDOW_NONE); m_client.reset(w, false); const uint32_t zero_value = 0; xcb_connection_t *conn = connection(); // We don't want the window to be destroyed when we quit xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client); m_client.selectInput(zero_value); m_client.unmap(); m_client.setBorderWidth(zero_value); // Note: These values must match the order in the xcb_cw_t enum const uint32_t cw_values[] = { 0, // back_pixmap 0, // border_pixel colormap, // colormap Cursor::x11Cursor(Qt::ArrowCursor) }; const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP | XCB_CW_CURSOR; const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; const uint32_t frame_event_mask = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_VISIBILITY_CHANGE; const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE; // Create the frame window xcb_window_t frame = xcb_generate_id(conn); xcb_create_window(conn, depth, frame, rootWindow(), 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); m_frame.reset(frame); setWindowHandles(m_client); // Create the wrapper window xcb_window_t wrapperId = xcb_generate_id(conn); xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); m_wrapper.reset(wrapperId); m_client.reparent(m_wrapper); // We could specify the event masks when we create the windows, but the original // Xlib code didn't. Let's preserve that behavior here for now so we don't end up // receiving any unexpected events from the wrapper creation or the reparenting. m_frame.selectInput(frame_event_mask); m_wrapper.selectInput(wrapper_event_mask); m_client.selectInput(client_event_mask); updateMouseGrab(); } void X11Client::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(frameGeometry().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 X11Client::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = frameGeometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); updateShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); updateInputWindow(); blockGeometryUpdates(false); updateFrameExtents(); } void X11Client::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::updateShadow); connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &X11Client::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 = frameGeometry(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, &X11Client::updateInputWindow); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, &X11Client::updateInputWindow); } setDecoration(decoration); move(calculateGravitation(false)); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (Compositor::compositing()) { discardWindowPixmap(); } emit geometryShapeChanged(this, oldgeom); } void X11Client::destroyDecoration() { QRect oldgeom = frameGeometry(); 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 X11Client::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 X11Client::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 X11Client::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 : case NET::CriticalNotification : 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 X11Client::updateFrameExtents() { NETStrut strut; strut.left = borderLeft(); strut.right = borderRight(); strut.top = borderTop(); strut.bottom = borderBottom(); info->setFrameExtents(strut); } void X11Client::setClientFrameExtents(const NETStrut &strut) { const QMargins clientFrameExtents(strut.left, strut.top, strut.right, strut.bottom); if (m_clientFrameExtents == clientFrameExtents) { return; } const bool wasClientSideDecorated = isClientSideDecorated(); m_clientFrameExtents = clientFrameExtents; // We should resize the client when its custom frame extents are changed so // the logical bounds remain the same. This however means that we will send // several configure requests to the application upon restoring it from the // maximized or fullscreen state. Notice that a client-side decorated client // cannot be shaded, therefore it's okay not to use the adjusted size here. setFrameGeometry(frameGeometry()); if (wasClientSideDecorated != isClientSideDecorated()) { emit clientSideDecoratedChanged(); } // This will invalidate the window quads cache. emit geometryShapeChanged(this, frameGeometry()); } /** * 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 X11Client::resizeDecoration() { triggerDecorationRepaint(); updateInputWindow(); } bool X11Client::userNoBorder() const { return noborder; } bool X11Client::isFullScreenable() const { if (!rules()->checkFullScreen(true)) { return false; } if (rules()->checkStrictGeometry(true)) { // check geometry constraints (rule to obey is set) const 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 X11Client::noBorder() const { return userNoBorder() || isFullScreen(); } bool X11Client::userCanSetNoBorder() const { // Client-side decorations and server-side decorations are mutually exclusive. if (isClientSideDecorated()) { return false; } return !isFullScreen() && !isShade(); } void X11Client::setNoBorder(bool set) { if (!userCanSetNoBorder()) return; set = rules()->checkNoBorder(set); if (noborder == set) return; noborder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void X11Client::checkNoBorder() { setNoBorder(app_noborder); } bool X11Client::wantsShadowToBeRendered() const { return !isFullScreen() && maximizeMode() != MaximizeFull; } void X11Client::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, frameGeometry()); } static Xcb::Window shape_helper_window(XCB_WINDOW_NONE); void X11Client::cleanupX11() { shape_helper_window.reset(); } void X11Client::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(m_bufferGeometry.size()); 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 X11Client::hideClient(bool hide) { if (hidden == hide) return; hidden = hide; updateVisibility(); } bool X11Client::setupCompositing() { if (!Toplevel::setupCompositing()){ return false; } updateVisibility(); // for internalKeep() return true; } void X11Client::finishCompositing(ReleaseReason releaseReason) { Toplevel::finishCompositing(releaseReason); updateVisibility(); // for safety in case KWin is just resizing the window resetHaveResizeEffect(); } /** * Returns whether the window is minimizable or not */ bool X11Client::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 X11Client::doMinimize() { updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients(this); } QRect X11Client::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()) { X11Client *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 X11Client::isShadeable() const { return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone)); } void X11Client::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 if (was_shade == isShade()) { // Decoration may want to update after e.g. hover-shade changes emit shadeChanged(); return; // No real change in shaded state } Q_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(XCB_ICCCM_WM_STATE_ICONIC); 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); setGeometryRestore(frameGeometry()); if ((shade_mode == ShadeHover || shade_mode == ShadeActivated) && rules()->checkAcceptFocus(info->input())) setActive(true); if (shade_mode == ShadeHover) { QList order = workspace()->stackingOrder(); // invalidate, since "this" could be the topmost toplevel and shade_below dangeling shade_below = nullptr; // 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 = nullptr; } m_wrapper.map(); m_client.map(); exportMappingState(XCB_ICCCM_WM_STATE_NORMAL); if (isActive()) workspace()->requestFocus(this); } info->setState(isShade() ? NET::Shaded : NET::States(), NET::Shaded); info->setState(isShown(false) ? NET::States() : NET::Hidden, NET::Hidden); discardWindowPixmap(); updateVisibility(); updateAllowedActions(); updateWindowRules(Rules::Shade); emit shadeChanged(); } void X11Client::shadeHover() { setShade(ShadeHover); cancelShadeHoverTimer(); } void X11Client::shadeUnhover() { setShade(ShadeNormal); cancelShadeHoverTimer(); } void X11Client::cancelShadeHoverTimer() { delete shadeHoverTimer; shadeHoverTimer = nullptr; } void X11Client::toggleShade() { // If the mode is ShadeHover or ShadeActive, cancel shade too setShade(shade_mode == ShadeNone ? ShadeNormal : ShadeNone); } void X11Client::updateVisibility() { if (deleting) return; if (hidden) { info->setState(NET::Hidden, NET::Hidden); setSkipTaskbar(true); // Also hide from taskbar if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden' if (isMinimized()) { info->setState(NET::Hidden, NET::Hidden); if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } info->setState(NET::States(), 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 X11Client::exportMappingState(int s) { Q_ASSERT(m_client != XCB_WINDOW_NONE); Q_ASSERT(!deleting || s == XCB_ICCCM_WM_STATE_WITHDRAWN); if (s == XCB_ICCCM_WM_STATE_WITHDRAWN) { m_client.deleteProperty(atoms->wm_state); return; } Q_ASSERT(s == XCB_ICCCM_WM_STATE_NORMAL || s == XCB_ICCCM_WM_STATE_ICONIC); int32_t data[2]; data[0] = s; data[1] = XCB_NONE; m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data); } void X11Client::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 X11Client::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 X11Client::internalKeep() { Q_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 X11Client::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(XCB_ICCCM_WM_STATE_NORMAL); } else exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); addLayerRepaint(visibleRect()); } /** * Unmaps the client. Again, this is about the frame. */ void X11Client::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(XCB_ICCCM_WM_STATE_ICONIC); } /** * 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 X11Client::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, nullptr); } } else { workspace()->forceRestacking(); updateInputShape(); } } void X11Client::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 X11Client::isCloseable() const { return rules()->checkCloseable(m_motif.close() && !isSpecialWindow()); } /** * Closes the window by either sending a delete_window message or using XKill. */ void X11Client::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 X11Client::killWindow() { qCDebug(KWIN_CORE) << "X11Client::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 X11Client::pingWindow() { if (!info->supportsProtocol(NET::PingProtocol)) return; // Can't ping :( if (options->killPingTimeout() == 0) return; // Turned off if (ping_timer != nullptr) 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 X11Client::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 = nullptr; setUnresponsive(false); if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } } void X11Client::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()); // execute helper from build dir or the system installed one const QFileInfo buildDirBinary{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("kwin_killer_helper")}; QProcess::startDetached(buildDirBinary.exists() ? buildDirBinary.absoluteFilePath() : 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 X11Client::doSetSkipTaskbar() { info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(), NET::SkipTaskbar); } void X11Client::doSetSkipPager() { info->setState(skipPager() ? NET::SkipPager : NET::States(), NET::SkipPager); } void X11Client::doSetSkipSwitcher() { info->setState(skipSwitcher() ? NET::SkipSwitcher : NET::States(), NET::SkipSwitcher); } void X11Client::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) updateVisibility(); } /** * 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 X11Client::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 X11Client::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.toLatin1(); activityList = newActivitiesList; m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData()); } updateActivities(false); #else Q_UNUSED(newActivitiesList) #endif } void X11Client::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 X11Client::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); } /** * 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 X11Client::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 X11Client::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 X11Client::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 X11Client *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 X11Client::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 X11Client::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 X11Client::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 X11Client::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 https://www.unicode.org/reports/tr9/ (#154840) static const QChar LRM(0x200E); void X11Client::setCaption(const QString& _s, bool force) { QString s(_s); for (int i = 0; i < s.length(); ) { if (!s[i].isPrint()) { if (QChar(s[i]).isHighSurrogate() && i + 1 < s.length() && QChar(s[i + 1]).isLowSurrogate()) { const uint uc = QChar::surrogateToUcs4(s[i], s[i + 1]); if (!QChar::isPrint(uc)) { s.remove(i, 2); } else { i += 2; } continue; } s.remove(i, 1); continue; } ++i; } const bool changed = (s != cap_normal); if (!force && !changed) { return; } 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 X11Client::updateCaption() { setCaption(cap_normal, true); } void X11Client::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(""); } } } void X11Client::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 (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 FocusChain::self()->update(this, FocusChain::MakeLast); addWorkspaceRepaint(visibleRect()); } } void X11Client::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 X11Client::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 X11Client::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 X11Client::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 X11Client::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 X11Client::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol)); } bool X11Client::acceptsFocus() const { return info->input(); } void X11Client::setBlockingCompositing(bool block) { const bool usedToBlock = blocks_compositing; blocks_compositing = rules()->checkBlockCompositing(block && options->windowsBlockCompositing()); if (usedToBlock != blocks_compositing) { emit blockingCompositingChanged(blocks_compositing ? this : nullptr); } } void X11Client::updateAllowedActions(bool force) { if (!isManaged() && !force) return; NET::Actions old_allowed_actions = NET::Actions(allowed_actions); allowed_actions = NET::Actions(); 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 X11Client::debug(QDebug& stream) const { stream.nospace(); print(stream); } Xcb::StringProperty X11Client::fetchActivities() const { #ifdef KWIN_BUILD_ACTIVITIES return Xcb::StringProperty(window(), atoms->activities); #else return Xcb::StringProperty(); #endif } void X11Client::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 X11Client::checkActivities() { #ifdef KWIN_BUILD_ACTIVITIES Xcb::StringProperty property = fetchActivities(); readActivities(property); #endif } void X11Client::setSessionActivityOverride(bool needed) { sessionActivityOverride = needed; updateActivities(false); } QRect X11Client::decorationRect() const { return QRect(0, 0, width(), height()); } Xcb::Property X11Client::fetchFirstInTabBox() const { return Xcb::Property(false, m_client, atoms->kde_first_in_window_list, atoms->kde_first_in_window_list, 0, 1); } void X11Client::readFirstInTabBox(Xcb::Property &property) { setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list)); } void X11Client::updateFirstInTabBox() { // TODO: move into KWindowInfo Xcb::Property property = fetchFirstInTabBox(); readFirstInTabBox(property); } Xcb::StringProperty X11Client::fetchColorScheme() const { return Xcb::StringProperty(m_client, atoms->kde_color_sheme); } void X11Client::readColorScheme(Xcb::StringProperty &property) { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString::fromUtf8(property))); } void X11Client::updateColorScheme() { Xcb::StringProperty property = fetchColorScheme(); readColorScheme(property); } bool X11Client::isClient() const { return true; } NET::WindowType X11Client::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 X11Client::cancelFocusOutTimer() { if (m_focusOutTimer) { m_focusOutTimer->stop(); } } xcb_window_t X11Client::frameId() const { return m_frame; } QRect X11Client::bufferGeometry() const { return m_bufferGeometry; } QMargins X11Client::bufferMargins() const { return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom()); } QPoint X11Client::framePosToClientPos(const QPoint &point) const { int x = point.x(); int y = point.y(); if (isDecorated()) { x += borderLeft(); y += borderTop(); } else { x -= m_clientFrameExtents.left(); y -= m_clientFrameExtents.top(); } return QPoint(x, y); } QPoint X11Client::clientPosToFramePos(const QPoint &point) const { int x = point.x(); int y = point.y(); if (isDecorated()) { x -= borderLeft(); y -= borderTop(); } else { x += m_clientFrameExtents.left(); y += m_clientFrameExtents.top(); } return QPoint(x, y); } QSize X11Client::frameSizeToClientSize(const QSize &size) const { int width = size.width(); int height = size.height(); if (isDecorated()) { width -= borderLeft() + borderRight(); height -= borderTop() + borderBottom(); } else { width += m_clientFrameExtents.left() + m_clientFrameExtents.right(); height += m_clientFrameExtents.top() + m_clientFrameExtents.bottom(); } return QSize(width, height); } QSize X11Client::clientSizeToFrameSize(const QSize &size) const { int width = size.width(); int height = size.height(); if (isDecorated()) { width += borderLeft() + borderRight(); height += borderTop() + borderBottom(); } else { width -= m_clientFrameExtents.left() + m_clientFrameExtents.right(); height -= m_clientFrameExtents.top() + m_clientFrameExtents.bottom(); } return QSize(width, height); } Xcb::Property X11Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); } void X11Client::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, &X11Client::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 X11Client::updateShowOnScreenEdge() { Xcb::Property property = fetchShowOnScreenEdge(); readShowOnScreenEdge(property); } void X11Client::showOnScreenEdge() { disconnect(m_edgeRemoveConnection); hideClient(false); setKeepBelow(false); xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } void X11Client::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.translated(bufferGeometry().topLeft() - frameGeometry().topLeft()); Toplevel::addDamage(damage); } bool X11Client::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const { const X11Client *c2 = dynamic_cast(other); if (!c2) { return false; } return X11Client::belongToSameApplication(this, c2, checks); } QSize X11Client::resizeIncrements() const { return m_geometryHints.resizeIncrements(); } Xcb::StringProperty X11Client::fetchApplicationMenuServiceName() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name); } void X11Client::readApplicationMenuServiceName(Xcb::StringProperty &property) { updateApplicationMenuServiceName(QString::fromUtf8(property)); } void X11Client::checkApplicationMenuServiceName() { Xcb::StringProperty property = fetchApplicationMenuServiceName(); readApplicationMenuServiceName(property); } Xcb::StringProperty X11Client::fetchApplicationMenuObjectPath() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path); } void X11Client::readApplicationMenuObjectPath(Xcb::StringProperty &property) { updateApplicationMenuObjectPath(QString::fromUtf8(property)); } void X11Client::checkApplicationMenuObjectPath() { Xcb::StringProperty property = fetchApplicationMenuObjectPath(); readApplicationMenuObjectPath(property); } void X11Client::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(); } void X11Client::move(int x, int y, ForceGeometry_t force) { const QPoint framePosition(x, y); m_clientGeometry.moveTopLeft(framePosToClientPos(framePosition)); const QPoint bufferPosition = isDecorated() ? framePosition : m_clientGeometry.topLeft(); // resuming geometry updates is handled only in setGeometry() Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); if (!areGeometryUpdatesBlocked() && framePosition != rules()->checkPosition(framePosition)) { qCDebug(KWIN_CORE) << "forced position fail:" << framePosition << ":" << rules()->checkPosition(framePosition); } m_frameGeometry.moveTopLeft(framePosition); if (force == NormalGeometrySet && m_bufferGeometry.topLeft() == bufferPosition) { return; } m_bufferGeometry.moveTopLeft(bufferPosition); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) { // Maximum, nothing needed. } else if (force == ForceGeometrySet) { setPendingGeometryUpdate(PendingGeometryForced); } else { setPendingGeometryUpdate(PendingGeometryNormal); } return; } updateServerGeometry(); updateWindowRules(Rules::Position); screens()->setCurrent(this); workspace()->updateStackingOrder(); // client itself is not damaged addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); emit geometryChanged(); } bool X11Client::belongToSameApplication(const X11Client *c1, const X11Client *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() && !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 && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) ; // different client leader else if (!resourceMatch(c1, c2)) ; // different apps 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 X11Client::sameAppWindowRoleMatch(const X11Client *c1, const X11Client *c2, bool active_hack) { if (c1->isTransient()) { while (const X11Client *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 X11Client *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.). X11Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after possibly being adjusted by KWin. X11Client::transient_for points to the Client this Client is transient for, or is NULL. If X11Client::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, X11Client::transient_for is NULL, and X11Client::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). X11Client::original_transient_for_id is the value of the property, which may be different if X11Client::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 X11Client::fetchTransient() const { return Xcb::TransientFor(window()); } void X11Client::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 X11Client::readTransient() { Xcb::TransientFor transientFor = fetchTransient(); readTransientProperty(transientFor); } void X11Client::setTransient(xcb_window_t new_transient_for_id) { if (new_transient_for_id != m_transientForId) { removeFromMainClients(); X11Client *transient_for = nullptr; m_transientForId = new_transient_for_id; if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) { transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId); Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this transient_for->addTransient(this); } // checkGroup() will check 'check_active_modal' setTransientFor(transient_for); checkGroup(nullptr, true); // force, because transiency has changed workspace()->updateClientLayer(this); workspace()->resetUpdateToolWindowsTimer(); emit transientChanged(); } } void X11Client::removeFromMainClients() { if (transientFor()) transientFor()->removeTransient(this); if (groupTransient()) { for (auto 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 X11Client::cleanGrouping() { // qDebug() << "CLEANGROUPING:" << this; // for ( auto it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL:" << *it; // QList mains; // mains = mainClients(); // for ( auto it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN:" << *it; removeFromMainClients(); // qDebug() << "CLEANGROUPING2:" << this; // for ( auto it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL2:" << *it; // mains = mainClients(); // for ( auto 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 ( auto it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL3:" << *it; // mains = mainClients(); // for ( auto 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 :( QList group_members = group()->members(); group()->removeMember(this); in_group = nullptr; for (auto it = group_members.constBegin(); it != group_members.constEnd(); ++it) (*it)->removeTransient(this); // qDebug() << "CLEANGROUPING4:" << this; // for ( auto 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 X11Client::checkGroupTransients() { for (auto 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 (auto 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 != nullptr; 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 (auto 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 X11Client::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 (X11Client *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()) { X11Client *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos); if (pos == nullptr) 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) == nullptr) { // 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 X11Client::addTransient(AbstractClient* cl) { AbstractClient::addTransient(cl); if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal()) check_active_modal = true; // qDebug() << "ADDTRANS:" << this << ":" << cl; // qDebug() << kBacktrace(); // for ( auto it = transients_list.begin(); // it != transients_list.end(); // ++it ) // qDebug() << "AT:" << (*it); } void X11Client::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 (X11Client *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 X11Client::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 X11Client::hasTransient(const AbstractClient* cl, bool indirect) const { if (const X11Client *c = dynamic_cast(cl)) { // checkGroupTransients() uses this to break loops, so hasTransient() must detect them QList set; return hasTransientInternal(c, indirect, set); } return false; } bool X11Client::hasTransientInternal(const X11Client *cl, bool indirect, QList &set) const { if (const X11Client *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< X11Client *>(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) { const X11Client *c = qobject_cast(*it); if (!c) { continue; } if (c->hasTransientInternal(cl, indirect, set)) return true; } return false; } QList X11Client::mainClients() const { if (!isTransient()) return QList(); if (const AbstractClient *t = transientFor()) return QList{const_cast< AbstractClient* >(t)}; QList result; Q_ASSERT(group()); for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) if ((*it)->hasTransient(this, false)) result.append(*it); return result; } AbstractClient* X11Client::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 nullptr; } // X11Client::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 X11Client::checkGroup(Group* set_group, bool force) { Group* old_group = in_group; if (old_group != nullptr) old_group->ref(); // turn off automatic deleting if (set_group != nullptr) { if (set_group != in_group) { if (in_group != nullptr) 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()); X11Client *t = qobject_cast(transientFor()); if (t != nullptr && 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 == nullptr) // doesn't exist yet new_group = new Group(info->groupLeader()); if (new_group != in_group) { if (in_group != nullptr) in_group->removeMember(this); in_group = new_group; in_group->addMember(this); } } else { if (X11Client *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 != nullptr) 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 == nullptr) new_group = new Group(XCB_WINDOW_NONE); if (new_group != in_group) { if (in_group != nullptr) 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 != nullptr && in_group != new_group) { in_group->removeMember(this); in_group = nullptr; } if (new_group == nullptr) new_group = new Group(XCB_WINDOW_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(); ) { auto *c = *it; // 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 != nullptr) { for (auto it = old_group->members().constBegin(); it != old_group->members().constEnd(); ++it) (*it)->removeTransient(this); } // and make transient for all in the new group for (auto 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 (auto 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 != nullptr) old_group->deref(); // can be now deleted if empty checkGroupTransients(); checkActiveModal(); workspace()->updateClientLayer(this); } // used by Workspace::findClientLeaderGroup() void X11Client::changeClientLeaderGroup(Group* gr) { // transientFor() != NULL are in the group of their mainwindow, so keep them there if (transientFor() != nullptr) return; // also don't change the group for window which have group set if (info->groupLeader()) return; checkGroup(gr); // change group } bool X11Client::check_active_modal = false; void X11Client::checkActiveModal() { // if the active window got new modal transient, activate it. // cannot be done in AddTransient(), because there may temporarily // exist loops, breaking findModal X11Client *check_modal = dynamic_cast(workspace()->mostRecentlyActivatedClient()); if (check_modal != nullptr && check_modal->check_active_modal) { X11Client *new_modal = dynamic_cast(check_modal->findModal()); if (new_modal != nullptr && 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 +/** + * 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 X11Client::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 = minSize(); + QSize max_size = 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; + } + + QSize size(w, h); + if (!noframe) { + size = clientSizeToFrameSize(size); + } + return rules()->checkSize(size); +} + +/** + * Gets the client's normal WM hints and reconfigures itself respectively. + */ +void X11Client::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 constraints + maximize(max_mode); + } + if (isManaged()) { + // update to match restrictions + QSize new_size = adjustedSize(); + if (new_size != size() && !isFullScreen()) { + QRect origClientGeometry = m_clientGeometry; + 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 X11Client::minSize() const +{ + return rules()->checkMinSize(m_geometryHints.minSize()); +} + +QSize X11Client::maxSize() const +{ + return rules()->checkMaxSize(m_geometryHints.maxSize()); +} + +QSize X11Client::basicUnit() const +{ + return m_geometryHints.resizeIncrements(); +} + +/** + * Auxiliary function to inform the client about the current window + * configuration. + */ +void X11Client::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 = m_clientGeometry.x(); + c.y = m_clientGeometry.y(); + c.width = m_clientGeometry.width(); + c.height = m_clientGeometry.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()); +} + +QPoint X11Client::gravityAdjustment(xcb_gravity_t gravity) const +{ + int dx = 0; + int dy = 0; + + // dx, dy specify how the client window moves to make space for the frame. + // In general we have to compute the reference point and from that figure + // out how much we need to shift the client, however given that we ignore + // the border width attribute and the extents of the server-side decoration + // are known in advance, we can simplify the math quite a bit and express + // the required window gravity adjustment in terms of border sizes. + switch(gravity) { + case XCB_GRAVITY_NORTH_WEST: // move down right + default: + dx = borderLeft(); + dy = borderTop(); + break; + case XCB_GRAVITY_NORTH: // move right + dx = 0; + dy = borderTop(); + break; + case XCB_GRAVITY_NORTH_EAST: // move down left + dx = -borderRight(); + dy = borderTop(); + break; + case XCB_GRAVITY_WEST: // move right + dx = borderLeft(); + dy = 0; + break; + case XCB_GRAVITY_CENTER: + dx = (borderLeft() - borderRight()) / 2; + dy = (borderTop() - borderBottom()) / 2; + break; + case XCB_GRAVITY_STATIC: // don't move + dx = 0; + dy = 0; + break; + case XCB_GRAVITY_EAST: // move left + dx = -borderRight(); + dy = 0; + break; + case XCB_GRAVITY_SOUTH_WEST: // move up right + dx = borderLeft() ; + dy = -borderBottom(); + break; + case XCB_GRAVITY_SOUTH: // move up + dx = 0; + dy = -borderBottom(); + break; + case XCB_GRAVITY_SOUTH_EAST: // move up left + dx = -borderRight(); + dy = -borderBottom(); + break; + } + + return QPoint(dx, dy); +} + +const QPoint X11Client::calculateGravitation(bool invert) const +{ + const QPoint adjustment = gravityAdjustment(m_geometryHints.windowGravity()); + + // translate from client movement to frame movement + const int dx = adjustment.x() - borderLeft(); + const int dy = adjustment.y() - borderTop(); + + if (!invert) + return QPoint(x() + dx, y() + dy); + else + return QPoint(x() - dx, y() - dy); +} + +void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool) +{ + const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; + const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + const int configureGeometryMask = configurePositionMask | configureSizeMask; + + // "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 & configureGeometryMask) << + 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 &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT); + if (maximizeMode() == MaximizeHorizontal) + value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH); + if (!(value_mask & configureGeometryMask)) { + 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 & configureGeometryMask); + + if (gravity == 0) // default (nonsense) value for the argument + gravity = m_geometryHints.windowGravity(); + if (value_mask & configurePositionMask) { + QPoint new_pos = framePosToClientPos(pos()); + new_pos -= gravityAdjustment(xcb_gravity_t(gravity)); + if (value_mask & XCB_CONFIG_WINDOW_X) { + new_pos.setX(rx); + } + if (value_mask & XCB_CONFIG_WINDOW_Y) { + 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() == m_clientGeometry.x() && new_pos.y() == m_clientGeometry.y() + && gravity == XCB_GRAVITY_NORTH_WEST && !from_tool) { + new_pos.setX(x()); + new_pos.setY(y()); + } + new_pos += gravityAdjustment(xcb_gravity_t(gravity)); + new_pos = clientPosToFramePos(new_pos); + + int nw = clientSize().width(); + int nh = clientSize().height(); + if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { + nw = rw; + } + if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { + 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 = m_clientGeometry; + GeometryUpdatesBlocker blocker(this); + move(new_pos); + plainResize(ns); + 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 + // X11Client::adjustedClientArea() + if (hasStrut()) + workspace() -> updateClientArea(); + } + + if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) { // pure resize + int nw = clientSize().width(); + int nh = clientSize().height(); + if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { + nw = rw; + } + if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { + nh = rh; + } + QSize ns = sizeForClientSize(QSize(nw, nh)); + + if (ns != size()) { // don't restore if some app sets its own size again + QRect origClientGeometry = m_clientGeometry; + GeometryUpdatesBlocker blocker(this); + resizeWithChecks(ns, xcb_gravity_t(gravity)); + 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 = frameGeometry(); + // 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 X11Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force) +{ + Q_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 XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move + default: + break; + case XCB_GRAVITY_NORTH: // middle of top border doesn't move + newx = (newx + width() / 2) - (w / 2); + break; + case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move + newx = newx + width() - w; + break; + case XCB_GRAVITY_WEST: // middle of left border doesn't move + newy = (newy + height() / 2) - (h / 2); + break; + case XCB_GRAVITY_CENTER: // middle point doesn't move + newx = (newx + width() / 2) - (w / 2); + newy = (newy + height() / 2) - (h / 2); + break; + case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move + // since decoration doesn't change, equal to NorthWestGravity + break; + case XCB_GRAVITY_EAST: // // middle of right border doesn't move + newx = newx + width() - w; + newy = (newy + height() / 2) - (h / 2); + break; + case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move + newy = newy + height() - h; + break; + case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move + newx = (newx + width() / 2) - (w / 2); + newy = newy + height() - h; + break; + case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move + newx = newx + width() - w; + newy = newy + height() - h; + break; + } + setFrameGeometry(newx, newy, w, h, force); +} + +// _NET_MOVERESIZE_WINDOW +void X11Client::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 |= XCB_CONFIG_WINDOW_X; + } + if (flags & (1 << 9)) { + value_mask |= XCB_CONFIG_WINDOW_Y; + } + if (flags & (1 << 10)) { + value_mask |= XCB_CONFIG_WINDOW_WIDTH; + } + if (flags & (1 << 11)) { + value_mask |= XCB_CONFIG_WINDOW_HEIGHT; + } + configureRequest(value_mask, x, y, width, height, gravity, true); +} + +bool X11Client::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; +} + +bool X11Client::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; +} + +bool X11Client::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 = minSize(); + QSize max = maxSize(); + return min.width() < max.width() || min.height() < max.height(); +} + +bool X11Client::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 X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t force) +{ + // this code is also duplicated in X11Client::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 X11Client::clientSize() + + QRect frameGeometry(x, y, w, h); + QRect bufferGeometry; + + if (shade_geometry_change) + ; // nothing + else if (isShade()) { + if (frameGeometry.height() == borderTop() + borderBottom()) { + qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; + } else { + m_clientGeometry = frameRectToClientRect(frameGeometry); + frameGeometry.setHeight(borderTop() + borderBottom()); + } + } else { + m_clientGeometry = frameRectToClientRect(frameGeometry); + } + if (isDecorated()) { + bufferGeometry = frameGeometry; + } else { + bufferGeometry = m_clientGeometry; + } + if (!areGeometryUpdatesBlocked() && frameGeometry != rules()->checkGeometry(frameGeometry)) { + qCDebug(KWIN_CORE) << "forced geometry fail:" << frameGeometry << ":" << rules()->checkGeometry(frameGeometry); + } + m_frameGeometry = frameGeometry; + if (force == NormalGeometrySet && m_bufferGeometry == bufferGeometry && pendingGeometryUpdate() == PendingGeometryNone) { + return; + } + m_bufferGeometry = bufferGeometry; + if (areGeometryUpdatesBlocked()) { + if (pendingGeometryUpdate() == PendingGeometryForced) + {} // maximum, nothing needed + else if (force == ForceGeometrySet) + setPendingGeometryUpdate(PendingGeometryForced); + else + setPendingGeometryUpdate(PendingGeometryNormal); + return; + } + updateServerGeometry(); + 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 the buffer size is changed. + if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) { + discardWindowPixmap(); + } + emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); + addRepaintDuringGeometryUpdates(); + updateGeometryBeforeUpdateBlocking(); + // TODO: this signal is emitted too often + emit geometryChanged(); +} + +void X11Client::plainResize(int w, int h, ForceGeometry_t force) +{ + QSize frameSize(w, h); + QSize bufferSize; + + // this code is also duplicated in X11Client::setGeometry(), and it's also commented there + if (shade_geometry_change) + ; // nothing + else if (isShade()) { + if (frameSize.height() == borderTop() + borderBottom()) { + qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; + } else { + m_clientGeometry.setSize(frameSizeToClientSize(frameSize)); + frameSize.setHeight(borderTop() + borderBottom()); + } + } else { + m_clientGeometry.setSize(frameSizeToClientSize(frameSize)); + } + if (isDecorated()) { + bufferSize = frameSize; + } else { + bufferSize = m_clientGeometry.size(); + } + if (!areGeometryUpdatesBlocked() && frameSize != rules()->checkSize(frameSize)) { + qCDebug(KWIN_CORE) << "forced size fail:" << frameSize << ":" << rules()->checkSize(frameSize); + } + m_frameGeometry.setSize(frameSize); + // resuming geometry updates is handled only in setGeometry() + Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); + if (force == NormalGeometrySet && m_bufferGeometry.size() == bufferSize) { + return; + } + m_bufferGeometry.setSize(bufferSize); + if (areGeometryUpdatesBlocked()) { + if (pendingGeometryUpdate() == PendingGeometryForced) + {} // maximum, nothing needed + else if (force == ForceGeometrySet) + setPendingGeometryUpdate(PendingGeometryForced); + else + setPendingGeometryUpdate(PendingGeometryNormal); + return; + } + updateServerGeometry(); + updateWindowRules(Rules::Position|Rules::Size); + screens()->setCurrent(this); + workspace()->updateStackingOrder(); + if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) { + discardWindowPixmap(); + } + emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); + addRepaintDuringGeometryUpdates(); + updateGeometryBeforeUpdateBlocking(); + // TODO: this signal is emitted too often + emit geometryChanged(); +} +void X11Client::updateServerGeometry() +{ + if (m_frame.geometry().size() != m_bufferGeometry.size() || pendingGeometryUpdate() == PendingGeometryForced) { + resizeDecoration(); + m_frame.setGeometry(m_bufferGeometry); + 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(m_bufferGeometry.topLeft()); // sendSyntheticConfigureNotify() on finish shall be sufficient + } + } else { + m_frame.move(m_bufferGeometry.topLeft()); + sendSyntheticConfigureNotify(); + } + // Unconditionally move the input window: it won't affect rendering + m_decoInputExtent.move(pos() + inputPos()); + } +} + +static bool changeMaximizeRecursion = false; +void X11Client::changeMaximize(bool horizontal, bool vertical, 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); + + // 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 { + setFrameGeometry(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)); + setFrameGeometry(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 { + setFrameGeometry(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)); + setFrameGeometry(r, geom_mode); + } + info->setState(NET::MaxHoriz, NET::Max); + break; + } + + case MaximizeRestore: { + QRect restore = frameGeometry(); + // 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 = frameGeometry(); + 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)); + } + setFrameGeometry(restore, geom_mode); + if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen + Placement::self()->place(this, clientArea); + info->setState(NET::States(), 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())); + } + setFrameGeometry(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; + } + + updateAllowedActions(); + updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size); + emit quickTileModeChanged(); +} + +bool X11Client::userCanSetFullScreen() const +{ + if (!isFullScreenable()) { + return false; + } + return isNormalWindow() || isDialog(); +} + +void X11Client::setFullScreen(bool set, bool user) +{ + set = rules()->checkFullScreen(set); + + const bool wasFullscreen = isFullScreen(); + if (wasFullscreen == set) { + return; + } + if (user && !userCanSetFullScreen()) { + return; + } + + setShade(ShadeNone); + + if (wasFullscreen) { + workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event + } else { + geom_fs_restore = frameGeometry(); + } + + if (set) { + m_fullscreenMode = FullScreenNormal; + workspace()->raiseClient(this); + } else { + m_fullscreenMode = FullScreenNone; + } + + StackingUpdatesBlocker blocker1(workspace()); + GeometryUpdatesBlocker blocker2(this); + + // active fullscreens get different layer + workspace()->updateClientLayer(this); + + info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen); + updateDecoration(false, false); + + if (set) { + if (info->fullscreenMonitors().isSet()) { + setFrameGeometry(fullscreenMonitorsArea(info->fullscreenMonitors())); + } else { + setFrameGeometry(workspace()->clientArea(FullScreenArea, this)); + } + } else { + Q_ASSERT(!geom_fs_restore.isNull()); + const int currentScreen = screen(); + setFrameGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size()))); + if(currentScreen != screen()) { + workspace()->sendClientToScreen(this, currentScreen); + } + } + + updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size); + emit clientFullScreenSet(this, set, user); + emit fullScreenChanged(); +} + + +void X11Client::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()) + setFrameGeometry(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 X11Client::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; +} + +static GeometryTip* geometryTip = nullptr; + +void X11Client::positionGeometryTip() +{ + Q_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 X11Client::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* + // (https://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, nullptr, 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, nullptr)); + 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 X11Client::leaveMoveResize() +{ + if (needsXWindowMove) { + // Do the deferred move + m_frame.move(m_bufferGeometry.topLeft()); + needsXWindowMove = false; + } + if (!isResize()) + sendSyntheticConfigureNotify(); // tell the client about it's new final position + if (geometryTip) { + geometryTip->hide(); + delete geometryTip; + geometryTip = nullptr; + } + 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 = nullptr; + AbstractClient::leaveMoveResize(); +} + +bool X11Client::isWaitingForMoveResizeSync() const +{ + return syncRequest.isPending && isResize(); +} + +void X11Client::doResizeSync() +{ + if (!syncRequest.timeout) { + syncRequest.timeout = new QTimer(this); + connect(syncRequest.timeout, &QTimer::timeout, this, &X11Client::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 moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry()); + m_client.setGeometry(0, 0, moveResizeClientGeometry.width(), moveResizeClientGeometry.height()); +} + +void X11Client::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) +} + +/** + * Returns \a area with the client's strut taken into account. + * + * Used from Workspace in updateClientArea. + */ +// TODO move to Workspace? + +QRect X11Client::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 X11Client::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 X11Client::strutRect(StrutArea area) const +{ + Q_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 X11Client::strutRects() const +{ + StrutRects region; + region += strutRect(StrutAreaTop); + region += strutRect(StrutAreaRight); + region += strutRect(StrutAreaBottom); + region += strutRect(StrutAreaLeft); + return region; +} + +bool X11Client::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 X11Client::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(); +} + +} // namespace