File indexing completed on 2024-04-28 16:49:15

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
0006     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 // own
0011 #include "x11window.h"
0012 // kwin
0013 #include "core/output.h"
0014 #if KWIN_BUILD_ACTIVITIES
0015 #include "activities.h"
0016 #endif
0017 #include "atoms.h"
0018 #include "client_machine.h"
0019 #include "composite.h"
0020 #include "cursor.h"
0021 #include "decorations/decoratedclient.h"
0022 #include "decorations/decorationbridge.h"
0023 #include "deleted.h"
0024 #include "effects.h"
0025 #include "focuschain.h"
0026 #include "group.h"
0027 #include "netinfo.h"
0028 #include "placement.h"
0029 #include "scene/surfaceitem_x11.h"
0030 #include "scene/windowitem.h"
0031 #include "screenedge.h"
0032 #include "shadow.h"
0033 #include "virtualdesktops.h"
0034 #include "wayland_server.h"
0035 #include "workspace.h"
0036 #include <KDecoration2/DecoratedClient>
0037 #include <KDecoration2/Decoration>
0038 // KDE
0039 #include <KLocalizedString>
0040 #include <KStartupInfo>
0041 #include <KX11Extras>
0042 // Qt
0043 #include <QApplication>
0044 #include <QDebug>
0045 #include <QDir>
0046 #include <QFile>
0047 #include <QFileInfo>
0048 #include <QMouseEvent>
0049 #include <QProcess>
0050 // xcb
0051 #include <xcb/xcb_icccm.h>
0052 // system
0053 #include <unistd.h>
0054 // c++
0055 #include <cmath>
0056 #include <csignal>
0057 
0058 // Put all externs before the namespace statement to allow the linker
0059 // to resolve them properly
0060 
0061 namespace KWin
0062 {
0063 
0064 static uint32_t frameEventMask()
0065 {
0066     if (waylandServer()) {
0067         return XCB_EVENT_MASK_FOCUS_CHANGE
0068             | XCB_EVENT_MASK_STRUCTURE_NOTIFY
0069             | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
0070             | XCB_EVENT_MASK_PROPERTY_CHANGE;
0071     } else {
0072         return XCB_EVENT_MASK_FOCUS_CHANGE
0073             | XCB_EVENT_MASK_STRUCTURE_NOTIFY
0074             | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
0075             | XCB_EVENT_MASK_PROPERTY_CHANGE
0076             | XCB_EVENT_MASK_KEY_PRESS
0077             | XCB_EVENT_MASK_KEY_RELEASE
0078             | XCB_EVENT_MASK_ENTER_WINDOW
0079             | XCB_EVENT_MASK_LEAVE_WINDOW
0080             | XCB_EVENT_MASK_BUTTON_PRESS
0081             | XCB_EVENT_MASK_BUTTON_RELEASE
0082             | XCB_EVENT_MASK_BUTTON_MOTION
0083             | XCB_EVENT_MASK_POINTER_MOTION
0084             | XCB_EVENT_MASK_KEYMAP_STATE
0085             | XCB_EVENT_MASK_EXPOSURE
0086             | XCB_EVENT_MASK_VISIBILITY_CHANGE;
0087     }
0088 }
0089 
0090 static uint32_t wrapperEventMask()
0091 {
0092     if (waylandServer()) {
0093         return XCB_EVENT_MASK_FOCUS_CHANGE
0094             | XCB_EVENT_MASK_STRUCTURE_NOTIFY
0095             | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
0096             | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
0097     } else {
0098         return XCB_EVENT_MASK_FOCUS_CHANGE
0099             | XCB_EVENT_MASK_STRUCTURE_NOTIFY
0100             | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
0101             | XCB_EVENT_MASK_KEY_PRESS
0102             | XCB_EVENT_MASK_KEY_RELEASE
0103             | XCB_EVENT_MASK_ENTER_WINDOW
0104             | XCB_EVENT_MASK_LEAVE_WINDOW
0105             | XCB_EVENT_MASK_BUTTON_PRESS
0106             | XCB_EVENT_MASK_BUTTON_RELEASE
0107             | XCB_EVENT_MASK_BUTTON_MOTION
0108             | XCB_EVENT_MASK_POINTER_MOTION
0109             | XCB_EVENT_MASK_KEYMAP_STATE
0110             | XCB_EVENT_MASK_EXPOSURE
0111             | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
0112     }
0113 }
0114 
0115 static uint32_t clientEventMask()
0116 {
0117     if (waylandServer()) {
0118         return XCB_EVENT_MASK_FOCUS_CHANGE
0119             | XCB_EVENT_MASK_PROPERTY_CHANGE;
0120     } else {
0121         return XCB_EVENT_MASK_FOCUS_CHANGE
0122             | XCB_EVENT_MASK_PROPERTY_CHANGE
0123             | XCB_EVENT_MASK_ENTER_WINDOW
0124             | XCB_EVENT_MASK_LEAVE_WINDOW
0125             | XCB_EVENT_MASK_KEY_PRESS
0126             | XCB_EVENT_MASK_KEY_RELEASE;
0127     }
0128 }
0129 
0130 // window types that are supported as normal windows (i.e. KWin actually manages them)
0131 const NET::WindowTypes SUPPORTED_MANAGED_WINDOW_TYPES_MASK = NET::NormalMask
0132     | NET::DesktopMask
0133     | NET::DockMask
0134     | NET::ToolbarMask
0135     | NET::MenuMask
0136     | NET::DialogMask
0137     /*| NET::OverrideMask*/
0138     | NET::TopMenuMask
0139     | NET::UtilityMask
0140     | NET::SplashMask
0141     | NET::NotificationMask
0142     | NET::OnScreenDisplayMask
0143     | NET::CriticalNotificationMask
0144     | NET::AppletPopupMask;
0145 
0146 X11DecorationRenderer::X11DecorationRenderer(Decoration::DecoratedClientImpl *client)
0147     : DecorationRenderer(client)
0148     , m_scheduleTimer(new QTimer(this))
0149     , m_gc(XCB_NONE)
0150 {
0151     // Delay any rendering to end of event cycle to catch multiple updates per cycle.
0152     m_scheduleTimer->setSingleShot(true);
0153     m_scheduleTimer->setInterval(0);
0154     connect(m_scheduleTimer, &QTimer::timeout, this, &X11DecorationRenderer::update);
0155     connect(this, &X11DecorationRenderer::damaged, m_scheduleTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
0156 }
0157 
0158 X11DecorationRenderer::~X11DecorationRenderer()
0159 {
0160     if (m_gc != XCB_NONE) {
0161         xcb_free_gc(kwinApp()->x11Connection(), m_gc);
0162     }
0163 }
0164 
0165 void X11DecorationRenderer::update()
0166 {
0167     if (!damage().isEmpty()) {
0168         render(damage());
0169         resetDamage();
0170     }
0171 }
0172 
0173 void X11DecorationRenderer::render(const QRegion &region)
0174 {
0175     if (!client()) {
0176         return;
0177     }
0178     xcb_connection_t *c = kwinApp()->x11Connection();
0179     if (m_gc == XCB_NONE) {
0180         m_gc = xcb_generate_id(c);
0181         xcb_create_gc(c, m_gc, client()->window()->frameId(), 0, nullptr);
0182     }
0183 
0184     QRectF left, top, right, bottom;
0185     client()->window()->layoutDecorationRects(left, top, right, bottom);
0186 
0187     const QRect geometry = region.boundingRect();
0188     left = left.intersected(geometry);
0189     top = top.intersected(geometry);
0190     right = right.intersected(geometry);
0191     bottom = bottom.intersected(geometry);
0192 
0193     auto renderPart = [this, c](const QRect &geo) {
0194         if (!geo.isValid()) {
0195             return;
0196         }
0197         QImage image = renderToImage(geo);
0198         xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, client()->window()->frameId(), m_gc,
0199                       image.width(), image.height(), geo.x(), geo.y(), 0, client()->window()->depth(),
0200                       image.sizeInBytes(), image.constBits());
0201     };
0202     renderPart(left.toRect());
0203     renderPart(top.toRect());
0204     renderPart(right.toRect());
0205     renderPart(bottom.toRect());
0206 
0207     xcb_flush(c);
0208     resetImageSizesDirty();
0209 }
0210 
0211 // Creating a client:
0212 //  - only by calling Workspace::createClient()
0213 //      - it creates a new client and calls manage() for it
0214 //
0215 // Destroying a client:
0216 //  - destroyWindow() - only when the window itself has been destroyed
0217 //      - releaseWindow() - the window is kept, only the client itself is destroyed
0218 
0219 /**
0220  * \class Client x11client.h
0221  * \brief The Client class encapsulates a window decoration frame.
0222  */
0223 
0224 /**
0225  * This ctor is "dumb" - it only initializes data. All the real initialization
0226  * is done in manage().
0227  */
0228 X11Window::X11Window()
0229     : Window()
0230     , m_client()
0231     , m_wrapper()
0232     , m_frame()
0233     , m_activityUpdatesBlocked(false)
0234     , m_blockedActivityUpdatesRequireTransients(false)
0235     , m_moveResizeGrabWindow()
0236     , move_resize_has_keyboard_grab(false)
0237     , m_managed(false)
0238     , m_transientForId(XCB_WINDOW_NONE)
0239     , m_originalTransientForId(XCB_WINDOW_NONE)
0240     , shade_below(nullptr)
0241     , m_motif(atoms->motif_wm_hints)
0242     , blocks_compositing(false)
0243     , m_colormap(XCB_COLORMAP_NONE)
0244     , in_group(nullptr)
0245     , ping_timer(nullptr)
0246     , m_killHelperPID(0)
0247     , m_pingTimestamp(XCB_TIME_CURRENT_TIME)
0248     , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet
0249     , allowed_actions()
0250     , shade_geometry_change(false)
0251     , sm_stacking_order(-1)
0252     , activitiesDefined(false)
0253     , sessionActivityOverride(false)
0254     , m_decoInputExtent()
0255     , m_focusOutTimer(nullptr)
0256 {
0257     // TODO: Do all as initialization
0258     m_syncRequest.counter = m_syncRequest.alarm = XCB_NONE;
0259     m_syncRequest.timeout = m_syncRequest.failsafeTimeout = nullptr;
0260     m_syncRequest.lastTimestamp = xTime();
0261     m_syncRequest.isPending = false;
0262     m_syncRequest.interactiveResize = false;
0263 
0264     // Set the initial mapping state
0265     mapping_state = Withdrawn;
0266 
0267     info = nullptr;
0268 
0269     m_fullscreenMode = FullScreenNone;
0270     hidden = false;
0271     noborder = false;
0272     app_noborder = false;
0273     ignore_focus_stealing = false;
0274     check_active_modal = false;
0275 
0276     max_mode = MaximizeRestore;
0277 
0278     connect(clientMachine(), &ClientMachine::localhostChanged, this, &X11Window::updateCaption);
0279     connect(options, &Options::configChanged, this, &X11Window::updateMouseGrab);
0280     connect(options, &Options::condensedTitleChanged, this, &X11Window::updateCaption);
0281 
0282     if (kwinApp()->operationMode() == Application::OperationModeX11) {
0283         connect(this, &X11Window::moveResizeCursorChanged, this, [this](CursorShape cursor) {
0284             xcb_cursor_t nativeCursor = Cursors::self()->mouse()->x11Cursor(cursor);
0285             m_frame.defineCursor(nativeCursor);
0286             if (m_decoInputExtent.isValid()) {
0287                 m_decoInputExtent.defineCursor(nativeCursor);
0288             }
0289             if (isInteractiveMoveResize()) {
0290                 // changing window attributes doesn't change cursor if there's pointer grab active
0291                 xcb_change_active_pointer_grab(kwinApp()->x11Connection(), nativeCursor, xTime(),
0292                                                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);
0293             }
0294         });
0295     }
0296 
0297     // SELI TODO: Initialize xsizehints??
0298 }
0299 
0300 /**
0301  * "Dumb" destructor.
0302  */
0303 X11Window::~X11Window()
0304 {
0305     if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
0306         ::kill(m_killHelperPID, SIGTERM);
0307         m_killHelperPID = 0;
0308     }
0309     if (m_syncRequest.alarm != XCB_NONE) {
0310         xcb_sync_destroy_alarm(kwinApp()->x11Connection(), m_syncRequest.alarm);
0311     }
0312     Q_ASSERT(!isInteractiveMoveResize());
0313     Q_ASSERT(m_client == XCB_WINDOW_NONE);
0314     Q_ASSERT(m_wrapper == XCB_WINDOW_NONE);
0315     Q_ASSERT(m_frame == XCB_WINDOW_NONE);
0316     Q_ASSERT(!check_active_modal);
0317 }
0318 
0319 std::unique_ptr<WindowItem> X11Window::createItem(Scene *scene)
0320 {
0321     return std::make_unique<WindowItemX11>(this, scene);
0322 }
0323 
0324 // Use destroyWindow() or releaseWindow(), Client instances cannot be deleted directly
0325 void X11Window::deleteClient(X11Window *c)
0326 {
0327     delete c;
0328 }
0329 
0330 /**
0331  * Releases the window. The client has done its job and the window is still existing.
0332  */
0333 void X11Window::releaseWindow(bool on_shutdown)
0334 {
0335     markAsZombie();
0336     cleanTabBox();
0337     Deleted *del = nullptr;
0338     if (!on_shutdown) {
0339         del = Deleted::create(this);
0340     }
0341     if (isInteractiveMoveResize()) {
0342         Q_EMIT clientFinishUserMovedResized(this);
0343     }
0344     Q_EMIT windowClosed(this, del);
0345     finishCompositing();
0346     workspace()->rulebook()->discardUsed(this, true); // Remove ForceTemporarily rules
0347     StackingUpdatesBlocker blocker(workspace());
0348     if (isInteractiveMoveResize()) {
0349         leaveInteractiveMoveResize();
0350     }
0351     finishWindowRules();
0352     blockGeometryUpdates();
0353     // Grab X during the release to make removing of properties, setting to withdrawn state
0354     // and repareting to root an atomic operation (https://lists.kde.org/?l=kde-devel&m=116448102901184&w=2)
0355     grabXServer();
0356     exportMappingState(XCB_ICCCM_WM_STATE_WITHDRAWN);
0357     setModal(false); // Otherwise its mainwindow wouldn't get focus
0358     hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags)
0359     if (!on_shutdown) {
0360         workspace()->windowHidden(this);
0361     }
0362     m_frame.unmap(); // Destroying decoration would cause ugly visual effect
0363     destroyDecoration();
0364     cleanGrouping();
0365     workspace()->removeX11Window(this);
0366     if (!on_shutdown) {
0367         // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7)
0368         info->setDesktop(0);
0369         info->setState(NET::States(), info->state()); // Reset all state flags
0370     }
0371     xcb_connection_t *c = kwinApp()->x11Connection();
0372     m_client.deleteProperty(atoms->kde_net_wm_user_creation_time);
0373     m_client.deleteProperty(atoms->net_frame_extents);
0374     m_client.deleteProperty(atoms->kde_net_wm_frame_strut);
0375     m_client.reparent(kwinApp()->x11RootWindow(), m_bufferGeometry.x(), m_bufferGeometry.y());
0376     xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client);
0377     m_client.selectInput(XCB_EVENT_MASK_NO_EVENT);
0378     if (on_shutdown) {
0379         // Map the window, so it can be found after another WM is started
0380         m_client.map();
0381         // TODO: Preserve minimized, shaded etc. state?
0382     } else { // Make sure it's not mapped if the app unmapped it (#65279). The app
0383         // may do map+unmap before we initially map the window by calling rawShow() from manage().
0384         m_client.unmap();
0385     }
0386     m_client.reset();
0387     m_wrapper.reset();
0388     m_frame.reset();
0389     unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry
0390     if (!on_shutdown) {
0391         disownDataPassedToDeleted();
0392         del->unrefWindow();
0393     }
0394     deleteClient(this);
0395     ungrabXServer();
0396 }
0397 
0398 /**
0399  * Like releaseWindow(), but this one is called when the window has been already destroyed
0400  * (E.g. The application closed it)
0401  */
0402 void X11Window::destroyWindow()
0403 {
0404     markAsZombie();
0405     cleanTabBox();
0406     Deleted *del = Deleted::create(this);
0407     if (isInteractiveMoveResize()) {
0408         Q_EMIT clientFinishUserMovedResized(this);
0409     }
0410     Q_EMIT windowClosed(this, del);
0411     finishCompositing(ReleaseReason::Destroyed);
0412     workspace()->rulebook()->discardUsed(this, true); // Remove ForceTemporarily rules
0413     StackingUpdatesBlocker blocker(workspace());
0414     if (isInteractiveMoveResize()) {
0415         leaveInteractiveMoveResize();
0416     }
0417     finishWindowRules();
0418     blockGeometryUpdates();
0419     setModal(false);
0420     hidden = true; // So that it's not considered visible anymore
0421     workspace()->windowHidden(this);
0422     destroyDecoration();
0423     cleanGrouping();
0424     workspace()->removeX11Window(this);
0425     m_client.reset(); // invalidate
0426     m_wrapper.reset();
0427     m_frame.reset();
0428     unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry
0429     disownDataPassedToDeleted();
0430     del->unrefWindow();
0431     deleteClient(this);
0432 }
0433 
0434 /**
0435  * Manages the clients. This means handling the very first maprequest:
0436  * reparenting, initial geometry, initial state, placement, etc.
0437  * Returns false if KWin is not going to manage this window.
0438  */
0439 bool X11Window::manage(xcb_window_t w, bool isMapped)
0440 {
0441     StackingUpdatesBlocker stacking_blocker(workspace());
0442 
0443     Xcb::WindowAttributes attr(w);
0444     Xcb::WindowGeometry windowGeometry(w);
0445     if (attr.isNull() || windowGeometry.isNull()) {
0446         return false;
0447     }
0448 
0449     // From this place on, manage() must not return false
0450     blockGeometryUpdates();
0451 
0452     embedClient(w, attr->visual, attr->colormap, windowGeometry->depth);
0453 
0454     m_visual = attr->visual;
0455     bit_depth = windowGeometry->depth;
0456 
0457     // SELI TODO: Order all these things in some sane manner
0458 
0459     const NET::Properties properties =
0460         NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMIconName;
0461     const NET::Properties2 properties2 =
0462         NET::WM2BlockCompositing | NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2UserTime | NET::WM2StartupId | NET::WM2ExtendedStrut | NET::WM2Opacity | NET::WM2FullscreenMonitors | NET::WM2GroupLeader | NET::WM2Urgency | NET::WM2Input | NET::WM2Protocols | NET::WM2InitialMappingState | NET::WM2IconPixmap | NET::WM2OpaqueRegion | NET::WM2DesktopFileName | NET::WM2GTKFrameExtents | NET::WM2GTKApplicationId;
0463 
0464     auto wmClientLeaderCookie = fetchWmClientLeader();
0465     auto skipCloseAnimationCookie = fetchSkipCloseAnimation();
0466     auto showOnScreenEdgeCookie = fetchShowOnScreenEdge();
0467     auto colorSchemeCookie = fetchPreferredColorScheme();
0468     auto firstInTabBoxCookie = fetchFirstInTabBox();
0469     auto transientCookie = fetchTransient();
0470     auto activitiesCookie = fetchActivities();
0471     auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName();
0472     auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath();
0473 
0474     m_geometryHints.init(window());
0475     m_motif.init(window());
0476     info = new WinInfo(this, m_client, kwinApp()->x11RootWindow(), properties, properties2);
0477 
0478     if (isDesktop() && bit_depth == 32) {
0479         // force desktop windows to be opaque. It's a desktop after all, there is no window below
0480         bit_depth = 24;
0481     }
0482 
0483     // If it's already mapped, ignore hint
0484     bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic);
0485 
0486     m_colormap = attr->colormap;
0487 
0488     getResourceClass();
0489     readWmClientLeader(wmClientLeaderCookie);
0490     getWmClientMachine();
0491     getSyncCounter();
0492     // First only read the caption text, so that setupWindowRules() can use it for matching,
0493     // and only then really set the caption using setCaption(), which checks for duplicates etc.
0494     // and also relies on rules already existing
0495     cap_normal = readName();
0496     setupWindowRules(false);
0497     setCaption(cap_normal, true);
0498 
0499     connect(this, &X11Window::windowClassChanged, this, &X11Window::evaluateWindowRules);
0500 
0501     if (Xcb::Extensions::self()->isShapeAvailable()) {
0502         xcb_shape_select_input(kwinApp()->x11Connection(), window(), true);
0503     }
0504     detectShape(window());
0505     detectNoBorder();
0506     fetchIconicName();
0507     setClientFrameExtents(info->gtkFrameExtents());
0508 
0509     // Needs to be done before readTransient() because of reading the group
0510     checkGroup();
0511     updateUrgency();
0512     updateAllowedActions(); // Group affects isMinimizable()
0513 
0514     setModal((info->state() & NET::Modal) != 0); // Needs to be valid before handling groups
0515     readTransientProperty(transientCookie);
0516     QString desktopFileName = QString::fromUtf8(info->desktopFileName());
0517     if (desktopFileName.isEmpty()) {
0518         desktopFileName = QString::fromUtf8(info->gtkApplicationId());
0519     }
0520     setDesktopFileName(rules()->checkDesktopFile(desktopFileName, true));
0521     getIcons();
0522     connect(this, &X11Window::desktopFileNameChanged, this, &X11Window::getIcons);
0523 
0524     m_geometryHints.read();
0525     getMotifHints();
0526     getWmOpaqueRegion();
0527     readSkipCloseAnimation(skipCloseAnimationCookie);
0528 
0529     // TODO: Try to obey all state information from info->state()
0530 
0531     setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0);
0532     setSkipPager((info->state() & NET::SkipPager) != 0);
0533     setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0);
0534     readFirstInTabBox(firstInTabBoxCookie);
0535 
0536     setupCompositing();
0537 
0538     KStartupInfoId asn_id;
0539     KStartupInfoData asn_data;
0540     bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data);
0541 
0542     // Make sure that the input window is created before we update the stacking order
0543     updateInputWindow();
0544     updateLayer();
0545 
0546     SessionInfo *session = workspace()->sessionManager()->takeSessionInfo(this);
0547     if (session) {
0548         init_minimize = session->minimized;
0549         noborder = session->noBorder;
0550     }
0551 
0552     setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true));
0553 
0554     init_minimize = rules()->checkMinimize(init_minimize, !isMapped);
0555     noborder = rules()->checkNoBorder(noborder, !isMapped);
0556 
0557     readActivities(activitiesCookie);
0558 
0559     // Initial desktop placement
0560     std::optional<QVector<VirtualDesktop *>> initialDesktops;
0561     if (session) {
0562         if (session->onAllDesktops) {
0563             initialDesktops = QVector<VirtualDesktop *>{};
0564         } else {
0565             VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(session->desktop);
0566             if (desktop) {
0567                 initialDesktops = QVector<VirtualDesktop *>{desktop};
0568             }
0569         }
0570         setOnActivities(session->activities);
0571     } else {
0572         // If this window is transient, ensure that it is opened on the
0573         // same window as its parent.  this is necessary when an application
0574         // starts up on a different desktop than is currently displayed
0575         if (isTransient()) {
0576             auto mainwindows = mainWindows();
0577             bool on_current = false;
0578             bool on_all = false;
0579             Window *maincl = nullptr;
0580             // This is slightly duplicated from Placement::placeOnMainWindow()
0581             for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) {
0582                 if (mainwindows.count() > 1 && // A group-transient
0583                     (*it)->isSpecialWindow() && // Don't consider toolbars etc when placing
0584                     !(info->state() & NET::Modal)) { // except when it's modal (blocks specials as well)
0585                     continue;
0586                 }
0587                 maincl = *it;
0588                 if ((*it)->isOnCurrentDesktop()) {
0589                     on_current = true;
0590                 }
0591                 if ((*it)->isOnAllDesktops()) {
0592                     on_all = true;
0593                 }
0594             }
0595             if (on_all) {
0596                 initialDesktops = QVector<VirtualDesktop *>{};
0597             } else if (on_current) {
0598                 initialDesktops = QVector<VirtualDesktop *>{VirtualDesktopManager::self()->currentDesktop()};
0599             } else if (maincl) {
0600                 initialDesktops = maincl->desktops();
0601             }
0602 
0603             if (maincl) {
0604                 setOnActivities(maincl->activities());
0605             }
0606         } else { // a transient shall appear on its leader and not drag that around
0607             int desktopId = 0;
0608             if (info->desktop()) {
0609                 desktopId = info->desktop(); // Window had the initial desktop property, force it
0610             }
0611             if (desktopId == 0 && asn_valid && asn_data.desktop() != 0) {
0612                 desktopId = asn_data.desktop();
0613             }
0614             if (desktopId) {
0615                 if (desktopId == NET::OnAllDesktops) {
0616                     initialDesktops = QVector<VirtualDesktop *>{};
0617                 } else {
0618                     VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(desktopId);
0619                     if (desktop) {
0620                         initialDesktops = QVector<VirtualDesktop *>{desktop};
0621                     }
0622                 }
0623             }
0624         }
0625 #if KWIN_BUILD_ACTIVITIES
0626         if (Workspace::self()->activities() && !isMapped && !skipTaskbar() && isNormalWindow() && !activitiesDefined) {
0627             // a new, regular window, when we're not recovering from a crash,
0628             // and it hasn't got an activity. let's try giving it the current one.
0629             // TODO: decide whether to keep this before the 4.6 release
0630             // TODO: if we are keeping it (at least as an option), replace noborder checking
0631             // with a public API for setting windows to be on all activities.
0632             // something like KWindowSystem::setOnAllActivities or
0633             // KActivityConsumer::setOnAllActivities
0634             setOnActivity(Workspace::self()->activities()->current(), true);
0635         }
0636 #endif
0637     }
0638 
0639     // If initialDesktops has no value, it means that the client doesn't prefer any
0640     // desktop so place it on the current virtual desktop.
0641     if (!initialDesktops.has_value()) {
0642         if (isDesktop()) {
0643             initialDesktops = QVector<VirtualDesktop *>{};
0644         } else {
0645             initialDesktops = QVector<VirtualDesktop *>{VirtualDesktopManager::self()->currentDesktop()};
0646         }
0647     }
0648     setDesktops(rules()->checkDesktops(*initialDesktops, !isMapped));
0649     info->setDesktop(desktop());
0650     workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO
0651     // onAllDesktopsChange(); // Decoration doesn't exist here yet
0652 
0653     QStringList activitiesList;
0654     activitiesList = rules()->checkActivity(activitiesList, !isMapped);
0655     if (!activitiesList.isEmpty()) {
0656         setOnActivities(activitiesList);
0657     }
0658 
0659     QRectF geom = session ? session->geometry : windowGeometry.rect();
0660     bool placementDone = false;
0661 
0662     QRectF area;
0663     bool partial_keep_in_area = isMapped || session;
0664     if (isMapped || session) {
0665         area = workspace()->clientArea(FullArea, this, geom.center());
0666         checkOffscreenPosition(&geom, area);
0667     } else {
0668         Output *output = nullptr;
0669         if (asn_data.xinerama() != -1) {
0670             output = workspace()->xineramaIndexToOutput(asn_data.xinerama());
0671         }
0672         if (!output) {
0673             output = workspace()->activeOutput();
0674         }
0675         output = rules()->checkOutput(output, !isMapped);
0676         area = workspace()->clientArea(PlacementArea, this, output->geometry().center());
0677     }
0678 
0679     if (isDesktop()) {
0680         // KWin doesn't manage desktop windows
0681         placementDone = true;
0682     }
0683 
0684     bool usePosition = false;
0685     if (isMapped || session || placementDone) {
0686         placementDone = true; // Use geometry
0687     } else if (isTransient() && !isUtility() && !isDialog() && !isSplash()) {
0688         usePosition = true;
0689     } else if (isTransient() && !hasNETSupport()) {
0690         usePosition = true;
0691     } else if (isDialog() && hasNETSupport()) {
0692         // If the dialog is actually non-NETWM transient window, don't try to apply placement to it,
0693         // it breaks with too many things (xmms, display)
0694         if (mainWindows().count() >= 1) {
0695 #if 1
0696             // #78082 - Ok, it seems there are after all some cases when an application has a good
0697             // reason to specify a position for its dialog. Too bad other WMs have never bothered
0698             // with placement for dialogs, so apps always specify positions for their dialogs,
0699             // including such silly positions like always centered on the screen or under mouse.
0700             // Using ignoring requested position in window-specific settings helps, and now
0701             // there's also _NET_WM_FULL_PLACEMENT.
0702             usePosition = true;
0703 #else
0704             ; // Force using placement policy
0705 #endif
0706         } else {
0707             usePosition = true;
0708         }
0709     } else if (isSplash()) {
0710         ; // Force using placement policy
0711     } else {
0712         usePosition = true;
0713     }
0714     if (!rules()->checkIgnoreGeometry(!usePosition, true)) {
0715         if (m_geometryHints.hasPosition()) {
0716             placementDone = true;
0717             // Disobey xinerama placement option for now (#70943)
0718             area = workspace()->clientArea(PlacementArea, this, geom.center());
0719         }
0720     }
0721 
0722     if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) {
0723         placementDone = false; // Weird, do not trust.
0724     }
0725 
0726     if (placementDone) {
0727         QPointF position = geom.topLeft();
0728         // Session contains the position of the frame geometry before gravitating.
0729         if (!session) {
0730             position = clientPosToFramePos(position);
0731         }
0732         move(position);
0733     }
0734 
0735     // Create client group if the window will have a decoration
0736     bool dontKeepInArea = false;
0737     setColorScheme(readPreferredColorScheme(colorSchemeCookie));
0738 
0739     readApplicationMenuServiceName(applicationMenuServiceNameCookie);
0740     readApplicationMenuObjectPath(applicationMenuObjectPathCookie);
0741 
0742     updateDecoration(false); // Also gravitates
0743     // TODO: Is CentralGravity right here, when resizing is done after gravitating?
0744     const QSizeF constrainedClientSize = constrainClientSize(geom.size());
0745     resize(rules()->checkSize(clientSizeToFrameSize(constrainedClientSize), !isMapped));
0746 
0747     QPointF forced_pos = rules()->checkPositionSafe(invalidPoint, !isMapped);
0748     if (forced_pos != invalidPoint) {
0749         move(forced_pos);
0750         placementDone = true;
0751         // Don't keep inside workarea if the window has specially configured position
0752         partial_keep_in_area = true;
0753         area = workspace()->clientArea(FullArea, this, geom.center());
0754     }
0755     if (!placementDone) {
0756         // Placement needs to be after setting size
0757         workspace()->placement()->place(this, area);
0758         // The client may have been moved to another screen, update placement area.
0759         area = workspace()->clientArea(PlacementArea, this, moveResizeOutput());
0760         dontKeepInArea = true;
0761         placementDone = true;
0762     }
0763 
0764     // bugs #285967, #286146, #183694
0765     // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully)
0766     // Maximization for oversized windows must happen NOW.
0767     // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained
0768     // to the combo of all screen MINUS all struts on the edges
0769     // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked
0770     // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack
0771     // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well.
0772 
0773     if (!session) { // has a better handling of this
0774         setGeometryRestore(moveResizeGeometry()); // Remember restore geometry
0775         if (isMaximizable() && (width() >= area.width() || height() >= area.height())) {
0776             // Window is too large for the screen, maximize in the
0777             // directions necessary
0778             const QSizeF ss = workspace()->clientArea(ScreenArea, this, area.center()).size();
0779             const QRectF fsa = workspace()->clientArea(FullArea, this, geom.center());
0780             const QSizeF cs = clientSize();
0781             int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0);
0782             if (width() >= area.width()) {
0783                 pseudo_max |= MaximizeHorizontal;
0784             }
0785             if (height() >= area.height()) {
0786                 pseudo_max |= MaximizeVertical;
0787             }
0788 
0789             // heuristics:
0790             // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen)
0791             // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an
0792             // attempt for maximization, but just constrain the size (the window simply wants to be bigger)
0793             // NOTICE
0794             // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller
0795             // than the workspace") but gtk / gimp seems to store it's size including the decoration,
0796             // thus a former maximized window wil become non-maximized
0797             bool keepInFsArea = false;
0798             if (width() < fsa.width() && (cs.width() > ss.width() + 1)) {
0799                 pseudo_max &= ~MaximizeHorizontal;
0800                 keepInFsArea = true;
0801             }
0802             if (height() < fsa.height() && (cs.height() > ss.height() + 1)) {
0803                 pseudo_max &= ~MaximizeVertical;
0804                 keepInFsArea = true;
0805             }
0806 
0807             if (pseudo_max != MaximizeRestore) {
0808                 maximize((MaximizeMode)pseudo_max);
0809                 // from now on, care about maxmode, since the maximization call will override mode for fix aspects
0810                 dontKeepInArea |= (max_mode == MaximizeFull);
0811                 QRectF savedGeometry; // Use placement when unmaximizing ...
0812                 if (!(max_mode & MaximizeVertical)) {
0813                     savedGeometry.setY(y()); // ...but only for horizontal direction
0814                     savedGeometry.setHeight(height());
0815                 }
0816                 if (!(max_mode & MaximizeHorizontal)) {
0817                     savedGeometry.setX(x()); // ...but only for vertical direction
0818                     savedGeometry.setWidth(width());
0819                 }
0820                 setGeometryRestore(savedGeometry);
0821             }
0822             if (keepInFsArea) {
0823                 keepInArea(fsa, partial_keep_in_area);
0824             }
0825         }
0826     }
0827 
0828     if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea) {
0829         keepInArea(area, partial_keep_in_area);
0830     }
0831 
0832     updateShape();
0833 
0834     // CT: Extra check for stupid jdk 1.3.1. But should make sense in general
0835     // if client has initial state set to Iconic and is transient with a parent
0836     // window that is not Iconic, set init_state to Normal
0837     if (init_minimize && isTransient()) {
0838         auto mainwindows = mainWindows();
0839         for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) {
0840             if ((*it)->isShown()) {
0841                 init_minimize = false; // SELI TODO: Even e.g. for NET::Utility?
0842             }
0843         }
0844     }
0845     // If a dialog is shown for minimized window, minimize it too
0846     if (!init_minimize && isTransient() && mainWindows().count() > 0 && workspace()->sessionManager()->state() != SessionState::Saving) {
0847         bool visible_parent = false;
0848         // Use allMainWindows(), to include also main clients of group transients
0849         // that have been optimized out in X11Window::checkGroupTransients()
0850         auto mainwindows = allMainWindows();
0851         for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) {
0852             if ((*it)->isShown()) {
0853                 visible_parent = true;
0854             }
0855         }
0856         if (!visible_parent) {
0857             init_minimize = true;
0858             demandAttention();
0859         }
0860     }
0861 
0862     if (init_minimize) {
0863         minimize(true); // No animation
0864     }
0865 
0866     // Other settings from the previous session
0867     if (session) {
0868         // Session restored windows are not considered to be new windows WRT rules,
0869         // I.e. obey only forcing rules
0870         setKeepAbove(session->keepAbove);
0871         setKeepBelow(session->keepBelow);
0872         setOriginalSkipTaskbar(session->skipTaskbar);
0873         setSkipPager(session->skipPager);
0874         setSkipSwitcher(session->skipSwitcher);
0875         setShade(session->shaded ? ShadeNormal : ShadeNone);
0876         setOpacity(session->opacity);
0877         setGeometryRestore(session->restore);
0878         if (session->maximized != MaximizeRestore) {
0879             maximize(MaximizeMode(session->maximized));
0880         }
0881         if (session->fullscreen != FullScreenNone) {
0882             setFullScreen(true, false);
0883             setFullscreenGeometryRestore(session->fsrestore);
0884         }
0885         QRectF checkedGeometryRestore = geometryRestore();
0886         checkOffscreenPosition(&checkedGeometryRestore, area);
0887         setGeometryRestore(checkedGeometryRestore);
0888         QRectF checkedFullscreenGeometryRestore = fullscreenGeometryRestore();
0889         checkOffscreenPosition(&checkedFullscreenGeometryRestore, area);
0890         setFullscreenGeometryRestore(checkedFullscreenGeometryRestore);
0891     } else {
0892         // Window may want to be maximized
0893         // done after checking that the window isn't larger than the workarea, so that
0894         // the restore geometry from the checks above takes precedence, and window
0895         // isn't restored larger than the workarea
0896         MaximizeMode maxmode = static_cast<MaximizeMode>(
0897             ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0));
0898         MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped);
0899 
0900         // Either hints were set to maximize, or is forced to maximize,
0901         // or is forced to non-maximize and hints were set to maximize
0902         if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore) {
0903             maximize(forced_maxmode);
0904         }
0905 
0906         // Read other initial states
0907         setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped));
0908         setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped));
0909         setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped));
0910         setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped));
0911         setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped));
0912         setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped));
0913         if (info->state() & NET::DemandsAttention) {
0914             demandAttention();
0915         }
0916         if (info->state() & NET::Modal) {
0917             setModal(true);
0918         }
0919         setOpacity(info->opacityF());
0920 
0921         setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false);
0922     }
0923 
0924     updateAllowedActions(true);
0925 
0926     // Set initial user time directly
0927     m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session);
0928     group()->updateUserTime(m_userTime); // And do what X11Window::updateUserTime() does
0929 
0930     // This should avoid flicker, because real restacking is done
0931     // only after manage() finishes because of blocking, but the window is shown sooner
0932     m_frame.lower();
0933     if (session && session->stackingOrder != -1) {
0934         sm_stacking_order = session->stackingOrder;
0935         workspace()->restoreSessionStackingOrder(this);
0936     }
0937 
0938     if (Compositor::compositing()) {
0939         // Sending ConfigureNotify is done when setting mapping state below,
0940         // Getting the first sync response means window is ready for compositing
0941         sendSyncRequest();
0942     } else {
0943         ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393
0944     }
0945 
0946     if (isShown()) {
0947         bool allow;
0948         if (session) {
0949             allow = session->active && (!workspace()->wasUserInteraction() || workspace()->activeWindow() == nullptr || workspace()->activeWindow()->isDesktop());
0950         } else {
0951             allow = allowWindowActivation(userTime(), false);
0952         }
0953 
0954         const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving;
0955 
0956         // If session saving, force showing new windows (i.e. "save file?" dialogs etc.)
0957         // also force if activation is allowed
0958         if (!isOnCurrentDesktop() && !isMapped && !session && (allow || isSessionSaving)) {
0959             VirtualDesktopManager::self()->setCurrent(desktop());
0960         }
0961 
0962         // If the window is on an inactive activity during session saving, temporarily force it to show.
0963         if (!isMapped && !session && isSessionSaving && !isOnCurrentActivity()) {
0964             setSessionActivityOverride(true);
0965             const auto windows = mainWindows();
0966             for (Window *w : windows) {
0967                 if (X11Window *mw = dynamic_cast<X11Window *>(w)) {
0968                     mw->setSessionActivityOverride(true);
0969                 }
0970             }
0971         }
0972 
0973         if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0)) {
0974             workspace()->restackWindowUnderActive(this);
0975         }
0976 
0977         updateVisibility();
0978 
0979         if (!isMapped) {
0980             if (allow && isOnCurrentDesktop()) {
0981                 if (!isSpecialWindow()) {
0982                     if (options->focusPolicyIsReasonable() && wantsTabFocus()) {
0983                         workspace()->requestFocus(this);
0984                     }
0985                 }
0986             } else if (!session && !isSpecialWindow()) {
0987                 demandAttention();
0988             }
0989         }
0990     } else {
0991         updateVisibility();
0992     }
0993     Q_ASSERT(mapping_state != Withdrawn);
0994     m_managed = true;
0995     blockGeometryUpdates(false);
0996 
0997     if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) {
0998         // No known user time, set something old
0999         m_userTime = xTime() - 1000000;
1000         if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) { // Let's be paranoid
1001             m_userTime = xTime() - 1000000 + 10;
1002         }
1003     }
1004 
1005     // sendSyntheticConfigureNotify(); // Done when setting mapping state
1006 
1007     delete session;
1008 
1009     discardTemporaryRules();
1010     applyWindowRules(); // Just in case
1011     workspace()->rulebook()->discardUsed(this, false); // Remove ApplyNow rules
1012     updateWindowRules(Rules::All); // Was blocked while !isManaged()
1013 
1014     setBlockingCompositing(info->isBlockingCompositing());
1015     readShowOnScreenEdge(showOnScreenEdgeCookie);
1016 
1017     setupWindowManagementInterface();
1018 
1019     // Forward all opacity values to the frame in case there'll be other CM running.
1020     connect(Compositor::self(), &Compositor::compositingToggled, this, [this](bool active) {
1021         if (active) {
1022             return;
1023         }
1024         if (opacity() == 1.0) {
1025             return;
1026         }
1027         NETWinInfo info(kwinApp()->x11Connection(), frameId(), kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
1028         info.setOpacityF(opacity());
1029     });
1030 
1031     // TODO: there's a small problem here - isManaged() depends on the mapping state,
1032     // but this client is not yet in Workspace's client list at this point, will
1033     // be only done in addClient()
1034     Q_EMIT clientManaging(this);
1035     return true;
1036 }
1037 
1038 // Called only from manage()
1039 void X11Window::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth)
1040 {
1041     Q_ASSERT(m_client == XCB_WINDOW_NONE);
1042     Q_ASSERT(frameId() == XCB_WINDOW_NONE);
1043     Q_ASSERT(m_wrapper == XCB_WINDOW_NONE);
1044     m_client.reset(w, false);
1045 
1046     const uint32_t zero_value = 0;
1047 
1048     xcb_connection_t *conn = kwinApp()->x11Connection();
1049 
1050     // We don't want the window to be destroyed when we quit
1051     xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client);
1052 
1053     m_client.selectInput(zero_value);
1054     m_client.unmap();
1055     m_client.setBorderWidth(zero_value);
1056 
1057     // Note: These values must match the order in the xcb_cw_t enum
1058     const uint32_t cw_values[] = {
1059         0, // back_pixmap
1060         0, // border_pixel
1061         colormap, // colormap
1062         Cursors::self()->mouse()->x11Cursor(Qt::ArrowCursor)};
1063 
1064     const uint32_t cw_mask = XCB_CW_BACK_PIXMAP
1065         | XCB_CW_BORDER_PIXEL
1066         | XCB_CW_COLORMAP
1067         | XCB_CW_CURSOR;
1068 
1069     // Create the frame window
1070     xcb_window_t frame = xcb_generate_id(conn);
1071     xcb_create_window(conn, depth, frame, kwinApp()->x11RootWindow(), 0, 0, 1, 1, 0,
1072                       XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
1073     m_frame.reset(frame);
1074 
1075     setWindowHandles(m_client);
1076 
1077     // Create the wrapper window
1078     xcb_window_t wrapperId = xcb_generate_id(conn);
1079     xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0,
1080                       XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
1081     m_wrapper.reset(wrapperId);
1082 
1083     m_client.reparent(m_wrapper);
1084 
1085     // We could specify the event masks when we create the windows, but the original
1086     // Xlib code didn't.  Let's preserve that behavior here for now so we don't end up
1087     // receiving any unexpected events from the wrapper creation or the reparenting.
1088     m_frame.selectInput(frameEventMask());
1089     m_wrapper.selectInput(wrapperEventMask());
1090     m_client.selectInput(clientEventMask());
1091 
1092     updateMouseGrab();
1093 }
1094 
1095 void X11Window::updateInputWindow()
1096 {
1097     if (!Xcb::Extensions::self()->isShapeInputAvailable()) {
1098         return;
1099     }
1100 
1101     if (kwinApp()->operationMode() != Application::OperationModeX11) {
1102         return;
1103     }
1104 
1105     QRegion region;
1106 
1107     if (decoration()) {
1108         const QMargins &r = decoration()->resizeOnlyBorders();
1109         const int left = r.left();
1110         const int top = r.top();
1111         const int right = r.right();
1112         const int bottom = r.bottom();
1113         if (left != 0 || top != 0 || right != 0 || bottom != 0) {
1114             region = QRegion(-left,
1115                              -top,
1116                              decoration()->size().width() + left + right,
1117                              decoration()->size().height() + top + bottom);
1118             region = region.subtracted(decoration()->rect());
1119         }
1120     }
1121 
1122     if (region.isEmpty()) {
1123         m_decoInputExtent.reset();
1124         return;
1125     }
1126 
1127     QRectF bounds = region.boundingRect();
1128     input_offset = bounds.topLeft();
1129 
1130     // Move the bounding rect to screen coordinates
1131     bounds.translate(frameGeometry().topLeft());
1132 
1133     // Move the region to input window coordinates
1134     region.translate(-input_offset.toPoint());
1135 
1136     if (!m_decoInputExtent.isValid()) {
1137         const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
1138         const uint32_t values[] = {true,
1139                                    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};
1140         m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values);
1141         if (mapping_state == Mapped) {
1142             m_decoInputExtent.map();
1143         }
1144     } else {
1145         m_decoInputExtent.setGeometry(bounds);
1146     }
1147 
1148     const QVector<xcb_rectangle_t> rects = Xcb::regionToRects(region);
1149     xcb_shape_rectangles(kwinApp()->x11Connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED,
1150                          m_decoInputExtent, 0, 0, rects.count(), rects.constData());
1151 }
1152 
1153 void X11Window::updateDecoration(bool check_workspace_pos, bool force)
1154 {
1155     if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) {
1156         return;
1157     }
1158     QRectF oldgeom = moveResizeGeometry();
1159     blockGeometryUpdates(true);
1160     if (force) {
1161         destroyDecoration();
1162     }
1163     if (!noBorder()) {
1164         createDecoration(oldgeom);
1165     } else {
1166         destroyDecoration();
1167     }
1168     updateShadow();
1169     if (check_workspace_pos) {
1170         checkWorkspacePosition(oldgeom);
1171     }
1172     updateInputWindow();
1173     blockGeometryUpdates(false);
1174     updateFrameExtents();
1175 }
1176 
1177 void X11Window::invalidateDecoration()
1178 {
1179     updateDecoration(true, true);
1180 }
1181 
1182 void X11Window::createDecoration(const QRectF &oldgeom)
1183 {
1184     std::shared_ptr<KDecoration2::Decoration> decoration(Workspace::self()->decorationBridge()->createDecoration(this));
1185     if (decoration) {
1186         connect(decoration.get(), &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &X11Window::updateInputWindow);
1187         connect(decoration.get(), &KDecoration2::Decoration::bordersChanged, this, &X11Window::updateFrameExtents);
1188         connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged, this, &X11Window::updateInputWindow);
1189     }
1190     setDecoration(decoration);
1191 
1192     moveResize(QRectF(calculateGravitation(false), clientSizeToFrameSize(clientSize())));
1193     maybeCreateX11DecorationRenderer();
1194     Q_EMIT geometryShapeChanged(this, oldgeom);
1195 }
1196 
1197 void X11Window::destroyDecoration()
1198 {
1199     QRectF oldgeom = moveResizeGeometry();
1200     if (isDecorated()) {
1201         QPointF grav = calculateGravitation(true);
1202         setDecoration(nullptr);
1203         maybeDestroyX11DecorationRenderer();
1204         moveResize(QRectF(grav, clientSizeToFrameSize(clientSize())));
1205         if (!isZombie()) {
1206             Q_EMIT geometryShapeChanged(this, oldgeom);
1207         }
1208     }
1209     m_decoInputExtent.reset();
1210 }
1211 
1212 void X11Window::maybeCreateX11DecorationRenderer()
1213 {
1214     if (kwinApp()->operationMode() != Application::OperationModeX11) {
1215         return;
1216     }
1217     if (!Compositor::compositing() && decoratedClient()) {
1218         m_decorationRenderer.reset(new X11DecorationRenderer(decoratedClient()));
1219         decoration()->update();
1220     }
1221 }
1222 
1223 void X11Window::maybeDestroyX11DecorationRenderer()
1224 {
1225     m_decorationRenderer.reset();
1226 }
1227 
1228 void X11Window::detectNoBorder()
1229 {
1230     if (shape()) {
1231         noborder = true;
1232         app_noborder = true;
1233         return;
1234     }
1235     switch (windowType()) {
1236     case NET::Desktop:
1237     case NET::Dock:
1238     case NET::TopMenu:
1239     case NET::Splash:
1240     case NET::Notification:
1241     case NET::OnScreenDisplay:
1242     case NET::CriticalNotification:
1243     case NET::AppletPopup:
1244         noborder = true;
1245         app_noborder = true;
1246         break;
1247     case NET::Unknown:
1248     case NET::Normal:
1249     case NET::Toolbar:
1250     case NET::Menu:
1251     case NET::Dialog:
1252     case NET::Utility:
1253         noborder = false;
1254         break;
1255     default:
1256         Q_UNREACHABLE();
1257     }
1258     // NET::Override is some strange beast without clear definition, usually
1259     // just meaning "noborder", so let's treat it only as such flag, and ignore it as
1260     // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it)
1261     if (info->windowType(NET::OverrideMask) == NET::Override) {
1262         noborder = true;
1263         app_noborder = true;
1264     }
1265 }
1266 
1267 void X11Window::updateFrameExtents()
1268 {
1269     NETStrut strut;
1270     strut.left = Xcb::toXNative(borderLeft());
1271     strut.right = Xcb::toXNative(borderRight());
1272     strut.top = Xcb::toXNative(borderTop());
1273     strut.bottom = Xcb::toXNative(borderBottom());
1274     info->setFrameExtents(strut);
1275 }
1276 
1277 void X11Window::setClientFrameExtents(const NETStrut &strut)
1278 {
1279     const QMarginsF clientFrameExtents(Xcb::fromXNative(strut.left),
1280                                        Xcb::fromXNative(strut.top),
1281                                        Xcb::fromXNative(strut.right),
1282                                        Xcb::fromXNative(strut.bottom));
1283     if (m_clientFrameExtents == clientFrameExtents) {
1284         return;
1285     }
1286 
1287     const bool wasClientSideDecorated = isClientSideDecorated();
1288     m_clientFrameExtents = clientFrameExtents;
1289 
1290     // We should resize the client when its custom frame extents are changed so
1291     // the logical bounds remain the same. This however means that we will send
1292     // several configure requests to the application upon restoring it from the
1293     // maximized or fullscreen state. Notice that a client-side decorated client
1294     // cannot be shaded, therefore it's okay not to use the adjusted size here.
1295     moveResize(moveResizeGeometry());
1296 
1297     if (wasClientSideDecorated != isClientSideDecorated()) {
1298         Q_EMIT clientSideDecoratedChanged();
1299     }
1300 
1301     // This will invalidate the window quads cache.
1302     Q_EMIT geometryShapeChanged(this, frameGeometry());
1303 }
1304 
1305 /**
1306  * Resizes the decoration, and makes sure the decoration widget gets resize event
1307  * even if the size hasn't changed. This is needed to make sure the decoration
1308  * re-layouts (e.g. when maximization state changes,
1309  * the decoration may alter some borders, but the actual size
1310  * of the decoration stays the same).
1311  */
1312 void X11Window::resizeDecoration()
1313 {
1314     triggerDecorationRepaint();
1315     updateInputWindow();
1316 }
1317 
1318 bool X11Window::userNoBorder() const
1319 {
1320     return noborder;
1321 }
1322 
1323 bool X11Window::isFullScreenable() const
1324 {
1325     if (!rules()->checkFullScreen(true)) {
1326         return false;
1327     }
1328     if (rules()->checkStrictGeometry(true)) {
1329         // check geometry constraints (rule to obey is set)
1330         const QRectF fullScreenArea = workspace()->clientArea(FullScreenArea, this);
1331         const QSizeF constrainedClientSize = constrainClientSize(fullScreenArea.size());
1332         if (rules()->checkSize(constrainedClientSize) != fullScreenArea.size()) {
1333             return false; // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements
1334         }
1335     }
1336     // don't check size constrains - some apps request fullscreen despite requesting fixed size
1337     return !isSpecialWindow(); // also better disallow only weird types to go fullscreen
1338 }
1339 
1340 bool X11Window::noBorder() const
1341 {
1342     return userNoBorder() || isFullScreen();
1343 }
1344 
1345 bool X11Window::userCanSetNoBorder() const
1346 {
1347     // Client-side decorations and server-side decorations are mutually exclusive.
1348     if (isClientSideDecorated()) {
1349         return false;
1350     }
1351 
1352     return !isFullScreen() && !isShade();
1353 }
1354 
1355 void X11Window::setNoBorder(bool set)
1356 {
1357     if (!userCanSetNoBorder()) {
1358         return;
1359     }
1360     set = rules()->checkNoBorder(set);
1361     if (noborder == set) {
1362         return;
1363     }
1364     noborder = set;
1365     updateDecoration(true, false);
1366     updateWindowRules(Rules::NoBorder);
1367 }
1368 
1369 void X11Window::checkNoBorder()
1370 {
1371     setNoBorder(app_noborder);
1372 }
1373 
1374 void X11Window::updateShape()
1375 {
1376     if (shape()) {
1377         // Workaround for #19644 - Shaped windows shouldn't have decoration
1378         if (!app_noborder) {
1379             // Only when shape is detected for the first time, still let the user to override
1380             app_noborder = true;
1381             noborder = rules()->checkNoBorder(true);
1382             updateDecoration(true);
1383         }
1384         if (!isDecorated()) {
1385             xcb_shape_combine(kwinApp()->x11Connection(),
1386                               XCB_SHAPE_SO_SET,
1387                               XCB_SHAPE_SK_BOUNDING,
1388                               XCB_SHAPE_SK_BOUNDING,
1389                               frameId(),
1390                               Xcb::toXNative(clientPos().x()),
1391                               Xcb::toXNative(clientPos().y()),
1392                               window());
1393         }
1394     } else if (app_noborder) {
1395         xcb_shape_mask(kwinApp()->x11Connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE);
1396         detectNoBorder();
1397         app_noborder = noborder;
1398         noborder = rules()->checkNoBorder(noborder || m_motif.noBorder());
1399         updateDecoration(true);
1400     }
1401 
1402     // Decoration mask (i.e. 'else' here) setting is done in setMask()
1403     // when the decoration calls it or when the decoration is created/destroyed
1404     updateInputShape();
1405     Q_EMIT geometryShapeChanged(this, frameGeometry());
1406 }
1407 
1408 static Xcb::Window shape_helper_window(XCB_WINDOW_NONE);
1409 
1410 void X11Window::cleanupX11()
1411 {
1412     shape_helper_window.reset();
1413 }
1414 
1415 void X11Window::updateInputShape()
1416 {
1417     if (hiddenPreview()) { // Sets it to none, don't change
1418         return;
1419     }
1420     if (Xcb::Extensions::self()->isShapeInputAvailable()) {
1421         // There appears to be no way to find out if a window has input
1422         // shape set or not, so always propagate the input shape
1423         // (it's the same like the bounding shape by default).
1424         // Also, build the shape using a helper window, not directly
1425         // in the frame window, because the sequence set-shape-to-frame,
1426         // remove-shape-of-client, add-input-shape-of-client has the problem
1427         // that after the second step there's a hole in the input shape
1428         // until the real shape of the client is added and that can make
1429         // the window lose focus (which is a problem with mouse focus policies)
1430         // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better
1431         if (!shape_helper_window.isValid()) {
1432             shape_helper_window.create(QRect(0, 0, 1, 1));
1433         }
1434         const QSizeF bufferSize = m_bufferGeometry.size();
1435         shape_helper_window.resize(bufferSize);
1436         xcb_connection_t *c = kwinApp()->x11Connection();
1437         xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING,
1438                           shape_helper_window, 0, 0, frameId());
1439         xcb_shape_combine(c,
1440                           XCB_SHAPE_SO_SUBTRACT,
1441                           XCB_SHAPE_SK_INPUT,
1442                           XCB_SHAPE_SK_BOUNDING,
1443                           shape_helper_window,
1444                           Xcb::toXNative(clientPos().x()),
1445                           Xcb::toXNative(clientPos().y()),
1446                           window());
1447         xcb_shape_combine(c,
1448                           XCB_SHAPE_SO_UNION,
1449                           XCB_SHAPE_SK_INPUT,
1450                           XCB_SHAPE_SK_INPUT,
1451                           shape_helper_window,
1452                           Xcb::toXNative(clientPos().x()),
1453                           Xcb::toXNative(clientPos().y()),
1454                           window());
1455         xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT,
1456                           frameId(), 0, 0, shape_helper_window);
1457     }
1458 }
1459 
1460 void X11Window::hideClient()
1461 {
1462     if (hidden) {
1463         return;
1464     }
1465     hidden = true;
1466     updateVisibility();
1467 }
1468 
1469 void X11Window::showClient()
1470 {
1471     if (!hidden) {
1472         return;
1473     }
1474     hidden = false;
1475     updateVisibility();
1476 }
1477 
1478 bool X11Window::setupCompositing()
1479 {
1480     if (!Window::setupCompositing()) {
1481         return false;
1482     }
1483     // If compositing is back on, stop rendering decoration in the frame window.
1484     maybeDestroyX11DecorationRenderer();
1485     updateVisibility(); // for internalKeep()
1486     return true;
1487 }
1488 
1489 void X11Window::finishCompositing(ReleaseReason releaseReason)
1490 {
1491     Window::finishCompositing(releaseReason);
1492     updateVisibility();
1493     // If compositing is off, render the decoration in the X11 frame window.
1494     maybeCreateX11DecorationRenderer();
1495 }
1496 
1497 /**
1498  * Returns whether the window is minimizable or not
1499  */
1500 bool X11Window::isMinimizable() const
1501 {
1502     if (isSpecialWindow() && !isTransient()) {
1503         return false;
1504     }
1505     if (isAppletPopup()) {
1506         return false;
1507     }
1508     if (!rules()->checkMinimize(true)) {
1509         return false;
1510     }
1511 
1512     if (isTransient()) {
1513         // #66868 - Let other xmms windows be minimized when the mainwindow is minimized
1514         bool shown_mainwindow = false;
1515         auto mainwindows = mainWindows();
1516         for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) {
1517             if ((*it)->isShown()) {
1518                 shown_mainwindow = true;
1519             }
1520         }
1521         if (!shown_mainwindow) {
1522             return true;
1523         }
1524     }
1525 #if 0
1526     // This is here because kicker's taskbar doesn't provide separate entries
1527     // for windows with an explicitly given parent
1528     // TODO: perhaps this should be redone
1529     // Disabled for now, since at least modal dialogs should be minimizable
1530     // (resulting in the mainwindow being minimized too).
1531     if (transientFor() != NULL)
1532         return false;
1533 #endif
1534     if (!wantsTabFocus()) { // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ?
1535         return false;
1536     }
1537     return true;
1538 }
1539 
1540 void X11Window::doMinimize()
1541 {
1542     if (isShade()) {
1543         // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded
1544         info->setState(isMinimized() ? NET::States() : NET::Shaded, NET::Shaded);
1545     }
1546     updateVisibility();
1547     updateAllowedActions();
1548     workspace()->updateMinimizedOfTransients(this);
1549 }
1550 
1551 QRectF X11Window::iconGeometry() const
1552 {
1553     NETRect r = info->iconGeometry();
1554     QRectF geom = Xcb::fromXNative(QRect(r.pos.x, r.pos.y, r.size.width, r.size.height));
1555     if (geom.isValid()) {
1556         return geom;
1557     } else {
1558         // Check all mainwindows of this window (recursively)
1559         const auto &clients = mainWindows();
1560         for (Window *amainwin : clients) {
1561             X11Window *mainwin = dynamic_cast<X11Window *>(amainwin);
1562             if (!mainwin) {
1563                 continue;
1564             }
1565             geom = mainwin->iconGeometry();
1566             if (geom.isValid()) {
1567                 return geom;
1568             }
1569         }
1570         // No mainwindow (or their parents) with icon geometry was found
1571         return Window::iconGeometry();
1572     }
1573 }
1574 
1575 bool X11Window::isShadeable() const
1576 {
1577     return !isSpecialWindow() && isDecorated() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone));
1578 }
1579 
1580 void X11Window::doSetShade(ShadeMode previousShadeMode)
1581 {
1582     // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere
1583     if (isShade()) {
1584         shade_geometry_change = true;
1585         QSizeF s(implicitSize());
1586         s.setHeight(borderTop() + borderBottom());
1587         m_wrapper.selectInput(wrapperEventMask() & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); // Avoid getting UnmapNotify
1588         m_wrapper.unmap();
1589         m_client.unmap();
1590         m_wrapper.selectInput(wrapperEventMask());
1591         exportMappingState(XCB_ICCCM_WM_STATE_ICONIC);
1592         resize(s);
1593         shade_geometry_change = false;
1594         if (previousShadeMode == ShadeHover) {
1595             if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1) {
1596                 workspace()->restack(this, shade_below, true);
1597             }
1598             if (isActive()) {
1599                 workspace()->activateNextWindow(this);
1600             }
1601         } else if (isActive()) {
1602             workspace()->focusToNull();
1603         }
1604     } else {
1605         shade_geometry_change = true;
1606         if (decoratedClient()) {
1607             decoratedClient()->signalShadeChange();
1608         }
1609         QSizeF s(implicitSize());
1610         shade_geometry_change = false;
1611         resize(s);
1612         setGeometryRestore(moveResizeGeometry());
1613         if ((shadeMode() == ShadeHover || shadeMode() == ShadeActivated) && rules()->checkAcceptFocus(info->input())) {
1614             setActive(true);
1615         }
1616         if (shadeMode() == ShadeHover) {
1617             QList<Window *> order = workspace()->stackingOrder();
1618             // invalidate, since "this" could be the topmost toplevel and shade_below dangeling
1619             shade_below = nullptr;
1620             // this is likely related to the index parameter?!
1621             for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) {
1622                 shade_below = qobject_cast<X11Window *>(order.at(idx));
1623                 if (shade_below) {
1624                     break;
1625                 }
1626             }
1627             if (shade_below && shade_below->isNormalWindow()) {
1628                 workspace()->raiseWindow(this);
1629             } else {
1630                 shade_below = nullptr;
1631             }
1632         }
1633         m_wrapper.map();
1634         m_client.map();
1635         exportMappingState(XCB_ICCCM_WM_STATE_NORMAL);
1636         if (isActive()) {
1637             workspace()->requestFocus(this);
1638         }
1639     }
1640     info->setState(isShade() ? NET::Shaded : NET::States(), NET::Shaded);
1641     info->setState((isShade() || !isShown()) ? NET::Hidden : NET::States(), NET::Hidden);
1642     updateVisibility();
1643     updateAllowedActions();
1644     discardWindowPixmap();
1645 }
1646 
1647 void X11Window::updateVisibility()
1648 {
1649     if (isZombie()) {
1650         return;
1651     }
1652     if (hidden) {
1653         info->setState(NET::Hidden, NET::Hidden);
1654         setSkipTaskbar(true); // Also hide from taskbar
1655         if (Compositor::compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) {
1656             internalKeep();
1657         } else {
1658             internalHide();
1659         }
1660         return;
1661     }
1662     setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden'
1663     if (isMinimized()) {
1664         info->setState(NET::Hidden, NET::Hidden);
1665         if (Compositor::compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) {
1666             internalKeep();
1667         } else {
1668             internalHide();
1669         }
1670         return;
1671     }
1672     info->setState(NET::States(), NET::Hidden);
1673     if (!isOnCurrentDesktop()) {
1674         if (Compositor::compositing() && options->hiddenPreviews() != HiddenPreviewsNever) {
1675             internalKeep();
1676         } else {
1677             internalHide();
1678         }
1679         return;
1680     }
1681     if (!isOnCurrentActivity()) {
1682         if (Compositor::compositing() && options->hiddenPreviews() != HiddenPreviewsNever) {
1683             internalKeep();
1684         } else {
1685             internalHide();
1686         }
1687         return;
1688     }
1689     internalShow();
1690 }
1691 
1692 /**
1693  * Sets the client window's mapping state. Possible values are
1694  * WithdrawnState, IconicState, NormalState.
1695  */
1696 void X11Window::exportMappingState(int s)
1697 {
1698     Q_ASSERT(m_client != XCB_WINDOW_NONE);
1699     Q_ASSERT(!isZombie() || s == XCB_ICCCM_WM_STATE_WITHDRAWN);
1700     if (s == XCB_ICCCM_WM_STATE_WITHDRAWN) {
1701         m_client.deleteProperty(atoms->wm_state);
1702         return;
1703     }
1704     Q_ASSERT(s == XCB_ICCCM_WM_STATE_NORMAL || s == XCB_ICCCM_WM_STATE_ICONIC);
1705 
1706     int32_t data[2];
1707     data[0] = s;
1708     data[1] = XCB_NONE;
1709     m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data);
1710 }
1711 
1712 void X11Window::internalShow()
1713 {
1714     if (mapping_state == Mapped) {
1715         return;
1716     }
1717     MappingState old = mapping_state;
1718     mapping_state = Mapped;
1719     if (old == Unmapped || old == Withdrawn) {
1720         map();
1721     }
1722     if (old == Kept) {
1723         m_decoInputExtent.map();
1724         updateHiddenPreview();
1725     }
1726     Q_EMIT windowShown(this);
1727 }
1728 
1729 void X11Window::internalHide()
1730 {
1731     if (mapping_state == Unmapped) {
1732         return;
1733     }
1734     MappingState old = mapping_state;
1735     mapping_state = Unmapped;
1736     if (old == Mapped || old == Kept) {
1737         unmap();
1738     }
1739     if (old == Kept) {
1740         updateHiddenPreview();
1741     }
1742     workspace()->windowHidden(this);
1743     Q_EMIT windowHidden(this);
1744 }
1745 
1746 void X11Window::internalKeep()
1747 {
1748     Q_ASSERT(Compositor::compositing());
1749     if (mapping_state == Kept) {
1750         return;
1751     }
1752     MappingState old = mapping_state;
1753     mapping_state = Kept;
1754     if (old == Unmapped || old == Withdrawn) {
1755         map();
1756     }
1757     m_decoInputExtent.unmap();
1758     if (isActive()) {
1759         workspace()->focusToNull(); // get rid of input focus, bug #317484
1760     }
1761     updateHiddenPreview();
1762     workspace()->windowHidden(this);
1763 }
1764 
1765 /**
1766  * Maps (shows) the client. Note that it is mapping state of the frame,
1767  * not necessarily the client window itself (i.e. a shaded window is here
1768  * considered mapped, even though it is in IconicState).
1769  */
1770 void X11Window::map()
1771 {
1772     // XComposite invalidates backing pixmaps on unmap (minimize, different
1773     // virtual desktop, etc.).  We kept the last known good pixmap around
1774     // for use in effects, but now we want to have access to the new pixmap
1775     if (Compositor::compositing()) {
1776         discardWindowPixmap();
1777     }
1778     m_frame.map();
1779     if (!isShade()) {
1780         m_wrapper.map();
1781         m_client.map();
1782         m_decoInputExtent.map();
1783         exportMappingState(XCB_ICCCM_WM_STATE_NORMAL);
1784     } else {
1785         exportMappingState(XCB_ICCCM_WM_STATE_ICONIC);
1786     }
1787 }
1788 
1789 /**
1790  * Unmaps the client. Again, this is about the frame.
1791  */
1792 void X11Window::unmap()
1793 {
1794     // Here it may look like a race condition, as some other client might try to unmap
1795     // the window between these two XSelectInput() calls. However, they're supposed to
1796     // use XWithdrawWindow(), which also sends a synthetic event to the root window,
1797     // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify
1798     // will be missed is also very minimal, so I don't think it's needed to grab the server
1799     // here.
1800     m_wrapper.selectInput(wrapperEventMask() & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); // Avoid getting UnmapNotify
1801     m_frame.unmap();
1802     m_wrapper.unmap();
1803     m_client.unmap();
1804     m_decoInputExtent.unmap();
1805     m_wrapper.selectInput(wrapperEventMask());
1806     exportMappingState(XCB_ICCCM_WM_STATE_ICONIC);
1807 }
1808 
1809 /**
1810  * XComposite doesn't keep window pixmaps of unmapped windows, which means
1811  * there wouldn't be any previews of windows that are minimized or on another
1812  * virtual desktop. Therefore rawHide() actually keeps such windows mapped.
1813  * However special care needs to be taken so that such windows don't interfere.
1814  * Therefore they're put very low in the stacking order and they have input shape
1815  * set to none, which hopefully is enough. If there's no input shape available,
1816  * then it's hoped that there will be some other desktop above it *shrug*.
1817  * Using normal shape would be better, but that'd affect other things, e.g. painting
1818  * of the actual preview.
1819  */
1820 void X11Window::updateHiddenPreview()
1821 {
1822     if (hiddenPreview()) {
1823         workspace()->forceRestacking();
1824         if (Xcb::Extensions::self()->isShapeInputAvailable()) {
1825             xcb_shape_rectangles(kwinApp()->x11Connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT,
1826                                  XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, nullptr);
1827         }
1828     } else {
1829         workspace()->forceRestacking();
1830         updateInputShape();
1831     }
1832 }
1833 
1834 void X11Window::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3)
1835 {
1836     xcb_client_message_event_t ev;
1837     // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy
1838     // 32 unconditionally. Add a static_assert to ensure we don't disclose
1839     // stack memory.
1840     static_assert(sizeof(ev) == 32, "Would leak stack data otherwise");
1841     memset(&ev, 0, sizeof(ev));
1842     ev.response_type = XCB_CLIENT_MESSAGE;
1843     ev.window = w;
1844     ev.type = a;
1845     ev.format = 32;
1846     ev.data.data32[0] = protocol;
1847     ev.data.data32[1] = xTime();
1848     ev.data.data32[2] = data1;
1849     ev.data.data32[3] = data2;
1850     ev.data.data32[4] = data3;
1851     uint32_t eventMask = 0;
1852     if (w == kwinApp()->x11RootWindow()) {
1853         eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic!
1854     }
1855     xcb_send_event(kwinApp()->x11Connection(), false, w, eventMask, reinterpret_cast<const char *>(&ev));
1856     xcb_flush(kwinApp()->x11Connection());
1857 }
1858 
1859 /**
1860  * Returns whether the window may be closed (have a close button)
1861  */
1862 bool X11Window::isCloseable() const
1863 {
1864     return rules()->checkCloseable(m_motif.close() && !isSpecialWindow());
1865 }
1866 
1867 /**
1868  * Closes the window by either sending a delete_window message or using XKill.
1869  */
1870 void X11Window::closeWindow()
1871 {
1872     if (!isCloseable()) {
1873         return;
1874     }
1875 
1876     // Update user time, because the window may create a confirming dialog.
1877     updateUserTime();
1878 
1879     if (info->supportsProtocol(NET::DeleteWindowProtocol)) {
1880         sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window);
1881         pingWindow();
1882     } else { // Client will not react on wm_delete_window. We have not choice
1883         // but destroy his connection to the XServer.
1884         killWindow();
1885     }
1886 }
1887 
1888 /**
1889  * Kills the window via XKill
1890  */
1891 void X11Window::killWindow()
1892 {
1893     qCDebug(KWIN_CORE) << "X11Window::killWindow():" << caption();
1894     killProcess(false);
1895     m_client.kill(); // Always kill this client at the server
1896     destroyWindow();
1897 }
1898 
1899 /**
1900  * Send a ping to the window using _NET_WM_PING if possible if it
1901  * doesn't respond within a reasonable time, it will be killed.
1902  */
1903 void X11Window::pingWindow()
1904 {
1905     if (!info->supportsProtocol(NET::PingProtocol)) {
1906         return; // Can't ping :(
1907     }
1908     if (options->killPingTimeout() == 0) {
1909         return; // Turned off
1910     }
1911     if (ping_timer != nullptr) {
1912         return; // Pinging already
1913     }
1914     ping_timer = new QTimer(this);
1915     connect(ping_timer, &QTimer::timeout, this, [this]() {
1916         if (unresponsive()) {
1917             qCDebug(KWIN_CORE) << "Final ping timeout, asking to kill:" << caption();
1918             ping_timer->deleteLater();
1919             ping_timer = nullptr;
1920             killProcess(true, m_pingTimestamp);
1921             return;
1922         }
1923 
1924         qCDebug(KWIN_CORE) << "First ping timeout:" << caption();
1925 
1926         setUnresponsive(true);
1927         ping_timer->start();
1928     });
1929     ping_timer->setSingleShot(true);
1930     // we'll run the timer twice, at first we'll desaturate the window
1931     // and the second time we'll show the "do you want to kill" prompt
1932     ping_timer->start(options->killPingTimeout() / 2);
1933     m_pingTimestamp = xTime();
1934     rootInfo()->sendPing(window(), m_pingTimestamp);
1935 }
1936 
1937 void X11Window::gotPing(xcb_timestamp_t timestamp)
1938 {
1939     // Just plain compare is not good enough because of 64bit and truncating and whatnot
1940     if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0) {
1941         return;
1942     }
1943     delete ping_timer;
1944     ping_timer = nullptr;
1945 
1946     setUnresponsive(false);
1947 
1948     if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
1949         ::kill(m_killHelperPID, SIGTERM);
1950         m_killHelperPID = 0;
1951     }
1952 }
1953 
1954 void X11Window::killProcess(bool ask, xcb_timestamp_t timestamp)
1955 {
1956     if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
1957         return;
1958     }
1959     Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME);
1960     pid_t pid = info->pid();
1961     if (pid <= 0 || clientMachine()->hostName().isEmpty()) { // Needed properties missing
1962         return;
1963     }
1964     qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")";
1965     if (!ask) {
1966         if (!clientMachine()->isLocal()) {
1967             QStringList lst;
1968             lst << clientMachine()->hostName() << QStringLiteral("kill") << QString::number(pid);
1969             QProcess::startDetached(QStringLiteral("xon"), lst);
1970         } else {
1971             ::kill(pid, SIGTERM);
1972         }
1973     } else {
1974         QString hostname = clientMachine()->isLocal() ? QStringLiteral("localhost") : clientMachine()->hostName();
1975         // execute helper from build dir or the system installed one
1976         const QFileInfo buildDirBinary{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("kwin_killer_helper")};
1977         QProcess::startDetached(buildDirBinary.exists() ? buildDirBinary.absoluteFilePath() : QStringLiteral(KWIN_KILLER_BIN),
1978                                 QStringList() << QStringLiteral("--pid") << QString::number(unsigned(pid)) << QStringLiteral("--hostname") << hostname
1979                                               << QStringLiteral("--windowname") << captionNormal()
1980                                               << QStringLiteral("--applicationname") << resourceClass()
1981                                               << QStringLiteral("--wid") << QString::number(window())
1982                                               << QStringLiteral("--timestamp") << QString::number(timestamp),
1983                                 QString(), &m_killHelperPID);
1984     }
1985 }
1986 
1987 void X11Window::doSetKeepAbove()
1988 {
1989     info->setState(keepAbove() ? NET::KeepAbove : NET::States(), NET::KeepAbove);
1990 }
1991 
1992 void X11Window::doSetKeepBelow()
1993 {
1994     info->setState(keepBelow() ? NET::KeepBelow : NET::States(), NET::KeepBelow);
1995 }
1996 
1997 void X11Window::doSetSkipTaskbar()
1998 {
1999     info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(), NET::SkipTaskbar);
2000 }
2001 
2002 void X11Window::doSetSkipPager()
2003 {
2004     info->setState(skipPager() ? NET::SkipPager : NET::States(), NET::SkipPager);
2005 }
2006 
2007 void X11Window::doSetSkipSwitcher()
2008 {
2009     info->setState(skipSwitcher() ? NET::SkipSwitcher : NET::States(), NET::SkipSwitcher);
2010 }
2011 
2012 void X11Window::doSetDesktop()
2013 {
2014     updateVisibility();
2015 }
2016 
2017 void X11Window::doSetDemandsAttention()
2018 {
2019     info->setState(isDemandingAttention() ? NET::DemandsAttention : NET::States(), NET::DemandsAttention);
2020 }
2021 
2022 void X11Window::doSetOnActivities(const QStringList &activityList)
2023 {
2024 #if KWIN_BUILD_ACTIVITIES
2025     if (activityList.isEmpty()) {
2026         const QByteArray nullUuid = Activities::nullUuid().toUtf8();
2027         m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData());
2028     } else {
2029         QByteArray joined = activityList.join(QStringLiteral(",")).toLatin1();
2030         m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData());
2031     }
2032 #endif
2033 }
2034 
2035 void X11Window::updateActivities(bool includeTransients)
2036 {
2037     Window::updateActivities(includeTransients);
2038     if (!m_activityUpdatesBlocked) {
2039         updateVisibility();
2040     }
2041 }
2042 
2043 /**
2044  * Returns the list of activities the client window is on.
2045  * if it's on all activities, the list will be empty.
2046  * Don't use this, use isOnActivity() and friends (from class Window)
2047  */
2048 QStringList X11Window::activities() const
2049 {
2050     if (sessionActivityOverride) {
2051         return QStringList();
2052     }
2053     return Window::activities();
2054 }
2055 
2056 /**
2057  * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS
2058  */
2059 bool X11Window::takeFocus()
2060 {
2061     if (rules()->checkAcceptFocus(info->input())) {
2062         xcb_void_cookie_t cookie = xcb_set_input_focus_checked(kwinApp()->x11Connection(),
2063                                                                XCB_INPUT_FOCUS_POINTER_ROOT,
2064                                                                window(), XCB_TIME_CURRENT_TIME);
2065         UniqueCPtr<xcb_generic_error_t> error(xcb_request_check(kwinApp()->x11Connection(), cookie));
2066         if (error) {
2067             qCWarning(KWIN_CORE, "Failed to focus 0x%x (error %d)", window(), error->error_code);
2068             return false;
2069         }
2070     } else {
2071         demandAttention(false); // window cannot take input, at least withdraw urgency
2072     }
2073     if (info->supportsProtocol(NET::TakeFocusProtocol)) {
2074         kwinApp()->updateXTime();
2075         sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus);
2076     }
2077     workspace()->setShouldGetFocus(this);
2078 
2079     bool breakShowingDesktop = !keepAbove();
2080     if (breakShowingDesktop) {
2081         const auto members = group()->members();
2082         for (const X11Window *c : members) {
2083             if (c->isDesktop()) {
2084                 breakShowingDesktop = false;
2085                 break;
2086             }
2087         }
2088     }
2089     if (breakShowingDesktop) {
2090         workspace()->setShowingDesktop(false);
2091     }
2092 
2093     return true;
2094 }
2095 
2096 /**
2097  * Returns whether the window provides context help or not. If it does,
2098  * you should show a help menu item or a help button like '?' and call
2099  * contextHelp() if this is invoked.
2100  *
2101  * \sa contextHelp()
2102  */
2103 bool X11Window::providesContextHelp() const
2104 {
2105     return info->supportsProtocol(NET::ContextHelpProtocol);
2106 }
2107 
2108 /**
2109  * Invokes context help on the window. Only works if the window
2110  * actually provides context help.
2111  *
2112  * \sa providesContextHelp()
2113  */
2114 void X11Window::showContextHelp()
2115 {
2116     if (info->supportsProtocol(NET::ContextHelpProtocol)) {
2117         sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help);
2118     }
2119 }
2120 
2121 /**
2122  * Fetches the window's caption (WM_NAME property). It will be
2123  * stored in the client's caption().
2124  */
2125 void X11Window::fetchName()
2126 {
2127     setCaption(readName());
2128 }
2129 
2130 static inline QString readNameProperty(xcb_window_t w, xcb_atom_t atom)
2131 {
2132     const auto cookie = xcb_icccm_get_text_property_unchecked(kwinApp()->x11Connection(), w, atom);
2133     xcb_icccm_get_text_property_reply_t reply;
2134     if (xcb_icccm_get_wm_name_reply(kwinApp()->x11Connection(), cookie, &reply, nullptr)) {
2135         QString retVal;
2136         if (reply.encoding == atoms->utf8_string) {
2137             retVal = QString::fromUtf8(QByteArray(reply.name, reply.name_len));
2138         } else if (reply.encoding == XCB_ATOM_STRING) {
2139             retVal = QString::fromLatin1(QByteArray(reply.name, reply.name_len));
2140         }
2141         xcb_icccm_get_text_property_reply_wipe(&reply);
2142         return retVal.simplified();
2143     }
2144     return QString();
2145 }
2146 
2147 QString X11Window::readName() const
2148 {
2149     if (info->name() && info->name()[0] != '\0') {
2150         return QString::fromUtf8(info->name()).simplified();
2151     } else {
2152         return readNameProperty(window(), XCB_ATOM_WM_NAME);
2153     }
2154 }
2155 
2156 // The list is taken from https://www.unicode.org/reports/tr9/ (#154840)
2157 static const QChar LRM(0x200E);
2158 
2159 void X11Window::setCaption(const QString &_s, bool force)
2160 {
2161     QString s(_s);
2162     for (int i = 0; i < s.length();) {
2163         if (!s[i].isPrint()) {
2164             if (QChar(s[i]).isHighSurrogate() && i + 1 < s.length() && QChar(s[i + 1]).isLowSurrogate()) {
2165                 const uint uc = QChar::surrogateToUcs4(s[i], s[i + 1]);
2166                 if (!QChar::isPrint(uc)) {
2167                     s.remove(i, 2);
2168                 } else {
2169                     i += 2;
2170                 }
2171                 continue;
2172             }
2173             s.remove(i, 1);
2174             continue;
2175         }
2176         ++i;
2177     }
2178     const bool changed = (s != cap_normal);
2179     if (!force && !changed) {
2180         return;
2181     }
2182     cap_normal = s;
2183     if (!force && !changed) {
2184         Q_EMIT captionChanged();
2185         return;
2186     }
2187 
2188     bool reset_name = force;
2189     bool was_suffix = (!cap_suffix.isEmpty());
2190     cap_suffix.clear();
2191     QString machine_suffix;
2192     if (!options->condensedTitle()) { // machine doesn't qualify for "clean"
2193         if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal()) {
2194             machine_suffix = QLatin1String(" <@") + clientMachine()->hostName() + QLatin1Char('>') + LRM;
2195         }
2196     }
2197     QString shortcut_suffix = shortcutCaptionSuffix();
2198     cap_suffix = machine_suffix + shortcut_suffix;
2199     if ((!isSpecialWindow() || isToolbar()) && findWindowWithSameCaption()) {
2200         int i = 2;
2201         do {
2202             cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM;
2203             i++;
2204         } while (findWindowWithSameCaption());
2205         info->setVisibleName(caption().toUtf8().constData());
2206         reset_name = false;
2207     }
2208     if ((was_suffix && cap_suffix.isEmpty()) || reset_name) {
2209         // If it was new window, it may have old value still set, if the window is reused
2210         info->setVisibleName("");
2211         info->setVisibleIconName("");
2212     } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty()) {
2213         // Keep the same suffix in iconic name if it's set
2214         info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData());
2215     }
2216 
2217     Q_EMIT captionChanged();
2218 }
2219 
2220 void X11Window::updateCaption()
2221 {
2222     setCaption(cap_normal, true);
2223 }
2224 
2225 void X11Window::fetchIconicName()
2226 {
2227     QString s;
2228     if (info->iconName() && info->iconName()[0] != '\0') {
2229         s = QString::fromUtf8(info->iconName());
2230     } else {
2231         s = readNameProperty(window(), XCB_ATOM_WM_ICON_NAME);
2232     }
2233     if (s != cap_iconic) {
2234         bool was_set = !cap_iconic.isEmpty();
2235         cap_iconic = s;
2236         if (!cap_suffix.isEmpty()) {
2237             if (!cap_iconic.isEmpty()) { // Keep the same suffix in iconic name if it's set
2238                 info->setVisibleIconName(QString(s + cap_suffix).toUtf8().constData());
2239             } else if (was_set) {
2240                 info->setVisibleIconName("");
2241             }
2242         }
2243     }
2244 }
2245 
2246 void X11Window::getMotifHints()
2247 {
2248     const bool wasClosable = isCloseable();
2249     const bool wasNoBorder = m_motif.noBorder();
2250     if (m_managed) { // only on property change, initial read is prefetched
2251         m_motif.fetch();
2252     }
2253     m_motif.read();
2254     if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) {
2255         // If we just got a hint telling us to hide decorations, we do so.
2256         if (m_motif.noBorder()) {
2257             noborder = rules()->checkNoBorder(true);
2258             // If the Motif hint is now telling us to show decorations, we only do so if the app didn't
2259             // instruct us to hide decorations in some other way, though.
2260         } else if (!app_noborder) {
2261             noborder = rules()->checkNoBorder(false);
2262         }
2263     }
2264 
2265     // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too
2266     // mmaximize; - Ignore, bogus - Maximizing is basically just resizing
2267     const bool closabilityChanged = wasClosable != isCloseable();
2268     if (isManaged()) {
2269         updateDecoration(true); // Check if noborder state has changed
2270     }
2271     if (closabilityChanged) {
2272         Q_EMIT closeableChanged(isCloseable());
2273     }
2274 }
2275 
2276 void X11Window::getIcons()
2277 {
2278     // First read icons from the window itself
2279     const QString themedIconName = iconFromDesktopFile();
2280     if (!themedIconName.isEmpty()) {
2281         setIcon(QIcon::fromTheme(themedIconName));
2282         return;
2283     }
2284     QIcon icon;
2285     auto readIcon = [this, &icon](int size, bool scale = true) {
2286         const QPixmap pix = KX11Extras::icon(window(), size, size, scale, KX11Extras::NETWM | KX11Extras::WMHints, info);
2287         if (!pix.isNull()) {
2288             icon.addPixmap(pix);
2289         }
2290     };
2291     readIcon(16);
2292     readIcon(32);
2293     readIcon(48, false);
2294     readIcon(64, false);
2295     readIcon(128, false);
2296     if (icon.isNull()) {
2297         // Then try window group
2298         icon = group()->icon();
2299     }
2300     if (icon.isNull() && isTransient()) {
2301         // Then mainwindows
2302         auto mainwindows = mainWindows();
2303         for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd() && icon.isNull(); ++it) {
2304             if (!(*it)->icon().isNull()) {
2305                 icon = (*it)->icon();
2306                 break;
2307             }
2308         }
2309     }
2310     if (icon.isNull()) {
2311         // And if nothing else, load icon from classhint or xapp icon
2312         icon.addPixmap(KX11Extras::icon(window(), 32, 32, true, KX11Extras::ClassHint | KX11Extras::XApp, info));
2313         icon.addPixmap(KX11Extras::icon(window(), 16, 16, true, KX11Extras::ClassHint | KX11Extras::XApp, info));
2314         icon.addPixmap(KX11Extras::icon(window(), 64, 64, false, KX11Extras::ClassHint | KX11Extras::XApp, info));
2315         icon.addPixmap(KX11Extras::icon(window(), 128, 128, false, KX11Extras::ClassHint | KX11Extras::XApp, info));
2316     }
2317     setIcon(icon);
2318 }
2319 
2320 /**
2321  * Returns \c true if X11Client wants to throttle resizes; otherwise returns \c false.
2322  */
2323 bool X11Window::wantsSyncCounter() const
2324 {
2325     return true;
2326 }
2327 
2328 void X11Window::getSyncCounter()
2329 {
2330     if (!Xcb::Extensions::self()->isSyncAvailable()) {
2331         return;
2332     }
2333     if (!wantsSyncCounter()) {
2334         return;
2335     }
2336 
2337     Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1);
2338     const xcb_sync_counter_t counter = syncProp.value<xcb_sync_counter_t>(XCB_NONE);
2339     if (counter != XCB_NONE) {
2340         m_syncRequest.counter = counter;
2341         m_syncRequest.value.hi = 0;
2342         m_syncRequest.value.lo = 0;
2343         auto *c = kwinApp()->x11Connection();
2344         xcb_sync_set_counter(c, m_syncRequest.counter, m_syncRequest.value);
2345         if (m_syncRequest.alarm == XCB_NONE) {
2346             const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS;
2347             const uint32_t values[] = {
2348                 m_syncRequest.counter,
2349                 XCB_SYNC_VALUETYPE_RELATIVE,
2350                 XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION,
2351                 1};
2352             m_syncRequest.alarm = xcb_generate_id(c);
2353             auto cookie = xcb_sync_create_alarm_checked(c, m_syncRequest.alarm, mask, values);
2354             UniqueCPtr<xcb_generic_error_t> error(xcb_request_check(c, cookie));
2355             if (error) {
2356                 m_syncRequest.alarm = XCB_NONE;
2357             } else {
2358                 xcb_sync_change_alarm_value_list_t value;
2359                 memset(&value, 0, sizeof(value));
2360                 value.value.hi = 0;
2361                 value.value.lo = 1;
2362                 value.delta.hi = 0;
2363                 value.delta.lo = 1;
2364                 xcb_sync_change_alarm_aux(c, m_syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value);
2365             }
2366         }
2367     }
2368 }
2369 
2370 /**
2371  * Send the client a _NET_SYNC_REQUEST
2372  */
2373 void X11Window::sendSyncRequest()
2374 {
2375     if (m_syncRequest.counter == XCB_NONE || m_syncRequest.isPending) {
2376         return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ...
2377     }
2378 
2379     if (!m_syncRequest.failsafeTimeout) {
2380         m_syncRequest.failsafeTimeout = new QTimer(this);
2381         connect(m_syncRequest.failsafeTimeout, &QTimer::timeout, this, [this]() {
2382             // client does not respond to XSYNC requests in reasonable time, remove support
2383             if (!ready_for_painting) {
2384                 // failed on initial pre-show request
2385                 setReadyForPainting();
2386                 return;
2387             }
2388             // failed during resize
2389             m_syncRequest.isPending = false;
2390             m_syncRequest.interactiveResize = false;
2391             m_syncRequest.counter = XCB_NONE;
2392             m_syncRequest.alarm = XCB_NONE;
2393             delete m_syncRequest.timeout;
2394             delete m_syncRequest.failsafeTimeout;
2395             m_syncRequest.timeout = nullptr;
2396             m_syncRequest.failsafeTimeout = nullptr;
2397             m_syncRequest.lastTimestamp = XCB_CURRENT_TIME;
2398         });
2399         m_syncRequest.failsafeTimeout->setSingleShot(true);
2400     }
2401     // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client.
2402     // see events.cpp X11Window::syncEvent()
2403     m_syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000);
2404 
2405     // We increment before the notify so that after the notify
2406     // syncCounterSerial will equal the value we are expecting
2407     // in the acknowledgement
2408     const uint32_t oldLo = m_syncRequest.value.lo;
2409     m_syncRequest.value.lo++;
2410     if (oldLo > m_syncRequest.value.lo) {
2411         m_syncRequest.value.hi++;
2412     }
2413     if (m_syncRequest.lastTimestamp >= xTime()) {
2414         kwinApp()->updateXTime();
2415     }
2416 
2417     // Send the message to client
2418     sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request,
2419                       m_syncRequest.value.lo, m_syncRequest.value.hi);
2420     m_syncRequest.isPending = true;
2421     m_syncRequest.interactiveResize = isInteractiveResize();
2422     m_syncRequest.lastTimestamp = xTime();
2423 }
2424 
2425 bool X11Window::wantsInput() const
2426 {
2427     return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol));
2428 }
2429 
2430 bool X11Window::acceptsFocus() const
2431 {
2432     return info->input();
2433 }
2434 
2435 void X11Window::setBlockingCompositing(bool block)
2436 {
2437     const bool usedToBlock = blocks_compositing;
2438     blocks_compositing = rules()->checkBlockCompositing(block && options->windowsBlockCompositing());
2439     if (usedToBlock != blocks_compositing) {
2440         Q_EMIT blockingCompositingChanged(blocks_compositing ? this : nullptr);
2441     }
2442 }
2443 
2444 void X11Window::updateAllowedActions(bool force)
2445 {
2446     if (!isManaged() && !force) {
2447         return;
2448     }
2449     NET::Actions old_allowed_actions = NET::Actions(allowed_actions);
2450     allowed_actions = NET::Actions();
2451     if (isMovable()) {
2452         allowed_actions |= NET::ActionMove;
2453     }
2454     if (isResizable()) {
2455         allowed_actions |= NET::ActionResize;
2456     }
2457     if (isMinimizable()) {
2458         allowed_actions |= NET::ActionMinimize;
2459     }
2460     if (isShadeable()) {
2461         allowed_actions |= NET::ActionShade;
2462     }
2463     // Sticky state not supported
2464     if (isMaximizable()) {
2465         allowed_actions |= NET::ActionMax;
2466     }
2467     if (userCanSetFullScreen()) {
2468         allowed_actions |= NET::ActionFullScreen;
2469     }
2470     allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.)
2471     if (isCloseable()) {
2472         allowed_actions |= NET::ActionClose;
2473     }
2474     if (old_allowed_actions == allowed_actions) {
2475         return;
2476     }
2477     // TODO: This could be delayed and compressed - It's only for pagers etc. anyway
2478     info->setAllowedActions(allowed_actions);
2479     // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes)
2480     const NET::Actions relevant = ~(NET::ActionMove | NET::ActionResize);
2481     if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) {
2482         if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) {
2483             Q_EMIT minimizeableChanged(allowed_actions & NET::ActionMinimize);
2484         }
2485         if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) {
2486             Q_EMIT shadeableChanged(allowed_actions & NET::ActionShade);
2487         }
2488         if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) {
2489             Q_EMIT maximizeableChanged(allowed_actions & NET::ActionMax);
2490         }
2491         if ((allowed_actions & NET::ActionClose) != (old_allowed_actions & NET::ActionClose)) {
2492             Q_EMIT closeableChanged(allowed_actions & NET::ActionClose);
2493         }
2494     }
2495 }
2496 
2497 Xcb::StringProperty X11Window::fetchActivities() const
2498 {
2499 #if KWIN_BUILD_ACTIVITIES
2500     return Xcb::StringProperty(window(), atoms->activities);
2501 #else
2502     return Xcb::StringProperty();
2503 #endif
2504 }
2505 
2506 void X11Window::readActivities(Xcb::StringProperty &property)
2507 {
2508 #if KWIN_BUILD_ACTIVITIES
2509     QString prop = QString::fromUtf8(property);
2510     activitiesDefined = !prop.isEmpty();
2511 
2512     if (prop == Activities::nullUuid()) {
2513         // copied from setOnAllActivities to avoid a redundant XChangeProperty.
2514         if (!m_activityList.isEmpty()) {
2515             m_activityList.clear();
2516             updateActivities(true);
2517         }
2518         return;
2519     }
2520     if (prop.isEmpty()) {
2521         // note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL'
2522         if (!m_activityList.isEmpty()) {
2523             m_activityList.clear();
2524             updateActivities(true);
2525         }
2526         return;
2527     }
2528 
2529     const QStringList newActivitiesList = prop.split(u',');
2530 
2531     if (newActivitiesList == m_activityList) {
2532         return; // expected change, it's ok.
2533     }
2534 
2535     setOnActivities(newActivitiesList);
2536 #endif
2537 }
2538 
2539 void X11Window::checkActivities()
2540 {
2541 #if KWIN_BUILD_ACTIVITIES
2542     Xcb::StringProperty property = fetchActivities();
2543     readActivities(property);
2544 #endif
2545 }
2546 
2547 void X11Window::setSessionActivityOverride(bool needed)
2548 {
2549     sessionActivityOverride = needed;
2550     updateActivities(false);
2551 }
2552 
2553 Xcb::Property X11Window::fetchFirstInTabBox() const
2554 {
2555     return Xcb::Property(false, m_client, atoms->kde_first_in_window_list,
2556                          atoms->kde_first_in_window_list, 0, 1);
2557 }
2558 
2559 void X11Window::readFirstInTabBox(Xcb::Property &property)
2560 {
2561     setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list));
2562 }
2563 
2564 void X11Window::updateFirstInTabBox()
2565 {
2566     // TODO: move into KWindowInfo
2567     Xcb::Property property = fetchFirstInTabBox();
2568     readFirstInTabBox(property);
2569 }
2570 
2571 Xcb::StringProperty X11Window::fetchPreferredColorScheme() const
2572 {
2573     return Xcb::StringProperty(m_client, atoms->kde_color_sheme);
2574 }
2575 
2576 QString X11Window::readPreferredColorScheme(Xcb::StringProperty &property) const
2577 {
2578     return rules()->checkDecoColor(QString::fromUtf8(property));
2579 }
2580 
2581 QString X11Window::preferredColorScheme() const
2582 {
2583     Xcb::StringProperty property = fetchPreferredColorScheme();
2584     return readPreferredColorScheme(property);
2585 }
2586 
2587 bool X11Window::isClient() const
2588 {
2589     return true;
2590 }
2591 
2592 NET::WindowType X11Window::windowType(bool direct, int supportedTypes) const
2593 {
2594     // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK?
2595     if (supportedTypes == 0) {
2596         supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK;
2597     }
2598     NET::WindowType wt = info->windowType(NET::WindowTypes(supportedTypes));
2599     if (direct) {
2600         return wt;
2601     }
2602     NET::WindowType wt2 = rules()->checkType(wt);
2603     if (wt != wt2) {
2604         wt = wt2;
2605         info->setWindowType(wt); // force hint change
2606     }
2607     // hacks here
2608     if (wt == NET::Unknown) { // this is more or less suggested in NETWM spec
2609         wt = isTransient() ? NET::Dialog : NET::Normal;
2610     }
2611     return wt;
2612 }
2613 
2614 void X11Window::cancelFocusOutTimer()
2615 {
2616     if (m_focusOutTimer) {
2617         m_focusOutTimer->stop();
2618     }
2619 }
2620 
2621 xcb_window_t X11Window::frameId() const
2622 {
2623     return m_frame;
2624 }
2625 
2626 QRectF X11Window::inputGeometry() const
2627 {
2628     // Notice that the buffer geometry corresponds to the geometry of the frame window.
2629     if (isDecorated()) {
2630         return m_bufferGeometry + decoration()->resizeOnlyBorders();
2631     }
2632     return m_bufferGeometry;
2633 }
2634 
2635 QPointF X11Window::framePosToClientPos(const QPointF &point) const
2636 {
2637     qreal x = point.x();
2638     qreal y = point.y();
2639 
2640     if (isDecorated()) {
2641         x += borderLeft();
2642         y += borderTop();
2643     } else {
2644         x -= m_clientFrameExtents.left();
2645         y -= m_clientFrameExtents.top();
2646     }
2647 
2648     return QPointF(x, y);
2649 }
2650 
2651 QPointF X11Window::clientPosToFramePos(const QPointF &point) const
2652 {
2653     qreal x = point.x();
2654     qreal y = point.y();
2655 
2656     if (isDecorated()) {
2657         x -= borderLeft();
2658         y -= borderTop();
2659     } else {
2660         x += m_clientFrameExtents.left();
2661         y += m_clientFrameExtents.top();
2662     }
2663 
2664     return QPointF(x, y);
2665 }
2666 
2667 QSizeF X11Window::frameSizeToClientSize(const QSizeF &size) const
2668 {
2669     qreal width = size.width();
2670     qreal height = size.height();
2671 
2672     if (isDecorated()) {
2673         width -= borderLeft() + borderRight();
2674         height -= borderTop() + borderBottom();
2675     } else {
2676         width += m_clientFrameExtents.left() + m_clientFrameExtents.right();
2677         height += m_clientFrameExtents.top() + m_clientFrameExtents.bottom();
2678     }
2679 
2680     return QSizeF(width, height);
2681 }
2682 
2683 QSizeF X11Window::clientSizeToFrameSize(const QSizeF &size) const
2684 {
2685     qreal width = size.width();
2686     qreal height = size.height();
2687 
2688     if (isDecorated()) {
2689         width += borderLeft() + borderRight();
2690         height += borderTop() + borderBottom();
2691     } else {
2692         width -= m_clientFrameExtents.left() + m_clientFrameExtents.right();
2693         height -= m_clientFrameExtents.top() + m_clientFrameExtents.bottom();
2694     }
2695 
2696     return QSizeF(width, height);
2697 }
2698 
2699 QRectF X11Window::frameRectToBufferRect(const QRectF &rect) const
2700 {
2701     if (isDecorated()) {
2702         return rect;
2703     }
2704     return frameRectToClientRect(rect);
2705 }
2706 
2707 /**
2708  * Returns the natural size of the window, if the window is not shaded it's the same
2709  * as size().
2710  */
2711 QSizeF X11Window::implicitSize() const
2712 {
2713     return clientSizeToFrameSize(m_client.geometry().size());
2714 }
2715 
2716 QMatrix4x4 X11Window::inputTransformation() const
2717 {
2718     QMatrix4x4 matrix;
2719     matrix.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y());
2720     return matrix;
2721 }
2722 
2723 Xcb::Property X11Window::fetchShowOnScreenEdge() const
2724 {
2725     return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1);
2726 }
2727 
2728 void X11Window::readShowOnScreenEdge(Xcb::Property &property)
2729 {
2730     // value comes in two parts, edge in the lower byte
2731     // then the type in the upper byte
2732     //  0 = autohide
2733     //  1 = raise in front on activate
2734 
2735     const uint32_t value = property.value<uint32_t>(ElectricNone);
2736     ElectricBorder border = ElectricNone;
2737     switch (value & 0xFF) {
2738     case 0:
2739         border = ElectricTop;
2740         break;
2741     case 1:
2742         border = ElectricRight;
2743         break;
2744     case 2:
2745         border = ElectricBottom;
2746         break;
2747     case 3:
2748         border = ElectricLeft;
2749         break;
2750     }
2751     if (border != ElectricNone) {
2752         disconnect(m_edgeRemoveConnection);
2753         disconnect(m_edgeGeometryTrackingConnection);
2754         bool successfullyHidden = false;
2755 
2756         if (((value >> 8) & 0xFF) == 1) {
2757             setKeepBelow(true);
2758             successfullyHidden = keepBelow(); // request could have failed due to user kwin rules
2759 
2760             m_edgeRemoveConnection = connect(this, &Window::keepBelowChanged, this, [this]() {
2761                 if (!keepBelow()) {
2762                     workspace()->screenEdges()->reserve(this, ElectricNone);
2763                 }
2764             });
2765         } else {
2766             hideClient();
2767             successfullyHidden = isHiddenInternal();
2768 
2769             m_edgeGeometryTrackingConnection = connect(this, &X11Window::frameGeometryChanged, this, [this, border]() {
2770                 hideClient();
2771                 workspace()->screenEdges()->reserve(this, border);
2772             });
2773         }
2774 
2775         if (successfullyHidden) {
2776             workspace()->screenEdges()->reserve(this, border);
2777         } else {
2778             workspace()->screenEdges()->reserve(this, ElectricNone);
2779         }
2780     } else if (!property.isNull() && property->type != XCB_ATOM_NONE) {
2781         // property value is incorrect, delete the property
2782         // so that the client knows that it is not hidden
2783         xcb_delete_property(kwinApp()->x11Connection(), window(), atoms->kde_screen_edge_show);
2784     } else {
2785         // restore
2786         // TODO: add proper unreserve
2787 
2788         // this will call showOnScreenEdge to reset the state
2789         disconnect(m_edgeGeometryTrackingConnection);
2790         workspace()->screenEdges()->reserve(this, ElectricNone);
2791     }
2792 }
2793 
2794 void X11Window::updateShowOnScreenEdge()
2795 {
2796     Xcb::Property property = fetchShowOnScreenEdge();
2797     readShowOnScreenEdge(property);
2798 }
2799 
2800 void X11Window::showOnScreenEdge()
2801 {
2802     disconnect(m_edgeRemoveConnection);
2803 
2804     showClient();
2805     setKeepBelow(false);
2806     xcb_delete_property(kwinApp()->x11Connection(), window(), atoms->kde_screen_edge_show);
2807 }
2808 
2809 bool X11Window::belongsToSameApplication(const Window *other, SameApplicationChecks checks) const
2810 {
2811     const X11Window *c2 = dynamic_cast<const X11Window *>(other);
2812     if (!c2) {
2813         return false;
2814     }
2815     return X11Window::belongToSameApplication(this, c2, checks);
2816 }
2817 
2818 QSizeF X11Window::resizeIncrements() const
2819 {
2820     return m_geometryHints.resizeIncrements();
2821 }
2822 
2823 Xcb::StringProperty X11Window::fetchApplicationMenuServiceName() const
2824 {
2825     return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name);
2826 }
2827 
2828 void X11Window::readApplicationMenuServiceName(Xcb::StringProperty &property)
2829 {
2830     updateApplicationMenuServiceName(QString::fromUtf8(property));
2831 }
2832 
2833 void X11Window::checkApplicationMenuServiceName()
2834 {
2835     Xcb::StringProperty property = fetchApplicationMenuServiceName();
2836     readApplicationMenuServiceName(property);
2837 }
2838 
2839 Xcb::StringProperty X11Window::fetchApplicationMenuObjectPath() const
2840 {
2841     return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path);
2842 }
2843 
2844 void X11Window::readApplicationMenuObjectPath(Xcb::StringProperty &property)
2845 {
2846     updateApplicationMenuObjectPath(QString::fromUtf8(property));
2847 }
2848 
2849 void X11Window::checkApplicationMenuObjectPath()
2850 {
2851     Xcb::StringProperty property = fetchApplicationMenuObjectPath();
2852     readApplicationMenuObjectPath(property);
2853 }
2854 
2855 void X11Window::handleSync()
2856 {
2857     setReadyForPainting();
2858     m_syncRequest.isPending = false;
2859     if (m_syncRequest.failsafeTimeout) {
2860         m_syncRequest.failsafeTimeout->stop();
2861     }
2862 
2863     // Sync request can be acknowledged shortly after finishing resize.
2864     if (m_syncRequest.interactiveResize) {
2865         m_syncRequest.interactiveResize = false;
2866         if (m_syncRequest.timeout) {
2867             m_syncRequest.timeout->stop();
2868         }
2869         performInteractiveResize();
2870         updateWindowPixmap();
2871     }
2872 }
2873 
2874 void X11Window::performInteractiveResize()
2875 {
2876     resize(moveResizeGeometry().size());
2877 }
2878 
2879 bool X11Window::belongToSameApplication(const X11Window *c1, const X11Window *c2, SameApplicationChecks checks)
2880 {
2881     bool same_app = false;
2882 
2883     // tests that definitely mean they belong together
2884     if (c1 == c2) {
2885         same_app = true;
2886     } else if (c1->isTransient() && c2->hasTransient(c1, true)) {
2887         same_app = true; // c1 has c2 as mainwindow
2888     } else if (c2->isTransient() && c1->hasTransient(c2, true)) {
2889         same_app = true; // c2 has c1 as mainwindow
2890     } else if (c1->group() == c2->group()) {
2891         same_app = true; // same group
2892     } else if (c1->wmClientLeader() == c2->wmClientLeader()
2893                && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
2894                && c2->wmClientLeader() != c2->window()) { // don't use in this test then
2895         same_app = true; // same client leader
2896 
2897         // tests that mean they most probably don't belong together
2898     } else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses))
2899                || c1->wmClientMachine(false) != c2->wmClientMachine(false)) {
2900         ; // different processes
2901     } else if (c1->wmClientLeader() != c2->wmClientLeader()
2902                && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
2903                && c2->wmClientLeader() != c2->window() // don't use in this test then
2904                && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) {
2905         ; // different client leader
2906     } else if (!resourceMatch(c1, c2)) {
2907         ; // different apps
2908     } else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive))
2909                && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) {
2910         ; // "different" apps
2911     } else if (c1->pid() == 0 || c2->pid() == 0) {
2912         ; // old apps that don't have _NET_WM_PID, consider them different
2913         // if they weren't found to match above
2914     } else {
2915         same_app = true; // looks like it's the same app
2916     }
2917 
2918     return same_app;
2919 }
2920 
2921 // Non-transient windows with window role containing '#' are always
2922 // considered belonging to different applications (unless
2923 // the window role is exactly the same). KMainWindow sets
2924 // window role this way by default, and different KMainWindow
2925 // usually "are" different application from user's point of view.
2926 // This help with no-focus-stealing for e.g. konqy reusing.
2927 // On the other hand, if one of the windows is active, they are
2928 // considered belonging to the same application. This is for
2929 // the cases when opening new mainwindow directly from the application,
2930 // e.g. 'Open New Window' in konqy ( active_hack == true ).
2931 bool X11Window::sameAppWindowRoleMatch(const X11Window *c1, const X11Window *c2, bool active_hack)
2932 {
2933     if (c1->isTransient()) {
2934         while (const X11Window *t = dynamic_cast<const X11Window *>(c1->transientFor())) {
2935             c1 = t;
2936         }
2937         if (c1->groupTransient()) {
2938             return c1->group() == c2->group();
2939         }
2940 #if 0
2941         // if a group transient is in its own group, it didn't possibly have a group,
2942         // and therefore should be considered belonging to the same app like
2943         // all other windows from the same app
2944         || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
2945 #endif
2946     }
2947     if (c2->isTransient()) {
2948         while (const X11Window *t = dynamic_cast<const X11Window *>(c2->transientFor())) {
2949             c2 = t;
2950         }
2951         if (c2->groupTransient()) {
2952             return c1->group() == c2->group();
2953         }
2954 #if 0
2955         || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
2956 #endif
2957     }
2958     int pos1 = c1->windowRole().indexOf('#');
2959     int pos2 = c2->windowRole().indexOf('#');
2960     if ((pos1 >= 0 && pos2 >= 0)) {
2961         if (!active_hack) { // without the active hack for focus stealing prevention,
2962             return c1 == c2; // different mainwindows are always different apps
2963         }
2964         if (!c1->isActive() && !c2->isActive()) {
2965             return c1 == c2;
2966         } else {
2967             return true;
2968         }
2969     }
2970     return true;
2971 }
2972 
2973 /*
2974 
2975  Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
2976 
2977  WM_TRANSIENT_FOR is basically means "this is my mainwindow".
2978  For NET::Unknown windows, transient windows are considered to be NET::Dialog
2979  windows, for compatibility with non-NETWM clients. KWin may adjust the value
2980  of this property in some cases (window pointing to itself or creating a loop,
2981  keeping NET::Splash windows above other windows from the same app, etc.).
2982 
2983  X11Window::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
2984  possibly being adjusted by KWin. X11Window::transient_for points to the Client
2985  this Client is transient for, or is NULL. If X11Window::transient_for_id is
2986  poiting to the root window, the window is considered to be transient
2987  for the whole window group, as suggested in NETWM 7.3.
2988 
2989  In the case of group transient window, X11Window::transient_for is NULL,
2990  and X11Window::groupTransient() returns true. Such window is treated as
2991  if it were transient for every window in its window group that has been
2992  mapped _before_ it (or, to be exact, was added to the same group before it).
2993  Otherwise two group transients can create loops, which can lead very very
2994  nasty things (bug #67914 and all its dupes).
2995 
2996  X11Window::original_transient_for_id is the value of the property, which
2997  may be different if X11Window::transient_for_id if e.g. forcing NET::Splash
2998  to be kept on top of its window group, or when the mainwindow is not mapped
2999  yet, in which case the window is temporarily made group transient,
3000  and when the mainwindow is mapped, transiency is re-evaluated.
3001 
3002  This can get a bit complicated with with e.g. two Konqueror windows created
3003  by the same process. They should ideally appear like two independent applications
3004  to the user. This should be accomplished by all windows in the same process
3005  having the same window group (needs to be changed in Qt at the moment), and
3006  using non-group transients poiting to their relevant mainwindow for toolwindows
3007  etc. KWin should handle both group and non-group transient dialogs well.
3008 
3009  In other words:
3010  - non-transient windows     : isTransient() == false
3011  - normal transients         : transientFor() != NULL
3012  - group transients          : groupTransient() == true
3013 
3014  - list of mainwindows       : mainClients()  (call once and loop over the result)
3015  - list of transients        : transients()
3016  - every window in the group : group()->members()
3017 */
3018 
3019 Xcb::TransientFor X11Window::fetchTransient() const
3020 {
3021     return Xcb::TransientFor(window());
3022 }
3023 
3024 void X11Window::readTransientProperty(Xcb::TransientFor &transientFor)
3025 {
3026     xcb_window_t new_transient_for_id = XCB_WINDOW_NONE;
3027     if (transientFor.getTransientFor(&new_transient_for_id)) {
3028         m_originalTransientForId = new_transient_for_id;
3029         new_transient_for_id = verifyTransientFor(new_transient_for_id, true);
3030     } else {
3031         m_originalTransientForId = XCB_WINDOW_NONE;
3032         new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false);
3033     }
3034     setTransient(new_transient_for_id);
3035 }
3036 
3037 void X11Window::readTransient()
3038 {
3039     Xcb::TransientFor transientFor = fetchTransient();
3040     readTransientProperty(transientFor);
3041 }
3042 
3043 void X11Window::setTransient(xcb_window_t new_transient_for_id)
3044 {
3045     if (new_transient_for_id != m_transientForId) {
3046         removeFromMainClients();
3047         X11Window *transient_for = nullptr;
3048         m_transientForId = new_transient_for_id;
3049         if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) {
3050             transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId);
3051             Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this
3052             transient_for->addTransient(this);
3053         } // checkGroup() will check 'check_active_modal'
3054         setTransientFor(transient_for);
3055         checkGroup(nullptr, true); // force, because transiency has changed
3056         updateLayer();
3057         workspace()->resetUpdateToolWindowsTimer();
3058         Q_EMIT transientChanged();
3059     }
3060 }
3061 
3062 void X11Window::removeFromMainClients()
3063 {
3064     if (transientFor()) {
3065         transientFor()->removeTransient(this);
3066     }
3067     if (groupTransient()) {
3068         for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) {
3069             (*it)->removeTransient(this);
3070         }
3071     }
3072 }
3073 
3074 // *sigh* this transiency handling is madness :(
3075 // This one is called when destroying/releasing a window.
3076 // It makes sure this client is removed from all grouping
3077 // related lists.
3078 void X11Window::cleanGrouping()
3079 {
3080     //    qDebug() << "CLEANGROUPING:" << this;
3081     //    for ( auto it = group()->members().begin();
3082     //         it != group()->members().end();
3083     //         ++it )
3084     //        qDebug() << "CL:" << *it;
3085     //    QList<X11Window *> mains;
3086     //    mains = mainClients();
3087     //    for ( auto it = mains.begin();
3088     //         it != mains.end();
3089     //         ++it )
3090     //        qDebug() << "MN:" << *it;
3091     removeFromMainClients();
3092     //    qDebug() << "CLEANGROUPING2:" << this;
3093     //    for ( auto it = group()->members().begin();
3094     //         it != group()->members().end();
3095     //         ++it )
3096     //        qDebug() << "CL2:" << *it;
3097     //    mains = mainClients();
3098     //    for ( auto it = mains.begin();
3099     //         it != mains.end();
3100     //         ++it )
3101     //        qDebug() << "MN2:" << *it;
3102     for (auto it = transients().constBegin(); it != transients().constEnd();) {
3103         if ((*it)->transientFor() == this) {
3104             removeTransient(*it);
3105             it = transients().constBegin(); // restart, just in case something more has changed with the list
3106         } else {
3107             ++it;
3108         }
3109     }
3110     //    qDebug() << "CLEANGROUPING3:" << this;
3111     //    for ( auto it = group()->members().begin();
3112     //         it != group()->members().end();
3113     //         ++it )
3114     //        qDebug() << "CL3:" << *it;
3115     //    mains = mainClients();
3116     //    for ( auto it = mains.begin();
3117     //         it != mains.end();
3118     //         ++it )
3119     //        qDebug() << "MN3:" << *it;
3120     // HACK
3121     // removeFromMainClients() did remove 'this' from transient
3122     // lists of all group members, but then made windows that
3123     // were transient for 'this' group transient, which again
3124     // added 'this' to those transient lists :(
3125     QList<X11Window *> group_members = group()->members();
3126     group()->removeMember(this);
3127     in_group = nullptr;
3128     for (auto it = group_members.constBegin(); it != group_members.constEnd(); ++it) {
3129         (*it)->removeTransient(this);
3130     }
3131     //    qDebug() << "CLEANGROUPING4:" << this;
3132     //    for ( auto it = group_members.begin();
3133     //         it != group_members.end();
3134     //         ++it )
3135     //        qDebug() << "CL4:" << *it;
3136     m_transientForId = XCB_WINDOW_NONE;
3137 }
3138 
3139 // Make sure that no group transient is considered transient
3140 // for a window that is (directly or indirectly) transient for it
3141 // (including another group transients).
3142 // Non-group transients not causing loops are checked in verifyTransientFor().
3143 void X11Window::checkGroupTransients()
3144 {
3145     for (auto it1 = group()->members().constBegin(); it1 != group()->members().constEnd(); ++it1) {
3146         if (!(*it1)->groupTransient()) { // check all group transients in the group
3147             continue; // TODO optimize to check only the changed ones?
3148         }
3149         for (auto it2 = group()->members().constBegin(); it2 != group()->members().constEnd(); ++it2) { // group transients can be transient only for others in the group,
3150             // so don't make them transient for the ones that are transient for it
3151             if (*it1 == *it2) {
3152                 continue;
3153             }
3154             for (Window *cl = (*it2)->transientFor(); cl != nullptr; cl = cl->transientFor()) {
3155                 if (cl == *it1) {
3156                     // don't use removeTransient(), that would modify *it2 too
3157                     (*it2)->removeTransientFromList(*it1);
3158                     continue;
3159                 }
3160             }
3161             // if *it1 and *it2 are both group transients, and are transient for each other,
3162             // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
3163             // and should be therefore on top of *it1
3164             // TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
3165             if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true)) {
3166                 (*it2)->removeTransientFromList(*it1);
3167             }
3168             // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
3169             // is added, make it transient only for W2, not for W1, because it's already indirectly
3170             // transient for it - the indirect transiency actually shouldn't break anything,
3171             // but it can lead to exponentially expensive operations (#95231)
3172             // TODO this is pretty slow as well
3173             for (auto it3 = group()->members().constBegin(); it3 != group()->members().constEnd(); ++it3) {
3174                 if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3) {
3175                     continue;
3176                 }
3177                 if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) {
3178                     if ((*it2)->hasTransient(*it3, true)) {
3179                         (*it2)->removeTransientFromList(*it1);
3180                     }
3181                     if ((*it3)->hasTransient(*it2, true)) {
3182                         (*it3)->removeTransientFromList(*it1);
3183                     }
3184                 }
3185             }
3186         }
3187     }
3188 }
3189 
3190 /**
3191  * Check that the window is not transient for itself, and similar nonsense.
3192  */
3193 xcb_window_t X11Window::verifyTransientFor(xcb_window_t new_transient_for, bool set)
3194 {
3195     xcb_window_t new_property_value = new_transient_for;
3196     // make sure splashscreens are shown above all their app's windows, even though
3197     // they're in Normal layer
3198     if (isSplash() && new_transient_for == XCB_WINDOW_NONE) {
3199         new_transient_for = kwinApp()->x11RootWindow();
3200     }
3201     if (new_transient_for == XCB_WINDOW_NONE) {
3202         if (set) { // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
3203             new_property_value = new_transient_for = kwinApp()->x11RootWindow();
3204         } else {
3205             return XCB_WINDOW_NONE;
3206         }
3207     }
3208     if (new_transient_for == window()) { // pointing to self
3209         // also fix the property itself
3210         qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself.";
3211         new_property_value = new_transient_for = kwinApp()->x11RootWindow();
3212     }
3213     //  The transient_for window may be embedded in another application,
3214     //  so kwin cannot see it. Try to find the managed client for the
3215     //  window and fix the transient_for property if possible.
3216     xcb_window_t before_search = new_transient_for;
3217     while (new_transient_for != XCB_WINDOW_NONE
3218            && new_transient_for != kwinApp()->x11RootWindow()
3219            && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) {
3220         Xcb::Tree tree(new_transient_for);
3221         if (tree.isNull()) {
3222             break;
3223         }
3224         new_transient_for = tree->parent;
3225     }
3226     if (X11Window *new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) {
3227         if (new_transient_for != before_search) {
3228             qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
3229                                << before_search << ", child of " << new_transient_for_client << ", adjusting.";
3230             new_property_value = new_transient_for; // also fix the property
3231         }
3232     } else {
3233         new_transient_for = before_search; // nice try
3234     }
3235     // loop detection
3236     // group transients cannot cause loops, because they're considered transient only for non-transient
3237     // windows in the group
3238     int count = 20;
3239     xcb_window_t loop_pos = new_transient_for;
3240     while (loop_pos != XCB_WINDOW_NONE && loop_pos != kwinApp()->x11RootWindow()) {
3241         X11Window *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos);
3242         if (pos == nullptr) {
3243             break;
3244         }
3245         loop_pos = pos->m_transientForId;
3246         if (--count == 0 || pos == this) {
3247             qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop.";
3248             new_transient_for = kwinApp()->x11RootWindow();
3249         }
3250     }
3251     if (new_transient_for != kwinApp()->x11RootWindow()
3252         && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) {
3253         // it's transient for a specific window, but that window is not mapped
3254         new_transient_for = kwinApp()->x11RootWindow();
3255     }
3256     if (new_property_value != m_originalTransientForId) {
3257         Xcb::setTransientFor(window(), new_property_value);
3258     }
3259     return new_transient_for;
3260 }
3261 
3262 void X11Window::addTransient(Window *cl)
3263 {
3264     Window::addTransient(cl);
3265     if (workspace()->mostRecentlyActivatedWindow() == this && cl->isModal()) {
3266         check_active_modal = true;
3267     }
3268     //    qDebug() << "ADDTRANS:" << this << ":" << cl;
3269     //    qDebug() << kBacktrace();
3270     //    for ( auto it = transients_list.begin();
3271     //         it != transients_list.end();
3272     //         ++it )
3273     //        qDebug() << "AT:" << (*it);
3274 }
3275 
3276 void X11Window::removeTransient(Window *cl)
3277 {
3278     //    qDebug() << "REMOVETRANS:" << this << ":" << cl;
3279     //    qDebug() << kBacktrace();
3280     // cl is transient for this, but this is going away
3281     // make cl group transient
3282     Window::removeTransient(cl);
3283     if (cl->transientFor() == this) {
3284         if (X11Window *c = dynamic_cast<X11Window *>(cl)) {
3285             c->m_transientForId = XCB_WINDOW_NONE;
3286             c->setTransientFor(nullptr); // SELI
3287             // SELI       cl->setTransient( kwinApp()->x11RootWindow());
3288             c->setTransient(XCB_WINDOW_NONE);
3289         }
3290     }
3291 }
3292 
3293 // A new window has been mapped. Check if it's not a mainwindow for this already existing window.
3294 void X11Window::checkTransient(xcb_window_t w)
3295 {
3296     if (m_originalTransientForId != w) {
3297         return;
3298     }
3299     w = verifyTransientFor(w, true);
3300     setTransient(w);
3301 }
3302 
3303 // returns true if cl is the transient_for window for this client,
3304 // or recursively the transient_for window
3305 bool X11Window::hasTransient(const Window *cl, bool indirect) const
3306 {
3307     if (const X11Window *c = dynamic_cast<const X11Window *>(cl)) {
3308         // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
3309         QList<const X11Window *> set;
3310         return hasTransientInternal(c, indirect, set);
3311     }
3312     return false;
3313 }
3314 
3315 bool X11Window::hasTransientInternal(const X11Window *cl, bool indirect, QList<const X11Window *> &set) const
3316 {
3317     if (const X11Window *t = dynamic_cast<const X11Window *>(cl->transientFor())) {
3318         if (t == this) {
3319             return true;
3320         }
3321         if (!indirect) {
3322             return false;
3323         }
3324         if (set.contains(cl)) {
3325             return false;
3326         }
3327         set.append(cl);
3328         return hasTransientInternal(t, indirect, set);
3329     }
3330     if (!cl->isTransient()) {
3331         return false;
3332     }
3333     if (group() != cl->group()) {
3334         return false;
3335     }
3336     // cl is group transient, search from top
3337     if (transients().contains(const_cast<X11Window *>(cl))) {
3338         return true;
3339     }
3340     if (!indirect) {
3341         return false;
3342     }
3343     if (set.contains(this)) {
3344         return false;
3345     }
3346     set.append(this);
3347     for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) {
3348         const X11Window *c = qobject_cast<const X11Window *>(*it);
3349         if (!c) {
3350             continue;
3351         }
3352         if (c->hasTransientInternal(cl, indirect, set)) {
3353             return true;
3354         }
3355     }
3356     return false;
3357 }
3358 
3359 QList<Window *> X11Window::mainWindows() const
3360 {
3361     if (!isTransient()) {
3362         return QList<Window *>();
3363     }
3364     if (const Window *t = transientFor()) {
3365         return QList<Window *>{const_cast<Window *>(t)};
3366     }
3367     QList<Window *> result;
3368     Q_ASSERT(group());
3369     for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) {
3370         if ((*it)->hasTransient(this, false)) {
3371             result.append(*it);
3372         }
3373     }
3374     return result;
3375 }
3376 
3377 Window *X11Window::findModal(bool allow_itself)
3378 {
3379     for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) {
3380         if (Window *ret = (*it)->findModal(true)) {
3381             return ret;
3382         }
3383     }
3384     if (isModal() && allow_itself) {
3385         return this;
3386     }
3387     return nullptr;
3388 }
3389 
3390 // X11Window::window_group only holds the contents of the hint,
3391 // but it should be used only to find the group, not for anything else
3392 // Argument is only when some specific group needs to be set.
3393 void X11Window::checkGroup(Group *set_group, bool force)
3394 {
3395     Group *old_group = in_group;
3396     if (old_group != nullptr) {
3397         old_group->ref(); // turn off automatic deleting
3398     }
3399     if (set_group != nullptr) {
3400         if (set_group != in_group) {
3401             if (in_group != nullptr) {
3402                 in_group->removeMember(this);
3403             }
3404             in_group = set_group;
3405             in_group->addMember(this);
3406         }
3407     } else if (info->groupLeader() != XCB_WINDOW_NONE) {
3408         Group *new_group = workspace()->findGroup(info->groupLeader());
3409         X11Window *t = qobject_cast<X11Window *>(transientFor());
3410         if (t != nullptr && t->group() != new_group) {
3411             // move the window to the right group (e.g. a dialog provided
3412             // by different app, but transient for this one, so make it part of that group)
3413             new_group = t->group();
3414         }
3415         if (new_group == nullptr) { // doesn't exist yet
3416             new_group = new Group(info->groupLeader());
3417         }
3418         if (new_group != in_group) {
3419             if (in_group != nullptr) {
3420                 in_group->removeMember(this);
3421             }
3422             in_group = new_group;
3423             in_group->addMember(this);
3424         }
3425     } else {
3426         if (X11Window *t = qobject_cast<X11Window *>(transientFor())) {
3427             // doesn't have window group set, but is transient for something
3428             // so make it part of that group
3429             Group *new_group = t->group();
3430             if (new_group != in_group) {
3431                 if (in_group != nullptr) {
3432                     in_group->removeMember(this);
3433                 }
3434                 in_group = t->group();
3435                 in_group->addMember(this);
3436             }
3437         } else if (groupTransient()) {
3438             // group transient which actually doesn't have a group :(
3439             // try creating group with other windows with the same client leader
3440             Group *new_group = workspace()->findClientLeaderGroup(this);
3441             if (new_group == nullptr) {
3442                 new_group = new Group(XCB_WINDOW_NONE);
3443             }
3444             if (new_group != in_group) {
3445                 if (in_group != nullptr) {
3446                     in_group->removeMember(this);
3447                 }
3448                 in_group = new_group;
3449                 in_group->addMember(this);
3450             }
3451         } else { // Not transient without a group, put it in its client leader group.
3452             // This might be stupid if grouping was used for e.g. taskbar grouping
3453             // or minimizing together the whole group, but as long as it is used
3454             // only for dialogs it's better to keep windows from one app in one group.
3455             Group *new_group = workspace()->findClientLeaderGroup(this);
3456             if (in_group != nullptr && in_group != new_group) {
3457                 in_group->removeMember(this);
3458                 in_group = nullptr;
3459             }
3460             if (new_group == nullptr) {
3461                 new_group = new Group(XCB_WINDOW_NONE);
3462             }
3463             if (in_group != new_group) {
3464                 in_group = new_group;
3465                 in_group->addMember(this);
3466             }
3467         }
3468     }
3469     if (in_group != old_group || force) {
3470         for (auto it = transients().constBegin(); it != transients().constEnd();) {
3471             auto *c = *it;
3472             // group transients in the old group are no longer transient for it
3473             if (c->groupTransient() && c->group() != group()) {
3474                 removeTransientFromList(c);
3475                 it = transients().constBegin(); // restart, just in case something more has changed with the list
3476             } else {
3477                 ++it;
3478             }
3479         }
3480         if (groupTransient()) {
3481             // no longer transient for ones in the old group
3482             if (old_group != nullptr) {
3483                 for (auto it = old_group->members().constBegin(); it != old_group->members().constEnd(); ++it) {
3484                     (*it)->removeTransient(this);
3485                 }
3486             }
3487             // and make transient for all in the new group
3488             for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) {
3489                 if (*it == this) {
3490                     break; // this means the window is only transient for windows mapped before it
3491                 }
3492                 (*it)->addTransient(this);
3493             }
3494         }
3495         // group transient splashscreens should be transient even for windows
3496         // in group mapped later
3497         for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) {
3498             if (!(*it)->isSplash()) {
3499                 continue;
3500             }
3501             if (!(*it)->groupTransient()) {
3502                 continue;
3503             }
3504             if (*it == this || hasTransient(*it, true)) { // TODO indirect?
3505                 continue;
3506             }
3507             addTransient(*it);
3508         }
3509     }
3510     if (old_group != nullptr) {
3511         old_group->deref(); // can be now deleted if empty
3512     }
3513     checkGroupTransients();
3514     checkActiveModal();
3515     updateLayer();
3516 }
3517 
3518 // used by Workspace::findClientLeaderGroup()
3519 void X11Window::changeClientLeaderGroup(Group *gr)
3520 {
3521     // transientFor() != NULL are in the group of their mainwindow, so keep them there
3522     if (transientFor() != nullptr) {
3523         return;
3524     }
3525     // also don't change the group for window which have group set
3526     if (info->groupLeader()) {
3527         return;
3528     }
3529     checkGroup(gr); // change group
3530 }
3531 
3532 bool X11Window::check_active_modal = false;
3533 
3534 void X11Window::checkActiveModal()
3535 {
3536     // if the active window got new modal transient, activate it.
3537     // cannot be done in AddTransient(), because there may temporarily
3538     // exist loops, breaking findModal
3539     X11Window *check_modal = dynamic_cast<X11Window *>(workspace()->mostRecentlyActivatedWindow());
3540     if (check_modal != nullptr && check_modal->check_active_modal) {
3541         X11Window *new_modal = dynamic_cast<X11Window *>(check_modal->findModal());
3542         if (new_modal != nullptr && new_modal != check_modal) {
3543             if (!new_modal->isManaged()) {
3544                 return; // postpone check until end of manage()
3545             }
3546             workspace()->activateWindow(new_modal);
3547         }
3548         check_modal->check_active_modal = false;
3549     }
3550 }
3551 
3552 QSizeF X11Window::constrainClientSize(const QSizeF &size, SizeMode mode) const
3553 {
3554     qreal w = size.width();
3555     qreal h = size.height();
3556 
3557     if (w < 1) {
3558         w = 1;
3559     }
3560     if (h < 1) {
3561         h = 1;
3562     }
3563 
3564     // basesize, minsize, maxsize, paspect and resizeinc have all values defined,
3565     // even if they're not set in flags - see getWmNormalHints()
3566     QSizeF min_size = minSize();
3567     QSizeF max_size = maxSize();
3568     if (isDecorated()) {
3569         QSizeF decominsize(0, 0);
3570         QSizeF border_size(borderLeft() + borderRight(), borderTop() + borderBottom());
3571         if (border_size.width() > decominsize.width()) { // just in case
3572             decominsize.setWidth(border_size.width());
3573         }
3574         if (border_size.height() > decominsize.height()) {
3575             decominsize.setHeight(border_size.height());
3576         }
3577         if (decominsize.width() > min_size.width()) {
3578             min_size.setWidth(decominsize.width());
3579         }
3580         if (decominsize.height() > min_size.height()) {
3581             min_size.setHeight(decominsize.height());
3582         }
3583     }
3584     w = std::min(max_size.width(), w);
3585     h = std::min(max_size.height(), h);
3586     w = std::max(min_size.width(), w);
3587     h = std::max(min_size.height(), h);
3588 
3589     if (!rules()->checkStrictGeometry(!isFullScreen())) {
3590         // Disobey increments and aspect by explicit rule.
3591         return QSizeF(w, h);
3592     }
3593 
3594     qreal width_inc = m_geometryHints.resizeIncrements().width();
3595     qreal height_inc = m_geometryHints.resizeIncrements().height();
3596     qreal basew_inc = m_geometryHints.baseSize().width();
3597     qreal baseh_inc = m_geometryHints.baseSize().height();
3598     if (!m_geometryHints.hasBaseSize()) {
3599         basew_inc = m_geometryHints.minSize().width();
3600         baseh_inc = m_geometryHints.minSize().height();
3601     }
3602 
3603     w = std::floor((w - basew_inc) / width_inc) * width_inc + basew_inc;
3604     h = std::floor((h - baseh_inc) / height_inc) * height_inc + baseh_inc;
3605 
3606     // code for aspect ratios based on code from FVWM
3607     /*
3608      * The math looks like this:
3609      *
3610      * minAspectX    dwidth     maxAspectX
3611      * ---------- <= ------- <= ----------
3612      * minAspectY    dheight    maxAspectY
3613      *
3614      * If that is multiplied out, then the width and height are
3615      * invalid in the following situations:
3616      *
3617      * minAspectX * dheight > minAspectY * dwidth
3618      * maxAspectX * dheight < maxAspectY * dwidth
3619      *
3620      */
3621     if (m_geometryHints.hasAspect()) {
3622         double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT
3623         double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise
3624         double max_aspect_w = m_geometryHints.maxAspect().width();
3625         double max_aspect_h = m_geometryHints.maxAspect().height();
3626         // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments,
3627         // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time,
3628         // and I have no idea how it works, let's hope nobody relies on that.
3629         const QSizeF baseSize = m_geometryHints.baseSize();
3630         w -= baseSize.width();
3631         h -= baseSize.height();
3632         qreal max_width = max_size.width() - baseSize.width();
3633         qreal min_width = min_size.width() - baseSize.width();
3634         qreal max_height = max_size.height() - baseSize.height();
3635         qreal min_height = min_size.height() - baseSize.height();
3636 #define ASPECT_CHECK_GROW_W                                                           \
3637     if (min_aspect_w * h > min_aspect_h * w) {                                        \
3638         int delta = int(min_aspect_w * h / min_aspect_h - w) / width_inc * width_inc; \
3639         if (w + delta <= max_width)                                                   \
3640             w += delta;                                                               \
3641     }
3642 #define ASPECT_CHECK_SHRINK_H_GROW_W                                                      \
3643     if (min_aspect_w * h > min_aspect_h * w) {                                            \
3644         int delta = int(h - w * min_aspect_h / min_aspect_w) / height_inc * height_inc;   \
3645         if (h - delta >= min_height)                                                      \
3646             h -= delta;                                                                   \
3647         else {                                                                            \
3648             int delta = int(min_aspect_w * h / min_aspect_h - w) / width_inc * width_inc; \
3649             if (w + delta <= max_width)                                                   \
3650                 w += delta;                                                               \
3651         }                                                                                 \
3652     }
3653 #define ASPECT_CHECK_GROW_H                                                             \
3654     if (max_aspect_w * h < max_aspect_h * w) {                                          \
3655         int delta = int(w * max_aspect_h / max_aspect_w - h) / height_inc * height_inc; \
3656         if (h + delta <= max_height)                                                    \
3657             h += delta;                                                                 \
3658     }
3659 #define ASPECT_CHECK_SHRINK_W_GROW_H                                                        \
3660     if (max_aspect_w * h < max_aspect_h * w) {                                              \
3661         int delta = int(w - max_aspect_w * h / max_aspect_h) / width_inc * width_inc;       \
3662         if (w - delta >= min_width)                                                         \
3663             w -= delta;                                                                     \
3664         else {                                                                              \
3665             int delta = int(w * max_aspect_h / max_aspect_w - h) / height_inc * height_inc; \
3666             if (h + delta <= max_height)                                                    \
3667                 h += delta;                                                                 \
3668         }                                                                                   \
3669     }
3670         switch (mode) {
3671         case SizeModeAny:
3672 #if 0 // make SizeModeAny equal to SizeModeFixedW - prefer keeping fixed width,
3673       // so that changing aspect ratio to a different value and back keeps the same size (#87298)
3674             {
3675                 ASPECT_CHECK_SHRINK_H_GROW_W
3676                 ASPECT_CHECK_SHRINK_W_GROW_H
3677                 ASPECT_CHECK_GROW_H
3678                 ASPECT_CHECK_GROW_W
3679                 break;
3680             }
3681 #endif
3682         case SizeModeFixedW: {
3683             // the checks are order so that attempts to modify height are first
3684             ASPECT_CHECK_GROW_H
3685             ASPECT_CHECK_SHRINK_H_GROW_W
3686             ASPECT_CHECK_SHRINK_W_GROW_H
3687             ASPECT_CHECK_GROW_W
3688             break;
3689         }
3690         case SizeModeFixedH: {
3691             ASPECT_CHECK_GROW_W
3692             ASPECT_CHECK_SHRINK_W_GROW_H
3693             ASPECT_CHECK_SHRINK_H_GROW_W
3694             ASPECT_CHECK_GROW_H
3695             break;
3696         }
3697         case SizeModeMax: {
3698             // first checks that try to shrink
3699             ASPECT_CHECK_SHRINK_H_GROW_W
3700             ASPECT_CHECK_SHRINK_W_GROW_H
3701             ASPECT_CHECK_GROW_W
3702             ASPECT_CHECK_GROW_H
3703             break;
3704         }
3705         }
3706 #undef ASPECT_CHECK_SHRINK_H_GROW_W
3707 #undef ASPECT_CHECK_SHRINK_W_GROW_H
3708 #undef ASPECT_CHECK_GROW_W
3709 #undef ASPECT_CHECK_GROW_H
3710         w += baseSize.width();
3711         h += baseSize.height();
3712     }
3713 
3714     return QSizeF(w, h);
3715 }
3716 
3717 /**
3718  * Gets the client's normal WM hints and reconfigures itself respectively.
3719  */
3720 void X11Window::getWmNormalHints()
3721 {
3722     const bool hadFixedAspect = m_geometryHints.hasAspect();
3723     // roundtrip to X server
3724     m_geometryHints.fetch();
3725     m_geometryHints.read();
3726 
3727     if (!hadFixedAspect && m_geometryHints.hasAspect()) {
3728         // align to eventual new constraints
3729         maximize(max_mode);
3730     }
3731     if (isManaged()) {
3732         // update to match restrictions
3733         QSizeF new_size = clientSizeToFrameSize(constrainClientSize(clientSize()));
3734         if (new_size != size() && !isFullScreen()) {
3735             QRectF origClientGeometry = m_clientGeometry;
3736             moveResize(resizeWithChecks(moveResizeGeometry(), new_size));
3737             if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
3738                 // try to keep the window in its xinerama screen if possible,
3739                 // if that fails at least keep it visible somewhere
3740                 QRectF area = workspace()->clientArea(MovementArea, this, moveResizeOutput());
3741                 if (area.contains(origClientGeometry)) {
3742                     keepInArea(area);
3743                 }
3744                 area = workspace()->clientArea(WorkArea, this, moveResizeOutput());
3745                 if (area.contains(origClientGeometry)) {
3746                     keepInArea(area);
3747                 }
3748             }
3749         }
3750     }
3751     updateAllowedActions(); // affects isResizeable()
3752 }
3753 
3754 QSizeF X11Window::minSize() const
3755 {
3756     return rules()->checkMinSize(m_geometryHints.minSize());
3757 }
3758 
3759 QSizeF X11Window::maxSize() const
3760 {
3761     return rules()->checkMaxSize(m_geometryHints.maxSize());
3762 }
3763 
3764 QSizeF X11Window::basicUnit() const
3765 {
3766     return m_geometryHints.resizeIncrements();
3767 }
3768 
3769 /**
3770  * Auxiliary function to inform the client about the current window
3771  * configuration.
3772  */
3773 void X11Window::sendSyntheticConfigureNotify()
3774 {
3775     // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy
3776     // 32 unconditionally. Use a union to ensure we don't disclose stack memory.
3777     union {
3778         xcb_configure_notify_event_t event;
3779         char buffer[32];
3780     } u;
3781     static_assert(sizeof(u.event) < 32, "wouldn't need the union otherwise");
3782     memset(&u, 0, sizeof(u));
3783     xcb_configure_notify_event_t &c = u.event;
3784     u.event.response_type = XCB_CONFIGURE_NOTIFY;
3785     u.event.event = window();
3786     u.event.window = window();
3787     u.event.x = Xcb::toXNative(m_clientGeometry.x());
3788     u.event.y = Xcb::toXNative(m_clientGeometry.y());
3789     u.event.width = Xcb::toXNative(m_clientGeometry.width());
3790     u.event.height = Xcb::toXNative(m_clientGeometry.height());
3791     u.event.border_width = 0;
3792     u.event.above_sibling = XCB_WINDOW_NONE;
3793     u.event.override_redirect = 0;
3794     xcb_send_event(kwinApp()->x11Connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast<const char *>(&u));
3795     xcb_flush(kwinApp()->x11Connection());
3796 }
3797 
3798 QPointF X11Window::gravityAdjustment(xcb_gravity_t gravity) const
3799 {
3800     qreal dx = 0;
3801     qreal dy = 0;
3802 
3803     // dx, dy specify how the client window moves to make space for the frame.
3804     // In general we have to compute the reference point and from that figure
3805     // out how much we need to shift the client, however given that we ignore
3806     // the border width attribute and the extents of the server-side decoration
3807     // are known in advance, we can simplify the math quite a bit and express
3808     // the required window gravity adjustment in terms of border sizes.
3809     switch (gravity) {
3810     case XCB_GRAVITY_NORTH_WEST: // move down right
3811     default:
3812         dx = borderLeft();
3813         dy = borderTop();
3814         break;
3815     case XCB_GRAVITY_NORTH: // move right
3816         dx = 0;
3817         dy = borderTop();
3818         break;
3819     case XCB_GRAVITY_NORTH_EAST: // move down left
3820         dx = -borderRight();
3821         dy = borderTop();
3822         break;
3823     case XCB_GRAVITY_WEST: // move right
3824         dx = borderLeft();
3825         dy = 0;
3826         break;
3827     case XCB_GRAVITY_CENTER:
3828         dx = (borderLeft() - borderRight()) / 2;
3829         dy = (borderTop() - borderBottom()) / 2;
3830         break;
3831     case XCB_GRAVITY_STATIC: // don't move
3832         dx = 0;
3833         dy = 0;
3834         break;
3835     case XCB_GRAVITY_EAST: // move left
3836         dx = -borderRight();
3837         dy = 0;
3838         break;
3839     case XCB_GRAVITY_SOUTH_WEST: // move up right
3840         dx = borderLeft();
3841         dy = -borderBottom();
3842         break;
3843     case XCB_GRAVITY_SOUTH: // move up
3844         dx = 0;
3845         dy = -borderBottom();
3846         break;
3847     case XCB_GRAVITY_SOUTH_EAST: // move up left
3848         dx = -borderRight();
3849         dy = -borderBottom();
3850         break;
3851     }
3852 
3853     return QPoint(dx, dy);
3854 }
3855 
3856 const QPointF X11Window::calculateGravitation(bool invert) const
3857 {
3858     const QPointF adjustment = gravityAdjustment(m_geometryHints.windowGravity());
3859 
3860     // translate from client movement to frame movement
3861     const qreal dx = adjustment.x() - borderLeft();
3862     const qreal dy = adjustment.y() - borderTop();
3863 
3864     if (!invert) {
3865         return QPointF(x() + dx, y() + dy);
3866     } else {
3867         return QPointF(x() - dx, y() - dy);
3868     }
3869 }
3870 
3871 // co-ordinate are in kwin logical
3872 void X11Window::configureRequest(int value_mask, qreal rx, qreal ry, qreal rw, qreal rh, int gravity, bool from_tool)
3873 {
3874     const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
3875     const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
3876     const int configureGeometryMask = configurePositionMask | configureSizeMask;
3877 
3878     // "maximized" is a user setting -> we do not allow the client to resize itself
3879     // away from this & against the users explicit wish
3880     qCDebug(KWIN_CORE) << this << bool(value_mask & configureGeometryMask) << bool(maximizeMode() & MaximizeVertical) << bool(maximizeMode() & MaximizeHorizontal);
3881 
3882     // we want to (partially) ignore the request when the window is somehow maximized or quicktiled
3883     bool ignore = !app_noborder && (quickTileMode() != QuickTileMode(QuickTileFlag::None) || maximizeMode() != MaximizeRestore);
3884     // however, the user shall be able to force obedience despite and also disobedience in general
3885     ignore = rules()->checkIgnoreGeometry(ignore);
3886     if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it.
3887         updateQuickTileMode(QuickTileFlag::None);
3888         max_mode = MaximizeRestore;
3889         Q_EMIT quickTileModeChanged();
3890     } else if (!app_noborder && quickTileMode() == QuickTileMode(QuickTileFlag::None) && (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) {
3891         // ignoring can be, because either we do, or the user does explicitly not want it.
3892         // for partially maximized windows we want to allow configures in the other dimension.
3893         // so we've to ask the user again - to know whether we just ignored for the partial maximization.
3894         // the problem here is, that the user can explicitly permit configure requests - even for maximized windows!
3895         // we cannot distinguish that from passing "false" for partially maximized windows.
3896         ignore = rules()->checkIgnoreGeometry(false);
3897         if (!ignore) { // the user is not interested, so we fix up dimensions
3898             if (maximizeMode() == MaximizeVertical) {
3899                 value_mask &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT);
3900             }
3901             if (maximizeMode() == MaximizeHorizontal) {
3902                 value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH);
3903             }
3904             if (!(value_mask & configureGeometryMask)) {
3905                 ignore = true; // the modification turned the request void
3906             }
3907         }
3908     }
3909 
3910     if (ignore) {
3911         qCDebug(KWIN_CORE) << "DENIED";
3912         return; // nothing to (left) to do for use - bugs #158974, #252314, #321491
3913     }
3914 
3915     qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & configureGeometryMask);
3916 
3917     if (gravity == 0) { // default (nonsense) value for the argument
3918         gravity = m_geometryHints.windowGravity();
3919     }
3920     if (value_mask & configurePositionMask) {
3921         QPointF new_pos = framePosToClientPos(pos());
3922         new_pos -= gravityAdjustment(xcb_gravity_t(gravity));
3923         if (value_mask & XCB_CONFIG_WINDOW_X) {
3924             new_pos.setX(rx);
3925         }
3926         if (value_mask & XCB_CONFIG_WINDOW_Y) {
3927             new_pos.setY(ry);
3928         }
3929         new_pos += gravityAdjustment(xcb_gravity_t(gravity));
3930         new_pos = clientPosToFramePos(new_pos);
3931 
3932         qreal nw = clientSize().width();
3933         qreal nh = clientSize().height();
3934         if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
3935             nw = rw;
3936         }
3937         if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
3938             nh = rh;
3939         }
3940         const QSizeF requestedClientSize = constrainClientSize(QSizeF(nw, nh));
3941         QSizeF requestedFrameSize = clientSizeToFrameSize(requestedClientSize);
3942         requestedFrameSize = rules()->checkSize(requestedFrameSize);
3943         new_pos = rules()->checkPosition(new_pos);
3944 
3945         Output *newOutput = workspace()->outputAt(QRectF(new_pos, requestedFrameSize).center());
3946         if (newOutput != rules()->checkOutput(newOutput)) {
3947             return; // not allowed by rule
3948         }
3949 
3950         QRectF origClientGeometry = m_clientGeometry;
3951         GeometryUpdatesBlocker blocker(this);
3952         move(new_pos);
3953         resize(requestedFrameSize);
3954         QRectF area = workspace()->clientArea(WorkArea, this, moveResizeOutput());
3955         if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()
3956             && area.contains(origClientGeometry)) {
3957             keepInArea(area);
3958         }
3959 
3960         // this is part of the kicker-xinerama-hack... it should be
3961         // safe to remove when kicker gets proper ExtendedStrut support;
3962         // see Workspace::updateClientArea() and
3963         // X11Window::adjustedClientArea()
3964         if (hasStrut()) {
3965             workspace()->updateClientArea();
3966         }
3967     }
3968 
3969     if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) { // pure resize
3970         qreal nw = clientSize().width();
3971         qreal nh = clientSize().height();
3972         if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
3973             nw = rw;
3974         }
3975         if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
3976             nh = rh;
3977         }
3978 
3979         const QSizeF requestedClientSize = constrainClientSize(QSizeF(nw, nh));
3980         QSizeF requestedFrameSize = clientSizeToFrameSize(requestedClientSize);
3981         requestedFrameSize = rules()->checkSize(requestedFrameSize);
3982 
3983         if (requestedFrameSize != size()) { // don't restore if some app sets its own size again
3984             QRectF origClientGeometry = m_clientGeometry;
3985             GeometryUpdatesBlocker blocker(this);
3986             moveResize(resizeWithChecks(moveResizeGeometry(), requestedFrameSize, xcb_gravity_t(gravity)));
3987             if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
3988                 // try to keep the window in its xinerama screen if possible,
3989                 // if that fails at least keep it visible somewhere
3990                 QRectF area = workspace()->clientArea(MovementArea, this, moveResizeOutput());
3991                 if (area.contains(origClientGeometry)) {
3992                     keepInArea(area);
3993                 }
3994                 area = workspace()->clientArea(WorkArea, this, moveResizeOutput());
3995                 if (area.contains(origClientGeometry)) {
3996                     keepInArea(area);
3997                 }
3998             }
3999         }
4000     }
4001     // No need to send synthetic configure notify event here, either it's sent together
4002     // with geometry change, or there's no need to send it.
4003     // Handling of the real ConfigureRequest event forces sending it, as there it's necessary.
4004 }
4005 
4006 QRectF X11Window::resizeWithChecks(const QRectF &geometry, qreal w, qreal h, xcb_gravity_t gravity)
4007 {
4008     Q_ASSERT(!shade_geometry_change);
4009     if (isShade()) {
4010         if (h == borderTop() + borderBottom()) {
4011             qCWarning(KWIN_CORE) << "Shaded geometry passed for size:";
4012         }
4013     }
4014     qreal newx = geometry.x();
4015     qreal newy = geometry.y();
4016     QRectF area = workspace()->clientArea(WorkArea, this, geometry.center());
4017     // don't allow growing larger than workarea
4018     if (w > area.width()) {
4019         w = area.width();
4020     }
4021     if (h > area.height()) {
4022         h = area.height();
4023     }
4024     QSizeF tmp = constrainFrameSize(QSizeF(w, h)); // checks size constraints, including min/max size
4025     w = tmp.width();
4026     h = tmp.height();
4027     if (gravity == 0) {
4028         gravity = m_geometryHints.windowGravity();
4029     }
4030     switch (gravity) {
4031     case XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move
4032     default:
4033         break;
4034     case XCB_GRAVITY_NORTH: // middle of top border doesn't move
4035         newx = (newx + geometry.width() / 2) - (w / 2);
4036         break;
4037     case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move
4038         newx = newx + geometry.width() - w;
4039         break;
4040     case XCB_GRAVITY_WEST: // middle of left border doesn't move
4041         newy = (newy + geometry.height() / 2) - (h / 2);
4042         break;
4043     case XCB_GRAVITY_CENTER: // middle point doesn't move
4044         newx = (newx + geometry.width() / 2) - (w / 2);
4045         newy = (newy + geometry.height() / 2) - (h / 2);
4046         break;
4047     case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move
4048         // since decoration doesn't change, equal to NorthWestGravity
4049         break;
4050     case XCB_GRAVITY_EAST: // // middle of right border doesn't move
4051         newx = newx + geometry.width() - w;
4052         newy = (newy + geometry.height() / 2) - (h / 2);
4053         break;
4054     case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move
4055         newy = newy + geometry.height() - h;
4056         break;
4057     case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move
4058         newx = (newx + geometry.width() / 2) - (w / 2);
4059         newy = newy + geometry.height() - h;
4060         break;
4061     case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move
4062         newx = newx + geometry.width() - w;
4063         newy = newy + geometry.height() - h;
4064         break;
4065     }
4066     return QRectF{newx, newy, w, h};
4067 }
4068 
4069 // _NET_MOVERESIZE_WINDOW
4070 // note co-ordinates are kwin logical
4071 void X11Window::NETMoveResizeWindow(int flags, qreal x, qreal y, qreal width, qreal height)
4072 {
4073     int gravity = flags & 0xff;
4074     int value_mask = 0;
4075     if (flags & (1 << 8)) {
4076         value_mask |= XCB_CONFIG_WINDOW_X;
4077     }
4078     if (flags & (1 << 9)) {
4079         value_mask |= XCB_CONFIG_WINDOW_Y;
4080     }
4081     if (flags & (1 << 10)) {
4082         value_mask |= XCB_CONFIG_WINDOW_WIDTH;
4083     }
4084     if (flags & (1 << 11)) {
4085         value_mask |= XCB_CONFIG_WINDOW_HEIGHT;
4086     }
4087     configureRequest(value_mask, x, y, width, height, gravity, true);
4088 }
4089 
4090 // _GTK_SHOW_WINDOW_MENU
4091 void X11Window::GTKShowWindowMenu(qreal x_root, qreal y_root)
4092 {
4093     QPoint globalPos(x_root, y_root);
4094     workspace()->showWindowMenu(QRect(globalPos, globalPos), this);
4095 }
4096 
4097 bool X11Window::isMovable() const
4098 {
4099     if (!hasNETSupport() && !m_motif.move()) {
4100         return false;
4101     }
4102     if (isFullScreen()) {
4103         return false;
4104     }
4105     if (isSpecialWindow() && !isSplash() && !isToolbar()) { // allow moving of splashscreens :)
4106         return false;
4107     }
4108     if (rules()->checkPosition(invalidPoint) != invalidPoint) { // forced position
4109         return false;
4110     }
4111     return true;
4112 }
4113 
4114 bool X11Window::isMovableAcrossScreens() const
4115 {
4116     if (!hasNETSupport() && !m_motif.move()) {
4117         return false;
4118     }
4119     if (isSpecialWindow() && !isSplash() && !isToolbar()) { // allow moving of splashscreens :)
4120         return false;
4121     }
4122     if (rules()->checkPosition(invalidPoint) != invalidPoint) { // forced position
4123         return false;
4124     }
4125     return true;
4126 }
4127 
4128 bool X11Window::isResizable() const
4129 {
4130     if (!hasNETSupport() && !m_motif.resize()) {
4131         return false;
4132     }
4133     if (isFullScreen()) {
4134         return false;
4135     }
4136     if (isSpecialWindow() || isSplash() || isToolbar()) {
4137         return false;
4138     }
4139     if (rules()->checkSize(QSize()).isValid()) { // forced size
4140         return false;
4141     }
4142     const Gravity gravity = interactiveMoveResizeGravity();
4143     if ((gravity == Gravity::Top || gravity == Gravity::TopLeft || gravity == Gravity::TopRight || gravity == Gravity::Left || gravity == Gravity::BottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint) {
4144         return false;
4145     }
4146 
4147     QSizeF min = minSize();
4148     QSizeF max = maxSize();
4149     return min.width() < max.width() || min.height() < max.height();
4150 }
4151 
4152 bool X11Window::isMaximizable() const
4153 {
4154     if (!isResizable() || isToolbar()) { // SELI isToolbar() ?
4155         return false;
4156     }
4157     if (isAppletPopup()) {
4158         return false;
4159     }
4160     if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore) {
4161         return true;
4162     }
4163     return false;
4164 }
4165 
4166 /**
4167  * Reimplemented to inform the client about the new window position.
4168  */
4169 void X11Window::moveResizeInternal(const QRectF &rect, MoveResizeMode mode)
4170 {
4171     // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry,
4172     // simply because there are too many places dealing with geometry. Those places
4173     // ignore shaded state and use normal geometry, which they usually should get
4174     // from adjustedSize(). Such geometry comes here, and if the window is shaded,
4175     // the geometry is used only for client_size, since that one is not used when
4176     // shading. Then the frame geometry is adjusted for the shaded geometry.
4177     // This gets more complicated in the case the code does only something like
4178     // setGeometry( geometry()) - geometry() will return the shaded frame geometry.
4179     // Such code is wrong and should be changed to handle the case when the window is shaded,
4180     // for example using X11Window::clientSize()
4181 
4182     QRectF frameGeometry = Xcb::fromXNative(Xcb::toXNative(rect));
4183 
4184     if (shade_geometry_change) {
4185         ; // nothing
4186     } else if (isShade()) {
4187         if (frameGeometry.height() == borderTop() + borderBottom()) {
4188             qCDebug(KWIN_CORE) << "Shaded geometry passed for size:";
4189         } else {
4190             m_clientGeometry = frameRectToClientRect(frameGeometry);
4191             frameGeometry.setHeight(borderTop() + borderBottom());
4192         }
4193     } else {
4194         m_clientGeometry = frameRectToClientRect(frameGeometry);
4195     }
4196     m_frameGeometry = frameGeometry;
4197     m_bufferGeometry = frameRectToBufferRect(frameGeometry);
4198 
4199     if (pendingMoveResizeMode() == MoveResizeMode::None && m_lastBufferGeometry == m_bufferGeometry && m_lastFrameGeometry == m_frameGeometry && m_lastClientGeometry == m_clientGeometry) {
4200         return;
4201     }
4202 
4203     m_output = workspace()->outputAt(frameGeometry.center());
4204     if (areGeometryUpdatesBlocked()) {
4205         setPendingMoveResizeMode(mode);
4206         return;
4207     }
4208 
4209     Q_EMIT frameGeometryAboutToChange(this);
4210     const QRectF oldBufferGeometry = m_lastBufferGeometry;
4211     const QRectF oldFrameGeometry = m_lastFrameGeometry;
4212     const QRectF oldClientGeometry = m_lastClientGeometry;
4213     const Output *oldOutput = m_lastOutput;
4214 
4215     updateServerGeometry();
4216     updateWindowRules(Rules::Position | Rules::Size);
4217 
4218     m_lastBufferGeometry = m_bufferGeometry;
4219     m_lastFrameGeometry = m_frameGeometry;
4220     m_lastClientGeometry = m_clientGeometry;
4221     m_lastOutput = m_output;
4222 
4223     if (isActive()) {
4224         workspace()->setActiveOutput(output());
4225     }
4226     workspace()->updateStackingOrder();
4227 
4228     if (oldBufferGeometry != m_bufferGeometry) {
4229         Q_EMIT bufferGeometryChanged(this, oldBufferGeometry);
4230     }
4231     if (oldClientGeometry != m_clientGeometry) {
4232         Q_EMIT clientGeometryChanged(this, oldClientGeometry);
4233     }
4234     if (oldFrameGeometry != m_frameGeometry) {
4235         Q_EMIT frameGeometryChanged(this, oldFrameGeometry);
4236     }
4237     if (oldOutput != m_output) {
4238         Q_EMIT screenChanged();
4239     }
4240     Q_EMIT geometryShapeChanged(this, oldFrameGeometry);
4241 }
4242 
4243 void X11Window::updateServerGeometry()
4244 {
4245     const QRectF oldBufferGeometry = m_lastBufferGeometry;
4246 
4247     // Compute the old client rect, the client geometry is always inside the buffer geometry.
4248     QRectF oldClientRect = m_lastClientGeometry;
4249     oldClientRect.translate(-m_lastBufferGeometry.topLeft());
4250 
4251     if (oldBufferGeometry.size() != m_bufferGeometry.size() || oldClientRect != QRectF(clientPos(), clientSize())) {
4252         resizeDecoration();
4253         // If the client is being interactively resized, then the frame window, the wrapper window,
4254         // and the client window have correct geometry at this point, so we don't have to configure
4255         // them again.
4256         if (m_frame.geometry() != m_bufferGeometry) {
4257             m_frame.setGeometry(m_bufferGeometry);
4258         }
4259         if (!isShade()) {
4260             const QRectF requestedWrapperGeometry(clientPos(), clientSize());
4261             if (m_wrapper.geometry() != requestedWrapperGeometry) {
4262                 m_wrapper.setGeometry(requestedWrapperGeometry);
4263             }
4264             const QRectF requestedClientGeometry(QPoint(0, 0), clientSize());
4265             if (m_client.geometry() != requestedClientGeometry) {
4266                 m_client.setGeometry(requestedClientGeometry);
4267             }
4268             // SELI - won't this be too expensive?
4269             // THOMAS - yes, but gtk+ clients will not resize without ...
4270             sendSyntheticConfigureNotify();
4271         }
4272         updateShape();
4273     } else {
4274         m_frame.move(m_bufferGeometry.topLeft());
4275         sendSyntheticConfigureNotify();
4276         // Unconditionally move the input window: it won't affect rendering
4277         m_decoInputExtent.move(pos().toPoint() + inputPos());
4278     }
4279 }
4280 
4281 static bool changeMaximizeRecursion = false;
4282 void X11Window::maximize(MaximizeMode mode)
4283 {
4284     if (changeMaximizeRecursion) {
4285         return;
4286     }
4287 
4288     if (!isResizable() || isToolbar()) { // SELI isToolbar() ?
4289         return;
4290     }
4291     if (!isMaximizable()) {
4292         return;
4293     }
4294 
4295     QRectF clientArea;
4296     if (isElectricBorderMaximizing()) {
4297         clientArea = workspace()->clientArea(MaximizeArea, this, Cursors::self()->mouse()->pos());
4298     } else {
4299         clientArea = workspace()->clientArea(MaximizeArea, this, moveResizeOutput());
4300     }
4301 
4302     MaximizeMode old_mode = max_mode;
4303 
4304     // if the client insist on a fix aspect ratio, we check whether the maximizing will get us
4305     // out of screen bounds and take that as a "full maximization with aspect check" then
4306     if (m_geometryHints.hasAspect() && // fixed aspect
4307         (mode == MaximizeVertical || mode == MaximizeHorizontal) && // ondimensional maximization
4308         rules()->checkStrictGeometry(true)) { // obey aspect
4309         const QSize minAspect = m_geometryHints.minAspect();
4310         const QSize maxAspect = m_geometryHints.maxAspect();
4311         if (mode == MaximizeVertical || (old_mode & MaximizeVertical)) {
4312             const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT
4313             const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT
4314             if (fx * clientArea.height() / fy > clientArea.width()) { // too big
4315                 mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull;
4316             }
4317         } else { // mode == MaximizeHorizontal
4318             const double fx = maxAspect.width();
4319             const double fy = minAspect.height();
4320             if (fy * clientArea.width() / fx > clientArea.height()) { // too big
4321                 mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull;
4322             }
4323         }
4324     }
4325 
4326     mode = rules()->checkMaximize(mode);
4327     if (max_mode == mode) {
4328         return;
4329     }
4330 
4331     blockGeometryUpdates(true);
4332 
4333     // maximing one way and unmaximizing the other way shouldn't happen,
4334     // so restore first and then maximize the other way
4335     if ((old_mode == MaximizeVertical && mode == MaximizeHorizontal)
4336         || (old_mode == MaximizeHorizontal && mode == MaximizeVertical)) {
4337         maximize(MaximizeRestore); // restore
4338     }
4339 
4340     Q_EMIT clientMaximizedStateAboutToChange(this, mode);
4341     max_mode = mode;
4342 
4343     // save sizes for restoring, if maximalizing
4344     QSizeF sz;
4345     if (isShade()) {
4346         sz = implicitSize();
4347     } else {
4348         sz = size();
4349     }
4350 
4351     if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
4352         QRectF savedGeometry = geometryRestore();
4353         if (!(old_mode & MaximizeVertical)) {
4354             savedGeometry.setTop(y());
4355             savedGeometry.setHeight(sz.height());
4356         }
4357         if (!(old_mode & MaximizeHorizontal)) {
4358             savedGeometry.setLeft(x());
4359             savedGeometry.setWidth(sz.width());
4360         }
4361         setGeometryRestore(savedGeometry);
4362     }
4363 
4364     // call into decoration update borders
4365     if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) {
4366         changeMaximizeRecursion = true;
4367         const auto c = decoration()->client().toStrongRef();
4368         if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) {
4369             Q_EMIT c->maximizedVerticallyChanged(max_mode & MaximizeVertical);
4370         }
4371         if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) {
4372             Q_EMIT c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal);
4373         }
4374         if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) {
4375             Q_EMIT c->maximizedChanged(max_mode == MaximizeFull);
4376         }
4377         changeMaximizeRecursion = false;
4378     }
4379 
4380     if (options->borderlessMaximizedWindows()) {
4381         // triggers a maximize change.
4382         // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry
4383         changeMaximizeRecursion = true;
4384         setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull));
4385         changeMaximizeRecursion = false;
4386     }
4387 
4388     // Conditional quick tiling exit points
4389     if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
4390         if (old_mode == MaximizeFull && !clientArea.contains(geometryRestore().center())) {
4391             // Not restoring on the same screen
4392             // TODO: The following doesn't work for some reason
4393             // quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually
4394         } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) || (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) {
4395             // Modifying geometry of a tiled window
4396             updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry
4397         }
4398     }
4399 
4400     switch (max_mode) {
4401 
4402     case MaximizeVertical: {
4403         if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull
4404             if (geometryRestore().width() == 0 || !clientArea.contains(geometryRestore().center())) {
4405                 // needs placement
4406                 resize(constrainFrameSize(QSize(width() * 2 / 3, clientArea.height()), SizeModeFixedH));
4407                 workspace()->placement()->placeSmart(this, clientArea);
4408             } else {
4409                 moveResize(QRectF(QPointF(geometryRestore().x(), clientArea.top()),
4410                                   constrainFrameSize(QSize(geometryRestore().width(), clientArea.height()), SizeModeFixedH)));
4411             }
4412         } else {
4413             QRectF r(x(), clientArea.top(), width(), clientArea.height());
4414             r.setTopLeft(rules()->checkPosition(r.topLeft()));
4415             r.setSize(constrainFrameSize(r.size(), SizeModeFixedH));
4416             moveResize(r);
4417         }
4418         info->setState(NET::MaxVert, NET::Max);
4419         break;
4420     }
4421 
4422     case MaximizeHorizontal: {
4423         if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull
4424             if (geometryRestore().height() == 0 || !clientArea.contains(geometryRestore().center())) {
4425                 // needs placement
4426                 resize(constrainFrameSize(QSize(clientArea.width(), height() * 2 / 3), SizeModeFixedW));
4427                 workspace()->placement()->placeSmart(this, clientArea);
4428             } else {
4429                 moveResize(QRectF(QPoint(clientArea.left(), geometryRestore().y()),
4430                                   constrainFrameSize(QSize(clientArea.width(), geometryRestore().height()), SizeModeFixedW)));
4431             }
4432         } else {
4433             QRectF r(clientArea.left(), y(), clientArea.width(), height());
4434             r.setTopLeft(rules()->checkPosition(r.topLeft()));
4435             r.setSize(constrainFrameSize(r.size(), SizeModeFixedW));
4436             moveResize(r);
4437         }
4438         info->setState(NET::MaxHoriz, NET::Max);
4439         break;
4440     }
4441 
4442     case MaximizeRestore: {
4443         QRectF restore = moveResizeGeometry();
4444         // when only partially maximized, geom_restore may not have the other dimension remembered
4445         if (old_mode & MaximizeVertical) {
4446             restore.setTop(geometryRestore().top());
4447             restore.setBottom(geometryRestore().bottom());
4448         }
4449         if (old_mode & MaximizeHorizontal) {
4450             restore.setLeft(geometryRestore().left());
4451             restore.setRight(geometryRestore().right());
4452         }
4453         if (!restore.isValid()) {
4454             QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3);
4455             if (geometryRestore().width() > 0) {
4456                 s.setWidth(geometryRestore().width());
4457             }
4458             if (geometryRestore().height() > 0) {
4459                 s.setHeight(geometryRestore().height());
4460             }
4461             resize(constrainFrameSize(s));
4462             workspace()->placement()->placeSmart(this, clientArea);
4463             restore = moveResizeGeometry();
4464             if (geometryRestore().width() > 0) {
4465                 restore.moveLeft(geometryRestore().x());
4466             }
4467             if (geometryRestore().height() > 0) {
4468                 restore.moveTop(geometryRestore().y());
4469             }
4470             setGeometryRestore(restore); // relevant for mouse pos calculation, bug #298646
4471         }
4472         if (m_geometryHints.hasAspect()) {
4473             restore.setSize(constrainFrameSize(restore.size(), SizeModeAny));
4474         }
4475         moveResize(restore);
4476         if (!clientArea.contains(geometryRestore().center())) { // Not restoring to the same screen
4477             workspace()->placement()->place(this, clientArea);
4478         }
4479         info->setState(NET::States(), NET::Max);
4480         updateQuickTileMode(QuickTileFlag::None);
4481         break;
4482     }
4483 
4484     case MaximizeFull: {
4485         QRectF r(clientArea);
4486         r.setTopLeft(rules()->checkPosition(r.topLeft()));
4487         r.setSize(constrainFrameSize(r.size(), SizeModeMax));
4488         if (r.size() != clientArea.size()) { // to avoid off-by-one errors...
4489             if (isElectricBorderMaximizing() && r.width() < clientArea.width()) {
4490                 r.moveLeft(std::max(clientArea.left(), Cursors::self()->mouse()->pos().x() - r.width() / 2));
4491                 r.moveRight(std::min(clientArea.right(), r.right()));
4492             } else {
4493                 r.moveCenter(clientArea.center());
4494                 const bool closeHeight = r.height() > 97 * clientArea.height() / 100;
4495                 const bool closeWidth = r.width() > 97 * clientArea.width() / 100;
4496                 const bool overHeight = r.height() > clientArea.height();
4497                 const bool overWidth = r.width() > clientArea.width();
4498                 if (closeWidth || closeHeight) {
4499                     Qt::Edge titlePos = titlebarPosition();
4500                     const QRectF screenArea = workspace()->clientArea(ScreenArea, this, clientArea.center());
4501                     if (closeHeight) {
4502                         bool tryBottom = titlePos == Qt::BottomEdge;
4503                         if ((overHeight && titlePos == Qt::TopEdge) || screenArea.top() == clientArea.top()) {
4504                             r.setTop(clientArea.top());
4505                         } else {
4506                             tryBottom = true;
4507                         }
4508                         if (tryBottom && (overHeight || screenArea.bottom() == clientArea.bottom())) {
4509                             r.setBottom(clientArea.bottom());
4510                         }
4511                     }
4512                     if (closeWidth) {
4513                         bool tryLeft = titlePos == Qt::LeftEdge;
4514                         if ((overWidth && titlePos == Qt::RightEdge) || screenArea.right() == clientArea.right()) {
4515                             r.setRight(clientArea.right());
4516                         } else {
4517                             tryLeft = true;
4518                         }
4519                         if (tryLeft && (overWidth || screenArea.left() == clientArea.left())) {
4520                             r.setLeft(clientArea.left());
4521                         }
4522                     }
4523                 }
4524             }
4525             r.moveTopLeft(rules()->checkPosition(r.topLeft()));
4526         }
4527         // The above code tries to center align the window followed by setting top and bottom
4528         // it's possible that we're in between two pixels
4529         r = Xcb::nativeFloor(r);
4530 
4531         moveResize(r);
4532         if (options->electricBorderMaximize() && r.top() == clientArea.top()) {
4533             updateQuickTileMode(QuickTileFlag::Maximize);
4534         } else {
4535             updateQuickTileMode(QuickTileFlag::None);
4536         }
4537         info->setState(NET::Max, NET::Max);
4538         break;
4539     }
4540     default:
4541         break;
4542     }
4543 
4544     blockGeometryUpdates(false);
4545     updateAllowedActions();
4546     updateWindowRules(Rules::MaximizeVert | Rules::MaximizeHoriz | Rules::Position | Rules::Size);
4547     Q_EMIT quickTileModeChanged();
4548 
4549     if (max_mode != old_mode) {
4550         Q_EMIT clientMaximizedStateChanged(this, max_mode);
4551         Q_EMIT clientMaximizedStateChanged(this, max_mode & MaximizeHorizontal, max_mode & MaximizeVertical);
4552     }
4553 }
4554 
4555 bool X11Window::userCanSetFullScreen() const
4556 {
4557     if (!isFullScreenable()) {
4558         return false;
4559     }
4560     return isNormalWindow() || isDialog();
4561 }
4562 
4563 void X11Window::setFullScreen(bool set, bool user)
4564 {
4565     set = rules()->checkFullScreen(set);
4566 
4567     const bool wasFullscreen = isFullScreen();
4568     if (wasFullscreen == set) {
4569         return;
4570     }
4571     if (user && !userCanSetFullScreen()) {
4572         return;
4573     }
4574 
4575     setShade(ShadeNone);
4576 
4577     if (wasFullscreen) {
4578         workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event
4579     } else {
4580         setFullscreenGeometryRestore(moveResizeGeometry());
4581     }
4582 
4583     if (set) {
4584         m_fullscreenMode = FullScreenNormal;
4585         workspace()->raiseWindow(this);
4586     } else {
4587         m_fullscreenMode = FullScreenNone;
4588     }
4589 
4590     StackingUpdatesBlocker blocker1(workspace());
4591     GeometryUpdatesBlocker blocker2(this);
4592 
4593     // active fullscreens get different layer
4594     updateLayer();
4595 
4596     info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen);
4597     updateDecoration(false, false);
4598 
4599     if (set) {
4600         if (info->fullscreenMonitors().isSet()) {
4601             moveResize(fullscreenMonitorsArea(info->fullscreenMonitors()));
4602         } else {
4603             moveResize(workspace()->clientArea(FullScreenArea, this, moveResizeOutput()));
4604         }
4605     } else {
4606         Q_ASSERT(!fullscreenGeometryRestore().isNull());
4607         moveResize(QRectF(fullscreenGeometryRestore().topLeft(), constrainFrameSize(fullscreenGeometryRestore().size())));
4608     }
4609 
4610     updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size);
4611     updateAllowedActions(false);
4612     Q_EMIT clientFullScreenSet(this, set, user);
4613     Q_EMIT fullScreenChanged();
4614 }
4615 
4616 void X11Window::updateFullscreenMonitors(NETFullscreenMonitors topology)
4617 {
4618     const int outputCount = workspace()->outputs().count();
4619 
4620     //    qDebug() << "incoming request with top: " << topology.top << " bottom: " << topology.bottom
4621     //                   << " left: " << topology.left << " right: " << topology.right
4622     //                   << ", we have: " << nscreens << " screens.";
4623 
4624     if (topology.top >= outputCount || topology.bottom >= outputCount || topology.left >= outputCount || topology.right >= outputCount) {
4625         qCWarning(KWIN_CORE) << "fullscreenMonitors update failed. request higher than number of screens.";
4626         return;
4627     }
4628 
4629     info->setFullscreenMonitors(topology);
4630     if (isFullScreen()) {
4631         moveResize(fullscreenMonitorsArea(topology));
4632     }
4633 }
4634 
4635 /**
4636  * Calculates the bounding rectangle defined by the 4 monitor indices indicating the
4637  * top, bottom, left, and right edges of the window when the fullscreen state is enabled.
4638  */
4639 QRect X11Window::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const
4640 {
4641     QRect total;
4642 
4643     if (auto output = workspace()->xineramaIndexToOutput(requestedTopology.top)) {
4644         total = total.united(output->geometry());
4645     }
4646     if (auto output = workspace()->xineramaIndexToOutput(requestedTopology.bottom)) {
4647         total = total.united(output->geometry());
4648     }
4649     if (auto output = workspace()->xineramaIndexToOutput(requestedTopology.left)) {
4650         total = total.united(output->geometry());
4651     }
4652     if (auto output = workspace()->xineramaIndexToOutput(requestedTopology.right)) {
4653         total = total.united(output->geometry());
4654     }
4655 
4656     return total;
4657 }
4658 
4659 bool X11Window::doStartInteractiveMoveResize()
4660 {
4661     if (kwinApp()->operationMode() == Application::OperationModeX11) {
4662         bool has_grab = false;
4663         // This reportedly improves smoothness of the moveresize operation,
4664         // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug*
4665         // (https://lists.kde.org/?t=107302193400001&r=1&w=2)
4666         QRectF r = workspace()->clientArea(FullArea, this, moveResizeOutput());
4667         m_moveResizeGrabWindow.create(Xcb::toXNative(r), XCB_WINDOW_CLASS_INPUT_ONLY, 0, nullptr, kwinApp()->x11RootWindow());
4668         m_moveResizeGrabWindow.map();
4669         m_moveResizeGrabWindow.raise();
4670         kwinApp()->updateXTime();
4671         const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(kwinApp()->x11Connection(), false, m_moveResizeGrabWindow,
4672                                                                             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,
4673                                                                             XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursors::self()->mouse()->x11Cursor(cursor()), xTime());
4674         UniqueCPtr<xcb_grab_pointer_reply_t> pointerGrab(xcb_grab_pointer_reply(kwinApp()->x11Connection(), cookie, nullptr));
4675         if (pointerGrab && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) {
4676             has_grab = true;
4677         }
4678         if (!has_grab && grabXKeyboard(frameId())) {
4679             has_grab = move_resize_has_keyboard_grab = true;
4680         }
4681         if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize
4682             m_moveResizeGrabWindow.reset();
4683             return false;
4684         }
4685     }
4686     return true;
4687 }
4688 
4689 void X11Window::leaveInteractiveMoveResize()
4690 {
4691     if (kwinApp()->operationMode() == Application::OperationModeX11) {
4692         if (move_resize_has_keyboard_grab) {
4693             ungrabXKeyboard();
4694         }
4695         move_resize_has_keyboard_grab = false;
4696         xcb_ungrab_pointer(kwinApp()->x11Connection(), xTime());
4697         m_moveResizeGrabWindow.reset();
4698     }
4699     Window::leaveInteractiveMoveResize();
4700 }
4701 
4702 bool X11Window::isWaitingForInteractiveMoveResizeSync() const
4703 {
4704     return m_syncRequest.isPending && m_syncRequest.interactiveResize;
4705 }
4706 
4707 void X11Window::doInteractiveResizeSync(const QRectF &rect)
4708 {
4709     setMoveResizeGeometry(rect);
4710 
4711     if (!m_syncRequest.timeout) {
4712         m_syncRequest.timeout = new QTimer(this);
4713         connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Window::handleSyncTimeout);
4714         m_syncRequest.timeout->setSingleShot(true);
4715     }
4716 
4717     if (m_syncRequest.counter != XCB_NONE) {
4718         m_syncRequest.timeout->start(250);
4719         sendSyncRequest();
4720     } else {
4721         // For clients not supporting the XSYNC protocol, we limit the resizes to 30Hz
4722         // to take pointless load from X11 and the client, the mouse is still moved at
4723         // full speed and no human can control faster resizes anyway.
4724         m_syncRequest.isPending = true;
4725         m_syncRequest.interactiveResize = true;
4726         m_syncRequest.timeout->start(33);
4727     }
4728 
4729     const QRectF moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry());
4730     const QRectF moveResizeBufferGeometry = frameRectToBufferRect(moveResizeGeometry());
4731 
4732     // According to the Composite extension spec, a window will get a new pixmap allocated each time
4733     // it is mapped or resized. Given that we redirect frame windows and not client windows, we have
4734     // to resize the frame window in order to forcefully reallocate offscreen storage. If we don't do
4735     // this, then we might render partially updated client window. I know, it sucks.
4736     m_frame.setGeometry(moveResizeBufferGeometry);
4737     m_wrapper.setGeometry(QRectF(clientPos(), moveResizeClientGeometry.size()));
4738     m_client.setGeometry(QRectF(QPointF(0, 0), moveResizeClientGeometry.size()));
4739 }
4740 
4741 void X11Window::handleSyncTimeout()
4742 {
4743     if (m_syncRequest.counter == XCB_NONE) { // client w/o XSYNC support. allow the next resize event
4744         m_syncRequest.isPending = false; // NEVER do this for clients with a valid counter
4745         m_syncRequest.interactiveResize = false; // (leads to sync request races in some clients)
4746     }
4747     performInteractiveResize();
4748 }
4749 
4750 NETExtendedStrut X11Window::strut() const
4751 {
4752     NETExtendedStrut ext = info->extendedStrut();
4753     NETStrut str = info->strut();
4754     const QSize displaySize = workspace()->geometry().size();
4755     if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0
4756         && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) {
4757         // build extended from simple
4758         if (str.left != 0) {
4759             ext.left_width = str.left;
4760             ext.left_start = 0;
4761             ext.left_end = displaySize.height();
4762         }
4763         if (str.right != 0) {
4764             ext.right_width = str.right;
4765             ext.right_start = 0;
4766             ext.right_end = displaySize.height();
4767         }
4768         if (str.top != 0) {
4769             ext.top_width = str.top;
4770             ext.top_start = 0;
4771             ext.top_end = displaySize.width();
4772         }
4773         if (str.bottom != 0) {
4774             ext.bottom_width = str.bottom;
4775             ext.bottom_start = 0;
4776             ext.bottom_end = displaySize.width();
4777         }
4778     }
4779     return ext;
4780 }
4781 
4782 StrutRect X11Window::strutRect(StrutArea area) const
4783 {
4784     Q_ASSERT(area != StrutAreaAll); // Not valid
4785     const QSize displaySize = workspace()->geometry().size();
4786     NETExtendedStrut strutArea = strut();
4787     switch (area) {
4788     case StrutAreaTop:
4789         if (strutArea.top_width != 0) {
4790             return StrutRect(QRect(
4791                                  strutArea.top_start, 0,
4792                                  strutArea.top_end - strutArea.top_start, strutArea.top_width),
4793                              StrutAreaTop);
4794         }
4795         break;
4796     case StrutAreaRight:
4797         if (strutArea.right_width != 0) {
4798             return StrutRect(QRect(
4799                                  displaySize.width() - strutArea.right_width, strutArea.right_start,
4800                                  strutArea.right_width, strutArea.right_end - strutArea.right_start),
4801                              StrutAreaRight);
4802         }
4803         break;
4804     case StrutAreaBottom:
4805         if (strutArea.bottom_width != 0) {
4806             return StrutRect(QRect(
4807                                  strutArea.bottom_start, displaySize.height() - strutArea.bottom_width,
4808                                  strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width),
4809                              StrutAreaBottom);
4810         }
4811         break;
4812     case StrutAreaLeft:
4813         if (strutArea.left_width != 0) {
4814             return StrutRect(QRect(
4815                                  0, strutArea.left_start,
4816                                  strutArea.left_width, strutArea.left_end - strutArea.left_start),
4817                              StrutAreaLeft);
4818         }
4819         break;
4820     default:
4821         Q_UNREACHABLE(); // Not valid
4822     }
4823     return StrutRect(); // Null rect
4824 }
4825 
4826 bool X11Window::hasStrut() const
4827 {
4828     NETExtendedStrut ext = strut();
4829     if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0) {
4830         return false;
4831     }
4832     return true;
4833 }
4834 
4835 void X11Window::applyWindowRules()
4836 {
4837     Window::applyWindowRules();
4838     updateAllowedActions();
4839     setBlockingCompositing(info->isBlockingCompositing());
4840 }
4841 
4842 bool X11Window::supportsWindowRules() const
4843 {
4844     return true;
4845 }
4846 
4847 void X11Window::updateWindowRules(Rules::Types selection)
4848 {
4849     if (!isManaged()) { // not fully setup yet
4850         return;
4851     }
4852     Window::updateWindowRules(selection);
4853 }
4854 
4855 void X11Window::damageNotifyEvent()
4856 {
4857     Q_ASSERT(kwinApp()->operationMode() == Application::OperationModeX11);
4858 
4859     if (!readyForPainting()) { // avoid "setReadyForPainting()" function calling overhead
4860         if (m_syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now
4861             setReadyForPainting();
4862         }
4863     }
4864 
4865     SurfaceItemX11 *item = static_cast<SurfaceItemX11 *>(surfaceItem());
4866     if (item) {
4867         item->processDamage();
4868     }
4869 }
4870 
4871 void X11Window::discardWindowPixmap()
4872 {
4873     if (auto item = surfaceItem()) {
4874         item->discardPixmap();
4875     }
4876 }
4877 
4878 void X11Window::updateWindowPixmap()
4879 {
4880     if (auto item = surfaceItem()) {
4881         item->updatePixmap();
4882     }
4883 }
4884 
4885 } // namespace