File indexing completed on 2024-05-12 09:35:27

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