diff --git a/CMakeLists.txt b/CMakeLists.txt index efd2362df..affa16095 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,648 +1,651 @@ project(KWIN) set(PROJECT_VERSION "5.8.90") set(PROJECT_VERSION_MAJOR 5) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.5.0") set(KF5_MIN_VERSION "5.26.0") set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ) find_package(ECM 0.0.11 REQUIRED NO_MODULE) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(GenerateExportHeader) # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Concurrent Core DBus Quick QuickWidgets Script UiTools Widgets X11Extras ) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" TYPE OPTIONAL ) add_feature_info("Qt5Test" Qt5Test_FOUND "Required for building tests") if (NOT Qt5Test_FOUND) set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") endif() include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0 -DQT_USE_QSTRINGBUILDER) # require at least gcc 4.8 if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") if ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "4.8") message(SEND_ERROR "Version ${CMAKE_CXX_COMPILER_VERSION} of the ${CMAKE_CXX_COMPILER_ID} C++ compiler is not supported. Please use version 4.8 or later.") endif() endif() find_package(Qt5Multimedia QUIET) set_package_properties(Qt5Multimedia PROPERTIES PURPOSE "Runtime-only dependency for effect video playback" TYPE RUNTIME ) # required frameworks by Core find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Config ConfigWidgets CoreAddons Crash GlobalAccel I18n Init Notifications Package Plasma WidgetsAddons WindowSystem IconThemes IdleTime Wayland ) # required frameworks by config modules find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Declarative KCMUtils KIO TextWidgets NewStuff Service XmlGui ) find_package(Threads) set_package_properties(Threads PROPERTIES PURPOSE "Needed for VirtualTerminal support in KWin Wayland" TYPE REQUIRED ) # optional frameworks find_package(KF5Activities ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Activities PROPERTIES PURPOSE "Enable building of KWin with kactivities support" TYPE OPTIONAL ) add_feature_info("KF5Activities" KF5Activities_FOUND "Enable building of KWin with kactivities support") find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5DocTools PROPERTIES PURPOSE "Enable building documentation" TYPE OPTIONAL ) add_feature_info("KF5DocTools" KF5DocTools_FOUND "Enable building documentation") find_package(KDecoration2 CONFIG REQUIRED) find_package(KScreenLocker CONFIG REQUIRED) set_package_properties(KScreenLocker PROPERTIES TYPE REQUIRED PURPOSE "For screenlocker integration in kwin_wayland") find_package(Breeze ${PROJECT_VERSION} CONFIG) set_package_properties(Breeze PROPERTIES TYPE OPTIONAL PURPOSE "For setting the default window decoration plugin") if(${Breeze_FOUND}) if(${BREEZE_WITH_KDECORATION}) set(HAVE_BREEZE_DECO true) else() set(HAVE_BREEZE_DECO FALSE) endif() else() set(HAVE_BREEZE_DECO FALSE) endif() add_feature_info("Breeze-Decoration" HAVE_BREEZE_DECO "Default decoration plugin Breeze") find_package(EGL) set_package_properties(EGL PROPERTIES TYPE RUNTIME PURPOSE "Required to build KWin with EGL support" ) find_package(epoxy) set_package_properties(epoxy PROPERTIES DESCRIPTION "libepoxy" URL "http://github.com/anholt/libepoxy" TYPE REQUIRED PURPOSE "OpenGL dispatch library" ) set(HAVE_DL_LIBRARY FALSE) if(epoxy_HAS_GLX) find_library(DL_LIBRARY dl) if(DL_LIBRARY) set(HAVE_DL_LIBRARY TRUE) endif() endif() find_package(Wayland 1.2 REQUIRED COMPONENTS Cursor OPTIONAL_COMPONENTS Egl) set_package_properties(Wayland PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) add_feature_info("Wayland::EGL" Wayland_Egl_FOUND "Enable building of Wayland backend and QPA with EGL support.") set(HAVE_WAYLAND_EGL FALSE) if(Wayland_Egl_FOUND) set(HAVE_WAYLAND_EGL TRUE) endif() find_package(XKB 0.7.0) set_package_properties(XKB PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) find_package(Libinput 1.5) set_package_properties(Libinput PROPERTIES TYPE OPTIONAL PURPOSE "Required for input handling on Wayland.") find_package(UDev) set_package_properties(UDev PROPERTIES URL "http://www.freedesktop.org/software/systemd/libudev/" DESCRIPTION "Linux device library." TYPE OPTIONAL PURPOSE "Required for input handling on Wayland." ) set(HAVE_INPUT FALSE) if (Libinput_FOUND AND UDEV_FOUND) set(HAVE_INPUT TRUE) endif() set(HAVE_UDEV FALSE) if (UDEV_FOUND) set(HAVE_UDEV TRUE) endif() find_package(Libdrm 2.4.62) set_package_properties(Libdrm PROPERTIES TYPE OPTIONAL PURPOSE "Required for drm output on Wayland.") set(HAVE_DRM FALSE) if(Libdrm_FOUND AND UDEV_FOUND) set(HAVE_DRM TRUE) endif() find_package(gbm) set_package_properties(gbm PROPERTIES TYPE OPTIONAL PURPOSE "Required for egl ouput of drm backend.") set(HAVE_GBM FALSE) if(HAVE_DRM AND gbm_FOUND) set(HAVE_GBM TRUE) endif() find_package(libhybris) set_package_properties(libhybris PROPERTIES TYPE OPTIONAL PURPOSE "Required for libhybris backend") set(HAVE_LIBHYBRIS ${libhybris_FOUND}) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" TYPE REQUIRED ) add_feature_info("XInput" X11_Xinput_FOUND "Required for poll-free mouse cursor updates") set(HAVE_X11_XINPUT ${X11_Xinput_FOUND}) # All the required XCB components find_package(XCB 1.10 REQUIRED COMPONENTS XCB XFIXES DAMAGE COMPOSITE SHAPE SYNC RENDER RANDR KEYSYMS IMAGE SHM GLX CURSOR OPTIONAL_COMPONENTS ICCCM ) set_package_properties(XCB PROPERTIES TYPE REQUIRED) # and the optional XCB dependencies if (XCB_ICCCM_VERSION VERSION_LESS "0.4") set(XCB_ICCCM_FOUND FALSE) endif() add_feature_info("XCB-ICCCM" XCB_ICCCM_FOUND "Required for building test applications for KWin") find_package(X11_XCB) set_package_properties(X11_XCB PROPERTIES PURPOSE "Required for building X11 windowed backend of kwin_wayland" TYPE OPTIONAL) # dependencies for QPA plugin if(Qt5Core_VERSION VERSION_LESS "5.8.0") find_package(Qt5PlatformSupport REQUIRED) else() find_package(Qt5FontDatabaseSupport REQUIRED) find_package(Qt5ThemeSupport REQUIRED) find_package(Qt5EventDispatcherSupport REQUIRED) endif() find_package(Freetype REQUIRED) set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine" URL "http://www.freetype.org" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Fontconfig REQUIRED) set_package_properties(Fontconfig PROPERTIES DESCRIPTION "Font access configuration library" URL "http://www.freedesktop.org/wiki/Software/fontconfig" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Xwayland) set_package_properties(Xwayland PROPERTIES URL "http://x.org" DESCRIPTION "Xwayland X server" TYPE RUNTIME PURPOSE "Needed for running kwin_wayland" ) ########### configure tests ############### include(CMakeDependentOption) option(KWIN_BUILD_DECORATIONS "Enable building of KWin decorations." ON) option(KWIN_BUILD_KCMS "Enable building of KWin configuration modules." ON) option(KWIN_BUILD_TABBOX "Enable building of KWin Tabbox functionality" ON) option(KWIN_BUILD_XRENDER_COMPOSITING "Enable building of KWin with XRender Compositing support" ON) cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "KF5Activities_FOUND" OFF) # Binary name of KWin set(KWIN_NAME "kwin") set(KWIN_INTERNAL_NAME_X11 "kwin_x11") set(KWIN_INTERNAL_NAME_WAYLAND "kwin_wayland") set(KWIN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # KWIN_HAVE_XRENDER_COMPOSITING - whether XRender-based compositing support is available: may be disabled if( KWIN_BUILD_XRENDER_COMPOSITING ) set( KWIN_HAVE_XRENDER_COMPOSITING 1 ) endif() include_directories(${XKB_INCLUDE_DIR}) include_directories(${epoxy_INCLUDE_DIR}) set(HAVE_EPOXY_GLX ${epoxy_HAS_GLX}) # for things that are also used by kwin libraries configure_file(libkwineffects/kwinconfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects/kwinconfig.h ) # for kwin internal things set(HAVE_X11_XCB ${X11_XCB_FOUND}) include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckSymbolExists) check_include_files(unistd.h HAVE_UNISTD_H) check_include_files(malloc.h HAVE_MALLOC_H) check_include_file("sys/prctl.h" HAVE_SYS_PRCTL_H) check_symbol_exists(PR_SET_DUMPABLE "sys/prctl.h" HAVE_PR_SET_DUMPABLE) 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") configure_file(config-kwin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.h ) ########### global ############### set(kwin_effects_dbus_xml ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.kwin.Effects.xml) include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/libkwineffects ${CMAKE_CURRENT_SOURCE_DIR}/effects ${CMAKE_CURRENT_SOURCE_DIR}/tabbox ) add_subdirectory( libkwineffects ) if(KWIN_BUILD_KCMS) add_subdirectory( kcmkwin ) endif() add_subdirectory( data ) add_subdirectory( effects ) add_subdirectory( scripts ) add_subdirectory( tabbox ) add_subdirectory(scripting) add_subdirectory(helpers) ########### next target ############### set(kwin_KDEINIT_SRCS workspace.cpp dbusinterface.cpp abstract_client.cpp client.cpp client_machine.cpp cursor.cpp debug_console.cpp tabgroup.cpp focuschain.cpp globalshortcuts.cpp input.cpp input_event.cpp input_event_spy.cpp keyboard_input.cpp pointer_input.cpp touch_input.cpp netinfo.cpp placement.cpp atoms.cpp utils.cpp layers.cpp main.cpp options.cpp outline.cpp events.cpp killwindow.cpp geometrytip.cpp screens.cpp shadow.cpp sm.cpp group.cpp manage.cpp overlaywindow.cpp activation.cpp useractions.cpp geometry.cpp rules.cpp composite.cpp toplevel.cpp unmanaged.cpp scene.cpp scene_xrender.cpp scene_opengl.cpp scene_qpainter.cpp screenlockerwatcher.cpp thumbnailitem.cpp lanczosfilter.cpp deleted.cpp effects.cpp effectloader.cpp virtualdesktops.cpp xcbutils.cpp x11eventfilter.cpp logind.cpp onscreennotification.cpp osd.cpp screenedge.cpp scripting/scripting.cpp scripting/workspace_wrapper.cpp scripting/meta.cpp scripting/scriptedeffect.cpp scripting/scriptingutils.cpp scripting/timer.cpp scripting/scripting_model.cpp scripting/dbuscall.cpp scripting/screenedgeitem.cpp scripting/scripting_logging.cpp decorations/decoratedclient.cpp decorations/decorationbridge.cpp decorations/decorationpalette.cpp decorations/settings.cpp decorations/decorationrenderer.cpp decorations/decorations_logging.cpp abstract_egl_backend.cpp platform.cpp shell_client.cpp wayland_server.cpp wayland_cursor_theme.cpp virtualkeyboard.cpp + appmenu.cpp ) if(KWIN_BUILD_TABBOX) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} tabbox/tabbox.cpp tabbox/clientmodel.cpp tabbox/desktopchain.cpp tabbox/desktopmodel.cpp tabbox/switcheritem.cpp tabbox/tabboxconfig.cpp tabbox/tabboxhandler.cpp tabbox/tabbox_logging.cpp ) endif() if(KWIN_BUILD_ACTIVITIES) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} activities.cpp ) endif() if(UDEV_FOUND) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} udev.cpp ) endif() if(HAVE_INPUT) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} libinput/context.cpp libinput/connection.cpp libinput/device.cpp libinput/events.cpp libinput/libinput_logging.cpp virtual_terminal.cpp ) endif() kconfig_add_kcfg_files(kwin_KDEINIT_SRCS settings.kcfgc) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.KWin.xml dbusinterface.h KWin::DBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.kwin.Compositing.xml dbusinterface.h KWin::CompositorDBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl ) qt5_add_dbus_interface( kwin_KDEINIT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.ScreenSaver.xml screenlocker_interface) +qt5_add_dbus_interface( kwin_KDEINIT_SRCS org.kde.kappmenu.xml appmenu_interface ) + qt5_add_resources( kwin_KDEINIT_SRCS resources.qrc ) ki18n_wrap_ui(kwin_KDEINIT_SRCS debug_console.ui shortcutdialog.ui ) ########### target link libraries ############### set(kwin_OWN_LIBS kwineffects kwinxrenderutils kwin4_effect_builtins ) set(kwin_QT_LIBS Qt5::Concurrent Qt5::DBus Qt5::Quick Qt5::Script Qt5::X11Extras ) set(kwin_KDE_LIBS KF5::ConfigCore KF5::CoreAddons KF5::ConfigWidgets KF5::GlobalAccel KF5::GlobalAccelPrivate KF5::I18n KF5::Notifications KF5::Package KF5::Plasma KF5::WindowSystem KDecoration2::KDecoration KDecoration2::KDecoration2Private PW::KScreenLocker ) set(kwin_XLIB_LIBS ${X11_X11_LIB} ${X11_ICE_LIB} ${X11_SM_LIB} ) set(kwin_XCB_LIBS XCB::XCB XCB::XFIXES XCB::DAMAGE XCB::COMPOSITE XCB::SHAPE XCB::SYNC XCB::RENDER XCB::RANDR XCB::KEYSYMS XCB::SHM XCB::GLX ) set(kwin_WAYLAND_LIBS XKB::XKB KF5::WaylandClient KF5::WaylandServer Wayland::Cursor ${CMAKE_THREAD_LIBS_INIT} ) if(KWIN_BUILD_ACTIVITIES) set(kwin_KDE_LIBS ${kwin_KDE_LIBS} KF5::Activities) endif() set(kwinLibs ${kwin_OWN_LIBS} ${kwin_QT_LIBS} ${kwin_KDE_LIBS} ${kwin_XLIB_LIBS} ${kwin_XCB_LIBS} ${kwin_WAYLAND_LIBS} ) if(UDEV_FOUND) set(kwinLibs ${kwinLibs} ${UDEV_LIBS}) endif() if(HAVE_INPUT) set(kwinLibs ${kwinLibs} Libinput::Libinput) endif() add_library(kwin SHARED ${kwin_KDEINIT_SRCS}) set_target_properties(kwin PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) target_link_libraries(kwin ${kwinLibs}) generate_export_header(kwin EXPORT_FILE_NAME kwin_export.h) target_link_libraries(kwin kwinglutils ${epoxy_LIBRARY}) kf5_add_kdeinit_executable(kwin_x11 main_x11.cpp) target_link_libraries(kdeinit_kwin_x11 kwin KF5::Crash) install(TARGETS kwin ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP ) install(TARGETS kdeinit_kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} ) install(TARGETS kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} ) add_executable(kwin_wayland main_wayland.cpp) target_link_libraries(kwin_wayland kwin) install(TARGETS kwin_wayland ${INSTALL_TARGETS_DEFAULT_ARGS} ) add_subdirectory(plugins) ########### install files ############### install( FILES kwin.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}.kcfg ) install( FILES kwin.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR} RENAME ${KWIN_NAME}.notifyrc) install( FILES org.kde.KWin.xml org.kde.kwin.Compositing.xml org.kde.kwin.Effects.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kwin_export.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel) # Install the KWin/Script service type install( FILES scripting/kwinscript.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) ecm_install_icons( ICONS 16-apps-kwin.png 32-apps-kwin.png 48-apps-kwin.png sc-apps-kwin.svgz DESTINATION ${ICON_INSTALL_DIR} THEME hicolor ) add_subdirectory(qml) add_subdirectory(autotests) add_subdirectory(tests) if (KF5DocTools_FOUND) add_subdirectory(doc) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) include(ECMPackageConfigHelpers) set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KWinDBusInterface") ecm_configure_package_config_file(KWinDBusInterfaceConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake" PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR}) diff --git a/abstract_client.cpp b/abstract_client.cpp index a33821a91..6183fd316 100644 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1,1679 +1,1734 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "abstract_client.h" + +#include "appmenu.h" #include "decorations/decoratedclient.h" #include "decorations/decorationpalette.h" #include "decorations/decorationbridge.h" #include "cursor.h" #include "effects.h" #include "focuschain.h" #include "outline.h" #include "screens.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "screenedge.h" #include "tabgroup.h" #include "useractions.h" #include "workspace.h" #include "wayland_server.h" #include #include #include #include #include namespace KWin { QHash> AbstractClient::s_palettes; std::shared_ptr AbstractClient::s_defaultPalette; AbstractClient::AbstractClient() : Toplevel() #ifdef KWIN_BUILD_TABBOX , m_tabBoxClient(QSharedPointer(new TabBox::TabBoxClientImpl(this))) #endif , m_colorScheme(QStringLiteral("kdeglobals")) { connect(this, &AbstractClient::geometryShapeChanged, this, &AbstractClient::geometryChanged); auto signalMaximizeChanged = static_cast(&AbstractClient::clientMaximizedStateChanged); connect(this, signalMaximizeChanged, this, &AbstractClient::geometryChanged); connect(this, &AbstractClient::clientStepUserMovedResized, this, &AbstractClient::geometryChanged); connect(this, &AbstractClient::clientStartUserMovedResized, this, &AbstractClient::moveResizedChanged); connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::moveResizedChanged); connect(this, &AbstractClient::clientStartUserMovedResized, this, &AbstractClient::removeCheckScreenConnection); connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::setupCheckScreenConnection); connect(this, &AbstractClient::paletteChanged, this, &AbstractClient::triggerDecorationRepaint); connect(Decoration::DecorationBridge::self(), &QObject::destroyed, this, &AbstractClient::destroyDecoration); // replace on-screen-display on size changes connect(this, &AbstractClient::geometryShapeChanged, this, [this] (Toplevel *c, const QRect &old) { Q_UNUSED(c) if (isOnScreenDisplay() && !geometry().isEmpty() && old.size() != geometry().size()) { GeometryUpdatesBlocker blocker(this); QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); Placement::self()->place(this, area); setGeometryRestore(geometry()); } } ); + + connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] { + emit hasApplicationMenuChanged(hasApplicationMenu()); + }); } AbstractClient::~AbstractClient() { assert(m_blockGeometryUpdates == 0); Q_ASSERT(m_decoration.decoration == nullptr); } void AbstractClient::updateMouseGrab() { } bool AbstractClient::belongToSameApplication(const AbstractClient *c1, const AbstractClient *c2, bool active_hack) { return c1->belongsToSameApplication(c2, active_hack); } bool AbstractClient::isTransient() const { return false; } TabGroup *AbstractClient::tabGroup() const { return nullptr; } bool AbstractClient::untab(const QRect &toGeometry, bool clientRemoved) { Q_UNUSED(toGeometry) Q_UNUSED(clientRemoved) return false; } bool AbstractClient::isCurrentTab() const { return true; } xcb_timestamp_t AbstractClient::userTime() const { return XCB_TIME_CURRENT_TIME; } void AbstractClient::setSkipSwitcher(bool set) { set = rules()->checkSkipSwitcher(set); if (set == skipSwitcher()) return; m_skipSwitcher = set; updateWindowRules(Rules::SkipSwitcher); emit skipSwitcherChanged(); } void AbstractClient::setSkipPager(bool b) { b = rules()->checkSkipPager(b); if (b == skipPager()) return; m_skipPager = b; doSetSkipPager(); info->setState(b ? NET::SkipPager : NET::States(0), NET::SkipPager); updateWindowRules(Rules::SkipPager); emit skipPagerChanged(); } void AbstractClient::doSetSkipPager() { } void AbstractClient::setSkipTaskbar(bool b) { int was_wants_tab_focus = wantsTabFocus(); if (b == skipTaskbar()) return; m_skipTaskbar = b; doSetSkipTaskbar(); updateWindowRules(Rules::SkipTaskbar); if (was_wants_tab_focus != wantsTabFocus()) { FocusChain::self()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update); } emit skipTaskbarChanged(); } void AbstractClient::setOriginalSkipTaskbar(bool b) { m_originalSkipTaskbar = rules()->checkSkipTaskbar(b); setSkipTaskbar(m_originalSkipTaskbar); } void AbstractClient::doSetSkipTaskbar() { } void AbstractClient::setIcon(const QIcon &icon) { m_icon = icon; emit iconChanged(); } void AbstractClient::setActive(bool act) { if (m_active == act) { return; } m_active = act; const int ruledOpacity = m_active ? rules()->checkOpacityActive(qRound(opacity() * 100.0)) : rules()->checkOpacityInactive(qRound(opacity() * 100.0)); setOpacity(ruledOpacity / 100.0); workspace()->setActiveClient(act ? this : NULL); if (!m_active) cancelAutoRaise(); if (!m_active && shadeMode() == ShadeActivated) setShade(ShadeNormal); StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active windows may get different layer auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isFullScreen()) // fullscreens go high even if their transient is active workspace()->updateClientLayer(*it); doSetActive(); emit activeChanged(); updateMouseGrab(); } void AbstractClient::doSetActive() { } Layer AbstractClient::layer() const { if (m_layer == UnknownLayer) const_cast< AbstractClient* >(this)->m_layer = belongsToLayer(); return m_layer; } void AbstractClient::updateLayer() { if (layer() == belongsToLayer()) return; StackingUpdatesBlocker blocker(workspace()); invalidateLayer(); // invalidate, will be updated when doing restacking for (auto it = transients().constBegin(), end = transients().constEnd(); it != end; ++it) (*it)->updateLayer(); } void AbstractClient::invalidateLayer() { m_layer = UnknownLayer; } Layer AbstractClient::belongsToLayer() const { // NOTICE while showingDesktop, desktops move to the AboveLayer // (interchangeable w/ eg. yakuake etc. which will at first remain visible) // and the docks move into the NotificationLayer (which is between Above- and // ActiveLayer, so that active fullscreen windows will still cover everything) // Since the desktop is also activated, nothing should be in the ActiveLayer, though if (isDesktop()) return workspace()->showingDesktop() ? AboveLayer : DesktopLayer; if (isSplash()) // no damn annoying splashscreens return NormalLayer; // getting in the way of everything else if (isDock()) { if (workspace()->showingDesktop()) return NotificationLayer; return layerForDock(); } if (isOnScreenDisplay()) return OnScreenDisplayLayer; if (isNotification()) return NotificationLayer; if (workspace()->showingDesktop() && belongsToDesktop()) { return AboveLayer; } if (keepBelow()) return BelowLayer; if (isActiveFullScreen()) return ActiveLayer; if (keepAbove()) return AboveLayer; return NormalLayer; } bool AbstractClient::belongsToDesktop() const { return false; } Layer AbstractClient::layerForDock() const { // slight hack for the 'allow window to cover panel' Kicker setting // don't move keepbelow docks below normal window, but only to the same // layer, so that both may be raised to cover the other if (keepBelow()) return NormalLayer; if (keepAbove()) // slight hack for the autohiding panels return AboveLayer; return DockLayer; } void AbstractClient::setKeepAbove(bool b) { b = rules()->checkKeepAbove(b); if (b && !rules()->checkKeepBelow(false)) setKeepBelow(false); if (b == keepAbove()) { // force hint change if different if (info && bool(info->state() & NET::KeepAbove) != keepAbove()) info->setState(keepAbove() ? NET::KeepAbove : NET::States(0), NET::KeepAbove); return; } m_keepAbove = b; if (info) { info->setState(keepAbove() ? NET::KeepAbove : NET::States(0), NET::KeepAbove); } workspace()->updateClientLayer(this); updateWindowRules(Rules::Above); doSetKeepAbove(); emit keepAboveChanged(m_keepAbove); } void AbstractClient::doSetKeepAbove() { } void AbstractClient::setKeepBelow(bool b) { b = rules()->checkKeepBelow(b); if (b && !rules()->checkKeepAbove(false)) setKeepAbove(false); if (b == keepBelow()) { // force hint change if different if (info && bool(info->state() & NET::KeepBelow) != keepBelow()) info->setState(keepBelow() ? NET::KeepBelow : NET::States(0), NET::KeepBelow); return; } m_keepBelow = b; if (info) { info->setState(keepBelow() ? NET::KeepBelow : NET::States(0), NET::KeepBelow); } workspace()->updateClientLayer(this); updateWindowRules(Rules::Below); doSetKeepBelow(); emit keepBelowChanged(m_keepBelow); } void AbstractClient::doSetKeepBelow() { } void AbstractClient::startAutoRaise() { delete m_autoRaiseTimer; m_autoRaiseTimer = new QTimer(this); connect(m_autoRaiseTimer, &QTimer::timeout, this, &AbstractClient::autoRaise); m_autoRaiseTimer->setSingleShot(true); m_autoRaiseTimer->start(options->autoRaiseInterval()); } void AbstractClient::cancelAutoRaise() { delete m_autoRaiseTimer; m_autoRaiseTimer = nullptr; } void AbstractClient::autoRaise() { workspace()->raiseClient(this); cancelAutoRaise(); } bool AbstractClient::wantsTabFocus() const { return (isNormalWindow() || isDialog()) && wantsInput(); } bool AbstractClient::isSpecialWindow() const { // TODO return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay(); } void AbstractClient::demandAttention(bool set) { if (isActive()) set = false; if (m_demandsAttention == set) return; m_demandsAttention = set; if (info) { info->setState(set ? NET::DemandsAttention : NET::States(0), NET::DemandsAttention); } workspace()->clientAttentionChanged(this, set); emit demandsAttentionChanged(); } void AbstractClient::setDesktop(int desktop) { const int numberOfDesktops = VirtualDesktopManager::self()->count(); if (desktop != NET::OnAllDesktops) // Do range check desktop = qMax(1, qMin(numberOfDesktops, desktop)); desktop = qMin(numberOfDesktops, rules()->checkDesktop(desktop)); if (m_desktop == desktop) return; int was_desk = m_desktop; const bool wasOnCurrentDesktop = isOnCurrentDesktop(); m_desktop = desktop; if (info) { info->setDesktop(desktop); } if ((was_desk == NET::OnAllDesktops) != (desktop == NET::OnAllDesktops)) { // onAllDesktops changed workspace()->updateOnAllDesktopsOfTransients(this); } auto transients_stacking_order = workspace()->ensureStackingOrder(transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) (*it)->setDesktop(desktop); if (isModal()) // if a modal dialog is moved, move the mainwindow with it as otherwise // the (just moved) modal dialog will confusingly return to the mainwindow with // the next desktop change { foreach (AbstractClient * c2, mainClients()) c2->setDesktop(desktop); } doSetDesktop(desktop, was_desk); FocusChain::self()->update(this, FocusChain::MakeFirst); updateWindowRules(Rules::Desktop); emit desktopChanged(); if (wasOnCurrentDesktop != isOnCurrentDesktop()) emit desktopPresenceChanged(this, was_desk); } void AbstractClient::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) } void AbstractClient::setOnAllDesktops(bool b) { if ((b && isOnAllDesktops()) || (!b && !isOnAllDesktops())) return; if (b) setDesktop(NET::OnAllDesktops); else setDesktop(VirtualDesktopManager::self()->current()); } bool AbstractClient::isShadeable() const { return false; } void AbstractClient::setShade(bool set) { set ? setShade(ShadeNormal) : setShade(ShadeNone); } void AbstractClient::setShade(ShadeMode mode) { Q_UNUSED(mode) } ShadeMode AbstractClient::shadeMode() const { return ShadeNone; } AbstractClient::Position AbstractClient::titlebarPosition() const { // TODO: still needed, remove? return PositionTop; } bool AbstractClient::titlebarPositionUnderMouse() const { if (!isDecorated()) { return false; } const auto sectionUnderMouse = decoration()->sectionUnderMouse(); if (sectionUnderMouse == Qt::TitleBarArea) { return true; } // check other sections based on titlebarPosition switch (titlebarPosition()) { case AbstractClient::PositionTop: return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::TopSection || sectionUnderMouse == Qt::TopRightSection); case AbstractClient::PositionLeft: return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::LeftSection || sectionUnderMouse == Qt::BottomLeftSection); case AbstractClient::PositionRight: return (sectionUnderMouse == Qt::BottomRightSection || sectionUnderMouse == Qt::RightSection || sectionUnderMouse == Qt::TopRightSection); case AbstractClient::PositionBottom: return (sectionUnderMouse == Qt::BottomLeftSection || sectionUnderMouse == Qt::BottomSection || sectionUnderMouse == Qt::BottomRightSection); default: // nothing return false; } } void AbstractClient::setMinimized(bool set) { set ? minimize() : unminimize(); } void AbstractClient::minimize(bool avoid_animation) { if (!isMinimizable() || isMinimized()) return; if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(0, NET::Shaded); m_minimized = true; doMinimize(); updateWindowRules(Rules::Minimize); FocusChain::self()->update(this, FocusChain::MakeFirstMinimized); // TODO: merge signal with s_minimized emit clientMinimized(this, !avoid_animation); emit minimizedChanged(); } void AbstractClient::unminimize(bool avoid_animation) { if (!isMinimized()) return; if (rules()->checkMinimize(false)) { return; } if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(NET::Shaded, NET::Shaded); m_minimized = false; doMinimize(); updateWindowRules(Rules::Minimize); emit clientUnminimized(this, !avoid_animation); emit minimizedChanged(); } void AbstractClient::doMinimize() { } QPalette AbstractClient::palette() const { if (!m_palette) { return QPalette(); } return m_palette->palette(); } const Decoration::DecorationPalette *AbstractClient::decorationPalette() const { return m_palette.get(); } void AbstractClient::updateColorScheme(QString path) { if (path.isEmpty()) { path = QStringLiteral("kdeglobals"); } if (!m_palette || m_colorScheme != path) { m_colorScheme = path; if (m_palette) { disconnect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange); } auto it = s_palettes.find(m_colorScheme); if (it == s_palettes.end() || it->expired()) { m_palette = std::make_shared(m_colorScheme); if (m_palette->isValid()) { s_palettes[m_colorScheme] = m_palette; } else { if (!s_defaultPalette) { s_defaultPalette = std::make_shared(QStringLiteral("kdeglobals")); s_palettes[QStringLiteral("kdeglobals")] = s_defaultPalette; } m_palette = s_defaultPalette; } if (m_colorScheme == QStringLiteral("kdeglobals")) { s_defaultPalette = m_palette; } } else { m_palette = it->lock(); } connect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange); emit paletteChanged(palette()); } } void AbstractClient::handlePaletteChange() { emit paletteChanged(palette()); } void AbstractClient::keepInArea(QRect area, bool partial) { if (partial) { // increase the area so that can have only 100 pixels in the area area.setLeft(qMin(area.left() - width() + 100, area.left())); area.setTop(qMin(area.top() - height() + 100, area.top())); area.setRight(qMax(area.right() + width() - 100, area.right())); area.setBottom(qMax(area.bottom() + height() - 100, area.bottom())); } if (!partial) { // resize to fit into area if (area.width() < width() || area.height() < height()) resizeWithChecks(qMin(area.width(), width()), qMin(area.height(), height())); } int tx = x(), ty = y(); if (geometry().right() > area.right() && width() <= area.width()) tx = area.right() - width() + 1; if (geometry().bottom() > area.bottom() && height() <= area.height()) ty = area.bottom() - height() + 1; if (!area.contains(geometry().topLeft())) { if (tx < area.x()) tx = area.x(); if (ty < area.y()) ty = area.y(); } if (tx != x() || ty != y()) move(tx, ty); } QSize AbstractClient::maxSize() const { return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX)); } QSize AbstractClient::minSize() const { return rules()->checkMinSize(QSize(0, 0)); } void AbstractClient::updateMoveResize(const QPointF ¤tGlobalCursor) { handleMoveResize(pos(), currentGlobalCursor.toPoint()); } bool AbstractClient::hasStrut() const { return false; } void AbstractClient::setupWindowManagementInterface() { if (m_windowManagementInterface) { // already setup return; } if (!waylandServer() || !surface()) { return; } if (!waylandServer()->windowManagement()) { return; } using namespace KWayland::Server; auto w = waylandServer()->windowManagement()->createWindow(waylandServer()->windowManagement()); w->setTitle(caption()); w->setVirtualDesktop(isOnAllDesktops() ? 0 : desktop() - 1); w->setActive(isActive()); w->setFullscreen(isFullScreen()); w->setKeepAbove(keepAbove()); w->setKeepBelow(keepBelow()); w->setMaximized(maximizeMode() == KWin::MaximizeFull); w->setMinimized(isMinimized()); w->setOnAllDesktops(isOnAllDesktops()); w->setDemandsAttention(isDemandingAttention()); w->setCloseable(isCloseable()); w->setMaximizeable(isMaximizable()); w->setMinimizeable(isMinimizable()); w->setFullscreenable(isFullScreenable()); w->setIcon(icon()); auto updateAppId = [this, w] { w->setAppId(QString::fromUtf8(m_desktopFileName.isEmpty() ? resourceName() : m_desktopFileName)); }; updateAppId(); w->setSkipTaskbar(skipTaskbar()); w->setShadeable(isShadeable()); w->setShaded(isShade()); w->setResizable(isResizable()); w->setMovable(isMovable()); w->setVirtualDesktopChangeable(true); // FIXME Matches Client::actionSupported(), but both should be implemented. w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); w->setGeometry(geom); connect(this, &AbstractClient::skipTaskbarChanged, w, [w, this] { w->setSkipTaskbar(skipTaskbar()); } ); connect(this, &AbstractClient::captionChanged, w, [w, this] { w->setTitle(caption()); }); connect(this, &AbstractClient::desktopChanged, w, [w, this] { if (isOnAllDesktops()) { w->setOnAllDesktops(true); return; } w->setVirtualDesktop(desktop() - 1); w->setOnAllDesktops(false); } ); connect(this, &AbstractClient::activeChanged, w, [w, this] { w->setActive(isActive()); }); connect(this, &AbstractClient::fullScreenChanged, w, [w, this] { w->setFullscreen(isFullScreen()); }); connect(this, &AbstractClient::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove); connect(this, &AbstractClient::keepBelowChanged, w, &PlasmaWindowInterface::setKeepBelow); connect(this, &AbstractClient::minimizedChanged, w, [w, this] { w->setMinimized(isMinimized()); }); connect(this, static_cast(&AbstractClient::clientMaximizedStateChanged), w, [w] (KWin::AbstractClient *c, MaximizeMode mode) { Q_UNUSED(c); w->setMaximized(mode == KWin::MaximizeFull); } ); connect(this, &AbstractClient::demandsAttentionChanged, w, [w, this] { w->setDemandsAttention(isDemandingAttention()); }); connect(this, &AbstractClient::iconChanged, w, [w, this] { w->setIcon(icon()); } ); connect(this, &AbstractClient::windowClassChanged, w, updateAppId); connect(this, &AbstractClient::desktopFileNameChanged, w, updateAppId); connect(this, &AbstractClient::shadeChanged, w, [w, this] { w->setShaded(isShade()); }); connect(this, &AbstractClient::transientChanged, w, [w, this] { w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); } ); connect(this, &AbstractClient::geometryChanged, w, [w, this] { w->setGeometry(geom); } ); connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); }); connect(w, &PlasmaWindowInterface::moveRequested, this, [this] { Cursor::setPos(geometry().center()); performMouseCommand(Options::MouseMove, Cursor::pos()); } ); connect(w, &PlasmaWindowInterface::resizeRequested, this, [this] { Cursor::setPos(geometry().bottomRight()); performMouseCommand(Options::MouseResize, Cursor::pos()); } ); connect(w, &PlasmaWindowInterface::virtualDesktopRequested, this, [this] (quint32 desktop) { workspace()->sendClientToDesktop(this, desktop + 1, true); } ); connect(w, &PlasmaWindowInterface::fullscreenRequested, this, [this] (bool set) { setFullScreen(set, false); } ); connect(w, &PlasmaWindowInterface::minimizedRequested, this, [this] (bool set) { if (set) { minimize(); } else { unminimize(); } } ); connect(w, &PlasmaWindowInterface::maximizedRequested, this, [this] (bool set) { maximize(set ? MaximizeFull : MaximizeRestore); } ); connect(w, &PlasmaWindowInterface::keepAboveRequested, this, [this] (bool set) { setKeepAbove(set); } ); connect(w, &PlasmaWindowInterface::keepBelowRequested, this, [this] (bool set) { setKeepBelow(set); } ); connect(w, &PlasmaWindowInterface::demandsAttentionRequested, this, [this] (bool set) { demandAttention(set); } ); connect(w, &PlasmaWindowInterface::activeRequested, this, [this] (bool set) { if (set) { workspace()->activateClient(this, true); } } ); connect(w, &PlasmaWindowInterface::shadedRequested, this, [this] (bool set) { setShade(set); } ); m_windowManagementInterface = w; } void AbstractClient::destroyWindowManagementInterface() { if (m_windowManagementInterface) { m_windowManagementInterface->unmap(); m_windowManagementInterface = nullptr; } } Options::MouseCommand AbstractClient::getMouseCommand(Qt::MouseButton button, bool *handled) const { *handled = false; if (button == Qt::NoButton) { return Options::MouseNothing; } if (isActive()) { if (options->isClickRaise()) { *handled = true; return Options::MouseActivateRaiseAndPassClick; } } else { *handled = true; switch (button) { case Qt::LeftButton: return options->commandWindow1(); case Qt::MiddleButton: return options->commandWindow2(); case Qt::RightButton: return options->commandWindow3(); default: // all other buttons pass Activate & Pass Client return Options::MouseActivateAndPassClick; } } return Options::MouseNothing; } Options::MouseCommand AbstractClient::getWheelCommand(Qt::Orientation orientation, bool *handled) const { *handled = false; if (orientation != Qt::Vertical) { return Options::MouseNothing; } if (!isActive()) { *handled = true; return options->commandWindowWheel(); } return Options::MouseNothing; } bool AbstractClient::performMouseCommand(Options::MouseCommand cmd, const QPoint &globalPos) { bool replay = false; switch(cmd) { case Options::MouseRaise: workspace()->raiseClient(this); break; case Options::MouseLower: { workspace()->lowerClient(this); // used to be activateNextClient(this), then topClientOnDesktop // since this is a mouseOp it's however safe to use the client under the mouse instead if (isActive() && options->focusPolicyIsReasonable()) { AbstractClient *next = workspace()->clientUnderMouse(screen()); if (next && next != this) workspace()->requestFocus(next, false); } break; } case Options::MouseOperationsMenu: if (isActive() && options->isClickRaise()) autoRaise(); workspace()->showWindowMenu(QRect(globalPos, globalPos), this); break; case Options::MouseToggleRaiseAndLower: workspace()->raiseOrLowerClient(this); break; case Options::MouseActivateAndRaise: { replay = isActive(); // for clickraise mode bool mustReplay = !rules()->checkAcceptFocus(acceptsFocus()); if (mustReplay) { ToplevelList::const_iterator it = workspace()->stackingOrder().constEnd(), begin = workspace()->stackingOrder().constBegin(); while (mustReplay && --it != begin && *it != this) { AbstractClient *c = qobject_cast(*it); if (!c || (c->keepAbove() && !keepAbove()) || (keepBelow() && !c->keepBelow())) continue; // can never raise above "it" mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->geometry().intersects(geometry())); } } workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); screens()->setCurrent(globalPos); replay = replay || mustReplay; break; } case Options::MouseActivateAndLower: workspace()->requestFocus(this); workspace()->lowerClient(this); screens()->setCurrent(globalPos); replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); break; case Options::MouseActivate: replay = isActive(); // for clickraise mode workspace()->takeActivity(this, Workspace::ActivityFocus); screens()->setCurrent(globalPos); replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); break; case Options::MouseActivateRaiseAndPassClick: workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); screens()->setCurrent(globalPos); replay = true; break; case Options::MouseActivateAndPassClick: workspace()->takeActivity(this, Workspace::ActivityFocus); screens()->setCurrent(globalPos); replay = true; break; case Options::MouseMaximize: maximize(MaximizeFull); break; case Options::MouseRestore: maximize(MaximizeRestore); break; case Options::MouseMinimize: minimize(); break; case Options::MouseAbove: { StackingUpdatesBlocker blocker(workspace()); if (keepBelow()) setKeepBelow(false); else setKeepAbove(true); break; } case Options::MouseBelow: { StackingUpdatesBlocker blocker(workspace()); if (keepAbove()) setKeepAbove(false); else setKeepBelow(true); break; } case Options::MousePreviousDesktop: workspace()->windowToPreviousDesktop(this); break; case Options::MouseNextDesktop: workspace()->windowToNextDesktop(this); break; case Options::MouseOpacityMore: if (!isDesktop()) // No point in changing the opacity of the desktop setOpacity(qMin(opacity() + 0.1, 1.0)); break; case Options::MouseOpacityLess: if (!isDesktop()) // No point in changing the opacity of the desktop setOpacity(qMax(opacity() - 0.1, 0.1)); break; case Options::MousePreviousTab: if (tabGroup()) tabGroup()->activatePrev(); break; case Options::MouseNextTab: if (tabGroup()) tabGroup()->activateNext(); break; case Options::MouseClose: closeWindow(); break; case Options::MouseActivateRaiseAndMove: case Options::MouseActivateRaiseAndUnrestrictedMove: workspace()->raiseClient(this); workspace()->requestFocus(this); screens()->setCurrent(globalPos); // fallthrough case Options::MouseMove: case Options::MouseUnrestrictedMove: { if (!isMovableAcrossScreens()) break; if (isMoveResize()) finishMoveResize(false); setMoveResizePointerMode(PositionCenter); setMoveResizePointerButtonDown(true); setMoveOffset(QPoint(globalPos.x() - x(), globalPos.y() - y())); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize((cmd == Options::MouseActivateRaiseAndUnrestrictedMove || cmd == Options::MouseUnrestrictedMove)); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); break; } case Options::MouseResize: case Options::MouseUnrestrictedResize: { if (!isResizable() || isShade()) break; if (isMoveResize()) finishMoveResize(false); setMoveResizePointerButtonDown(true); const QPoint moveOffset = QPoint(globalPos.x() - x(), globalPos.y() - y()); // map from global setMoveOffset(moveOffset); int x = moveOffset.x(), y = moveOffset.y(); bool left = x < width() / 3; bool right = x >= 2 * width() / 3; bool top = y < height() / 3; bool bot = y >= 2 * height() / 3; Position mode; if (top) mode = left ? PositionTopLeft : (right ? PositionTopRight : PositionTop); else if (bot) mode = left ? PositionBottomLeft : (right ? PositionBottomRight : PositionBottom); else mode = (x < width() / 2) ? PositionLeft : PositionRight; setMoveResizePointerMode(mode); setInvertedMoveOffset(rect().bottomRight() - moveOffset); setUnrestrictedMoveResize((cmd == Options::MouseUnrestrictedResize)); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); break; } case Options::MouseDragTab: case Options::MouseNothing: default: replay = true; break; } return replay; } void AbstractClient::setTransientFor(AbstractClient *transientFor) { if (transientFor == this) { // cannot be transient for one self return; } if (m_transientFor == transientFor) { return; } m_transientFor = transientFor; emit transientChanged(); } const AbstractClient *AbstractClient::transientFor() const { return m_transientFor; } AbstractClient *AbstractClient::transientFor() { return m_transientFor; } bool AbstractClient::hasTransientPlacementHint() const { return false; } QPoint AbstractClient::transientPlacementHint() const { return QPoint(); } bool AbstractClient::hasTransient(const AbstractClient *c, bool indirect) const { Q_UNUSED(indirect); return c->transientFor() == this; } QList< AbstractClient* > AbstractClient::mainClients() const { if (const AbstractClient *t = transientFor()) { return QList{const_cast< AbstractClient* >(t)}; } return QList(); } QList AbstractClient::allMainClients() const { auto result = mainClients(); foreach (const auto *cl, result) { result += cl->allMainClients(); } return result; } void AbstractClient::setModal(bool m) { // Qt-3.2 can have even modal normal windows :( if (m_modal == m) return; m_modal = m; emit modalChanged(); // Changing modality for a mapped window is weird (?) // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG } bool AbstractClient::isModal() const { return m_modal; } void AbstractClient::addTransient(AbstractClient *cl) { assert(!m_transients.contains(cl)); assert(cl != this); m_transients.append(cl); } void AbstractClient::removeTransient(AbstractClient *cl) { m_transients.removeAll(cl); if (cl->transientFor() == this) { cl->setTransientFor(nullptr); } } void AbstractClient::removeTransientFromList(AbstractClient *cl) { m_transients.removeAll(cl); } bool AbstractClient::isActiveFullScreen() const { if (!isFullScreen()) return false; const auto ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker // according to NETWM spec implementation notes suggests // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer. // we'll also take the screen into account return ac && (ac == this || ac->screen() != screen()); } #define BORDER(which) \ int AbstractClient::border##which() const \ { \ return isDecorated() ? decoration()->border##which() : 0; \ } BORDER(Bottom) BORDER(Left) BORDER(Right) BORDER(Top) #undef BORDER QSize AbstractClient::sizeForClientSize(const QSize &wsize, Sizemode mode, bool noframe) const { Q_UNUSED(mode) Q_UNUSED(noframe) return wsize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); } void AbstractClient::addRepaintDuringGeometryUpdates() { const QRect deco_rect = visibleRect(); addLayerRepaint(m_visibleRectBeforeGeometryUpdate); addLayerRepaint(deco_rect); // trigger repaint of window's new location m_visibleRectBeforeGeometryUpdate = deco_rect; } void AbstractClient::updateGeometryBeforeUpdateBlocking() { m_geometryBeforeUpdateBlocking = geom; } void AbstractClient::updateTabGroupStates(TabGroup::States) { } void AbstractClient::doMove(int, int) { } void AbstractClient::updateInitialMoveResizeGeometry() { m_moveResize.initialGeometry = geometry(); m_moveResize.geometry = m_moveResize.initialGeometry; m_moveResize.startScreen = screen(); } void AbstractClient::updateCursor() { Position m = moveResizePointerMode(); if (!isResizable() || isShade()) m = PositionCenter; Qt::CursorShape c = Qt::ArrowCursor; switch(m) { case PositionTopLeft: case PositionBottomRight: c = Qt::SizeFDiagCursor; break; case PositionBottomLeft: case PositionTopRight: c = Qt::SizeBDiagCursor; break; case PositionTop: case PositionBottom: c = Qt::SizeVerCursor; break; case PositionLeft: case PositionRight: c = Qt::SizeHorCursor; break; default: if (isMoveResize()) c = Qt::SizeAllCursor; else c = Qt::ArrowCursor; break; } if (c == m_moveResize.cursor) return; m_moveResize.cursor = c; emit moveResizeCursorChanged(c); } void AbstractClient::leaveMoveResize() { workspace()->setClientIsMoving(nullptr); setMoveResize(false); if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) ScreenEdges::self()->reserveDesktopSwitching(false, Qt::Vertical|Qt::Horizontal); if (isElectricBorderMaximizing()) { outline()->hide(); elevate(false); } } bool AbstractClient::s_haveResizeEffect = false; void AbstractClient::updateHaveResizeEffect() { s_haveResizeEffect = effects && static_cast(effects)->provides(Effect::Resize); } bool AbstractClient::doStartMoveResize() { return true; } void AbstractClient::positionGeometryTip() { } void AbstractClient::doPerformMoveResize() { } bool AbstractClient::isWaitingForMoveResizeSync() const { return false; } void AbstractClient::doResizeSync() { } void AbstractClient::checkQuickTilingMaximizationZones(int xroot, int yroot) { QuickTileMode mode = QuickTileNone; 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 |= QuickTileLeft; innerBorder = isInScreen(QPoint(area.x() - 1, yroot)); } else if (xroot >= area.x() + area.width() - 20) { mode |= QuickTileRight; innerBorder = isInScreen(QPoint(area.right() + 1, yroot)); } } if (mode != QuickTileNone) { if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio()) mode |= QuickTileTop; else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio()) mode |= QuickTileBottom; } else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) { mode = QuickTileMaximize; 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() != QuickTileNone); }); } m_electricMaximizingDelay->start(); } else { setElectricBorderMaximizing(mode != QuickTileNone); } } } void AbstractClient::keyPressEvent(uint key_code) { if (!isMove() && !isResize()) return; bool is_control = key_code & Qt::CTRL; bool is_alt = key_code & Qt::ALT; key_code = key_code & ~Qt::KeyboardModifierMask; int delta = is_control ? 1 : is_alt ? 32 : 8; QPoint pos = Cursor::pos(); switch(key_code) { case Qt::Key_Left: pos.rx() -= delta; break; case Qt::Key_Right: pos.rx() += delta; break; case Qt::Key_Up: pos.ry() -= delta; break; case Qt::Key_Down: pos.ry() += delta; break; case Qt::Key_Space: case Qt::Key_Return: case Qt::Key_Enter: finishMoveResize(false); setMoveResizePointerButtonDown(false); updateCursor(); break; case Qt::Key_Escape: finishMoveResize(true); setMoveResizePointerButtonDown(false); updateCursor(); break; default: return; } Cursor::setPos(pos); } QSize AbstractClient::resizeIncrements() const { return QSize(1, 1); } void AbstractClient::dontMoveResize() { setMoveResizePointerButtonDown(false); stopDelayedMoveResize(); if (isMoveResize()) finishMoveResize(false); } AbstractClient::Position AbstractClient::mousePosition() const { if (isDecorated()) { switch (decoration()->sectionUnderMouse()) { case Qt::BottomLeftSection: return PositionBottomLeft; case Qt::BottomRightSection: return PositionBottomRight; case Qt::BottomSection: return PositionBottom; case Qt::LeftSection: return PositionLeft; case Qt::RightSection: return PositionRight; case Qt::TopSection: return PositionTop; case Qt::TopLeftSection: return PositionTopLeft; case Qt::TopRightSection: return PositionTopRight; default: return PositionCenter; } } return PositionCenter; } void AbstractClient::endMoveResize() { setMoveResizePointerButtonDown(false); stopDelayedMoveResize(); if (isMoveResize()) { finishMoveResize(false); setMoveResizePointerMode(mousePosition()); } updateCursor(); } void AbstractClient::destroyDecoration() { delete m_decoration.decoration; m_decoration.decoration = nullptr; } bool AbstractClient::decorationHasAlpha() const { if (!isDecorated() || decoration()->isOpaque()) { // either no decoration or decoration has alpha disabled return false; } return true; } void AbstractClient::triggerDecorationRepaint() { if (isDecorated()) { decoration()->update(); } } void AbstractClient::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); top = QRect(r.x(), r.y(), r.width(), borderTop()); bottom = QRect(r.x(), r.y() + r.height() - borderBottom(), r.width(), borderBottom()); left = QRect(r.x(), r.y() + top.height(), borderLeft(), r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight(), r.y() + top.height(), borderRight(), r.height() - top.height() - bottom.height()); } void AbstractClient::processDecorationMove(const QPoint &localPos, const QPoint &globalPos) { if (isMoveResizePointerButtonDown()) { handleMoveResize(localPos.x(), localPos.y(), globalPos.x(), globalPos.y()); return; } // TODO: handle modifiers Position newmode = mousePosition(); if (newmode != moveResizePointerMode()) { setMoveResizePointerMode(newmode); updateCursor(); } } bool AbstractClient::processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu) { Options::MouseCommand com = Options::MouseNothing; bool active = isActive(); if (!wantsInput()) // we cannot be active, use it anyway active = true; // check whether it is a double click if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) { if (m_decoration.doubleClickTimer.isValid()) { const qint64 interval = m_decoration.doubleClickTimer.elapsed(); m_decoration.doubleClickTimer.invalidate(); if (interval > QGuiApplication::styleHints()->mouseDoubleClickInterval()) { m_decoration.doubleClickTimer.invalidate(); // expired -> new first click and pot. init } else { Workspace::self()->performWindowOperation(this, options->operationTitlebarDblClick()); dontMoveResize(); return false; } } else { m_decoration.doubleClickTimer.start(); // new first click and pot. init, could be invalidated by release - see below } } if (event->button() == Qt::LeftButton) com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); else if (event->button() == Qt::MidButton) com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); else if (event->button() == Qt::RightButton) com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); if (event->button() == Qt::LeftButton && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching && com != Options::MouseMinimize // mouse release event && com != Options::MouseDragTab) { setMoveResizePointerMode(mousePosition()); setMoveResizePointerButtonDown(true); setMoveOffset(event->pos()); setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); startDelayedMoveResize(); updateCursor(); } // In the new API the decoration may process the menu action to display an inactive tab's menu. // If the event is unhandled then the core will create one for the active window in the group. if (!ignoreMenu || com != Options::MouseOperationsMenu) performMouseCommand(com, event->globalPos()); return !( // Return events that should be passed to the decoration in the new API com == Options::MouseRaise || com == Options::MouseOperationsMenu || com == Options::MouseActivateAndRaise || com == Options::MouseActivate || com == Options::MouseActivateRaiseAndPassClick || com == Options::MouseActivateAndPassClick || com == Options::MouseDragTab || com == Options::MouseNothing); } void AbstractClient::processDecorationButtonRelease(QMouseEvent *event) { if (isDecorated()) { if (event->isAccepted() || !titlebarPositionUnderMouse()) { invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick } } if (event->buttons() == Qt::NoButton) { setMoveResizePointerButtonDown(false); stopDelayedMoveResize(); if (isMoveResize()) { finishMoveResize(false); setMoveResizePointerMode(mousePosition()); } updateCursor(); } } void AbstractClient::startDecorationDoubleClickTimer() { m_decoration.doubleClickTimer.start(); } void AbstractClient::invalidateDecorationDoubleClickTimer() { m_decoration.doubleClickTimer.invalidate(); } bool AbstractClient::providesContextHelp() const { return false; } void AbstractClient::showContextHelp() { } QPointer AbstractClient::decoratedClient() const { return m_decoration.client; } void AbstractClient::setDecoratedClient(QPointer< Decoration::DecoratedClientImpl > client) { m_decoration.client = client; } void AbstractClient::enterEvent(const QPoint &globalPos) { // TODO: shade hover if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown()) return; if (options->isAutoRaise() && !isDesktop() && !isDock() && workspace()->focusChangeEnabled() && globalPos != workspace()->focusMousePosition() && workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), options->isSeparateScreenFocus() ? screen() : -1) != this) { startAutoRaise(); } if (isDesktop() || isDock()) return; // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus // change came because of window changes (e.g. closing a window) - #92290 if (options->focusPolicy() != Options::FocusFollowsMouse || globalPos != workspace()->focusMousePosition()) { workspace()->requestDelayFocus(this); } } void AbstractClient::leaveEvent() { cancelAutoRaise(); workspace()->cancelDelayFocus(); // TODO: shade hover // TODO: send hover leave to deco // TODO: handle Options::FocusStrictlyUnderMouse } QRect AbstractClient::iconGeometry() const { if (!windowManagementInterface() || !waylandServer()) { // window management interface is only available if the surface is mapped return QRect(); } int minDistance = INT_MAX; AbstractClient *candidatePanel = nullptr; QRect candidateGeom; for (auto i = windowManagementInterface()->minimizedGeometries().constBegin(), end = windowManagementInterface()->minimizedGeometries().constEnd(); i != end; ++i) { AbstractClient *client = waylandServer()->findAbstractClient(i.key()); if (!client) { continue; } const int distance = QPoint(client->pos() - pos()).manhattanLength(); if (distance < minDistance) { minDistance = distance; candidatePanel = client; candidateGeom = i.value(); } } if (!candidatePanel) { return QRect(); } return candidateGeom.translated(candidatePanel->pos()); } QRect AbstractClient::inputGeometry() const { if (isDecorated()) { return Toplevel::inputGeometry() + decoration()->resizeOnlyBorders(); } return Toplevel::inputGeometry(); } bool AbstractClient::dockWantsInput() const { return false; } void AbstractClient::setDesktopFileName(const QByteArray &name) { if (name == m_desktopFileName) { return; } m_desktopFileName = name; emit desktopFileNameChanged(); } QString AbstractClient::iconFromDesktopFile() const { if (m_desktopFileName.isEmpty()) { return QString(); } QString desktopFile = QString::fromUtf8(m_desktopFileName); if (!desktopFile.endsWith(QLatin1String(".desktop"))) { desktopFile.append(QLatin1String(".desktop")); } KDesktopFile df(desktopFile); return df.readIcon(); } +bool AbstractClient::hasApplicationMenu() const +{ + return ApplicationMenu::self()->applicationMenuEnabled() && !m_applicationMenuServiceName.isEmpty() && !m_applicationMenuObjectPath.isEmpty(); +} + +void AbstractClient::updateApplicationMenuServiceName(const QString &serviceName) +{ + const bool old_hasApplicationMenu = hasApplicationMenu(); + + m_applicationMenuServiceName = serviceName; + + const bool new_hasApplicationMenu = hasApplicationMenu(); + + if (old_hasApplicationMenu != new_hasApplicationMenu) { + emit hasApplicationMenuChanged(new_hasApplicationMenu); + } +} + +void AbstractClient::updateApplicationMenuObjectPath(const QString &objectPath) +{ + const bool old_hasApplicationMenu = hasApplicationMenu(); + + m_applicationMenuObjectPath = objectPath; + + const bool new_hasApplicationMenu = hasApplicationMenu(); + + if (old_hasApplicationMenu != new_hasApplicationMenu) { + emit hasApplicationMenuChanged(new_hasApplicationMenu); + } +} + +void AbstractClient::setApplicationMenuActive(bool applicationMenuActive) +{ + if (m_applicationMenuActive != applicationMenuActive) { + m_applicationMenuActive = applicationMenuActive; + emit applicationMenuActiveChanged(applicationMenuActive); + } +} + +void AbstractClient::showApplicationMenu(int actionId) +{ + if (isDecorated()) { + decoration()->showApplicationMenu(actionId); + } else { + // we don't know where the application menu button will be, show it in the top left corner instead + Workspace::self()->showApplicationMenu(QRect(), this, actionId); + } +} + } diff --git a/abstract_client.h b/abstract_client.h index 25feb7f82..e509fac93 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -1,1089 +1,1125 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_ABSTRACT_CLIENT_H #define KWIN_ABSTRACT_CLIENT_H #include "toplevel.h" #include "options.h" #include "rules.h" #include "tabgroup.h" #include #include #include namespace KWayland { namespace Server { class PlasmaWindowInterface; } } namespace KDecoration2 { class Decoration; } namespace KWin { namespace TabBox { class TabBoxClientImpl; } namespace Decoration { class DecoratedClientImpl; class DecorationPalette; } class KWIN_EXPORT AbstractClient : public Toplevel { Q_OBJECT /** * Whether this Client is fullScreen. A Client might either be fullScreen due to the _NET_WM property * or through a legacy support hack. The fullScreen state can only be changed if the Client does not * use the legacy hack. To be sure whether the state changed, connect to the notify signal. **/ Q_PROPERTY(bool fullScreen READ isFullScreen WRITE setFullScreen NOTIFY fullScreenChanged) /** * Whether the Client can be set to fullScreen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool fullScreenable READ isFullScreenable) /** * Whether this Client is the currently visible Client in its Client Group (Window Tabs). * For change connect to the visibleChanged signal on the Client's Group. **/ Q_PROPERTY(bool isCurrentTab READ isCurrentTab) /** * Whether this Client is active or not. Use Workspace::activateClient() to activate a Client. * @see Workspace::activateClient **/ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) /** * The desktop this Client is on. If the Client is on all desktops the property has value -1. **/ Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) /** * Whether the Client is on all desktops. That is desktop is -1. **/ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops WRITE setOnAllDesktops NOTIFY desktopChanged) /** * Indicates that the window should not be included on a taskbar. **/ Q_PROPERTY(bool skipTaskbar READ skipTaskbar WRITE setSkipTaskbar NOTIFY skipTaskbarChanged) /** * Indicates that the window should not be included on a Pager. **/ Q_PROPERTY(bool skipPager READ skipPager WRITE setSkipPager NOTIFY skipPagerChanged) /** * Whether the Client should be excluded from window switching effects. **/ Q_PROPERTY(bool skipSwitcher READ skipSwitcher WRITE setSkipSwitcher NOTIFY skipSwitcherChanged) /** * Whether the window can be closed by the user. The value is evaluated each time the getter is called. * Because of that no changed signal is provided. **/ Q_PROPERTY(bool closeable READ isCloseable) Q_PROPERTY(QIcon icon READ icon NOTIFY iconChanged) /** * Whether the Client is set to be kept above other windows. **/ Q_PROPERTY(bool keepAbove READ keepAbove WRITE setKeepAbove NOTIFY keepAboveChanged) /** * Whether the Client is set to be kept below other windows. **/ Q_PROPERTY(bool keepBelow READ keepBelow WRITE setKeepBelow NOTIFY keepBelowChanged) /** * Whether the Client can be shaded. The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool shadeable READ isShadeable) /** * Whether the Client is shaded. **/ Q_PROPERTY(bool shade READ isShade WRITE setShade NOTIFY shadeChanged) /** * Whether the Client can be minimized. The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool minimizable READ isMinimizable) /** * Whether the Client is minimized. **/ Q_PROPERTY(bool minimized READ isMinimized WRITE setMinimized NOTIFY minimizedChanged) /** * The optional geometry representing the minimized Client in e.g a taskbar. * See _NET_WM_ICON_GEOMETRY at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. **/ Q_PROPERTY(QRect iconGeometry READ iconGeometry) /** * Returns whether the window is any of special windows types (desktop, dock, splash, ...), * i.e. window types that usually don't have a window frame and the user does not use window * management (moving, raising,...) on them. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. **/ Q_PROPERTY(bool specialWindow READ isSpecialWindow) /** * Whether window state _NET_WM_STATE_DEMANDS_ATTENTION is set. This state indicates that some * action in or with the window happened. For example, it may be set by the Window Manager if * the window requested activation but the Window Manager refused it, or the application may set * it if it finished some work. This state may be set by both the Client and the Window Manager. * It should be unset by the Window Manager when it decides the window got the required attention * (usually, that it got activated). **/ Q_PROPERTY(bool demandsAttention READ isDemandingAttention WRITE demandAttention NOTIFY demandsAttentionChanged) /** * The Caption of the Client. Read from WM_NAME property together with a suffix for hostname and shortcut. * To read only the caption as provided by WM_NAME, use the getter with an additional @c false value. **/ Q_PROPERTY(QString caption READ caption NOTIFY captionChanged) /** * Minimum size as specified in WM_NORMAL_HINTS **/ Q_PROPERTY(QSize minSize READ minSize) /** * Maximum size as specified in WM_NORMAL_HINTS **/ Q_PROPERTY(QSize maxSize READ maxSize) /** * Whether the Client can accept keyboard focus. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. **/ Q_PROPERTY(bool wantsInput READ wantsInput) /** * Whether the Client is a transient Window to another Window. * @see transientFor **/ Q_PROPERTY(bool transient READ isTransient NOTIFY transientChanged) /** * The Client to which this Client is a transient if any. **/ Q_PROPERTY(KWin::AbstractClient *transientFor READ transientFor NOTIFY transientChanged) /** * Whether the Client represents a modal window. **/ Q_PROPERTY(bool modal READ isModal NOTIFY modalChanged) /** * The geometry of this Client. Be aware that depending on resize mode the geometryChanged signal * might be emitted at each resize step or only at the end of the resize operation. **/ Q_PROPERTY(QRect geometry READ geometry WRITE setGeometry) /** * Whether the Client is currently being moved by the user. * Notify signal is emitted when the Client starts or ends move/resize mode. **/ Q_PROPERTY(bool move READ isMove NOTIFY moveResizedChanged) /** * Whether the Client is currently being resized by the user. * Notify signal is emitted when the Client starts or ends move/resize mode. **/ Q_PROPERTY(bool resize READ isResize NOTIFY moveResizedChanged) /** * Whether the decoration is currently using an alpha channel. **/ Q_PROPERTY(bool decorationHasAlpha READ decorationHasAlpha) /** * Whether the window has a decoration or not. * This property is not allowed to be set by applications themselves. * The decision whether a window has a border or not belongs to the window manager. * If this property gets abused by application developers, it will be removed again. **/ Q_PROPERTY(bool noBorder READ noBorder WRITE setNoBorder) /** * Whether the Client provides context help. Mostly needed by decorations to decide whether to * show the help button or not. **/ Q_PROPERTY(bool providesContextHelp READ providesContextHelp CONSTANT) /** * Whether the Client can be maximized both horizontally and vertically. * The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool maximizable READ isMaximizable) /** * Whether the Client is moveable. Even if it is not moveable, it might be possible to move * it to another screen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. * @see moveableAcrossScreens **/ Q_PROPERTY(bool moveable READ isMovable) /** * Whether the Client can be moved to another screen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. * @see moveable **/ Q_PROPERTY(bool moveableAcrossScreens READ isMovableAcrossScreens) /** * Whether the Client can be resized. The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool resizeable READ isResizable) /** * The desktop file name of the application this AbstractClient belongs to. * * This is either the base name without full path and without file extension of the * desktop file for the window's application (e.g. "org.kde.foo"). * * The application's desktop file name can also be the full path to the desktop file * (e.g. "/opt/kde/share/org.kde.foo.desktop") in case it's not in a standard location. **/ Q_PROPERTY(QByteArray desktopFileName READ desktopFileName NOTIFY desktopFileNameChanged) + + /** + * Whether an application menu is available for this Client + */ + Q_PROPERTY(bool hasApplicationMenu READ hasApplicationMenu NOTIFY hasApplicationMenuChanged) + /** + * Whether the application menu for this Client is currently opened + */ + Q_PROPERTY(bool applicationMenuActive READ applicationMenuActive NOTIFY applicationMenuActiveChanged) + public: virtual ~AbstractClient(); QWeakPointer tabBoxClient() const { return m_tabBoxClient.toWeakRef(); } bool isFirstInTabBox() const { return m_firstInTabBox; } bool skipSwitcher() const { return m_skipSwitcher; } void setSkipSwitcher(bool set); bool skipTaskbar() const { return m_skipTaskbar; } void setSkipTaskbar(bool set); void setOriginalSkipTaskbar(bool set); bool originalSkipTaskbar() const { return m_originalSkipTaskbar; } bool skipPager() const { return m_skipPager; } void setSkipPager(bool set); const QIcon &icon() const { return m_icon; } bool isActive() const { return m_active; } /** * Sets the client's active state to \a act. * * This function does only change the visual appearance of the client, * it does not change the focus setting. Use * Workspace::activateClient() or Workspace::requestFocus() instead. * * If a client receives or looses the focus, it calls setActive() on * its own. **/ void setActive(bool); bool keepAbove() const { return m_keepAbove; } void setKeepAbove(bool); bool keepBelow() const { return m_keepBelow; } void setKeepBelow(bool); void demandAttention(bool set = true); bool isDemandingAttention() const { return m_demandsAttention; } void cancelAutoRaise(); bool wantsTabFocus() const; QPoint clientPos() const override { return QPoint(borderLeft(), borderTop()); } virtual void updateMouseGrab(); virtual QString caption(bool full = true, bool stripped = false) const = 0; virtual bool isCloseable() const = 0; // TODO: remove boolean trap virtual bool isShown(bool shaded_is_shown) const = 0; virtual bool isHiddenInternal() const = 0; // TODO: remove boolean trap virtual void hideClient(bool hide) = 0; virtual bool isFullScreenable() const = 0; virtual bool isFullScreen() const = 0; // TODO: remove boolean trap virtual AbstractClient *findModal(bool allow_itself = false) = 0; virtual bool isTransient() const; /** * @returns Whether there is a hint available to place the AbstractClient on it's parent, default @c false. * @see transientPlacementHint **/ virtual bool hasTransientPlacementHint() const; /** * @returns The recommended position of the transient in parent coordinates **/ virtual QPoint transientPlacementHint() const; const AbstractClient* transientFor() const; AbstractClient* transientFor(); /** * @returns @c true if c is the transient_for window for this client, * or recursively the transient_for window * @todo: remove boolean trap **/ virtual bool hasTransient(const AbstractClient* c, bool indirect) const; const QList& transients() const; // Is not indirect virtual void removeTransient(AbstractClient* cl); virtual QList mainClients() const; // Call once before loop , is not indirect QList allMainClients() const; // Call once before loop , is indirect /** * Returns true for "special" windows and false for windows which are "normal" * (normal=window which has a border, can be moved by the user, can be closed, etc.) * true for Desktop, Dock, Splash, Override and TopMenu (and Toolbar??? - for now) * false for Normal, Dialog, Utility and Menu (and Toolbar??? - not yet) TODO */ bool isSpecialWindow() const; void sendToScreen(int screen); virtual const QKeySequence &shortcut() const = 0; virtual void setShortcut(const QString &cut) = 0; virtual bool performMouseCommand(Options::MouseCommand, const QPoint &globalPos); void setOnAllDesktops(bool set); void setDesktop(int); int desktop() const override { return m_desktop; } void setMinimized(bool set); /** * Minimizes this client plus its transients */ void minimize(bool avoid_animation = false); void unminimize(bool avoid_animation = false); bool isMinimized() const { return m_minimized; } virtual void setFullScreen(bool set, bool user = true) = 0; virtual TabGroup *tabGroup() const; Q_INVOKABLE virtual bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false); virtual bool isCurrentTab() const; virtual QRect geometryRestore() const = 0; virtual MaximizeMode maximizeMode() const = 0; void maximize(MaximizeMode); void setMaximize(bool vertically, bool horizontally); virtual bool noBorder() const = 0; virtual void setNoBorder(bool set) = 0; virtual void blockActivityUpdates(bool b = true) = 0; QPalette palette() const; const Decoration::DecorationPalette *decorationPalette() const; virtual bool isResizable() const = 0; virtual bool isMovable() const = 0; virtual bool isMovableAcrossScreens() const = 0; /** * @c true only for @c ShadeNormal **/ bool isShade() const { return shadeMode() == ShadeNormal; } /** * Default implementation returns @c ShadeNone **/ virtual ShadeMode shadeMode() const; // Prefer isShade() void setShade(bool set); /** * Default implementation does nothing **/ virtual void setShade(ShadeMode mode); /** * Whether the Client can be shaded. Default implementation returns @c false. **/ virtual bool isShadeable() const; virtual bool isMaximizable() const = 0; virtual bool isMinimizable() const = 0; virtual QRect iconGeometry() const; virtual bool userCanSetFullScreen() const = 0; virtual bool userCanSetNoBorder() const = 0; virtual void setOnAllActivities(bool set) = 0; virtual const WindowRules* rules() const = 0; virtual void takeFocus() = 0; virtual bool wantsInput() const = 0; /** * Whether a dock window wants input. * * By default KWin doesn't pass focus to a dock window unless a force activate * request is provided. * * This method allows to have dock windows take focus also through flags set on * the window. * * The default implementation returns @c false. **/ virtual bool dockWantsInput() const; void checkWorkspacePosition(QRect oldGeometry = QRect(), int oldDesktop = -2, QRect oldClientGeometry = QRect()); virtual xcb_timestamp_t userTime() const; virtual void updateWindowRules(Rules::Types selection) = 0; void growHorizontal(); void shrinkHorizontal(); void growVertical(); void shrinkVertical(); void updateMoveResize(const QPointF ¤tGlobalCursor); /** * Ends move resize when all pointer buttons are up again. **/ void endMoveResize(); void keyPressEvent(uint key_code); void enterEvent(const QPoint &globalPos); void leaveEvent(); /** * These values represent positions inside an area */ enum Position { // without prefix, they'd conflict with Qt::TopLeftCorner etc. :( PositionCenter = 0x00, PositionLeft = 0x01, PositionRight = 0x02, PositionTop = 0x04, PositionBottom = 0x08, PositionTopLeft = PositionLeft | PositionTop, PositionTopRight = PositionRight | PositionTop, PositionBottomLeft = PositionLeft | PositionBottom, PositionBottomRight = PositionRight | PositionBottom }; Position titlebarPosition() const; bool titlebarPositionUnderMouse() const; // a helper for the workspace window packing. tests for screen validity and updates since in maximization case as with normal moving void packTo(int left, int top); enum QuickTileFlag { QuickTileNone = 0, QuickTileLeft = 1, QuickTileRight = 1<<1, QuickTileTop = 1<<2, QuickTileBottom = 1<<3, QuickTileHorizontal = QuickTileLeft|QuickTileRight, QuickTileVertical = QuickTileTop|QuickTileBottom, QuickTileMaximize = QuickTileLeft|QuickTileRight|QuickTileTop|QuickTileBottom }; Q_DECLARE_FLAGS(QuickTileMode, QuickTileFlag) /** Set the quick tile mode ("snap") of this window. * This will also handle preserving and restoring of window geometry as necessary. * @param mode The tile mode (left/right) to give this window. */ void setQuickTileMode(QuickTileMode mode, bool keyboard = false); QuickTileMode quickTileMode() const { return QuickTileMode(m_quickTileMode); } Layer layer() const override; void updateLayer(); enum ForceGeometry_t { NormalGeometrySet, ForceGeometrySet }; void move(int x, int y, ForceGeometry_t force = NormalGeometrySet); void move(const QPoint &p, ForceGeometry_t force = NormalGeometrySet); virtual void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) = 0; void resizeWithChecks(const QSize& s, ForceGeometry_t force = NormalGeometrySet); void keepInArea(QRect area, bool partial = false); virtual QSize minSize() const; virtual QSize maxSize() const; virtual void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) = 0; void setGeometry(const QRect& r, ForceGeometry_t force = NormalGeometrySet); /// How to resize the window in order to obey constains (mainly aspect ratios) enum Sizemode { SizemodeAny, SizemodeFixedW, ///< Try not to affect width SizemodeFixedH, ///< Try not to affect height SizemodeMax ///< Try not to make it larger in either direction }; /** *Calculate the appropriate frame size for the given client size @p wsize. * * @p wsize is adapted according to the window's size hints (minimum, maximum and incremental size changes). * * Default implementation returns the passed in @p wsize. */ virtual QSize sizeForClientSize(const QSize &wsize, Sizemode mode = SizemodeAny, bool noframe = false) const; QSize adjustedSize(const QSize&, Sizemode mode = SizemodeAny) const; QSize adjustedSize() const; bool isMove() const { return isMoveResize() && moveResizePointerMode() == PositionCenter; } bool isResize() const { return isMoveResize() && moveResizePointerMode() != PositionCenter; } /** * Cursor shape for move/resize mode. **/ Qt::CursorShape cursor() const { return m_moveResize.cursor; } virtual bool hasStrut() const; void setModal(bool modal); bool isModal() const; /** * Determines the mouse command for the given @p button in the current state. * * The @p handled argument specifies whether the button was handled or not. * This value should be used to determine whether the mouse button should be * passed to the AbstractClient or being filtered out. **/ Options::MouseCommand getMouseCommand(Qt::MouseButton button, bool *handled) const; Options::MouseCommand getWheelCommand(Qt::Orientation orientation, bool *handled) const; // decoration related KDecoration2::Decoration *decoration() { return m_decoration.decoration; } const KDecoration2::Decoration *decoration() const { return m_decoration.decoration; } bool isDecorated() const { return m_decoration.decoration != nullptr; } QPointer decoratedClient() const; void setDecoratedClient(QPointer client); bool decorationHasAlpha() const; void triggerDecorationRepaint(); virtual void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const; void processDecorationMove(const QPoint &localPos, const QPoint &globalPos); bool processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu = false); void processDecorationButtonRelease(QMouseEvent *event); /** * TODO: fix boolean traps **/ virtual void updateDecoration(bool check_workspace_pos, bool force = false) = 0; /** * Returns whether the window provides context help or not. If it does, * you should show a help menu item or a help button like '?' and call * contextHelp() if this is invoked. * * Default implementation returns @c false. * @see showContextHelp; */ virtual bool providesContextHelp() const; /** * Invokes context help on the window. Only works if the window * actually provides context help. * * Default implementation does nothing. * * @see providesContextHelp() */ virtual void showContextHelp(); QRect inputGeometry() const override; /** * Restores the AbstractClient after it had been hidden due to show on screen edge functionality. * The AbstractClient also gets raised (e.g. Panel mode windows can cover) and the AbstractClient * gets informed in a window specific way that it is shown and raised again. **/ virtual void showOnScreenEdge() = 0; QByteArray desktopFileName() const { return m_desktopFileName; } /** * Tries to terminate the process of this AbstractClient. * * Implementing subclasses can perform a windowing system solution for terminating. **/ virtual void killWindow() = 0; // TODO: remove boolean trap static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false); + bool hasApplicationMenu() const; + bool applicationMenuActive() const { + return m_applicationMenuActive; + } + void setApplicationMenuActive(bool applicationMenuActive); + + QString applicationMenuServiceName() const { + return m_applicationMenuServiceName; + } + QString applicationMenuObjectPath() const { + return m_applicationMenuObjectPath; + } + + /** + * Request showing the application menu bar + * @param actionId The DBus menu ID of the action that should be highlighted, 0 for the root menu + */ + void showApplicationMenu(int actionId); + public Q_SLOTS: virtual void closeWindow() = 0; Q_SIGNALS: void fullScreenChanged(); void skipTaskbarChanged(); void skipPagerChanged(); void skipSwitcherChanged(); void iconChanged(); void activeChanged(); void keepAboveChanged(bool); void keepBelowChanged(bool); /** * Emitted whenever the demands attention state changes. **/ void demandsAttentionChanged(); void desktopPresenceChanged(KWin::AbstractClient*, int); // to be forwarded by Workspace void desktopChanged(); void shadeChanged(); void minimizedChanged(); void clientMinimized(KWin::AbstractClient* client, bool animate); void clientUnminimized(KWin::AbstractClient* client, bool animate); void paletteChanged(const QPalette &p); void captionChanged(); void clientMaximizedStateChanged(KWin::AbstractClient*, MaximizeMode); void clientMaximizedStateChanged(KWin::AbstractClient* c, bool h, bool v); void transientChanged(); void modalChanged(); void quickTileModeChanged(); void moveResizedChanged(); void moveResizeCursorChanged(Qt::CursorShape); void clientStartUserMovedResized(KWin::AbstractClient*); void clientStepUserMovedResized(KWin::AbstractClient *, const QRect&); void clientFinishUserMovedResized(KWin::AbstractClient*); void closeableChanged(bool); void minimizeableChanged(bool); void shadeableChanged(bool); void maximizeableChanged(bool); void desktopFileNameChanged(); + void hasApplicationMenuChanged(bool); + void applicationMenuActiveChanged(bool); protected: AbstractClient(); void setFirstInTabBox(bool enable) { m_firstInTabBox = enable; } void setIcon(const QIcon &icon); void startAutoRaise(); void autoRaise(); /** * Whether the window accepts focus. * The difference to wantsInput is that the implementation should not check rules and return * what the window effectively supports. **/ virtual bool acceptsFocus() const = 0; /** * Called from ::setActive once the active value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. **/ virtual void doSetActive(); /** * Called from ::setKeepAbove once the keepBelow value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. **/ virtual void doSetKeepAbove(); /** * Called from ::setKeepBelow once the keepBelow value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. **/ virtual void doSetKeepBelow(); /** * Called from ::setDeskop once the desktop value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. * @param desktop The new desktop the Client is on * @param was_desk The desktop the Client was on before **/ virtual void doSetDesktop(int desktop, int was_desk); /** * Called from ::minimize and ::unminimize once the minimized value got updated, but before the * changed signal is emitted. * * Default implementation does nothig. **/ virtual void doMinimize(); // TODO: remove boolean trap virtual bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const = 0; virtual void doSetSkipTaskbar(); virtual void doSetSkipPager(); void setupWindowManagementInterface(); void destroyWindowManagementInterface(); void updateColorScheme(QString path); void setTransientFor(AbstractClient *transientFor); virtual void addTransient(AbstractClient* cl); /** * Just removes the @p cl from the transients without any further checks. **/ void removeTransientFromList(AbstractClient* cl); Layer belongsToLayer() const; virtual bool belongsToDesktop() const; void invalidateLayer(); virtual bool isActiveFullScreen() const; virtual Layer layerForDock() const; // electric border / quick tiling void setElectricBorderMode(QuickTileMode mode); QuickTileMode electricBorderMode() const { return m_electricMode; } void setElectricBorderMaximizing(bool maximizing); bool isElectricBorderMaximizing() const { return m_electricMaximizing; } QRect electricBorderMaximizeGeometry(QPoint pos, int desktop); void updateQuickTileMode(QuickTileMode newMode) { m_quickTileMode = newMode; } KWayland::Server::PlasmaWindowInterface *windowManagementInterface() const { return m_windowManagementInterface; } // geometry handling void checkOffscreenPosition(QRect *geom, const QRect &screenArea); int borderLeft() const; int borderRight() const; int borderTop() const; int borderBottom() const; virtual void changeMaximize(bool horizontal, bool vertical, bool adjust) = 0; virtual void setGeometryRestore(const QRect &geo) = 0; /** * Called from move after updating the geometry. Can be reimplemented to perform specific tasks. * The base implementation does nothing. **/ virtual void doMove(int x, int y); void blockGeometryUpdates(bool block); void blockGeometryUpdates(); void unblockGeometryUpdates(); bool areGeometryUpdatesBlocked() const; enum PendingGeometry_t { PendingGeometryNone, PendingGeometryNormal, PendingGeometryForced }; PendingGeometry_t pendingGeometryUpdate() const; void setPendingGeometryUpdate(PendingGeometry_t update); QRect geometryBeforeUpdateBlocking() const { return m_geometryBeforeUpdateBlocking; } void updateGeometryBeforeUpdateBlocking(); /** * Schedules a repaint for the visibleRect before and after a * geometry update. The current visibleRect is stored for the * next time this method is called as the before geometry. **/ void addRepaintDuringGeometryUpdates(); /** * Convenient method to update the TabGroup states if there is one present. * Marked as virtual as TabGroup does not yet handle AbstractClient, but only * subclasses of AbstractClient. Given that the default implementation does nothing. **/ virtual void updateTabGroupStates(TabGroup::States states); /** * @returns whether the Client is currently in move resize mode **/ bool isMoveResize() const { return m_moveResize.enabled; } /** * Sets whether the Client is in move resize mode to @p enabled. **/ void setMoveResize(bool enabled) { m_moveResize.enabled = enabled; } /** * @returns whether the move resize mode is unrestricted. **/ bool isUnrestrictedMoveResize() const { return m_moveResize.unrestricted; } /** * Sets whether move resize mode is unrestricted to @p set. **/ void setUnrestrictedMoveResize(bool set) { m_moveResize.unrestricted = set; } QPoint moveOffset() const { return m_moveResize.offset; } void setMoveOffset(const QPoint &offset) { m_moveResize.offset = offset; } QPoint invertedMoveOffset() const { return m_moveResize.invertedOffset; } void setInvertedMoveOffset(const QPoint &offset) { m_moveResize.invertedOffset = offset; } QRect initialMoveResizeGeometry() const { return m_moveResize.initialGeometry; } /** * Sets the initial move resize geometry to the current geometry. **/ void updateInitialMoveResizeGeometry(); QRect moveResizeGeometry() const { return m_moveResize.geometry; } void setMoveResizeGeometry(const QRect &geo) { m_moveResize.geometry = geo; } Position moveResizePointerMode() const { return m_moveResize.pointer; } void setMoveResizePointerMode(Position mode) { m_moveResize.pointer = mode; } bool isMoveResizePointerButtonDown() const { return m_moveResize.buttonDown; } void setMoveResizePointerButtonDown(bool down) { m_moveResize.buttonDown = down; } int moveResizeStartScreen() const { return m_moveResize.startScreen; } void checkUnrestrictedMoveResize(); /** * Sets an appropriate cursor shape for the logical mouse position. */ void updateCursor(); void startDelayedMoveResize(); void stopDelayedMoveResize(); bool startMoveResize(); /** * Called from @link startMoveResize. * * Implementing classes should return @c false if starting move resize should * get aborted. In that case @link startMoveResize will also return @c false. * * Base implementation returns @c true. **/ virtual bool doStartMoveResize(); void finishMoveResize(bool cancel); /** * Leaves the move resize mode. * * Inheriting classes must invoke the base implementation which * ensures that the internal mode is properly ended. **/ virtual void leaveMoveResize(); virtual void positionGeometryTip(); void performMoveResize(); /** * Called from performMoveResize() after actually performing the change of geometry. * Implementing subclasses can perform windowing system specific handling here. * * Default implementation does nothing. **/ virtual void doPerformMoveResize(); /* * Checks if the mouse cursor is near the edge of the screen and if so * activates quick tiling or maximization */ void checkQuickTilingMaximizationZones(int xroot, int yroot); /** * Whether a sync request is still pending. * Default implementation returns @c false. **/ virtual bool isWaitingForMoveResizeSync() const; /** * Called during handling a resize. Implementing subclasses can use this * method to perform windowing system specific syncing. * * Default implementation does nothing. **/ virtual void doResizeSync(); void handleMoveResize(int x, int y, int x_root, int y_root); void handleMoveResize(const QPoint &local, const QPoint &global); void dontMoveResize(); virtual QSize resizeIncrements() const; /** * Returns the position depending on the Decoration's section under mouse. * If no decoration it returns PositionCenter. **/ Position mousePosition() const; static bool haveResizeEffect() { return s_haveResizeEffect; } static void updateHaveResizeEffect(); static void resetHaveResizeEffect() { s_haveResizeEffect = false; } void setDecoration(KDecoration2::Decoration *decoration) { m_decoration.decoration = decoration; } virtual void destroyDecoration(); void startDecorationDoubleClickTimer(); void invalidateDecorationDoubleClickTimer(); void setDesktopFileName(const QByteArray &name); QString iconFromDesktopFile() const; + void updateApplicationMenuServiceName(const QString &serviceName); + void updateApplicationMenuObjectPath(const QString &objectPath); + private: void handlePaletteChange(); QSharedPointer m_tabBoxClient; bool m_firstInTabBox = false; bool m_skipTaskbar = false; /** * Unaffected by KWin **/ bool m_originalSkipTaskbar = false; bool m_skipPager = false; bool m_skipSwitcher = false; QIcon m_icon; bool m_active = false; bool m_keepAbove = false; bool m_keepBelow = false; bool m_demandsAttention = false; bool m_minimized = false; QTimer *m_autoRaiseTimer = nullptr; int m_desktop = 0; // 0 means not on any desktop yet QString m_colorScheme; std::shared_ptr m_palette; static QHash> s_palettes; static std::shared_ptr s_defaultPalette; KWayland::Server::PlasmaWindowInterface *m_windowManagementInterface = nullptr; AbstractClient *m_transientFor = nullptr; QList m_transients; bool m_modal = false; Layer m_layer = UnknownLayer; // electric border/quick tiling QuickTileMode m_electricMode = QuickTileNone; bool m_electricMaximizing = false; /** The quick tile mode of this window. */ int m_quickTileMode = QuickTileNone; QTimer *m_electricMaximizingDelay = nullptr; // geometry int m_blockGeometryUpdates = 0; // > 0 = New geometry is remembered, but not actually set PendingGeometry_t m_pendingGeometryUpdate = PendingGeometryNone; friend class GeometryUpdatesBlocker; QRect m_visibleRectBeforeGeometryUpdate; QRect m_geometryBeforeUpdateBlocking; struct { bool enabled = false; bool unrestricted = false; QPoint offset; QPoint invertedOffset; QRect initialGeometry; QRect geometry; Position pointer = PositionCenter; bool buttonDown = false; Qt::CursorShape cursor = Qt::ArrowCursor; int startScreen = 0; QTimer *delayedTimer = nullptr; } m_moveResize; struct { KDecoration2::Decoration *decoration = nullptr; QPointer client; QElapsedTimer doubleClickTimer; } m_decoration; - QByteArray m_desktopFileName; + bool m_applicationMenuActive = false; + QString m_applicationMenuServiceName; + QString m_applicationMenuObjectPath; static bool s_haveResizeEffect; }; /** * Helper for AbstractClient::blockGeometryUpdates() being called in pairs (true/false) */ class GeometryUpdatesBlocker { public: explicit GeometryUpdatesBlocker(AbstractClient* c) : cl(c) { cl->blockGeometryUpdates(true); } ~GeometryUpdatesBlocker() { cl->blockGeometryUpdates(false); } private: AbstractClient* cl; }; inline void AbstractClient::move(const QPoint& p, ForceGeometry_t force) { move(p.x(), p.y(), force); } inline void AbstractClient::resizeWithChecks(const QSize& s, AbstractClient::ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), force); } inline void AbstractClient::setGeometry(const QRect& r, ForceGeometry_t force) { setGeometry(r.x(), r.y(), r.width(), r.height(), force); } inline const QList& AbstractClient::transients() const { return m_transients; } inline bool AbstractClient::areGeometryUpdatesBlocked() const { return m_blockGeometryUpdates != 0; } inline void AbstractClient::blockGeometryUpdates() { m_blockGeometryUpdates++; } inline void AbstractClient::unblockGeometryUpdates() { m_blockGeometryUpdates--; } inline AbstractClient::PendingGeometry_t AbstractClient::pendingGeometryUpdate() const { return m_pendingGeometryUpdate; } inline void AbstractClient::setPendingGeometryUpdate(PendingGeometry_t update) { m_pendingGeometryUpdate = update; } } Q_DECLARE_METATYPE(KWin::AbstractClient*) Q_DECLARE_METATYPE(QList) Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::AbstractClient::QuickTileMode) #endif diff --git a/appmenu.cpp b/appmenu.cpp new file mode 100644 index 000000000..4ce96f511 --- /dev/null +++ b/appmenu.cpp @@ -0,0 +1,111 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (c) 2011 Lionel Chauvin +Copyright (c) 2011,2012 Cédric Bellegarde +Copyright (C) 2013 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "appmenu.h" +#include "client.h" +#include "workspace.h" +#include + +#include + +using namespace KWin; + +KWIN_SINGLETON_FACTORY(ApplicationMenu) + +ApplicationMenu::ApplicationMenu(QObject *parent) + : QObject(parent) + , m_appmenuInterface(new OrgKdeKappmenuInterface(QStringLiteral("org.kde.kappmenu"), QStringLiteral("/KAppMenu"), QDBusConnection::sessionBus(), this)) +{ + connect(m_appmenuInterface, &OrgKdeKappmenuInterface::showRequest, this, &ApplicationMenu::slotShowRequest); + connect(m_appmenuInterface, &OrgKdeKappmenuInterface::menuShown, this, &ApplicationMenu::slotMenuShown); + connect(m_appmenuInterface, &OrgKdeKappmenuInterface::menuHidden, this, &ApplicationMenu::slotMenuHidden); + connect(m_appmenuInterface, &OrgKdeKappmenuInterface::reconfigured, this, &ApplicationMenu::slotReconfigured); + + updateApplicationMenuEnabled(); +} + +ApplicationMenu::~ApplicationMenu() +{ + s_self = nullptr; +} + +void ApplicationMenu::slotReconfigured() +{ + updateApplicationMenuEnabled(); +} + +bool ApplicationMenu::applicationMenuEnabled() const +{ + return m_applicationMenuEnabled; +} + +void ApplicationMenu::updateApplicationMenuEnabled() +{ + const bool old_enabled = m_applicationMenuEnabled; + + KConfigGroup config(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("Appmenu Style")); + const QString &menuStyle = config.readEntry(QStringLiteral("Style")); + + const bool enabled = (menuStyle == QLatin1String("Decoration")); + + if (old_enabled != enabled) { + m_applicationMenuEnabled = enabled; + emit applicationMenuEnabledChanged(enabled); + } +} + +void ApplicationMenu::slotShowRequest(const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId) +{ + if (AbstractClient *c = findAbstractClientWithApplicationMenu(serviceName, menuObjectPath)) { + c->showApplicationMenu(actionId); + } +} + +void ApplicationMenu::slotMenuShown(const QString &serviceName, const QDBusObjectPath &menuObjectPath) +{ + if (AbstractClient *c = findAbstractClientWithApplicationMenu(serviceName, menuObjectPath)) { + c->setApplicationMenuActive(true); + } +} + +void ApplicationMenu::slotMenuHidden(const QString &serviceName, const QDBusObjectPath &menuObjectPath) +{ + if (AbstractClient *c = findAbstractClientWithApplicationMenu(serviceName, menuObjectPath)) { + c->setApplicationMenuActive(false); + } +} + +void ApplicationMenu::showApplicationMenu(const QPoint &p, AbstractClient *c, int actionId) +{ + m_appmenuInterface->showMenu(p.x(), p.y(), c->applicationMenuServiceName(), QDBusObjectPath(c->applicationMenuObjectPath()), actionId); +} + +AbstractClient *ApplicationMenu::findAbstractClientWithApplicationMenu(const QString &serviceName, const QDBusObjectPath &menuObjectPath) +{ + if (serviceName.isEmpty() || menuObjectPath.path().isEmpty()) { + return nullptr; + } + + return Workspace::self()->findAbstractClient([&](const AbstractClient *c) { + return c->applicationMenuServiceName() == serviceName + && c->applicationMenuObjectPath() == menuObjectPath.path(); + }); +} diff --git a/appmenu.h b/appmenu.h new file mode 100644 index 000000000..1a6af23e2 --- /dev/null +++ b/appmenu.h @@ -0,0 +1,74 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (c) 2011 Lionel Chauvin +Copyright (c) 2011,2012 Cédric Bellegarde +Copyright (C) 2013 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_APPLICATIONMENU_H +#define KWIN_APPLICATIONMENU_H +// KWin +#include +// Qt +#include +// xcb +#include + +class QPoint; +class OrgKdeKappmenuInterface; +class QDBusObjectPath; + +namespace KWin +{ + +class AbstractClient; + +class ApplicationMenu : public QObject +{ + Q_OBJECT + +public: + ~ApplicationMenu() override; + + void showApplicationMenu(const QPoint &pos, AbstractClient *c, int actionId); + + bool applicationMenuEnabled() const; + +signals: + void applicationMenuEnabledChanged(bool enabled); + +private Q_SLOTS: + void slotReconfigured(); + void slotShowRequest(const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId); + void slotMenuShown(const QString &serviceName, const QDBusObjectPath &menuObjectPath); + void slotMenuHidden(const QString &serviceName, const QDBusObjectPath &menuObjectPath); + +private: + void updateApplicationMenuEnabled(); + + OrgKdeKappmenuInterface *m_appmenuInterface; + + AbstractClient *findAbstractClientWithApplicationMenu(const QString &serviceName, const QDBusObjectPath &menuObjectPath); + + bool m_applicationMenuEnabled = false; + + KWIN_SINGLETON(ApplicationMenu) +}; + +} + +#endif // KWIN_APPLICATIONMENU_H diff --git a/atoms.cpp b/atoms.cpp index 55a8aa368..ee9e8e29e 100644 --- a/atoms.cpp +++ b/atoms.cpp @@ -1,82 +1,84 @@ /******************************************************************** 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) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "atoms.h" namespace KWin { Atoms::Atoms() : kwin_running(QByteArrayLiteral("KWIN_RUNNING")) , activities(QByteArrayLiteral("_KDE_NET_WM_ACTIVITIES")) , wm_protocols(QByteArrayLiteral("WM_PROTOCOLS")) , wm_delete_window(QByteArrayLiteral("WM_DELETE_WINDOW")) , wm_take_focus(QByteArrayLiteral("WM_TAKE_FOCUS")) , wm_change_state(QByteArrayLiteral("WM_CHANGE_STATE")) , wm_client_leader(QByteArrayLiteral("WM_CLIENT_LEADER")) , wm_window_role(QByteArrayLiteral("WM_WINDOW_ROLE")) , wm_state(QByteArrayLiteral("WM_STATE")) , sm_client_id(QByteArrayLiteral("SM_CLIENT_ID")) , motif_wm_hints(QByteArrayLiteral("_MOTIF_WM_HINTS")) , net_wm_context_help(QByteArrayLiteral("_NET_WM_CONTEXT_HELP")) , net_wm_ping(QByteArrayLiteral("_NET_WM_PING")) , net_wm_user_time(QByteArrayLiteral("_NET_WM_USER_TIME")) , kde_net_wm_user_creation_time(QByteArrayLiteral("_KDE_NET_WM_USER_CREATION_TIME")) , net_wm_take_activity(QByteArrayLiteral("_NET_WM_TAKE_ACTIVITY")) , net_wm_window_opacity(QByteArrayLiteral("_NET_WM_WINDOW_OPACITY")) , xdnd_aware(QByteArrayLiteral("XdndAware")) , xdnd_position(QByteArrayLiteral("XdndPosition")) , net_frame_extents(QByteArrayLiteral("_NET_FRAME_EXTENTS")) , kde_net_wm_frame_strut(QByteArrayLiteral("_KDE_NET_WM_FRAME_STRUT")) , net_wm_sync_request_counter(QByteArrayLiteral("_NET_WM_SYNC_REQUEST_COUNTER")) , net_wm_sync_request(QByteArrayLiteral("_NET_WM_SYNC_REQUEST")) , kde_net_wm_shadow(QByteArrayLiteral("_KDE_NET_WM_SHADOW")) , kde_net_wm_tab_group(QByteArrayLiteral("_KDE_NET_WM_TAB_GROUP")) , kde_first_in_window_list(QByteArrayLiteral("_KDE_FIRST_IN_WINDOWLIST")) , kde_color_sheme(QByteArrayLiteral("_KDE_NET_WM_COLOR_SCHEME")) , kde_skip_close_animation(QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION")) , kde_screen_edge_show(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW")) , gtk_frame_extents(QByteArrayLiteral("_GTK_FRAME_EXTENTS")) , kwin_dbus_service(QByteArrayLiteral("_ORG_KDE_KWIN_DBUS_SERVICE")) , utf8_string(QByteArrayLiteral("UTF8_STRING")) , wl_surface_id(QByteArrayLiteral("WL_SURFACE_ID")) + , kde_net_wm_appmenu_service_name(QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME")) + , kde_net_wm_appmenu_object_path(QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH")) , m_dtSmWindowInfo(QByteArrayLiteral("_DT_SM_WINDOW_INFO")) , m_motifSupport(QByteArrayLiteral("_MOTIF_WM_INFO")) , m_helpersRetrieved(false) { } void Atoms::retrieveHelpers() { if (m_helpersRetrieved) { return; } // just retrieve the atoms once, all others are retrieved when being accessed // Q_UNUSED is used in the hope that the compiler doesn't optimize the operations away xcb_atom_t atom = m_dtSmWindowInfo; Q_UNUSED(atom) atom = m_motifSupport; Q_UNUSED(atom) m_helpersRetrieved = true; } } // namespace diff --git a/atoms.h b/atoms.h index 4b1e97c69..2c7b2d9f1 100644 --- a/atoms.h +++ b/atoms.h @@ -1,89 +1,91 @@ /******************************************************************** 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) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_ATOMS_H #define KWIN_ATOMS_H #include "xcbutils.h" namespace KWin { class KWIN_EXPORT Atoms { public: Atoms(); Xcb::Atom kwin_running; Xcb::Atom activities; Xcb::Atom wm_protocols; Xcb::Atom wm_delete_window; Xcb::Atom wm_take_focus; Xcb::Atom wm_change_state; Xcb::Atom wm_client_leader; Xcb::Atom wm_window_role; Xcb::Atom wm_state; Xcb::Atom sm_client_id; Xcb::Atom motif_wm_hints; Xcb::Atom net_wm_context_help; Xcb::Atom net_wm_ping; Xcb::Atom net_wm_user_time; Xcb::Atom kde_net_wm_user_creation_time; Xcb::Atom net_wm_take_activity; Xcb::Atom net_wm_window_opacity; Xcb::Atom xdnd_aware; Xcb::Atom xdnd_position; Xcb::Atom net_frame_extents; Xcb::Atom kde_net_wm_frame_strut; Xcb::Atom net_wm_sync_request_counter; Xcb::Atom net_wm_sync_request; Xcb::Atom kde_net_wm_shadow; Xcb::Atom kde_net_wm_tab_group; Xcb::Atom kde_first_in_window_list; Xcb::Atom kde_color_sheme; Xcb::Atom kde_skip_close_animation; Xcb::Atom kde_screen_edge_show; Xcb::Atom gtk_frame_extents; Xcb::Atom kwin_dbus_service; Xcb::Atom utf8_string; Xcb::Atom wl_surface_id; + Xcb::Atom kde_net_wm_appmenu_service_name; + Xcb::Atom kde_net_wm_appmenu_object_path; /** * @internal **/ void retrieveHelpers(); private: // helper atoms we need to resolve to "announce" support (see #172028) Xcb::Atom m_dtSmWindowInfo; Xcb::Atom m_motifSupport; bool m_helpersRetrieved; }; extern KWIN_EXPORT Atoms* atoms; } // namespace #endif diff --git a/client.cpp b/client.cpp index 99934d2c3..1413951ac 100644 --- a/client.cpp +++ b/client.cpp @@ -1,2156 +1,2188 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "client.h" // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client_machine.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "focuschain.h" #include "group.h" #include "shadow.h" #include "workspace.h" #include "screenedge.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include // KDE #include #include // Qt #include #include #include #include #include #include #include #include #include // XLib #include #include // system #include #include // Put all externs before the namespace statement to allow the linker // to resolve them properly namespace KWin { const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | // need this, too! XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Creating a client: // - only by calling Workspace::createClient() // - it creates a new client and calls manage() for it // // Destroying a client: // - destroyClient() - only when the window itself has been destroyed // - releaseWindow() - the window is kept, only the client itself is destroyed /** * \class Client client.h * \brief The Client class encapsulates a window decoration frame. */ /** * This ctor is "dumb" - it only initializes data. All the real initialization * is done in manage(). */ Client::Client() : AbstractClient() , m_client() , m_wrapper() , m_frame() , m_activityUpdatesBlocked(false) , m_blockedActivityUpdatesRequireTransients(false) , m_moveResizeGrabWindow() , move_resize_has_keyboard_grab(false) , m_managed(false) , m_transientForId(XCB_WINDOW_NONE) , m_originalTransientForId(XCB_WINDOW_NONE) , shade_below(NULL) , m_motif(atoms->motif_wm_hints) , blocks_compositing(false) , shadeHoverTimer(NULL) , m_colormap(XCB_COLORMAP_NONE) , in_group(NULL) , tab_group(NULL) , ping_timer(NULL) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet , allowed_actions(0) , shade_geometry_change(false) , sm_stacking_order(-1) , activitiesDefined(false) , needsSessionInteract(false) , needsXWindowMove(false) , m_decoInputExtent() , m_focusOutTimer(nullptr) , m_clientSideDecorated(false) { // TODO: Do all as initialization syncRequest.counter = syncRequest.alarm = XCB_NONE; syncRequest.timeout = syncRequest.failsafeTimeout = NULL; syncRequest.lastTimestamp = xTime(); syncRequest.isPending = false; // Set the initial mapping state mapping_state = Withdrawn; info = NULL; shade_mode = ShadeNone; deleting = false; fullscreen_mode = FullScreenNone; hidden = false; noborder = false; app_noborder = false; ignore_focus_stealing = false; check_active_modal = false; max_mode = MaximizeRestore; //Client to workspace connections require that each //client constructed be connected to the workspace wrapper geom = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) client_size = QSize(100, 100); ready_for_painting = false; // wait for first damage or sync reply connect(clientMachine(), &ClientMachine::localhostChanged, this, &Client::updateCaption); connect(options, &Options::condensedTitleChanged, this, &Client::updateCaption); connect(this, &Client::moveResizeCursorChanged, this, [this] (Qt::CursorShape cursor) { xcb_cursor_t nativeCursor = Cursor::x11Cursor(cursor); m_frame.defineCursor(nativeCursor); if (m_decoInputExtent.isValid()) m_decoInputExtent.defineCursor(nativeCursor); if (isMoveResize()) { // changing window attributes doesn't change cursor if there's pointer grab active xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(), XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW); } }); // SELI TODO: Initialize xsizehints?? } /** * "Dumb" destructor. */ Client::~Client() { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } //SWrapper::Client::clientRelease(this); if (syncRequest.alarm != XCB_NONE) xcb_sync_destroy_alarm(connection(), syncRequest.alarm); assert(!isMoveResize()); assert(m_client == XCB_WINDOW_NONE); assert(m_wrapper == XCB_WINDOW_NONE); //assert( frameId() == None ); assert(!check_active_modal); for (auto it = m_connections.constBegin(); it != m_connections.constEnd(); ++it) { disconnect(*it); } } // Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly void Client::deleteClient(Client* c) { delete c; } /** * Releases the window. The client has done its job and the window is still existing. */ void Client::releaseWindow(bool on_shutdown) { assert(!deleting); deleting = true; destroyWindowManagementInterface(); Deleted* del = NULL; if (!on_shutdown) { del = Deleted::create(this); } if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); // Grab X during the release to make removing of properties, setting to withdrawn state // and repareting to root an atomic operation (http://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) grabXServer(); exportMappingState(WithdrawnState); setModal(false); // Otherwise its mainwindow wouldn't get focus hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags) if (!on_shutdown) workspace()->clientHidden(this); m_frame.unmap(); // Destroying decoration would cause ugly visual effect destroyDecoration(); cleanGrouping(); if (!on_shutdown) { workspace()->removeClient(this); // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7) info->setDesktop(0); info->setState(0, info->state()); // Reset all state flags } else untab(); xcb_connection_t *c = connection(); m_client.deleteProperty(atoms->kde_net_wm_user_creation_time); m_client.deleteProperty(atoms->net_frame_extents); m_client.deleteProperty(atoms->kde_net_wm_frame_strut); m_client.reparent(rootWindow(), x(), y()); xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client); m_client.selectInput(XCB_EVENT_MASK_NO_EVENT); if (on_shutdown) // Map the window, so it can be found after another WM is started m_client.map(); // TODO: Preserve minimized, shaded etc. state? else // Make sure it's not mapped if the app unmapped it (#65279). The app // may do map+unmap before we initially map the window by calling rawShow() from manage(). m_client.unmap(); m_client.reset(); m_wrapper.reset(); m_frame.reset(); //frame = None; unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry if (!on_shutdown) { disownDataPassedToDeleted(); del->unrefWindow(); } checkNonExistentClients(); deleteClient(this); ungrabXServer(); } /** * Like releaseWindow(), but this one is called when the window has been already destroyed * (E.g. The application closed it) */ void Client::destroyClient() { assert(!deleting); deleting = true; destroyWindowManagementInterface(); Deleted* del = Deleted::create(this); if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(ReleaseReason::Destroyed); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); setModal(false); hidden = true; // So that it's not considered visible anymore workspace()->clientHidden(this); destroyDecoration(); cleanGrouping(); workspace()->removeClient(this); m_client.reset(); // invalidate m_wrapper.reset(); m_frame.reset(); //frame = None; unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry disownDataPassedToDeleted(); del->unrefWindow(); checkNonExistentClients(); deleteClient(this); } void Client::updateInputWindow() { if (!Xcb::Extensions::self()->isShapeInputAvailable()) return; QRegion region; if (!noBorder() && isDecorated()) { const QMargins &r = decoration()->resizeOnlyBorders(); const int left = r.left(); const int top = r.top(); const int right = r.right(); const int bottom = r.bottom(); if (left != 0 || top != 0 || right != 0 || bottom != 0) { region = QRegion(-left, -top, decoration()->size().width() + left + right, decoration()->size().height() + top + bottom); region = region.subtracted(decoration()->rect()); } } if (region.isEmpty()) { m_decoInputExtent.reset(); return; } QRect bounds = region.boundingRect(); input_offset = bounds.topLeft(); // Move the bounding rect to screen coordinates bounds.translate(geometry().topLeft()); // Move the region to input window coordinates region.translate(-input_offset); if (!m_decoInputExtent.isValid()) { const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; const uint32_t values[] = {true, XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION }; m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values); if (mapping_state == Mapped) m_decoInputExtent.map(); } else { m_decoInputExtent.setGeometry(bounds); } const QVector rects = Xcb::regionToRects(region); xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_decoInputExtent, 0, 0, rects.count(), rects.constData()); } void Client::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); updateInputWindow(); blockGeometryUpdates(false); updateFrameExtents(); } void Client::createDecoration(const QRect& oldgeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow); connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &Client::updateInputWindow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { updateFrameExtents(); GeometryUpdatesBlocker blocker(this); // TODO: this is obviously idempotent // calculateGravitation(true) would have to operate on the old border sizes // move(calculateGravitation(true)); // move(calculateGravitation(false)); QRect oldgeom = geometry(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, &Client::updateInputWindow); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, &Client::updateInputWindow); } setDecoration(decoration); move(calculateGravitation(false)); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (Compositor::compositing()) { discardWindowPixmap(); } emit geometryShapeChanged(this, oldgeom); } void Client::destroyDecoration() { QRect oldgeom = geometry(); if (isDecorated()) { QPoint grav = calculateGravitation(true); AbstractClient::destroyDecoration(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); move(grav); if (compositing()) discardWindowPixmap(); if (!deleting) { emit geometryShapeChanged(this, oldgeom); } } m_decoInputExtent.reset(); } void Client::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); NETStrut strut = info->frameOverlap(); // Ignore the overlap strut when compositing is disabled if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) { top = QRect(r.x(), r.y(), r.width(), r.height() / 3); left = QRect(r.x(), r.y() + top.height(), width() / 2, r.height() / 3); right = QRect(r.x() + left.width(), r.y() + top.height(), r.width() - left.width(), left.height()); bottom = QRect(r.x(), r.y() + top.height() + left.height(), r.width(), r.height() - left.height() - top.height()); return; } top = QRect(r.x(), r.y(), r.width(), borderTop() + strut.top); bottom = QRect(r.x(), r.y() + r.height() - borderBottom() - strut.bottom, r.width(), borderBottom() + strut.bottom); left = QRect(r.x(), r.y() + top.height(), borderLeft() + strut.left, r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight() - strut.right, r.y() + top.height(), borderRight() + strut.right, r.height() - top.height() - bottom.height()); } QRect Client::transparentRect() const { if (isShade()) return QRect(); NETStrut strut = info->frameOverlap(); // Ignore the strut when compositing is disabled or the decoration doesn't support it if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) return QRect(); const QRect r = QRect(clientPos(), clientSize()) .adjusted(strut.left, strut.top, -strut.right, -strut.bottom); if (r.isValid()) return r; return QRect(); } void Client::detectNoBorder() { if (shape()) { noborder = true; app_noborder = true; return; } switch(windowType()) { case NET::Desktop : case NET::Dock : case NET::TopMenu : case NET::Splash : case NET::Notification : case NET::OnScreenDisplay : noborder = true; app_noborder = true; break; case NET::Unknown : case NET::Normal : case NET::Toolbar : case NET::Menu : case NET::Dialog : case NET::Utility : noborder = false; break; default: abort(); } // NET::Override is some strange beast without clear definition, usually // just meaning "noborder", so let's treat it only as such flag, and ignore it as // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) if (info->windowType(NET::OverrideMask) == NET::Override) { noborder = true; app_noborder = true; } } void Client::updateFrameExtents() { NETStrut strut; strut.left = borderLeft(); strut.right = borderRight(); strut.top = borderTop(); strut.bottom = borderBottom(); info->setFrameExtents(strut); } Xcb::Property Client::fetchGtkFrameExtents() const { return Xcb::Property(false, m_client, atoms->gtk_frame_extents, XCB_ATOM_CARDINAL, 0, 4); } void Client::readGtkFrameExtents(Xcb::Property &prop) { m_clientSideDecorated = !prop.isNull() && prop->type != 0; emit clientSideDecoratedChanged(); } void Client::detectGtkFrameExtents() { Xcb::Property prop = fetchGtkFrameExtents(); readGtkFrameExtents(prop); } /** * Resizes the decoration, and makes sure the decoration widget gets resize event * even if the size hasn't changed. This is needed to make sure the decoration * re-layouts (e.g. when maximization state changes, * the decoration may alter some borders, but the actual size * of the decoration stays the same). */ void Client::resizeDecoration() { triggerDecorationRepaint(); updateInputWindow(); } bool Client::noBorder() const { return noborder || isFullScreen(); } bool Client::userCanSetNoBorder() const { return !isFullScreen() && !isShade() && !tabGroup(); } void Client::setNoBorder(bool set) { if (!userCanSetNoBorder()) return; set = rules()->checkNoBorder(set); if (noborder == set) return; noborder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void Client::checkNoBorder() { setNoBorder(app_noborder); } bool Client::wantsShadowToBeRendered() const { return !isFullScreen() && maximizeMode() != MaximizeFull; } void Client::updateShape() { if (shape()) { // Workaround for #19644 - Shaped windows shouldn't have decoration if (!app_noborder) { // Only when shape is detected for the first time, still let the user to override app_noborder = true; noborder = rules()->checkNoBorder(true); updateDecoration(true); } if (noBorder()) { xcb_shape_combine(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, frameId(), clientPos().x(), clientPos().y(), window()); } } else if (app_noborder) { xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE); detectNoBorder(); app_noborder = noborder; noborder = rules()->checkNoBorder(noborder || m_motif.noBorder()); updateDecoration(true); } // Decoration mask (i.e. 'else' here) setting is done in setMask() // when the decoration calls it or when the decoration is created/destroyed updateInputShape(); if (compositing()) { addRepaintFull(); addWorkspaceRepaint(visibleRect()); // In case shape change removes part of this window } emit geometryShapeChanged(this, geometry()); } static Xcb::Window shape_helper_window(XCB_WINDOW_NONE); void Client::cleanupX11() { shape_helper_window.reset(); } void Client::updateInputShape() { if (hiddenPreview()) // Sets it to none, don't change return; if (Xcb::Extensions::self()->isShapeInputAvailable()) { // There appears to be no way to find out if a window has input // shape set or not, so always propagate the input shape // (it's the same like the bounding shape by default). // Also, build the shape using a helper window, not directly // in the frame window, because the sequence set-shape-to-frame, // remove-shape-of-client, add-input-shape-of-client has the problem // that after the second step there's a hole in the input shape // until the real shape of the client is added and that can make // the window lose focus (which is a problem with mouse focus policies) // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better if (!shape_helper_window.isValid()) shape_helper_window.create(QRect(0, 0, 1, 1)); shape_helper_window.resize(width(), height()); xcb_connection_t *c = connection(); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, 0, 0, frameId()); xcb_shape_combine(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, frameId(), 0, 0, shape_helper_window); } } void Client::hideClient(bool hide) { if (hidden == hide) return; hidden = hide; updateVisibility(); } /** * Returns whether the window is minimizable or not */ bool Client::isMinimizable() const { if (isSpecialWindow() && !isTransient()) return false; if (!rules()->checkMinimize(true)) return false; if (isTransient()) { // #66868 - Let other xmms windows be minimized when the mainwindow is minimized bool shown_mainwindow = false; auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) shown_mainwindow = true; if (!shown_mainwindow) return true; } #if 0 // This is here because kicker's taskbar doesn't provide separate entries // for windows with an explicitly given parent // TODO: perhaps this should be redone // Disabled for now, since at least modal dialogs should be minimizable // (resulting in the mainwindow being minimized too). if (transientFor() != NULL) return false; #endif if (!wantsTabFocus()) // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ? return false; return true; } void Client::doMinimize() { updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients(this); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Minimized); } QRect Client::iconGeometry() const { NETRect r = info->iconGeometry(); QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height); if (geom.isValid()) return geom; else { // Check all mainwindows of this window (recursively) foreach (AbstractClient * amainwin, mainClients()) { Client *mainwin = dynamic_cast(amainwin); if (!mainwin) { continue; } geom = mainwin->iconGeometry(); if (geom.isValid()) return geom; } // No mainwindow (or their parents) with icon geometry was found return AbstractClient::iconGeometry(); } } bool Client::isShadeable() const { return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone)); } void Client::setShade(ShadeMode mode) { if (mode == ShadeHover && isMove()) return; // causes geometry breaks and is probably nasty if (isSpecialWindow() || noBorder()) mode = ShadeNone; mode = rules()->checkShade(mode); if (shade_mode == mode) return; bool was_shade = isShade(); ShadeMode was_shade_mode = shade_mode; shade_mode = mode; // Decorations may turn off some borders when shaded // this has to happen _before_ the tab alignment since it will restrict the minimum geometry #if 0 if (decoration) decoration->borders(border_left, border_right, border_top, border_bottom); #endif // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Shaded); if (was_shade == isShade()) { // Decoration may want to update after e.g. hover-shade changes emit shadeChanged(); return; // No real change in shaded state } assert(isDecorated()); // noborder windows can't be shaded GeometryUpdatesBlocker blocker(this); // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere if (isShade()) { // shade_mode == ShadeNormal addWorkspaceRepaint(visibleRect()); // Shade shade_geometry_change = true; QSize s(sizeForClientSize(QSize(clientSize()))); s.setHeight(borderTop() + borderBottom()); m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_wrapper.unmap(); m_client.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(IconicState); plainResize(s); shade_geometry_change = false; if (was_shade_mode == ShadeHover) { if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1) workspace()->restack(this, shade_below, true); if (isActive()) workspace()->activateNextClient(this); } else if (isActive()) { workspace()->focusToNull(); } } else { shade_geometry_change = true; if (decoratedClient()) decoratedClient()->signalShadeChange(); QSize s(sizeForClientSize(clientSize())); shade_geometry_change = false; plainResize(s); geom_restore = geometry(); if ((shade_mode == ShadeHover || shade_mode == ShadeActivated) && rules()->checkAcceptFocus(info->input())) setActive(true); if (shade_mode == ShadeHover) { ToplevelList order = workspace()->stackingOrder(); // invalidate, since "this" could be the topmost toplevel and shade_below dangeling shade_below = NULL; // this is likely related to the index parameter?! for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) { shade_below = qobject_cast(order.at(idx)); if (shade_below) { break; } } if (shade_below && shade_below->isNormalWindow()) workspace()->raiseClient(this); else shade_below = NULL; } m_wrapper.map(); m_client.map(); exportMappingState(NormalState); if (isActive()) workspace()->requestFocus(this); } info->setState(isShade() ? NET::Shaded : NET::States(0), NET::Shaded); info->setState(isShown(false) ? NET::States(0) : NET::Hidden, NET::Hidden); discardWindowPixmap(); updateVisibility(); updateAllowedActions(); updateWindowRules(Rules::Shade); emit shadeChanged(); } void Client::shadeHover() { setShade(ShadeHover); cancelShadeHoverTimer(); } void Client::shadeUnhover() { if (!tabGroup() || tabGroup()->current() == this || tabGroup()->current()->shadeMode() == ShadeNormal) setShade(ShadeNormal); cancelShadeHoverTimer(); } void Client::cancelShadeHoverTimer() { delete shadeHoverTimer; shadeHoverTimer = 0; } void Client::toggleShade() { // If the mode is ShadeHover or ShadeActive, cancel shade too setShade(shade_mode == ShadeNone ? ShadeNormal : ShadeNone); } void Client::updateVisibility() { if (deleting) return; if (hidden && isCurrentTab()) { info->setState(NET::Hidden, NET::Hidden); setSkipTaskbar(true); // Also hide from taskbar if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } if (isCurrentTab()) setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden' if (isMinimized()) { info->setState(NET::Hidden, NET::Hidden); if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } info->setState(0, NET::Hidden); if (!isOnCurrentDesktop()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } if (!isOnCurrentActivity()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } internalShow(); } /** * Sets the client window's mapping state. Possible values are * WithdrawnState, IconicState, NormalState. */ void Client::exportMappingState(int s) { assert(m_client != XCB_WINDOW_NONE); assert(!deleting || s == WithdrawnState); if (s == WithdrawnState) { m_client.deleteProperty(atoms->wm_state); return; } assert(s == NormalState || s == IconicState); int32_t data[2]; data[0] = s; data[1] = XCB_NONE; m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data); } void Client::internalShow() { if (mapping_state == Mapped) return; MappingState old = mapping_state; mapping_state = Mapped; if (old == Unmapped || old == Withdrawn) map(); if (old == Kept) { m_decoInputExtent.map(); updateHiddenPreview(); } emit windowShown(this); } void Client::internalHide() { if (mapping_state == Unmapped) return; MappingState old = mapping_state; mapping_state = Unmapped; if (old == Mapped || old == Kept) unmap(); if (old == Kept) updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); } void Client::internalKeep() { assert(compositing()); if (mapping_state == Kept) return; MappingState old = mapping_state; mapping_state = Kept; if (old == Unmapped || old == Withdrawn) map(); m_decoInputExtent.unmap(); if (isActive()) workspace()->focusToNull(); // get rid of input focus, bug #317484 updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } /** * Maps (shows) the client. Note that it is mapping state of the frame, * not necessarily the client window itself (i.e. a shaded window is here * considered mapped, even though it is in IconicState). */ void Client::map() { // XComposite invalidates backing pixmaps on unmap (minimize, different // virtual desktop, etc.). We kept the last known good pixmap around // for use in effects, but now we want to have access to the new pixmap if (compositing()) discardWindowPixmap(); m_frame.map(); if (!isShade()) { m_wrapper.map(); m_client.map(); m_decoInputExtent.map(); exportMappingState(NormalState); } else exportMappingState(IconicState); addLayerRepaint(visibleRect()); } /** * Unmaps the client. Again, this is about the frame. */ void Client::unmap() { // Here it may look like a race condition, as some other client might try to unmap // the window between these two XSelectInput() calls. However, they're supposed to // use XWithdrawWindow(), which also sends a synthetic event to the root window, // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify // will be missed is also very minimal, so I don't think it's needed to grab the server // here. m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_frame.unmap(); m_wrapper.unmap(); m_client.unmap(); m_decoInputExtent.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(IconicState); } /** * XComposite doesn't keep window pixmaps of unmapped windows, which means * there wouldn't be any previews of windows that are minimized or on another * virtual desktop. Therefore rawHide() actually keeps such windows mapped. * However special care needs to be taken so that such windows don't interfere. * Therefore they're put very low in the stacking order and they have input shape * set to none, which hopefully is enough. If there's no input shape available, * then it's hoped that there will be some other desktop above it *shrug*. * Using normal shape would be better, but that'd affect other things, e.g. painting * of the actual preview. */ void Client::updateHiddenPreview() { if (hiddenPreview()) { workspace()->forceRestacking(); if (Xcb::Extensions::self()->isShapeInputAvailable()) { xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, NULL); } } else { workspace()->forceRestacking(); updateInputShape(); } } void Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3, xcb_timestamp_t timestamp) { xcb_client_message_event_t ev; memset(&ev, 0, sizeof(ev)); ev.response_type = XCB_CLIENT_MESSAGE; ev.window = w; ev.type = a; ev.format = 32; ev.data.data32[0] = protocol; ev.data.data32[1] = timestamp; ev.data.data32[2] = data1; ev.data.data32[3] = data2; ev.data.data32[4] = data3; uint32_t eventMask = 0; if (w == rootWindow()) { eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic! } xcb_send_event(connection(), false, w, eventMask, reinterpret_cast(&ev)); xcb_flush(connection()); } /** * Returns whether the window may be closed (have a close button) */ bool Client::isCloseable() const { return rules()->checkCloseable(m_motif.close() && !isSpecialWindow()); } /** * Closes the window by either sending a delete_window message or using XKill. */ void Client::closeWindow() { if (!isCloseable()) return; // Update user time, because the window may create a confirming dialog. updateUserTime(); if (info->supportsProtocol(NET::DeleteWindowProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window); pingWindow(); } else // Client will not react on wm_delete_window. We have not choice // but destroy his connection to the XServer. killWindow(); } /** * Kills the window via XKill */ void Client::killWindow() { qCDebug(KWIN_CORE) << "Client::killWindow():" << caption(); killProcess(false); m_client.kill(); // Always kill this client at the server destroyClient(); } /** * Send a ping to the window using _NET_WM_PING if possible if it * doesn't respond within a reasonable time, it will be killed. */ void Client::pingWindow() { if (!info->supportsProtocol(NET::PingProtocol)) return; // Can't ping :( if (options->killPingTimeout() == 0) return; // Turned off if (ping_timer != NULL) return; // Pinging already ping_timer = new QTimer(this); connect(ping_timer, &QTimer::timeout, this, [this]() { qCDebug(KWIN_CORE) << "Ping timeout:" << caption(); ping_timer->deleteLater(); ping_timer = nullptr; killProcess(true, m_pingTimestamp); } ); ping_timer->setSingleShot(true); ping_timer->start(options->killPingTimeout()); m_pingTimestamp = xTime(); workspace()->sendPingToWindow(window(), m_pingTimestamp); } void Client::gotPing(xcb_timestamp_t timestamp) { // Just plain compare is not good enough because of 64bit and truncating and whatnot if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0) return; delete ping_timer; ping_timer = NULL; if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } } void Client::killProcess(bool ask, xcb_timestamp_t timestamp) { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive return; Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME); pid_t pid = info->pid(); if (pid <= 0 || clientMachine()->hostName().isEmpty()) // Needed properties missing return; qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")"; if (!ask) { if (!clientMachine()->isLocal()) { QStringList lst; lst << QString::fromUtf8(clientMachine()->hostName()) << QStringLiteral("kill") << QString::number(pid); QProcess::startDetached(QStringLiteral("xon"), lst); } else ::kill(pid, SIGTERM); } else { QString hostname = clientMachine()->isLocal() ? QStringLiteral("localhost") : QString::fromUtf8(clientMachine()->hostName()); QProcess::startDetached(QStringLiteral(KWIN_KILLER_BIN), QStringList() << QStringLiteral("--pid") << QString::number(unsigned(pid)) << QStringLiteral("--hostname") << hostname << QStringLiteral("--windowname") << caption() << QStringLiteral("--applicationname") << QString::fromUtf8(resourceClass()) << QStringLiteral("--wid") << QString::number(window()) << QStringLiteral("--timestamp") << QString::number(timestamp), QString(), &m_killHelperPID); } } void Client::doSetSkipTaskbar() { info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(0), NET::SkipTaskbar); } void Client::doSetSkipPager() { info->setState(skipPager() ? NET::SkipPager : NET::States(0), NET::SkipPager); } void Client::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) updateVisibility(); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Desktop); } /** * Sets whether the client is on @p activity. * If you remove it from its last activity, then it's on all activities. * * Note: If it was on all activities and you try to remove it from one, nothing will happen; * I don't think that's an important enough use case to handle here. */ void Client::setOnActivity(const QString &activity, bool enable) { #ifdef KWIN_BUILD_ACTIVITIES if (! Activities::self()) { return; } QStringList newActivitiesList = activities(); if (newActivitiesList.contains(activity) == enable) //nothing to do return; if (enable) { QStringList allActivities = Activities::self()->all(); if (!allActivities.contains(activity)) //bogus ID return; newActivitiesList.append(activity); } else newActivitiesList.removeOne(activity); setOnActivities(newActivitiesList); #else Q_UNUSED(activity) Q_UNUSED(enable) #endif } /** * set exactly which activities this client is on */ void Client::setOnActivities(QStringList newActivitiesList) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } QString joinedActivitiesList = newActivitiesList.join(QStringLiteral(",")); joinedActivitiesList = rules()->checkActivity(joinedActivitiesList, false); newActivitiesList = joinedActivitiesList.split(u',', QString::SkipEmptyParts); QStringList allActivities = Activities::self()->all(); auto it = newActivitiesList.begin(); while (it != newActivitiesList.end()) { if (! allActivities.contains(*it)) { it = newActivitiesList.erase(it); } else { it++; } } if (// If we got the request to be on all activities explicitly newActivitiesList.isEmpty() || joinedActivitiesList == Activities::nullUuid() || // If we got a list of activities that covers all activities (newActivitiesList.count() > 1 && newActivitiesList.count() == allActivities.count())) { activityList.clear(); const QByteArray nullUuid = Activities::nullUuid().toUtf8(); m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData()); } else { QByteArray joined = joinedActivitiesList.toAscii(); activityList = newActivitiesList; m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData()); } updateActivities(false); #else Q_UNUSED(newActivitiesList) #endif } void Client::blockActivityUpdates(bool b) { if (b) { ++m_activityUpdatesBlocked; } else { Q_ASSERT(m_activityUpdatesBlocked); --m_activityUpdatesBlocked; if (!m_activityUpdatesBlocked) updateActivities(m_blockedActivityUpdatesRequireTransients); } } /** * update after activities changed */ void Client::updateActivities(bool includeTransients) { if (m_activityUpdatesBlocked) { m_blockedActivityUpdatesRequireTransients |= includeTransients; return; } emit activitiesChanged(this); m_blockedActivityUpdatesRequireTransients = false; // reset FocusChain::self()->update(this, FocusChain::MakeFirst); updateVisibility(); updateWindowRules(Rules::Activity); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Activity); } /** * Returns the virtual desktop within the workspace() the client window * is located in, 0 if it isn't located on any special desktop (not mapped yet), * or NET::OnAllDesktops. Do not use desktop() directly, use * isOnDesktop() instead. */ int Client::desktop() const { if (needsSessionInteract) { return NET::OnAllDesktops; } return AbstractClient::desktop(); } /** * Returns the list of activities the client window is on. * if it's on all activities, the list will be empty. * Don't use this, use isOnActivity() and friends (from class Toplevel) */ QStringList Client::activities() const { if (needsSessionInteract) { return QStringList(); } return activityList; } /** * if @p on is true, sets on all activities. * if it's false, sets it to only be on the current activity */ void Client::setOnAllActivities(bool on) { #ifdef KWIN_BUILD_ACTIVITIES if (on == isOnAllActivities()) return; if (on) { setOnActivities(QStringList()); } else { setOnActivity(Activities::self()->current(), true); } #else Q_UNUSED(on) #endif } /** * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS */ void Client::takeFocus() { if (rules()->checkAcceptFocus(info->input())) m_client.focus(); else demandAttention(false); // window cannot take input, at least withdraw urgency if (info->supportsProtocol(NET::TakeFocusProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus, 0, 0, 0, XCB_CURRENT_TIME); } workspace()->setShouldGetFocus(this); bool breakShowingDesktop = !keepAbove(); if (breakShowingDesktop) { foreach (const Client *c, group()->members()) { if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } /** * Returns whether the window provides context help or not. If it does, * you should show a help menu item or a help button like '?' and call * contextHelp() if this is invoked. * * \sa contextHelp() */ bool Client::providesContextHelp() const { return info->supportsProtocol(NET::ContextHelpProtocol); } /** * Invokes context help on the window. Only works if the window * actually provides context help. * * \sa providesContextHelp() */ void Client::showContextHelp() { if (info->supportsProtocol(NET::ContextHelpProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); QWhatsThis::enterWhatsThisMode(); // SELI TODO: ? } } /** * Fetches the window's caption (WM_NAME property). It will be * stored in the client's caption(). */ void Client::fetchName() { setCaption(readName()); } QString Client::readName() const { if (info->name() && info->name()[0] != '\0') return QString::fromUtf8(info->name()).simplified(); else return KWindowSystem::readNameProperty(window(), XCB_ATOM_WM_NAME).simplified(); } // The list is taken from http://www.unicode.org/reports/tr9/ (#154840) static const QChar LRM(0x200E); static const QChar RLM(0x200F); static const QChar LRE(0x202A); static const QChar RLE(0x202B); static const QChar LRO(0x202D); static const QChar RLO(0x202E); static const QChar PDF(0x202C); void Client::setCaption(const QString& _s, bool force) { if (!force && _s == cap_normal) return; QString s(_s); for (int i = 0; i < s.length(); ++i) if (!s[i].isPrint()) s[i] = QChar(u' '); cap_normal = s; if (options->condensedTitle()) { static QScriptEngine engine; static QScriptProgram stripTitle; static QScriptValue script; if (stripTitle.isNull()) { const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME "/stripTitle.js")); if (!scriptFile.isEmpty()) { QFile f(scriptFile); if (f.open(QIODevice::ReadOnly|QIODevice::Text)) { f.reset(); stripTitle = QScriptProgram(QString::fromLocal8Bit(f.readAll()), QStringLiteral("stripTitle.js")); f.close(); } } if (stripTitle.isNull()) stripTitle = QScriptProgram(QStringLiteral("(function(title, wm_name, wm_class){ return title ; })"), QStringLiteral("stripTitle.js")); script = engine.evaluate(stripTitle); } QScriptValueList args; args << _s << QString::fromUtf8(resourceName()) << QString::fromUtf8(resourceClass()); s = script.call(QScriptValue(), args).toString(); } if (!force && s == cap_deco) return; cap_deco = s; 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 = !shortcut().isEmpty() ? (QLatin1String(" {") + shortcut().toString() + QLatin1Char('}')) : QString(); cap_suffix = machine_suffix + shortcut_suffix; auto fetchNameInternalPredicate = [this](const Client *cl) { return (!cl->isSpecialWindow() || cl->isToolbar()) && cl != this && cl->caption() == caption(); }; if ((!isSpecialWindow() || isToolbar()) && workspace()->findClient(fetchNameInternalPredicate)) { int i = 2; do { cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM; i++; } while (workspace()->findClient(fetchNameInternalPredicate)); info->setVisibleName(caption().toUtf8().constData()); reset_name = false; } if ((was_suffix && cap_suffix.isEmpty()) || reset_name) { // If it was new window, it may have old value still set, if the window is reused info->setVisibleName(""); info->setVisibleIconName(""); } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData()); emit captionChanged(); } void Client::updateCaption() { setCaption(cap_normal, true); } void Client::evaluateWindowRules() { setupWindowRules(true); applyWindowRules(); } void Client::fetchIconicName() { QString s; if (info->iconName() && info->iconName()[0] != '\0') s = QString::fromUtf8(info->iconName()); else s = KWindowSystem::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(""); } } } /** * \reimp */ QString Client::caption(bool full, bool stripped) const { QString cap = stripped ? cap_deco : cap_normal; if (full) cap += cap_suffix; return cap; } bool Client::tabTo(Client *other, bool behind, bool activate) { Q_ASSERT(other && other != this); if (tab_group && tab_group == other->tabGroup()) { // special case: move inside group tab_group->move(this, other, behind); return true; } GeometryUpdatesBlocker blocker(this); const bool wasBlocking = signalsBlocked(); blockSignals(true); // prevent client emitting "retabbed to nowhere" cause it's about to be entabbed the next moment untab(); blockSignals(wasBlocking); TabGroup *newGroup = other->tabGroup() ? other->tabGroup() : new TabGroup(other); if (!newGroup->add(this, other, behind, activate)) { if (newGroup->count() < 2) { // adding "c" to "to" failed for whatever reason newGroup->remove(other); delete newGroup; } return false; } return true; } bool Client::untab(const QRect &toGeometry, bool clientRemoved) { TabGroup *group = tab_group; if (group && group->remove(this)) { // remove sets the tabgroup to "0", therefore the pointer is cached if (group->isEmpty()) { delete group; } if (clientRemoved) return true; // there's been a broadcast signal that this client is now removed - don't touch it setClientShown(!(isMinimized() || isShade())); bool keepSize = toGeometry.size() == size(); bool changedSize = false; if (quickTileMode() != QuickTileNone) { changedSize = true; setQuickTileMode(QuickTileNone); // if we leave a quicktiled group, assume that the user wants to untile } if (toGeometry.isValid()) { if (maximizeMode() != MaximizeRestore) { changedSize = true; maximize(MaximizeRestore); // explicitly calling for a geometry -> unmaximize } if (keepSize && changedSize) { geom_restore = geometry(); // checkWorkspacePosition() invokes it QPoint cpoint = Cursor::pos(); QPoint point = cpoint; point.setX((point.x() - toGeometry.x()) * geom_restore.width() / toGeometry.width()); point.setY((point.y() - toGeometry.y()) * geom_restore.height() / toGeometry.height()); geom_restore.moveTo(cpoint-point); } else { geom_restore = toGeometry; // checkWorkspacePosition() invokes it } setGeometry(geom_restore); checkWorkspacePosition(); } return true; } return false; } void Client::setTabGroup(TabGroup *group) { tab_group = group; if (group) { unsigned long data[] = {qHash(group)}; //->id(); m_client.changeProperty(atoms->kde_net_wm_tab_group, XCB_ATOM_CARDINAL, 32, 1, data); } else m_client.deleteProperty(atoms->kde_net_wm_tab_group); emit tabGroupChanged(); } bool Client::isCurrentTab() const { return !tab_group || tab_group->current() == this; } void Client::syncTabGroupFor(QString property, bool fromThisClient) { if (tab_group) tab_group->sync(property.toAscii().data(), fromThisClient ? this : tab_group->current()); } void Client::setClientShown(bool shown) { if (deleting) return; // Don't change shown status if this client is being deleted if (shown != hidden) return; // nothing to change hidden = !shown; if (options->isInactiveTabsSkipTaskbar()) setSkipTaskbar(hidden); // TODO: Causes reshuffle of the taskbar if (shown) { map(); takeFocus(); autoRaise(); FocusChain::self()->update(this, FocusChain::MakeFirst); } else { unmap(); // Don't move tabs to the end of the list when another tab get's activated if (isCurrentTab()) FocusChain::self()->update(this, FocusChain::MakeLast); addWorkspaceRepaint(visibleRect()); } } void Client::getMotifHints() { const bool wasClosable = m_motif.close(); const bool wasNoBorder = m_motif.noBorder(); if (m_managed) // only on property change, initial read is prefetched m_motif.fetch(); m_motif.read(); if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) { // If we just got a hint telling us to hide decorations, we do so. if (m_motif.noBorder()) noborder = rules()->checkNoBorder(true); // If the Motif hint is now telling us to show decorations, we only do so if the app didn't // instruct us to hide decorations in some other way, though. else if (!app_noborder) noborder = rules()->checkNoBorder(false); } // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too // mmaximize; - Ignore, bogus - Maximizing is basically just resizing const bool closabilityChanged = wasClosable != m_motif.close(); if (isManaged()) updateDecoration(true); // Check if noborder state has changed if (closabilityChanged) { emit closeableChanged(isCloseable()); } } void Client::getIcons() { // First read icons from the window itself const QString themedIconName = iconFromDesktopFile(); if (!themedIconName.isEmpty()) { setIcon(QIcon::fromTheme(themedIconName)); return; } QIcon icon; auto readIcon = [this, &icon](int size, bool scale = true) { const QPixmap pix = KWindowSystem::icon(window(), size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, info); if (!pix.isNull()) { icon.addPixmap(pix); } }; readIcon(16); readIcon(32); readIcon(48, false); readIcon(64, false); readIcon(128, false); if (icon.isNull()) { // Then try window group icon = group()->icon(); } if (icon.isNull() && isTransient()) { // Then mainclients auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd() && icon.isNull(); ++it) { if (!(*it)->icon().isNull()) { icon = (*it)->icon(); break; } } } if (icon.isNull()) { // And if nothing else, load icon from classhint or xapp icon icon.addPixmap(KWindowSystem::icon(window(), 32, 32, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 16, 16, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 64, 64, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); } setIcon(icon); } void Client::getSyncCounter() { if (!Xcb::Extensions::self()->isSyncAvailable()) return; Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1); const xcb_sync_counter_t counter = syncProp.value(XCB_NONE); if (counter != XCB_NONE) { syncRequest.counter = counter; syncRequest.value.hi = 0; syncRequest.value.lo = 0; auto *c = connection(); xcb_sync_set_counter(c, syncRequest.counter, syncRequest.value); if (syncRequest.alarm == XCB_NONE) { const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS; const uint32_t values[] = { syncRequest.counter, XCB_SYNC_VALUETYPE_RELATIVE, XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION, 1 }; syncRequest.alarm = xcb_generate_id(c); auto cookie = xcb_sync_create_alarm_checked(c, syncRequest.alarm, mask, values); ScopedCPointer error(xcb_request_check(c, cookie)); if (!error.isNull()) { syncRequest.alarm = XCB_NONE; } else { xcb_sync_change_alarm_value_list_t value; memset(&value, 0, sizeof(value)); value.value.hi = 0; value.value.lo = 1; value.delta.hi = 0; value.delta.lo = 1; xcb_sync_change_alarm_aux(c, syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value); } } } } /** * Send the client a _NET_SYNC_REQUEST */ void Client::sendSyncRequest() { if (syncRequest.counter == XCB_NONE || syncRequest.isPending) return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... if (!syncRequest.failsafeTimeout) { syncRequest.failsafeTimeout = new QTimer(this); connect(syncRequest.failsafeTimeout, &QTimer::timeout, this, [this]() { // client does not respond to XSYNC requests in reasonable time, remove support if (!ready_for_painting) { // failed on initial pre-show request setReadyForPainting(); setupWindowManagementInterface(); return; } // failed during resize syncRequest.isPending = false; syncRequest.counter = syncRequest.alarm = XCB_NONE; delete syncRequest.timeout; delete syncRequest.failsafeTimeout; syncRequest.timeout = syncRequest.failsafeTimeout = nullptr; syncRequest.lastTimestamp = XCB_CURRENT_TIME; } ); syncRequest.failsafeTimeout->setSingleShot(true); } // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client. // see events.cpp Client::syncEvent() syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000); // We increment before the notify so that after the notify // syncCounterSerial will equal the value we are expecting // in the acknowledgement const uint32_t oldLo = syncRequest.value.lo; syncRequest.value.lo++;; if (oldLo > syncRequest.value.lo) { syncRequest.value.hi++; } if (syncRequest.lastTimestamp >= xTime()) { updateXTime(); } // Send the message to client sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request, syncRequest.value.lo, syncRequest.value.hi); syncRequest.isPending = true; syncRequest.lastTimestamp = xTime(); } bool Client::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol)); } bool Client::acceptsFocus() const { return info->input(); } void Client::setBlockingCompositing(bool block) { const bool usedToBlock = blocks_compositing; blocks_compositing = rules()->checkBlockCompositing(block && options->windowsBlockCompositing()); if (usedToBlock != blocks_compositing) { emit blockingCompositingChanged(blocks_compositing ? this : 0); } } void Client::updateAllowedActions(bool force) { if (!isManaged() && !force) return; NET::Actions old_allowed_actions = NET::Actions(allowed_actions); allowed_actions = 0; if (isMovable()) allowed_actions |= NET::ActionMove; if (isResizable()) allowed_actions |= NET::ActionResize; if (isMinimizable()) allowed_actions |= NET::ActionMinimize; if (isShadeable()) allowed_actions |= NET::ActionShade; // Sticky state not supported if (isMaximizable()) allowed_actions |= NET::ActionMax; if (userCanSetFullScreen()) allowed_actions |= NET::ActionFullScreen; allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.) if (isCloseable()) allowed_actions |= NET::ActionClose; if (old_allowed_actions == allowed_actions) return; // TODO: This could be delayed and compressed - It's only for pagers etc. anyway info->setAllowedActions(allowed_actions); // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes) const NET::Actions relevant = ~(NET::ActionMove|NET::ActionResize); if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) { if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) { emit minimizeableChanged(allowed_actions & NET::ActionMinimize); } if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) { emit shadeableChanged(allowed_actions & NET::ActionShade); } if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) { emit maximizeableChanged(allowed_actions & NET::ActionMax); } } } void Client::debug(QDebug& stream) const { print(stream); } Xcb::StringProperty Client::fetchActivities() const { #ifdef KWIN_BUILD_ACTIVITIES return Xcb::StringProperty(window(), atoms->activities); #else return Xcb::StringProperty(); #endif } void Client::readActivities(Xcb::StringProperty &property) { #ifdef KWIN_BUILD_ACTIVITIES QStringList newActivitiesList; QString prop = QString::fromUtf8(property); activitiesDefined = !prop.isEmpty(); if (prop == Activities::nullUuid()) { //copied from setOnAllActivities to avoid a redundant XChangeProperty. if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } if (prop.isEmpty()) { //note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL' if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } newActivitiesList = prop.split(u','); if (newActivitiesList == activityList) return; //expected change, it's ok. //otherwise, somebody else changed it. we need to validate before reacting. //if the activities are not synced, and there are existing clients with //activities specified, somebody has restarted kwin. we can not validate //activities in this case. we need to trust the old values. if (Activities::self() && Activities::self()->serviceStatus() != KActivities::Consumer::Unknown) { QStringList allActivities = Activities::self()->all(); if (allActivities.isEmpty()) { qCDebug(KWIN_CORE) << "no activities!?!?"; //don't touch anything, there's probably something bad going on and we don't wanna make it worse return; } for (int i = 0; i < newActivitiesList.size(); ++i) { if (! allActivities.contains(newActivitiesList.at(i))) { qCDebug(KWIN_CORE) << "invalid:" << newActivitiesList.at(i); newActivitiesList.removeAt(i--); } } } setOnActivities(newActivitiesList); #else Q_UNUSED(property) #endif } void Client::checkActivities() { #ifdef KWIN_BUILD_ACTIVITIES Xcb::StringProperty property = fetchActivities(); readActivities(property); #endif } void Client::setSessionInteract(bool needed) { needsSessionInteract = needed; } QRect Client::decorationRect() const { return QRect(0, 0, width(), height()); } Xcb::Property Client::fetchFirstInTabBox() const { return Xcb::Property(false, m_client, atoms->kde_first_in_window_list, atoms->kde_first_in_window_list, 0, 1); } void Client::readFirstInTabBox(Xcb::Property &property) { setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list)); } void Client::updateFirstInTabBox() { // TODO: move into KWindowInfo Xcb::Property property = fetchFirstInTabBox(); readFirstInTabBox(property); } Xcb::StringProperty Client::fetchColorScheme() const { return Xcb::StringProperty(m_client, atoms->kde_color_sheme); } void Client::readColorScheme(Xcb::StringProperty &property) { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString::fromUtf8(property))); } void Client::updateColorScheme() { Xcb::StringProperty property = fetchColorScheme(); readColorScheme(property); } bool Client::isClient() const { return true; } NET::WindowType Client::windowType(bool direct, int supportedTypes) const { // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK? if (supportedTypes == 0) { supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK; } NET::WindowType wt = info->windowType(NET::WindowTypes(supportedTypes)); if (direct) { return wt; } NET::WindowType wt2 = client_rules.checkType(wt); if (wt != wt2) { wt = wt2; info->setWindowType(wt); // force hint change } // hacks here if (wt == NET::Unknown) // this is more or less suggested in NETWM spec wt = isTransient() ? NET::Dialog : NET::Normal; return wt; } void Client::cancelFocusOutTimer() { if (m_focusOutTimer) { m_focusOutTimer->stop(); } } xcb_window_t Client::frameId() const { return m_frame; } Xcb::Property Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); } void Client::readShowOnScreenEdge(Xcb::Property &property) { //value comes in two parts, edge in the lower byte //then the type in the upper byte // 0 = autohide // 1 = raise in front on activate const uint32_t value = property.value(ElectricNone); ElectricBorder border = ElectricNone; switch (value & 0xFF) { case 0: border = ElectricTop; break; case 1: border = ElectricRight; break; case 2: border = ElectricBottom; break; case 3: border = ElectricLeft; break; } if (border != ElectricNone) { disconnect(m_edgeRemoveConnection); 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_edgeRemoveConnection = connect(this, &Client::geometryChanged, this, [this](){ ScreenEdges::self()->reserve(this, ElectricNone); }); } 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 ScreenEdges::self()->reserve(this, ElectricNone); } } void Client::updateShowOnScreenEdge() { Xcb::Property property = fetchShowOnScreenEdge(); readShowOnScreenEdge(property); } void Client::showOnScreenEdge() { disconnect(m_edgeRemoveConnection); hideClient(false); setKeepBelow(false); xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } void Client::addDamage(const QRegion &damage) { if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } repaints_region += damage; Toplevel::addDamage(damage); } bool Client::belongsToSameApplication(const AbstractClient *other, bool active_hack) const { const Client *c2 = dynamic_cast(other); if (!c2) { return false; } return Client::belongToSameApplication(this, c2, active_hack); } void Client::updateTabGroupStates(TabGroup::States states) { if (auto t = tabGroup()) { t->updateStates(this, states); } } QSize Client::resizeIncrements() const { return m_geometryHints.resizeIncrements(); } +Xcb::StringProperty Client::fetchApplicationMenuServiceName() const +{ + return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name); +} + +void Client::readApplicationMenuServiceName(Xcb::StringProperty &property) +{ + updateApplicationMenuServiceName(QString::fromUtf8(property)); +} + +void Client::checkApplicationMenuServiceName() +{ + Xcb::StringProperty property = fetchApplicationMenuServiceName(); + readApplicationMenuServiceName(property); +} + +Xcb::StringProperty Client::fetchApplicationMenuObjectPath() const +{ + return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path); +} + +void Client::readApplicationMenuObjectPath(Xcb::StringProperty &property) +{ + updateApplicationMenuObjectPath(QString::fromUtf8(property)); +} + +void Client::checkApplicationMenuObjectPath() +{ + Xcb::StringProperty property = fetchApplicationMenuObjectPath(); + readApplicationMenuObjectPath(property); +} + } // namespace diff --git a/client.h b/client.h index 8be241dd2..25deaf046 100644 --- a/client.h +++ b/client.h @@ -1,760 +1,768 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_CLIENT_H #define KWIN_CLIENT_H // kwin #include "options.h" #include "rules.h" #include "tabgroup.h" #include "abstract_client.h" #include "xcbutils.h" // Qt #include #include #include #include #include // X #include // TODO: Cleanup the order of things in this .h file class QTimer; class KStartupInfoData; class KStartupInfoId; struct xcb_sync_alarm_notify_event_t; namespace KWin { /** * @brief Defines Predicates on how to search for a Client. * * Used by Workspace::findClient. */ enum class Predicate { WindowMatch, WrapperIdMatch, FrameIdMatch, InputIdMatch }; class KWIN_EXPORT Client : public AbstractClient { Q_OBJECT /** * By how much the window wishes to grow/shrink at least. Usually QSize(1,1). * MAY BE DISOBEYED BY THE WM! It's only for information, do NOT rely on it at all. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(QSize basicUnit READ basicUnit) /** * The "Window Tabs" Group this Client belongs to. **/ Q_PROPERTY(KWin::TabGroup* tabGroup READ tabGroup NOTIFY tabGroupChanged SCRIPTABLE false) /** * A client can block compositing. That is while the Client is alive and the state is set, * Compositing is suspended and is resumed when there are no Clients blocking compositing any * more. * * This is actually set by a window property, unfortunately not used by the target application * group. For convenience it's exported as a property to the scripts. * * Use with care! **/ Q_PROPERTY(bool blocksCompositing READ isBlockingCompositing WRITE setBlockingCompositing NOTIFY blockingCompositingChanged) /** * Whether the Client uses client side window decorations. * Only GTK+ are detected. **/ Q_PROPERTY(bool clientSideDecorated READ isClientSideDecorated NOTIFY clientSideDecoratedChanged) public: explicit Client(); xcb_window_t wrapperId() const; xcb_window_t inputId() const { return m_decoInputExtent; } virtual xcb_window_t frameId() const override; bool isTransient() const override; bool groupTransient() const; bool wasOriginallyGroupTransient() const; QList mainClients() const override; // Call once before loop , is not indirect bool hasTransient(const AbstractClient* c, bool indirect) const override; void checkTransient(xcb_window_t w); AbstractClient* findModal(bool allow_itself = false) override; const Group* group() const; Group* group(); void checkGroup(Group* gr = NULL, bool force = false); void changeClientLeaderGroup(Group* gr); const WindowRules* rules() const override; void removeRule(Rules* r); void setupWindowRules(bool ignore_temporary); void applyWindowRules(); void updateWindowRules(Rules::Types selection) override; void updateFullscreenMonitors(NETFullscreenMonitors topology); bool hasNETSupport() const; QSize minSize() const override; QSize maxSize() const override; QSize basicUnit() const; virtual QSize clientSize() const; QPoint inputPos() const { return input_offset; } // Inside of geometry() bool windowEvent(xcb_generic_event_t *e); void syncEvent(xcb_sync_alarm_notify_event_t* e); NET::WindowType windowType(bool direct = false, int supported_types = 0) const; bool manage(xcb_window_t w, bool isMapped); void releaseWindow(bool on_shutdown = false); void destroyClient(); virtual int desktop() const; virtual QStringList activities() const; void setOnActivity(const QString &activity, bool enable); void setOnAllActivities(bool set) override; void setOnActivities(QStringList newActivitiesList); void updateActivities(bool includeTransients); void blockActivityUpdates(bool b = true) override; /// Is not minimized and not hidden. I.e. normally visible on some virtual desktop. bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override; // For compositing ShadeMode shadeMode() const override; // Prefer isShade() void setShade(ShadeMode mode) override; bool isShadeable() const override; bool isMaximizable() const override; QRect geometryRestore() const override; MaximizeMode maximizeMode() const override; bool isMinimizable() const override; QRect iconGeometry() const override; void setFullScreen(bool set, bool user = true) override; bool isFullScreen() const override; bool isFullScreenable() const override; bool isFullScreenable(bool fullscreen_hack) const; bool userCanSetFullScreen() const override; QRect geometryFSRestore() const { return geom_fs_restore; // Only for session saving } int fullScreenMode() const { return fullscreen_mode; // only for session saving } bool noBorder() const override; void setNoBorder(bool set) override; bool userCanSetNoBorder() const override; void checkNoBorder(); int sessionStackingOrder() const; // Auxiliary functions, depend on the windowType bool wantsInput() const override; bool isResizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isCloseable() const override; ///< May be closed by the user (May have a close button) void takeFocus() override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void updateShape(); using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; /// plainResize() simply resizes void plainResize(int w, int h, ForceGeometry_t force = NormalGeometrySet); void plainResize(const QSize& s, ForceGeometry_t force = NormalGeometrySet); /// resizeWithChecks() resizes according to gravity, and checks workarea position using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; void resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); void resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); QSize sizeForClientSize(const QSize&, Sizemode mode = SizemodeAny, bool noframe = false) const override; bool providesContextHelp() const override; const QKeySequence &shortcut() const override; void setShortcut(const QString& cut) override; Options::WindowOperation mouseButtonToWindowOperation(Qt::MouseButtons button); bool performMouseCommand(Options::MouseCommand, const QPoint& globalPos) override; QRect adjustedClientArea(const QRect& desktop, const QRect& area) const; xcb_colormap_t colormap() const; /// Updates visibility depending on being shaded, virtual desktop, etc. void updateVisibility(); /// Hides a client - Basically like minimize, but without effects, it's simply hidden void hideClient(bool hide) override; bool hiddenPreview() const; ///< Window is mapped in order to get a window pixmap virtual bool setupCompositing(); void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void setBlockingCompositing(bool block); inline bool isBlockingCompositing() { return blocks_compositing; } QString caption(bool full = true, bool stripped = false) const override; using AbstractClient::keyPressEvent; void keyPressEvent(uint key_code, xcb_timestamp_t time); // FRAME ?? void updateMouseGrab() override; xcb_window_t moveResizeGrabWindow() const; const QPoint calculateGravitation(bool invert, int gravity = 0) const; // FRAME public? void NETMoveResize(int x_root, int y_root, NET::Direction direction); void NETMoveResizeWindow(int flags, int x, int y, int width, int height); void restackWindow(xcb_window_t above, int detail, NET::RequestSource source, xcb_timestamp_t timestamp, bool send_event = false); void gotPing(xcb_timestamp_t timestamp); void updateUserTime(xcb_timestamp_t time = XCB_TIME_CURRENT_TIME); xcb_timestamp_t userTime() const override; bool hasUserTimeSupport() const; /// Does 'delete c;' static void deleteClient(Client* c); static bool belongToSameApplication(const Client* c1, const Client* c2, bool active_hack = false); static bool sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack); void killWindow() override; void toggleShade(); void showContextHelp() override; void cancelShadeHoverTimer(); void checkActiveModal(); StrutRect strutRect(StrutArea area) const; StrutRects strutRects() const; bool hasStrut() const override; // Tabbing functions TabGroup* tabGroup() const override; // Returns a pointer to client_group Q_INVOKABLE inline bool tabBefore(Client *other, bool activate) { return tabTo(other, false, activate); } Q_INVOKABLE inline bool tabBehind(Client *other, bool activate) { return tabTo(other, true, activate); } /** * Syncs the *dynamic* @param property @param fromThisClient or the @link currentTab() to * all members of the @link tabGroup() (if there is one) * * eg. if you call: * client->setProperty("kwin_tiling_floats", true); * client->syncTabGroupFor("kwin_tiling_floats", true) * all clients in this tabGroup will have ::property("kwin_tiling_floats").toBool() == true * * WARNING: non dynamic properties are ignored - you're not supposed to alter/update such explicitly */ Q_INVOKABLE void syncTabGroupFor(QString property, bool fromThisClient = false); Q_INVOKABLE bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false) override; /** * Set tab group - this is to be invoked by TabGroup::add/remove(client) and NO ONE ELSE */ void setTabGroup(TabGroup* group); /* * If shown is true the client is mapped and raised, if false * the client is unmapped and hidden, this function is called * when the tabbing group of the client switches its visible * client. */ void setClientShown(bool shown); /* * When a click is done in the decoration and it calls the group * to change the visible client it starts to move-resize the new * client, this function stops it. */ bool isCurrentTab() const override; /** * Whether or not the window has a strut that expands through the invisible area of * an xinerama setup where the monitors are not the same resolution. */ bool hasOffscreenXineramaStrut() const; // Decorations <-> Effects QRect decorationRect() const; QRect transparentRect() const; bool isClientSideDecorated() const; bool wantsShadowToBeRendered() const override; void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const override; Xcb::Property fetchFirstInTabBox() const; void readFirstInTabBox(Xcb::Property &property); void updateFirstInTabBox(); Xcb::StringProperty fetchColorScheme() const; void readColorScheme(Xcb::StringProperty &property); void updateColorScheme(); //sets whether the client should be treated as a SessionInteract window void setSessionInteract(bool needed); virtual bool isClient() const; template void print(T &stream) const; void cancelFocusOutTimer(); /** * Restores the Client after it had been hidden due to show on screen edge functionality. * In addition the property gets deleted so that the Client knows that it is visible again. **/ void showOnScreenEdge() override; + Xcb::StringProperty fetchApplicationMenuServiceName() const; + void readApplicationMenuServiceName(Xcb::StringProperty &property); + void checkApplicationMenuServiceName(); + + Xcb::StringProperty fetchApplicationMenuObjectPath() const; + void readApplicationMenuObjectPath(Xcb::StringProperty &property); + void checkApplicationMenuObjectPath(); + static void cleanupX11(); public Q_SLOTS: void closeWindow() override; void updateCaption(); void evaluateWindowRules(); private Q_SLOTS: void shadeHover(); void shadeUnhover(); private: // Use Workspace::createClient() virtual ~Client(); ///< Use destroyClient() or releaseWindow() // Handlers for X11 events bool mapRequestEvent(xcb_map_request_event_t *e); void unmapNotifyEvent(xcb_unmap_notify_event_t *e); void destroyNotifyEvent(xcb_destroy_notify_event_t *e); void configureRequestEvent(xcb_configure_request_event_t *e); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e) override; void clientMessageEvent(xcb_client_message_event_t *e) override; void enterNotifyEvent(xcb_enter_notify_event_t *e); void leaveNotifyEvent(xcb_leave_notify_event_t *e); void focusInEvent(xcb_focus_in_event_t *e); void focusOutEvent(xcb_focus_out_event_t *e); virtual void damageNotifyEvent(); bool buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time = XCB_CURRENT_TIME); bool buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root); bool motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root); Client* findAutogroupCandidate() const; protected: virtual void debug(QDebug& stream) const; void addDamage(const QRegion &damage) override; bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const override; void doSetActive() override; void doSetKeepAbove() override; void doSetKeepBelow() override; void doSetDesktop(int desktop, int was_desk) override; void doMinimize() override; void doSetSkipPager() override; void doSetSkipTaskbar() override; bool belongsToDesktop() const override; bool isActiveFullScreen() const override; void setGeometryRestore(const QRect &geo) override; void updateTabGroupStates(TabGroup::States states) override; void doMove(int x, int y) override; bool doStartMoveResize() override; void doPerformMoveResize() override; bool isWaitingForMoveResizeSync() const override; void doResizeSync() override; QSize resizeIncrements() const override; bool acceptsFocus() const override; private Q_SLOTS: void delayedSetShortcut(); //Signals for the scripting interface //Signals make an excellent way for communication //in between objects as compared to simple function //calls Q_SIGNALS: void clientManaging(KWin::Client*); void clientFullScreenSet(KWin::Client*, bool, bool); /** * Emitted whenever the Client's TabGroup changed. That is whenever the Client is moved to * another group, but not when a Client gets added or removed to the Client's ClientGroup. **/ void tabGroupChanged(); /** * Emitted whenever the Client want to show it menu */ void showRequest(); /** * Emitted whenever the Client's menu is closed */ void menuHidden(); /** * Emitted whenever the Client's menu is available **/ void appMenuAvailable(); /** * Emitted whenever the Client's menu is unavailable */ void appMenuUnavailable(); /** * Emitted whenever the Client's block compositing state changes. **/ void blockingCompositingChanged(KWin::Client *client); void clientSideDecoratedChanged(); private: void exportMappingState(int s); // ICCCM 4.1.3.1, 4.1.4, NETWM 2.5.1 bool isManaged() const; ///< Returns false if this client is not yet managed void updateAllowedActions(bool force = false); QRect fullscreenMonitorsArea(NETFullscreenMonitors topology) const; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; int checkFullScreenHack(const QRect& geom) const; // 0 - None, 1 - One xinerama screen, 2 - Full area void updateFullScreenHack(const QRect& geom); void getWmNormalHints(); void getMotifHints(); void getIcons(); void fetchName(); void fetchIconicName(); QString readName() const; void setCaption(const QString& s, bool force = false); bool hasTransientInternal(const Client* c, bool indirect, ConstClientList& set) const; void finishWindowRules(); void setShortcutInternal(const QKeySequence &cut = QKeySequence()); void configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool); NETExtendedStrut strut() const; int checkShadeGeometry(int w, int h); void getSyncCounter(); void sendSyncRequest(); void leaveMoveResize() override; void positionGeometryTip() override; void grabButton(int mod); void ungrabButton(int mod); void resizeDecoration(); void createDecoration(const QRect &oldgeom); void pingWindow(); void killProcess(bool ask, xcb_timestamp_t timestamp = XCB_TIME_CURRENT_TIME); void updateUrgency(); static void sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1 = 0, uint32_t data2 = 0, uint32_t data3 = 0, xcb_timestamp_t timestamp = xTime()); void embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth); void detectNoBorder(); Xcb::Property fetchGtkFrameExtents() const; void readGtkFrameExtents(Xcb::Property &prop); void detectGtkFrameExtents(); void destroyDecoration() override; void updateFrameExtents(); void internalShow(); void internalHide(); void internalKeep(); void map(); void unmap(); void updateHiddenPreview(); void updateInputShape(); xcb_timestamp_t readUserTimeMapTimestamp(const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, bool session) const; xcb_timestamp_t readUserCreationTime() const; void startupIdChanged(); void updateInputWindow(); bool tabTo(Client *other, bool behind, bool activate); Xcb::Property fetchShowOnScreenEdge() const; void readShowOnScreenEdge(Xcb::Property &property); /** * Reads the property and creates/destroys the screen edge if required * and shows/hides the client. **/ void updateShowOnScreenEdge(); Xcb::Window m_client; Xcb::Window m_wrapper; Xcb::Window m_frame; QStringList activityList; int m_activityUpdatesBlocked; bool m_blockedActivityUpdatesRequireTransients; Xcb::Window m_moveResizeGrabWindow; bool move_resize_has_keyboard_grab; bool m_managed; Xcb::GeometryHints m_geometryHints; void sendSyntheticConfigureNotify(); enum MappingState { Withdrawn, ///< Not handled, as per ICCCM WithdrawnState Mapped, ///< The frame is mapped Unmapped, ///< The frame is not mapped Kept ///< The frame should be unmapped, but is kept (For compositing) }; MappingState mapping_state; Xcb::TransientFor fetchTransient() const; void readTransientProperty(Xcb::TransientFor &transientFor); void readTransient(); xcb_window_t verifyTransientFor(xcb_window_t transient_for, bool set); void addTransient(AbstractClient* cl) override; void removeTransient(AbstractClient* cl) override; void removeFromMainClients(); void cleanGrouping(); void checkGroupTransients(); void setTransient(xcb_window_t new_transient_for_id); xcb_window_t m_transientForId; xcb_window_t m_originalTransientForId; ShadeMode shade_mode; Client *shade_below; uint deleting : 1; ///< True when doing cleanup and destroying the client Xcb::MotifHints m_motif; uint hidden : 1; ///< Forcibly hidden by calling hide() uint noborder : 1; uint app_noborder : 1; ///< App requested no border via window type, shape extension, etc. uint ignore_focus_stealing : 1; ///< Don't apply focus stealing prevention to this client bool blocks_compositing; WindowRules client_rules; // DON'T reorder - Saved to config files !!! enum FullScreenMode { FullScreenNone, FullScreenNormal, FullScreenHack ///< Non-NETWM fullscreen (noborder and size of desktop) }; FullScreenMode fullscreen_mode; MaximizeMode max_mode; QRect geom_restore; QRect geom_fs_restore; QTimer* shadeHoverTimer; xcb_colormap_t m_colormap; QString cap_normal, cap_iconic, cap_suffix, cap_deco; Group* in_group; TabGroup* tab_group; QTimer* ping_timer; qint64 m_killHelperPID; xcb_timestamp_t m_pingTimestamp; xcb_timestamp_t m_userTime; NET::Actions allowed_actions; QSize client_size; bool shade_geometry_change; struct { xcb_sync_counter_t counter; xcb_sync_int64_t value; xcb_sync_alarm_t alarm; xcb_timestamp_t lastTimestamp; QTimer *timeout, *failsafeTimeout; bool isPending; } syncRequest; static bool check_active_modal; ///< \see Client::checkActiveModal() QKeySequence _shortcut; int sm_stacking_order; friend struct ResetupRulesProcedure; friend bool performTransiencyCheck(); Xcb::StringProperty fetchActivities() const; void readActivities(Xcb::StringProperty &property); void checkActivities(); bool activitiesDefined; //whether the x property was actually set bool needsSessionInteract; bool needsXWindowMove; Xcb::Window m_decoInputExtent; QPoint input_offset; QTimer *m_focusOutTimer; QList m_connections; bool m_clientSideDecorated; QMetaObject::Connection m_edgeRemoveConnection; }; inline xcb_window_t Client::wrapperId() const { return m_wrapper; } inline bool Client::isClientSideDecorated() const { return m_clientSideDecorated; } inline bool Client::groupTransient() const { return m_transientForId == rootWindow(); } // Needed because verifyTransientFor() may set transient_for_id to root window, // if the original value has a problem (window doesn't exist, etc.) inline bool Client::wasOriginallyGroupTransient() const { return m_originalTransientForId == rootWindow(); } inline bool Client::isTransient() const { return m_transientForId != XCB_WINDOW_NONE; } inline const Group* Client::group() const { return in_group; } inline Group* Client::group() { return in_group; } inline TabGroup* Client::tabGroup() const { return tab_group; } inline bool Client::isShown(bool shaded_is_shown) const { return !isMinimized() && (!isShade() || shaded_is_shown) && !hidden && (!tabGroup() || tabGroup()->current() == this); } inline bool Client::isHiddenInternal() const { return hidden; } inline ShadeMode Client::shadeMode() const { return shade_mode; } inline QRect Client::geometryRestore() const { return geom_restore; } inline void Client::setGeometryRestore(const QRect &geo) { geom_restore = geo; } inline MaximizeMode Client::maximizeMode() const { return max_mode; } inline bool Client::isFullScreen() const { return fullscreen_mode != FullScreenNone; } inline bool Client::hasNETSupport() const { return info->hasNETSupport(); } inline xcb_colormap_t Client::colormap() const { return m_colormap; } inline int Client::sessionStackingOrder() const { return sm_stacking_order; } inline bool Client::isManaged() const { return m_managed; } inline QSize Client::clientSize() const { return client_size; } inline void Client::plainResize(const QSize& s, ForceGeometry_t force) { plainResize(s.width(), s.height(), force); } inline void Client::resizeWithChecks(int w, int h, AbstractClient::ForceGeometry_t force) { resizeWithChecks(w, h, XCB_GRAVITY_BIT_FORGET, force); } inline void Client::resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), gravity, force); } inline bool Client::hasUserTimeSupport() const { return info->userTime() != -1U; } inline const WindowRules* Client::rules() const { return &client_rules; } inline xcb_window_t Client::moveResizeGrabWindow() const { return m_moveResizeGrabWindow; } inline const QKeySequence &Client::shortcut() const { return _shortcut; } inline void Client::removeRule(Rules* rule) { client_rules.remove(rule); } inline bool Client::hiddenPreview() const { return mapping_state == Kept; } template inline void Client::print(T &stream) const { stream << "\'ID:" << window() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } } // namespace Q_DECLARE_METATYPE(KWin::Client*) Q_DECLARE_METATYPE(QList) #endif diff --git a/decorations/decoratedclient.cpp b/decorations/decoratedclient.cpp index 0bb012c50..907b80d08 100644 --- a/decorations/decoratedclient.cpp +++ b/decorations/decoratedclient.cpp @@ -1,284 +1,307 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "decoratedclient.h" #include "decorationpalette.h" #include "decorationrenderer.h" #include "abstract_client.h" #include "composite.h" #include "cursor.h" #include "options.h" #include "scene.h" #include "workspace.h" #include #include #include namespace KWin { namespace Decoration { DecoratedClientImpl::DecoratedClientImpl(AbstractClient *client, KDecoration2::DecoratedClient *decoratedClient, KDecoration2::Decoration *decoration) : QObject() - , DecoratedClientPrivate(decoratedClient, decoration) + , ApplicationMenuEnabledDecoratedClientPrivate(decoratedClient, decoration) , m_client(client) , m_clientSize(client->clientSize()) , m_renderer(nullptr) { createRenderer(); client->setDecoratedClient(QPointer(this)); connect(client, &AbstractClient::activeChanged, this, [decoratedClient, client]() { emit decoratedClient->activeChanged(client->isActive()); } ); connect(client, &AbstractClient::geometryChanged, this, [decoratedClient, this]() { if (m_client->clientSize() == m_clientSize) { return; } const auto oldSize = m_clientSize; m_clientSize = m_client->clientSize(); if (oldSize.width() != m_clientSize.width()) { emit decoratedClient->widthChanged(m_clientSize.width()); } if (oldSize.height() != m_clientSize.height()) { emit decoratedClient->heightChanged(m_clientSize.height()); } } ); connect(client, &AbstractClient::desktopChanged, this, [decoratedClient, client]() { emit decoratedClient->onAllDesktopsChanged(client->isOnAllDesktops()); } ); connect(client, &AbstractClient::captionChanged, this, [decoratedClient, client]() { emit decoratedClient->captionChanged(client->caption()); } ); connect(client, &AbstractClient::iconChanged, this, [decoratedClient, client]() { emit decoratedClient->iconChanged(client->icon()); } ); connect(client, &AbstractClient::shadeChanged, this, &Decoration::DecoratedClientImpl::signalShadeChange); connect(client, &AbstractClient::keepAboveChanged, decoratedClient, &KDecoration2::DecoratedClient::keepAboveChanged); connect(client, &AbstractClient::keepBelowChanged, decoratedClient, &KDecoration2::DecoratedClient::keepBelowChanged); m_compositorToggledConnection = connect(Compositor::self(), &Compositor::compositingToggled, this, [this, decoration]() { delete m_renderer; m_renderer = nullptr; createRenderer(); decoration->update(); } ); connect(Compositor::self(), &Compositor::aboutToDestroy, this, [this] { disconnect(m_compositorToggledConnection); m_compositorToggledConnection = QMetaObject::Connection(); } ); connect(client, &AbstractClient::quickTileModeChanged, decoratedClient, [this, decoratedClient]() { emit decoratedClient->adjacentScreenEdgesChanged(adjacentScreenEdges()); } ); connect(client, &AbstractClient::closeableChanged, decoratedClient, &KDecoration2::DecoratedClient::closeableChanged); connect(client, &AbstractClient::shadeableChanged, decoratedClient, &KDecoration2::DecoratedClient::shadeableChanged); connect(client, &AbstractClient::minimizeableChanged, decoratedClient, &KDecoration2::DecoratedClient::minimizeableChanged); connect(client, &AbstractClient::maximizeableChanged, decoratedClient, &KDecoration2::DecoratedClient::maximizeableChanged); connect(client, &AbstractClient::paletteChanged, decoratedClient, &KDecoration2::DecoratedClient::paletteChanged); + + connect(client, &AbstractClient::hasApplicationMenuChanged, decoratedClient, &KDecoration2::DecoratedClient::hasApplicationMenuChanged); + connect(client, &AbstractClient::applicationMenuActiveChanged, decoratedClient, &KDecoration2::DecoratedClient::applicationMenuActiveChanged); } DecoratedClientImpl::~DecoratedClientImpl() = default; void DecoratedClientImpl::signalShadeChange() { emit decoratedClient()->shadedChanged(m_client->isShade()); } #define DELEGATE(type, name, clientName) \ type DecoratedClientImpl::name() const \ { \ return m_client->clientName(); \ } #define DELEGATE2(type, name) DELEGATE(type, name, name) DELEGATE2(QString, caption) DELEGATE2(bool, isActive) DELEGATE2(bool, isCloseable) DELEGATE(bool, isMaximizeable, isMaximizable) DELEGATE(bool, isMinimizeable, isMinimizable) DELEGATE2(bool, isModal) DELEGATE(bool, isMoveable, isMovable) DELEGATE(bool, isResizeable, isResizable) DELEGATE2(bool, isShadeable) DELEGATE2(bool, providesContextHelp) DELEGATE2(int, desktop) DELEGATE2(bool, isOnAllDesktops) DELEGATE2(QPalette, palette) DELEGATE2(QIcon, icon) #undef DELEGATE2 #undef DELEGATE #define DELEGATE(type, name, clientName) \ type DecoratedClientImpl::name() const \ { \ return m_client->clientName(); \ } DELEGATE(bool, isKeepAbove, keepAbove) DELEGATE(bool, isKeepBelow, keepBelow) DELEGATE(bool, isShaded, isShade) DELEGATE(WId, windowId, windowId) DELEGATE(WId, decorationId, frameId) #undef DELEGATE #define DELEGATE(name, op) \ void DecoratedClientImpl::name() \ { \ Workspace::self()->performWindowOperation(m_client, Options::op); \ } DELEGATE(requestToggleShade, ShadeOp) DELEGATE(requestToggleOnAllDesktops, OnAllDesktopsOp) DELEGATE(requestToggleKeepAbove, KeepAboveOp) DELEGATE(requestToggleKeepBelow, KeepBelowOp) #undef DELEGATE #define DELEGATE(name, clientName) \ void DecoratedClientImpl::name() \ { \ m_client->clientName(); \ } DELEGATE(requestContextHelp, showContextHelp) DELEGATE(requestMinimize, minimize) #undef DELEGATE void DecoratedClientImpl::requestClose() { QMetaObject::invokeMethod(m_client, "closeWindow", Qt::QueuedConnection); } QColor DecoratedClientImpl::color(KDecoration2::ColorGroup group, KDecoration2::ColorRole role) const { auto dp = m_client->decorationPalette(); if (dp) { return dp->color(group, role); } return QColor(); } void DecoratedClientImpl::requestShowWindowMenu() { // TODO: add rect to requestShowWindowMenu Workspace::self()->showWindowMenu(QRect(Cursor::pos(), Cursor::pos()), m_client); } +void DecoratedClientImpl::requestShowApplicationMenu(const QRect &rect, int actionId) +{ + Workspace::self()->showApplicationMenu(rect, m_client, actionId); +} + +void DecoratedClientImpl::showApplicationMenu(int actionId) +{ + decoration()->showApplicationMenu(actionId); +} + void DecoratedClientImpl::requestToggleMaximization(Qt::MouseButtons buttons) { QMetaObject::invokeMethod(this, "delayedRequestToggleMaximization", Qt::QueuedConnection, Q_ARG(Options::WindowOperation, options->operationMaxButtonClick(buttons))); } void DecoratedClientImpl::delayedRequestToggleMaximization(Options::WindowOperation operation) { Workspace::self()->performWindowOperation(m_client, operation); } int DecoratedClientImpl::width() const { return m_clientSize.width(); } int DecoratedClientImpl::height() const { return m_clientSize.height(); } bool DecoratedClientImpl::isMaximizedVertically() const { return m_client->maximizeMode() & MaximizeVertical; } bool DecoratedClientImpl::isMaximized() const { return isMaximizedHorizontally() && isMaximizedVertically(); } bool DecoratedClientImpl::isMaximizedHorizontally() const { return m_client->maximizeMode() & MaximizeHorizontal; } Qt::Edges DecoratedClientImpl::adjacentScreenEdges() const { Qt::Edges edges; const AbstractClient::QuickTileMode mode = m_client->quickTileMode(); if (mode.testFlag(AbstractClient::QuickTileLeft)) { edges |= Qt::LeftEdge; if (!mode.testFlag(AbstractClient::QuickTileTop) && !mode.testFlag(AbstractClient::QuickTileBottom)) { // using complete side edges |= Qt::TopEdge | Qt::BottomEdge; } } if (mode.testFlag(AbstractClient::QuickTileTop)) { edges |= Qt::TopEdge; } if (mode.testFlag(AbstractClient::QuickTileRight)) { edges |= Qt::RightEdge; if (!mode.testFlag(AbstractClient::QuickTileTop) && !mode.testFlag(AbstractClient::QuickTileBottom)) { // using complete side edges |= Qt::TopEdge | Qt::BottomEdge; } } if (mode.testFlag(AbstractClient::QuickTileBottom)) { edges |= Qt::BottomEdge; } return edges; } +bool DecoratedClientImpl::hasApplicationMenu() const +{ + return m_client->hasApplicationMenu(); +} + +bool DecoratedClientImpl::isApplicationMenuActive() const +{ + return m_client->applicationMenuActive(); +} + void DecoratedClientImpl::createRenderer() { if (Compositor::self()->hasScene()) { m_renderer = Compositor::self()->scene()->createDecorationRenderer(this); } else { m_renderer = new X11Renderer(this); } } void DecoratedClientImpl::destroyRenderer() { delete m_renderer; m_renderer = nullptr; } } } diff --git a/decorations/decoratedclient.h b/decorations/decoratedclient.h index d31b1dfdd..e01a2e6a0 100644 --- a/decorations/decoratedclient.h +++ b/decorations/decoratedclient.h @@ -1,109 +1,115 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DECORATED_CLIENT_H #define KWIN_DECORATED_CLIENT_H #include "options.h" #include #include namespace KWin { class AbstractClient; namespace Decoration { class Renderer; -class DecoratedClientImpl : public QObject, public KDecoration2::DecoratedClientPrivate +class DecoratedClientImpl : public QObject, public KDecoration2::ApplicationMenuEnabledDecoratedClientPrivate { Q_OBJECT public: explicit DecoratedClientImpl(AbstractClient *client, KDecoration2::DecoratedClient *decoratedClient, KDecoration2::Decoration *decoration); virtual ~DecoratedClientImpl(); QString caption() const override; WId decorationId() const override; int desktop() const override; int height() const override; QIcon icon() const override; bool isActive() const override; bool isCloseable() const override; bool isKeepAbove() const override; bool isKeepBelow() const override; bool isMaximizeable() const override; bool isMaximized() const override; bool isMaximizedHorizontally() const override; bool isMaximizedVertically() const override; bool isMinimizeable() const override; bool isModal() const override; bool isMoveable() const override; bool isOnAllDesktops() const override; bool isResizeable() const override; bool isShadeable() const override; bool isShaded() const override; QPalette palette() const override; QColor color(KDecoration2::ColorGroup group, KDecoration2::ColorRole role) const override; bool providesContextHelp() const override; int width() const override; WId windowId() const override; Qt::Edges adjacentScreenEdges() const override; + bool hasApplicationMenu() const override; + bool isApplicationMenuActive() const override; + void requestClose() override; void requestContextHelp() override; void requestToggleMaximization(Qt::MouseButtons buttons) override; void requestMinimize() override; void requestShowWindowMenu() override; + void requestShowApplicationMenu(const QRect &rect, int actionId) override; void requestToggleKeepAbove() override; void requestToggleKeepBelow() override; void requestToggleOnAllDesktops() override; void requestToggleShade() override; + void showApplicationMenu(int actionId); + AbstractClient *client() { return m_client; } Renderer *renderer() { return m_renderer; } void destroyRenderer(); KDecoration2::DecoratedClient *decoratedClient() { return KDecoration2::DecoratedClientPrivate::client(); } void signalShadeChange(); private Q_SLOTS: void delayedRequestToggleMaximization(Options::WindowOperation operation); private: void createRenderer(); AbstractClient *m_client; QSize m_clientSize; Renderer *m_renderer; QMetaObject::Connection m_compositorToggledConnection; }; } } #endif diff --git a/decorations/settings.cpp b/decorations/settings.cpp index c1eb22c02..92deb2519 100644 --- a/decorations/settings.cpp +++ b/decorations/settings.cpp @@ -1,189 +1,190 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "settings.h" // KWin #include "composite.h" #include "virtualdesktops.h" #include "workspace.h" #include #include #include namespace KWin { namespace Decoration { SettingsImpl::SettingsImpl(KDecoration2::DecorationSettings *parent) : QObject() , DecorationSettingsPrivate(parent) , m_borderSize(KDecoration2::BorderSize::Normal) { readSettings(); auto c = connect(Compositor::self(), &Compositor::compositingToggled, parent, &KDecoration2::DecorationSettings::alphaChannelSupportedChanged); connect(VirtualDesktopManager::self(), &VirtualDesktopManager::countChanged, this, [parent](uint previous, uint current) { if (previous != 1 && current != 1) { return; } emit parent->onAllDesktopsAvailableChanged(current > 1); } ); // prevent changes in Decoration due to Compositor being destroyed connect(Compositor::self(), &Compositor::aboutToDestroy, this, [this, c] { disconnect(c); } ); connect(Workspace::self(), &Workspace::configChanged, this, &SettingsImpl::readSettings); } SettingsImpl::~SettingsImpl() = default; bool SettingsImpl::isAlphaChannelSupported() const { return Compositor::self()->compositing(); } bool SettingsImpl::isOnAllDesktopsAvailable() const { return VirtualDesktopManager::self()->count() > 1; } bool SettingsImpl::isCloseOnDoubleClickOnMenu() const { return m_closeDoubleClickMenu; } static QHash s_buttonNames; static void initButtons() { if (!s_buttonNames.isEmpty()) { return; } s_buttonNames[KDecoration2::DecorationButtonType::Menu] = QChar('M'); s_buttonNames[KDecoration2::DecorationButtonType::ApplicationMenu] = QChar('N'); s_buttonNames[KDecoration2::DecorationButtonType::OnAllDesktops] = QChar('S'); s_buttonNames[KDecoration2::DecorationButtonType::ContextHelp] = QChar('H'); s_buttonNames[KDecoration2::DecorationButtonType::Minimize] = QChar('I'); s_buttonNames[KDecoration2::DecorationButtonType::Maximize] = QChar('A'); s_buttonNames[KDecoration2::DecorationButtonType::Close] = QChar('X'); s_buttonNames[KDecoration2::DecorationButtonType::KeepAbove] = QChar('F'); s_buttonNames[KDecoration2::DecorationButtonType::KeepBelow] = QChar('B'); s_buttonNames[KDecoration2::DecorationButtonType::Shade] = QChar('L'); } static QString buttonsToString(const QVector &buttons) { auto buttonToString = [](KDecoration2::DecorationButtonType button) -> QChar { const auto it = s_buttonNames.constFind(button); if (it != s_buttonNames.constEnd()) { return it.value(); } return QChar(); }; QString ret; for (auto button : buttons) { ret.append(buttonToString(button)); } return ret; } QVector< KDecoration2::DecorationButtonType > SettingsImpl::readDecorationButtons(const KConfigGroup &config, const char *key, const QVector< KDecoration2::DecorationButtonType > &defaultValue) const { initButtons(); auto buttonsFromString = [](const QString &buttons) -> QVector { QVector ret; for (auto it = buttons.begin(); it != buttons.end(); ++it) { for (auto it2 = s_buttonNames.constBegin(); it2 != s_buttonNames.constEnd(); ++it2) { if (it2.value() == (*it)) { ret << it2.key(); } } } return ret; }; return buttonsFromString(config.readEntry(key, buttonsToString(defaultValue))); } static KDecoration2::BorderSize stringToSize(const QString &name) { static const QMap s_sizes = QMap({ {QStringLiteral("None"), KDecoration2::BorderSize::None}, {QStringLiteral("NoSides"), KDecoration2::BorderSize::NoSides}, {QStringLiteral("Tiny"), KDecoration2::BorderSize::Tiny}, {QStringLiteral("Normal"), KDecoration2::BorderSize::Normal}, {QStringLiteral("Large"), KDecoration2::BorderSize::Large}, {QStringLiteral("VeryLarge"), KDecoration2::BorderSize::VeryLarge}, {QStringLiteral("Huge"), KDecoration2::BorderSize::Huge}, {QStringLiteral("VeryHuge"), KDecoration2::BorderSize::VeryHuge}, {QStringLiteral("Oversized"), KDecoration2::BorderSize::Oversized} }); auto it = s_sizes.constFind(name); if (it == s_sizes.constEnd()) { // non sense values are interpreted just like normal return KDecoration2::BorderSize::Normal; } return it.value(); } void SettingsImpl::readSettings() { KConfigGroup config = kwinApp()->config()->group(QStringLiteral("org.kde.kdecoration2")); const auto &left = readDecorationButtons(config, "ButtonsOnLeft", QVector({ KDecoration2::DecorationButtonType::Menu, + KDecoration2::DecorationButtonType::ApplicationMenu, KDecoration2::DecorationButtonType::OnAllDesktops })); if (left != m_leftButtons) { m_leftButtons = left; emit decorationSettings()->decorationButtonsLeftChanged(m_leftButtons); } const auto &right = readDecorationButtons(config, "ButtonsOnRight", QVector({ KDecoration2::DecorationButtonType::ContextHelp, KDecoration2::DecorationButtonType::Minimize, KDecoration2::DecorationButtonType::Maximize, KDecoration2::DecorationButtonType::Close })); if (right != m_rightButtons) { m_rightButtons = right; emit decorationSettings()->decorationButtonsRightChanged(m_rightButtons); } const bool close = config.readEntry("CloseOnDoubleClickOnMenu", false); if (close != m_closeDoubleClickMenu) { m_closeDoubleClickMenu = close; emit decorationSettings()->closeOnDoubleClickOnMenuChanged(m_closeDoubleClickMenu); } const auto size = stringToSize(config.readEntry("BorderSize", QStringLiteral("Normal"))); if (size != m_borderSize) { m_borderSize = size; emit decorationSettings()->borderSizeChanged(m_borderSize); } emit decorationSettings()->reconfigured(); } } } diff --git a/events.cpp b/events.cpp index 9740e493c..d100747a1 100644 --- a/events.cpp +++ b/events.cpp @@ -1,1536 +1,1540 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to handling incoming events. */ #include "client.h" #include "cursor.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #include "atoms.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "group.h" #include "overlaywindow.h" #include "rules.h" #include "unmanaged.h" #include "useractions.h" #include "effects.h" #include "screenedge.h" #include "screens.h" #include "xcbutils.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef XCB_ICCCM_FOUND #include #endif #include "composite.h" #include "x11eventfilter.h" #include "wayland_server.h" #include #ifndef XCB_GE_GENERIC #define XCB_GE_GENERIC 35 typedef struct xcb_ge_generic_event_t { uint8_t response_type; /**< */ uint8_t extension; /**< */ uint16_t sequence; /**< */ uint32_t length; /**< */ uint16_t event_type; /**< */ uint8_t pad0[22]; /**< */ uint32_t full_sequence; /**< */ } xcb_ge_generic_event_t; #endif namespace KWin { extern int currentRefreshRate(); // **************************************** // Workspace // **************************************** static xcb_window_t findEventWindow(xcb_generic_event_t *event) { const uint8_t eventType = event->response_type & ~0x80; switch(eventType) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: return reinterpret_cast(event)->event; case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: return reinterpret_cast(event)->event; case XCB_MOTION_NOTIFY: return reinterpret_cast(event)->event; case XCB_ENTER_NOTIFY: case XCB_LEAVE_NOTIFY: return reinterpret_cast(event)->event; case XCB_FOCUS_IN: case XCB_FOCUS_OUT: return reinterpret_cast(event)->event; case XCB_EXPOSE: return reinterpret_cast(event)->window; case XCB_GRAPHICS_EXPOSURE: return reinterpret_cast(event)->drawable; case XCB_NO_EXPOSURE: return reinterpret_cast(event)->drawable; case XCB_VISIBILITY_NOTIFY: return reinterpret_cast(event)->window; case XCB_CREATE_NOTIFY: return reinterpret_cast(event)->window; case XCB_DESTROY_NOTIFY: return reinterpret_cast(event)->window; case XCB_UNMAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_MAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_MAP_REQUEST: return reinterpret_cast(event)->window; case XCB_REPARENT_NOTIFY: return reinterpret_cast(event)->window; case XCB_CONFIGURE_NOTIFY: return reinterpret_cast(event)->window; case XCB_CONFIGURE_REQUEST: return reinterpret_cast(event)->window; case XCB_GRAVITY_NOTIFY: return reinterpret_cast(event)->window; case XCB_RESIZE_REQUEST: return reinterpret_cast(event)->window; case XCB_CIRCULATE_NOTIFY: case XCB_CIRCULATE_REQUEST: return reinterpret_cast(event)->window; case XCB_PROPERTY_NOTIFY: return reinterpret_cast(event)->window; case XCB_COLORMAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_CLIENT_MESSAGE: return reinterpret_cast(event)->window; default: // extension handling if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { return reinterpret_cast(event)->affected_window; } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) { return reinterpret_cast(event)->drawable; } return XCB_WINDOW_NONE; } } QVector s_xcbEerrors({ QByteArrayLiteral("Success"), QByteArrayLiteral("BadRequest"), QByteArrayLiteral("BadValue"), QByteArrayLiteral("BadWindow"), QByteArrayLiteral("BadPixmap"), QByteArrayLiteral("BadAtom"), QByteArrayLiteral("BadCursor"), QByteArrayLiteral("BadFont"), QByteArrayLiteral("BadMatch"), QByteArrayLiteral("BadDrawable"), QByteArrayLiteral("BadAccess"), QByteArrayLiteral("BadAlloc"), QByteArrayLiteral("BadColor"), QByteArrayLiteral("BadGC"), QByteArrayLiteral("BadIDChoice"), QByteArrayLiteral("BadName"), QByteArrayLiteral("BadLength"), QByteArrayLiteral("BadImplementation"), QByteArrayLiteral("Unknown")}); void Workspace::registerEventFilter(X11EventFilter *filter) { if (filter->isGenericEvent()) m_genericEventFilters.append(filter); else m_eventFilters.append(filter); } void Workspace::unregisterEventFilter(X11EventFilter *filter) { if (filter->isGenericEvent()) m_genericEventFilters.removeOne(filter); else m_eventFilters.removeOne(filter); } /*! Handles workspace specific XCB event */ bool Workspace::workspaceEvent(xcb_generic_event_t *e) { const uint8_t eventType = e->response_type & ~0x80; if (!eventType) { // let's check whether it's an error from one of the extensions KWin uses xcb_generic_error_t *error = reinterpret_cast(e); const QVector extensions = Xcb::Extensions::self()->extensions(); for (const auto &extension : extensions) { if (error->major_code == extension.majorOpcode) { QByteArray errorName; if (error->error_code < s_xcbEerrors.size()) { errorName = s_xcbEerrors.at(error->error_code); } else if (error->error_code >= extension.errorBase) { const int index = error->error_code - extension.errorBase; if (index >= 0 && index < extension.errorCodes.size()) { errorName = extension.errorCodes.at(index); } } if (errorName.isEmpty()) { errorName = QByteArrayLiteral("Unknown"); } qCWarning(KWIN_CORE, "XCB error: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d (%s)", int(error->error_code), errorName.constData(), int(error->sequence), int(error->resource_id), int(error->major_code), extension.name.constData(), int(error->minor_code), extension.opCodes.size() > error->minor_code ? extension.opCodes.at(error->minor_code).constData() : "Unknown"); return true; } } return false; } if (eventType == XCB_GE_GENERIC) { xcb_ge_generic_event_t *ge = reinterpret_cast(e); foreach (X11EventFilter *filter, m_genericEventFilters) { if (filter->extension() == ge->extension && filter->genericEventTypes().contains(ge->event_type) && filter->event(e)) { return true; } } } else { foreach (X11EventFilter *filter, m_eventFilters) { if (filter->eventTypes().contains(eventType) && filter->event(e)) { return true; } } } if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab() && (eventType == XCB_KEY_PRESS || eventType == XCB_KEY_RELEASE)) return false; // let Qt process it, it'll be intercepted again in eventFilter() if (eventType == XCB_PROPERTY_NOTIFY || eventType == XCB_CLIENT_MESSAGE) { NET::Properties dirtyProtocols; NET::Properties2 dirtyProtocols2; rootInfo()->event(e, &dirtyProtocols, &dirtyProtocols2); if (dirtyProtocols & NET::DesktopNames) VirtualDesktopManager::self()->save(); if (dirtyProtocols2 & NET::WM2DesktopLayout) VirtualDesktopManager::self()->updateLayout(); } // events that should be handled before Clients can get them switch (eventType) { case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: { was_user_interaction = true; auto *mouseEvent = reinterpret_cast(e); #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isGrabbed()) { return TabBox::TabBox::self()->handleMouseEvent(mouseEvent); } #endif if (effects && static_cast(effects)->checkInputWindowEvent(mouseEvent)) { return true; } break; } case XCB_MOTION_NOTIFY: { if (kwinApp()->operationMode() != Application::OperationModeX11) { // ignore X11 pointer events generated on X windows if we are not on X return true; } auto *mouseEvent = reinterpret_cast(e); const QPoint rootPos(mouseEvent->root_x, mouseEvent->root_y); #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isGrabbed()) { ScreenEdges::self()->check(rootPos, QDateTime::fromMSecsSinceEpoch(xTime()), true); return TabBox::TabBox::self()->handleMouseEvent(mouseEvent); } #endif if (effects && static_cast(effects)->checkInputWindowEvent(mouseEvent)) { return true; } if (QWidget::mouseGrabber()) { ScreenEdges::self()->check(rootPos, QDateTime::fromMSecsSinceEpoch(xTime()), true); } else { ScreenEdges::self()->check(rootPos, QDateTime::fromMSecsSinceEpoch(mouseEvent->time)); } break; } case XCB_KEY_PRESS: { was_user_interaction = true; int keyQt; xcb_key_press_event_t *event = reinterpret_cast(e); KKeyServer::xcbKeyPressEventToQt(event, &keyQt); // qDebug() << "Workspace::keyPress( " << keyQt << " )"; if (Client *c = dynamic_cast(movingClient)) { c->keyPressEvent(keyQt, event->time); return true; } #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isGrabbed()) { TabBox::TabBox::self()->keyPress(keyQt); return true; } #endif break; } case XCB_KEY_RELEASE: was_user_interaction = true; #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isGrabbed()) { TabBox::TabBox::self()->keyRelease(reinterpret_cast(e)); return true; } #endif break; case XCB_CONFIGURE_NOTIFY: if (reinterpret_cast(e)->event == rootWindow()) x_stacking_dirty = true; break; }; const xcb_window_t eventWindow = findEventWindow(e); if (eventWindow != XCB_WINDOW_NONE) { if (Client* c = findClient(Predicate::WindowMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Client* c = findClient(Predicate::WrapperIdMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Client* c = findClient(Predicate::FrameIdMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Client *c = findClient(Predicate::InputIdMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Unmanaged* c = findUnmanaged(eventWindow)) { if (c->windowEvent(e)) return true; } else { // We want to pass root window property events to effects if (eventType == XCB_PROPERTY_NOTIFY) { auto *event = reinterpret_cast(e); if (event->window == rootWindow()) { emit propertyNotify(event->atom); } } } } if (Client *c = dynamic_cast(movingClient)) { if (eventType == XCB_BUTTON_PRESS || eventType == XCB_BUTTON_RELEASE || eventType == XCB_MOTION_NOTIFY) { if (c->moveResizeGrabWindow() == reinterpret_cast(e)->event && c->windowEvent(e)) { return true; } } } switch (eventType) { case XCB_CREATE_NOTIFY: { const auto *event = reinterpret_cast(e); if (event->parent == rootWindow() && !QWidget::find(event->window) && !event->override_redirect) { // see comments for allowClientActivation() updateXTime(); const xcb_timestamp_t t = xTime(); xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, event->window, atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 32, 1, &t); } break; } case XCB_UNMAP_NOTIFY: { const auto *event = reinterpret_cast(e); return (event->event != event->window); // hide wm typical event from Qt } case XCB_REPARENT_NOTIFY: { //do not confuse Qt with these events. After all, _we_ are the //window manager who does the reparenting. return true; } case XCB_DESTROY_NOTIFY: { return false; } case XCB_MAP_REQUEST: { updateXTime(); const auto *event = reinterpret_cast(e); if (Client* c = findClient(Predicate::WindowMatch, event->window)) { // e->xmaprequest.window is different from e->xany.window // TODO this shouldn't be necessary now c->windowEvent(e); FocusChain::self()->update(c, FocusChain::Update); } else if ( true /*|| e->xmaprequest.parent != root */ ) { // NOTICE don't check for the parent being the root window, this breaks when some app unmaps // a window, changes something and immediately maps it back, without giving KWin // a chance to reparent it back to root // since KWin can get MapRequest only for root window children and // children of WindowWrapper (=clients), the check is AFAIK useless anyway // NOTICE: The save-set support in Client::mapRequestEvent() actually requires that // this code doesn't check the parent to be root. if (!createClient(event->window, false)) { xcb_map_window(connection(), event->window); const uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(connection(), event->window, XCB_CONFIG_WINDOW_STACK_MODE, values); } } return true; } case XCB_MAP_NOTIFY: { const auto *event = reinterpret_cast(e); if (event->override_redirect) { Unmanaged* c = findUnmanaged(event->window); if (c == NULL) c = createUnmanaged(event->window); if (c) return c->windowEvent(e); } return (event->event != event->window); // hide wm typical event from Qt } case XCB_ENTER_NOTIFY: { if (QWhatsThis::inWhatsThisMode()) { QWidget* w = QWidget::find(reinterpret_cast(e)->event); if (w) QWhatsThis::leaveWhatsThisMode(); } if (ScreenEdges::self()->isEntered(reinterpret_cast(e))) return true; break; } case XCB_LEAVE_NOTIFY: { if (!QWhatsThis::inWhatsThisMode()) break; // TODO is this cliente ever found, given that client events are searched above? const auto *event = reinterpret_cast(e); Client* c = findClient(Predicate::FrameIdMatch, event->event); if (c && event->detail != XCB_NOTIFY_DETAIL_INFERIOR) QWhatsThis::leaveWhatsThisMode(); break; } case XCB_CONFIGURE_REQUEST: { const auto *event = reinterpret_cast(e); if (event->parent == rootWindow()) { uint32_t values[5] = { 0, 0, 0, 0, 0}; const uint32_t value_mask = event->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH); int i = 0; if (value_mask & XCB_CONFIG_WINDOW_X) { values[i++] = event->x; } if (value_mask & XCB_CONFIG_WINDOW_Y) { values[i++] = event->y; } if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { values[i++] = event->width; } if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { values[i++] = event->height; } if (value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { values[i++] = event->border_width; } xcb_configure_window(connection(), event->window, value_mask, values); return true; } break; } case XCB_FOCUS_IN: { const auto *event = reinterpret_cast(e); if (event->event == rootWindow() && (event->detail == XCB_NOTIFY_DETAIL_NONE || event->detail == XCB_NOTIFY_DETAIL_POINTER_ROOT || event->detail == XCB_NOTIFY_DETAIL_INFERIOR)) { Xcb::CurrentInput currentInput; updateXTime(); // focusToNull() uses xTime(), which is old now (FocusIn has no timestamp) // it seems we can "loose" focus reversions when the closing client hold a grab // => catch the typical pattern (though we don't want the focus on the root anyway) #348935 const bool lostFocusPointerToRoot = currentInput->focus == rootWindow() && event->detail == XCB_NOTIFY_DETAIL_INFERIOR; if (!currentInput.isNull() && (currentInput->focus == XCB_WINDOW_NONE || currentInput->focus == XCB_INPUT_FOCUS_POINTER_ROOT || lostFocusPointerToRoot)) { //kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" ; AbstractClient *c = mostRecentlyActivatedClient(); if (c != NULL) requestFocus(c, true); else if (activateNextClient(NULL)) ; // ok, activated else focusToNull(); } } } // fall through case XCB_FOCUS_OUT: return true; // always eat these, they would tell Qt that KWin is the active app case XCB_CLIENT_MESSAGE: if (ScreenEdges::self()->isEntered(reinterpret_cast(e))) return true; break; case XCB_EXPOSE: { const auto *event = reinterpret_cast(e); if (compositing() && (event->window == rootWindow() // root window needs repainting || (m_compositor->overlayWindow() != XCB_WINDOW_NONE && event->window == m_compositor->overlayWindow()))) { // overlay needs repainting m_compositor->addRepaint(event->x, event->y, event->width, event->height); } break; } case XCB_VISIBILITY_NOTIFY: { const auto *event = reinterpret_cast(e); if (compositing() && m_compositor->overlayWindow() != XCB_WINDOW_NONE && event->window == m_compositor->overlayWindow()) { bool was_visible = m_compositor->isOverlayWindowVisible(); m_compositor->setOverlayWindowVisibility((event->state != XCB_VISIBILITY_FULLY_OBSCURED)); if (!was_visible && m_compositor->isOverlayWindowVisible()) { // hack for #154825 m_compositor->addRepaintFull(); QTimer::singleShot(2000, m_compositor, SLOT(addRepaintFull())); } m_compositor->scheduleRepaint(); } break; } default: if (eventType == Xcb::Extensions::self()->randrNotifyEvent() && Xcb::Extensions::self()->isRandrAvailable()) { auto *event = reinterpret_cast(e); xcb_screen_t *screen = defaultScreen(); if (event->rotation & (XCB_RANDR_ROTATION_ROTATE_90 | XCB_RANDR_ROTATION_ROTATE_270)) { screen->width_in_pixels = event->height; screen->height_in_pixels = event->width; screen->width_in_millimeters = event->mheight; screen->height_in_millimeters = event->mwidth; } else { screen->width_in_pixels = event->width; screen->height_in_pixels = event->height; screen->width_in_millimeters = event->mwidth; screen->height_in_millimeters = event->mheight; } if (compositing()) { // desktopResized() should take care of when the size or // shape of the desktop has changed, but we also want to // catch refresh rate changes if (m_compositor->xrrRefreshRate() != currentRefreshRate()) m_compositor->setCompositeResetTimer(0); } } else if (eventType == Xcb::Extensions::self()->syncAlarmNotifyEvent() && Xcb::Extensions::self()->isSyncAvailable()) { for (Client *c : clients) c->syncEvent(reinterpret_cast< xcb_sync_alarm_notify_event_t* >(e)); for (Client *c : desktops) c->syncEvent(reinterpret_cast< xcb_sync_alarm_notify_event_t* >(e)); } else if (eventType == Xcb::Extensions::self()->fixesCursorNotifyEvent() && Xcb::Extensions::self()->isFixesAvailable()) { Cursor::self()->notifyCursorChanged(reinterpret_cast(e)->cursor_serial); } break; } return false; } // Used only to filter events that need to be processed by Qt first // (e.g. keyboard input to be composed), otherwise events are // handle by the XEvent filter above bool Workspace::workspaceEvent(QEvent* e) { if ((e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride) && effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(static_cast< QKeyEvent* >(e)); return true; } return false; } // **************************************** // Client // **************************************** /*! General handler for XEvents concerning the client window */ bool Client::windowEvent(xcb_generic_event_t *e) { if (findEventWindow(e) == window()) { // avoid doing stuff on frame or wrapper NET::Properties dirtyProperties; NET::Properties2 dirtyProperties2; double old_opacity = opacity(); info->event(e, &dirtyProperties, &dirtyProperties2); // pass through the NET stuff if ((dirtyProperties & NET::WMName) != 0) fetchName(); if ((dirtyProperties & NET::WMIconName) != 0) fetchIconicName(); if ((dirtyProperties & NET::WMStrut) != 0 || (dirtyProperties2 & NET::WM2ExtendedStrut) != 0) { workspace()->updateClientArea(); } if ((dirtyProperties & NET::WMIcon) != 0) getIcons(); // Note there's a difference between userTime() and info->userTime() // info->userTime() is the value of the property, userTime() also includes // updates of the time done by KWin (ButtonPress on windowrapper etc.). if ((dirtyProperties2 & NET::WM2UserTime) != 0) { workspace()->setWasUserInteraction(); updateUserTime(info->userTime()); } if ((dirtyProperties2 & NET::WM2StartupId) != 0) startupIdChanged(); if (dirtyProperties2 & NET::WM2Opacity) { if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } else { // forward to the frame if there's possibly another compositing manager running NETWinInfo i(connection(), frameId(), rootWindow(), 0, 0); i.setOpacity(info->opacity()); } } if (dirtyProperties2 & NET::WM2FrameOverlap) { // ### Inform the decoration } if (dirtyProperties2.testFlag(NET::WM2WindowRole)) { emit windowRoleChanged(); } if (dirtyProperties2.testFlag(NET::WM2WindowClass)) { getResourceClass(); } if (dirtyProperties2.testFlag(NET::WM2BlockCompositing)) { setBlockingCompositing(info->isBlockingCompositing()); } if (dirtyProperties2.testFlag(NET::WM2GroupLeader)) { checkGroup(); updateAllowedActions(); // Group affects isMinimizable() } if (dirtyProperties2.testFlag(NET::WM2Urgency)) { updateUrgency(); } if (dirtyProperties2 & NET::WM2OpaqueRegion) { getWmOpaqueRegion(); } if (dirtyProperties2 & NET::WM2DesktopFileName) { setDesktopFileName(QByteArray(info->desktopFileName())); } } const uint8_t eventType = e->response_type & ~0x80; switch(eventType) { case XCB_UNMAP_NOTIFY: unmapNotifyEvent(reinterpret_cast(e)); break; case XCB_DESTROY_NOTIFY: destroyNotifyEvent(reinterpret_cast(e)); break; case XCB_MAP_REQUEST: // this one may pass the event to workspace return mapRequestEvent(reinterpret_cast(e)); case XCB_CONFIGURE_REQUEST: configureRequestEvent(reinterpret_cast(e)); break; case XCB_PROPERTY_NOTIFY: propertyNotifyEvent(reinterpret_cast(e)); break; case XCB_KEY_PRESS: updateUserTime(reinterpret_cast(e)->time); workspace()->setWasUserInteraction(); break; case XCB_BUTTON_PRESS: { const auto *event = reinterpret_cast(e); updateUserTime(event->time); workspace()->setWasUserInteraction(); buttonPressEvent(event->event, event->detail, event->state, event->event_x, event->event_y, event->root_x, event->root_y, event->time); break; } case XCB_KEY_RELEASE: // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window break; case XCB_BUTTON_RELEASE: { const auto *event = reinterpret_cast(e); // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window buttonReleaseEvent(event->event, event->detail, event->state, event->event_x, event->event_y, event->root_x, event->root_y); break; } case XCB_MOTION_NOTIFY: { const auto *event = reinterpret_cast(e); motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y)); break; } case XCB_ENTER_NOTIFY: { auto *event = reinterpret_cast(e); enterNotifyEvent(event); // MotionNotify is guaranteed to be generated only if the mouse // move start and ends in the window; for cases when it only // starts or only ends there, Enter/LeaveNotify are generated. // Fake a MotionEvent in such cases to make handle of mouse // events simpler (Qt does that too). motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y)); break; } case XCB_LEAVE_NOTIFY: { auto *event = reinterpret_cast(e); motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); leaveNotifyEvent(event); // not here, it'd break following enter notify handling // workspace()->updateFocusMousePosition( QPoint( e->xcrossing.x_root, e->xcrossing.y_root )); break; } case XCB_FOCUS_IN: focusInEvent(reinterpret_cast(e)); break; case XCB_FOCUS_OUT: focusOutEvent(reinterpret_cast(e)); break; case XCB_REPARENT_NOTIFY: break; case XCB_CLIENT_MESSAGE: clientMessageEvent(reinterpret_cast(e)); break; case XCB_EXPOSE: { xcb_expose_event_t *event = reinterpret_cast(e); if (event->window == frameId() && !Compositor::self()->isActive()) { // TODO: only repaint required areas triggerDecorationRepaint(); } break; } default: if (eventType == Xcb::Extensions::self()->shapeNotifyEvent() && reinterpret_cast(e)->affected_window == window()) { detectShape(window()); // workaround for #19644 updateShape(); } if (eventType == Xcb::Extensions::self()->damageNotifyEvent() && reinterpret_cast(e)->drawable == frameId()) damageNotifyEvent(); break; } return true; // eat all events } /*! Handles map requests of the client window */ bool Client::mapRequestEvent(xcb_map_request_event_t *e) { if (e->window != window()) { // Special support for the save-set feature, which is a bit broken. // If there's a window from one client embedded in another one, // e.g. using XEMBED, and the embedder suddenly loses its X connection, // save-set will reparent the embedded window to its closest ancestor // that will remains. Unfortunately, with reparenting window managers, // this is not the root window, but the frame (or in KWin's case, // it's the wrapper for the client window). In this case, // the wrapper will get ReparentNotify for a window it won't know, // which will be ignored, and then it gets MapRequest, as save-set // always maps. Returning true here means that Workspace::workspaceEvent() // will handle this MapRequest and manage this window (i.e. act as if // it was reparented to root window). if (e->parent == wrapperId()) return false; return true; // no messing with frame etc. } // also copied in clientMessage() if (isMinimized()) unminimize(); if (isShade()) setShade(ShadeNone); if (!isOnCurrentDesktop()) { if (workspace()->allowClientActivation(this)) workspace()->activateClient(this); else demandAttention(); } return true; } /*! Handles unmap notify events of the client window */ void Client::unmapNotifyEvent(xcb_unmap_notify_event_t *e) { if (e->window != window()) return; if (e->event != wrapperId()) { // most probably event from root window when initially reparenting bool ignore = true; if (e->event == rootWindow() && (e->response_type & 0x80)) ignore = false; // XWithdrawWindow() if (ignore) return; } // check whether this is result of an XReparentWindow - client then won't be parented by wrapper // in this case do not release the client (causes reparent to root, removal from saveSet and what not) // but just destroy the client Xcb::Tree tree(m_client); xcb_window_t daddy = tree.parent(); if (daddy == m_wrapper) { releaseWindow(); // unmapped from a regular client state } else { destroyClient(); // the client was moved to some other parent } } void Client::destroyNotifyEvent(xcb_destroy_notify_event_t *e) { if (e->window != window()) return; destroyClient(); } /*! Handles client messages for the client window */ void Client::clientMessageEvent(xcb_client_message_event_t *e) { Toplevel::clientMessageEvent(e); if (e->window != window()) return; // ignore frame/wrapper // WM_STATE if (e->type == atoms->wm_change_state) { if (e->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) minimize(); return; } } /*! Handles configure requests of the client window */ void Client::configureRequestEvent(xcb_configure_request_event_t *e) { if (e->window != window()) return; // ignore frame/wrapper if (isResize() || isMove()) return; // we have better things to do right now if (fullscreen_mode == FullScreenNormal) { // refuse resizing of fullscreen windows // but allow resizing fullscreen hacks in order to let them cancel fullscreen mode sendSyntheticConfigureNotify(); return; } if (isSplash()) { // no manipulations with splashscreens either sendSyntheticConfigureNotify(); return; } if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { // first, get rid of a window border m_client.setBorderWidth(0); } if (e->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_WIDTH)) configureRequest(e->value_mask, e->x, e->y, e->width, e->height, 0, false); if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) restackWindow(e->sibling, e->stack_mode, NET::FromApplication, userTime(), false); // Sending a synthetic configure notify always is fine, even in cases where // the ICCCM doesn't require this - it can be though of as 'the WM decided to move // the window later'. The client should not cause that many configure request, // so this should not have any significant impact. With user moving/resizing // the it should be optimized though (see also Client::setGeometry()/plainResize()/move()). sendSyntheticConfigureNotify(); // SELI TODO accept configure requests for isDesktop windows (because kdesktop // may get XRANDR resize event before kwin), but check it's still at the bottom? } /*! Handles property changes of the client window */ void Client::propertyNotifyEvent(xcb_property_notify_event_t *e) { Toplevel::propertyNotifyEvent(e); if (e->window != window()) return; // ignore frame/wrapper switch(e->atom) { case XCB_ATOM_WM_NORMAL_HINTS: getWmNormalHints(); break; case XCB_ATOM_WM_NAME: fetchName(); break; case XCB_ATOM_WM_ICON_NAME: fetchIconicName(); break; case XCB_ATOM_WM_TRANSIENT_FOR: readTransient(); break; case XCB_ATOM_WM_HINTS: getIcons(); // because KWin::icon() uses WMHints as fallback break; default: if (e->atom == atoms->motif_wm_hints) { getMotifHints(); } else if (e->atom == atoms->net_wm_sync_request_counter) getSyncCounter(); else if (e->atom == atoms->activities) checkActivities(); else if (e->atom == atoms->kde_first_in_window_list) updateFirstInTabBox(); else if (e->atom == atoms->kde_color_sheme) updateColorScheme(); else if (e->atom == atoms->kde_screen_edge_show) updateShowOnScreenEdge(); else if (e->atom == atoms->gtk_frame_extents) detectGtkFrameExtents(); + else if (e->atom == atoms->kde_net_wm_appmenu_service_name) + checkApplicationMenuServiceName(); + else if (e->atom == atoms->kde_net_wm_appmenu_object_path) + checkApplicationMenuObjectPath(); break; } } void Client::enterNotifyEvent(xcb_enter_notify_event_t *e) { if (e->event != frameId()) return; // care only about entering the whole frame #define MOUSE_DRIVEN_FOCUS (!options->focusPolicyIsReasonable() || \ (options->focusPolicy() == Options::FocusFollowsMouse && options->isNextFocusPrefersMouse())) if (e->mode == XCB_NOTIFY_MODE_NORMAL || (e->mode == XCB_NOTIFY_MODE_UNGRAB && MOUSE_DRIVEN_FOCUS)) { if (options->isShadeHover()) { cancelShadeHoverTimer(); if (isShade()) { shadeHoverTimer = new QTimer(this); connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeHover())); shadeHoverTimer->setSingleShot(true); shadeHoverTimer->start(options->shadeHoverInterval()); } } #undef MOUSE_DRIVEN_FOCUS enterEvent(QPoint(e->root_x, e->root_y)); return; } } void Client::leaveNotifyEvent(xcb_leave_notify_event_t *e) { if (e->event != frameId()) return; // care only about leaving the whole frame if (e->mode == XCB_NOTIFY_MODE_NORMAL) { if (!isMoveResizePointerButtonDown()) { setMoveResizePointerMode(PositionCenter); updateCursor(); } bool lostMouse = !rect().contains(QPoint(e->event_x, e->event_y)); // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event // comes after leaving the rect) - so lets check if the pointer is really outside the window // TODO this still sucks if a window appears above this one - it should lose the mouse // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :( // (repeat after me 'AARGHL!') if (!lostMouse && e->detail != XCB_NOTIFY_DETAIL_INFERIOR) { Xcb::Pointer pointer(frameId()); if (!pointer || !pointer->same_screen || pointer->child == XCB_WINDOW_NONE) { // really lost the mouse lostMouse = true; } } if (lostMouse) { leaveEvent(); cancelShadeHoverTimer(); if (shade_mode == ShadeHover && !isMoveResize() && !isMoveResizePointerButtonDown()) { shadeHoverTimer = new QTimer(this); connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeUnhover())); shadeHoverTimer->setSingleShot(true); shadeHoverTimer->start(options->shadeHoverInterval()); } if (isDecorated()) { // sending a move instead of a leave. With leave we need to send proper coords, with move it's handled internally QHoverEvent leaveEvent(QEvent::HoverMove, QPointF(-1, -1), QPointF(-1, -1), Qt::NoModifier); QCoreApplication::sendEvent(decoration(), &leaveEvent); } } if (options->focusPolicy() == Options::FocusStrictlyUnderMouse && isActive() && lostMouse) { workspace()->requestDelayFocus(0); } return; } } #define XCapL KKeyServer::modXLock() #define XNumL KKeyServer::modXNumLock() #define XScrL KKeyServer::modXScrollLock() void Client::grabButton(int modifier) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for (int i = 0; i < 8; ++i) m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, modifier | mods[ i ]); } void Client::ungrabButton(int modifier) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for (int i = 0; i < 8; ++i) m_wrapper.ungrabButton(modifier | mods[ i ]); } #undef XCapL #undef XNumL #undef XScrL /* Releases the passive grab for some modifier combinations when a window becomes active. This helps broken X programs that missinterpret LeaveNotify events in grab mode to work properly (Motif, AWT, Tk, ...) */ void Client::updateMouseGrab() { if (workspace()->globalShortcutsDisabled()) { m_wrapper.ungrabButton(); // keep grab for the simple click without modifiers if needed (see below) bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; if (!(!options->isClickRaise() || not_obscured)) grabButton(XCB_NONE); return; } if (isActive() && !TabBox::TabBox::self()->forcedGlobalMouseGrab()) { // see TabBox::establishTabBoxGrab() // first grab all modifier combinations m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); // remove the grab for no modifiers only if the window // is unobscured or if the user doesn't want click raise // (it is unobscured if it the topmost in the unconstrained stacking order, i.e. it is // the most recently raised window) bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; if (!options->isClickRaise() || not_obscured) ungrabButton(XCB_NONE); else grabButton(XCB_NONE); ungrabButton(XCB_MOD_MASK_SHIFT); ungrabButton(XCB_MOD_MASK_CONTROL); ungrabButton(XCB_MOD_MASK_CONTROL | XCB_MOD_MASK_SHIFT); } else { m_wrapper.ungrabButton(); // simply grab all modifier combinations m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); } } static bool modKeyDown(int state) { const uint keyModX = (options->keyCmdAllModKey() == Qt::Key_Meta) ? KKeyServer::modXMeta() : KKeyServer::modXAlt(); return keyModX && (state & KKeyServer::accelModMaskX()) == keyModX; } // return value matters only when filtering events before decoration gets them bool Client::buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time) { if (isMoveResizePointerButtonDown()) { if (w == wrapperId()) xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w == wrapperId() || w == frameId() || w == inputId()) { // FRAME neco s tohohle by se melo zpracovat, nez to dostane dekorace updateUserTime(time); workspace()->setWasUserInteraction(); const bool bModKeyHeld = modKeyDown(state); if (isSplash() && button == XCB_BUTTON_INDEX_1 && !bModKeyHeld) { // hide splashwindow if the user clicks on it hideClient(true); if (w == wrapperId()) xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } Options::MouseCommand com = Options::MouseNothing; bool was_action = false; if (bModKeyHeld) { was_action = true; switch(button) { case XCB_BUTTON_INDEX_1: com = options->commandAll1(); break; case XCB_BUTTON_INDEX_2: com = options->commandAll2(); break; case XCB_BUTTON_INDEX_3: com = options->commandAll3(); break; case XCB_BUTTON_INDEX_4: case XCB_BUTTON_INDEX_5: com = options->operationWindowMouseWheel(button == XCB_BUTTON_INDEX_4 ? 120 : -120); break; } } else { if (w == wrapperId()) { if (button < 4) { com = getMouseCommand(x11ToQtMouseButton(button), &was_action); } else if (button < 6) { com = getWheelCommand(Qt::Vertical, &was_action); } } } if (was_action) { bool replay = performMouseCommand(com, QPoint(x_root, y_root)); if (isSpecialWindow()) replay = true; if (w == wrapperId()) // these can come only from a grab xcb_allow_events(connection(), replay ? XCB_ALLOW_REPLAY_POINTER : XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } } if (w == wrapperId()) { // these can come only from a grab xcb_allow_events(connection(), XCB_ALLOW_REPLAY_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w == inputId()) { x = x_root - geometry().x(); y = y_root - geometry().y(); // New API processes core events FIRST and only passes unused ones to the decoration QMouseEvent ev(QMouseEvent::MouseButtonPress, QPoint(x, y), QPoint(x_root, y_root), x11ToQtMouseButton(button), x11ToQtMouseButtons(state), Qt::KeyboardModifiers()); return processDecorationButtonPress(&ev, true); } if (w == frameId() && isDecorated()) { if (button >= 4 && button <= 7) { const Qt::KeyboardModifiers modifiers = x11ToQtKeyboardModifiers(state); // Logic borrowed from qapplication_x11.cpp const int delta = 120 * ((button == 4 || button == 6) ? 1 : -1); const bool hor = (((button == 4 || button == 5) && (modifiers & Qt::AltModifier)) || (button == 6 || button == 7)); const QPoint angle = hor ? QPoint(delta, 0) : QPoint(0, delta); QWheelEvent event(QPointF(x, y), QPointF(x_root, y_root), QPoint(), angle, delta, hor ? Qt::Horizontal : Qt::Vertical, x11ToQtMouseButtons(state), modifiers); event.setAccepted(false); QCoreApplication::sendEvent(decoration(), &event); if (!event.isAccepted() && !hor) { if (titlebarPositionUnderMouse()) { performMouseCommand(options->operationTitlebarMouseWheel(delta), QPoint(x_root, y_root)); } } } else { QMouseEvent event(QEvent::MouseButtonPress, QPointF(x, y), QPointF(x_root, y_root), x11ToQtMouseButton(button), x11ToQtMouseButtons(state), x11ToQtKeyboardModifiers(state)); event.setAccepted(false); QCoreApplication::sendEvent(decoration(), &event); if (!event.isAccepted()) { processDecorationButtonPress(&event); } } return true; } return true; } // return value matters only when filtering events before decoration gets them bool Client::buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root) { if (w == frameId() && isDecorated()) { // wheel handled on buttonPress if (button < 4 || button > 7) { QMouseEvent event(QEvent::MouseButtonRelease, QPointF(x, y), QPointF(x_root, y_root), x11ToQtMouseButton(button), x11ToQtMouseButtons(state) & ~x11ToQtMouseButton(button), x11ToQtKeyboardModifiers(state)); event.setAccepted(false); QCoreApplication::sendEvent(decoration(), &event); if (event.isAccepted() || !titlebarPositionUnderMouse()) { invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick } } } if (w == wrapperId()) { xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w != frameId() && w != inputId() && w != moveResizeGrabWindow()) return true; if (w == frameId() && workspace()->userActionsMenu() && workspace()->userActionsMenu()->isShown()) { const_cast(workspace()->userActionsMenu())->grabInput(); } x = this->x(); // translate from grab window to local coords y = this->y(); // Check whether other buttons are still left pressed int buttonMask = XCB_BUTTON_MASK_1 | XCB_BUTTON_MASK_2 | XCB_BUTTON_MASK_3; if (button == XCB_BUTTON_INDEX_1) buttonMask &= ~XCB_BUTTON_MASK_1; else if (button == XCB_BUTTON_INDEX_2) buttonMask &= ~XCB_BUTTON_MASK_2; else if (button == XCB_BUTTON_INDEX_3) buttonMask &= ~XCB_BUTTON_MASK_3; if ((state & buttonMask) == 0) { endMoveResize(); } return true; } // return value matters only when filtering events before decoration gets them bool Client::motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root) { if (w == frameId() && isDecorated() && !isMinimized()) { // TODO Mouse move event dependent on state QHoverEvent event(QEvent::HoverMove, QPointF(x, y), QPointF(x, y)); QCoreApplication::instance()->sendEvent(decoration(), &event); } if (w != frameId() && w != inputId() && w != moveResizeGrabWindow()) return true; // care only about the whole frame if (!isMoveResizePointerButtonDown()) { if (w == inputId()) { int x = x_root - geometry().x();// + padding_left; int y = y_root - geometry().y();// + padding_top; if (isDecorated()) { QHoverEvent event(QEvent::HoverMove, QPointF(x, y), QPointF(x, y)); QCoreApplication::instance()->sendEvent(decoration(), &event); } } Position newmode = modKeyDown(state) ? PositionCenter : mousePosition(); if (newmode != moveResizePointerMode()) { setMoveResizePointerMode(newmode); updateCursor(); } return false; } if (w == moveResizeGrabWindow()) { x = this->x(); // translate from grab window to local coords y = this->y(); } handleMoveResize(QPoint(x, y), QPoint(x_root, y_root)); return true; } void Client::focusInEvent(xcb_focus_in_event_t *e) { if (e->event != window()) return; // only window gets focus if (e->mode == XCB_NOTIFY_MODE_UNGRAB) return; // we don't care if (e->detail == XCB_NOTIFY_DETAIL_POINTER) return; // we don't care if (!isShown(false) || !isOnCurrentDesktop()) // we unmapped it, but it got focus meanwhile -> return; // activateNextClient() already transferred focus elsewhere workspace()->forEachClient([](Client *client) { client->cancelFocusOutTimer(); }); // check if this client is in should_get_focus list or if activation is allowed bool activate = workspace()->allowClientActivation(this, -1U, true); workspace()->gotFocusIn(this); // remove from should_get_focus list if (activate) setActive(true); else { workspace()->restoreFocus(); demandAttention(); } } void Client::focusOutEvent(xcb_focus_out_event_t *e) { if (e->event != window()) return; // only window gets focus if (e->mode == XCB_NOTIFY_MODE_GRAB) return; // we don't care if (isShade()) return; // here neither if (e->detail != XCB_NOTIFY_DETAIL_NONLINEAR && e->detail != XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL) // SELI check all this return; // hack for motif apps like netscape if (QApplication::activePopupWidget()) return; // When a client loses focus, FocusOut events are usually immediatelly // followed by FocusIn events for another client that gains the focus // (unless the focus goes to another screen, or to the nofocus widget). // Without this check, the former focused client would have to be // deactivated, and after that, the new one would be activated, with // a short time when there would be no active client. This can cause // flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred // from it to its transient, the fullscreen would be kept in the Active layer // at the beginning and at the end, but not in the middle, when the active // client would be temporarily none (see Client::belongToLayer() ). // Therefore the setActive(false) call is moved to the end of the current // event queue. If there is a matching FocusIn event in the current queue // this will be processed before the setActive(false) call and the activation // of the Client which gained FocusIn will automatically deactivate the // previously active client. if (!m_focusOutTimer) { m_focusOutTimer = new QTimer(this); m_focusOutTimer->setSingleShot(true); m_focusOutTimer->setInterval(0); connect(m_focusOutTimer, &QTimer::timeout, [this]() { setActive(false); }); } m_focusOutTimer->start(); } // performs _NET_WM_MOVERESIZE void Client::NETMoveResize(int x_root, int y_root, NET::Direction direction) { if (direction == NET::Move) { // move cursor to the provided position to prevent the window jumping there on first movement // the expectation is that the cursor is already at the provided position, // thus it's more a safety measurement Cursor::setPos(QPoint(x_root, y_root)); performMouseCommand(Options::MouseMove, QPoint(x_root, y_root)); } else if (isMoveResize() && direction == NET::MoveResizeCancel) { finishMoveResize(true); setMoveResizePointerButtonDown(false); updateCursor(); } else if (direction >= NET::TopLeft && direction <= NET::Left) { static const Position convert[] = { PositionTopLeft, PositionTop, PositionTopRight, PositionRight, PositionBottomRight, PositionBottom, PositionBottomLeft, PositionLeft }; if (!isResizable() || isShade()) return; if (isMoveResize()) finishMoveResize(false); setMoveResizePointerButtonDown(true); setMoveOffset(QPoint(x_root - x(), y_root - y())); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); setMoveResizePointerMode(convert[ direction ]); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); } else if (direction == NET::KeyboardMove) { // ignore mouse coordinates given in the message, mouse position is used by the moving algorithm Cursor::setPos(geometry().center()); performMouseCommand(Options::MouseUnrestrictedMove, geometry().center()); } else if (direction == NET::KeyboardSize) { // ignore mouse coordinates given in the message, mouse position is used by the resizing algorithm Cursor::setPos(geometry().bottomRight()); performMouseCommand(Options::MouseUnrestrictedResize, geometry().bottomRight()); } } void Client::keyPressEvent(uint key_code, xcb_timestamp_t time) { updateUserTime(time); AbstractClient::keyPressEvent(key_code); } void Client::syncEvent(xcb_sync_alarm_notify_event_t* e) { if (e->alarm == syncRequest.alarm && e->counter_value.hi == syncRequest.value.hi && e->counter_value.lo == syncRequest.value.lo) { 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(); } } // **************************************** // Unmanaged // **************************************** bool Unmanaged::windowEvent(xcb_generic_event_t *e) { double old_opacity = opacity(); NET::Properties dirtyProperties; NET::Properties2 dirtyProperties2; info->event(e, &dirtyProperties, &dirtyProperties2); // pass through the NET stuff if (dirtyProperties2 & NET::WM2Opacity) { if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } } if (dirtyProperties2 & NET::WM2OpaqueRegion) { getWmOpaqueRegion(); } if (dirtyProperties2.testFlag(NET::WM2WindowRole)) { emit windowRoleChanged(); } if (dirtyProperties2.testFlag(NET::WM2WindowClass)) { getResourceClass(); } const uint8_t eventType = e->response_type & ~0x80; switch (eventType) { case XCB_DESTROY_NOTIFY: release(ReleaseReason::Destroyed); break; case XCB_UNMAP_NOTIFY:{ workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event // unmap notify might have been emitted due to a destroy notify // but unmap notify gets emitted before the destroy notify, nevertheless at this // point the window is already destroyed. This means any XCB request with the window // will cause an error. // To not run into these errors we try to wait for the destroy notify. For this we // generate a round trip to the X server and wait a very short time span before // handling the release. updateXTime(); // using 1 msec to not just move it at the end of the event loop but add an very short // timespan to cover cases like unmap() followed by destroy(). The only other way to // ensure that the window is not destroyed when we do the release handling is to grab // the XServer which we do not want to do for an Unmanaged. The timespan of 1 msec is // short enough to not cause problems in the close window animations. // It's of course still possible that we miss the destroy in which case non-fatal // X errors are reported to the event loop and logged by Qt. QTimer::singleShot(1, this, SLOT(release())); break; } case XCB_CONFIGURE_NOTIFY: configureNotifyEvent(reinterpret_cast(e)); break; case XCB_PROPERTY_NOTIFY: propertyNotifyEvent(reinterpret_cast(e)); break; case XCB_CLIENT_MESSAGE: clientMessageEvent(reinterpret_cast(e)); break; default: { if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { detectShape(window()); addRepaintFull(); addWorkspaceRepaint(geometry()); // in case shape change removes part of this window emit geometryShapeChanged(this, geometry()); } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) damageNotifyEvent(); break; } } return false; // don't eat events, even our own unmanaged widgets are tracked } void Unmanaged::configureNotifyEvent(xcb_configure_notify_event_t *e) { if (effects) static_cast(effects)->checkInputWindowStacking(); // keep them on top QRect newgeom(e->x, e->y, e->width, e->height); if (newgeom != geom) { addWorkspaceRepaint(visibleRect()); // damage old area QRect old = geom; geom = newgeom; emit geometryChanged(); // update shadow region addRepaintFull(); if (old.size() != geom.size()) discardWindowPixmap(); emit geometryShapeChanged(this, old); } } // **************************************** // Toplevel // **************************************** void Toplevel::propertyNotifyEvent(xcb_property_notify_event_t *e) { if (e->window != window()) return; // ignore frame/wrapper switch(e->atom) { default: if (e->atom == atoms->wm_client_leader) getWmClientLeader(); else if (e->atom == atoms->kde_net_wm_shadow) getShadow(); else if (e->atom == atoms->kde_skip_close_animation) getSkipCloseAnimation(); break; } emit propertyNotify(this, e->atom); } void Toplevel::clientMessageEvent(xcb_client_message_event_t *e) { if (e->type == atoms->wl_surface_id) { m_surfaceId = e->data.data32[0]; if (auto w = waylandServer()) { if (auto s = KWayland::Server::SurfaceInterface::get(m_surfaceId, w->xWaylandConnection())) { setSurface(s); } } emit surfaceIdChanged(m_surfaceId); } } } // namespace diff --git a/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.cpp b/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.cpp index b2ac124ed..26d779015 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.cpp +++ b/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.cpp @@ -1,172 +1,173 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "buttonsmodel.h" #include #include namespace KDecoration2 { namespace Preview { ButtonsModel::ButtonsModel(const QVector< DecorationButtonType > &buttons, QObject *parent) : QAbstractListModel(parent) , m_buttons(buttons) { } ButtonsModel::ButtonsModel(QObject* parent) : ButtonsModel(QVector({ DecorationButtonType::Menu, + DecorationButtonType::ApplicationMenu, DecorationButtonType::OnAllDesktops, DecorationButtonType::Minimize, DecorationButtonType::Maximize, DecorationButtonType::Close, DecorationButtonType::ContextHelp, DecorationButtonType::Shade, DecorationButtonType::KeepBelow, DecorationButtonType::KeepAbove }), parent) { } ButtonsModel::~ButtonsModel() = default; int ButtonsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_buttons.count(); } static QString buttonToName(DecorationButtonType type) { switch (type) { case DecorationButtonType::Menu: return i18n("Menu"); case DecorationButtonType::ApplicationMenu: return i18n("Application menu"); case DecorationButtonType::OnAllDesktops: return i18n("On all desktops"); case DecorationButtonType::Minimize: return i18n("Minimize"); case DecorationButtonType::Maximize: return i18n("Maximize"); case DecorationButtonType::Close: return i18n("Close"); case DecorationButtonType::ContextHelp: return i18n("Context help"); case DecorationButtonType::Shade: return i18n("Shade"); case DecorationButtonType::KeepBelow: return i18n("Keep below"); case DecorationButtonType::KeepAbove: return i18n("Keep above"); default: return QString(); } } QVariant ButtonsModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_buttons.count() || index.column() != 0) { return QVariant(); } switch (role) { case Qt::DisplayRole: return buttonToName(m_buttons.at(index.row())); case Qt::UserRole: return QVariant::fromValue(int(m_buttons.at(index.row()))); } return QVariant(); } QHash< int, QByteArray > ButtonsModel::roleNames() const { QHash roles; roles.insert(Qt::DisplayRole, QByteArrayLiteral("display")); roles.insert(Qt::UserRole, QByteArrayLiteral("button")); return roles; } void ButtonsModel::remove(int row) { if (row < 0 || row >= m_buttons.count()) { return; } beginRemoveRows(QModelIndex(), row, row); m_buttons.removeAt(row); endRemoveRows(); } void ButtonsModel::down(int index) { if (m_buttons.count() < 2 || index == m_buttons.count() -1) { return; } beginMoveRows(QModelIndex(), index, index, QModelIndex(), index + 2); m_buttons.insert(index +1, m_buttons.takeAt(index)); endMoveRows(); } void ButtonsModel::up(int index) { if (m_buttons.count() < 2 || index == 0) { return; } beginMoveRows(QModelIndex(), index, index, QModelIndex(), index -1); m_buttons.insert(index -1, m_buttons.takeAt(index)); endMoveRows(); } void ButtonsModel::add(DecorationButtonType type) { beginInsertRows(QModelIndex(), m_buttons.count(), m_buttons.count()); m_buttons.append(type); endInsertRows(); } void ButtonsModel::add(int index, int type) { beginInsertRows(QModelIndex(), index + 1, index+1); m_buttons.insert(index+1, KDecoration2::DecorationButtonType(type)); endInsertRows(); } void ButtonsModel::move(int sourceIndex, int targetIndex) { if (sourceIndex == qMax(0, targetIndex)) { return; } beginMoveRows(QModelIndex(), sourceIndex, sourceIndex, QModelIndex(), targetIndex + 1); m_buttons.insert(qMax(0, targetIndex), m_buttons.takeAt(sourceIndex)); endMoveRows(); } } } diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp b/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp index 146915f53..7d7c413c2 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp +++ b/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp @@ -1,458 +1,479 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "previewclient.h" #include #include #include #include #include #include #include #include namespace KDecoration2 { namespace Preview { PreviewClient::PreviewClient(DecoratedClient *c, Decoration *decoration) : QObject(decoration) - , DecoratedClientPrivate(c, decoration) + , ApplicationMenuEnabledDecoratedClientPrivate(c, decoration) , m_colorSchemeManager(new KColorSchemeManager(this)) , m_colorSchemeIndex(0) , m_icon(QIcon::fromTheme(QStringLiteral("start-here-kde"))) , m_iconName(m_icon.name()) , m_palette(QStringLiteral("kdeglobals")) , m_active(true) , m_closeable(true) , m_keepBelow(false) , m_keepAbove(false) , m_maximizable(true) , m_maximizedHorizontally(false) , m_maximizedVertically(false) , m_minimizable(true) , m_modal(false) , m_movable(true) , m_resizable(true) , m_shadeable(true) , m_shaded(false) , m_providesContextHelp(false) , m_desktop(1) , m_width(0) , m_height(0) , m_bordersTopEdge(false) , m_bordersLeftEdge(false) , m_bordersRightEdge(false) , m_bordersBottomEdge(false) { connect(this, &PreviewClient::captionChanged, c, &DecoratedClient::captionChanged); connect(this, &PreviewClient::activeChanged, c, &DecoratedClient::activeChanged); connect(this, &PreviewClient::closeableChanged, c, &DecoratedClient::closeableChanged); connect(this, &PreviewClient::keepAboveChanged, c, &DecoratedClient::keepAboveChanged); connect(this, &PreviewClient::keepBelowChanged, c, &DecoratedClient::keepBelowChanged); connect(this, &PreviewClient::maximizableChanged, c, &DecoratedClient::maximizeableChanged); connect(this, &PreviewClient::maximizedChanged, c, &DecoratedClient::maximizedChanged); connect(this, &PreviewClient::maximizedVerticallyChanged, c, &DecoratedClient::maximizedVerticallyChanged); connect(this, &PreviewClient::maximizedHorizontallyChanged, c, &DecoratedClient::maximizedHorizontallyChanged); connect(this, &PreviewClient::minimizableChanged, c, &DecoratedClient::minimizeableChanged); // connect(this, &PreviewClient::modalChanged, c, &DecoratedClient::modalChanged); connect(this, &PreviewClient::movableChanged, c, &DecoratedClient::moveableChanged); connect(this, &PreviewClient::onAllDesktopsChanged, c, &DecoratedClient::onAllDesktopsChanged); connect(this, &PreviewClient::resizableChanged, c, &DecoratedClient::resizeableChanged); connect(this, &PreviewClient::shadeableChanged, c, &DecoratedClient::shadeableChanged); connect(this, &PreviewClient::shadedChanged, c, &DecoratedClient::shadedChanged); connect(this, &PreviewClient::providesContextHelpChanged, c, &DecoratedClient::providesContextHelpChanged); connect(this, &PreviewClient::onAllDesktopsChanged, c, &DecoratedClient::onAllDesktopsChanged); connect(this, &PreviewClient::widthChanged, c, &DecoratedClient::widthChanged); connect(this, &PreviewClient::heightChanged, c, &DecoratedClient::heightChanged); connect(this, &PreviewClient::iconChanged, c, &DecoratedClient::iconChanged); connect(this, &PreviewClient::paletteChanged, c, &DecoratedClient::paletteChanged); // connect(this, &PreviewClient::, c, &DecoratedClient::); connect(this, &PreviewClient::maximizedVerticallyChanged, this, [this]() { emit maximizedChanged(isMaximized()); } ); connect(this, &PreviewClient::maximizedHorizontallyChanged, this, [this]() { emit maximizedChanged(isMaximized()); } ); connect(this, &PreviewClient::iconNameChanged, this, [this]() { m_icon = QIcon::fromTheme(m_iconName); emit iconChanged(m_icon); } ); connect(this, &PreviewClient::desktopChanged, this, [this]() { emit onAllDesktopsChanged(isOnAllDesktops()); } ); connect(&m_palette, &KWin::Decoration::DecorationPalette::changed, [this]() { emit paletteChanged(m_palette.palette()); }); auto emitEdgesChanged = [this, c]() { c->adjacentScreenEdgesChanged(adjacentScreenEdges()); }; connect(this, &PreviewClient::bordersTopEdgeChanged, this, emitEdgesChanged); connect(this, &PreviewClient::bordersLeftEdgeChanged, this, emitEdgesChanged); connect(this, &PreviewClient::bordersRightEdgeChanged, this, emitEdgesChanged); connect(this, &PreviewClient::bordersBottomEdgeChanged, this, emitEdgesChanged); qApp->installEventFilter(this); } PreviewClient::~PreviewClient() = default; void PreviewClient::setIcon(const QIcon &pixmap) { m_icon = pixmap; emit iconChanged(m_icon); } int PreviewClient::width() const { return m_width; } int PreviewClient::height() const { return m_height; } QString PreviewClient::caption() const { return m_caption; } WId PreviewClient::decorationId() const { return 0; } int PreviewClient::desktop() const { return m_desktop; } void PreviewClient::setDesktop(int desktop) { if (desktop == 0) { desktop = 1; } if (m_desktop == desktop) { return; } m_desktop = desktop; emit desktopChanged(m_desktop); } QIcon PreviewClient::icon() const { return m_icon; } QString PreviewClient::iconName() const { return m_iconName; } bool PreviewClient::isActive() const { return m_active; } bool PreviewClient::isCloseable() const { return m_closeable; } bool PreviewClient::isKeepAbove() const { return m_keepAbove; } bool PreviewClient::isKeepBelow() const { return m_keepBelow; } bool PreviewClient::isMaximizeable() const { return m_maximizable; } bool PreviewClient::isMaximized() const { return isMaximizedHorizontally() && isMaximizedVertically(); } bool PreviewClient::isMaximizedHorizontally() const { return m_maximizedHorizontally; } bool PreviewClient::isMaximizedVertically() const { return m_maximizedVertically; } bool PreviewClient::isMinimizeable() const { return m_minimizable; } bool PreviewClient::isModal() const { return m_modal; } bool PreviewClient::isMoveable() const { return m_movable; } bool PreviewClient::isOnAllDesktops() const { return desktop() == -1; } bool PreviewClient::isResizeable() const { return m_resizable; } bool PreviewClient::isShadeable() const { return m_shadeable; } bool PreviewClient::isShaded() const { return m_shaded; } bool PreviewClient::providesContextHelp() const { return m_providesContextHelp; } WId PreviewClient::windowId() const { return 0; } QPalette PreviewClient::palette() const { return m_palette.palette(); } QColor PreviewClient::color(ColorGroup group, ColorRole role) const { return m_palette.color(group, role); } QAbstractItemModel *PreviewClient::colorSchemeModel() const { return m_colorSchemeManager->model(); } int PreviewClient::colorSchemeIndex() const { return m_colorSchemeIndex; } void PreviewClient::setColorSchemeIndex(int index) { if (m_colorSchemeIndex == index) { return; } m_colorSchemeIndex = index; emit colorSchemeIndexChanged(m_colorSchemeIndex); } Qt::Edges PreviewClient::adjacentScreenEdges() const { Qt::Edges edges; if (m_bordersBottomEdge) { edges |= Qt::BottomEdge; } if (m_bordersLeftEdge) { edges |= Qt::LeftEdge; } if (m_bordersRightEdge) { edges |= Qt::RightEdge; } if (m_bordersTopEdge) { edges |= Qt::TopEdge; } return edges; } +bool PreviewClient::hasApplicationMenu() const +{ + return true; +} + +bool PreviewClient::isApplicationMenuActive() const +{ + return false; +} + bool PreviewClient::bordersBottomEdge() const { return m_bordersBottomEdge; } bool PreviewClient::bordersLeftEdge() const { return m_bordersLeftEdge; } bool PreviewClient::bordersRightEdge() const { return m_bordersRightEdge; } bool PreviewClient::bordersTopEdge() const { return m_bordersTopEdge; } void PreviewClient::setBordersBottomEdge(bool enabled) { if (m_bordersBottomEdge == enabled) { return; } m_bordersBottomEdge = enabled; emit bordersBottomEdgeChanged(enabled); } void PreviewClient::setBordersLeftEdge(bool enabled) { if (m_bordersLeftEdge == enabled) { return; } m_bordersLeftEdge = enabled; emit bordersLeftEdgeChanged(enabled); } void PreviewClient::setBordersRightEdge(bool enabled) { if (m_bordersRightEdge == enabled) { return; } m_bordersRightEdge = enabled; emit bordersRightEdgeChanged(enabled); } void PreviewClient::setBordersTopEdge(bool enabled) { if (m_bordersTopEdge == enabled) { return; } m_bordersTopEdge = enabled; emit bordersTopEdgeChanged(enabled); } void PreviewClient::requestClose() { emit closeRequested(); } void PreviewClient::requestContextHelp() { qDebug() << "context help requested"; } void PreviewClient::requestToggleMaximization(Qt::MouseButtons buttons) { if (buttons.testFlag(Qt::LeftButton)) { const bool set = !isMaximized(); setMaximizedHorizontally(set); setMaximizedVertically(set); } else if (buttons.testFlag(Qt::RightButton)) { setMaximizedHorizontally(!isMaximizedHorizontally()); } else if (buttons.testFlag(Qt::MiddleButton)) { setMaximizedVertically(!isMaximizedVertically()); } } void PreviewClient::requestMinimize() { emit minimizeRequested(); } void PreviewClient::requestToggleKeepAbove() { setKeepAbove(!isKeepAbove()); } void PreviewClient::requestToggleKeepBelow() { setKeepBelow(!isKeepBelow()); } void PreviewClient::requestShowWindowMenu() { emit showWindowMenuRequested(); } +void PreviewClient::requestShowApplicationMenu(const QRect &rect, int actionId) +{ + Q_UNUSED(rect); + Q_UNUSED(actionId); +} + +void PreviewClient::showApplicationMenu(int actionId) +{ + Q_UNUSED(actionId) +} + void PreviewClient::requestToggleOnAllDesktops() { setDesktop(isOnAllDesktops() ? 1 : -1); } void PreviewClient::requestToggleShade() { setShaded(!isShaded()); } #define SETTER(type, name, variable) \ void PreviewClient::name(type variable) \ { \ if (m_##variable == variable) { \ return; \ } \ qDebug() << "Setting " << #variable << ":" << variable;\ m_##variable = variable; \ emit variable##Changed(m_##variable); \ } #define SETTER2(name, variable) SETTER(bool, name, variable) SETTER(const QString &, setCaption, caption) SETTER(const QString &, setIconName, iconName) SETTER(int, setWidth, width) SETTER(int, setHeight, height) SETTER2(setActive, active) SETTER2(setCloseable, closeable) SETTER2(setMaximizable, maximizable) SETTER2(setKeepBelow, keepBelow) SETTER2(setKeepAbove, keepAbove) SETTER2(setMaximizedHorizontally, maximizedHorizontally) SETTER2(setMaximizedVertically, maximizedVertically) SETTER2(setMinimizable, minimizable) SETTER2(setModal, modal) SETTER2(setMovable, movable) SETTER2(setResizable, resizable) SETTER2(setShadeable, shadeable) SETTER2(setShaded, shaded) SETTER2(setProvidesContextHelp, providesContextHelp) #undef SETTER2 #undef SETTER } // namespace Preview } // namespace KDecoration2 diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewclient.h b/kcmkwin/kwindecoration/declarative-plugin/previewclient.h index 806a4fbd3..47ff2c415 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/previewclient.h +++ b/kcmkwin/kwindecoration/declarative-plugin/previewclient.h @@ -1,215 +1,222 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KDECOARTIONS_PREVIEW_CLIENT_H #define KDECOARTIONS_PREVIEW_CLIENT_H #include "../../../decorations/decorationpalette.h" #include #include #include class KColorSchemeManager; class QAbstractItemModel; namespace KDecoration2 { namespace Preview { -class PreviewClient : public QObject, public DecoratedClientPrivate +class PreviewClient : public QObject, public ApplicationMenuEnabledDecoratedClientPrivate { Q_OBJECT Q_PROPERTY(KDecoration2::Decoration *decoration READ decoration CONSTANT) Q_PROPERTY(QString caption READ caption WRITE setCaption NOTIFY captionChanged) Q_PROPERTY(QIcon icon READ icon WRITE setIcon NOTIFY iconChanged) Q_PROPERTY(QString iconName READ iconName WRITE setIconName NOTIFY iconNameChanged) Q_PROPERTY(QAbstractItemModel *colorSchemeModel READ colorSchemeModel CONSTANT) Q_PROPERTY(int colorSchemeIndex READ colorSchemeIndex WRITE setColorSchemeIndex NOTIFY colorSchemeIndexChanged) Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) Q_PROPERTY(bool closeable READ isCloseable WRITE setCloseable NOTIFY closeableChanged) Q_PROPERTY(bool keepAbove READ isKeepAbove WRITE setKeepAbove NOTIFY keepAboveChanged) Q_PROPERTY(bool keepBelow READ isKeepBelow WRITE setKeepBelow NOTIFY keepBelowChanged) Q_PROPERTY(bool maximizable READ isMaximizeable WRITE setMaximizable NOTIFY maximizableChanged) Q_PROPERTY(bool maximized READ isMaximized NOTIFY maximizedChanged) Q_PROPERTY(bool maximizedVertically READ isMaximizedVertically WRITE setMaximizedVertically NOTIFY maximizedVerticallyChanged) Q_PROPERTY(bool maximizedHorizontally READ isMaximizedHorizontally WRITE setMaximizedHorizontally NOTIFY maximizedHorizontallyChanged) Q_PROPERTY(bool minimizable READ isMinimizeable WRITE setMinimizable NOTIFY minimizableChanged) Q_PROPERTY(bool modal READ isModal WRITE setModal NOTIFY modalChanged) Q_PROPERTY(bool movable READ isMoveable WRITE setMovable NOTIFY movableChanged) Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops NOTIFY onAllDesktopsChanged) Q_PROPERTY(bool resizable READ isResizeable WRITE setResizable NOTIFY resizableChanged) Q_PROPERTY(bool shadeable READ isShadeable WRITE setShadeable NOTIFY shadeableChanged) Q_PROPERTY(bool shaded READ isShaded WRITE setShaded NOTIFY shadedChanged) Q_PROPERTY(bool providesContextHelp READ providesContextHelp WRITE setProvidesContextHelp NOTIFY providesContextHelpChanged) Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged) Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged) Q_PROPERTY(bool bordersTopEdge READ bordersTopEdge WRITE setBordersTopEdge NOTIFY bordersTopEdgeChanged) Q_PROPERTY(bool bordersLeftEdge READ bordersLeftEdge WRITE setBordersLeftEdge NOTIFY bordersLeftEdgeChanged) Q_PROPERTY(bool bordersRightEdge READ bordersRightEdge WRITE setBordersRightEdge NOTIFY bordersRightEdgeChanged) Q_PROPERTY(bool bordersBottomEdge READ bordersBottomEdge WRITE setBordersBottomEdge NOTIFY bordersBottomEdgeChanged) public: explicit PreviewClient(DecoratedClient *client, Decoration *decoration); virtual ~PreviewClient(); QString caption() const override; WId decorationId() const override; WId windowId() const override; int desktop() const override; QIcon icon() const override; bool isActive() const override; bool isCloseable() const override; bool isKeepAbove() const override; bool isKeepBelow() const override; bool isMaximizeable() const override; bool isMaximized() const override; bool isMaximizedVertically() const override; bool isMaximizedHorizontally() const override; bool isMinimizeable() const override; bool isModal() const override; bool isMoveable() const override; bool isOnAllDesktops() const override; bool isResizeable() const override; bool isShadeable() const override; bool isShaded() const override; bool providesContextHelp() const override; int width() const override; int height() const override; QPalette palette() const override; QColor color(ColorGroup group, ColorRole role) const override; Qt::Edges adjacentScreenEdges() const override; + bool hasApplicationMenu() const override; + bool isApplicationMenuActive() const override; + void requestClose() override; void requestContextHelp() override; void requestToggleMaximization(Qt::MouseButtons buttons) override; void requestMinimize() override; void requestToggleKeepAbove() override; void requestToggleKeepBelow() override; void requestToggleShade() override; void requestShowWindowMenu() override; + void requestShowApplicationMenu(const QRect &rect, int actionId) override; void requestToggleOnAllDesktops() override; + void showApplicationMenu(int actionId); + void setCaption(const QString &caption); void setActive(bool active); void setCloseable(bool closeable); void setMaximizable(bool maximizable); void setKeepBelow(bool keepBelow); void setKeepAbove(bool keepAbove); void setMaximizedHorizontally(bool maximized); void setMaximizedVertically(bool maximized); void setMinimizable(bool minimizable); void setModal(bool modal); void setMovable(bool movable); void setResizable(bool resizable); void setShadeable(bool shadeable); void setShaded(bool shaded); void setProvidesContextHelp(bool contextHelp); void setDesktop(int desktop); void setWidth(int width); void setHeight(int height); QString iconName() const; void setIconName(const QString &icon); void setIcon(const QIcon &icon); QAbstractItemModel *colorSchemeModel() const; int colorSchemeIndex() const; void setColorSchemeIndex(int index); bool bordersTopEdge() const; bool bordersLeftEdge() const; bool bordersRightEdge() const; bool bordersBottomEdge() const; void setBordersTopEdge(bool enabled); void setBordersLeftEdge(bool enabled); void setBordersRightEdge(bool enabled); void setBordersBottomEdge(bool enabled); Q_SIGNALS: void captionChanged(const QString &); void iconChanged(const QIcon &); void iconNameChanged(const QString &); void activeChanged(bool); void closeableChanged(bool); void keepAboveChanged(bool); void keepBelowChanged(bool); void maximizableChanged(bool); void maximizedChanged(bool); void maximizedVerticallyChanged(bool); void maximizedHorizontallyChanged(bool); void minimizableChanged(bool); void modalChanged(bool); void movableChanged(bool); void onAllDesktopsChanged(bool); void resizableChanged(bool); void shadeableChanged(bool); void shadedChanged(bool); void providesContextHelpChanged(bool); void desktopChanged(int); void widthChanged(int); void heightChanged(int); void colorSchemeIndexChanged(int); void paletteChanged(const QPalette&); void bordersTopEdgeChanged(bool); void bordersLeftEdgeChanged(bool); void bordersRightEdgeChanged(bool); void bordersBottomEdgeChanged(bool); void showWindowMenuRequested(); + void showApplicationMenuRequested(); void minimizeRequested(); void closeRequested(); private: KColorSchemeManager *m_colorSchemeManager; int m_colorSchemeIndex; QString m_caption; QIcon m_icon; QString m_iconName; KWin::Decoration::DecorationPalette m_palette; bool m_active; bool m_closeable; bool m_keepBelow; bool m_keepAbove; bool m_maximizable; bool m_maximizedHorizontally; bool m_maximizedVertically; bool m_minimizable; bool m_modal; bool m_movable; bool m_resizable; bool m_shadeable; bool m_shaded; bool m_providesContextHelp; int m_desktop; int m_width; int m_height; bool m_bordersTopEdge; bool m_bordersLeftEdge; bool m_bordersRightEdge; bool m_bordersBottomEdge; }; } // namespace Preview } // namespace KDecoration2 #endif diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewsettings.cpp b/kcmkwin/kwindecoration/declarative-plugin/previewsettings.cpp index c5c767a65..dd996dab3 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/previewsettings.cpp +++ b/kcmkwin/kwindecoration/declarative-plugin/previewsettings.cpp @@ -1,278 +1,279 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "previewsettings.h" #include "previewbridge.h" #include "buttonsmodel.h" #include #include namespace KDecoration2 { namespace Preview { BorderSizesModel::BorderSizesModel(QObject *parent) : QAbstractListModel(parent) { } BorderSizesModel::~BorderSizesModel() = default; QVariant BorderSizesModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_borders.count() || index.column() != 0) { return QVariant(); } if (role != Qt::DisplayRole && role != Qt::UserRole) { return QVariant(); } return QVariant::fromValue(m_borders.at(index.row())); } int BorderSizesModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_borders.count(); } QHash< int, QByteArray > BorderSizesModel::roleNames() const { QHash roles; roles.insert(Qt::DisplayRole, QByteArrayLiteral("display")); return roles; } PreviewSettings::PreviewSettings(DecorationSettings *parent) : QObject() , DecorationSettingsPrivate(parent) , m_alphaChannelSupported(true) , m_onAllDesktopsAvailable(true) , m_closeOnDoubleClick(false) , m_leftButtons(new ButtonsModel(QVector({ DecorationButtonType::Menu, + DecorationButtonType::ApplicationMenu, DecorationButtonType::OnAllDesktops }), this)) , m_rightButtons(new ButtonsModel(QVector({ DecorationButtonType::ContextHelp, DecorationButtonType::Minimize, DecorationButtonType::Maximize, DecorationButtonType::Close }), this)) , m_availableButtons(new ButtonsModel(QVector({ DecorationButtonType::Menu, DecorationButtonType::ApplicationMenu, DecorationButtonType::OnAllDesktops, DecorationButtonType::Minimize, DecorationButtonType::Maximize, DecorationButtonType::Close, DecorationButtonType::ContextHelp, DecorationButtonType::Shade, DecorationButtonType::KeepBelow, DecorationButtonType::KeepAbove }), this)) , m_borderSizes(new BorderSizesModel(this)) , m_borderSize(int(BorderSize::Normal)) , m_font(QFontDatabase::systemFont(QFontDatabase::TitleFont)) { connect(this, &PreviewSettings::alphaChannelSupportedChanged, parent, &DecorationSettings::alphaChannelSupportedChanged); connect(this, &PreviewSettings::onAllDesktopsAvailableChanged, parent, &DecorationSettings::onAllDesktopsAvailableChanged); connect(this, &PreviewSettings::closeOnDoubleClickOnMenuChanged, parent, &DecorationSettings::closeOnDoubleClickOnMenuChanged); connect(this, &PreviewSettings::fontChanged, parent, &DecorationSettings::fontChanged); auto updateLeft = [this, parent]() { parent->decorationButtonsLeftChanged(decorationButtonsLeft()); }; auto updateRight = [this, parent]() { parent->decorationButtonsRightChanged(decorationButtonsRight()); }; connect(m_leftButtons, &QAbstractItemModel::rowsRemoved, this, updateLeft); connect(m_leftButtons, &QAbstractItemModel::rowsMoved, this, updateLeft); connect(m_leftButtons, &QAbstractItemModel::rowsInserted, this, updateLeft); connect(m_rightButtons, &QAbstractItemModel::rowsRemoved, this, updateRight); connect(m_rightButtons, &QAbstractItemModel::rowsMoved, this, updateRight); connect(m_rightButtons, &QAbstractItemModel::rowsInserted, this, updateRight); } PreviewSettings::~PreviewSettings() = default; QAbstractItemModel *PreviewSettings::availableButtonsModel() const { return m_availableButtons; } QAbstractItemModel *PreviewSettings::leftButtonsModel() const { return m_leftButtons; } QAbstractItemModel *PreviewSettings::rightButtonsModel() const { return m_rightButtons; } bool PreviewSettings::isAlphaChannelSupported() const { return m_alphaChannelSupported; } bool PreviewSettings::isOnAllDesktopsAvailable() const { return m_onAllDesktopsAvailable; } void PreviewSettings::setAlphaChannelSupported(bool supported) { if (m_alphaChannelSupported == supported) { return; } m_alphaChannelSupported = supported; emit alphaChannelSupportedChanged(m_alphaChannelSupported); } void PreviewSettings::setOnAllDesktopsAvailable(bool available) { if (m_onAllDesktopsAvailable == available) { return; } m_onAllDesktopsAvailable = available; emit onAllDesktopsAvailableChanged(m_onAllDesktopsAvailable); } void PreviewSettings::setCloseOnDoubleClickOnMenu(bool enabled) { if (m_closeOnDoubleClick == enabled) { return; } m_closeOnDoubleClick = enabled; emit closeOnDoubleClickOnMenuChanged(enabled); } QVector< DecorationButtonType > PreviewSettings::decorationButtonsLeft() const { return m_leftButtons->buttons(); } QVector< DecorationButtonType > PreviewSettings::decorationButtonsRight() const { return m_rightButtons->buttons(); } void PreviewSettings::addButtonToLeft(int row) { QModelIndex index = m_availableButtons->index(row); if (!index.isValid()) { return; } m_leftButtons->add(index.data(Qt::UserRole).value()); } void PreviewSettings::addButtonToRight(int row) { QModelIndex index = m_availableButtons->index(row); if (!index.isValid()) { return; } m_rightButtons->add(index.data(Qt::UserRole).value()); } void PreviewSettings::setBorderSizesIndex(int index) { if (m_borderSize == index) { return; } m_borderSize = index; emit borderSizesIndexChanged(index); emit decorationSettings()->borderSizeChanged(borderSize()); } BorderSize PreviewSettings::borderSize() const { return m_borderSizes->index(m_borderSize).data(Qt::UserRole).value(); } void PreviewSettings::setFont(const QFont &font) { if (m_font == font) { return; } m_font = font; emit fontChanged(m_font); } Settings::Settings(QObject *parent) : QObject(parent) { connect(this, &Settings::bridgeChanged, this, &Settings::createSettings); } Settings::~Settings() = default; void Settings::setBridge(PreviewBridge *bridge) { if (m_bridge == bridge) { return; } m_bridge = bridge; emit bridgeChanged(); } PreviewBridge *Settings::bridge() const { return m_bridge.data(); } void Settings::createSettings() { if (m_bridge.isNull()) { m_settings.clear(); } else { m_settings = QSharedPointer::create(m_bridge.data()); m_previewSettings = m_bridge->lastCreatedSettings(); m_previewSettings->setBorderSizesIndex(m_borderSize); connect(this, &Settings::borderSizesIndexChanged, m_previewSettings, &PreviewSettings::setBorderSizesIndex); } emit settingsChanged(); } QSharedPointer Settings::settings() const { return m_settings; } DecorationSettings *Settings::settingsPointer() const { return m_settings.data(); } void Settings::setBorderSizesIndex(int index) { if (m_borderSize == index) { return; } m_borderSize = index; emit borderSizesIndexChanged(m_borderSize); } } } diff --git a/manage.cpp b/manage.cpp index bf372a4d7..a1f927b92 100644 --- a/manage.cpp +++ b/manage.cpp @@ -1,800 +1,806 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // This file contains things relevant to handling incoming events. #include "client.h" #include #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "cursor.h" #include "rules.h" #include "group.h" #include "netinfo.h" #include "screens.h" #include "workspace.h" #include "xcbutils.h" #include namespace KWin { /** * 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 Client::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; auto wmClientLeaderCookie = fetchWmClientLeader(); auto skipCloseAnimationCookie = fetchSkipCloseAnimation(); auto gtkFrameExtentsCookie = fetchGtkFrameExtents(); 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); if (Xcb::Extensions::self()->isShapeAvailable()) xcb_shape_select_input(connection(), window(), true); detectShape(window()); readGtkFrameExtents(gtkFrameExtentsCookie); detectNoBorder(); fetchIconicName(); // 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(QByteArray(info->desktopFileName())); getIcons(); connect(this, &Client::desktopFileNameChanged, this, &Client::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); 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 != NULL) 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 (int type = checkFullScreenHack(geom)) { fullscreen_mode = FullScreenHack; if (rules()->checkStrictGeometry(false)) { geom = type == 2 // 1 = It's xinerama-aware fullscreen hack, 2 = It's full area ? workspace()->clientArea(FullArea, geom.center(), desktop()) : workspace()->clientArea(ScreenArea, geom.center(), desktop()); } else geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop()); placementDone = true; } 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 ( true ) // Size is always obeyed for now, only with constraints applied // if (( xSizeHint.flags & USSize ) || ( xSizeHint.flags & PSize )) // { // // Keep in mind that we now actually have a size :-) // } if (m_geometryHints.hasMaxSize()) geom.setSize(geom.size().boundedTo( rules()->checkMaxSize(m_geometryHints.maxSize()))); if (m_geometryHints.hasMinSize()) geom.setSize(geom.size().expandedTo( rules()->checkMinSize(m_geometryHints.minSize()))); if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) placementDone = false; // Weird, do not trust. if (placementDone) move(geom.x(), geom.y()); // Before gravitating // Create client group if the window will have a decoration bool dontKeepInArea = false; setTabGroup(NULL); if (!noBorder() && false) { const bool autogrouping = rules()->checkAutogrouping(options->isAutogroupSimilarWindows()); const bool autogroupInFg = rules()->checkAutogroupInForeground(options->isAutogroupInForeground()); // Automatically add to previous groups on session restore if (session && session->tabGroupClient && !workspace()->hasClient(session->tabGroupClient)) session->tabGroupClient = NULL; if (session && session->tabGroupClient && session->tabGroupClient != this) { tabBehind(session->tabGroupClient, autogroupInFg); } else if (isMapped && autogrouping) { // If the window is already mapped (Restarted KWin) add any windows that already have the // same geometry to the same client group. (May incorrectly handle maximized windows) foreach (Client *other, workspace()->clientList()) { if (other->maximizeMode() != MaximizeFull && geom == QRect(other->pos(), other->clientSize()) && desk == other->desktop() && activities() == other->activities()) { tabBehind(other, autogroupInFg); break; } } } if (!(tab_group || isMapped || session)) { // Attempt to automatically group similar windows Client* similar = findAutogroupCandidate(); if (similar && !similar->noBorder()) { if (autogroupInFg) { similar->setDesktop(desk); // can happen when grouping by id. ... similar->setMinimized(false); // ... or anyway - still group, but "here" and visible } if (!similar->isMinimized()) { // do not attempt to tab in background of a hidden group geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize()); updateDecoration(false); if (tabBehind(similar, autogroupInFg)) { // Don't move entire group geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize()); placementDone = true; dontKeepInArea = true; } } } } } 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); 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 = geometry(); // 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()->sessionSaving()) { bool visible_parent = false; // Use allMainClients(), to include also main clients of group transients // that have been optimized out in Client::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 == FullScreenHack) ; // Nothing, this should be already set again above else 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(false, !isMapped)); if (info->state() & NET::DemandsAttention) demandAttention(); if (info->state() & NET::Modal) setModal(true); if (fullscreen_mode != FullScreenHack) setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false); } updateAllowedActions(true); // Set initial user time directly m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session); group()->updateUserTime(m_userTime); // And do what Client::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() == NULL || workspace()->activeClient()->isDesktop()); else allow = workspace()->allowClientActivation(this, userTime(), false); if (!(isMapped || session)) { if (workspace()->sessionSaving()) { /* * If we get a new window during session saving, we assume it's some 'save file?' dialog * which the user really needs to see (to know why logout's stalled). * * Given the current session management protocol, I can't see a nicer way of doing this. * Someday I'd like to see a protocol that tells the windowmanager who's doing SessionInteract. */ needsSessionInteract = true; //show the parent too auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) { if (Client *mc = dynamic_cast((*it))) { mc->setSessionInteract(true); } (*it)->unminimize(); } } else if (allow) { // also force if activation is allowed if (!isOnCurrentDesktop() && options->focusPolicyIsReasonable()) { VirtualDesktopManager::self()->setCurrent(desktop()); } /*if (!isOnCurrentActivity()) { workspace()->setCurrentActivity( activities().first() ); } FIXME no such method*/ } } 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(); 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; client_rules.discardTemporary(); applyWindowRules(); // Just in case RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules updateWindowRules(Rules::All); // Was blocked while !isManaged() setBlockingCompositing(info->isBlockingCompositing()); readShowOnScreenEdge(showOnScreenEdgeCookie); // 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 Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth) { assert(m_client == XCB_WINDOW_NONE); assert(frameId() == XCB_WINDOW_NONE); 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(); } // To accept "mainwindow#1" to "mainwindow#2" static QByteArray truncatedWindowRole(QByteArray a) { int i = a.indexOf('#'); if (i == -1) return a; QByteArray b(a); b.truncate(i); return b; } Client* Client::findAutogroupCandidate() const { // Attempt to find a similar window to the input. If we find multiple possibilities that are in // different groups then ignore all of them. This function is for automatic window grouping. Client *found = NULL; // See if the window has a group ID to match with QString wGId = rules()->checkAutogroupById(QString()); if (!wGId.isEmpty()) { foreach (Client *c, workspace()->clientList()) { if (activities() != c->activities()) continue; // don't cross activities if (wGId == c->rules()->checkAutogroupById(QString())) { if (found && found->tabGroup() != c->tabGroup()) { // We've found two, ignore both found = NULL; break; // Continue to the next test } found = c; } } if (found) return found; } // If this is a transient window don't take a guess if (isTransient()) return NULL; // If we don't have an ID take a guess if (rules()->checkAutogrouping(options->isAutogroupSimilarWindows())) { QByteArray wRole = truncatedWindowRole(windowRole()); foreach (Client *c, workspace()->clientList()) { if (desktop() != c->desktop() || activities() != c->activities()) continue; QByteArray wRoleB = truncatedWindowRole(c->windowRole()); if (resourceClass() == c->resourceClass() && // Same resource class wRole == wRoleB && // Same window role c->isNormalWindow()) { // Normal window TODO: Can modal windows be "normal"? if (found && found->tabGroup() != c->tabGroup()) // We've found two, ignore both return NULL; found = c; } } } return found; } } // namespace diff --git a/org.kde.kappmenu.xml b/org.kde.kappmenu.xml new file mode 100644 index 000000000..d29d3ee86 --- /dev/null +++ b/org.kde.kappmenu.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shell_client.cpp b/shell_client.cpp index 172f10e2a..db96bafe7 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -1,1469 +1,1484 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "shell_client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "placement.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "virtualdesktops.h" #include "workspace.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KWayland::Server; static const QByteArray s_schemePropertyName = QByteArrayLiteral("KDE_COLOR_SCHEME_PATH"); +static const QByteArray s_appMenuServiceNamePropertyName = QByteArrayLiteral("KDE_APPMENU_SERVICE_NAME"); +static const QByteArray s_appMenuObjectPathPropertyName = QByteArrayLiteral("KDE_APPMENU_OBJECT_PATH"); namespace KWin { ShellClient::ShellClient(ShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(surface) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(nullptr) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::ShellClient(XdgShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(surface) , m_xdgShellPopup(nullptr) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::ShellClient(XdgShellPopupInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(surface) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::~ShellClient() = default; template void ShellClient::initSurface(T *shellSurface) { m_caption = shellSurface->title().simplified(); connect(shellSurface, &T::titleChanged, this, &ShellClient::captionChanged); connect(shellSurface, &T::destroyed, this, &ShellClient::destroyClient); connect(shellSurface, &T::titleChanged, this, [this] (const QString &s) { m_caption = s.simplified(); emit captionChanged(); } ); connect(shellSurface, &T::moveRequested, this, [this] { // TODO: check the seat and serial performMouseCommand(Options::MouseMove, Cursor::pos()); } ); setResourceClass(shellSurface->windowClass()); setDesktopFileName(shellSurface->windowClass()); connect(shellSurface, &T::windowClassChanged, this, [this] (const QByteArray &windowClass) { setResourceClass(windowClass); setDesktopFileName(windowClass); } ); connect(shellSurface, &T::resizeRequested, this, [this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) { // TODO: check the seat and serial Q_UNUSED(seat) Q_UNUSED(serial) if (!isResizable() || isShade()) { return; } if (isMoveResize()) { finishMoveResize(false); } setMoveResizePointerButtonDown(true); setMoveOffset(Cursor::pos() - pos()); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); auto toPosition = [edges] { Position pos = PositionCenter; if (edges.testFlag(Qt::TopEdge)) { pos = PositionTop; } else if (edges.testFlag(Qt::BottomEdge)) { pos = PositionBottom; } if (edges.testFlag(Qt::LeftEdge)) { pos = Position(pos | PositionLeft); } else if (edges.testFlag(Qt::RightEdge)) { pos = Position(pos | PositionRight); } return pos; }; setMoveResizePointerMode(toPosition()); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); } ); connect(shellSurface, &T::maximizedChanged, this, [this] (bool maximized) { if (m_shellSurface && isFullScreen()) { // ignore for wl_shell - there it is mutual exclusive and messes with the geometry return; } maximize(maximized ? MaximizeFull : MaximizeRestore); } ); // TODO: consider output! connect(shellSurface, &T::fullscreenChanged, this, &ShellClient::clientFullScreenChanged); connect(shellSurface, &T::transientForChanged, this, &ShellClient::setTransient); } void ShellClient::init() { connect(this, &ShellClient::desktopFileNameChanged, this, &ShellClient::updateIcon); findInternalWindow(); createWindowId(); setupCompositing(); SurfaceInterface *s = surface(); Q_ASSERT(s); if (s->buffer()) { setReadyForPainting(); if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } m_unmapped = false; m_clientSize = s->buffer()->size(); } else { ready_for_painting = false; } if (m_internalWindow) { updateInternalWindowGeometry(); setOnAllDesktops(true); updateDecoration(true); } else { doSetGeometry(QRect(QPoint(0, 0), m_clientSize)); setDesktop(VirtualDesktopManager::self()->current()); } if (waylandServer()->inputMethodConnection() == s->client()) { m_windowType = NET::OnScreenDisplay; } connect(s, &SurfaceInterface::sizeChanged, this, [this] { m_clientSize = surface()->buffer()->size(); doSetGeometry(QRect(geom.topLeft(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } ); connect(s, &SurfaceInterface::unmapped, this, &ShellClient::unmap); connect(s, &SurfaceInterface::unbound, this, &ShellClient::destroyClient); connect(s, &SurfaceInterface::destroyed, this, &ShellClient::destroyClient); if (m_shellSurface) { initSurface(m_shellSurface); } else if (m_xdgShellSurface) { initSurface(m_xdgShellSurface); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowMenuRequested, this, [this] (SeatInterface *seat, quint32 serial, const QPoint &surfacePos) { // TODO: check serial on seat Q_UNUSED(seat) Q_UNUSED(serial) performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); } ); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::minimizeRequested, this, [this] { performMouseCommand(Options::MouseMinimize, Cursor::pos()); } ); auto configure = [this] { if (m_closing) { return; } m_xdgShellSurface->configure(xdgSurfaceStates()); }; connect(this, &AbstractClient::activeChanged, this, configure); connect(this, &AbstractClient::clientStartUserMovedResized, this, configure); connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure); } else if (m_xdgShellPopup) { connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &ShellClient::destroyClient); } // setup shadow integration getShadow(); connect(s, &SurfaceInterface::shadowChanged, this, &Toplevel::getShadow); setTransient(); // check whether we have a ServerSideDecoration if (ServerSideDecorationInterface *deco = ServerSideDecorationInterface::get(s)) { installServerSideDecoration(deco); } updateColorScheme(QString()); + updateApplicationMenu(); } void ShellClient::destroyClient() { m_closing = true; Deleted *del = nullptr; if (workspace()) { del = Deleted::create(this); } emit windowClosed(this, del); destroyWindowManagementInterface(); destroyDecoration(); if (workspace()) { StackingUpdatesBlocker blocker(workspace()); if (transientFor()) { transientFor()->removeTransient(this); } for (auto it = transients().constBegin(); it != transients().constEnd();) { if ((*it)->transientFor() == this) { removeTransient(*it); it = transients().constBegin(); // restart, just in case something more has changed with the list } else { ++it; } } } waylandServer()->removeClient(this); if (del) { del->unrefWindow(); } m_shellSurface = nullptr; m_xdgShellSurface = nullptr; m_xdgShellPopup = nullptr; deleteClient(this); } void ShellClient::deleteClient(ShellClient *c) { delete c; } QStringList ShellClient::activities() const { // TODO: implement return QStringList(); } QPoint ShellClient::clientContentPos() const { return -1 * clientPos(); } QSize ShellClient::clientSize() const { // TODO: connect for changes return m_clientSize; } void ShellClient::debug(QDebug &stream) const { // TODO: implement Q_UNUSED(stream) } Layer ShellClient::layerForDock() const { if (m_plasmaShellSurface) { switch (m_plasmaShellSurface->panelBehavior()) { case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: return NormalLayer; case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: return AboveLayer; case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: return DockLayer; default: Q_UNREACHABLE(); break; } } return AbstractClient::layerForDock(); } QRect ShellClient::transparentRect() const { // TODO: implement return QRect(); } NET::WindowType ShellClient::windowType(bool direct, int supported_types) const { // TODO: implement Q_UNUSED(direct) Q_UNUSED(supported_types) return m_windowType; } double ShellClient::opacity() const { return m_opacity; } void ShellClient::setOpacity(double opacity) { const qreal newOpacity = qBound(0.0, opacity, 1.0); if (newOpacity == m_opacity) { return; } const qreal oldOpacity = m_opacity; m_opacity = newOpacity; addRepaintFull(); emit opacityChanged(this, oldOpacity); } void ShellClient::addDamage(const QRegion &damage) { auto s = surface(); if (s->buffer()->size().isValid()) { m_clientSize = s->buffer()->size(); QPoint position = geom.topLeft(); if (m_positionAfterResize.isValid()) { addLayerRepaint(geometry()); position = m_positionAfterResize.point(); m_positionAfterResize.clear(); } doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } markAsMapped(); setDepth((s->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24); repaints_region += damage.translated(clientPos()); Toplevel::addDamage(damage); } void ShellClient::setInternalFramebufferObject(const QSharedPointer &fbo) { if (fbo.isNull()) { unmap(); return; } markAsMapped(); m_clientSize = fbo->size(); doSetGeometry(QRect(geom.topLeft(), m_clientSize)); Toplevel::setInternalFramebufferObject(fbo); Toplevel::addDamage(QRegion(0, 0, width(), height())); } void ShellClient::markAsMapped() { if (!m_unmapped) { return; } m_unmapped = false; if (!ready_for_painting) { setReadyForPainting(); } else { addRepaintFull(); emit windowShown(this); } if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } updateShowOnScreenEdge(); } void ShellClient::createDecoration(const QRect &oldGeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { GeometryUpdatesBlocker blocker(this); RequestGeometryBlocker requestBlocker(this); QRect oldgeom = geometry(); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); } setDecoration(decoration); // TODO: ensure the new geometry still fits into the client area (e.g. maximized windows) doSetGeometry(QRect(oldGeom.topLeft(), m_clientSize + (decoration ? QSize(decoration->borderLeft() + decoration->borderRight(), decoration->borderBottom() + decoration->borderTop()) : QSize()))); emit geometryShapeChanged(this, oldGeom); } void ShellClient::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); if (m_serverDecoration && isDecorated()) { m_serverDecoration->setMode(KWayland::Server::ServerSideDecorationManagerInterface::Mode::Server); } getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); blockGeometryUpdates(false); } void ShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { Q_UNUSED(force) // TODO: better merge with Client's implementation if (QSize(w, h) == geom.size()) { // size didn't change, update directly doSetGeometry(QRect(x, y, w, h)); } else { // size did change, Client needs to provide a new buffer requestGeometry(QRect(x, y, w, h)); } } void ShellClient::doSetGeometry(const QRect &rect) { if (geom == rect && pendingGeometryUpdate() == PendingGeometryNone) { return; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } const QRect old = geom; geom = rect; if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) { // use first valid geometry as restore geometry m_geomMaximizeRestore = geom; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } syncGeometryToInternalWindow(); if (hasStrut()) { workspace()->updateClientArea(); } emit geometryShapeChanged(this, old); } void ShellClient::doMove(int x, int y) { Q_UNUSED(x) Q_UNUSED(y) syncGeometryToInternalWindow(); } void ShellClient::syncGeometryToInternalWindow() { if (!m_internalWindow) { return; } const QRect windowRect = QRect(geom.topLeft() + QPoint(borderLeft(), borderTop()), geom.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom())); if (m_internalWindow->geometry() != windowRect) { m_internalWindow->setGeometry(windowRect); } } QByteArray ShellClient::windowRole() const { return QByteArray(); } bool ShellClient::belongsToSameApplication(const AbstractClient *other, bool active_hack) const { Q_UNUSED(active_hack) if (auto s = other->surface()) { return s->client() == surface()->client(); } return false; } void ShellClient::blockActivityUpdates(bool b) { Q_UNUSED(b) } QString ShellClient::caption(bool full, bool stripped) const { Q_UNUSED(full) Q_UNUSED(stripped) return m_caption; } void ShellClient::closeWindow() { if (m_xdgShellSurface && isCloseable()) { m_xdgShellSurface->close(); return; } if (m_qtExtendedSurface && isCloseable()) { m_qtExtendedSurface->close(); } if (m_internalWindow) { m_internalWindow->hide(); } } AbstractClient *ShellClient::findModal(bool allow_itself) { Q_UNUSED(allow_itself) return nullptr; } bool ShellClient::isCloseable() const { if (m_windowType == NET::Desktop || m_windowType == NET::Dock) { return false; } if (m_xdgShellSurface) { return true; } if (m_internal) { return true; } return m_qtExtendedSurface ? true : false; } bool ShellClient::isFullScreenable() const { return false; } bool ShellClient::isFullScreen() const { return m_fullScreen; } bool ShellClient::isMaximizable() const { if (m_internal) { return false; } return true; } bool ShellClient::isMinimizable() const { if (m_internal) { return false; } return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal); } bool ShellClient::isMovable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isMovableAcrossScreens() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isResizable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isShown(bool shaded_is_shown) const { Q_UNUSED(shaded_is_shown) return !m_closing && !m_unmapped && !isMinimized() && !m_hidden; } void ShellClient::hideClient(bool hide) { if (m_hidden == hide) { return; } m_hidden = hide; if (hide) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); } else { emit windowShown(this); } } static bool changeMaximizeRecursion = false; void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) { if (changeMaximizeRecursion) { return; } if (!isResizable()) { return; } const QRect clientArea = isElectricBorderMaximizing() ? workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()) : workspace()->clientArea(MaximizeArea, this); MaximizeMode oldMode = m_maximizeMode; StackingUpdatesBlocker blocker(workspace()); RequestGeometryBlocker geometryBlocker(this); // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeVertical); if (horizontal) m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeHorizontal); } // TODO: add more checks as in Client // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_maximizeMode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((m_maximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(m_maximizeMode & MaximizeVertical); } if ((m_maximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(m_maximizeMode & MaximizeHorizontal); } if ((m_maximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { emit c->maximizedChanged(m_maximizeMode & MaximizeFull); } changeMaximizeRecursion = false; } if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry changeMaximizeRecursion = true; setNoBorder(rules()->checkNoBorder(m_maximizeMode == MaximizeFull)); changeMaximizeRecursion = false; } // Conditional quick tiling exit points const auto oldQuickTileMode = quickTileMode(); if (quickTileMode() != QuickTileNone) { if (oldMode == MaximizeFull && !clientArea.contains(m_geomMaximizeRestore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually } else if ((oldMode == MaximizeVertical && m_maximizeMode == MaximizeRestore) || (oldMode == MaximizeFull && m_maximizeMode == MaximizeHorizontal)) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileNone); // Exit quick tile mode without restoring geometry } } // TODO: check rules if (m_maximizeMode == MaximizeFull) { m_geomMaximizeRestore = geometry(); // TODO: Client has more checks if (options->electricBorderMaximize()) { updateQuickTileMode(QuickTileMaximize); } else { updateQuickTileMode(QuickTileNone); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } requestGeometry(workspace()->clientArea(MaximizeArea, this)); workspace()->raiseClient(this); } else { if (m_maximizeMode == MaximizeRestore) { updateQuickTileMode(QuickTileNone); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } if (m_geomMaximizeRestore.isValid()) { requestGeometry(m_geomMaximizeRestore); } else { requestGeometry(workspace()->clientArea(PlacementArea, this)); } } } MaximizeMode ShellClient::maximizeMode() const { return m_maximizeMode; } bool ShellClient::noBorder() const { if (isInternal()) { return m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup); } if (m_serverDecoration) { if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return m_userNoBorder || isFullScreen(); } } return true; } const WindowRules *ShellClient::rules() const { static WindowRules s_rules; return &s_rules; } void ShellClient::setFullScreen(bool set, bool user) { Q_UNUSED(set) Q_UNUSED(user) } void ShellClient::setNoBorder(bool set) { if (!userCanSetNoBorder()) { return; } set = rules()->checkNoBorder(set); if (m_userNoBorder == set) { return; } m_userNoBorder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void ShellClient::setOnAllActivities(bool set) { Q_UNUSED(set) } void ShellClient::setShortcut(const QString &cut) { Q_UNUSED(cut) } const QKeySequence &ShellClient::shortcut() const { static QKeySequence seq; return seq; } void ShellClient::takeFocus() { if (rules()->checkAcceptFocus(wantsInput())) { setActive(true); } bool breakShowingDesktop = !keepAbove() && !isOnScreenDisplay(); if (breakShowingDesktop) { // check that it doesn't belong to the desktop const auto &clients = waylandServer()->clients(); for (auto c: clients) { if (!belongsToSameApplication(c, false)) { continue; } if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } void ShellClient::doSetActive() { if (!isActive()) { return; } StackingUpdatesBlocker blocker(workspace()); workspace()->focusToNull(); } void ShellClient::updateWindowRules(Rules::Types selection) { Q_UNUSED(selection) } bool ShellClient::userCanSetFullScreen() const { return false; } bool ShellClient::userCanSetNoBorder() const { if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return !isFullScreen() && !isShade() && !tabGroup(); } if (m_internal) { return !m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup); } return false; } bool ShellClient::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus()); } bool ShellClient::acceptsFocus() const { if (isInternal()) { return false; } if (waylandServer()->inputMethodConnection() == surface()->client()) { return false; } if (m_plasmaShellSurface) { if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification) { return false; } } if (m_closing) { // a closing window does not accept focus return false; } if (m_unmapped) { // an unmapped window does not accept focus return false; } if (m_shellSurface) { if (m_shellSurface->isPopup()) { return false; } return m_shellSurface->acceptsKeyboardFocus(); } if (m_xdgShellSurface) { // TODO: proper return true; } return false; } void ShellClient::createWindowId() { if (m_internalWindow) { m_windowId = m_internalWindow->winId(); } else { m_windowId = waylandServer()->createWindowId(surface()); } } void ShellClient::findInternalWindow() { if (surface()->client() != waylandServer()->internalConnection()) { return; } const QWindowList windows = kwinApp()->topLevelWindows(); for (QWindow *w: windows) { auto s = KWayland::Client::Surface::fromWindow(w); if (!s) { continue; } if (s->id() != surface()->id()) { continue; } m_internalWindow = w; m_internalWindowFlags = m_internalWindow->flags(); connect(m_internalWindow, &QWindow::xChanged, this, &ShellClient::updateInternalWindowGeometry); connect(m_internalWindow, &QWindow::yChanged, this, &ShellClient::updateInternalWindowGeometry); connect(m_internalWindow, &QWindow::destroyed, this, [this] { m_internalWindow = nullptr; }); connect(m_internalWindow, &QWindow::opacityChanged, this, &ShellClient::setOpacity); // Try reading the window type from the QWindow. PlasmaCore.Dialog provides a dynamic type property // let's check whether it exists, if it does it's our window type const QVariant windowType = m_internalWindow->property("type"); if (!windowType.isNull()) { m_windowType = static_cast(windowType.toInt()); } setOpacity(m_internalWindow->opacity()); return; } } void ShellClient::updateInternalWindowGeometry() { if (!m_internalWindow) { return; } doSetGeometry(QRect(m_internalWindow->geometry().topLeft() - QPoint(borderLeft(), borderTop()), m_internalWindow->geometry().size() + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } bool ShellClient::isInternal() const { return m_internal; } bool ShellClient::isLockScreen() const { return surface()->client() == waylandServer()->screenLockerClientConnection(); } bool ShellClient::isInputMethod() const { if (m_internal && m_internalWindow) { return m_internalWindow->property("__kwin_input_method").toBool(); } return surface()->client() == waylandServer()->inputMethodConnection(); } void ShellClient::requestGeometry(const QRect &rect) { if (m_requestGeometryBlockCounter != 0) { m_blockedRequestGeometry = rect; return; } m_positionAfterResize.setPoint(rect.topLeft()); const QSize size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); if (m_shellSurface) { m_shellSurface->requestSize(size); } if (m_xdgShellSurface) { m_xdgShellSurface->configure(xdgSurfaceStates(), size); } m_blockedRequestGeometry = QRect(); if (m_internal) { m_internalWindow->setGeometry(QRect(rect.topLeft() + QPoint(borderLeft(), borderTop()), rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } } void ShellClient::clientFullScreenChanged(bool fullScreen) { RequestGeometryBlocker requestBlocker(this); StackingUpdatesBlocker blocker(workspace()); const bool emitSignal = m_fullScreen != fullScreen; m_fullScreen = fullScreen; updateDecoration(false, false); workspace()->updateClientLayer(this); // active fullscreens get different layer if (fullScreen) { m_geomFsRestore = geometry(); requestGeometry(workspace()->clientArea(FullScreenArea, this)); workspace()->raiseClient(this); } else { if (m_geomFsRestore.isValid()) { requestGeometry(m_geomFsRestore); } else { requestGeometry(workspace()->clientArea(MaximizeArea, this)); } } if (emitSignal) { emit fullScreenChanged(); } } void ShellClient::resizeWithChecks(int w, int h, ForceGeometry_t force) { Q_UNUSED(force) QRect area = workspace()->clientArea(WorkArea, this); // don't allow growing larger than workarea if (w > area.width()) { w = area.width(); } if (h > area.height()) { h = area.height(); } if (m_shellSurface) { m_shellSurface->requestSize(QSize(w, h)); } if (m_xdgShellSurface) { m_xdgShellSurface->configure(xdgSurfaceStates(), QSize(w, h)); } if (m_internal) { m_internalWindow->setGeometry(QRect(pos() + QPoint(borderLeft(), borderTop()), QSize(w, h) - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } } void ShellClient::unmap() { m_unmapped = true; destroyWindowManagementInterface(); if (Workspace::self()) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } emit windowHidden(this); } void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface) { m_plasmaShellSurface = surface; auto updatePosition = [this, surface] { doSetGeometry(QRect(surface->position(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); }; auto updateRole = [this, surface] { NET::WindowType type = NET::Unknown; switch (surface->role()) { case PlasmaShellSurfaceInterface::Role::Desktop: type = NET::Desktop; break; case PlasmaShellSurfaceInterface::Role::Panel: type = NET::Dock; break; case PlasmaShellSurfaceInterface::Role::OnScreenDisplay: type = NET::OnScreenDisplay; break; case PlasmaShellSurfaceInterface::Role::Notification: type = NET::Notification; break; case PlasmaShellSurfaceInterface::Role::ToolTip: type = NET::Tooltip; break; case PlasmaShellSurfaceInterface::Role::Normal: default: type = NET::Normal; break; } if (type != m_windowType) { m_windowType = type; if (m_windowType == NET::Desktop || type == NET::Dock || type == NET::OnScreenDisplay || type == NET::Notification || type == NET::Tooltip) { setOnAllDesktops(true); } workspace()->updateClientArea(); } }; connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [this] { updateShowOnScreenEdge(); workspace()->updateClientArea(); } ); connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, [this] { hideClient(true); m_plasmaShellSurface->hideAutoHidingPanel(); updateShowOnScreenEdge(); } ); connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, [this] { hideClient(false); ScreenEdges::self()->reserve(this, ElectricNone); m_plasmaShellSurface->showAutoHidingPanel(); } ); updatePosition(); updateRole(); updateShowOnScreenEdge(); connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateShowOnScreenEdge); setSkipTaskbar(surface->skipTaskbar()); connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { setSkipTaskbar(m_plasmaShellSurface->skipTaskbar()); }); } void ShellClient::updateShowOnScreenEdge() { if (!ScreenEdges::self()) { return; } if (m_unmapped || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { ScreenEdges::self()->reserve(this, ElectricNone); return; } if ((m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && m_hidden) || m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) { // screen edge API requires an edge, thus we need to figure out which edge the window borders Qt::Edges edges; for (int i = 0; i < screens()->count(); i++) { const auto &screenGeo = screens()->geometry(i); if (screenGeo.x() == geom.x()) { edges |= Qt::LeftEdge; } if (screenGeo.x() + screenGeo.width() == geom.x() + geom.width()) { edges |= Qt::RightEdge; } if (screenGeo.y() == geom.y()) { edges |= Qt::TopEdge; } if (screenGeo.y() + screenGeo.height() == geom.y() + geom.height()) { edges |= Qt::BottomEdge; } } // a panel might border multiple screen edges. E.g. a horizontal panel at the bottom will // also border the left and right edge // let's remove such cases if (edges.testFlag(Qt::LeftEdge) && edges.testFlag(Qt::RightEdge)) { edges = edges & (~(Qt::LeftEdge | Qt::RightEdge)); } if (edges.testFlag(Qt::TopEdge) && edges.testFlag(Qt::BottomEdge)) { edges = edges & (~(Qt::TopEdge | Qt::BottomEdge)); } // it's still possible that a panel borders two edges, e.g. bottom and left // in that case the one which is sharing more with the edge wins auto check = [this](Qt::Edges edges, Qt::Edge horiz, Qt::Edge vert) { if (edges.testFlag(horiz) && edges.testFlag(vert)) { if (geom.width() >= geom.height()) { return edges & ~horiz; } else { return edges & ~vert; } } return edges; }; edges = check(edges, Qt::LeftEdge, Qt::TopEdge); edges = check(edges, Qt::LeftEdge, Qt::BottomEdge); edges = check(edges, Qt::RightEdge, Qt::TopEdge); edges = check(edges, Qt::RightEdge, Qt::BottomEdge); ElectricBorder border = ElectricNone; if (edges.testFlag(Qt::LeftEdge)) { border = ElectricLeft; } if (edges.testFlag(Qt::RightEdge)) { border = ElectricRight; } if (edges.testFlag(Qt::TopEdge)) { border = ElectricTop; } if (edges.testFlag(Qt::BottomEdge)) { border = ElectricBottom; } ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } bool ShellClient::isInitialPositionSet() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->isPositionSet(); } return false; } void ShellClient::installQtExtendedSurface(QtExtendedSurfaceInterface *surface) { m_qtExtendedSurface = surface; connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::raiseRequested, this, [this]() { workspace()->raiseClientRequest(this); }); connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::lowerRequested, this, [this]() { workspace()->lowerClientRequest(this); }); m_qtExtendedSurface->installEventFilter(this); } bool ShellClient::eventFilter(QObject *watched, QEvent *event) { if (watched == m_qtExtendedSurface.data() && event->type() == QEvent::DynamicPropertyChange) { QDynamicPropertyChangeEvent *pe = static_cast(event); if (pe->propertyName() == s_schemePropertyName) { updateColorScheme(rules()->checkDecoColor(m_qtExtendedSurface->property(pe->propertyName().constData()).toString())); + } else if (pe->propertyName() == s_appMenuServiceNamePropertyName) { + updateApplicationMenuServiceName(m_qtExtendedSurface->property(pe->propertyName().constData()).toString()); + } else if (pe->propertyName() == s_appMenuObjectPathPropertyName) { + updateApplicationMenuObjectPath(m_qtExtendedSurface->property(pe->propertyName().constData()).toString()); } } return false; } bool ShellClient::hasStrut() const { if (!isShown(true)) { return false; } if (!m_plasmaShellSurface) { return false; } if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { return false; } return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible; } void ShellClient::updateIcon() { const QString waylandIconName = QStringLiteral("wayland"); const QString dfIconName = iconFromDesktopFile(); const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName; if (iconName == icon().name()) { return; } setIcon(QIcon::fromTheme(iconName)); } bool ShellClient::isTransient() const { return m_transient; } void ShellClient::setTransient() { SurfaceInterface *s = nullptr; if (m_shellSurface) { s = m_shellSurface->transientFor().data(); } if (m_xdgShellSurface) { if (auto transient = m_xdgShellSurface->transientFor().data()) { s = transient->surface(); } } if (m_xdgShellPopup) { s = m_xdgShellPopup->transientFor().data(); } auto t = waylandServer()->findClient(s); if (t != transientFor()) { // remove from main client if (transientFor()) transientFor()->removeTransient(this); setTransientFor(t); if (t) { t->addTransient(this); } } m_transient = (s != nullptr); } bool ShellClient::hasTransientPlacementHint() const { return isTransient() && transientFor() != nullptr; } QPoint ShellClient::transientPlacementHint() const { if (m_shellSurface) { return m_shellSurface->transientOffset(); } if (m_xdgShellPopup) { return m_xdgShellPopup->transientOffset(); } return QPoint(); } bool ShellClient::isWaitingForMoveResizeSync() const { return m_positionAfterResize.isValid(); } void ShellClient::doResizeSync() { requestGeometry(moveResizeGeometry()); } QMatrix4x4 ShellClient::inputTransformation() const { QMatrix4x4 m = Toplevel::inputTransformation(); m.translate(-borderLeft(), -borderTop()); return m; } void ShellClient::installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *deco) { if (m_serverDecoration == deco) { return; } m_serverDecoration = deco; connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, [this] { m_serverDecoration = nullptr; if (m_closing || !Workspace::self()) { return; } if (!m_unmapped) { // maybe delay to next event cycle in case the ShellClient is getting destroyed, too updateDecoration(true); } } ); if (!m_unmapped) { updateDecoration(true); } connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, [this] (ServerSideDecorationManagerInterface::Mode mode) { const bool changed = mode != m_serverDecoration->mode(); // always acknowledge the requested mode m_serverDecoration->setMode(mode); if (changed && !m_unmapped) { updateDecoration(false); } } ); } bool ShellClient::shouldExposeToWindowManagement() { if (isInternal()) { return false; } if (isLockScreen()) { return false; } if (m_shellSurface) { if (m_shellSurface->isTransient() && !m_shellSurface->acceptsKeyboardFocus()) { return false; } } return true; } KWayland::Server::XdgShellSurfaceInterface::States ShellClient::xdgSurfaceStates() const { XdgShellSurfaceInterface::States states; if (isActive()) { states |= XdgShellSurfaceInterface::State::Activated; } if (isFullScreen()) { states |= XdgShellSurfaceInterface::State::Fullscreen; } if (maximizeMode() == MaximizeMode::MaximizeFull) { states |= XdgShellSurfaceInterface::State::Maximized; } if (isResize()) { states |= XdgShellSurfaceInterface::State::Resizing; } return states; } void ShellClient::doMinimize() { if (isMinimized()) { workspace()->clientHidden(this); } else { emit windowShown(this); } } bool ShellClient::setupCompositing() { if (m_compositingSetup) { return true; } m_compositingSetup = Toplevel::setupCompositing(); return m_compositingSetup; } void ShellClient::finishCompositing(ReleaseReason releaseReason) { m_compositingSetup = false; Toplevel::finishCompositing(releaseReason); } void ShellClient::placeIn(QRect &area) { Placement::self()->place(this, area); setGeometryRestore(geometry()); } void ShellClient::showOnScreenEdge() { if (!m_plasmaShellSurface || m_unmapped) { return; } hideClient(false); workspace()->raiseClient(this); if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) { m_plasmaShellSurface->showAutoHidingPanel(); } } bool ShellClient::dockWantsInput() const { if (m_plasmaShellSurface) { if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) { return m_plasmaShellSurface->panelTakesFocus(); } } return false; } void ShellClient::killWindow() { if (isInternal()) { return; } if (!surface()) { return; } auto c = surface()->client(); if (c->processId() == getpid()) { c->destroy(); return; } ::kill(c->processId(), SIGTERM); // give it time to terminate and only if terminate fails, try destroy Wayland connection QTimer::singleShot(5000, c, &ClientConnection::destroy); } +void ShellClient::updateApplicationMenu() +{ + if (m_qtExtendedSurface) { + updateApplicationMenuServiceName(m_qtExtendedSurface->property(s_appMenuObjectPathPropertyName).toString()); + updateApplicationMenuObjectPath(m_qtExtendedSurface->property(s_appMenuServiceNamePropertyName).toString()); + } +} + } diff --git a/shell_client.h b/shell_client.h index 6c3f38eb8..54fca4d18 100644 --- a/shell_client.h +++ b/shell_client.h @@ -1,235 +1,237 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_SHELL_CLIENT_H #define KWIN_SHELL_CLIENT_H #include "abstract_client.h" #include namespace KWayland { namespace Server { class ShellSurfaceInterface; class ServerSideDecorationInterface; class PlasmaShellSurfaceInterface; class QtExtendedSurfaceInterface; } } namespace KWin { class KWIN_EXPORT ShellClient : public AbstractClient { Q_OBJECT public: ShellClient(KWayland::Server::ShellSurfaceInterface *surface); ShellClient(KWayland::Server::XdgShellSurfaceInterface *surface); ShellClient(KWayland::Server::XdgShellPopupInterface *surface); virtual ~ShellClient(); bool eventFilter(QObject *watched, QEvent *event) override; QStringList activities() const override; QPoint clientContentPos() const override; QSize clientSize() const override; QRect transparentRect() const override; NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; void debug(QDebug &stream) const override; double opacity() const override; void setOpacity(double opacity) override; QByteArray windowRole() const override; KWayland::Server::ShellSurfaceInterface *shellSurface() const { return m_shellSurface; } void blockActivityUpdates(bool b = true) override; QString caption(bool full = true, bool stripped = false) const override; void closeWindow() override; AbstractClient *findModal(bool allow_itself = false) override; bool isCloseable() const override; bool isFullScreenable() const override; bool isFullScreen() const override; bool isMaximizable() const override; bool isMinimizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isResizable() const override; bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override { return m_unmapped || m_hidden; } void hideClient(bool hide) override; MaximizeMode maximizeMode() const override; QRect geometryRestore() const override { return m_geomMaximizeRestore; } bool noBorder() const override; const WindowRules *rules() const override; void setFullScreen(bool set, bool user = true) override; void setNoBorder(bool set) override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void setOnAllActivities(bool set) override; void setShortcut(const QString &cut) override; const QKeySequence &shortcut() const override; void takeFocus() override; void updateWindowRules(Rules::Types selection) override; bool userCanSetFullScreen() const override; bool userCanSetNoBorder() const override; bool wantsInput() const override; bool dockWantsInput() const override; using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; bool hasStrut() const override; void setInternalFramebufferObject(const QSharedPointer &fbo) override; quint32 windowId() const override { return m_windowId; } bool isInternal() const; bool isLockScreen() const override; bool isInputMethod() const override; QWindow *internalWindow() const { return m_internalWindow; } void installPlasmaShellSurface(KWayland::Server::PlasmaShellSurfaceInterface *surface); void installQtExtendedSurface(KWayland::Server::QtExtendedSurfaceInterface *surface); void installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *decoration); bool isInitialPositionSet() const; bool isTransient() const override; bool hasTransientPlacementHint() const override; QPoint transientPlacementHint() const override; QMatrix4x4 inputTransformation() const override; bool setupCompositing() override; void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void showOnScreenEdge() override; void killWindow() override; // TODO: const-ref void placeIn(QRect &area); + void updateApplicationMenu(); + protected: void addDamage(const QRegion &damage) override; bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const override; void doSetActive() override; Layer layerForDock() const override; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; void setGeometryRestore(const QRect &geo) override { m_geomMaximizeRestore = geo; } void doResizeSync() override; bool isWaitingForMoveResizeSync() const override; bool acceptsFocus() const override; void doMinimize() override; void doMove(int x, int y) override; private Q_SLOTS: void clientFullScreenChanged(bool fullScreen); private: void init(); template void initSurface(T *shellSurface); void requestGeometry(const QRect &rect); void doSetGeometry(const QRect &rect); void createDecoration(const QRect &oldgeom); void destroyClient(); void unmap(); void createWindowId(); void findInternalWindow(); void updateInternalWindowGeometry(); void syncGeometryToInternalWindow(); void updateIcon(); void markAsMapped(); void setTransient(); bool shouldExposeToWindowManagement(); KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const; void updateShowOnScreenEdge(); static void deleteClient(ShellClient *c); KWayland::Server::ShellSurfaceInterface *m_shellSurface; KWayland::Server::XdgShellSurfaceInterface *m_xdgShellSurface; KWayland::Server::XdgShellPopupInterface *m_xdgShellPopup; QSize m_clientSize; ClearablePoint m_positionAfterResize; // co-ordinates saved from a requestGeometry call, real geometry will be updated after the next damage event when the client has resized QRect m_geomFsRestore; //size and position of the window before it was set to fullscreen bool m_closing = false; quint32 m_windowId = 0; QWindow *m_internalWindow = nullptr; Qt::WindowFlags m_internalWindowFlags = Qt::WindowFlags(); bool m_unmapped = true; MaximizeMode m_maximizeMode = MaximizeRestore; QRect m_geomMaximizeRestore; // size and position of the window before it was set to maximize NET::WindowType m_windowType = NET::Normal; QPointer m_plasmaShellSurface; QPointer m_qtExtendedSurface; KWayland::Server::ServerSideDecorationInterface *m_serverDecoration = nullptr; bool m_userNoBorder = false; bool m_fullScreen = false; bool m_transient = false; bool m_hidden = false; bool m_internal; qreal m_opacity = 1.0; class RequestGeometryBlocker { public: RequestGeometryBlocker(ShellClient *client) : m_client(client) { m_client->m_requestGeometryBlockCounter++; } ~RequestGeometryBlocker() { m_client->m_requestGeometryBlockCounter--; if (m_client->m_requestGeometryBlockCounter == 0) { if (m_client->m_blockedRequestGeometry.isValid()) { m_client->requestGeometry(m_client->m_blockedRequestGeometry); } } } private: ShellClient *m_client; }; friend class RequestGeometryBlocker; int m_requestGeometryBlockCounter = 0; QRect m_blockedRequestGeometry; QString m_caption; bool m_compositingSetup = false; }; } Q_DECLARE_METATYPE(KWin::ShellClient*) #endif diff --git a/useractions.cpp b/useractions.cpp index 983ad8795..502be96b3 100755 --- a/useractions.cpp +++ b/useractions.cpp @@ -1,1886 +1,1892 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to direct user actions, such as responses to global keyboard shortcuts, or selecting actions from the window operations menu. */ /////////////////////////////////////////////////////////////////////////////// // NOTE: if you change the menu, keep kde-workspace/libs/taskmanager/taskactions.cpp in sync ////////////////////////////////////////////////////////////////////////////// #include "useractions.h" #include "cursor.h" #include "client.h" #include "input.h" #include "workspace.h" #include "effects.h" #include "screens.h" #include "virtualdesktops.h" #include "scripting/scripting.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #include #endif +#include "appmenu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "killwindow.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif namespace KWin { UserActionsMenu::UserActionsMenu(QObject *parent) : QObject(parent) , m_menu(NULL) , m_desktopMenu(NULL) , m_screenMenu(NULL) , m_activityMenu(NULL) , m_addTabsMenu(NULL) , m_switchToTabMenu(NULL) , m_scriptsMenu(NULL) , m_resizeOperation(NULL) , m_moveOperation(NULL) , m_maximizeOperation(NULL) , m_shadeOperation(NULL) , m_keepAboveOperation(NULL) , m_keepBelowOperation(NULL) , m_fullScreenOperation(NULL) , m_noBorderOperation(NULL) , m_minimizeOperation(NULL) , m_closeOperation(NULL) , m_removeFromTabGroup(NULL) , m_closeTabGroup(NULL) , m_client(QWeakPointer()) { } UserActionsMenu::~UserActionsMenu() { discard(); } bool UserActionsMenu::isShown() const { return m_menu && m_menu->isVisible(); } bool UserActionsMenu::hasClient() const { return !m_client.isNull() && isShown(); } void UserActionsMenu::close() { if (!m_menu) { return; } m_menu->close(); m_client.clear(); } bool UserActionsMenu::isMenuClient(const AbstractClient *c) const { if (!c || m_client.isNull()) { return false; } return c == m_client.data(); } void UserActionsMenu::show(const QRect &pos, const QWeakPointer &cl) { if (!KAuthorized::authorizeAction(QStringLiteral("kwin_rmb"))) return; if (cl.isNull()) return; if (isShown()) // recursion return; if (cl.data()->isDesktop() || cl.data()->isDock()) return; m_client = cl; init(); Workspace *ws = Workspace::self(); int x = pos.left(); int y = pos.bottom(); const bool needsPopup = kwinApp()->shouldUseWaylandForCompositing(); if (y == pos.top()) { m_client.data()->blockActivityUpdates(true); if (needsPopup) { m_menu->popup(QPoint(x, y)); } else { m_menu->exec(QPoint(x, y)); } if (!m_client.isNull()) m_client.data()->blockActivityUpdates(false); } else { m_client.data()->blockActivityUpdates(true); QRect area = ws->clientArea(ScreenArea, QPoint(x, y), VirtualDesktopManager::self()->current()); menuAboutToShow(); // needed for sizeHint() to be correct :-/ int popupHeight = m_menu->sizeHint().height(); if (y + popupHeight < area.height()) { if (needsPopup) { m_menu->popup(QPoint(x, y)); } else { m_menu->exec(QPoint(x, y)); } } else { if (needsPopup) { m_menu->popup(QPoint(x, pos.top() - popupHeight)); } else { m_menu->exec(QPoint(x, pos.top() - popupHeight)); } } if (!m_client.isNull()) m_client.data()->blockActivityUpdates(true); } } void UserActionsMenu::grabInput() { m_menu->windowHandle()->setMouseGrabEnabled(true); m_menu->windowHandle()->setKeyboardGrabEnabled(true); } void UserActionsMenu::helperDialog(const QString& message, const QWeakPointer &c) { QStringList args; QString type; auto shortcut = [](const QString &name) { QAction* action = Workspace::self()->findChild(name); assert(action != NULL); const auto shortcuts = KGlobalAccel::self()->shortcut(action); return QStringLiteral("%1 (%2)").arg(action->text()) .arg(shortcuts.isEmpty() ? QString() : shortcuts.first().toString(QKeySequence::NativeText)); }; if (message == QStringLiteral("noborderaltf3")) { args << QStringLiteral("--msgbox") << i18n( "You have selected to show a window without its border.\n" "Without the border, you will not be able to enable the border " "again using the mouse: use the window operations menu instead, " "activated using the %1 keyboard shortcut.", shortcut(QStringLiteral("Window Operations Menu"))); type = QStringLiteral("altf3warning"); } else if (message == QLatin1String("fullscreenaltf3")) { args << QStringLiteral("--msgbox") << i18n( "You have selected to show a window in fullscreen mode.\n" "If the application itself does not have an option to turn the fullscreen " "mode off you will not be able to disable it " "again using the mouse: use the window operations menu instead, " "activated using the %1 keyboard shortcut.", shortcut(QStringLiteral("Window Operations Menu"))); type = QStringLiteral("altf3warning"); } else abort(); if (!type.isEmpty()) { KConfig cfg(QStringLiteral("kwin_dialogsrc")); KConfigGroup cg(&cfg, "Notification Messages"); // Depends on KMessageBox if (!cg.readEntry(type, true)) return; args << QStringLiteral("--dontagain") << QLatin1String("kwin_dialogsrc:") + type; } if (!c.isNull()) args << QStringLiteral("--embed") << QString::number(c.data()->windowId()); QtConcurrent::run([args]() { KProcess::startDetached(QStringLiteral("kdialog"), args); }); } QStringList configModules(bool controlCenter) { QStringList args; args << QStringLiteral("kwindecoration"); if (controlCenter) args << QStringLiteral("kwinoptions"); else if (KAuthorized::authorizeControlModule(QStringLiteral("kde-kwinoptions.desktop"))) args << QStringLiteral("kwinactions") << QStringLiteral("kwinfocus") << QStringLiteral("kwinmoving") << QStringLiteral("kwinadvanced") << QStringLiteral("kwinrules") << QStringLiteral("kwincompositing") << QStringLiteral("kwineffects") #ifdef KWIN_BUILD_TABBOX << QStringLiteral("kwintabbox") #endif << QStringLiteral("kwinscreenedges") << QStringLiteral("kwinscripts") ; return args; } void UserActionsMenu::init() { if (m_menu) { return; } m_menu = new QMenu; connect(m_menu, &QMenu::aboutToShow, this, &UserActionsMenu::menuAboutToShow); connect(m_menu, &QMenu::triggered, this, &UserActionsMenu::slotWindowOperation, Qt::QueuedConnection); QMenu *advancedMenu = new QMenu(m_menu); connect(advancedMenu, &QMenu::aboutToShow, [this, advancedMenu]() { advancedMenu->setPalette(m_client.data()->palette()); }); auto setShortcut = [](QAction *action, const QString &actionName) { const auto shortcuts = KGlobalAccel::self()->shortcut(Workspace::self()->findChild(actionName)); if (!shortcuts.isEmpty()) { action->setShortcut(shortcuts.first()); } }; m_moveOperation = advancedMenu->addAction(i18n("&Move")); m_moveOperation->setIcon(QIcon::fromTheme(QStringLiteral("transform-move"))); setShortcut(m_moveOperation, QStringLiteral("Window Move")); m_moveOperation->setData(Options::UnrestrictedMoveOp); m_resizeOperation = advancedMenu->addAction(i18n("&Resize")); setShortcut(m_resizeOperation, QStringLiteral("Window Resize")); m_resizeOperation->setData(Options::ResizeOp); m_keepAboveOperation = advancedMenu->addAction(i18n("Keep &Above Others")); m_keepAboveOperation->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); setShortcut(m_keepAboveOperation, QStringLiteral("Window Above Other Windows")); m_keepAboveOperation->setCheckable(true); m_keepAboveOperation->setData(Options::KeepAboveOp); m_keepBelowOperation = advancedMenu->addAction(i18n("Keep &Below Others")); m_keepBelowOperation->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); setShortcut(m_keepBelowOperation, QStringLiteral("Window Below Other Windows")); m_keepBelowOperation->setCheckable(true); m_keepBelowOperation->setData(Options::KeepBelowOp); m_fullScreenOperation = advancedMenu->addAction(i18n("&Fullscreen")); m_fullScreenOperation->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); setShortcut(m_fullScreenOperation, QStringLiteral("Window Fullscreen")); m_fullScreenOperation->setCheckable(true); m_fullScreenOperation->setData(Options::FullScreenOp); m_shadeOperation = advancedMenu->addAction(i18n("&Shade")); setShortcut(m_shadeOperation, QStringLiteral("Window Shade")); m_shadeOperation->setCheckable(true); m_shadeOperation->setData(Options::ShadeOp); m_noBorderOperation = advancedMenu->addAction(i18n("&No Border")); setShortcut(m_noBorderOperation, QStringLiteral("Window No Border")); m_noBorderOperation->setCheckable(true); m_noBorderOperation->setData(Options::NoBorderOp); advancedMenu->addSeparator(); m_shortcutOperation = advancedMenu->addAction(i18n("Window Short&cut...")); m_shortcutOperation->setIcon(QIcon::fromTheme(QStringLiteral("configure-shortcuts"))); setShortcut(m_shortcutOperation, QStringLiteral("Setup Window Shortcut")); m_shortcutOperation->setData(Options::SetupWindowShortcutOp); QAction *action = advancedMenu->addAction(i18n("Special &Window Settings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions"))); action->setData(Options::WindowRulesOp); action = advancedMenu->addAction(i18n("S&pecial Application Settings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions"))); action->setData(Options::ApplicationRulesOp); if (!kwinApp()->config()->isImmutable() && !KAuthorized::authorizeControlModules(configModules(true)).isEmpty()) { advancedMenu->addSeparator(); action = advancedMenu->addAction(i18nc("Entry in context menu of window decoration to open the configuration module of KWin", "Window Manager S&ettings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(action, &QAction::triggered, this, [this]() { // opens the KWin configuration QStringList args; args << QStringLiteral("--icon") << QStringLiteral("preferences-system-windows"); const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/kwinfocus.desktop")); if (!path.isEmpty()) { args << QStringLiteral("--desktopfile") << path; } args << configModules(false); QProcess *p = new Process(this); p->setArguments(args); p->setProcessEnvironment(kwinApp()->processStartupEnvironment()); p->setProgram(QStringLiteral("kcmshell5")); connect(p, static_cast(&QProcess::finished), p, &QProcess::deleteLater); connect(p, static_cast(&QProcess::error), this, [p] (QProcess::ProcessError e) { if (e == QProcess::FailedToStart) { qCDebug(KWIN_CORE) << "Failed to start kcmshell5"; } } ); p->start(); } ); } m_minimizeOperation = m_menu->addAction(i18n("Mi&nimize")); setShortcut(m_minimizeOperation, QStringLiteral("Window Minimize")); m_minimizeOperation->setData(Options::MinimizeOp); m_maximizeOperation = m_menu->addAction(i18n("Ma&ximize")); setShortcut(m_maximizeOperation, QStringLiteral("Window Maximize")); m_maximizeOperation->setCheckable(true); m_maximizeOperation->setData(Options::MaximizeOp); m_menu->addSeparator(); // Actions for window tabbing if (false) { m_removeFromTabGroup = m_menu->addAction(i18n("&Untab")); setShortcut(m_removeFromTabGroup, QStringLiteral("Untab")); m_removeFromTabGroup->setData(Options::RemoveTabFromGroupOp); m_closeTabGroup = m_menu->addAction(i18n("Close Entire &Group")); m_closeTabGroup->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); setShortcut(m_closeTabGroup, QStringLiteral("Close TabGroup")); m_closeTabGroup->setData(Options::CloseTabGroupOp); m_menu->addSeparator(); } m_menu->addSeparator(); action = m_menu->addMenu(advancedMenu); action->setText(i18n("&More Actions")); m_menu->addSeparator(); m_closeOperation = m_menu->addAction(i18n("&Close")); m_closeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); setShortcut(m_closeOperation, QStringLiteral("Window Close")); m_closeOperation->setData(Options::CloseOp); } void UserActionsMenu::discard() { delete m_menu; m_menu = NULL; m_desktopMenu = NULL; m_screenMenu = NULL; m_activityMenu = NULL; m_switchToTabMenu = NULL; m_addTabsMenu = NULL; m_scriptsMenu = NULL; } void UserActionsMenu::menuAboutToShow() { if (m_client.isNull() || !m_menu) return; if (VirtualDesktopManager::self()->count() == 1) { delete m_desktopMenu; m_desktopMenu = 0; } else { initDesktopPopup(); } if (screens()->count() == 1 || (!m_client.data()->isMovable() && !m_client.data()->isMovableAcrossScreens())) { delete m_screenMenu; m_screenMenu = NULL; } else { initScreenPopup(); } m_menu->setPalette(m_client.data()->palette()); m_resizeOperation->setEnabled(m_client.data()->isResizable()); m_moveOperation->setEnabled(m_client.data()->isMovableAcrossScreens()); m_maximizeOperation->setEnabled(m_client.data()->isMaximizable()); m_maximizeOperation->setChecked(m_client.data()->maximizeMode() == MaximizeFull); m_shadeOperation->setEnabled(m_client.data()->isShadeable()); m_shadeOperation->setChecked(m_client.data()->shadeMode() != ShadeNone); m_keepAboveOperation->setChecked(m_client.data()->keepAbove()); m_keepBelowOperation->setChecked(m_client.data()->keepBelow()); m_fullScreenOperation->setEnabled(m_client.data()->userCanSetFullScreen()); m_fullScreenOperation->setChecked(m_client.data()->isFullScreen()); m_noBorderOperation->setEnabled(m_client.data()->userCanSetNoBorder()); m_noBorderOperation->setChecked(m_client.data()->noBorder()); m_minimizeOperation->setEnabled(m_client.data()->isMinimizable()); m_closeOperation->setEnabled(m_client.data()->isCloseable()); m_shortcutOperation->setEnabled(m_client.data()->rules()->checkShortcut(QString()).isNull()); if (false) { initTabbingPopups(); m_addTabsMenu->setPalette(m_client.data()->palette()); } else { delete m_addTabsMenu; m_addTabsMenu = 0; } // drop the existing scripts menu delete m_scriptsMenu; m_scriptsMenu = NULL; // ask scripts whether they want to add entries for the given Client QList scriptActions = Scripting::self()->actionsForUserActionMenu(m_client.data(), m_scriptsMenu); if (!scriptActions.isEmpty()) { m_scriptsMenu = new QMenu(m_menu); m_scriptsMenu->setPalette(m_client.data()->palette()); m_scriptsMenu->addActions(scriptActions); QAction *action = m_scriptsMenu->menuAction(); // set it as the first item after desktop m_menu->insertAction(m_closeOperation, action); action->setText(i18n("&Extensions")); } showHideActivityMenu(); } void UserActionsMenu::showHideActivityMenu() { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } const QStringList &openActivities_ = Activities::self()->running(); qCDebug(KWIN_CORE) << "activities:" << openActivities_.size(); if (openActivities_.size() < 2) { delete m_activityMenu; m_activityMenu = 0; } else { initActivityPopup(); } #endif } void UserActionsMenu::selectPopupClientTab(QAction* action) { if (!(!m_client.isNull() && m_client.data()->tabGroup()) || !action->data().isValid()) return; if (Client *other = action->data().value()) { m_client.data()->tabGroup()->setCurrent(other); return; } // failed conversion, try "1" & "2", being prev and next int direction = action->data().toInt(); if (direction == 1) m_client.data()->tabGroup()->activatePrev(); else if (direction == 2) m_client.data()->tabGroup()->activateNext(); } static QString shortCaption(const QString &s) { if (s.length() < 64) return s; QString ss = s; return ss.replace(32,s.length()-64, QStringLiteral("...")); } void UserActionsMenu::rebuildTabListPopup() { Q_ASSERT(m_switchToTabMenu); m_switchToTabMenu->clear(); // whatever happens "0x1" and "0x2" are no heap positions ;-) m_switchToTabMenu->addAction(i18nc("Switch to tab -> Previous", "Previous"))->setData(1); m_switchToTabMenu->addAction(i18nc("Switch to tab -> Next", "Next"))->setData(2); m_switchToTabMenu->addSeparator(); for (QList::const_iterator i = m_client.data()->tabGroup()->clients().constBegin(), end = m_client.data()->tabGroup()->clients().constEnd(); i != end; ++i) { if ((*i)->noBorder() || *i == m_client.data()->tabGroup()->current()) continue; // cannot tab there anyway m_switchToTabMenu->addAction(shortCaption((*i)->caption()))->setData(QVariant::fromValue(*i)); } } void UserActionsMenu::entabPopupClient(QAction* action) { if (m_client.isNull() || !action->data().isValid()) return; Client *other = dynamic_cast(action->data().value()); if (!Workspace::self()->clientList().contains(other)) // might have been lost betwenn pop-up and selection return; Client *c = dynamic_cast(m_client.data()); if (!c) { return; } c->tabBehind(other, true); if (options->focusPolicyIsReasonable()) Workspace::self()->requestFocus(c); } void UserActionsMenu::rebuildTabGroupPopup() { Q_ASSERT(m_addTabsMenu); m_addTabsMenu->clear(); QList handled; const ClientList &clientList = Workspace::self()->clientList(); for (QList::const_iterator i = clientList.constBegin(), end = clientList.constEnd(); i != end; ++i) { if (*i == m_client.data() || (*i)->noBorder()) continue; m_addTabsMenu->addAction(shortCaption((*i)->caption()))->setData(QVariant::fromValue(*i)); } if (m_addTabsMenu->actions().isEmpty()) m_addTabsMenu->addAction(i18nc("There's no window available to be attached as tab to this one", "None available"))->setEnabled(false); } void UserActionsMenu::initTabbingPopups() { bool needTabManagers = false; if (m_client.data()->tabGroup() && m_client.data()->tabGroup()->count() > 1) { needTabManagers = true; if (!m_switchToTabMenu) { m_switchToTabMenu = new QMenu(i18n("Switch to Tab"), m_menu); connect(m_switchToTabMenu, &QMenu::triggered, this, &UserActionsMenu::selectPopupClientTab); connect(m_switchToTabMenu, &QMenu::aboutToShow, this, &UserActionsMenu::rebuildTabListPopup); m_menu->insertMenu(m_removeFromTabGroup, m_switchToTabMenu); } } else { delete m_switchToTabMenu; m_switchToTabMenu = 0; } if (!m_addTabsMenu) { m_addTabsMenu = new QMenu(i18n("&Attach as tab to"), m_menu); connect(m_addTabsMenu, &QMenu::triggered, this, &UserActionsMenu::entabPopupClient); connect(m_addTabsMenu, &QMenu::aboutToShow, this, &UserActionsMenu::rebuildTabGroupPopup); m_menu->insertMenu(m_removeFromTabGroup, m_addTabsMenu); } m_addTabsMenu->menuAction()->setEnabled(!m_client.data()->isFullScreen()); m_removeFromTabGroup->setVisible(needTabManagers); m_closeTabGroup->setVisible(needTabManagers); } void UserActionsMenu::initDesktopPopup() { if (m_desktopMenu) return; m_desktopMenu = new QMenu(m_menu); connect(m_desktopMenu, &QMenu::triggered, this, &UserActionsMenu::slotSendToDesktop); connect(m_desktopMenu, &QMenu::aboutToShow, this, &UserActionsMenu::desktopPopupAboutToShow); QAction *action = m_desktopMenu->menuAction(); // set it as the first item m_menu->insertAction(m_minimizeOperation, action); action->setText(i18n("Move To &Desktop")); } void UserActionsMenu::initScreenPopup() { if (m_screenMenu) { return; } m_screenMenu = new QMenu(m_menu); connect(m_screenMenu, &QMenu::triggered, this, &UserActionsMenu::slotSendToScreen); connect(m_screenMenu, &QMenu::aboutToShow, this, &UserActionsMenu::screenPopupAboutToShow); QAction *action = m_screenMenu->menuAction(); // set it as the first item after desktop m_menu->insertAction(m_activityMenu ? m_activityMenu->menuAction() : m_minimizeOperation, action); action->setText(i18n("Move To &Screen")); } void UserActionsMenu::initActivityPopup() { if (m_activityMenu) return; m_activityMenu = new QMenu(m_menu); connect(m_activityMenu, &QMenu::triggered, this, &UserActionsMenu::slotToggleOnActivity); connect(m_activityMenu, &QMenu::aboutToShow, this, &UserActionsMenu::activityPopupAboutToShow); QAction *action = m_activityMenu->menuAction(); // set it as the first item m_menu->insertAction(m_minimizeOperation, action); action->setText(i18n("Ac&tivities")); //FIXME is that a good string? } void UserActionsMenu::desktopPopupAboutToShow() { if (!m_desktopMenu) return; const VirtualDesktopManager *vds = VirtualDesktopManager::self(); m_desktopMenu->clear(); m_desktopMenu->setPalette(m_client.data()->palette()); QActionGroup *group = new QActionGroup(m_desktopMenu); QAction *action = m_desktopMenu->addAction(i18n("&All Desktops")); action->setData(0); action->setCheckable(true); group->addAction(action); if (!m_client.isNull() && m_client.data()->isOnAllDesktops()) action->setChecked(true); m_desktopMenu->addSeparator(); const uint BASE = 10; for (uint i = 1; i <= vds->count(); ++i) { QString basic_name(QStringLiteral("%1 %2")); if (i < BASE) { basic_name.prepend(QLatin1Char('&')); } action = m_desktopMenu->addAction(basic_name.arg(i).arg(vds->name(i).replace(QLatin1Char('&'), QStringLiteral("&&")))); action->setData(i); action->setCheckable(true); group->addAction(action); if (!m_client.isNull() && !m_client.data()->isOnAllDesktops() && m_client.data()->isOnDesktop(i)) action->setChecked(true); } m_desktopMenu->addSeparator(); action = m_desktopMenu->addAction(i18nc("Create a new desktop and move there the window", "&New Desktop")); action->setData(vds->count() + 1); if (vds->count() >= vds->maximum()) action->setEnabled(false); } void UserActionsMenu::screenPopupAboutToShow() { if (!m_screenMenu) { return; } m_screenMenu->clear(); m_screenMenu->setPalette(m_client.data()->palette()); QActionGroup *group = new QActionGroup(m_screenMenu); for (int i = 0; icount(); ++i) { // assumption: there are not more than 9 screens attached. QAction *action = m_screenMenu->addAction(i18nc("@item:inmenu List of all Screens to send a window to. First argument is a number, second the output identifier. E.g. Screen 1 (HDMI1)", "Screen &%1 (%2)", (i+1), screens()->name(i))); action->setData(i); action->setCheckable(true); if (!m_client.isNull() && i == m_client.data()->screen()) { action->setChecked(true); } group->addAction(action); } } void UserActionsMenu::activityPopupAboutToShow() { if (!m_activityMenu) return; #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } m_activityMenu->clear(); m_activityMenu->setPalette(m_client.data()->palette()); QAction *action = m_activityMenu->addAction(i18n("&All Activities")); action->setData(QString()); action->setCheckable(true); static QPointer allActivitiesGroup; if (!allActivitiesGroup) { allActivitiesGroup = new QActionGroup(m_activityMenu); } allActivitiesGroup->addAction(action); if (!m_client.isNull() && m_client.data()->isOnAllActivities()) action->setChecked(true); m_activityMenu->addSeparator(); foreach (const QString &id, Activities::self()->running()) { KActivities::Info activity(id); QString name = activity.name(); name.replace('&', "&&"); QWidgetAction *action = new QWidgetAction(m_activityMenu); QCheckBox *box = new QCheckBox(name, m_activityMenu); action->setDefaultWidget(box); const QString icon = activity.icon(); if (!icon.isEmpty()) box->setIcon(QIcon::fromTheme(icon)); box->setBackgroundRole(m_activityMenu->backgroundRole()); box->setForegroundRole(m_activityMenu->foregroundRole()); box->setPalette(m_activityMenu->palette()); connect(box, &QCheckBox::clicked, action, &QAction::triggered); m_activityMenu->addAction(action); action->setData(id); if (!m_client.isNull() && !m_client.data()->isOnAllActivities() && m_client.data()->isOnActivity(id)) box->setChecked(true); } #endif } void UserActionsMenu::slotWindowOperation(QAction *action) { if (!action->data().isValid()) return; Options::WindowOperation op = static_cast< Options::WindowOperation >(action->data().toInt()); QWeakPointer c = (!m_client.isNull()) ? m_client : QWeakPointer(Workspace::self()->activeClient()); if (c.isNull()) return; QString type; switch(op) { case Options::FullScreenOp: if (!c.data()->isFullScreen() && c.data()->userCanSetFullScreen()) type = QStringLiteral("fullscreenaltf3"); break; case Options::NoBorderOp: if (!c.data()->noBorder() && c.data()->userCanSetNoBorder()) type = QStringLiteral("noborderaltf3"); break; default: break; }; if (!type.isEmpty()) helperDialog(type, c); // need to delay performing the window operation as we need to have the // user actions menu closed before we destroy the decoration. Otherwise Qt crashes qRegisterMetaType(); QMetaObject::invokeMethod(workspace(), "performWindowOperation", Qt::QueuedConnection, Q_ARG(KWin::AbstractClient*, c.data()), Q_ARG(Options::WindowOperation, op)); } void UserActionsMenu::slotSendToDesktop(QAction *action) { bool ok = false; uint desk = action->data().toUInt(&ok); if (!ok) { return; } if (m_client.isNull()) return; Workspace *ws = Workspace::self(); VirtualDesktopManager *vds = VirtualDesktopManager::self(); if (desk == 0) { // the 'on_all_desktops' menu entry m_client.data()->setOnAllDesktops(!m_client.data()->isOnAllDesktops()); return; } else if (desk > vds->count()) { vds->setCount(desk); } ws->sendClientToDesktop(m_client.data(), desk, false); } void UserActionsMenu::slotSendToScreen(QAction *action) { const int screen = action->data().toInt(); if (m_client.isNull()) { return; } if (screen >= screens()->count()) { return; } Workspace::self()->sendClientToScreen(m_client.data(), screen); } void UserActionsMenu::slotToggleOnActivity(QAction *action) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } QString activity = action->data().toString(); if (m_client.isNull()) return; if (activity.isEmpty()) { // the 'on_all_activities' menu entry m_client.data()->setOnAllActivities(!m_client.data()->isOnAllActivities()); return; } Client *c = dynamic_cast(m_client.data()); if (!c) { return; } Activities::self()->toggleClientOnActivity(c, activity, false); if (m_activityMenu && m_activityMenu->isVisible() && m_activityMenu->actions().count()) { const bool isOnAll = m_client.data()->isOnAllActivities(); m_activityMenu->actions().at(0)->setChecked(isOnAll); if (isOnAll) { // toggleClientOnActivity interprets "on all" as "on none" and // susequent toggling ("off") would move the client to only that activity. // bug #330838 -> set all but "on all" off to "force proper usage" for (int i = 1; i < m_activityMenu->actions().count(); ++i) { if (QWidgetAction *qwa = qobject_cast(m_activityMenu->actions().at(i))) { if (QCheckBox *qcb = qobject_cast(qwa->defaultWidget())) { qcb->setChecked(false); } } } } } #else Q_UNUSED(action) #endif } //**************************************** // ShortcutDialog //**************************************** ShortcutDialog::ShortcutDialog(const QKeySequence& cut) : _shortcut(cut) { m_ui.setupUi(this); m_ui.keySequenceEdit->setKeySequence(cut); m_ui.warning->hide(); // Listen to changed shortcuts connect(m_ui.keySequenceEdit, &QKeySequenceEdit::editingFinished, this, &ShortcutDialog::keySequenceChanged); connect(m_ui.clearButton, &QToolButton::clicked, [this]{ _shortcut = QKeySequence(); }); m_ui.keySequenceEdit->setFocus(); setWindowFlags(Qt::Popup | Qt::X11BypassWindowManagerHint); } void ShortcutDialog::accept() { QKeySequence seq = shortcut(); if (!seq.isEmpty()) { if (seq[0] == Qt::Key_Escape) { reject(); return; } if (seq[0] == Qt::Key_Space || (seq[0] & Qt::KeyboardModifierMask) == 0) { // clear m_ui.keySequenceEdit->clear(); QDialog::accept(); return; } } QDialog::accept(); } void ShortcutDialog::done(int r) { QDialog::done(r); emit dialogDone(r == Accepted); } void ShortcutDialog::keySequenceChanged() { activateWindow(); // where is the kbd focus lost? cause of popup state? QKeySequence seq = m_ui.keySequenceEdit->keySequence(); if (_shortcut == seq) return; // don't try to update the same if (seq.isEmpty()) { // clear _shortcut = seq; return; } if (seq.count() > 1) { seq = QKeySequence(seq[0]); m_ui.keySequenceEdit->setKeySequence(seq); } // Check if the key sequence is used currently QString sc = seq.toString(); // NOTICE - seq.toString() & the entries in "conflicting" randomly get invalidated after the next call (if no sc has been set & conflicting isn't empty?!) QList conflicting = KGlobalAccel::getGlobalShortcutsByKey(seq); if (!conflicting.isEmpty()) { const KGlobalShortcutInfo &conflict = conflicting.at(0); m_ui.warning->setText(i18nc("'%1' is a keyboard shortcut like 'ctrl+w'", "%1 is already in use", sc)); m_ui.warning->setToolTip(i18nc("keyboard shortcut '%1' is used by action '%2' in application '%3'", "%1 is used by %2 in %3", sc, conflict.friendlyName(), conflict.componentFriendlyName())); m_ui.warning->show(); m_ui.keySequenceEdit->setKeySequence(shortcut()); } else if (seq != _shortcut) { m_ui.warning->hide(); if (QPushButton *ok = m_ui.buttonBox->button(QDialogButtonBox::Ok)) ok->setFocus(); } _shortcut = seq; } QKeySequence ShortcutDialog::shortcut() const { return _shortcut; } //**************************************** // Workspace //**************************************** void Workspace::slotIncreaseWindowOpacity() { if (!active_client) { return; } active_client->setOpacity(qMin(active_client->opacity() + 0.05, 1.0)); } void Workspace::slotLowerWindowOpacity() { if (!active_client) { return; } active_client->setOpacity(qMax(active_client->opacity() - 0.05, 0.05)); } void Workspace::closeActivePopup() { if (active_popup) { active_popup->close(); active_popup = NULL; active_popup_client = NULL; } m_userActionsMenu->close(); } template void Workspace::initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, Slot slot, const QVariant &data) { QAction *a = new QAction(this); a->setProperty("componentName", QStringLiteral(KWIN_NAME)); a->setObjectName(actionName); a->setText(description); if (data.isValid()) { a->setData(data); } KGlobalAccel::self()->setDefaultShortcut(a, QList() << shortcut); KGlobalAccel::self()->setShortcut(a, QList() << shortcut); input()->registerShortcut(shortcut, a, this, slot); } /*! Create the global accel object \c keys. */ void Workspace::initShortcuts() { #define IN_KWIN #include "kwinbindings.cpp" #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->initShortcuts(); #endif VirtualDesktopManager::self()->initShortcuts(); m_userActionsMenu->discard(); // so that it's recreated next time } void Workspace::setupWindowShortcut(AbstractClient* c) { assert(client_keys_dialog == NULL); // TODO: PORT ME (KGlobalAccel related) //keys->setEnabled( false ); //disable_shortcuts_keys->setEnabled( false ); //client_keys->setEnabled( false ); client_keys_dialog = new ShortcutDialog(c->shortcut()); client_keys_client = c; connect(client_keys_dialog, &ShortcutDialog::dialogDone, this, &Workspace::setupWindowShortcutDone); QRect r = clientArea(ScreenArea, c); QSize size = client_keys_dialog->sizeHint(); QPoint pos = c->pos() + c->clientPos(); if (pos.x() + size.width() >= r.right()) pos.setX(r.right() - size.width()); if (pos.y() + size.height() >= r.bottom()) pos.setY(r.bottom() - size.height()); client_keys_dialog->move(pos); client_keys_dialog->show(); active_popup = client_keys_dialog; active_popup_client = c; } void Workspace::setupWindowShortcutDone(bool ok) { // keys->setEnabled( true ); // disable_shortcuts_keys->setEnabled( true ); // client_keys->setEnabled( true ); if (ok) client_keys_client->setShortcut(client_keys_dialog->shortcut().toString()); closeActivePopup(); client_keys_dialog->deleteLater(); client_keys_dialog = NULL; client_keys_client = NULL; if (active_client) active_client->takeFocus(); } void Workspace::clientShortcutUpdated(Client* c) { QString key = QStringLiteral("_k_session:%1").arg(c->window()); QAction* action = findChild(key); if (!c->shortcut().isEmpty()) { if (action == NULL) { // new shortcut action = new QAction(this); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(key); action->setText(i18n("Activate Window (%1)", c->caption())); connect(action, &QAction::triggered, c, [c]() { workspace()->activateClient(c, true); } ); } // no autoloading, since it's configured explicitly here and is not meant to be reused // (the key is the window id anyway, which is kind of random) KGlobalAccel::self()->setShortcut(action, QList() << c->shortcut(), KGlobalAccel::NoAutoloading); action->setEnabled(true); } else { KGlobalAccel::self()->removeAllShortcuts(action); delete action; } } void Workspace::performWindowOperation(AbstractClient* c, Options::WindowOperation op) { if (!c) return; if (op == Options::MoveOp || op == Options::UnrestrictedMoveOp) Cursor::setPos(c->geometry().center()); if (op == Options::ResizeOp || op == Options::UnrestrictedResizeOp) Cursor::setPos(c->geometry().bottomRight()); switch(op) { case Options::MoveOp: c->performMouseCommand(Options::MouseMove, Cursor::pos()); break; case Options::UnrestrictedMoveOp: c->performMouseCommand(Options::MouseUnrestrictedMove, Cursor::pos()); break; case Options::ResizeOp: c->performMouseCommand(Options::MouseResize, Cursor::pos()); break; case Options::UnrestrictedResizeOp: c->performMouseCommand(Options::MouseUnrestrictedResize, Cursor::pos()); break; case Options::CloseOp: QMetaObject::invokeMethod(c, "closeWindow", Qt::QueuedConnection); break; case Options::MaximizeOp: c->maximize(c->maximizeMode() == MaximizeFull ? MaximizeRestore : MaximizeFull); break; case Options::HMaximizeOp: c->maximize(c->maximizeMode() ^ MaximizeHorizontal); break; case Options::VMaximizeOp: c->maximize(c->maximizeMode() ^ MaximizeVertical); break; case Options::RestoreOp: c->maximize(MaximizeRestore); break; case Options::MinimizeOp: c->minimize(); break; case Options::ShadeOp: c->performMouseCommand(Options::MouseShade, Cursor::pos()); break; case Options::OnAllDesktopsOp: c->setOnAllDesktops(!c->isOnAllDesktops()); break; case Options::FullScreenOp: c->setFullScreen(!c->isFullScreen(), true); break; case Options::NoBorderOp: c->setNoBorder(!c->noBorder()); break; case Options::KeepAboveOp: { StackingUpdatesBlocker blocker(this); bool was = c->keepAbove(); c->setKeepAbove(!c->keepAbove()); if (was && !c->keepAbove()) raiseClient(c); break; } case Options::KeepBelowOp: { StackingUpdatesBlocker blocker(this); bool was = c->keepBelow(); c->setKeepBelow(!c->keepBelow()); if (was && !c->keepBelow()) lowerClient(c); break; } case Options::OperationsOp: c->performMouseCommand(Options::MouseShade, Cursor::pos()); break; case Options::WindowRulesOp: RuleBook::self()->edit(c, false); break; case Options::ApplicationRulesOp: RuleBook::self()->edit(c, true); break; case Options::SetupWindowShortcutOp: setupWindowShortcut(c); break; case Options::LowerOp: lowerClient(c); break; case Options::TabDragOp: // Handled by decoration itself case Options::NoOp: break; case Options::RemoveTabFromGroupOp: if (c->untab(c->geometry().translated(cascadeOffset(c))) && options->focusPolicyIsReasonable()) takeActivity(c, ActivityFocus | ActivityRaise); break; case Options::ActivateNextTabOp: if (c->tabGroup()) c->tabGroup()->activateNext(); break; case Options::ActivatePreviousTabOp: if (c->tabGroup()) c->tabGroup()->activatePrev(); break; case Options::CloseTabGroupOp: c->tabGroup()->closeAll(); break; } } /** * Called by the decoration in the new API to determine what buttons the user has configured for * window tab dragging and the operations menu. */ Options::WindowOperation Client::mouseButtonToWindowOperation(Qt::MouseButtons button) { Options::MouseCommand com = Options::MouseNothing; bool active = isActive(); if (!wantsInput()) // we cannot be active, use it anyway active = true; if (button == Qt::LeftButton) com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); else if (button == Qt::MidButton) com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); else if (button == Qt::RightButton) com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); // TODO: Complete the list if (com == Options::MouseDragTab) return Options::TabDragOp; if (com == Options::MouseOperationsMenu) return Options::OperationsOp; return Options::NoOp; } /*! Performs a mouse command on this client (see options.h) */ bool Client::performMouseCommand(Options::MouseCommand command, const QPoint &globalPos) { bool replay = false; switch(command) { case Options::MouseShade : toggleShade(); cancelShadeHoverTimer(); break; case Options::MouseSetShade: setShade(ShadeNormal); cancelShadeHoverTimer(); break; case Options::MouseUnsetShade: setShade(ShadeNone); cancelShadeHoverTimer(); break; default: return AbstractClient::performMouseCommand(command, globalPos); } return replay; } void Workspace::slotActivateAttentionWindow() { if (attention_chain.count() > 0) activateClient(attention_chain.first()); } static uint senderValue(QObject *sender) { QAction *act = qobject_cast(sender); bool ok = false; uint i = -1; if (act) i = act->data().toUInt(&ok); if (ok) return i; return -1; } #define USABLE_ACTIVE_CLIENT (active_client && !(active_client->isDesktop() || active_client->isDock())) void Workspace::slotWindowToDesktop() { if (USABLE_ACTIVE_CLIENT) { const uint i = senderValue(sender()); if (i < 1) return; if (i >= 1 && i <= VirtualDesktopManager::self()->count()) sendClientToDesktop(active_client, i, true); } } static bool screenSwitchImpossible() { if (!screens()->isCurrentFollowsMouse()) return false; QStringList args; args << QStringLiteral("--passivepopup") << i18n("The window manager is configured to consider the screen with the mouse on it as active one.\n" "Therefore it is not possible to switch to a screen explicitly.") << QStringLiteral("20"); KProcess::startDetached(QStringLiteral("kdialog"), args); return true; } void Workspace::slotSwitchToScreen() { if (screenSwitchImpossible()) return; const int i = senderValue(sender()); if (i > -1) setCurrentScreen(i); } void Workspace::slotSwitchToNextScreen() { if (screenSwitchImpossible()) return; setCurrentScreen((screens()->current() + 1) % screens()->count()); } void Workspace::slotSwitchToPrevScreen() { if (screenSwitchImpossible()) return; setCurrentScreen((screens()->current() + screens()->count() - 1) % screens()->count()); } void Workspace::slotWindowToScreen() { if (USABLE_ACTIVE_CLIENT) { const int i = senderValue(sender()); if (i < 0) return; if (i >= 0 && i <= screens()->count()) { sendClientToScreen(active_client, i); } } } void Workspace::slotWindowToNextScreen() { if (USABLE_ACTIVE_CLIENT) sendClientToScreen(active_client, (active_client->screen() + 1) % screens()->count()); } void Workspace::slotWindowToPrevScreen() { if (USABLE_ACTIVE_CLIENT) sendClientToScreen(active_client, (active_client->screen() + screens()->count() - 1) % screens()->count()); } /*! Maximizes the popup client */ void Workspace::slotWindowMaximize() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::MaximizeOp); } /*! Maximizes the popup client vertically */ void Workspace::slotWindowMaximizeVertical() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::VMaximizeOp); } /*! Maximizes the popup client horiozontally */ void Workspace::slotWindowMaximizeHorizontal() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::HMaximizeOp); } /*! Minimizes the popup client */ void Workspace::slotWindowMinimize() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::MinimizeOp); } /*! Shades/unshades the popup client respectively */ void Workspace::slotWindowShade() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::ShadeOp); } /*! Raises the popup client */ void Workspace::slotWindowRaise() { if (USABLE_ACTIVE_CLIENT) raiseClient(active_client); } /*! Lowers the popup client */ void Workspace::slotWindowLower() { if (USABLE_ACTIVE_CLIENT) { lowerClient(active_client); // As this most likely makes the window no longer visible change the // keyboard focus to the next available window. //activateNextClient( c ); // Doesn't work when we lower a child window if (active_client->isActive() && options->focusPolicyIsReasonable()) { if (options->isNextFocusPrefersMouse()) { AbstractClient *next = clientUnderMouse(active_client->screen()); if (next && next != active_client) requestFocus(next, false); } else { activateClient(topClientOnDesktop(VirtualDesktopManager::self()->current(), -1)); } } } } /*! Does a toggle-raise-and-lower on the popup client; */ void Workspace::slotWindowRaiseOrLower() { if (USABLE_ACTIVE_CLIENT) raiseOrLowerClient(active_client); } void Workspace::slotWindowOnAllDesktops() { if (USABLE_ACTIVE_CLIENT) active_client->setOnAllDesktops(!active_client->isOnAllDesktops()); } void Workspace::slotWindowFullScreen() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::FullScreenOp); } void Workspace::slotWindowNoBorder() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::NoBorderOp); } void Workspace::slotWindowAbove() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::KeepAboveOp); } void Workspace::slotWindowBelow() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::KeepBelowOp); } void Workspace::slotSetupWindowShortcut() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::SetupWindowShortcutOp); } /*! Toggles show desktop */ void Workspace::slotToggleShowDesktop() { setShowingDesktop(!showingDesktop()); } template void windowToDesktop(AbstractClient *c) { VirtualDesktopManager *vds = VirtualDesktopManager::self(); Workspace *ws = Workspace::self(); Direction functor; // TODO: why is options->isRollOverDesktops() not honored? const auto desktop = functor(nullptr, true); if (c && !c->isDesktop() && !c->isDock()) { ws->setClientIsMoving(c); vds->setCurrent(desktop); ws->setClientIsMoving(NULL); } } /*! Move window to next desktop */ void Workspace::slotWindowToNextDesktop() { if (USABLE_ACTIVE_CLIENT) windowToNextDesktop(active_client); } void Workspace::windowToNextDesktop(AbstractClient* c) { windowToDesktop(c); } /*! Move window to previous desktop */ void Workspace::slotWindowToPreviousDesktop() { if (USABLE_ACTIVE_CLIENT) windowToPreviousDesktop(active_client); } void Workspace::windowToPreviousDesktop(AbstractClient* c) { windowToDesktop(c); } template void activeClientToDesktop() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); Workspace *ws = Workspace::self(); const int current = vds->current(); Direction functor; const int d = functor(current, options->isRollOverDesktops()); if (d == current) { return; } ws->setClientIsMoving(ws->activeClient()); vds->setCurrent(d); ws->setClientIsMoving(NULL); } void Workspace::slotWindowToDesktopRight() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotWindowToDesktopLeft() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotWindowToDesktopUp() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotWindowToDesktopDown() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotActivateNextTab() { if (active_client && active_client->tabGroup()) active_client->tabGroup()->activateNext(); } void Workspace::slotActivatePrevTab() { if (active_client && active_client->tabGroup()) active_client->tabGroup()->activatePrev(); } void Workspace::slotUntab() { if (active_client) active_client->untab(active_client->geometry().translated(cascadeOffset(active_client))); } /*! Kill Window feature, similar to xkill */ void Workspace::slotKillWindow() { if (m_windowKiller.isNull()) { m_windowKiller.reset(new KillWindow()); } m_windowKiller->start(); } /*! Switches to the nearest window in given direction */ void Workspace::switchWindow(Direction direction) { if (!active_client) return; AbstractClient *c = active_client; int desktopNumber = c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(); // Centre of the active window QPoint curPos(c->pos().x() + c->geometry().width() / 2, c->pos().y() + c->geometry().height() / 2); if (!switchWindow(c, direction, curPos, desktopNumber)) { auto opposite = [&] { switch(direction) { case DirectionNorth: return QPoint(curPos.x(), screens()->geometry().height()); case DirectionSouth: return QPoint(curPos.x(), 0); case DirectionEast: return QPoint(0, curPos.y()); case DirectionWest: return QPoint(screens()->geometry().width(), curPos.y()); default: Q_UNREACHABLE(); } }; switchWindow(c, direction, opposite(), desktopNumber); } } bool Workspace::switchWindow(AbstractClient *c, Direction direction, QPoint curPos, int d) { Client *switchTo = nullptr; int bestScore = 0; ToplevelList clist = stackingOrder(); for (ToplevelList::Iterator i = clist.begin(); i != clist.end(); ++i) { Client *client = qobject_cast(*i); if (!client) { continue; } if (client->wantsTabFocus() && *i != c && client->isOnDesktop(d) && !client->isMinimized() && (*i)->isOnCurrentActivity()) { // Centre of the other window QPoint other(client->pos().x() + client->geometry().width() / 2, client->pos().y() + client->geometry().height() / 2); int distance; int offset; switch(direction) { case DirectionNorth: distance = curPos.y() - other.y(); offset = qAbs(other.x() - curPos.x()); break; case DirectionEast: distance = other.x() - curPos.x(); offset = qAbs(other.y() - curPos.y()); break; case DirectionSouth: distance = other.y() - curPos.y(); offset = qAbs(other.x() - curPos.x()); break; case DirectionWest: distance = curPos.x() - other.x(); offset = qAbs(other.y() - curPos.y()); break; default: distance = -1; offset = -1; } if (distance > 0) { // Inverse score int score = distance + offset + ((offset * offset) / distance); if (score < bestScore || !switchTo) { switchTo = client; bestScore = score; } } } } if (switchTo) { if (switchTo->tabGroup()) switchTo = switchTo->tabGroup()->current(); activateClient(switchTo); } return switchTo; } /*! Switches to upper window */ void Workspace::slotSwitchWindowUp() { switchWindow(DirectionNorth); } /*! Switches to lower window */ void Workspace::slotSwitchWindowDown() { switchWindow(DirectionSouth); } /*! Switches to window on the right */ void Workspace::slotSwitchWindowRight() { switchWindow(DirectionEast); } /*! Switches to window on the left */ void Workspace::slotSwitchWindowLeft() { switchWindow(DirectionWest); } /*! Shows the window operations popup menu for the activeClient() */ void Workspace::slotWindowOperations() { if (!active_client) return; QPoint pos = active_client->pos() + active_client->clientPos(); showWindowMenu(QRect(pos, pos), active_client); } void Workspace::showWindowMenu(const QRect &pos, AbstractClient* cl) { m_userActionsMenu->show(pos, cl); } +void Workspace::showApplicationMenu(const QRect &pos, AbstractClient *c, int actionId) +{ + ApplicationMenu::self()->showApplicationMenu(c->geometry().topLeft() + pos.bottomLeft(), c, actionId); +} + /*! Closes the popup client */ void Workspace::slotWindowClose() { // TODO: why? // if ( tab_box->isVisible()) // return; if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::CloseOp); } /*! Starts keyboard move mode for the popup client */ void Workspace::slotWindowMove() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::UnrestrictedMoveOp); } /*! Starts keyboard resize mode for the popup client */ void Workspace::slotWindowResize() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::UnrestrictedResizeOp); } void Workspace::slotInvertScreen() { using namespace Xcb::RandR; bool succeeded = false; if (Xcb::Extensions::self()->isRandrAvailable()) { ScreenResources res((active_client && active_client->window() != XCB_WINDOW_NONE) ? active_client->window() : rootWindow()); if (!res.isNull()) { for (int j = 0; j < res->num_crtcs; ++j) { auto crtc = res.crtcs()[j]; CrtcGamma gamma(crtc); if (gamma.isNull()) { continue; } if (gamma->size) { qCDebug(KWIN_CORE) << "inverting screen using xcb_randr_set_crtc_gamma"; const int half = gamma->size / 2 + 1; uint16_t *red = gamma.red(); uint16_t *green = gamma.green(); uint16_t *blue = gamma.blue(); for (int i = 0; i < half; ++i) { auto invert = [&gamma, i](uint16_t *ramp) { qSwap(ramp[i], ramp[gamma->size - 1 - i]); }; invert(red); invert(green); invert(blue); } xcb_randr_set_crtc_gamma(connection(), crtc, gamma->size, red, green, blue); succeeded = true; } } } } if (succeeded) return; //BEGIN effect plugin inversion - atm only works with OpenGL and has an overhead to it if (effects) { if (Effect *inverter = static_cast(effects)->provides(Effect::ScreenInversion)) { qCDebug(KWIN_CORE) << "inverting screen using Effect plugin"; QMetaObject::invokeMethod(inverter, "toggleScreenInversion", Qt::DirectConnection); } } if (!succeeded) qCDebug(KWIN_CORE) << "sorry - neither Xrandr, nor XF86VidModeSetGammaRamp worked and there's no inversion supplying effect plugin either"; } #undef USABLE_ACTIVE_CLIENT void Client::setShortcut(const QString& _cut) { QString cut = rules()->checkShortcut(_cut); if (cut.isEmpty()) return setShortcutInternal(); if (cut == shortcut().toString()) { return; // no change } // Format: // base+(abcdef)base+(abcdef) // E.g. Alt+Ctrl+(ABCDEF);Meta+X,Meta+(ABCDEF) if (!cut.contains(QLatin1Char('(')) && !cut.contains(QLatin1Char(')')) && !cut.contains(QLatin1String(" - "))) { if (workspace()->shortcutAvailable(cut, this)) setShortcutInternal(QKeySequence(cut)); else setShortcutInternal(); return; } QList< QKeySequence > keys; QStringList groups = cut.split(QStringLiteral(" - ")); for (QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) { QRegExp reg(QStringLiteral("(.*\\+)\\((.*)\\)")); if (reg.indexIn(*it) > -1) { QString base = reg.cap(1); QString list = reg.cap(2); for (int i = 0; i < list.length(); ++i) { QKeySequence c(base + list[ i ]); if (!c.isEmpty()) keys.append(c); } } else { // regexp doesn't match, so it should be a normal shortcut QKeySequence c(*it); if (!c.isEmpty()) { keys.append(c); } } } for (auto it = keys.constBegin(); it != keys.constEnd(); ++it) { if (_shortcut == *it) // current one is in the list return; } for (auto it = keys.constBegin(); it != keys.constEnd(); ++it) { if (workspace()->shortcutAvailable(*it, this)) { setShortcutInternal(*it); return; } } setShortcutInternal(); } void Client::setShortcutInternal(const QKeySequence &cut) { if (_shortcut == cut) return; _shortcut = cut; updateCaption(); #if 0 workspace()->clientShortcutUpdated(this); #else // Workaround for kwin<->kglobalaccel deadlock, when KWin has X grab and the kded // kglobalaccel module tries to create the key grab. KWin should preferably grab // they keys itself anyway :(. QTimer::singleShot(0, this, SLOT(delayedSetShortcut())); #endif } void Client::delayedSetShortcut() { workspace()->clientShortcutUpdated(this); } bool Workspace::shortcutAvailable(const QKeySequence &cut, Client* ignore) const { if (ignore && cut == ignore->shortcut()) return true; if (!KGlobalAccel::getGlobalShortcutsByKey(cut).isEmpty()) { return false; } for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if ((*it) != ignore && (*it)->shortcut() == cut) return false; } return true; } } // namespace diff --git a/workspace.cpp b/workspace.cpp index acbbcf487..360f9db6e 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,1761 +1,1764 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "workspace.h" // kwin libs #include #include // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif +#include "appmenu.h" #include "atoms.h" #include "client.h" #include "composite.h" #include "cursor.h" #include "dbusinterface.h" #include "deleted.h" #include "effects.h" #include "focuschain.h" #include "group.h" #include "input.h" #include "logind.h" #include "killwindow.h" #include "netinfo.h" #include "outline.h" #include "placement.h" #include "rules.h" #include "screenedge.h" #include "screens.h" #include "scripting/scripting.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "unmanaged.h" #include "useractions.h" #include "virtualdesktops.h" #include "shell_client.h" #include "wayland_server.h" #include "xcbutils.h" #include "main.h" #include "decorations/decorationbridge.h" // KDE #include #include #include #include // Qt #include namespace KWin { extern int screen_number; extern bool is_multihead; ColorMapper::ColorMapper(QObject *parent) : QObject(parent) , m_default(defaultScreen()->default_colormap) , m_installed(defaultScreen()->default_colormap) { } ColorMapper::~ColorMapper() { } void ColorMapper::update() { xcb_colormap_t cmap = m_default; if (Client *c = dynamic_cast(Workspace::self()->activeClient())) { if (c->colormap() != XCB_COLORMAP_NONE) { cmap = c->colormap(); } } if (cmap != m_installed) { xcb_install_colormap(connection(), cmap); m_installed = cmap; } } Workspace* Workspace::_self = 0; Workspace::Workspace(const QString &sessionKey) : QObject(0) , m_compositor(NULL) // Unsorted , active_popup(NULL) , active_popup_client(NULL) , m_initialDesktop(1) , active_client(0) , last_active_client(0) , most_recently_raised(0) , movingClient(0) , delayfocus_client(0) , force_restacking(false) , x_stacking_dirty(true) , showing_desktop(false) , was_user_interaction(false) , session_saving(false) , block_focus(0) , m_userActionsMenu(new UserActionsMenu(this)) , client_keys_dialog(NULL) , client_keys_client(NULL) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) , startup(0) , set_active_client_recursion(0) , block_stacking_updates(0) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); + ApplicationMenu::create(this); + _self = this; // first initialize the extensions Xcb::Extensions::self(); #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); ColorMapper *colormaps = new ColorMapper(this); connect(this, &Workspace::clientActivated, colormaps, &ColorMapper::update); delayFocusTimer = 0; if (!sessionKey.isEmpty()) loadSessionInfo(sessionKey); connect(qApp, &QGuiApplication::commitDataRequest, this, &Workspace::commitData); connect(qApp, &QGuiApplication::saveStateRequest, this, &Workspace::saveState); RuleBook::create(this)->load(); // Call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this); // Select windowmanager privileges selectWmInputEventMask(); ScreenEdges::create(this); // VirtualDesktopManager needs to be created prior to init shortcuts // and prior to TabBox, due to TabBox connecting to signals // actual initialization happens in init() VirtualDesktopManager::create(this); #ifdef KWIN_BUILD_TABBOX // need to create the tabbox before compositing scene is setup TabBox::TabBox::create(this); #endif // init XRenderUtils if (kwinApp()->operationMode() == Application::OperationModeX11) { XRenderUtils::init(connection(), rootWindow()); } if (Compositor::self()) { m_compositor = Compositor::self(); } else { m_compositor = Compositor::create(this); } connect(this, &Workspace::currentDesktopChanged, m_compositor, &Compositor::addRepaintFull); connect(m_compositor, &QObject::destroyed, this, [this] { m_compositor = nullptr; }); auto decorationBridge = Decoration::DecorationBridge::create(this); decorationBridge->init(); connect(this, &Workspace::configChanged, decorationBridge, &Decoration::DecorationBridge::reconfigure); new DBusInterface(this); // Compatibility int32_t data = 1; xcb_change_property(connection(), XCB_PROP_MODE_APPEND, rootWindow(), atoms->kwin_running, atoms->kwin_running, 32, 1, &data); Outline::create(this); initShortcuts(); init(); } void Workspace::init() { updateXTime(); // Needed for proper initialization of user_time in Client ctor 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()); 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(); // create VirtualDesktopManager and perform dependency injection VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(vds, SIGNAL(desktopsRemoved(uint)), SLOT(moveClientsFromRemovedDesktops())); connect(vds, SIGNAL(countChanged(uint,uint)), SLOT(slotDesktopCountChanged(uint,uint))); connect(vds, SIGNAL(currentChanged(uint,uint)), SLOT(slotCurrentDesktopChanged(uint,uint))); vds->setNavigationWrappingAround(options->isRollOverDesktops()); connect(options, SIGNAL(rollOverDesktopsChanged(bool)), vds, SLOT(setNavigationWrappingAround(bool))); vds->setRootInfo(rootInfo); 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(); // Extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info(connection(), NET::ActiveWindow | NET::CurrentDesktop); if (!qApp->isSessionRestored()) m_initialDesktop = client_info.currentDesktop(); if (!VirtualDesktopManager::self()->setCurrent(m_initialDesktop)) VirtualDesktopManager::self()->setCurrent(1); reconfigureTimer.setSingleShot(true); updateToolWindowsTimer.setSingleShot(true); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows())); // TODO: do we really need to reconfigure everything when fonts change? // maybe just reconfigure the decorations? Move this into libkdecoration? QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KDEPlatformTheme"), QStringLiteral("org.kde.KDEPlatformTheme"), QStringLiteral("refreshFonts"), this, SLOT(reconfigure())); active_client = NULL; 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 AbstractClient* new_active_client = nullptr; if (!qApp->isSessionRestored()) { --block_focus; new_active_client = findClient(Predicate::WindowMatch, client_info.activeWindow()); } if (new_active_client == NULL && activeClient() == NULL && should_get_focus.count() == 0) { // No client activated in manage() if (new_active_client == NULL) new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->current(), -1); if (new_active_client == NULL && !desktops.isEmpty()) new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); } if (new_active_client != NULL) activateClient(new_active_client); Scripting::create(this); if (auto w = waylandServer()) { connect(w, &WaylandServer::shellClientAdded, this, [this] (ShellClient *c) { setupClientConnections(c); c->updateDecoration(false); updateClientLayer(c); if (!c->isInternal()) { QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop()); bool placementDone = false; if (c->isInitialPositionSet()) { placementDone = true; } if (c->isFullScreen()) { placementDone = true; } if (!placementDone) { c->placeIn(area); } m_allClients.append(c); if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order } x_stacking_dirty = true; updateStackingOrder(true); updateClientArea(); if (c->wantsInput()) { activateClient(c); } connect(c, &ShellClient::windowShown, this, [this, c] { updateClientLayer(c); // TODO: when else should we send the client through placement? if (c->hasTransientPlacementHint()) { QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop()); c->placeIn(area); } x_stacking_dirty = true; updateStackingOrder(true); updateClientArea(); if (c->wantsInput()) { activateClient(c); } } ); connect(c, &ShellClient::windowHidden, this, [this] { x_stacking_dirty = true; updateStackingOrder(true); updateClientArea(); } ); } ); connect(w, &WaylandServer::shellClientRemoved, this, [this] (ShellClient *c) { m_allClients.removeAll(c); if (c == delayfocus_client) { cancelDelayFocus(); } clientHidden(c); emit clientRemoved(c); x_stacking_dirty = true; updateStackingOrder(true); updateClientArea(); } ); } // SELI TODO: This won't work with unreasonable focus policies, // and maybe in rare cases also if the selected client doesn't // want focus workspaceInit = false; // broadcast that Workspace is ready, but first process all events. QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection); // TODO: ungrabXServer() } Workspace::~Workspace() { blockStackingUpdates(true); // TODO: grabXServer(); // Use stacking_order, so that kwin --replace keeps stacking order const ToplevelList stack = stacking_order; // "mutex" the stackingorder, since anything trying to access it from now on will find // many dangeling pointers and crash stacking_order.clear(); for (ToplevelList::const_iterator it = stack.constBegin(), end = stack.constEnd(); it != end; ++it) { Client *c = qobject_cast(const_cast(*it)); if (!c) { continue; } // Only release the window c->releaseWindow(true); // No removeClient() is called, it does more than just removing. // However, remove from some lists to e.g. prevent performTransiencyCheck() // from crashing. clients.removeAll(c); m_allClients.removeAll(c); desktops.removeAll(c); } Client::cleanupX11(); for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(ReleaseReason::KWinShutsDown); xcb_delete_property(connection(), rootWindow(), 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(); if (kwinApp()->operationMode() == Application::OperationModeX11) { XRenderUtils::cleanup(); } Xcb::Extensions::destroy(); _self = 0; } void Workspace::setupClientConnections(AbstractClient *c) { connect(c, &Toplevel::needsRepaint, m_compositor, &Compositor::scheduleRepaint); connect(c, &AbstractClient::desktopPresenceChanged, this, &Workspace::desktopPresenceChanged); } Client* Workspace::createClient(xcb_window_t w, bool is_mapped) { StackingUpdatesBlocker blocker(this); Client* c = new Client(); setupClientConnections(c); connect(c, SIGNAL(blockingCompositingChanged(KWin::Client*)), m_compositor, SLOT(updateCompositeBlocking(KWin::Client*))); connect(c, SIGNAL(clientFullScreenSet(KWin::Client*,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking())); if (!c->manage(w, is_mapped)) { Client::deleteClient(c); return NULL; } addClient(c); return c; } Unmanaged* Workspace::createUnmanaged(xcb_window_t w) { if (m_compositor && m_compositor->checkForOverlayWindow(w)) return NULL; Unmanaged* c = new Unmanaged(); if (!c->track(w)) { Unmanaged::deleteUnmanaged(c); return NULL; } connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); addUnmanaged(c); emit unmanagedAdded(c); return c; } void Workspace::addClient(Client* c) { Group* grp = findGroup(c->window()); emit clientAdded(c); if (grp != NULL) grp->gotLeader(c); if (c->isDesktop()) { desktops.append(c); if (active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) requestFocus(c); // TODO: Make sure desktop is active after startup if there's no other window active } else { FocusChain::self()->update(c, FocusChain::Update); clients.append(c); m_allClients.append(c); } if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order x_stacking_dirty = true; updateClientArea(); // This cannot be in manage(), because the client got added only now updateClientLayer(c); if (c->isDesktop()) { raiseClient(c); // If there's no active client, make this desktop the active one if (activeClient() == NULL && should_get_focus.count() == 0) activateClient(findDesktop(true, VirtualDesktopManager::self()->current())); } c->checkActiveModal(); checkTransients(c->window()); // SELI TODO: Does this really belong here? updateStackingOrder(true); // Propagate new client if (c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows(true); checkNonExistentClients(); #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isDisplayed()) TabBox::TabBox::self()->reset(true); #endif } void Workspace::addUnmanaged(Unmanaged* c) { unmanaged.append(c); x_stacking_dirty = true; } /** * Destroys the client \a c */ void Workspace::removeClient(Client* c) { emit clientRemoved(c); if (c == active_popup_client) closeActivePopup(); if (m_userActionsMenu->isMenuClient(c)) { m_userActionsMenu->close(); } c->untab(QRect(), true); if (client_keys_client == c) setupWindowShortcutDone(false); if (!c->shortcut().isEmpty()) { c->setShortcut(QString()); // Remove from client_keys clientShortcutUpdated(c); // Needed, since this is otherwise delayed by setShortcut() and wouldn't run } #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox->isDisplayed() && tabBox->currentClient() == c) tabBox->nextPrev(true); #endif Q_ASSERT(clients.contains(c) || desktops.contains(c)); // TODO: if marked client is removed, notify the marked list clients.removeAll(c); m_allClients.removeAll(c); desktops.removeAll(c); x_stacking_dirty = true; attention_chain.removeAll(c); Group* group = findGroup(c->window()); if (group != NULL) group->lostLeader(); if (c == most_recently_raised) most_recently_raised = 0; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) last_active_client = 0; if (c == delayfocus_client) cancelDelayFocus(); updateStackingOrder(true); #ifdef KWIN_BUILD_TABBOX if (tabBox->isDisplayed()) tabBox->reset(true); #endif updateClientArea(); } void Workspace::removeUnmanaged(Unmanaged* c) { assert(unmanaged.contains(c)); unmanaged.removeAll(c); emit unmanagedRemoved(c); x_stacking_dirty = true; } void Workspace::addDeleted(Deleted* c, Toplevel *orig) { assert(!deleted.contains(c)); deleted.append(c); const int unconstraintedIndex = unconstrained_stacking_order.indexOf(orig); if (unconstraintedIndex != -1) { unconstrained_stacking_order.replace(unconstraintedIndex, c); } else { unconstrained_stacking_order.append(c); } const int index = stacking_order.indexOf(orig); if (index != -1) { stacking_order.replace(index, c); } else { stacking_order.append(c); } x_stacking_dirty = true; connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); } void Workspace::removeDeleted(Deleted* c) { assert(deleted.contains(c)); emit deletedRemoved(c); deleted.removeAll(c); unconstrained_stacking_order.removeAll(c); stacking_order.removeAll(c); x_stacking_dirty = true; if (c->wasClient() && m_compositor) { m_compositor->updateCompositeBlocking(); } } void Workspace::updateToolWindows(bool also_hide) { // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?) if (!options->isHideUtilityWindowsForInactive()) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) if (!(*it)->tabGroup() || (*it)->tabGroup()->current() == *it) (*it)->hideClient(false); return; } const Group* group = NULL; const Client* client = dynamic_cast(active_client); // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow // will be shown; if a group transient is group, all tools in the group will be shown while (client != NULL) { if (!client->isTransient()) break; if (client->groupTransient()) { group = client->group(); break; } client = dynamic_cast(client->transientFor()); } // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0, // I.e. if it's not up to date // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet? ClientList to_show, to_hide; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (c->isUtility() || c->isMenu() || c->isToolbar()) { bool show = true; if (!c->isTransient()) { if (c->group()->members().count() == 1) // Has its own group, keep always visible show = true; else if (client != NULL && c->group() == client->group()) show = true; else show = false; } else { if (group != NULL && c->group() == group) show = true; else if (client != NULL && client->hasTransient(c, true)) show = true; else show = false; } if (!show && also_hide) { const auto mainclients = c->mainClients(); // Don't hide utility windows which are standalone(?) or // have e.g. kicker as mainwindow if (mainclients.isEmpty()) show = true; for (auto it2 = mainclients.constBegin(); it2 != mainclients.constEnd(); ++it2) { if ((*it2)->isSpecialWindow()) show = true; } if (!show) to_hide.append(c); } if (show) to_show.append(c); } } // First show new ones, then hide for (int i = to_show.size() - 1; i >= 0; --i) // From topmost // TODO: Since this is in stacking order, the order of taskbar entries changes :( to_show.at(i)->hideClient(false); if (also_hide) { for (ClientList::ConstIterator it = to_hide.constBegin(); it != to_hide.constEnd(); ++it) // From bottommost (*it)->hideClient(true); updateToolWindowsTimer.stop(); } else // setActiveClient() is after called with NULL client, quickly followed // by setting a new client, which would result in flickering resetUpdateToolWindowsTimer(); } void Workspace::resetUpdateToolWindowsTimer() { updateToolWindowsTimer.start(200); } void Workspace::slotUpdateToolWindows() { updateToolWindows(true); } void Workspace::slotReloadConfig() { reconfigure(); } void Workspace::reconfigure() { reconfigureTimer.start(200); } /** * This D-Bus call is used by the compositing kcm. Since the reconfigure() * D-Bus call delays the actual reconfiguring, it is not possible to immediately * call compositingActive(). Therefore the kcm will instead call this to ensure * the reconfiguring has already happened. */ bool Workspace::waitForCompositingSetup() { if (reconfigureTimer.isActive()) { reconfigureTimer.stop(); slotReconfigure(); } if (m_compositor) { return m_compositor->isActive(); } return false; } /** * 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 (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { (*it)->setupWindowRules(true); (*it)->applyWindowRules(); RuleBook::self()->discardUsed(*it, false); } if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() && !options->borderlessMaximizedWindows()) { // in case borderless maximized windows option changed and new option // is to have borders, we need to unset the borders for all maximized windows for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { if ((*it)->maximizeMode() == MaximizeFull) (*it)->checkNoBorder(); } } } /** * During virt. desktop switching, desktop areas covered by windows that are * going to be hidden are first obscured by new windows with no background * ( i.e. transparent ) placed right below the windows. These invisible windows * are removed after the switch is complete. * Reduces desktop ( wallpaper ) repaints during desktop switching */ class ObscuringWindows { public: ~ObscuringWindows(); void create(Client* c); private: QList obscuring_windows; static QList* cached; static unsigned int max_cache_size; }; QList* ObscuringWindows::cached = nullptr; unsigned int ObscuringWindows::max_cache_size = 0; void ObscuringWindows::create(Client* c) { if (!cached) cached = new QList; Xcb::Window obs_win(XCB_WINDOW_NONE, false); if (cached->count() > 0) { obs_win.reset(cached->first(), false); cached->removeAll(obs_win); obs_win.setGeometry(c->geometry()); } else { uint32_t values[] = {XCB_PIXMAP_NONE, true}; obs_win.create(c->geometry(), XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CW_BACK_PIXMAP | XCB_CW_OVERRIDE_REDIRECT, values); } uint32_t values[] = {c->frameId(), XCB_STACK_MODE_BELOW}; xcb_configure_window(connection(), obs_win, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); obs_win.map(); obscuring_windows.append(obs_win); } ObscuringWindows::~ObscuringWindows() { max_cache_size = qMax(int(max_cache_size), obscuring_windows.count() + 4) - 1; for (auto it = obscuring_windows.constBegin(); it != obscuring_windows.constEnd(); ++it) { xcb_unmap_window(connection(), *it); if (cached->count() < int(max_cache_size)) cached->prepend(*it); else xcb_destroy_window(connection(), *it); } } void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop) { closeActivePopup(); ++block_focus; StackingUpdatesBlocker blocker(this); updateClientVisibilityOnDesktopChange(oldDesktop, newDesktop); // Restore the focus on this desktop --block_focus; activateClientOnNewDesktop(newDesktop); emit currentDesktopChanged(oldDesktop, movingClient); } void Workspace::updateClientVisibilityOnDesktopChange(uint oldDesktop, uint newDesktop) { ObscuringWindows obs_wins; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) { if (c->isShown(true) && c->isOnDesktop(oldDesktop) && !compositing()) obs_wins.create(c); (c)->updateVisibility(); } } // Now propagate the change, after hiding, before showing rootInfo()->setCurrentDesktop(VirtualDesktopManager::self()->current()); if (movingClient && !movingClient->isOnDesktop(newDesktop)) { movingClient->setDesktop(newDesktop); } for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnDesktop(newDesktop) && c->isOnCurrentActivity()) c->updateVisibility(); } if (showingDesktop()) // Do this only after desktop change to avoid flicker setShowingDesktop(false); } void Workspace::activateClientOnNewDesktop(uint desktop) { AbstractClient* c = NULL; if (options->focusPolicyIsReasonable()) { c = findClientToActivateOnDesktop(desktop); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, desktop); if (c != active_client) setActiveClient(NULL); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, desktop)); else focusToNull(); } AbstractClient *Workspace::findClientToActivateOnDesktop(uint desktop) { if (movingClient != NULL && active_client == movingClient && FocusChain::self()->contains(active_client, desktop) && active_client->isShown(true) && active_client->isOnCurrentDesktop()) { // A requestFocus call will fail, as the client is already active return active_client; } // from actiavtion.cpp if (options->isNextFocusPrefersMouse()) { ToplevelList::const_iterator it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { Client *client = qobject_cast(*(--it)); if (!client) { continue; } if (!(client->isShown(false) && client->isOnDesktop(desktop) && client->isOnCurrentActivity() && client->isOnActiveScreen())) continue; if (client->geometry().contains(Cursor::pos())) { if (!client->isDesktop()) return client; break; // unconditional break - we do not pass the focus to some client below an unusable one } } } return FocusChain::self()->getForActivation(desktop); } /** * Updates the current activity when it changes * do *not* call this directly; it does not set the activity. * * Shows/Hides windows according to the stacking order */ void Workspace::updateCurrentActivity(const QString &new_activity) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } //closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date StackingUpdatesBlocker blocker(this); // Optimized Desktop switching: unmapping done from back to front // mapping done from front to back => less exposure events //Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop)); ObscuringWindows obs_wins; const QString &old_activity = Activities::self()->previous(); for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) { if (c->isShown(true) && c->isOnActivity(old_activity) && !compositing()) obs_wins.create(c); c->updateVisibility(); } } // Now propagate the change, after hiding, before showing //rootInfo->setCurrentDesktop( currentDesktop() ); /* TODO someday enable dragging windows to other activities if ( movingClient && !movingClient->isOnDesktop( new_desktop )) { movingClient->setDesktop( new_desktop ); */ for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnActivity(new_activity)) c->updateVisibility(); } //FIXME not sure if I should do this either if (showingDesktop()) // Do this only after desktop change to avoid flicker setShowingDesktop(false); // Restore the focus on this desktop --block_focus; AbstractClient* c = 0; //FIXME below here is a lot of focuschain stuff, probably all wrong now if (options->focusPolicyIsReasonable()) { // Search in focus chain c = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current()); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, VirtualDesktopManager::self()->current()); if (c != active_client) setActiveClient(NULL); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, VirtualDesktopManager::self()->current())); else focusToNull(); // Not for the very first time, only if something changed and there are more than 1 desktops //if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) // static_cast( effects )->desktopChanged( old_desktop ); if (compositing() && m_compositor) m_compositor->addRepaintFull(); #else Q_UNUSED(new_activity) #endif } void Workspace::moveClientsFromRemovedDesktops() { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->isOnAllDesktops() && (*it)->desktop() > static_cast(VirtualDesktopManager::self()->count())) sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true); } } void Workspace::slotDesktopCountChanged(uint previousCount, uint newCount) { Q_UNUSED(previousCount) Placement::self()->reinitCascading(0); resetClientAreas(newCount); } void Workspace::resetClientAreas(uint desktopCount) { // Make it +1, so that it can be accessed as [1..numberofdesktops] workarea.clear(); workarea.resize(desktopCount + 1); restrictedmovearea.clear(); restrictedmovearea.resize(desktopCount + 1); screenarea.clear(); updateClientArea(true); } void Workspace::selectWmInputEventMask() { uint32_t presentMask = 0; Xcb::WindowAttributes attr(rootWindow()); if (!attr.isNull()) { presentMask = attr->your_event_mask; } Xcb::selectInput(rootWindow(), presentMask | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_FOCUS_CHANGE | // For NotifyDetailNone XCB_EVENT_MASK_EXPOSURE ); } /** * Sends client \a c to desktop \a desk. * * Takes care of transients as well. */ void Workspace::sendClientToDesktop(AbstractClient* c, int desk, bool dont_activate) { if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast(VirtualDesktopManager::self()->count())) return; int old_desktop = c->desktop(); bool was_on_desktop = c->isOnDesktop(desk) || c->isOnAllDesktops(); c->setDesktop(desk); if (c->desktop() != desk) // No change or desktop forced return; desk = c->desktop(); // Client did range checking if (c->isOnDesktop(VirtualDesktopManager::self()->current())) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_desktop && // for stickyness changes !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); c->checkWorkspacePosition( QRect(), old_desktop ); if (Client *client = dynamic_cast(c)) { // TODO: adjust transients for non-X11 auto transients_stacking_order = ensureStackingOrder(client->transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToDesktop(*it, desk, dont_activate); } updateClientArea(); } /** * checks whether the X Window with the input focus is on our X11 screen * if the window cannot be determined or inspected, resturn depends on whether there's actually * more than one screen * * this is NOT in any way related to XRandR multiscreen * */ extern bool is_multihead; // main.cpp bool Workspace::isOnCurrentHead() { if (!is_multihead) { return true; } Xcb::CurrentInput currentInput; if (currentInput.window() == XCB_WINDOW_NONE) { return !is_multihead; } Xcb::WindowGeometry geometry(currentInput.window()); if (geometry.isNull()) { // should not happen return !is_multihead; } return rootWindow() == geometry->root; } void Workspace::sendClientToScreen(AbstractClient* c, int screen) { c->sendToScreen(screen); } void Workspace::sendPingToWindow(xcb_window_t window, xcb_timestamp_t timestamp) { rootInfo()->sendPing(window, timestamp); } /** * Delayed focus functions */ void Workspace::delayFocus() { requestFocus(delayfocus_client); cancelDelayFocus(); } void Workspace::requestDelayFocus(AbstractClient* c) { delayfocus_client = c; delete delayFocusTimer; delayFocusTimer = new QTimer(this); connect(delayFocusTimer, SIGNAL(timeout()), this, SLOT(delayFocus())); delayFocusTimer->setSingleShot(true); delayFocusTimer->start(options->delayFocusInterval()); } void Workspace::cancelDelayFocus() { delete delayFocusTimer; delayFocusTimer = 0; } bool Workspace::checkStartupNotification(xcb_window_t w, KStartupInfoId &id, KStartupInfoData &data) { return startup->checkStartup(w, id, data) == KStartupInfo::Match; } /** * Puts the focus on a dummy window * Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { m_nullFocus->focus(); } void Workspace::setShowingDesktop(bool showing) { const bool changed = showing != showing_desktop; rootInfo()->setShowingDesktop(showing); showing_desktop = showing; AbstractClient *topDesk = nullptr; { // for the blocker RAII StackingUpdatesBlocker blocker(this); // updateLayer & lowerClient would invalidate stacking_order for (int i = stacking_order.count() - 1; i > -1; --i) { AbstractClient *c = qobject_cast(stacking_order.at(i)); if (c && c->isOnCurrentDesktop()) { if (c->isDock()) { c->updateLayer(); } else if (c->isDesktop() && c->isShown(true)) { c->updateLayer(); lowerClient(c); if (!topDesk) topDesk = c; if (Client *client = qobject_cast(c)) { foreach (Client *cm, client->group()->members()) { cm->updateLayer(); } } } } } } // ~StackingUpdatesBlocker if (showing_desktop && topDesk) requestFocus(topDesk); if (changed) emit showingDesktopChanged(showing); } void Workspace::disableGlobalShortcutsForClient(bool disable) { if (global_shortcuts_disabled_for_client == disable) return; QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/kglobalaccel"), QStringLiteral("org.kde.KGlobalAccel"), QStringLiteral("blockGlobalShortcuts")); message.setArguments(QList() << disable); QDBusConnection::sessionBus().asyncCall(message); global_shortcuts_disabled_for_client = disable; // Update also Alt+LMB actions etc. for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateMouseGrab(); } QString Workspace::supportInformation() const { QString support; const QString yes = QStringLiteral("yes\n"); const QString no = QStringLiteral("no\n"); support.append(ki18nc("Introductory text shown in the support information.", "KWin Support Information:\n" "The following information should be used when requesting support on e.g. http://forum.kde.org.\n" "It provides information about the currently running instance, which options are used,\n" "what OpenGL driver and which effects are running.\n" "Please post the information provided underneath this introductory text to a paste bin service\n" "like http://paste.kde.org instead of pasting into support threads.\n").toString()); support.append(QStringLiteral("\n==========================\n\n")); // all following strings are intended for support. They need to be pasted to e.g forums.kde.org // it is expected that the support will happen in English language or that the people providing // help understand English. Because of that all texts are not translated support.append(QStringLiteral("Version\n")); support.append(QStringLiteral("=======\n")); support.append(QStringLiteral("KWin version: ")); support.append(QStringLiteral(KWIN_VERSION_STRING)); support.append(QStringLiteral("\n")); support.append(QStringLiteral("Qt Version: ")); support.append(QString::fromUtf8(qVersion())); support.append(QStringLiteral("\n")); support.append(QStringLiteral("Qt compile version: %1\n").arg(QStringLiteral(QT_VERSION_STR))); support.append(QStringLiteral("XCB compile version: %1\n\n").arg(QStringLiteral(XCB_VERSION_STRING))); support.append(QStringLiteral("Operation Mode: ")); switch (kwinApp()->operationMode()) { case Application::OperationModeX11: support.append(QStringLiteral("X11 only")); break; case Application::OperationModeWaylandAndX11: support.append(QStringLiteral("Wayland and X11")); break; case Application::OperationModeXwayland: support.append(QStringLiteral("Xwayland")); break; } support.append(QStringLiteral("\n\n")); support.append(QStringLiteral("Build Options\n")); support.append(QStringLiteral("=============\n")); support.append(QStringLiteral("KWIN_BUILD_DECORATIONS: ")); #ifdef KWIN_BUILD_DECORATIONS support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("KWIN_BUILD_TABBOX: ")); #ifdef KWIN_BUILD_TABBOX support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("KWIN_BUILD_ACTIVITIES: ")); #ifdef KWIN_BUILD_ACTIVITIES support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_INPUT: ")); #if HAVE_INPUT support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_DRM: ")); #if HAVE_DRM support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_GBM: ")); #if HAVE_GBM support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_X11_XCB: ")); #if HAVE_X11_XCB support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_EPOXY_GLX: ")); #if HAVE_EPOXY_GLX support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_WAYLAND_EGL: ")); #if HAVE_WAYLAND_EGL support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("\n")); support.append(QStringLiteral("X11\n")); support.append(QStringLiteral("===\n")); auto x11setup = xcb_get_setup(connection()); support.append(QStringLiteral("Vendor: %1\n").arg(QString::fromUtf8(QByteArray::fromRawData(xcb_setup_vendor(x11setup), xcb_setup_vendor_length(x11setup))))); support.append(QStringLiteral("Vendor Release: %1\n").arg(x11setup->release_number)); support.append(QStringLiteral("Protocol Version/Revision: %1/%2\n").arg(x11setup->protocol_major_version).arg(x11setup->protocol_minor_version)); const auto extensions = Xcb::Extensions::self()->extensions(); for (const auto &e : extensions) { support.append(QStringLiteral("%1: %2; Version: 0x%3\n").arg(QString::fromUtf8(e.name)) .arg(e.present ? yes.trimmed() : no.trimmed()) .arg(QString::number(e.version, 16))); } 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("Options\n")); support.append(QStringLiteral("=======\n")); const QMetaObject *metaOptions = options->metaObject(); auto printProperty = [] (const QVariant &variant) { if (variant.type() == QVariant::Size) { const QSize &s = variant.toSize(); return QStringLiteral("%1x%2").arg(QString::number(s.width())).arg(QString::number(s.height())); } if (QLatin1String(variant.typeName()) == QLatin1String("KWin::OpenGLPlatformInterface") || QLatin1String(variant.typeName()) == QLatin1String("KWin::Options::WindowOperation")) { return QString::number(variant.toInt()); } return variant.toString(); }; for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (QLatin1String(property.name()) == QLatin1String("objectName")) { continue; } support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(options->property(property.name())))); } support.append(QStringLiteral("\nScreen Edges\n")); support.append(QStringLiteral( "============\n")); const QMetaObject *metaScreenEdges = ScreenEdges::self()->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaScreenEdges->property(i); if (QLatin1String(property.name()) == QLatin1String("objectName")) { continue; } support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(ScreenEdges::self()->property(property.name())))); } support.append(QStringLiteral("\nScreens\n")); support.append(QStringLiteral( "=======\n")); support.append(QStringLiteral("Multi-Head: ")); if (is_multihead) { support.append(QStringLiteral("yes\n")); support.append(QStringLiteral("Head: %1\n").arg(screen_number)); } else { support.append(QStringLiteral("no\n")); } support.append(QStringLiteral("Active screen follows mouse: ")); if (screens()->isCurrentFollowsMouse()) support.append(QStringLiteral(" yes\n")); else support.append(QStringLiteral(" no\n")); support.append(QStringLiteral("Number of Screens: %1\n\n").arg(screens()->count())); for (int i=0; icount(); ++i) { const QRect geo = screens()->geometry(i); support.append(QStringLiteral("Screen %1:\n").arg(i)); support.append(QStringLiteral("---------\n").arg(i)); support.append(QStringLiteral("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("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; } void Workspace::slotToggleCompositing() { if (m_compositor) { m_compositor->slotToggleCompositing(); } } Client *Workspace::findClient(std::function func) const { if (Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } return nullptr; } AbstractClient *Workspace::findAbstractClient(std::function func) const { if (AbstractClient *ret = Toplevel::findInList(m_allClients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } if (waylandServer()) { if (AbstractClient *ret = Toplevel::findInList(waylandServer()->internalClients(), func)) { return ret; } } return nullptr; } Unmanaged *Workspace::findUnmanaged(std::function func) const { return Toplevel::findInList(unmanaged, func); } Unmanaged *Workspace::findUnmanaged(xcb_window_t w) const { return findUnmanaged([w](const Unmanaged *u) { return u->window() == w; }); } Client *Workspace::findClient(Predicate predicate, xcb_window_t w) const { switch (predicate) { case Predicate::WindowMatch: return findClient([w](const Client *c) { return c->window() == w; }); case Predicate::WrapperIdMatch: return findClient([w](const Client *c) { return c->wrapperId() == w; }); case Predicate::FrameIdMatch: return findClient([w](const Client *c) { return c->frameId() == w; }); case Predicate::InputIdMatch: return findClient([w](const Client *c) { return c->inputId() == w; }); } return nullptr; } Toplevel *Workspace::findToplevel(std::function func) const { if (Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } if (Unmanaged *ret = Toplevel::findInList(unmanaged, func)) { return ret; } return nullptr; } Toplevel *Workspace::findToplevel(QWindow *w) const { if (!w) { return nullptr; } if (waylandServer()) { if (auto c = waylandServer()->findClient(w)) { return c; } } return findUnmanaged(w->winId()); } bool Workspace::hasClient(const AbstractClient *c) { if (auto cc = dynamic_cast(c)) { return hasClient(cc); } else { return findAbstractClient([c](const AbstractClient *test) { return test == c; }) != nullptr; } return false; } void Workspace::forEachAbstractClient(std::function< void (AbstractClient*) > func) { std::for_each(m_allClients.constBegin(), m_allClients.constEnd(), func); std::for_each(desktops.constBegin(), desktops.constEnd(), func); } Toplevel *Workspace::findInternal(QWindow *w) const { if (!w) { return nullptr; } if (kwinApp()->operationMode() == Application::OperationModeX11) { return findUnmanaged(w->winId()); } else { return waylandServer()->findClient(w); } } } // namespace diff --git a/workspace.h b/workspace.h index d2e49bd72..b390f9614 100644 --- a/workspace.h +++ b/workspace.h @@ -1,770 +1,772 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_WORKSPACE_H #define KWIN_WORKSPACE_H // kwin #include "sm.h" #include "options.h" #include "utils.h" // Qt #include #include // std #include // TODO: Cleanup the order of things in this .h file class QStringList; class KConfig; class KConfigGroup; class KStartupInfo; class KStartupInfoId; class KStartupInfoData; namespace KWin { namespace Xcb { class Window; } class AbstractClient; class Client; class KillWindow; class ShortcutDialog; class UserActionsMenu; class Compositor; class X11EventFilter; enum class Predicate; class KWIN_EXPORT Workspace : public QObject { Q_OBJECT public: explicit Workspace(const QString &sessionKey = QString()); virtual ~Workspace(); static Workspace* self() { return _self; } bool workspaceEvent(xcb_generic_event_t*); bool workspaceEvent(QEvent*); bool hasClient(const Client*); bool hasClient(const AbstractClient*); /** * @brief Finds the first Client matching the condition expressed by passed in @p func. * * Internally findClient uses the std::find_if algorithm and that determines how the function * needs to be implemented. An example usage for finding a Client with a matching windowId * @code * xcb_window_t w; // our test window * Client *client = findClient([w](const Client *c) -> bool { * return c->window() == w; * }); * @endcode * * For the standard cases of matching the window id with one of the Client's windows use * the simplified overload method findClient(Predicate, xcb_window_t). Above example * can be simplified to: * @code * xcb_window_t w; // our test window * Client *client = findClient(Predicate::WindowMatch, w); * @endcode * * @param func Unary function that accepts a Client* as argument and * returns a value convertible to bool. The value returned indicates whether the * Client* is considered a match in the context of this function. * The function shall not modify its argument. * This can either be a function pointer or a function object. * @return KWin::Client* The found Client or @c null * @see findClient(Predicate, xcb_window_t) */ Client *findClient(std::function func) const; AbstractClient *findAbstractClient(std::function func) const; /** * @brief Finds the Client matching the given match @p predicate for the given window. * * @param predicate Which window should be compared * @param w The window id to test against * @return KWin::Client* The found Client or @c null * @see findClient(std::function) */ Client *findClient(Predicate predicate, xcb_window_t w) const; void forEachClient(std::function func); void forEachAbstractClient(std::function func); Unmanaged *findUnmanaged(std::function func) const; /** * @brief Finds the Unmanaged with the given window id. * * @param w The window id to search for * @return KWin::Unmanaged* Found Unmanaged or @c null if there is no Unmanaged with given Id. */ Unmanaged *findUnmanaged(xcb_window_t w) const; void forEachUnmanaged(std::function func); Toplevel *findToplevel(std::function func) const; /** * Finds the Toplevel for the KWin internal window @p w. * On Wayland this is normally a ShellClient. For X11 an Unmanaged. **/ Toplevel *findToplevel(QWindow *w) const; /** * @brief Finds a Toplevel for the internal window @p w. * * Internal window means a window created by KWin itself. On X11 this is an Unmanaged * and mapped by the window id, on Wayland a ShellClient mapped on the internal window id. * * @returns Toplevel **/ Toplevel *findInternal(QWindow *w) const; QRect clientArea(clientAreaOption, const QPoint& p, int desktop) const; QRect clientArea(clientAreaOption, const AbstractClient* c) const; QRect clientArea(clientAreaOption, int screen, int desktop) const; QRegion restrictedMoveArea(int desktop, StrutAreas areas = StrutAreaAll) const; bool initializing() const; /** * Returns the active client, i.e. the client that has the focus (or None * if no client has the focus) */ AbstractClient* activeClient() const; /** * Client that was activated, but it's not yet really activeClient(), because * we didn't process yet the matching FocusIn event. Used mostly in focus * stealing prevention code. */ AbstractClient* mostRecentlyActivatedClient() const; AbstractClient* clientUnderMouse(int screen) const; void activateClient(AbstractClient*, bool force = false); void requestFocus(AbstractClient* c, bool force = false); enum ActivityFlag { ActivityFocus = 1 << 0, // focus the window ActivityFocusForce = 1 << 1 | ActivityFocus, // focus even if Dock etc. ActivityRaise = 1 << 2 // raise the window }; Q_DECLARE_FLAGS(ActivityFlags, ActivityFlag) void takeActivity(AbstractClient* c, ActivityFlags flags); bool allowClientActivation(const AbstractClient* c, xcb_timestamp_t time = -1U, bool focus_in = false, bool ignore_desktop = false); void restoreFocus(); void gotFocusIn(const AbstractClient*); void setShouldGetFocus(AbstractClient*); bool activateNextClient(AbstractClient* c); bool focusChangeEnabled() { return block_focus == 0; } /** * Indicates that the client c is being moved around by the user. */ void setClientIsMoving(AbstractClient* c); QPoint adjustClientPosition(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust = 1.0); QRect adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode); void raiseClient(AbstractClient* c, bool nogroup = false); void lowerClient(AbstractClient* c, bool nogroup = false); void raiseClientRequest(AbstractClient* c, NET::RequestSource src = NET::FromApplication, xcb_timestamp_t timestamp = 0); void lowerClientRequest(Client* c, NET::RequestSource src, xcb_timestamp_t timestamp); void lowerClientRequest(AbstractClient* c); void restackClientUnderActive(AbstractClient*); void restack(AbstractClient *c, AbstractClient *under, bool force = false); void updateClientLayer(AbstractClient* c); void raiseOrLowerClient(AbstractClient*); void resetUpdateToolWindowsTimer(); void restoreSessionStackingOrder(Client* c); void updateStackingOrder(bool propagate_new_clients = false); void forceRestacking(); void clientHidden(AbstractClient*); void clientAttentionChanged(AbstractClient* c, bool set); /** * @return List of clients currently managed by Workspace **/ const ClientList &clientList() const { return clients; } /** * @return List of unmanaged "clients" currently registered in Workspace **/ const UnmanagedList &unmanagedList() const { return unmanaged; } /** * @return List of desktop "clients" currently managed by Workspace **/ const ClientList &desktopList() const { return desktops; } /** * @return List of deleted "clients" currently managed by Workspace **/ const DeletedList &deletedList() const { return deleted; } /** * @returns List of all clients (either X11 or Wayland) currently managed by Workspace **/ const QList allClientList() const { return m_allClients; } void stackScreenEdgesUnderOverrideRedirect(); public: QPoint cascadeOffset(const AbstractClient *c) const; private: Compositor *m_compositor; //------------------------------------------------- // Unsorted public: bool isOnCurrentHead(); // True when performing Workspace::updateClientArea(). // The calls below are valid only in that case. bool inUpdateClientArea() const; QRegion previousRestrictedMoveArea(int desktop, StrutAreas areas = StrutAreaAll) const; QVector< QRect > previousScreenSizes() const; int oldDisplayWidth() const; int oldDisplayHeight() const; /** * Returns the list of clients sorted in stacking order, with topmost client * at the last position */ const ToplevelList& stackingOrder() const; ToplevelList xStackingOrder() const; ClientList ensureStackingOrder(const ClientList& clients) const; QList ensureStackingOrder(const QList &clients) const; AbstractClient* topClientOnDesktop(int desktop, int screen, bool unconstrained = false, bool only_normal = true) const; AbstractClient* findDesktop(bool topmost, int desktop) const; void sendClientToDesktop(AbstractClient* c, int desktop, bool dont_activate); void windowToPreviousDesktop(AbstractClient* c); void windowToNextDesktop(AbstractClient* c); void sendClientToScreen(AbstractClient* c, int screen); /** * Shows the menu operations menu for the client and makes it active if * it's not already. */ void showWindowMenu(const QRect& pos, AbstractClient* cl); const UserActionsMenu *userActionsMenu() const { return m_userActionsMenu; } + void showApplicationMenu(const QRect &pos, AbstractClient *c, int actionId); + void updateMinimizedOfTransients(AbstractClient*); void updateOnAllDesktopsOfTransients(AbstractClient*); void checkTransients(xcb_window_t w); void storeSession(KConfig* config, SMSavePhase phase); void storeClient(KConfigGroup &cg, int num, Client *c); void storeSubSession(const QString &name, QSet sessionIds); void loadSubSessionInfo(const QString &name); SessionInfo* takeSessionInfo(Client*); // D-Bus interface bool waitForCompositingSetup(); QString supportInformation() const; void setCurrentScreen(int new_screen); void setShowingDesktop(bool showing); bool showingDesktop() const; void sendPingToWindow(xcb_window_t w, xcb_timestamp_t timestamp); // Called from Client::pingWindow() void removeClient(Client*); // Only called from Client::destroyClient() or Client::releaseWindow() void setActiveClient(AbstractClient*); Group* findGroup(xcb_window_t leader) const; void addGroup(Group* group); void removeGroup(Group* group); Group* findClientLeaderGroup(const Client* c) const; void removeUnmanaged(Unmanaged*); // Only called from Unmanaged::release() void removeDeleted(Deleted*); void addDeleted(Deleted*, Toplevel*); bool checkStartupNotification(xcb_window_t w, KStartupInfoId& id, KStartupInfoData& data); void focusToNull(); // SELI TODO: Public? void clientShortcutUpdated(Client* c); bool shortcutAvailable(const QKeySequence &cut, Client* ignore = NULL) const; bool globalShortcutsDisabled() const; void disableGlobalShortcutsForClient(bool disable); void sessionSaveStarted(); void sessionSaveDone(); void setWasUserInteraction(); bool wasUserInteraction() const; bool sessionSaving() const; int packPositionLeft(const AbstractClient* cl, int oldx, bool left_edge) const; int packPositionRight(const AbstractClient* cl, int oldx, bool right_edge) const; int packPositionUp(const AbstractClient* cl, int oldy, bool top_edge) const; int packPositionDown(const AbstractClient* cl, int oldy, bool bottom_edge) const; void cancelDelayFocus(); void requestDelayFocus(AbstractClient*); /** * updates the mouse position to track whether a focus follow mouse focus change was caused by * an actual mouse move * is esp. called on enter/motion events of inactive windows * since an active window doesn't receive mouse events, it must also be invoked if a (potentially) * active window might be moved/resize away from the cursor (causing a leave event) */ void updateFocusMousePosition(const QPoint& pos); QPoint focusMousePosition() const; AbstractClient* getMovingClient() { return movingClient; } /** * @returns Whether we have a Compositor and it is active (Scene created) **/ bool compositing() const; void registerEventFilter(X11EventFilter *filter); void unregisterEventFilter(X11EventFilter *filter); public Q_SLOTS: void performWindowOperation(KWin::AbstractClient* c, Options::WindowOperation op); // Keybindings //void slotSwitchToWindow( int ); void slotWindowToDesktop(); //void slotWindowToListPosition( int ); void slotSwitchToScreen(); void slotWindowToScreen(); void slotSwitchToNextScreen(); void slotWindowToNextScreen(); void slotSwitchToPrevScreen(); void slotWindowToPrevScreen(); void slotToggleShowDesktop(); void slotWindowMaximize(); void slotWindowMaximizeVertical(); void slotWindowMaximizeHorizontal(); void slotWindowMinimize(); void slotWindowShade(); void slotWindowRaise(); void slotWindowLower(); void slotWindowRaiseOrLower(); void slotActivateAttentionWindow(); void slotWindowPackLeft(); void slotWindowPackRight(); void slotWindowPackUp(); void slotWindowPackDown(); void slotWindowGrowHorizontal(); void slotWindowGrowVertical(); void slotWindowShrinkHorizontal(); void slotWindowShrinkVertical(); void slotWindowQuickTileLeft(); void slotWindowQuickTileRight(); void slotWindowQuickTileTop(); void slotWindowQuickTileBottom(); void slotWindowQuickTileTopLeft(); void slotWindowQuickTileTopRight(); void slotWindowQuickTileBottomLeft(); void slotWindowQuickTileBottomRight(); void slotSwitchWindowUp(); void slotSwitchWindowDown(); void slotSwitchWindowRight(); void slotSwitchWindowLeft(); void slotIncreaseWindowOpacity(); void slotLowerWindowOpacity(); void slotWindowOperations(); void slotWindowClose(); void slotWindowMove(); void slotWindowResize(); void slotWindowAbove(); void slotWindowBelow(); void slotWindowOnAllDesktops(); void slotWindowFullScreen(); void slotWindowNoBorder(); void slotWindowToNextDesktop(); void slotWindowToPreviousDesktop(); void slotWindowToDesktopRight(); void slotWindowToDesktopLeft(); void slotWindowToDesktopUp(); void slotWindowToDesktopDown(); void reconfigure(); void slotReconfigure(); void slotKillWindow(); void slotSetupWindowShortcut(); void setupWindowShortcutDone(bool); void slotToggleCompositing(); void slotInvertScreen(); void updateClientArea(); void slotActivateNextTab(); // Slot to move left the active Client. void slotActivatePrevTab(); // Slot to move right the active Client. void slotUntab(); // Slot to remove the active client from its group. private Q_SLOTS: void desktopResized(); void selectWmInputEventMask(); void slotUpdateToolWindows(); void delayFocus(); void slotReloadConfig(); void updateCurrentActivity(const QString &new_activity); // virtual desktop handling void moveClientsFromRemovedDesktops(); void slotDesktopCountChanged(uint previousCount, uint newCount); void slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop); // session management void saveState(QSessionManager &sm); void commitData(QSessionManager &sm); Q_SIGNALS: /** * Emitted after the Workspace has setup the complete initialization process. * This can be used to connect to for performing post-workspace initialization. **/ void workspaceInitialized(); //Signals required for the scripting interface void desktopPresenceChanged(KWin::AbstractClient*, int); void currentDesktopChanged(int, KWin::AbstractClient*); void clientAdded(KWin::Client*); void clientRemoved(KWin::AbstractClient*); void clientActivated(KWin::AbstractClient*); void clientDemandsAttentionChanged(KWin::AbstractClient*, bool); void groupAdded(KWin::Group*); void unmanagedAdded(KWin::Unmanaged*); void unmanagedRemoved(KWin::Unmanaged*); void deletedRemoved(KWin::Deleted*); void propertyNotify(long a); void configChanged(); void reinitializeCompositing(); void showingDesktopChanged(bool showing); /** * This signels is emitted when ever the stacking order is change, ie. a window is risen * or lowered */ void stackingOrderChanged(); private: void init(); void initShortcuts(); template void initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, Slot slot, const QVariant &data = QVariant()); void setupWindowShortcut(AbstractClient* c); enum Direction { DirectionNorth, DirectionEast, DirectionSouth, DirectionWest }; void switchWindow(Direction direction); bool switchWindow(AbstractClient *c, Direction direction, QPoint curPos, int desktop); void propagateClients(bool propagate_new_clients); // Called only from updateStackingOrder ToplevelList constrainedStackingOrder(); void raiseClientWithinApplication(AbstractClient* c); void lowerClientWithinApplication(AbstractClient* c); bool allowFullClientRaising(const AbstractClient* c, xcb_timestamp_t timestamp); bool keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient); void blockStackingUpdates(bool block); void updateToolWindows(bool also_hide); void fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geom); void saveOldScreenSizes(); /// This is the right way to create a new client Client* createClient(xcb_window_t w, bool is_mapped); void setupClientConnections(AbstractClient *client); void addClient(Client* c); Unmanaged* createUnmanaged(xcb_window_t w); void addUnmanaged(Unmanaged* c); //--------------------------------------------------------------------- void closeActivePopup(); void updateClientArea(bool force); void resetClientAreas(uint desktopCount); void updateClientVisibilityOnDesktopChange(uint oldDesktop, uint newDesktop); void activateClientOnNewDesktop(uint desktop); AbstractClient *findClientToActivateOnDesktop(uint desktop); QWidget* active_popup; AbstractClient* active_popup_client; int m_initialDesktop; void loadSessionInfo(const QString &key); void addSessionInfo(KConfigGroup &cg); QList session; static const char* windowTypeToTxt(NET::WindowType type); static NET::WindowType txtToWindowType(const char* txt); static bool sessionInfoWindowTypeMatch(Client* c, SessionInfo* info); AbstractClient* active_client; AbstractClient* last_active_client; AbstractClient* most_recently_raised; // Used ONLY by raiseOrLowerClient() AbstractClient* movingClient; // Delay(ed) window focus timer and client QTimer* delayFocusTimer; AbstractClient* delayfocus_client; QPoint focusMousePos; ClientList clients; QList m_allClients; ClientList desktops; UnmanagedList unmanaged; DeletedList deleted; ToplevelList unconstrained_stacking_order; // Topmost last ToplevelList stacking_order; // Topmost last bool force_restacking; mutable ToplevelList x_stacking; // From XQueryTree() mutable bool x_stacking_dirty; QList should_get_focus; // Last is most recent QList attention_chain; bool showing_desktop; GroupList groups; bool was_user_interaction; bool session_saving; int session_active_client; int session_desktop; int block_focus; /** * Holds the menu containing the user actions which is shown * on e.g. right click the window decoration. **/ UserActionsMenu *m_userActionsMenu; void modalActionsSwitch(bool enabled); ShortcutDialog* client_keys_dialog; AbstractClient* client_keys_client; bool global_shortcuts_disabled_for_client; // Timer to collect requests for 'reconfigure' QTimer reconfigureTimer; QTimer updateToolWindowsTimer; static Workspace* _self; bool workspaceInit; KStartupInfo* startup; QVector workarea; // Array of workareas for virtual desktops // Array of restricted areas that window cannot be moved into QVector restrictedmovearea; // Array of the previous restricted areas that window cannot be moved into QVector oldrestrictedmovearea; QVector< QVector > screenarea; // Array of workareas per xinerama screen for all virtual desktops QVector< QRect > oldscreensizes; // array of previous sizes of xinerama screens QSize olddisplaysize; // previous sizes od displayWidth()/displayHeight() int set_active_client_recursion; int block_stacking_updates; // When > 0, stacking updates are temporarily disabled bool blocked_propagating_new_clients; // Propagate also new clients after enabling stacking updates? QScopedPointer m_nullFocus; friend class StackingUpdatesBlocker; QScopedPointer m_windowKiller; QList m_eventFilters; QList m_genericEventFilters; private: friend bool performTransiencyCheck(); friend Workspace *workspace(); }; /** * Helper for Workspace::blockStackingUpdates() being called in pairs (True/false) */ class StackingUpdatesBlocker { public: explicit StackingUpdatesBlocker(Workspace* w) : ws(w) { ws->blockStackingUpdates(true); } ~StackingUpdatesBlocker() { ws->blockStackingUpdates(false); } private: Workspace* ws; }; class ColorMapper : public QObject { Q_OBJECT public: ColorMapper(QObject *parent); virtual ~ColorMapper(); public Q_SLOTS: void update(); private: xcb_colormap_t m_default; xcb_colormap_t m_installed; }; //--------------------------------------------------------- // Unsorted inline bool Workspace::initializing() const { return workspaceInit; } inline AbstractClient *Workspace::activeClient() const { return active_client; } inline AbstractClient *Workspace::mostRecentlyActivatedClient() const { return should_get_focus.count() > 0 ? should_get_focus.last() : active_client; } inline void Workspace::addGroup(Group* group) { emit groupAdded(group); groups.append(group); } inline void Workspace::removeGroup(Group* group) { groups.removeAll(group); } inline const ToplevelList& Workspace::stackingOrder() const { // TODO: Q_ASSERT( block_stacking_updates == 0 ); return stacking_order; } inline void Workspace::setWasUserInteraction() { was_user_interaction = true; } inline bool Workspace::wasUserInteraction() const { return was_user_interaction; } inline void Workspace::sessionSaveStarted() { session_saving = true; } inline bool Workspace::sessionSaving() const { return session_saving; } inline bool Workspace::showingDesktop() const { return showing_desktop; } inline bool Workspace::globalShortcutsDisabled() const { return global_shortcuts_disabled_for_client; } inline void Workspace::forceRestacking() { force_restacking = true; StackingUpdatesBlocker blocker(this); // Do restacking if not blocked } inline void Workspace::updateFocusMousePosition(const QPoint& pos) { focusMousePos = pos; } inline QPoint Workspace::focusMousePosition() const { return focusMousePos; } inline void Workspace::forEachClient(std::function< void (Client*) > func) { std::for_each(clients.constBegin(), clients.constEnd(), func); std::for_each(desktops.constBegin(), desktops.constEnd(), func); } inline void Workspace::forEachUnmanaged(std::function< void (Unmanaged*) > func) { std::for_each(unmanaged.constBegin(), unmanaged.constEnd(), func); } inline bool Workspace::hasClient(const Client* c) { return findClient([c](const Client *test) { return test == c; }); } inline Workspace *workspace() { return Workspace::_self; } } // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Workspace::ActivityFlags) #endif