File indexing completed on 2024-04-28 05:30:41

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