1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
7 
8     SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 // own
11 #include "x11client.h"
12 // kwin
13 #include "abstract_output.h"
14 #ifdef KWIN_BUILD_ACTIVITIES
15 #include "activities.h"
16 #endif
17 #include "atoms.h"
18 #include "client_machine.h"
19 #include "composite.h"
20 #include "cursor.h"
21 #include "deleted.h"
22 #include "effects.h"
23 #include "focuschain.h"
24 #include "geometrytip.h"
25 #include "group.h"
26 #include "netinfo.h"
27 #include "platform.h"
28 #include "screenedge.h"
29 #include "shadow.h"
30 #include "surfaceitem_x11.h"
31 #include "virtualdesktops.h"
32 #include "workspace.h"
33 #include "decorations/decorationbridge.h"
34 #include "decorations/decoratedclient.h"
35 #include <KDecoration2/Decoration>
36 #include <KDecoration2/DecoratedClient>
37 // KDE
38 #include <KLocalizedString>
39 #include <KStartupInfo>
40 #include <KWindowSystem>
41 #include <KColorScheme>
42 // Qt
43 #include <QApplication>
44 #include <QDebug>
45 #include <QDir>
46 #include <QFile>
47 #include <QFileInfo>
48 #include <QMouseEvent>
49 #include <QProcess>
50 // xcb
51 #include <xcb/xcb_icccm.h>
52 // system
53 #include <unistd.h>
54 // c++
55 #include <csignal>
56 
57 // Put all externs before the namespace statement to allow the linker
58 // to resolve them properly
59 
60 namespace KWin
61 {
62 
63 const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
64                            XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
65                            XCB_EVENT_MASK_KEYMAP_STATE |
66                            XCB_EVENT_MASK_BUTTON_MOTION |
67                            XCB_EVENT_MASK_POINTER_MOTION | // need this, too!
68                            XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
69                            XCB_EVENT_MASK_FOCUS_CHANGE |
70                            XCB_EVENT_MASK_EXPOSURE |
71                            XCB_EVENT_MASK_STRUCTURE_NOTIFY |
72                            XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
73 
74 // window types that are supported as normal windows (i.e. KWin actually manages them)
75 const NET::WindowTypes SUPPORTED_MANAGED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask
76         | NET::ToolbarMask | NET::MenuMask | NET::DialogMask /*| NET::OverrideMask*/ | NET::TopMenuMask
77         | NET::UtilityMask | NET::SplashMask | NET::NotificationMask | NET::OnScreenDisplayMask
78         | NET::CriticalNotificationMask;
79 
X11DecorationRenderer(Decoration::DecoratedClientImpl * client)80 X11DecorationRenderer::X11DecorationRenderer(Decoration::DecoratedClientImpl *client)
81     : DecorationRenderer(client)
82     , m_scheduleTimer(new QTimer(this))
83     , m_gc(XCB_NONE)
84 {
85     // Delay any rendering to end of event cycle to catch multiple updates per cycle.
86     m_scheduleTimer->setSingleShot(true);
87     m_scheduleTimer->setInterval(0);
88     connect(m_scheduleTimer, &QTimer::timeout, this, &X11DecorationRenderer::update);
89     connect(this, &X11DecorationRenderer::damaged, m_scheduleTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
90 }
91 
~X11DecorationRenderer()92 X11DecorationRenderer::~X11DecorationRenderer()
93 {
94     if (m_gc != XCB_NONE) {
95         xcb_free_gc(connection(), m_gc);
96     }
97 }
98 
update()99 void X11DecorationRenderer::update()
100 {
101     if (!damage().isEmpty()) {
102         render(damage());
103         resetDamage();
104     }
105 }
106 
render(const QRegion & region)107 void X11DecorationRenderer::render(const QRegion &region)
108 {
109     if (!client()) {
110         return;
111     }
112     xcb_connection_t *c = kwinApp()->x11Connection();
113     if (m_gc == XCB_NONE) {
114         m_gc = xcb_generate_id(c);
115         xcb_create_gc(c, m_gc, client()->client()->frameId(), 0, nullptr);
116     }
117 
118     QRect left, top, right, bottom;
119     client()->client()->layoutDecorationRects(left, top, right, bottom);
120 
121     const QRect geometry = region.boundingRect();
122     left   = left.intersected(geometry);
123     top    = top.intersected(geometry);
124     right  = right.intersected(geometry);
125     bottom = bottom.intersected(geometry);
126 
127     auto renderPart = [this, c](const QRect &geo) {
128         if (!geo.isValid()) {
129             return;
130         }
131         QImage image = renderToImage(geo);
132         xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, client()->client()->frameId(), m_gc,
133                       image.width(), image.height(), geo.x(), geo.y(), 0, client()->client()->depth(),
134                       image.sizeInBytes(), image.constBits());
135     };
136     renderPart(left);
137     renderPart(top);
138     renderPart(right);
139     renderPart(bottom);
140 
141     xcb_flush(c);
142     resetImageSizesDirty();
143 }
144 
145 // Creating a client:
146 //  - only by calling Workspace::createClient()
147 //      - it creates a new client and calls manage() for it
148 //
149 // Destroying a client:
150 //  - destroyClient() - only when the window itself has been destroyed
151 //      - releaseWindow() - the window is kept, only the client itself is destroyed
152 
153 /**
154  * \class Client x11client.h
155  * \brief The Client class encapsulates a window decoration frame.
156  */
157 
158 /**
159  * This ctor is "dumb" - it only initializes data. All the real initialization
160  * is done in manage().
161  */
X11Client()162 X11Client::X11Client()
163     : AbstractClient()
164     , m_client()
165     , m_wrapper()
166     , m_frame()
167     , m_activityUpdatesBlocked(false)
168     , m_blockedActivityUpdatesRequireTransients(false)
169     , m_moveResizeGrabWindow()
170     , move_resize_has_keyboard_grab(false)
171     , m_managed(false)
172     , m_transientForId(XCB_WINDOW_NONE)
173     , m_originalTransientForId(XCB_WINDOW_NONE)
174     , shade_below(nullptr)
175     , m_motif(atoms->motif_wm_hints)
176     , blocks_compositing(false)
177     , m_colormap(XCB_COLORMAP_NONE)
178     , in_group(nullptr)
179     , ping_timer(nullptr)
180     , m_killHelperPID(0)
181     , m_pingTimestamp(XCB_TIME_CURRENT_TIME)
182     , m_userTime(XCB_TIME_CURRENT_TIME)   // Not known yet
183     , allowed_actions()
184     , shade_geometry_change(false)
185     , sm_stacking_order(-1)
186     , activitiesDefined(false)
187     , sessionActivityOverride(false)
188     , needsXWindowMove(false)
189     , m_decoInputExtent()
190     , m_focusOutTimer(nullptr)
191 {
192     // TODO: Do all as initialization
193     m_syncRequest.counter = m_syncRequest.alarm = XCB_NONE;
194     m_syncRequest.timeout = m_syncRequest.failsafeTimeout = nullptr;
195     m_syncRequest.lastTimestamp = xTime();
196     m_syncRequest.isPending = false;
197 
198     // Set the initial mapping state
199     mapping_state = Withdrawn;
200 
201     info = nullptr;
202 
203     m_fullscreenMode = FullScreenNone;
204     hidden = false;
205     noborder = false;
206     app_noborder = false;
207     ignore_focus_stealing = false;
208     check_active_modal = false;
209 
210     max_mode = MaximizeRestore;
211 
212     connect(clientMachine(), &ClientMachine::localhostChanged, this, &X11Client::updateCaption);
213     connect(options, &Options::configChanged, this, &X11Client::updateMouseGrab);
214     connect(options, &Options::condensedTitleChanged, this, &X11Client::updateCaption);
215 
216     connect(this, &X11Client::moveResizeCursorChanged, this, [this] (CursorShape cursor) {
217         xcb_cursor_t nativeCursor = Cursors::self()->mouse()->x11Cursor(cursor);
218         m_frame.defineCursor(nativeCursor);
219         if (m_decoInputExtent.isValid())
220             m_decoInputExtent.defineCursor(nativeCursor);
221         if (isInteractiveMoveResize()) {
222             // changing window attributes doesn't change cursor if there's pointer grab active
223             xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(),
224                 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);
225         }
226     });
227 
228     // SELI TODO: Initialize xsizehints??
229 }
230 
231 /**
232  * "Dumb" destructor.
233  */
~X11Client()234 X11Client::~X11Client()
235 {
236     if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
237         ::kill(m_killHelperPID, SIGTERM);
238         m_killHelperPID = 0;
239     }
240     if (m_syncRequest.alarm != XCB_NONE) {
241         xcb_sync_destroy_alarm(connection(), m_syncRequest.alarm);
242     }
243     Q_ASSERT(!isInteractiveMoveResize());
244     Q_ASSERT(m_client == XCB_WINDOW_NONE);
245     Q_ASSERT(m_wrapper == XCB_WINDOW_NONE);
246     Q_ASSERT(m_frame == XCB_WINDOW_NONE);
247     Q_ASSERT(!check_active_modal);
248 }
249 
250 // Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly
deleteClient(X11Client * c)251 void X11Client::deleteClient(X11Client *c)
252 {
253     delete c;
254 }
255 
256 /**
257  * Releases the window. The client has done its job and the window is still existing.
258  */
releaseWindow(bool on_shutdown)259 void X11Client::releaseWindow(bool on_shutdown)
260 {
261     markAsZombie();
262     cleanTabBox();
263     Deleted* del = nullptr;
264     if (!on_shutdown) {
265         del = Deleted::create(this);
266     }
267     if (isInteractiveMoveResize())
268         Q_EMIT clientFinishUserMovedResized(this);
269     Q_EMIT windowClosed(this, del);
270     finishCompositing();
271     RuleBook::self()->discardUsed(this, true);   // Remove ForceTemporarily rules
272     StackingUpdatesBlocker blocker(workspace());
273     if (isInteractiveMoveResize())
274         leaveInteractiveMoveResize();
275     finishWindowRules();
276     blockGeometryUpdates();
277     if (isOnCurrentDesktop() && isShown(true))
278         addWorkspaceRepaint(visibleGeometry());
279     // Grab X during the release to make removing of properties, setting to withdrawn state
280     // and repareting to root an atomic operation (https://lists.kde.org/?l=kde-devel&m=116448102901184&w=2)
281     grabXServer();
282     exportMappingState(XCB_ICCCM_WM_STATE_WITHDRAWN);
283     setModal(false);   // Otherwise its mainwindow wouldn't get focus
284     hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags)
285     if (!on_shutdown)
286         workspace()->clientHidden(this);
287     m_frame.unmap();  // Destroying decoration would cause ugly visual effect
288     destroyDecoration();
289     cleanGrouping();
290     workspace()->removeX11Client(this);
291     if (!on_shutdown) {
292         // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7)
293         info->setDesktop(0);
294         info->setState(NET::States(), info->state());  // Reset all state flags
295     }
296     xcb_connection_t *c = connection();
297     m_client.deleteProperty(atoms->kde_net_wm_user_creation_time);
298     m_client.deleteProperty(atoms->net_frame_extents);
299     m_client.deleteProperty(atoms->kde_net_wm_frame_strut);
300     m_client.reparent(rootWindow(), m_bufferGeometry.x(), m_bufferGeometry.y());
301     xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client);
302     m_client.selectInput(XCB_EVENT_MASK_NO_EVENT);
303     if (on_shutdown)
304         // Map the window, so it can be found after another WM is started
305         m_client.map();
306     // TODO: Preserve minimized, shaded etc. state?
307     else // Make sure it's not mapped if the app unmapped it (#65279). The app
308         // may do map+unmap before we initially map the window by calling rawShow() from manage().
309         m_client.unmap();
310     m_client.reset();
311     m_wrapper.reset();
312     m_frame.reset();
313     unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry
314     if (!on_shutdown) {
315         disownDataPassedToDeleted();
316         del->unrefWindow();
317     }
318     deleteClient(this);
319     ungrabXServer();
320 }
321 
322 /**
323  * Like releaseWindow(), but this one is called when the window has been already destroyed
324  * (E.g. The application closed it)
325  */
destroyClient()326 void X11Client::destroyClient()
327 {
328     markAsZombie();
329     cleanTabBox();
330     Deleted* del = Deleted::create(this);
331     if (isInteractiveMoveResize())
332         Q_EMIT clientFinishUserMovedResized(this);
333     Q_EMIT windowClosed(this, del);
334     finishCompositing(ReleaseReason::Destroyed);
335     RuleBook::self()->discardUsed(this, true);   // Remove ForceTemporarily rules
336     StackingUpdatesBlocker blocker(workspace());
337     if (isInteractiveMoveResize())
338         leaveInteractiveMoveResize();
339     finishWindowRules();
340     blockGeometryUpdates();
341     if (isOnCurrentDesktop() && isShown(true))
342         addWorkspaceRepaint(visibleGeometry());
343     setModal(false);
344     hidden = true; // So that it's not considered visible anymore
345     workspace()->clientHidden(this);
346     destroyDecoration();
347     cleanGrouping();
348     workspace()->removeX11Client(this);
349     m_client.reset(); // invalidate
350     m_wrapper.reset();
351     m_frame.reset();
352     unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry
353     disownDataPassedToDeleted();
354     del->unrefWindow();
355     deleteClient(this);
356 }
357 
358 /**
359  * Manages the clients. This means handling the very first maprequest:
360  * reparenting, initial geometry, initial state, placement, etc.
361  * Returns false if KWin is not going to manage this window.
362  */
manage(xcb_window_t w,bool isMapped)363 bool X11Client::manage(xcb_window_t w, bool isMapped)
364 {
365     StackingUpdatesBlocker stacking_blocker(workspace());
366 
367     Xcb::WindowAttributes attr(w);
368     Xcb::WindowGeometry windowGeometry(w);
369     if (attr.isNull() || windowGeometry.isNull()) {
370         return false;
371     }
372 
373     // From this place on, manage() must not return false
374     blockGeometryUpdates();
375 
376     embedClient(w, attr->visual, attr->colormap, windowGeometry->depth);
377 
378     m_visual = attr->visual;
379     bit_depth = windowGeometry->depth;
380 
381     // SELI TODO: Order all these things in some sane manner
382 
383     const NET::Properties properties =
384         NET::WMDesktop |
385         NET::WMState |
386         NET::WMWindowType |
387         NET::WMStrut |
388         NET::WMName |
389         NET::WMIconGeometry |
390         NET::WMIcon |
391         NET::WMPid |
392         NET::WMIconName;
393     const NET::Properties2 properties2 =
394         NET::WM2BlockCompositing |
395         NET::WM2WindowClass |
396         NET::WM2WindowRole |
397         NET::WM2UserTime |
398         NET::WM2StartupId |
399         NET::WM2ExtendedStrut |
400         NET::WM2Opacity |
401         NET::WM2FullscreenMonitors |
402         NET::WM2GroupLeader |
403         NET::WM2Urgency |
404         NET::WM2Input |
405         NET::WM2Protocols |
406         NET::WM2InitialMappingState |
407         NET::WM2IconPixmap |
408         NET::WM2OpaqueRegion |
409         NET::WM2DesktopFileName |
410         NET::WM2GTKFrameExtents;
411 
412     auto wmClientLeaderCookie = fetchWmClientLeader();
413     auto skipCloseAnimationCookie = fetchSkipCloseAnimation();
414     auto showOnScreenEdgeCookie = fetchShowOnScreenEdge();
415     auto colorSchemeCookie = fetchPreferredColorScheme();
416     auto firstInTabBoxCookie = fetchFirstInTabBox();
417     auto transientCookie = fetchTransient();
418     auto activitiesCookie = fetchActivities();
419     auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName();
420     auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath();
421 
422     m_geometryHints.init(window());
423     m_motif.init(window());
424     info = new WinInfo(this, m_client, rootWindow(), properties, properties2);
425 
426     if (isDesktop() && bit_depth == 32) {
427         // force desktop windows to be opaque. It's a desktop after all, there is no window below
428         bit_depth = 24;
429     }
430 
431     // If it's already mapped, ignore hint
432     bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic);
433 
434     m_colormap = attr->colormap;
435 
436     getResourceClass();
437     readWmClientLeader(wmClientLeaderCookie);
438     getWmClientMachine();
439     getSyncCounter();
440     // First only read the caption text, so that setupWindowRules() can use it for matching,
441     // and only then really set the caption using setCaption(), which checks for duplicates etc.
442     // and also relies on rules already existing
443     cap_normal = readName();
444     setupWindowRules(false);
445     setCaption(cap_normal, true);
446 
447     connect(this, &X11Client::windowClassChanged, this, &X11Client::evaluateWindowRules);
448 
449     if (Xcb::Extensions::self()->isShapeAvailable())
450         xcb_shape_select_input(connection(), window(), true);
451     detectShape(window());
452     detectNoBorder();
453     fetchIconicName();
454     setClientFrameExtents(info->gtkFrameExtents());
455 
456     // Needs to be done before readTransient() because of reading the group
457     checkGroup();
458     updateUrgency();
459     updateAllowedActions(); // Group affects isMinimizable()
460 
461     setModal((info->state() & NET::Modal) != 0);   // Needs to be valid before handling groups
462     readTransientProperty(transientCookie);
463     setDesktopFileName(rules()->checkDesktopFile(QByteArray(info->desktopFileName()), true).toUtf8());
464     getIcons();
465     connect(this, &X11Client::desktopFileNameChanged, this, &X11Client::getIcons);
466 
467     m_geometryHints.read();
468     getMotifHints();
469     getWmOpaqueRegion();
470     readSkipCloseAnimation(skipCloseAnimationCookie);
471 
472     // TODO: Try to obey all state information from info->state()
473 
474     setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0);
475     setSkipPager((info->state() & NET::SkipPager) != 0);
476     setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0);
477     readFirstInTabBox(firstInTabBoxCookie);
478 
479     setupCompositing();
480 
481     KStartupInfoId asn_id;
482     KStartupInfoData asn_data;
483     bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data);
484 
485     // Make sure that the input window is created before we update the stacking order
486     updateInputWindow();
487     updateLayer();
488 
489     SessionInfo* session = workspace()->takeSessionInfo(this);
490     if (session) {
491         init_minimize = session->minimized;
492         noborder = session->noBorder;
493     }
494 
495     setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true));
496 
497     init_minimize = rules()->checkMinimize(init_minimize, !isMapped);
498     noborder = rules()->checkNoBorder(noborder, !isMapped);
499 
500     readActivities(activitiesCookie);
501 
502     // Initial desktop placement
503     std::optional<QVector<VirtualDesktop *>> initialDesktops;
504     if (session) {
505         if (session->onAllDesktops) {
506             initialDesktops = QVector<VirtualDesktop *>{};
507         } else {
508             VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(session->desktop);
509             if (desktop) {
510                 initialDesktops = QVector<VirtualDesktop *>{desktop};
511             }
512         }
513         setOnActivities(session->activities);
514     } else {
515         // If this window is transient, ensure that it is opened on the
516         // same window as its parent.  this is necessary when an application
517         // starts up on a different desktop than is currently displayed
518         if (isTransient()) {
519             auto mainclients = mainClients();
520             bool on_current = false;
521             bool on_all = false;
522             AbstractClient* maincl = nullptr;
523             // This is slightly duplicated from Placement::placeOnMainWindow()
524             for (auto it = mainclients.constBegin();
525                     it != mainclients.constEnd();
526                     ++it) {
527                 if (mainclients.count() > 1 &&      // A group-transient
528                     (*it)->isSpecialWindow() &&     // Don't consider toolbars etc when placing
529                     !(info->state() & NET::Modal))  // except when it's modal (blocks specials as well)
530                     continue;
531                 maincl = *it;
532                 if ((*it)->isOnCurrentDesktop())
533                     on_current = true;
534                 if ((*it)->isOnAllDesktops())
535                     on_all = true;
536             }
537             if (on_all) {
538                 initialDesktops = QVector<VirtualDesktop *>{};
539             } else if (on_current) {
540                 initialDesktops = QVector<VirtualDesktop *>{VirtualDesktopManager::self()->currentDesktop()};
541             } else if (maincl) {
542                 initialDesktops = maincl->desktops();
543             }
544 
545             if (maincl)
546                 setOnActivities(maincl->activities());
547         } else { // a transient shall appear on its leader and not drag that around
548             int desktopId = 0;
549             if (info->desktop()) {
550                 desktopId = info->desktop(); // Window had the initial desktop property, force it
551             }
552             if (desktopId == 0 && asn_valid && asn_data.desktop() != 0) {
553                 desktopId = asn_data.desktop();
554             }
555             if (desktopId) {
556                 if (desktopId == NET::OnAllDesktops) {
557                     initialDesktops = QVector<VirtualDesktop *>{};
558                 } else {
559                     VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(desktopId);
560                     if (desktop) {
561                         initialDesktops = QVector<VirtualDesktop *>{desktop};
562                     }
563                 }
564             }
565         }
566 #ifdef KWIN_BUILD_ACTIVITIES
567         if (Activities::self() && !isMapped && !skipTaskbar() && isNormalWindow() && !activitiesDefined) {
568             //a new, regular window, when we're not recovering from a crash,
569             //and it hasn't got an activity. let's try giving it the current one.
570             //TODO: decide whether to keep this before the 4.6 release
571             //TODO: if we are keeping it (at least as an option), replace noborder checking
572             //with a public API for setting windows to be on all activities.
573             //something like KWindowSystem::setOnAllActivities or
574             //KActivityConsumer::setOnAllActivities
575             setOnActivity(Activities::self()->current(), true);
576         }
577 #endif
578     }
579 
580     // If initialDesktops has no value, it means that the client doesn't prefer any
581     // desktop so place it on the current virtual desktop.
582     if (!initialDesktops.has_value()) {
583         if (isDesktop()) {
584             initialDesktops = QVector<VirtualDesktop *>{};
585         } else {
586             initialDesktops = QVector<VirtualDesktop *>{VirtualDesktopManager::self()->currentDesktop()};
587         }
588     }
589     setDesktops(rules()->checkDesktops(*initialDesktops, !isMapped));
590     info->setDesktop(desktop());
591     workspace()->updateOnAllDesktopsOfTransients(this);   // SELI TODO
592     //onAllDesktopsChange(); // Decoration doesn't exist here yet
593 
594     QStringList activitiesList;
595     activitiesList = rules()->checkActivity(activitiesList, !isMapped);
596     if (!activitiesList.isEmpty())
597         setOnActivities(activitiesList);
598 
599     QRect geom(windowGeometry.rect());
600     bool placementDone = false;
601 
602     if (session)
603         geom = session->geometry;
604 
605     QRect area;
606     bool partial_keep_in_area = isMapped || session;
607     if (isMapped || session) {
608         area = workspace()->clientArea(FullArea, this, geom.center());
609         checkOffscreenPosition(&geom, area);
610     } else {
611         AbstractOutput *output = nullptr;
612         if (asn_data.xinerama() != -1) {
613             output = kwinApp()->platform()->findOutput(asn_data.xinerama());
614         }
615         if (!output) {
616             output = workspace()->activeOutput();
617         }
618         output = rules()->checkOutput(output, !isMapped);
619         area = workspace()->clientArea(PlacementArea, this, output->geometry().center());
620     }
621 
622     if (isDesktop())
623         // KWin doesn't manage desktop windows
624         placementDone = true;
625 
626     bool usePosition = false;
627     if (isMapped || session || placementDone)
628         placementDone = true; // Use geometry
629     else if (isTransient() && !isUtility() && !isDialog() && !isSplash())
630         usePosition = true;
631     else if (isTransient() && !hasNETSupport())
632         usePosition = true;
633     else if (isDialog() && hasNETSupport()) {
634         // If the dialog is actually non-NETWM transient window, don't try to apply placement to it,
635         // it breaks with too many things (xmms, display)
636         if (mainClients().count() >= 1) {
637 #if 1
638             // #78082 - Ok, it seems there are after all some cases when an application has a good
639             // reason to specify a position for its dialog. Too bad other WMs have never bothered
640             // with placement for dialogs, so apps always specify positions for their dialogs,
641             // including such silly positions like always centered on the screen or under mouse.
642             // Using ignoring requested position in window-specific settings helps, and now
643             // there's also _NET_WM_FULL_PLACEMENT.
644             usePosition = true;
645 #else
646             ; // Force using placement policy
647 #endif
648         } else
649             usePosition = true;
650     } else if (isSplash())
651         ; // Force using placement policy
652     else
653         usePosition = true;
654     if (!rules()->checkIgnoreGeometry(!usePosition, true)) {
655         if (m_geometryHints.hasPosition()) {
656             placementDone = true;
657             // Disobey xinerama placement option for now (#70943)
658             area = workspace()->clientArea(PlacementArea, this, geom.center());
659         }
660     }
661 
662     if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom()))
663         placementDone = false; // Weird, do not trust.
664 
665     if (placementDone) {
666         QPoint position = geom.topLeft();
667         // Session contains the position of the frame geometry before gravitating.
668         if (!session) {
669             position = clientPosToFramePos(position);
670         }
671         move(position);
672     }
673 
674     // Create client group if the window will have a decoration
675     bool dontKeepInArea = false;
676     setColorScheme(readPreferredColorScheme(colorSchemeCookie));
677 
678     readApplicationMenuServiceName(applicationMenuServiceNameCookie);
679     readApplicationMenuObjectPath(applicationMenuObjectPathCookie);
680 
681     updateDecoration(false);   // Also gravitates
682     // TODO: Is CentralGravity right here, when resizing is done after gravitating?
683     const QSize constrainedClientSize = constrainClientSize(geom.size());
684     resize(rules()->checkSize(clientSizeToFrameSize(constrainedClientSize), !isMapped));
685 
686     QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped);
687     if (forced_pos != invalidPoint) {
688         move(forced_pos);
689         placementDone = true;
690         // Don't keep inside workarea if the window has specially configured position
691         partial_keep_in_area = true;
692         area = workspace()->clientArea(FullArea, this, geom.center());
693     }
694     if (!placementDone) {
695         // Placement needs to be after setting size
696         Placement::self()->place(this, area);
697         // The client may have been moved to another screen, update placement area.
698         area = workspace()->clientArea(PlacementArea, this);
699         dontKeepInArea = true;
700         placementDone = true;
701     }
702 
703     // bugs #285967, #286146, #183694
704     // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully)
705     // Maximization for oversized windows must happen NOW.
706     // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained
707     // to the combo of all screen MINUS all struts on the edges
708     // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked
709     // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack
710     // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well.
711 
712     if (!session) { // has a better handling of this
713         setGeometryRestore(frameGeometry()); // Remember restore geometry
714         if (isMaximizable() && (width() >= area.width() || height() >= area.height())) {
715             // Window is too large for the screen, maximize in the
716             // directions necessary
717             const QSize ss = workspace()->clientArea(ScreenArea, this, area.center()).size();
718             const QRect fsa = workspace()->clientArea(FullArea, this, geom.center());
719             const QSize cs = clientSize();
720             int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) |
721                              ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0);
722             if (width() >= area.width())
723                 pseudo_max |=  MaximizeHorizontal;
724             if (height() >= area.height())
725                 pseudo_max |=  MaximizeVertical;
726 
727             // heuristics:
728             // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen)
729             // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an
730             // attempt for maximization, but just constrain the size (the window simply wants to be bigger)
731             // NOTICE
732             // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller
733             // than the workspace") but gtk / gimp seems to store it's size including the decoration,
734             // thus a former maximized window wil become non-maximized
735             bool keepInFsArea = false;
736             if (width() < fsa.width() && (cs.width() > ss.width()+1)) {
737                 pseudo_max &= ~MaximizeHorizontal;
738                 keepInFsArea = true;
739             }
740             if (height() < fsa.height() && (cs.height() > ss.height()+1)) {
741                 pseudo_max &= ~MaximizeVertical;
742                 keepInFsArea = true;
743             }
744 
745             if (pseudo_max != MaximizeRestore) {
746                 maximize((MaximizeMode)pseudo_max);
747                 // from now on, care about maxmode, since the maximization call will override mode for fix aspects
748                 dontKeepInArea |= (max_mode == MaximizeFull);
749                 QRect savedGeometry; // Use placement when unmaximizing ...
750                 if (!(max_mode & MaximizeVertical)) {
751                     savedGeometry.setY(y());   // ...but only for horizontal direction
752                     savedGeometry.setHeight(height());
753                 }
754                 if (!(max_mode & MaximizeHorizontal)) {
755                     savedGeometry.setX(x());   // ...but only for vertical direction
756                     savedGeometry.setWidth(width());
757                 }
758                 setGeometryRestore(savedGeometry);
759             }
760             if (keepInFsArea)
761                 keepInArea(fsa, partial_keep_in_area);
762         }
763     }
764 
765     if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea)
766         keepInArea(area, partial_keep_in_area);
767 
768     updateShape();
769 
770     // CT: Extra check for stupid jdk 1.3.1. But should make sense in general
771     // if client has initial state set to Iconic and is transient with a parent
772     // window that is not Iconic, set init_state to Normal
773     if (init_minimize && isTransient()) {
774         auto mainclients = mainClients();
775         for (auto it = mainclients.constBegin();
776                 it != mainclients.constEnd();
777                 ++it)
778             if ((*it)->isShown(true))
779                 init_minimize = false; // SELI TODO: Even e.g. for NET::Utility?
780     }
781     // If a dialog is shown for minimized window, minimize it too
782     if (!init_minimize && isTransient() && mainClients().count() > 0 &&
783             workspace()->sessionManager()->state() != SessionState::Saving) {
784         bool visible_parent = false;
785         // Use allMainClients(), to include also main clients of group transients
786         // that have been optimized out in X11Client::checkGroupTransients()
787         auto mainclients = allMainClients();
788         for (auto it = mainclients.constBegin();
789                 it != mainclients.constEnd();
790                 ++it)
791             if ((*it)->isShown(true))
792                 visible_parent = true;
793         if (!visible_parent) {
794             init_minimize = true;
795             demandAttention();
796         }
797     }
798 
799     if (init_minimize)
800         minimize(true);   // No animation
801 
802     // Other settings from the previous session
803     if (session) {
804         // Session restored windows are not considered to be new windows WRT rules,
805         // I.e. obey only forcing rules
806         setKeepAbove(session->keepAbove);
807         setKeepBelow(session->keepBelow);
808         setOriginalSkipTaskbar(session->skipTaskbar);
809         setSkipPager(session->skipPager);
810         setSkipSwitcher(session->skipSwitcher);
811         setShade(session->shaded ? ShadeNormal : ShadeNone);
812         setOpacity(session->opacity);
813         setGeometryRestore(session->restore);
814         if (session->maximized != MaximizeRestore) {
815             maximize(MaximizeMode(session->maximized));
816         }
817         if (session->fullscreen != FullScreenNone) {
818             setFullScreen(true, false);
819             setFullscreenGeometryRestore(session->fsrestore);
820         }
821         QRect checkedGeometryRestore = geometryRestore();
822         checkOffscreenPosition(&checkedGeometryRestore, area);
823         setGeometryRestore(checkedGeometryRestore);
824         QRect checkedFullscreenGeometryRestore = fullscreenGeometryRestore();
825         checkOffscreenPosition(&checkedFullscreenGeometryRestore, area);
826         setFullscreenGeometryRestore(checkedFullscreenGeometryRestore);
827     } else {
828         // Window may want to be maximized
829         // done after checking that the window isn't larger than the workarea, so that
830         // the restore geometry from the checks above takes precedence, and window
831         // isn't restored larger than the workarea
832         MaximizeMode maxmode = static_cast<MaximizeMode>(
833                                    ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) |
834                                    ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0));
835         MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped);
836 
837         // Either hints were set to maximize, or is forced to maximize,
838         // or is forced to non-maximize and hints were set to maximize
839         if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore)
840             maximize(forced_maxmode);
841 
842         // Read other initial states
843         setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped));
844         setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped));
845         setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped));
846         setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped));
847         setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped));
848         setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped));
849         if (info->state() & NET::DemandsAttention)
850             demandAttention();
851         if (info->state() & NET::Modal)
852             setModal(true);
853         setOpacity(info->opacityF());
854 
855         setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false);
856     }
857 
858     updateAllowedActions(true);
859 
860     // Set initial user time directly
861     m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session);
862     group()->updateUserTime(m_userTime);   // And do what X11Client::updateUserTime() does
863 
864     // This should avoid flicker, because real restacking is done
865     // only after manage() finishes because of blocking, but the window is shown sooner
866     m_frame.lower();
867     if (session && session->stackingOrder != -1) {
868         sm_stacking_order = session->stackingOrder;
869         workspace()->restoreSessionStackingOrder(this);
870     }
871 
872     if (Compositor::compositing())
873         // Sending ConfigureNotify is done when setting mapping state below,
874         // Getting the first sync response means window is ready for compositing
875         sendSyncRequest();
876     else
877         ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393
878 
879     if (isShown(true)) {
880         bool allow;
881         if (session)
882             allow = session->active &&
883                     (!workspace()->wasUserInteraction() || workspace()->activeClient() == nullptr ||
884                      workspace()->activeClient()->isDesktop());
885         else
886             allow = workspace()->allowClientActivation(this, userTime(), false);
887 
888         const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving;
889 
890         // If session saving, force showing new windows (i.e. "save file?" dialogs etc.)
891         // also force if activation is allowed
892         if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || isSessionSaving ))
893             VirtualDesktopManager::self()->setCurrent( desktop());
894 
895         // If the window is on an inactive activity during session saving, temporarily force it to show.
896         if( !isMapped && !session && isSessionSaving && !isOnCurrentActivity()) {
897             setSessionActivityOverride( true );
898             Q_FOREACH( AbstractClient* c, mainClients()) {
899                 if (X11Client *mc = dynamic_cast<X11Client *>(c)) {
900                     mc->setSessionActivityOverride(true);
901                 }
902             }
903         }
904 
905         if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0))
906             workspace()->restackClientUnderActive(this);
907 
908         updateVisibility();
909 
910         if (!isMapped) {
911             if (allow && isOnCurrentDesktop()) {
912                 if (!isSpecialWindow())
913                     if (options->focusPolicyIsReasonable() && wantsTabFocus())
914                         workspace()->requestFocus(this);
915             } else if (!session && !isSpecialWindow())
916                 demandAttention();
917         }
918     } else
919         updateVisibility();
920     Q_ASSERT(mapping_state != Withdrawn);
921     m_managed = true;
922     blockGeometryUpdates(false);
923 
924     if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) {
925         // No known user time, set something old
926         m_userTime = xTime() - 1000000;
927         if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U)   // Let's be paranoid
928             m_userTime = xTime() - 1000000 + 10;
929     }
930 
931     //sendSyntheticConfigureNotify(); // Done when setting mapping state
932 
933     delete session;
934 
935     discardTemporaryRules();
936     applyWindowRules(); // Just in case
937     RuleBook::self()->discardUsed(this, false);   // Remove ApplyNow rules
938     updateWindowRules(Rules::All); // Was blocked while !isManaged()
939 
940     setBlockingCompositing(info->isBlockingCompositing());
941     readShowOnScreenEdge(showOnScreenEdgeCookie);
942 
943     // Forward all opacity values to the frame in case there'll be other CM running.
944     connect(Compositor::self(), &Compositor::compositingToggled, this,
945         [this](bool active) {
946             if (active) {
947                 return;
948             }
949             if (opacity() == 1.0) {
950                 return;
951             }
952             NETWinInfo info(connection(), frameId(), rootWindow(), NET::Properties(), NET::Properties2());
953             info.setOpacityF(opacity());
954         }
955     );
956 
957     // TODO: there's a small problem here - isManaged() depends on the mapping state,
958     // but this client is not yet in Workspace's client list at this point, will
959     // be only done in addClient()
960     Q_EMIT clientManaging(this);
961     return true;
962 }
963 
964 // Called only from manage()
embedClient(xcb_window_t w,xcb_visualid_t visualid,xcb_colormap_t colormap,uint8_t depth)965 void X11Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth)
966 {
967     Q_ASSERT(m_client == XCB_WINDOW_NONE);
968     Q_ASSERT(frameId() == XCB_WINDOW_NONE);
969     Q_ASSERT(m_wrapper == XCB_WINDOW_NONE);
970     m_client.reset(w, false);
971 
972     const uint32_t zero_value = 0;
973 
974     xcb_connection_t *conn = connection();
975 
976     // We don't want the window to be destroyed when we quit
977     xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client);
978 
979     m_client.selectInput(zero_value);
980     m_client.unmap();
981     m_client.setBorderWidth(zero_value);
982 
983     // Note: These values must match the order in the xcb_cw_t enum
984     const uint32_t cw_values[] = {
985         0,                                // back_pixmap
986         0,                                // border_pixel
987         colormap,                    // colormap
988         Cursors::self()->mouse()->x11Cursor(Qt::ArrowCursor)
989     };
990 
991     const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL |
992                              XCB_CW_COLORMAP | XCB_CW_CURSOR;
993 
994     const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
995                                        XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
996                                        XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
997                                        XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION |
998                                        XCB_EVENT_MASK_KEYMAP_STATE |
999                                        XCB_EVENT_MASK_FOCUS_CHANGE |
1000                                        XCB_EVENT_MASK_EXPOSURE |
1001                                        XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
1002 
1003     const uint32_t frame_event_mask   = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_VISIBILITY_CHANGE;
1004     const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
1005 
1006     const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE |
1007                                        XCB_EVENT_MASK_COLOR_MAP_CHANGE |
1008                                        XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
1009                                        XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE;
1010 
1011     // Create the frame window
1012     xcb_window_t frame = xcb_generate_id(conn);
1013     xcb_create_window(conn, depth, frame, rootWindow(), 0, 0, 1, 1, 0,
1014                       XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
1015     m_frame.reset(frame);
1016 
1017     setWindowHandles(m_client);
1018 
1019     // Create the wrapper window
1020     xcb_window_t wrapperId = xcb_generate_id(conn);
1021     xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0,
1022                       XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
1023     m_wrapper.reset(wrapperId);
1024 
1025     m_client.reparent(m_wrapper);
1026 
1027     // We could specify the event masks when we create the windows, but the original
1028     // Xlib code didn't.  Let's preserve that behavior here for now so we don't end up
1029     // receiving any unexpected events from the wrapper creation or the reparenting.
1030     m_frame.selectInput(frame_event_mask);
1031     m_wrapper.selectInput(wrapper_event_mask);
1032     m_client.selectInput(client_event_mask);
1033 
1034     updateMouseGrab();
1035 }
1036 
updateInputWindow()1037 void X11Client::updateInputWindow()
1038 {
1039     if (!Xcb::Extensions::self()->isShapeInputAvailable())
1040         return;
1041 
1042     QRegion region;
1043 
1044     if (!noBorder() && isDecorated()) {
1045         const QMargins &r = decoration()->resizeOnlyBorders();
1046         const int left   = r.left();
1047         const int top    = r.top();
1048         const int right  = r.right();
1049         const int bottom = r.bottom();
1050         if (left != 0 || top != 0 || right != 0 || bottom != 0) {
1051             region = QRegion(-left,
1052                              -top,
1053                              decoration()->size().width() + left + right,
1054                              decoration()->size().height() + top + bottom);
1055             region = region.subtracted(decoration()->rect());
1056         }
1057     }
1058 
1059     if (region.isEmpty()) {
1060         m_decoInputExtent.reset();
1061         return;
1062     }
1063 
1064     QRect bounds = region.boundingRect();
1065     input_offset = bounds.topLeft();
1066 
1067     // Move the bounding rect to screen coordinates
1068     bounds.translate(frameGeometry().topLeft());
1069 
1070     // Move the region to input window coordinates
1071     region.translate(-input_offset);
1072 
1073     if (!m_decoInputExtent.isValid()) {
1074         const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
1075         const uint32_t values[] = {true,
1076             XCB_EVENT_MASK_ENTER_WINDOW   |
1077             XCB_EVENT_MASK_LEAVE_WINDOW   |
1078             XCB_EVENT_MASK_BUTTON_PRESS   |
1079             XCB_EVENT_MASK_BUTTON_RELEASE |
1080             XCB_EVENT_MASK_POINTER_MOTION
1081         };
1082         m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values);
1083         if (mapping_state == Mapped)
1084             m_decoInputExtent.map();
1085     } else {
1086         m_decoInputExtent.setGeometry(bounds);
1087     }
1088 
1089     const QVector<xcb_rectangle_t> rects = Xcb::regionToRects(region);
1090     xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED,
1091                          m_decoInputExtent, 0, 0, rects.count(), rects.constData());
1092 }
1093 
updateDecoration(bool check_workspace_pos,bool force)1094 void X11Client::updateDecoration(bool check_workspace_pos, bool force)
1095 {
1096     if (!force &&
1097             ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder())))
1098         return;
1099     QRect oldgeom = frameGeometry();
1100     QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom());
1101     blockGeometryUpdates(true);
1102     if (force)
1103         destroyDecoration();
1104     if (!noBorder()) {
1105         createDecoration(oldgeom);
1106     } else
1107         destroyDecoration();
1108     updateShadow();
1109     if (check_workspace_pos)
1110         checkWorkspacePosition(oldgeom, oldClientGeom);
1111     updateInputWindow();
1112     blockGeometryUpdates(false);
1113     updateFrameExtents();
1114 }
1115 
createDecoration(const QRect & oldgeom)1116 void X11Client::createDecoration(const QRect& oldgeom)
1117 {
1118     KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this);
1119     if (decoration) {
1120         QMetaObject::invokeMethod(decoration, QOverload<>::of(&KDecoration2::Decoration::update), Qt::QueuedConnection);
1121         connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::updateShadow);
1122         connect(decoration, &KDecoration2::Decoration::bordersChanged,
1123                 this, &X11Client::updateDecorationInputShape);
1124         connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged,
1125                 this, &X11Client::updateDecorationInputShape);
1126         connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &X11Client::updateInputWindow);
1127         connect(decoration, &KDecoration2::Decoration::bordersChanged, this,
1128             [this]() {
1129                 updateFrameExtents();
1130                 GeometryUpdatesBlocker blocker(this);
1131                 // TODO: this is obviously idempotent
1132                 // calculateGravitation(true) would have to operate on the old border sizes
1133 //                 move(calculateGravitation(true));
1134 //                 move(calculateGravitation(false));
1135                 QRect oldgeom = frameGeometry();
1136                 resize(adjustedSize());
1137                 if (!isShade())
1138                     checkWorkspacePosition(oldgeom);
1139                 Q_EMIT geometryShapeChanged(this, oldgeom);
1140             }
1141         );
1142         connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, &X11Client::updateInputWindow);
1143         connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, &X11Client::updateInputWindow);
1144         connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged,
1145                 this, &X11Client::updateDecorationInputShape);
1146     }
1147     setDecoration(decoration);
1148 
1149     move(calculateGravitation(false));
1150     resize(adjustedSize());
1151     maybeCreateX11DecorationRenderer();
1152     Q_EMIT geometryShapeChanged(this, oldgeom);
1153 }
1154 
destroyDecoration()1155 void X11Client::destroyDecoration()
1156 {
1157     QRect oldgeom = frameGeometry();
1158     if (isDecorated()) {
1159         QPoint grav = calculateGravitation(true);
1160         setDecoration(nullptr);
1161         maybeDestroyX11DecorationRenderer();
1162         resize(adjustedSize());
1163         move(grav);
1164         if (!isZombie()) {
1165             Q_EMIT geometryShapeChanged(this, oldgeom);
1166         }
1167     }
1168     m_decoInputExtent.reset();
1169 }
1170 
maybeCreateX11DecorationRenderer()1171 void X11Client::maybeCreateX11DecorationRenderer()
1172 {
1173     if (kwinApp()->operationMode() != Application::OperationModeX11) {
1174         return;
1175     }
1176     if (!Compositor::compositing() && decoratedClient()) {
1177         m_decorationRenderer.reset(new X11DecorationRenderer(decoratedClient()));
1178         decoration()->update();
1179     }
1180 }
1181 
maybeDestroyX11DecorationRenderer()1182 void X11Client::maybeDestroyX11DecorationRenderer()
1183 {
1184     m_decorationRenderer.reset();
1185 }
1186 
detectNoBorder()1187 void X11Client::detectNoBorder()
1188 {
1189     if (shape()) {
1190         noborder = true;
1191         app_noborder = true;
1192         return;
1193     }
1194     switch(windowType()) {
1195     case NET::Desktop :
1196     case NET::Dock :
1197     case NET::TopMenu :
1198     case NET::Splash :
1199     case NET::Notification :
1200     case NET::OnScreenDisplay :
1201     case NET::CriticalNotification :
1202         noborder = true;
1203         app_noborder = true;
1204         break;
1205     case NET::Unknown :
1206     case NET::Normal :
1207     case NET::Toolbar :
1208     case NET::Menu :
1209     case NET::Dialog :
1210     case NET::Utility :
1211         noborder = false;
1212         break;
1213     default:
1214         abort();
1215     }
1216     // NET::Override is some strange beast without clear definition, usually
1217     // just meaning "noborder", so let's treat it only as such flag, and ignore it as
1218     // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it)
1219     if (info->windowType(NET::OverrideMask) == NET::Override) {
1220         noborder = true;
1221         app_noborder = true;
1222     }
1223 }
1224 
updateFrameExtents()1225 void X11Client::updateFrameExtents()
1226 {
1227     NETStrut strut;
1228     strut.left = borderLeft();
1229     strut.right = borderRight();
1230     strut.top = borderTop();
1231     strut.bottom = borderBottom();
1232     info->setFrameExtents(strut);
1233 }
1234 
setClientFrameExtents(const NETStrut & strut)1235 void X11Client::setClientFrameExtents(const NETStrut &strut)
1236 {
1237     const QMargins clientFrameExtents(strut.left, strut.top, strut.right, strut.bottom);
1238     if (m_clientFrameExtents == clientFrameExtents) {
1239         return;
1240     }
1241 
1242     const bool wasClientSideDecorated = isClientSideDecorated();
1243     m_clientFrameExtents = clientFrameExtents;
1244 
1245     // We should resize the client when its custom frame extents are changed so
1246     // the logical bounds remain the same. This however means that we will send
1247     // several configure requests to the application upon restoring it from the
1248     // maximized or fullscreen state. Notice that a client-side decorated client
1249     // cannot be shaded, therefore it's okay not to use the adjusted size here.
1250     moveResize(moveResizeGeometry());
1251 
1252     if (wasClientSideDecorated != isClientSideDecorated()) {
1253         Q_EMIT clientSideDecoratedChanged();
1254     }
1255 
1256     // This will invalidate the window quads cache.
1257     Q_EMIT geometryShapeChanged(this, frameGeometry());
1258 }
1259 
1260 /**
1261  * Resizes the decoration, and makes sure the decoration widget gets resize event
1262  * even if the size hasn't changed. This is needed to make sure the decoration
1263  * re-layouts (e.g. when maximization state changes,
1264  * the decoration may alter some borders, but the actual size
1265  * of the decoration stays the same).
1266  */
resizeDecoration()1267 void X11Client::resizeDecoration()
1268 {
1269     triggerDecorationRepaint();
1270     updateInputWindow();
1271 }
1272 
userNoBorder() const1273 bool X11Client::userNoBorder() const
1274 {
1275     return noborder;
1276 }
1277 
isFullScreenable() const1278 bool X11Client::isFullScreenable() const
1279 {
1280     if (!rules()->checkFullScreen(true)) {
1281         return false;
1282     }
1283     if (rules()->checkStrictGeometry(true)) {
1284         // check geometry constraints (rule to obey is set)
1285         const QRect fullScreenArea = workspace()->clientArea(FullScreenArea, this);
1286         const QSize constrainedClientSize = constrainClientSize(fullScreenArea.size());
1287         if (rules()->checkSize(constrainedClientSize) != fullScreenArea.size()) {
1288             return false; // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements
1289         }
1290     }
1291     // don't check size constrains - some apps request fullscreen despite requesting fixed size
1292     return !isSpecialWindow(); // also better disallow only weird types to go fullscreen
1293 }
1294 
noBorder() const1295 bool X11Client::noBorder() const
1296 {
1297     return userNoBorder() || isFullScreen();
1298 }
1299 
userCanSetNoBorder() const1300 bool X11Client::userCanSetNoBorder() const
1301 {
1302     // Client-side decorations and server-side decorations are mutually exclusive.
1303     if (isClientSideDecorated()) {
1304         return false;
1305     }
1306 
1307     return !isFullScreen() && !isShade();
1308 }
1309 
setNoBorder(bool set)1310 void X11Client::setNoBorder(bool set)
1311 {
1312     if (!userCanSetNoBorder())
1313         return;
1314     set = rules()->checkNoBorder(set);
1315     if (noborder == set)
1316         return;
1317     noborder = set;
1318     updateDecoration(true, false);
1319     updateWindowRules(Rules::NoBorder);
1320 }
1321 
checkNoBorder()1322 void X11Client::checkNoBorder()
1323 {
1324     setNoBorder(app_noborder);
1325 }
1326 
updateShape()1327 void X11Client::updateShape()
1328 {
1329     if (shape()) {
1330         // Workaround for #19644 - Shaped windows shouldn't have decoration
1331         if (!app_noborder) {
1332             // Only when shape is detected for the first time, still let the user to override
1333             app_noborder = true;
1334             noborder = rules()->checkNoBorder(true);
1335             updateDecoration(true);
1336         }
1337         if (noBorder()) {
1338             xcb_shape_combine(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING,
1339                               frameId(), clientPos().x(), clientPos().y(), window());
1340         }
1341     } else if (app_noborder) {
1342         xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE);
1343         detectNoBorder();
1344         app_noborder = noborder;
1345         noborder = rules()->checkNoBorder(noborder || m_motif.noBorder());
1346         updateDecoration(true);
1347     }
1348 
1349     // Decoration mask (i.e. 'else' here) setting is done in setMask()
1350     // when the decoration calls it or when the decoration is created/destroyed
1351     updateInputShape();
1352     if (Compositor::compositing()) {
1353         addRepaintFull();
1354         addWorkspaceRepaint(visibleGeometry());   // In case shape change removes part of this window
1355     }
1356     Q_EMIT geometryShapeChanged(this, frameGeometry());
1357 }
1358 
1359 static Xcb::Window shape_helper_window(XCB_WINDOW_NONE);
1360 
cleanupX11()1361 void X11Client::cleanupX11()
1362 {
1363     shape_helper_window.reset();
1364 }
1365 
updateInputShape()1366 void X11Client::updateInputShape()
1367 {
1368     if (hiddenPreview())   // Sets it to none, don't change
1369         return;
1370     if (Xcb::Extensions::self()->isShapeInputAvailable()) {
1371         // There appears to be no way to find out if a window has input
1372         // shape set or not, so always propagate the input shape
1373         // (it's the same like the bounding shape by default).
1374         // Also, build the shape using a helper window, not directly
1375         // in the frame window, because the sequence set-shape-to-frame,
1376         // remove-shape-of-client, add-input-shape-of-client has the problem
1377         // that after the second step there's a hole in the input shape
1378         // until the real shape of the client is added and that can make
1379         // the window lose focus (which is a problem with mouse focus policies)
1380         // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better
1381         if (!shape_helper_window.isValid())
1382             shape_helper_window.create(QRect(0, 0, 1, 1));
1383         shape_helper_window.resize(m_bufferGeometry.size());
1384         xcb_connection_t *c = connection();
1385         xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING,
1386                           shape_helper_window, 0, 0, frameId());
1387         xcb_shape_combine(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING,
1388                           shape_helper_window, clientPos().x(), clientPos().y(), window());
1389         xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT,
1390                           shape_helper_window, clientPos().x(), clientPos().y(), window());
1391         xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT,
1392                           frameId(), 0, 0, shape_helper_window);
1393     }
1394 }
1395 
hideClient(bool hide)1396 void X11Client::hideClient(bool hide)
1397 {
1398     if (hidden == hide)
1399         return;
1400     hidden = hide;
1401     updateVisibility();
1402 }
1403 
setupCompositing()1404 bool X11Client::setupCompositing()
1405 {
1406     if (!Toplevel::setupCompositing()){
1407         return false;
1408     }
1409     // If compositing is back on, stop rendering decoration in the frame window.
1410     maybeDestroyX11DecorationRenderer();
1411     updateVisibility(); // for internalKeep()
1412     return true;
1413 }
1414 
finishCompositing(ReleaseReason releaseReason)1415 void X11Client::finishCompositing(ReleaseReason releaseReason)
1416 {
1417     Toplevel::finishCompositing(releaseReason);
1418     updateVisibility();
1419     // for safety in case KWin is just resizing the window
1420     resetHaveResizeEffect();
1421     // If compositing is off, render the decoration in the X11 frame window.
1422     maybeCreateX11DecorationRenderer();
1423 }
1424 
1425 /**
1426  * Returns whether the window is minimizable or not
1427  */
isMinimizable() const1428 bool X11Client::isMinimizable() const
1429 {
1430     if (isSpecialWindow() && !isTransient())
1431         return false;
1432     if (!rules()->checkMinimize(true))
1433         return false;
1434 
1435     if (isTransient()) {
1436         // #66868 - Let other xmms windows be minimized when the mainwindow is minimized
1437         bool shown_mainwindow = false;
1438         auto mainclients = mainClients();
1439         for (auto it = mainclients.constBegin();
1440                 it != mainclients.constEnd();
1441                 ++it)
1442             if ((*it)->isShown(true))
1443                 shown_mainwindow = true;
1444         if (!shown_mainwindow)
1445             return true;
1446     }
1447 #if 0
1448     // This is here because kicker's taskbar doesn't provide separate entries
1449     // for windows with an explicitly given parent
1450     // TODO: perhaps this should be redone
1451     // Disabled for now, since at least modal dialogs should be minimizable
1452     // (resulting in the mainwindow being minimized too).
1453     if (transientFor() != NULL)
1454         return false;
1455 #endif
1456     if (!wantsTabFocus())   // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ?
1457         return false;
1458     return true;
1459 }
1460 
doMinimize()1461 void X11Client::doMinimize()
1462 {
1463     if (isShade()) {
1464         // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded
1465         info->setState(isMinimized() ? NET::States() : NET::Shaded, NET::Shaded);
1466     }
1467     updateVisibility();
1468     updateAllowedActions();
1469     workspace()->updateMinimizedOfTransients(this);
1470 }
1471 
iconGeometry() const1472 QRect X11Client::iconGeometry() const
1473 {
1474     NETRect r = info->iconGeometry();
1475     QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height);
1476     if (geom.isValid())
1477         return geom;
1478     else {
1479         // Check all mainwindows of this window (recursively)
1480         Q_FOREACH (AbstractClient * amainwin, mainClients()) {
1481             X11Client *mainwin = dynamic_cast<X11Client *>(amainwin);
1482             if (!mainwin) {
1483                 continue;
1484             }
1485             geom = mainwin->iconGeometry();
1486             if (geom.isValid())
1487                 return geom;
1488         }
1489         // No mainwindow (or their parents) with icon geometry was found
1490         return AbstractClient::iconGeometry();
1491     }
1492 }
1493 
isShadeable() const1494 bool X11Client::isShadeable() const
1495 {
1496     return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone));
1497 }
1498 
doSetShade(ShadeMode previousShadeMode)1499 void X11Client::doSetShade(ShadeMode previousShadeMode)
1500 {
1501     // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere
1502     if (isShade()) {
1503         // shade_mode == ShadeNormal
1504         addWorkspaceRepaint(visibleGeometry());
1505         // Shade
1506         shade_geometry_change = true;
1507         QSize s(adjustedSize());
1508         s.setHeight(borderTop() + borderBottom());
1509         m_wrapper.selectInput(ClientWinMask);   // Avoid getting UnmapNotify
1510         m_wrapper.unmap();
1511         m_client.unmap();
1512         m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY);
1513         exportMappingState(XCB_ICCCM_WM_STATE_ICONIC);
1514         resize(s);
1515         shade_geometry_change = false;
1516         if (previousShadeMode == ShadeHover) {
1517             if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1)
1518                     workspace()->restack(this, shade_below, true);
1519             if (isActive())
1520                 workspace()->activateNextClient(this);
1521         } else if (isActive()) {
1522             workspace()->focusToNull();
1523         }
1524     } else {
1525         shade_geometry_change = true;
1526         if (decoratedClient())
1527             decoratedClient()->signalShadeChange();
1528         QSize s(adjustedSize());
1529         shade_geometry_change = false;
1530         resize(s);
1531         setGeometryRestore(frameGeometry());
1532         if ((shadeMode() == ShadeHover || shadeMode() == ShadeActivated) && rules()->checkAcceptFocus(info->input()))
1533             setActive(true);
1534         if (shadeMode() == ShadeHover) {
1535             QList<Toplevel *> order = workspace()->stackingOrder();
1536             // invalidate, since "this" could be the topmost toplevel and shade_below dangeling
1537             shade_below = nullptr;
1538             // this is likely related to the index parameter?!
1539             for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) {
1540                 shade_below = qobject_cast<X11Client *>(order.at(idx));
1541                 if (shade_below) {
1542                     break;
1543                 }
1544             }
1545             if (shade_below && shade_below->isNormalWindow())
1546                 workspace()->raiseClient(this);
1547             else
1548                 shade_below = nullptr;
1549         }
1550         m_wrapper.map();
1551         m_client.map();
1552         exportMappingState(XCB_ICCCM_WM_STATE_NORMAL);
1553         if (isActive())
1554             workspace()->requestFocus(this);
1555     }
1556     info->setState(isShade() ? NET::Shaded : NET::States(), NET::Shaded);
1557     info->setState(isShown(false) ? NET::States() : NET::Hidden, NET::Hidden);
1558     updateVisibility();
1559     updateAllowedActions();
1560     discardWindowPixmap();
1561 }
1562 
updateVisibility()1563 void X11Client::updateVisibility()
1564 {
1565     if (isZombie())
1566         return;
1567     if (hidden) {
1568         info->setState(NET::Hidden, NET::Hidden);
1569         setSkipTaskbar(true);   // Also hide from taskbar
1570         if (Compositor::compositing() && options->hiddenPreviews() == HiddenPreviewsAlways)
1571             internalKeep();
1572         else
1573             internalHide();
1574         return;
1575     }
1576     setSkipTaskbar(originalSkipTaskbar());   // Reset from 'hidden'
1577     if (isMinimized()) {
1578         info->setState(NET::Hidden, NET::Hidden);
1579         if (Compositor::compositing() && options->hiddenPreviews() == HiddenPreviewsAlways)
1580             internalKeep();
1581         else
1582             internalHide();
1583         return;
1584     }
1585     info->setState(NET::States(), NET::Hidden);
1586     if (!isOnCurrentDesktop()) {
1587         if (Compositor::compositing() && options->hiddenPreviews() != HiddenPreviewsNever)
1588             internalKeep();
1589         else
1590             internalHide();
1591         return;
1592     }
1593     if (!isOnCurrentActivity()) {
1594         if (Compositor::compositing() && options->hiddenPreviews() != HiddenPreviewsNever)
1595             internalKeep();
1596         else
1597             internalHide();
1598         return;
1599     }
1600     internalShow();
1601 }
1602 
1603 
1604 /**
1605  * Sets the client window's mapping state. Possible values are
1606  * WithdrawnState, IconicState, NormalState.
1607  */
exportMappingState(int s)1608 void X11Client::exportMappingState(int s)
1609 {
1610     Q_ASSERT(m_client != XCB_WINDOW_NONE);
1611     Q_ASSERT(!isZombie() || s == XCB_ICCCM_WM_STATE_WITHDRAWN);
1612     if (s == XCB_ICCCM_WM_STATE_WITHDRAWN) {
1613         m_client.deleteProperty(atoms->wm_state);
1614         return;
1615     }
1616     Q_ASSERT(s == XCB_ICCCM_WM_STATE_NORMAL || s == XCB_ICCCM_WM_STATE_ICONIC);
1617 
1618     int32_t data[2];
1619     data[0] = s;
1620     data[1] = XCB_NONE;
1621     m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data);
1622 }
1623 
internalShow()1624 void X11Client::internalShow()
1625 {
1626     if (mapping_state == Mapped)
1627         return;
1628     MappingState old = mapping_state;
1629     mapping_state = Mapped;
1630     if (old == Unmapped || old == Withdrawn)
1631         map();
1632     if (old == Kept) {
1633         m_decoInputExtent.map();
1634         updateHiddenPreview();
1635     }
1636     Q_EMIT windowShown(this);
1637 }
1638 
internalHide()1639 void X11Client::internalHide()
1640 {
1641     if (mapping_state == Unmapped)
1642         return;
1643     MappingState old = mapping_state;
1644     mapping_state = Unmapped;
1645     if (old == Mapped || old == Kept)
1646         unmap();
1647     if (old == Kept)
1648         updateHiddenPreview();
1649     addWorkspaceRepaint(visibleGeometry());
1650     workspace()->clientHidden(this);
1651     Q_EMIT windowHidden(this);
1652 }
1653 
internalKeep()1654 void X11Client::internalKeep()
1655 {
1656     Q_ASSERT(Compositor::compositing());
1657     if (mapping_state == Kept)
1658         return;
1659     MappingState old = mapping_state;
1660     mapping_state = Kept;
1661     if (old == Unmapped || old == Withdrawn)
1662         map();
1663     m_decoInputExtent.unmap();
1664     if (isActive())
1665         workspace()->focusToNull(); // get rid of input focus, bug #317484
1666     updateHiddenPreview();
1667     addWorkspaceRepaint(visibleGeometry());
1668     workspace()->clientHidden(this);
1669 }
1670 
1671 /**
1672  * Maps (shows) the client. Note that it is mapping state of the frame,
1673  * not necessarily the client window itself (i.e. a shaded window is here
1674  * considered mapped, even though it is in IconicState).
1675  */
map()1676 void X11Client::map()
1677 {
1678     // XComposite invalidates backing pixmaps on unmap (minimize, different
1679     // virtual desktop, etc.).  We kept the last known good pixmap around
1680     // for use in effects, but now we want to have access to the new pixmap
1681     if (Compositor::compositing())
1682         discardWindowPixmap();
1683     m_frame.map();
1684     if (!isShade()) {
1685         m_wrapper.map();
1686         m_client.map();
1687         m_decoInputExtent.map();
1688         exportMappingState(XCB_ICCCM_WM_STATE_NORMAL);
1689     } else
1690         exportMappingState(XCB_ICCCM_WM_STATE_ICONIC);
1691     addLayerRepaint(visibleGeometry());
1692 }
1693 
1694 /**
1695  * Unmaps the client. Again, this is about the frame.
1696  */
unmap()1697 void X11Client::unmap()
1698 {
1699     // Here it may look like a race condition, as some other client might try to unmap
1700     // the window between these two XSelectInput() calls. However, they're supposed to
1701     // use XWithdrawWindow(), which also sends a synthetic event to the root window,
1702     // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify
1703     // will be missed is also very minimal, so I don't think it's needed to grab the server
1704     // here.
1705     m_wrapper.selectInput(ClientWinMask);   // Avoid getting UnmapNotify
1706     m_frame.unmap();
1707     m_wrapper.unmap();
1708     m_client.unmap();
1709     m_decoInputExtent.unmap();
1710     m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY);
1711     exportMappingState(XCB_ICCCM_WM_STATE_ICONIC);
1712 }
1713 
1714 /**
1715  * XComposite doesn't keep window pixmaps of unmapped windows, which means
1716  * there wouldn't be any previews of windows that are minimized or on another
1717  * virtual desktop. Therefore rawHide() actually keeps such windows mapped.
1718  * However special care needs to be taken so that such windows don't interfere.
1719  * Therefore they're put very low in the stacking order and they have input shape
1720  * set to none, which hopefully is enough. If there's no input shape available,
1721  * then it's hoped that there will be some other desktop above it *shrug*.
1722  * Using normal shape would be better, but that'd affect other things, e.g. painting
1723  * of the actual preview.
1724  */
updateHiddenPreview()1725 void X11Client::updateHiddenPreview()
1726 {
1727     if (hiddenPreview()) {
1728         workspace()->forceRestacking();
1729         if (Xcb::Extensions::self()->isShapeInputAvailable()) {
1730             xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT,
1731                                  XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, nullptr);
1732         }
1733     } else {
1734         workspace()->forceRestacking();
1735         updateInputShape();
1736     }
1737 }
1738 
sendClientMessage(xcb_window_t w,xcb_atom_t a,xcb_atom_t protocol,uint32_t data1,uint32_t data2,uint32_t data3)1739 void X11Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3)
1740 {
1741     xcb_client_message_event_t ev;
1742     // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy
1743     // 32 unconditionally. Add a static_assert to ensure we don't disclose
1744     // stack memory.
1745     static_assert(sizeof(ev) == 32, "Would leak stack data otherwise");
1746     memset(&ev, 0, sizeof(ev));
1747     ev.response_type = XCB_CLIENT_MESSAGE;
1748     ev.window = w;
1749     ev.type = a;
1750     ev.format = 32;
1751     ev.data.data32[0] = protocol;
1752     ev.data.data32[1] = xTime();
1753     ev.data.data32[2] = data1;
1754     ev.data.data32[3] = data2;
1755     ev.data.data32[4] = data3;
1756     uint32_t eventMask = 0;
1757     if (w == rootWindow()) {
1758         eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic!
1759     }
1760     xcb_send_event(connection(), false, w, eventMask, reinterpret_cast<const char*>(&ev));
1761     xcb_flush(connection());
1762 }
1763 
1764 /**
1765  * Returns whether the window may be closed (have a close button)
1766  */
isCloseable() const1767 bool X11Client::isCloseable() const
1768 {
1769     return rules()->checkCloseable(m_motif.close() && !isSpecialWindow());
1770 }
1771 
1772 /**
1773  * Closes the window by either sending a delete_window message or using XKill.
1774  */
closeWindow()1775 void X11Client::closeWindow()
1776 {
1777     if (!isCloseable())
1778         return;
1779 
1780     // Update user time, because the window may create a confirming dialog.
1781     updateUserTime();
1782 
1783     if (info->supportsProtocol(NET::DeleteWindowProtocol)) {
1784         sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window);
1785         pingWindow();
1786     } else // Client will not react on wm_delete_window. We have not choice
1787         // but destroy his connection to the XServer.
1788         killWindow();
1789 }
1790 
1791 
1792 /**
1793  * Kills the window via XKill
1794  */
killWindow()1795 void X11Client::killWindow()
1796 {
1797     qCDebug(KWIN_CORE) << "X11Client::killWindow():" << caption();
1798     killProcess(false);
1799     m_client.kill();  // Always kill this client at the server
1800     destroyClient();
1801 }
1802 
1803 /**
1804  * Send a ping to the window using _NET_WM_PING if possible if it
1805  * doesn't respond within a reasonable time, it will be killed.
1806  */
pingWindow()1807 void X11Client::pingWindow()
1808 {
1809     if (!info->supportsProtocol(NET::PingProtocol))
1810         return; // Can't ping :(
1811     if (options->killPingTimeout() == 0)
1812         return; // Turned off
1813     if (ping_timer != nullptr)
1814         return; // Pinging already
1815     ping_timer = new QTimer(this);
1816     connect(ping_timer, &QTimer::timeout, this,
1817         [this]() {
1818             if (unresponsive()) {
1819                 qCDebug(KWIN_CORE) << "Final ping timeout, asking to kill:" << caption();
1820                 ping_timer->deleteLater();
1821                 ping_timer = nullptr;
1822                 killProcess(true, m_pingTimestamp);
1823                 return;
1824             }
1825 
1826             qCDebug(KWIN_CORE) << "First ping timeout:" << caption();
1827 
1828             setUnresponsive(true);
1829             ping_timer->start();
1830         }
1831     );
1832     ping_timer->setSingleShot(true);
1833     // we'll run the timer twice, at first we'll desaturate the window
1834     // and the second time we'll show the "do you want to kill" prompt
1835     ping_timer->start(options->killPingTimeout() / 2);
1836     m_pingTimestamp = xTime();
1837     rootInfo()->sendPing(window(), m_pingTimestamp);
1838 }
1839 
gotPing(xcb_timestamp_t timestamp)1840 void X11Client::gotPing(xcb_timestamp_t timestamp)
1841 {
1842     // Just plain compare is not good enough because of 64bit and truncating and whatnot
1843     if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0)
1844         return;
1845     delete ping_timer;
1846     ping_timer = nullptr;
1847 
1848     setUnresponsive(false);
1849 
1850     if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
1851         ::kill(m_killHelperPID, SIGTERM);
1852         m_killHelperPID = 0;
1853     }
1854 }
1855 
killProcess(bool ask,xcb_timestamp_t timestamp)1856 void X11Client::killProcess(bool ask, xcb_timestamp_t timestamp)
1857 {
1858     if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive
1859         return;
1860     Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME);
1861     pid_t pid = info->pid();
1862     if (pid <= 0 || clientMachine()->hostName().isEmpty())  // Needed properties missing
1863         return;
1864     qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")";
1865     if (!ask) {
1866         if (!clientMachine()->isLocal()) {
1867             QStringList lst;
1868             lst << QString::fromUtf8(clientMachine()->hostName()) << QStringLiteral("kill") << QString::number(pid);
1869             QProcess::startDetached(QStringLiteral("xon"), lst);
1870         } else
1871             ::kill(pid, SIGTERM);
1872     } else {
1873         QString hostname = clientMachine()->isLocal() ? QStringLiteral("localhost") : QString::fromUtf8(clientMachine()->hostName());
1874         // execute helper from build dir or the system installed one
1875         const QFileInfo buildDirBinary{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("kwin_killer_helper")};
1876         QProcess::startDetached(buildDirBinary.exists() ? buildDirBinary.absoluteFilePath() : QStringLiteral(KWIN_KILLER_BIN),
1877                                 QStringList() << QStringLiteral("--pid") << QString::number(unsigned(pid)) << QStringLiteral("--hostname") << hostname
1878                                 << QStringLiteral("--windowname") << captionNormal()
1879                                 << QStringLiteral("--applicationname") << QString::fromUtf8(resourceClass())
1880                                 << QStringLiteral("--wid") << QString::number(window())
1881                                 << QStringLiteral("--timestamp") << QString::number(timestamp),
1882                                 QString(), &m_killHelperPID);
1883     }
1884 }
1885 
doSetKeepAbove()1886 void X11Client::doSetKeepAbove()
1887 {
1888     info->setState(keepAbove() ? NET::KeepAbove : NET::States(), NET::KeepAbove);
1889 }
1890 
doSetKeepBelow()1891 void X11Client::doSetKeepBelow()
1892 {
1893     info->setState(keepBelow() ? NET::KeepBelow : NET::States(), NET::KeepBelow);
1894 }
1895 
doSetSkipTaskbar()1896 void X11Client::doSetSkipTaskbar()
1897 {
1898     info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(), NET::SkipTaskbar);
1899 }
1900 
doSetSkipPager()1901 void X11Client::doSetSkipPager()
1902 {
1903     info->setState(skipPager() ? NET::SkipPager : NET::States(), NET::SkipPager);
1904 }
1905 
doSetSkipSwitcher()1906 void X11Client::doSetSkipSwitcher()
1907 {
1908     info->setState(skipSwitcher() ? NET::SkipSwitcher : NET::States(), NET::SkipSwitcher);
1909 }
1910 
doSetDesktop()1911 void X11Client::doSetDesktop()
1912 {
1913     updateVisibility();
1914 }
1915 
doSetDemandsAttention()1916 void X11Client::doSetDemandsAttention()
1917 {
1918     info->setState(isDemandingAttention() ? NET::DemandsAttention : NET::States(), NET::DemandsAttention);
1919 }
1920 
doSetOnActivities(const QStringList & activityList)1921 void X11Client::doSetOnActivities(const QStringList &activityList)
1922 {
1923 #ifdef KWIN_BUILD_ACTIVITIES
1924     if (activityList.isEmpty()) {
1925         const QByteArray nullUuid = Activities::nullUuid().toUtf8();
1926         m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData());
1927     } else {
1928         QByteArray joined = activityList.join(QStringLiteral(",")).toLatin1();
1929         m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData());
1930     }
1931 #else
1932     Q_UNUSED(newActivitiesList)
1933 #endif
1934 }
1935 
updateActivities(bool includeTransients)1936 void X11Client::updateActivities(bool includeTransients)
1937 {
1938     AbstractClient::updateActivities(includeTransients);
1939     if (!m_activityUpdatesBlocked) {
1940         updateVisibility();
1941     }
1942 }
1943 
1944 /**
1945  * Returns the list of activities the client window is on.
1946  * if it's on all activities, the list will be empty.
1947  * Don't use this, use isOnActivity() and friends (from class Toplevel)
1948  */
activities() const1949 QStringList X11Client::activities() const
1950 {
1951     if (sessionActivityOverride) {
1952         return QStringList();
1953     }
1954     return AbstractClient::activities();
1955 }
1956 
1957 /**
1958  * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS
1959  */
takeFocus()1960 bool X11Client::takeFocus()
1961 {
1962     if (rules()->checkAcceptFocus(info->input())) {
1963         xcb_void_cookie_t cookie = xcb_set_input_focus_checked(connection(),
1964                                                                XCB_INPUT_FOCUS_POINTER_ROOT,
1965                                                                window(), XCB_TIME_CURRENT_TIME);
1966         ScopedCPointer<xcb_generic_error_t> error(xcb_request_check(connection(), cookie));
1967         if (error) {
1968             qCWarning(KWIN_CORE, "Failed to focus 0x%x (error %d)", window(), error->error_code);
1969             return false;
1970         }
1971     } else {
1972         demandAttention(false); // window cannot take input, at least withdraw urgency
1973     }
1974     if (info->supportsProtocol(NET::TakeFocusProtocol)) {
1975         updateXTime();
1976         sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus);
1977     }
1978     workspace()->setShouldGetFocus(this);
1979 
1980     bool breakShowingDesktop = !keepAbove();
1981     if (breakShowingDesktop) {
1982         Q_FOREACH (const X11Client *c, group()->members()) {
1983             if (c->isDesktop()) {
1984                 breakShowingDesktop = false;
1985                 break;
1986             }
1987         }
1988     }
1989     if (breakShowingDesktop)
1990         workspace()->setShowingDesktop(false);
1991 
1992     return true;
1993 }
1994 
1995 /**
1996  * Returns whether the window provides context help or not. If it does,
1997  * you should show a help menu item or a help button like '?' and call
1998  * contextHelp() if this is invoked.
1999  *
2000  * \sa contextHelp()
2001  */
providesContextHelp() const2002 bool X11Client::providesContextHelp() const
2003 {
2004     return info->supportsProtocol(NET::ContextHelpProtocol);
2005 }
2006 
2007 /**
2008  * Invokes context help on the window. Only works if the window
2009  * actually provides context help.
2010  *
2011  * \sa providesContextHelp()
2012  */
showContextHelp()2013 void X11Client::showContextHelp()
2014 {
2015     if (info->supportsProtocol(NET::ContextHelpProtocol)) {
2016         sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help);
2017     }
2018 }
2019 
2020 /**
2021  * Fetches the window's caption (WM_NAME property). It will be
2022  * stored in the client's caption().
2023  */
fetchName()2024 void X11Client::fetchName()
2025 {
2026     setCaption(readName());
2027 }
2028 
readNameProperty(xcb_window_t w,xcb_atom_t atom)2029 static inline QString readNameProperty(xcb_window_t w, xcb_atom_t atom)
2030 {
2031     const auto cookie = xcb_icccm_get_text_property_unchecked(connection(), w, atom);
2032     xcb_icccm_get_text_property_reply_t reply;
2033     if (xcb_icccm_get_wm_name_reply(connection(), cookie, &reply, nullptr)) {
2034         QString retVal;
2035         if (reply.encoding == atoms->utf8_string) {
2036             retVal = QString::fromUtf8(QByteArray(reply.name, reply.name_len));
2037         } else if (reply.encoding == XCB_ATOM_STRING) {
2038             retVal = QString::fromLocal8Bit(QByteArray(reply.name, reply.name_len));
2039         }
2040         xcb_icccm_get_text_property_reply_wipe(&reply);
2041         return retVal.simplified();
2042     }
2043     return QString();
2044 }
2045 
readName() const2046 QString X11Client::readName() const
2047 {
2048     if (info->name() && info->name()[0] != '\0')
2049         return QString::fromUtf8(info->name()).simplified();
2050     else {
2051         return readNameProperty(window(), XCB_ATOM_WM_NAME);
2052     }
2053 }
2054 
2055 // The list is taken from https://www.unicode.org/reports/tr9/ (#154840)
2056 static const QChar LRM(0x200E);
2057 
setCaption(const QString & _s,bool force)2058 void X11Client::setCaption(const QString& _s, bool force)
2059 {
2060     QString s(_s);
2061     for (int i = 0; i < s.length(); ) {
2062         if (!s[i].isPrint()) {
2063             if (QChar(s[i]).isHighSurrogate() && i + 1 < s.length() && QChar(s[i + 1]).isLowSurrogate()) {
2064                 const uint uc = QChar::surrogateToUcs4(s[i], s[i + 1]);
2065                 if (!QChar::isPrint(uc)) {
2066                     s.remove(i, 2);
2067                 } else {
2068                     i += 2;
2069                 }
2070                 continue;
2071             }
2072             s.remove(i, 1);
2073             continue;
2074         }
2075         ++i;
2076     }
2077     const bool changed = (s != cap_normal);
2078     if (!force && !changed) {
2079         return;
2080     }
2081     cap_normal = s;
2082     if (!force && !changed) {
2083         Q_EMIT captionChanged();
2084         return;
2085     }
2086 
2087     bool reset_name = force;
2088     bool was_suffix = (!cap_suffix.isEmpty());
2089     cap_suffix.clear();
2090     QString machine_suffix;
2091     if (!options->condensedTitle()) { // machine doesn't qualify for "clean"
2092         if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal())
2093             machine_suffix = QLatin1String(" <@") + QString::fromUtf8(clientMachine()->hostName()) + QLatin1Char('>') + LRM;
2094     }
2095     QString shortcut_suffix = shortcutCaptionSuffix();
2096     cap_suffix = machine_suffix + shortcut_suffix;
2097     if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) {
2098         int i = 2;
2099         do {
2100             cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM;
2101             i++;
2102         } while (findClientWithSameCaption());
2103         info->setVisibleName(caption().toUtf8().constData());
2104         reset_name = false;
2105     }
2106     if ((was_suffix && cap_suffix.isEmpty()) || reset_name) {
2107         // If it was new window, it may have old value still set, if the window is reused
2108         info->setVisibleName("");
2109         info->setVisibleIconName("");
2110     } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty())
2111         // Keep the same suffix in iconic name if it's set
2112         info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData());
2113 
2114     Q_EMIT captionChanged();
2115 }
2116 
updateCaption()2117 void X11Client::updateCaption()
2118 {
2119     setCaption(cap_normal, true);
2120 }
2121 
fetchIconicName()2122 void X11Client::fetchIconicName()
2123 {
2124     QString s;
2125     if (info->iconName() && info->iconName()[0] != '\0')
2126         s = QString::fromUtf8(info->iconName());
2127     else
2128         s = readNameProperty(window(), XCB_ATOM_WM_ICON_NAME);
2129     if (s != cap_iconic) {
2130         bool was_set = !cap_iconic.isEmpty();
2131         cap_iconic = s;
2132         if (!cap_suffix.isEmpty()) {
2133             if (!cap_iconic.isEmpty())  // Keep the same suffix in iconic name if it's set
2134                 info->setVisibleIconName(QString(s + cap_suffix).toUtf8().constData());
2135             else if (was_set)
2136                 info->setVisibleIconName("");
2137         }
2138     }
2139 }
2140 
setClientShown(bool shown)2141 void X11Client::setClientShown(bool shown)
2142 {
2143     if (isZombie())
2144         return; // Don't change shown status if this client is being deleted
2145     if (shown != hidden)
2146         return; // nothing to change
2147     hidden = !shown;
2148     if (shown) {
2149         map();
2150         takeFocus();
2151         autoRaise();
2152         FocusChain::self()->update(this, FocusChain::MakeFirst);
2153     } else {
2154         unmap();
2155         // Don't move tabs to the end of the list when another tab get's activated
2156         FocusChain::self()->update(this, FocusChain::MakeLast);
2157         addWorkspaceRepaint(visibleGeometry());
2158     }
2159 }
2160 
getMotifHints()2161 void X11Client::getMotifHints()
2162 {
2163     const bool wasClosable = isCloseable();
2164     const bool wasNoBorder = m_motif.noBorder();
2165     if (m_managed) // only on property change, initial read is prefetched
2166         m_motif.fetch();
2167     m_motif.read();
2168     if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) {
2169         // If we just got a hint telling us to hide decorations, we do so.
2170         if (m_motif.noBorder())
2171             noborder = rules()->checkNoBorder(true);
2172         // If the Motif hint is now telling us to show decorations, we only do so if the app didn't
2173         // instruct us to hide decorations in some other way, though.
2174         else if (!app_noborder)
2175             noborder = rules()->checkNoBorder(false);
2176     }
2177 
2178     // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too
2179     // mmaximize; - Ignore, bogus - Maximizing is basically just resizing
2180     const bool closabilityChanged = wasClosable != isCloseable();
2181     if (isManaged())
2182         updateDecoration(true);   // Check if noborder state has changed
2183     if (closabilityChanged) {
2184         Q_EMIT closeableChanged(isCloseable());
2185     }
2186 }
2187 
getIcons()2188 void X11Client::getIcons()
2189 {
2190     // First read icons from the window itself
2191     const QString themedIconName = iconFromDesktopFile();
2192     if (!themedIconName.isEmpty()) {
2193         setIcon(QIcon::fromTheme(themedIconName));
2194         return;
2195     }
2196     QIcon icon;
2197     auto readIcon = [this, &icon](int size, bool scale = true) {
2198         const QPixmap pix = KWindowSystem::icon(window(), size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, info);
2199         if (!pix.isNull()) {
2200             icon.addPixmap(pix);
2201         }
2202     };
2203     readIcon(16);
2204     readIcon(32);
2205     readIcon(48, false);
2206     readIcon(64, false);
2207     readIcon(128, false);
2208     if (icon.isNull()) {
2209         // Then try window group
2210         icon = group()->icon();
2211     }
2212     if (icon.isNull() && isTransient()) {
2213         // Then mainclients
2214         auto mainclients = mainClients();
2215         for (auto it = mainclients.constBegin();
2216                 it != mainclients.constEnd() && icon.isNull();
2217                 ++it) {
2218             if (!(*it)->icon().isNull()) {
2219                 icon = (*it)->icon();
2220                 break;
2221             }
2222         }
2223     }
2224     if (icon.isNull()) {
2225         // And if nothing else, load icon from classhint or xapp icon
2226         icon.addPixmap(KWindowSystem::icon(window(),  32,  32,  true, KWindowSystem::ClassHint | KWindowSystem::XApp, info));
2227         icon.addPixmap(KWindowSystem::icon(window(),  16,  16,  true, KWindowSystem::ClassHint | KWindowSystem::XApp, info));
2228         icon.addPixmap(KWindowSystem::icon(window(),  64,  64, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info));
2229         icon.addPixmap(KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info));
2230     }
2231     setIcon(icon);
2232 }
2233 
2234 /**
2235  * Returns \c true if X11Client wants to throttle resizes; otherwise returns \c false.
2236  */
wantsSyncCounter() const2237 bool X11Client::wantsSyncCounter() const
2238 {
2239     return true;
2240 }
2241 
getSyncCounter()2242 void X11Client::getSyncCounter()
2243 {
2244     if (!Xcb::Extensions::self()->isSyncAvailable())
2245         return;
2246     if (!wantsSyncCounter())
2247         return;
2248 
2249     Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1);
2250     const xcb_sync_counter_t counter = syncProp.value<xcb_sync_counter_t>(XCB_NONE);
2251     if (counter != XCB_NONE) {
2252         m_syncRequest.counter = counter;
2253         m_syncRequest.value.hi = 0;
2254         m_syncRequest.value.lo = 0;
2255         auto *c = connection();
2256         xcb_sync_set_counter(c, m_syncRequest.counter, m_syncRequest.value);
2257         if (m_syncRequest.alarm == XCB_NONE) {
2258             const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS;
2259             const uint32_t values[] = {
2260                 m_syncRequest.counter,
2261                 XCB_SYNC_VALUETYPE_RELATIVE,
2262                 XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION,
2263                 1
2264             };
2265             m_syncRequest.alarm = xcb_generate_id(c);
2266             auto cookie = xcb_sync_create_alarm_checked(c, m_syncRequest.alarm, mask, values);
2267             ScopedCPointer<xcb_generic_error_t> error(xcb_request_check(c, cookie));
2268             if (!error.isNull()) {
2269                 m_syncRequest.alarm = XCB_NONE;
2270             } else {
2271                 xcb_sync_change_alarm_value_list_t value;
2272                 memset(&value, 0, sizeof(value));
2273                 value.value.hi = 0;
2274                 value.value.lo = 1;
2275                 value.delta.hi = 0;
2276                 value.delta.lo = 1;
2277                 xcb_sync_change_alarm_aux(c, m_syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value);
2278             }
2279         }
2280     }
2281 }
2282 
2283 /**
2284  * Send the client a _NET_SYNC_REQUEST
2285  */
sendSyncRequest()2286 void X11Client::sendSyncRequest()
2287 {
2288     if (m_syncRequest.counter == XCB_NONE || m_syncRequest.isPending) {
2289         return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ...
2290     }
2291 
2292     if (!m_syncRequest.failsafeTimeout) {
2293         m_syncRequest.failsafeTimeout = new QTimer(this);
2294         connect(m_syncRequest.failsafeTimeout, &QTimer::timeout, this,
2295             [this]() {
2296                 // client does not respond to XSYNC requests in reasonable time, remove support
2297                 if (!ready_for_painting) {
2298                     // failed on initial pre-show request
2299                     setReadyForPainting();
2300                     setupWindowManagementInterface();
2301                     return;
2302                 }
2303                 // failed during resize
2304                 m_syncRequest.isPending = false;
2305                 m_syncRequest.counter = XCB_NONE;
2306                 m_syncRequest.alarm = XCB_NONE;
2307                 delete m_syncRequest.timeout;
2308                 delete m_syncRequest.failsafeTimeout;
2309                 m_syncRequest.timeout = nullptr;
2310                 m_syncRequest.failsafeTimeout = nullptr;
2311                 m_syncRequest.lastTimestamp = XCB_CURRENT_TIME;
2312             }
2313         );
2314         m_syncRequest.failsafeTimeout->setSingleShot(true);
2315     }
2316     // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client.
2317     // see events.cpp X11Client::syncEvent()
2318     m_syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000);
2319 
2320     // We increment before the notify so that after the notify
2321     // syncCounterSerial will equal the value we are expecting
2322     // in the acknowledgement
2323     const uint32_t oldLo = m_syncRequest.value.lo;
2324     m_syncRequest.value.lo++;
2325     if (oldLo > m_syncRequest.value.lo) {
2326         m_syncRequest.value.hi++;
2327     }
2328     if (m_syncRequest.lastTimestamp >= xTime()) {
2329         updateXTime();
2330     }
2331 
2332     // Send the message to client
2333     sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request,
2334                       m_syncRequest.value.lo, m_syncRequest.value.hi);
2335     m_syncRequest.isPending = true;
2336     m_syncRequest.lastTimestamp = xTime();
2337 }
2338 
wantsInput() const2339 bool X11Client::wantsInput() const
2340 {
2341     return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol));
2342 }
2343 
acceptsFocus() const2344 bool X11Client::acceptsFocus() const
2345 {
2346     return info->input();
2347 }
2348 
setBlockingCompositing(bool block)2349 void X11Client::setBlockingCompositing(bool block)
2350 {
2351     const bool usedToBlock = blocks_compositing;
2352     blocks_compositing = rules()->checkBlockCompositing(block && options->windowsBlockCompositing());
2353     if (usedToBlock != blocks_compositing) {
2354         Q_EMIT blockingCompositingChanged(blocks_compositing ? this : nullptr);
2355     }
2356 }
2357 
updateAllowedActions(bool force)2358 void X11Client::updateAllowedActions(bool force)
2359 {
2360     if (!isManaged() && !force)
2361         return;
2362     NET::Actions old_allowed_actions = NET::Actions(allowed_actions);
2363     allowed_actions = NET::Actions();
2364     if (isMovable())
2365         allowed_actions |= NET::ActionMove;
2366     if (isResizable())
2367         allowed_actions |= NET::ActionResize;
2368     if (isMinimizable())
2369         allowed_actions |= NET::ActionMinimize;
2370     if (isShadeable())
2371         allowed_actions |= NET::ActionShade;
2372     // Sticky state not supported
2373     if (isMaximizable())
2374         allowed_actions |= NET::ActionMax;
2375     if (userCanSetFullScreen())
2376         allowed_actions |= NET::ActionFullScreen;
2377     allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.)
2378     if (isCloseable())
2379         allowed_actions |= NET::ActionClose;
2380     if (old_allowed_actions == allowed_actions)
2381         return;
2382     // TODO: This could be delayed and compressed - It's only for pagers etc. anyway
2383     info->setAllowedActions(allowed_actions);
2384 
2385     // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes)
2386     const NET::Actions relevant = ~(NET::ActionMove|NET::ActionResize);
2387     if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) {
2388         if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) {
2389             Q_EMIT minimizeableChanged(allowed_actions & NET::ActionMinimize);
2390         }
2391         if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) {
2392             Q_EMIT shadeableChanged(allowed_actions & NET::ActionShade);
2393         }
2394         if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) {
2395             Q_EMIT maximizeableChanged(allowed_actions & NET::ActionMax);
2396         }
2397         if ((allowed_actions & NET::ActionClose) != (old_allowed_actions & NET::ActionClose)) {
2398             Q_EMIT closeableChanged(allowed_actions & NET::ActionClose);
2399         }
2400     }
2401 }
2402 
fetchActivities() const2403 Xcb::StringProperty X11Client::fetchActivities() const
2404 {
2405 #ifdef KWIN_BUILD_ACTIVITIES
2406     return Xcb::StringProperty(window(), atoms->activities);
2407 #else
2408     return Xcb::StringProperty();
2409 #endif
2410 }
2411 
readActivities(Xcb::StringProperty & property)2412 void X11Client::readActivities(Xcb::StringProperty &property)
2413 {
2414 #ifdef KWIN_BUILD_ACTIVITIES
2415     QStringList newActivitiesList;
2416     QString prop = QString::fromUtf8(property);
2417     activitiesDefined = !prop.isEmpty();
2418 
2419     if (prop == Activities::nullUuid()) {
2420         //copied from setOnAllActivities to avoid a redundant XChangeProperty.
2421         if (!activityList.isEmpty()) {
2422             activityList.clear();
2423             updateActivities(true);
2424         }
2425         return;
2426     }
2427     if (prop.isEmpty()) {
2428         //note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL'
2429         if (!activityList.isEmpty()) {
2430             activityList.clear();
2431             updateActivities(true);
2432         }
2433         return;
2434     }
2435 
2436     newActivitiesList = prop.split(u',');
2437 
2438     if (newActivitiesList == activityList)
2439         return; //expected change, it's ok.
2440 
2441     //otherwise, somebody else changed it. we need to validate before reacting.
2442     //if the activities are not synced, and there are existing clients with
2443     //activities specified, somebody has restarted kwin. we can not validate
2444     //activities in this case. we need to trust the old values.
2445     if (Activities::self() && Activities::self()->serviceStatus() != KActivities::Consumer::Unknown) {
2446         QStringList allActivities = Activities::self()->all();
2447         if (allActivities.isEmpty()) {
2448             qCDebug(KWIN_CORE) << "no activities!?!?";
2449             //don't touch anything, there's probably something bad going on and we don't wanna make it worse
2450             return;
2451         }
2452 
2453 
2454         for (int i = 0; i < newActivitiesList.size(); ++i) {
2455             if (! allActivities.contains(newActivitiesList.at(i))) {
2456                 qCDebug(KWIN_CORE) << "invalid:" << newActivitiesList.at(i);
2457                 newActivitiesList.removeAt(i--);
2458             }
2459         }
2460     }
2461     setOnActivities(newActivitiesList);
2462 #else
2463     Q_UNUSED(property)
2464 #endif
2465 }
2466 
checkActivities()2467 void X11Client::checkActivities()
2468 {
2469 #ifdef KWIN_BUILD_ACTIVITIES
2470     Xcb::StringProperty property = fetchActivities();
2471     readActivities(property);
2472 #endif
2473 }
2474 
setSessionActivityOverride(bool needed)2475 void X11Client::setSessionActivityOverride(bool needed)
2476 {
2477     sessionActivityOverride = needed;
2478     updateActivities(false);
2479 }
2480 
fetchFirstInTabBox() const2481 Xcb::Property X11Client::fetchFirstInTabBox() const
2482 {
2483     return Xcb::Property(false, m_client, atoms->kde_first_in_window_list,
2484                          atoms->kde_first_in_window_list, 0, 1);
2485 }
2486 
readFirstInTabBox(Xcb::Property & property)2487 void X11Client::readFirstInTabBox(Xcb::Property &property)
2488 {
2489     setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list));
2490 }
2491 
updateFirstInTabBox()2492 void X11Client::updateFirstInTabBox()
2493 {
2494     // TODO: move into KWindowInfo
2495     Xcb::Property property = fetchFirstInTabBox();
2496     readFirstInTabBox(property);
2497 }
2498 
fetchPreferredColorScheme() const2499 Xcb::StringProperty X11Client::fetchPreferredColorScheme() const
2500 {
2501     return Xcb::StringProperty(m_client, atoms->kde_color_sheme);
2502 }
2503 
readPreferredColorScheme(Xcb::StringProperty & property) const2504 QString X11Client::readPreferredColorScheme(Xcb::StringProperty &property) const
2505 {
2506     return rules()->checkDecoColor(QString::fromUtf8(property));
2507 }
2508 
preferredColorScheme() const2509 QString X11Client::preferredColorScheme() const
2510 {
2511     Xcb::StringProperty property = fetchPreferredColorScheme();
2512     return readPreferredColorScheme(property);
2513 }
2514 
isClient() const2515 bool X11Client::isClient() const
2516 {
2517     return true;
2518 }
2519 
windowType(bool direct,int supportedTypes) const2520 NET::WindowType X11Client::windowType(bool direct, int supportedTypes) const
2521 {
2522     // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK?
2523     if (supportedTypes == 0) {
2524         supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK;
2525     }
2526     NET::WindowType wt = info->windowType(NET::WindowTypes(supportedTypes));
2527     if (direct) {
2528         return wt;
2529     }
2530     NET::WindowType wt2 = rules()->checkType(wt);
2531     if (wt != wt2) {
2532         wt = wt2;
2533         info->setWindowType(wt);   // force hint change
2534     }
2535     // hacks here
2536     if (wt == NET::Unknown)   // this is more or less suggested in NETWM spec
2537         wt = isTransient() ? NET::Dialog : NET::Normal;
2538     return wt;
2539 }
2540 
cancelFocusOutTimer()2541 void X11Client::cancelFocusOutTimer()
2542 {
2543     if (m_focusOutTimer) {
2544         m_focusOutTimer->stop();
2545     }
2546 }
2547 
frameId() const2548 xcb_window_t X11Client::frameId() const
2549 {
2550     return m_frame;
2551 }
2552 
inputGeometry() const2553 QRect X11Client::inputGeometry() const
2554 {
2555     // Notice that the buffer geometry corresponds to the geometry of the frame window.
2556     if (isDecorated()) {
2557         return m_bufferGeometry + decoration()->resizeOnlyBorders();
2558     }
2559     return m_bufferGeometry;
2560 }
2561 
framePosToClientPos(const QPoint & point) const2562 QPoint X11Client::framePosToClientPos(const QPoint &point) const
2563 {
2564     int x = point.x();
2565     int y = point.y();
2566 
2567     if (isDecorated()) {
2568         x += borderLeft();
2569         y += borderTop();
2570     } else {
2571         x -= m_clientFrameExtents.left();
2572         y -= m_clientFrameExtents.top();
2573     }
2574 
2575     return QPoint(x, y);
2576 }
2577 
clientPosToFramePos(const QPoint & point) const2578 QPoint X11Client::clientPosToFramePos(const QPoint &point) const
2579 {
2580     int x = point.x();
2581     int y = point.y();
2582 
2583     if (isDecorated()) {
2584         x -= borderLeft();
2585         y -= borderTop();
2586     } else {
2587         x += m_clientFrameExtents.left();
2588         y += m_clientFrameExtents.top();
2589     }
2590 
2591     return QPoint(x, y);
2592 }
2593 
frameSizeToClientSize(const QSize & size) const2594 QSize X11Client::frameSizeToClientSize(const QSize &size) const
2595 {
2596     int width = size.width();
2597     int height = size.height();
2598 
2599     if (isDecorated()) {
2600         width -= borderLeft() + borderRight();
2601         height -= borderTop() + borderBottom();
2602     } else {
2603         width += m_clientFrameExtents.left() + m_clientFrameExtents.right();
2604         height += m_clientFrameExtents.top() + m_clientFrameExtents.bottom();
2605     }
2606 
2607     return QSize(width, height);
2608 }
2609 
clientSizeToFrameSize(const QSize & size) const2610 QSize X11Client::clientSizeToFrameSize(const QSize &size) const
2611 {
2612     int width = size.width();
2613     int height = size.height();
2614 
2615     if (isDecorated()) {
2616         width += borderLeft() + borderRight();
2617         height += borderTop() + borderBottom();
2618     } else {
2619         width -= m_clientFrameExtents.left() + m_clientFrameExtents.right();
2620         height -= m_clientFrameExtents.top() + m_clientFrameExtents.bottom();
2621     }
2622 
2623     return QSize(width, height);
2624 }
2625 
frameRectToBufferRect(const QRect & rect) const2626 QRect X11Client::frameRectToBufferRect(const QRect &rect) const
2627 {
2628     if (isDecorated()) {
2629         return rect;
2630     }
2631     return frameRectToClientRect(rect);
2632 }
2633 
inputTransformation() const2634 QMatrix4x4 X11Client::inputTransformation() const
2635 {
2636     QMatrix4x4 matrix;
2637     matrix.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y());
2638     return matrix;
2639 }
2640 
fetchShowOnScreenEdge() const2641 Xcb::Property X11Client::fetchShowOnScreenEdge() const
2642 {
2643     return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1);
2644 }
2645 
readShowOnScreenEdge(Xcb::Property & property)2646 void X11Client::readShowOnScreenEdge(Xcb::Property &property)
2647 {
2648     //value comes in two parts, edge in the lower byte
2649     //then the type in the upper byte
2650     // 0 = autohide
2651     // 1 = raise in front on activate
2652 
2653     const uint32_t value = property.value<uint32_t>(ElectricNone);
2654     ElectricBorder border = ElectricNone;
2655     switch (value & 0xFF) {
2656     case 0:
2657         border = ElectricTop;
2658         break;
2659     case 1:
2660         border = ElectricRight;
2661         break;
2662     case 2:
2663         border = ElectricBottom;
2664         break;
2665     case 3:
2666         border = ElectricLeft;
2667         break;
2668     }
2669     if (border != ElectricNone) {
2670         disconnect(m_edgeRemoveConnection);
2671         disconnect(m_edgeGeometryTrackingConnection);
2672         bool successfullyHidden = false;
2673 
2674         if (((value >> 8) & 0xFF) == 1) {
2675             setKeepBelow(true);
2676             successfullyHidden = keepBelow(); //request could have failed due to user kwin rules
2677 
2678             m_edgeRemoveConnection = connect(this, &AbstractClient::keepBelowChanged, this, [this](){
2679                 if (!keepBelow()) {
2680                     ScreenEdges::self()->reserve(this, ElectricNone);
2681                 }
2682             });
2683         } else {
2684             hideClient(true);
2685             successfullyHidden = isHiddenInternal();
2686 
2687             m_edgeGeometryTrackingConnection = connect(this, &X11Client::frameGeometryChanged, this, [this, border](){
2688                 hideClient(true);
2689                 ScreenEdges::self()->reserve(this, border);
2690             });
2691         }
2692 
2693         if (successfullyHidden) {
2694             ScreenEdges::self()->reserve(this, border);
2695         } else {
2696             ScreenEdges::self()->reserve(this, ElectricNone);
2697         }
2698     } else if (!property.isNull() && property->type != XCB_ATOM_NONE) {
2699         // property value is incorrect, delete the property
2700         // so that the client knows that it is not hidden
2701         xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show);
2702     } else {
2703         // restore
2704         // TODO: add proper unreserve
2705 
2706         //this will call showOnScreenEdge to reset the state
2707         disconnect(m_edgeGeometryTrackingConnection);
2708         ScreenEdges::self()->reserve(this, ElectricNone);
2709     }
2710 }
2711 
updateShowOnScreenEdge()2712 void X11Client::updateShowOnScreenEdge()
2713 {
2714     Xcb::Property property = fetchShowOnScreenEdge();
2715     readShowOnScreenEdge(property);
2716 }
2717 
showOnScreenEdge()2718 void X11Client::showOnScreenEdge()
2719 {
2720     disconnect(m_edgeRemoveConnection);
2721 
2722     hideClient(false);
2723     setKeepBelow(false);
2724     xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show);
2725 }
2726 
belongsToSameApplication(const AbstractClient * other,SameApplicationChecks checks) const2727 bool X11Client::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const
2728 {
2729     const X11Client *c2 = dynamic_cast<const X11Client *>(other);
2730     if (!c2) {
2731         return false;
2732     }
2733     return X11Client::belongToSameApplication(this, c2, checks);
2734 }
2735 
resizeIncrements() const2736 QSize X11Client::resizeIncrements() const
2737 {
2738     return m_geometryHints.resizeIncrements();
2739 }
2740 
fetchApplicationMenuServiceName() const2741 Xcb::StringProperty X11Client::fetchApplicationMenuServiceName() const
2742 {
2743     return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name);
2744 }
2745 
readApplicationMenuServiceName(Xcb::StringProperty & property)2746 void X11Client::readApplicationMenuServiceName(Xcb::StringProperty &property)
2747 {
2748     updateApplicationMenuServiceName(QString::fromUtf8(property));
2749 }
2750 
checkApplicationMenuServiceName()2751 void X11Client::checkApplicationMenuServiceName()
2752 {
2753     Xcb::StringProperty property = fetchApplicationMenuServiceName();
2754     readApplicationMenuServiceName(property);
2755 }
2756 
fetchApplicationMenuObjectPath() const2757 Xcb::StringProperty X11Client::fetchApplicationMenuObjectPath() const
2758 {
2759     return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path);
2760 }
2761 
readApplicationMenuObjectPath(Xcb::StringProperty & property)2762 void X11Client::readApplicationMenuObjectPath(Xcb::StringProperty &property)
2763 {
2764     updateApplicationMenuObjectPath(QString::fromUtf8(property));
2765 }
2766 
checkApplicationMenuObjectPath()2767 void X11Client::checkApplicationMenuObjectPath()
2768 {
2769     Xcb::StringProperty property = fetchApplicationMenuObjectPath();
2770     readApplicationMenuObjectPath(property);
2771 }
2772 
handleSync()2773 void X11Client::handleSync()
2774 {
2775     setReadyForPainting();
2776     setupWindowManagementInterface();
2777     m_syncRequest.isPending = false;
2778     if (m_syncRequest.failsafeTimeout) {
2779         m_syncRequest.failsafeTimeout->stop();
2780     }
2781     if (isInteractiveResize()) {
2782         if (m_syncRequest.timeout) {
2783             m_syncRequest.timeout->stop();
2784         }
2785         performInteractiveMoveResize();
2786         updateWindowPixmap();
2787     } else // setReadyForPainting does as well, but there's a small chance for resize syncs after the resize ended
2788         addRepaintFull();
2789 }
2790 
belongToSameApplication(const X11Client * c1,const X11Client * c2,SameApplicationChecks checks)2791 bool X11Client::belongToSameApplication(const X11Client *c1, const X11Client *c2, SameApplicationChecks checks)
2792 {
2793     bool same_app = false;
2794 
2795     // tests that definitely mean they belong together
2796     if (c1 == c2)
2797         same_app = true;
2798     else if (c1->isTransient() && c2->hasTransient(c1, true))
2799         same_app = true; // c1 has c2 as mainwindow
2800     else if (c2->isTransient() && c1->hasTransient(c2, true))
2801         same_app = true; // c2 has c1 as mainwindow
2802     else if (c1->group() == c2->group())
2803         same_app = true; // same group
2804     else if (c1->wmClientLeader() == c2->wmClientLeader()
2805             && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
2806             && c2->wmClientLeader() != c2->window()) // don't use in this test then
2807         same_app = true; // same client leader
2808 
2809     // tests that mean they most probably don't belong together
2810     else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses))
2811             || c1->wmClientMachine(false) != c2->wmClientMachine(false))
2812         ; // different processes
2813     else if (c1->wmClientLeader() != c2->wmClientLeader()
2814             && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
2815             && c2->wmClientLeader() != c2->window() // don't use in this test then
2816             && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses))
2817         ; // different client leader
2818     else if (!resourceMatch(c1, c2))
2819         ; // different apps
2820     else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive))
2821             && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses))
2822         ; // "different" apps
2823     else if (c1->pid() == 0 || c2->pid() == 0)
2824         ; // old apps that don't have _NET_WM_PID, consider them different
2825     // if they weren't found to match above
2826     else
2827         same_app = true; // looks like it's the same app
2828 
2829     return same_app;
2830 }
2831 
2832 // Non-transient windows with window role containing '#' are always
2833 // considered belonging to different applications (unless
2834 // the window role is exactly the same). KMainWindow sets
2835 // window role this way by default, and different KMainWindow
2836 // usually "are" different application from user's point of view.
2837 // This help with no-focus-stealing for e.g. konqy reusing.
2838 // On the other hand, if one of the windows is active, they are
2839 // considered belonging to the same application. This is for
2840 // the cases when opening new mainwindow directly from the application,
2841 // e.g. 'Open New Window' in konqy ( active_hack == true ).
sameAppWindowRoleMatch(const X11Client * c1,const X11Client * c2,bool active_hack)2842 bool X11Client::sameAppWindowRoleMatch(const X11Client *c1, const X11Client *c2, bool active_hack)
2843 {
2844     if (c1->isTransient()) {
2845         while (const X11Client *t = dynamic_cast<const X11Client *>(c1->transientFor()))
2846             c1 = t;
2847         if (c1->groupTransient())
2848             return c1->group() == c2->group();
2849 #if 0
2850         // if a group transient is in its own group, it didn't possibly have a group,
2851         // and therefore should be considered belonging to the same app like
2852         // all other windows from the same app
2853         || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
2854 #endif
2855     }
2856     if (c2->isTransient()) {
2857         while (const X11Client *t = dynamic_cast<const X11Client *>(c2->transientFor()))
2858             c2 = t;
2859         if (c2->groupTransient())
2860             return c1->group() == c2->group();
2861 #if 0
2862         || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
2863 #endif
2864     }
2865     int pos1 = c1->windowRole().indexOf('#');
2866     int pos2 = c2->windowRole().indexOf('#');
2867     if ((pos1 >= 0 && pos2 >= 0)) {
2868         if (!active_hack)     // without the active hack for focus stealing prevention,
2869             return c1 == c2; // different mainwindows are always different apps
2870         if (!c1->isActive() && !c2->isActive())
2871             return c1 == c2;
2872         else
2873             return true;
2874     }
2875     return true;
2876 }
2877 
2878 /*
2879 
2880  Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
2881 
2882  WM_TRANSIENT_FOR is basically means "this is my mainwindow".
2883  For NET::Unknown windows, transient windows are considered to be NET::Dialog
2884  windows, for compatibility with non-NETWM clients. KWin may adjust the value
2885  of this property in some cases (window pointing to itself or creating a loop,
2886  keeping NET::Splash windows above other windows from the same app, etc.).
2887 
2888  X11Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
2889  possibly being adjusted by KWin. X11Client::transient_for points to the Client
2890  this Client is transient for, or is NULL. If X11Client::transient_for_id is
2891  poiting to the root window, the window is considered to be transient
2892  for the whole window group, as suggested in NETWM 7.3.
2893 
2894  In the case of group transient window, X11Client::transient_for is NULL,
2895  and X11Client::groupTransient() returns true. Such window is treated as
2896  if it were transient for every window in its window group that has been
2897  mapped _before_ it (or, to be exact, was added to the same group before it).
2898  Otherwise two group transients can create loops, which can lead very very
2899  nasty things (bug #67914 and all its dupes).
2900 
2901  X11Client::original_transient_for_id is the value of the property, which
2902  may be different if X11Client::transient_for_id if e.g. forcing NET::Splash
2903  to be kept on top of its window group, or when the mainwindow is not mapped
2904  yet, in which case the window is temporarily made group transient,
2905  and when the mainwindow is mapped, transiency is re-evaluated.
2906 
2907  This can get a bit complicated with with e.g. two Konqueror windows created
2908  by the same process. They should ideally appear like two independent applications
2909  to the user. This should be accomplished by all windows in the same process
2910  having the same window group (needs to be changed in Qt at the moment), and
2911  using non-group transients poiting to their relevant mainwindow for toolwindows
2912  etc. KWin should handle both group and non-group transient dialogs well.
2913 
2914  In other words:
2915  - non-transient windows     : isTransient() == false
2916  - normal transients         : transientFor() != NULL
2917  - group transients          : groupTransient() == true
2918 
2919  - list of mainwindows       : mainClients()  (call once and loop over the result)
2920  - list of transients        : transients()
2921  - every window in the group : group()->members()
2922 */
2923 
fetchTransient() const2924 Xcb::TransientFor X11Client::fetchTransient() const
2925 {
2926     return Xcb::TransientFor(window());
2927 }
2928 
readTransientProperty(Xcb::TransientFor & transientFor)2929 void X11Client::readTransientProperty(Xcb::TransientFor &transientFor)
2930 {
2931     xcb_window_t new_transient_for_id = XCB_WINDOW_NONE;
2932     if (transientFor.getTransientFor(&new_transient_for_id)) {
2933         m_originalTransientForId = new_transient_for_id;
2934         new_transient_for_id = verifyTransientFor(new_transient_for_id, true);
2935     } else {
2936         m_originalTransientForId = XCB_WINDOW_NONE;
2937         new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false);
2938     }
2939     setTransient(new_transient_for_id);
2940 }
2941 
readTransient()2942 void X11Client::readTransient()
2943 {
2944     Xcb::TransientFor transientFor = fetchTransient();
2945     readTransientProperty(transientFor);
2946 }
2947 
setTransient(xcb_window_t new_transient_for_id)2948 void X11Client::setTransient(xcb_window_t new_transient_for_id)
2949 {
2950     if (new_transient_for_id != m_transientForId) {
2951         removeFromMainClients();
2952         X11Client *transient_for = nullptr;
2953         m_transientForId = new_transient_for_id;
2954         if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) {
2955             transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId);
2956             Q_ASSERT(transient_for != nullptr);   // verifyTransient() had to check this
2957             transient_for->addTransient(this);
2958         } // checkGroup() will check 'check_active_modal'
2959         setTransientFor(transient_for);
2960         checkGroup(nullptr, true);   // force, because transiency has changed
2961         updateLayer();
2962         workspace()->resetUpdateToolWindowsTimer();
2963         Q_EMIT transientChanged();
2964     }
2965 }
2966 
removeFromMainClients()2967 void X11Client::removeFromMainClients()
2968 {
2969     if (transientFor())
2970         transientFor()->removeTransient(this);
2971     if (groupTransient()) {
2972         for (auto it = group()->members().constBegin();
2973                 it != group()->members().constEnd();
2974                 ++it)
2975             (*it)->removeTransient(this);
2976     }
2977 }
2978 
2979 // *sigh* this transiency handling is madness :(
2980 // This one is called when destroying/releasing a window.
2981 // It makes sure this client is removed from all grouping
2982 // related lists.
cleanGrouping()2983 void X11Client::cleanGrouping()
2984 {
2985 //    qDebug() << "CLEANGROUPING:" << this;
2986 //    for ( auto it = group()->members().begin();
2987 //         it != group()->members().end();
2988 //         ++it )
2989 //        qDebug() << "CL:" << *it;
2990 //    QList<X11Client *> mains;
2991 //    mains = mainClients();
2992 //    for ( auto it = mains.begin();
2993 //         it != mains.end();
2994 //         ++it )
2995 //        qDebug() << "MN:" << *it;
2996     removeFromMainClients();
2997 //    qDebug() << "CLEANGROUPING2:" << this;
2998 //    for ( auto it = group()->members().begin();
2999 //         it != group()->members().end();
3000 //         ++it )
3001 //        qDebug() << "CL2:" << *it;
3002 //    mains = mainClients();
3003 //    for ( auto it = mains.begin();
3004 //         it != mains.end();
3005 //         ++it )
3006 //        qDebug() << "MN2:" << *it;
3007     for (auto it = transients().constBegin();
3008             it != transients().constEnd();
3009        ) {
3010         if ((*it)->transientFor() == this) {
3011             removeTransient(*it);
3012             it = transients().constBegin(); // restart, just in case something more has changed with the list
3013         } else
3014             ++it;
3015     }
3016 //    qDebug() << "CLEANGROUPING3:" << this;
3017 //    for ( auto it = group()->members().begin();
3018 //         it != group()->members().end();
3019 //         ++it )
3020 //        qDebug() << "CL3:" << *it;
3021 //    mains = mainClients();
3022 //    for ( auto it = mains.begin();
3023 //         it != mains.end();
3024 //         ++it )
3025 //        qDebug() << "MN3:" << *it;
3026     // HACK
3027     // removeFromMainClients() did remove 'this' from transient
3028     // lists of all group members, but then made windows that
3029     // were transient for 'this' group transient, which again
3030     // added 'this' to those transient lists :(
3031     QList<X11Client *> group_members = group()->members();
3032     group()->removeMember(this);
3033     in_group = nullptr;
3034     for (auto it = group_members.constBegin();
3035             it != group_members.constEnd();
3036             ++it)
3037         (*it)->removeTransient(this);
3038 //    qDebug() << "CLEANGROUPING4:" << this;
3039 //    for ( auto it = group_members.begin();
3040 //         it != group_members.end();
3041 //         ++it )
3042 //        qDebug() << "CL4:" << *it;
3043     m_transientForId = XCB_WINDOW_NONE;
3044 }
3045 
3046 // Make sure that no group transient is considered transient
3047 // for a window that is (directly or indirectly) transient for it
3048 // (including another group transients).
3049 // Non-group transients not causing loops are checked in verifyTransientFor().
checkGroupTransients()3050 void X11Client::checkGroupTransients()
3051 {
3052     for (auto it1 = group()->members().constBegin();
3053             it1 != group()->members().constEnd();
3054             ++it1) {
3055         if (!(*it1)->groupTransient())  // check all group transients in the group
3056             continue;                  // TODO optimize to check only the changed ones?
3057         for (auto it2 = group()->members().constBegin();
3058                 it2 != group()->members().constEnd();
3059                 ++it2) { // group transients can be transient only for others in the group,
3060             // so don't make them transient for the ones that are transient for it
3061             if (*it1 == *it2)
3062                 continue;
3063             for (AbstractClient* cl = (*it2)->transientFor();
3064                     cl != nullptr;
3065                     cl = cl->transientFor()) {
3066                 if (cl == *it1) {
3067                     // don't use removeTransient(), that would modify *it2 too
3068                     (*it2)->removeTransientFromList(*it1);
3069                     continue;
3070                 }
3071             }
3072             // if *it1 and *it2 are both group transients, and are transient for each other,
3073             // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
3074             // and should be therefore on top of *it1
3075             // TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
3076             if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true))
3077                 (*it2)->removeTransientFromList(*it1);
3078             // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
3079             // is added, make it transient only for W2, not for W1, because it's already indirectly
3080             // transient for it - the indirect transiency actually shouldn't break anything,
3081             // but it can lead to exponentially expensive operations (#95231)
3082             // TODO this is pretty slow as well
3083             for (auto it3 = group()->members().constBegin();
3084                     it3 != group()->members().constEnd();
3085                     ++it3) {
3086                 if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3)
3087                     continue;
3088                 if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) {
3089                     if ((*it2)->hasTransient(*it3, true))
3090                         (*it2)->removeTransientFromList(*it1);
3091                     if ((*it3)->hasTransient(*it2, true))
3092                         (*it3)->removeTransientFromList(*it1);
3093                 }
3094             }
3095         }
3096     }
3097 }
3098 
3099 /**
3100  * Check that the window is not transient for itself, and similar nonsense.
3101  */
verifyTransientFor(xcb_window_t new_transient_for,bool set)3102 xcb_window_t X11Client::verifyTransientFor(xcb_window_t new_transient_for, bool set)
3103 {
3104     xcb_window_t new_property_value = new_transient_for;
3105     // make sure splashscreens are shown above all their app's windows, even though
3106     // they're in Normal layer
3107     if (isSplash() && new_transient_for == XCB_WINDOW_NONE)
3108         new_transient_for = rootWindow();
3109     if (new_transient_for == XCB_WINDOW_NONE) {
3110         if (set)   // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
3111             new_property_value = new_transient_for = rootWindow();
3112         else
3113             return XCB_WINDOW_NONE;
3114     }
3115     if (new_transient_for == window()) { // pointing to self
3116         // also fix the property itself
3117         qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ;
3118         new_property_value = new_transient_for = rootWindow();
3119     }
3120 //  The transient_for window may be embedded in another application,
3121 //  so kwin cannot see it. Try to find the managed client for the
3122 //  window and fix the transient_for property if possible.
3123     xcb_window_t before_search = new_transient_for;
3124     while (new_transient_for != XCB_WINDOW_NONE
3125             && new_transient_for != rootWindow()
3126             && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) {
3127         Xcb::Tree tree(new_transient_for);
3128         if (tree.isNull()) {
3129             break;
3130         }
3131         new_transient_for = tree->parent;
3132     }
3133     if (X11Client *new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) {
3134         if (new_transient_for != before_search) {
3135             qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
3136                          << before_search << ", child of " << new_transient_for_client << ", adjusting.";
3137             new_property_value = new_transient_for; // also fix the property
3138         }
3139     } else
3140         new_transient_for = before_search; // nice try
3141 // loop detection
3142 // group transients cannot cause loops, because they're considered transient only for non-transient
3143 // windows in the group
3144     int count = 20;
3145     xcb_window_t loop_pos = new_transient_for;
3146     while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) {
3147         X11Client *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos);
3148         if (pos == nullptr)
3149             break;
3150         loop_pos = pos->m_transientForId;
3151         if (--count == 0 || pos == this) {
3152             qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ;
3153             new_transient_for = rootWindow();
3154         }
3155     }
3156     if (new_transient_for != rootWindow()
3157             && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) {
3158         // it's transient for a specific window, but that window is not mapped
3159         new_transient_for = rootWindow();
3160     }
3161     if (new_property_value != m_originalTransientForId)
3162         Xcb::setTransientFor(window(), new_property_value);
3163     return new_transient_for;
3164 }
3165 
addTransient(AbstractClient * cl)3166 void X11Client::addTransient(AbstractClient* cl)
3167 {
3168     AbstractClient::addTransient(cl);
3169     if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal())
3170         check_active_modal = true;
3171 //    qDebug() << "ADDTRANS:" << this << ":" << cl;
3172 //    qDebug() << kBacktrace();
3173 //    for ( auto it = transients_list.begin();
3174 //         it != transients_list.end();
3175 //         ++it )
3176 //        qDebug() << "AT:" << (*it);
3177 }
3178 
removeTransient(AbstractClient * cl)3179 void X11Client::removeTransient(AbstractClient* cl)
3180 {
3181 //    qDebug() << "REMOVETRANS:" << this << ":" << cl;
3182 //    qDebug() << kBacktrace();
3183     // cl is transient for this, but this is going away
3184     // make cl group transient
3185     AbstractClient::removeTransient(cl);
3186     if (cl->transientFor() == this) {
3187         if (X11Client *c = dynamic_cast<X11Client *>(cl)) {
3188             c->m_transientForId = XCB_WINDOW_NONE;
3189             c->setTransientFor(nullptr); // SELI
3190 // SELI       cl->setTransient( rootWindow());
3191             c->setTransient(XCB_WINDOW_NONE);
3192         }
3193     }
3194 }
3195 
3196 // A new window has been mapped. Check if it's not a mainwindow for this already existing window.
checkTransient(xcb_window_t w)3197 void X11Client::checkTransient(xcb_window_t w)
3198 {
3199     if (m_originalTransientForId != w)
3200         return;
3201     w = verifyTransientFor(w, true);
3202     setTransient(w);
3203 }
3204 
3205 // returns true if cl is the transient_for window for this client,
3206 // or recursively the transient_for window
hasTransient(const AbstractClient * cl,bool indirect) const3207 bool X11Client::hasTransient(const AbstractClient* cl, bool indirect) const
3208 {
3209     if (const X11Client *c = dynamic_cast<const X11Client *>(cl)) {
3210         // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
3211         QList<const X11Client *> set;
3212         return hasTransientInternal(c, indirect, set);
3213     }
3214     return false;
3215 }
3216 
hasTransientInternal(const X11Client * cl,bool indirect,QList<const X11Client * > & set) const3217 bool X11Client::hasTransientInternal(const X11Client *cl, bool indirect, QList<const X11Client *> &set) const
3218 {
3219     if (const X11Client *t = dynamic_cast<const X11Client *>(cl->transientFor())) {
3220         if (t == this)
3221             return true;
3222         if (!indirect)
3223             return false;
3224         if (set.contains(cl))
3225             return false;
3226         set.append(cl);
3227         return hasTransientInternal(t, indirect, set);
3228     }
3229     if (!cl->isTransient())
3230         return false;
3231     if (group() != cl->group())
3232         return false;
3233     // cl is group transient, search from top
3234     if (transients().contains(const_cast< X11Client *>(cl)))
3235         return true;
3236     if (!indirect)
3237         return false;
3238     if (set.contains(this))
3239         return false;
3240     set.append(this);
3241     for (auto it = transients().constBegin();
3242             it != transients().constEnd();
3243             ++it) {
3244         const X11Client *c = qobject_cast<const X11Client *>(*it);
3245         if (!c) {
3246             continue;
3247         }
3248         if (c->hasTransientInternal(cl, indirect, set))
3249             return true;
3250     }
3251     return false;
3252 }
3253 
mainClients() const3254 QList<AbstractClient*> X11Client::mainClients() const
3255 {
3256     if (!isTransient())
3257         return QList<AbstractClient*>();
3258     if (const AbstractClient *t = transientFor())
3259         return QList<AbstractClient*>{const_cast< AbstractClient* >(t)};
3260     QList<AbstractClient*> result;
3261     Q_ASSERT(group());
3262     for (auto it = group()->members().constBegin();
3263             it != group()->members().constEnd();
3264             ++it)
3265         if ((*it)->hasTransient(this, false))
3266             result.append(*it);
3267     return result;
3268 }
3269 
findModal(bool allow_itself)3270 AbstractClient* X11Client::findModal(bool allow_itself)
3271 {
3272     for (auto it = transients().constBegin();
3273             it != transients().constEnd();
3274             ++it)
3275         if (AbstractClient* ret = (*it)->findModal(true))
3276             return ret;
3277     if (isModal() && allow_itself)
3278         return this;
3279     return nullptr;
3280 }
3281 
3282 // X11Client::window_group only holds the contents of the hint,
3283 // but it should be used only to find the group, not for anything else
3284 // Argument is only when some specific group needs to be set.
checkGroup(Group * set_group,bool force)3285 void X11Client::checkGroup(Group* set_group, bool force)
3286 {
3287     Group* old_group = in_group;
3288     if (old_group != nullptr)
3289         old_group->ref(); // turn off automatic deleting
3290     if (set_group != nullptr) {
3291         if (set_group != in_group) {
3292             if (in_group != nullptr)
3293                 in_group->removeMember(this);
3294             in_group = set_group;
3295             in_group->addMember(this);
3296         }
3297     } else if (info->groupLeader() != XCB_WINDOW_NONE) {
3298         Group* new_group = workspace()->findGroup(info->groupLeader());
3299         X11Client *t = qobject_cast<X11Client *>(transientFor());
3300         if (t != nullptr && t->group() != new_group) {
3301             // move the window to the right group (e.g. a dialog provided
3302             // by different app, but transient for this one, so make it part of that group)
3303             new_group = t->group();
3304         }
3305         if (new_group == nullptr)   // doesn't exist yet
3306             new_group = new Group(info->groupLeader());
3307         if (new_group != in_group) {
3308             if (in_group != nullptr)
3309                 in_group->removeMember(this);
3310             in_group = new_group;
3311             in_group->addMember(this);
3312         }
3313     } else {
3314         if (X11Client *t = qobject_cast<X11Client *>(transientFor())) {
3315             // doesn't have window group set, but is transient for something
3316             // so make it part of that group
3317             Group* new_group = t->group();
3318             if (new_group != in_group) {
3319                 if (in_group != nullptr)
3320                     in_group->removeMember(this);
3321                 in_group = t->group();
3322                 in_group->addMember(this);
3323             }
3324         } else if (groupTransient()) {
3325             // group transient which actually doesn't have a group :(
3326             // try creating group with other windows with the same client leader
3327             Group* new_group = workspace()->findClientLeaderGroup(this);
3328             if (new_group == nullptr)
3329                 new_group = new Group(XCB_WINDOW_NONE);
3330             if (new_group != in_group) {
3331                 if (in_group != nullptr)
3332                     in_group->removeMember(this);
3333                 in_group = new_group;
3334                 in_group->addMember(this);
3335             }
3336         } else { // Not transient without a group, put it in its client leader group.
3337             // This might be stupid if grouping was used for e.g. taskbar grouping
3338             // or minimizing together the whole group, but as long as it is used
3339             // only for dialogs it's better to keep windows from one app in one group.
3340             Group* new_group = workspace()->findClientLeaderGroup(this);
3341             if (in_group != nullptr && in_group != new_group) {
3342                 in_group->removeMember(this);
3343                 in_group = nullptr;
3344             }
3345             if (new_group == nullptr)
3346                 new_group = new Group(XCB_WINDOW_NONE);
3347             if (in_group != new_group) {
3348                 in_group = new_group;
3349                 in_group->addMember(this);
3350             }
3351         }
3352     }
3353     if (in_group != old_group || force) {
3354         for (auto it = transients().constBegin();
3355                 it != transients().constEnd();
3356            ) {
3357             auto *c = *it;
3358             // group transients in the old group are no longer transient for it
3359             if (c->groupTransient() && c->group() != group()) {
3360                 removeTransientFromList(c);
3361                 it = transients().constBegin(); // restart, just in case something more has changed with the list
3362             } else
3363                 ++it;
3364         }
3365         if (groupTransient()) {
3366             // no longer transient for ones in the old group
3367             if (old_group != nullptr) {
3368                 for (auto it = old_group->members().constBegin();
3369                         it != old_group->members().constEnd();
3370                         ++it)
3371                     (*it)->removeTransient(this);
3372             }
3373             // and make transient for all in the new group
3374             for (auto it = group()->members().constBegin();
3375                     it != group()->members().constEnd();
3376                     ++it) {
3377                 if (*it == this)
3378                     break; // this means the window is only transient for windows mapped before it
3379                 (*it)->addTransient(this);
3380             }
3381         }
3382         // group transient splashscreens should be transient even for windows
3383         // in group mapped later
3384         for (auto it = group()->members().constBegin();
3385                 it != group()->members().constEnd();
3386                 ++it) {
3387             if (!(*it)->isSplash())
3388                 continue;
3389             if (!(*it)->groupTransient())
3390                 continue;
3391             if (*it == this || hasTransient(*it, true))    // TODO indirect?
3392                 continue;
3393             addTransient(*it);
3394         }
3395     }
3396     if (old_group != nullptr)
3397         old_group->deref(); // can be now deleted if empty
3398     checkGroupTransients();
3399     checkActiveModal();
3400     updateLayer();
3401 }
3402 
3403 // used by Workspace::findClientLeaderGroup()
changeClientLeaderGroup(Group * gr)3404 void X11Client::changeClientLeaderGroup(Group* gr)
3405 {
3406     // transientFor() != NULL are in the group of their mainwindow, so keep them there
3407     if (transientFor() != nullptr)
3408         return;
3409     // also don't change the group for window which have group set
3410     if (info->groupLeader())
3411         return;
3412     checkGroup(gr);   // change group
3413 }
3414 
3415 bool X11Client::check_active_modal = false;
3416 
checkActiveModal()3417 void X11Client::checkActiveModal()
3418 {
3419     // if the active window got new modal transient, activate it.
3420     // cannot be done in AddTransient(), because there may temporarily
3421     // exist loops, breaking findModal
3422     X11Client *check_modal = dynamic_cast<X11Client *>(workspace()->mostRecentlyActivatedClient());
3423     if (check_modal != nullptr && check_modal->check_active_modal) {
3424         X11Client *new_modal = dynamic_cast<X11Client *>(check_modal->findModal());
3425         if (new_modal != nullptr && new_modal != check_modal) {
3426             if (!new_modal->isManaged())
3427                 return; // postpone check until end of manage()
3428             workspace()->activateClient(new_modal);
3429         }
3430         check_modal->check_active_modal = false;
3431     }
3432 }
3433 
constrainClientSize(const QSize & size,SizeMode mode) const3434 QSize X11Client::constrainClientSize(const QSize &size, SizeMode mode) const
3435 {
3436     int w = size.width();
3437     int h = size.height();
3438 
3439     if (w < 1) w = 1;
3440     if (h < 1) h = 1;
3441 
3442     // basesize, minsize, maxsize, paspect and resizeinc have all values defined,
3443     // even if they're not set in flags - see getWmNormalHints()
3444     QSize min_size = minSize();
3445     QSize max_size = maxSize();
3446     if (isDecorated()) {
3447         QSize decominsize(0, 0);
3448         QSize border_size(borderLeft() + borderRight(), borderTop() + borderBottom());
3449         if (border_size.width() > decominsize.width())  // just in case
3450             decominsize.setWidth(border_size.width());
3451         if (border_size.height() > decominsize.height())
3452             decominsize.setHeight(border_size.height());
3453         if (decominsize.width() > min_size.width())
3454             min_size.setWidth(decominsize.width());
3455         if (decominsize.height() > min_size.height())
3456             min_size.setHeight(decominsize.height());
3457     }
3458     w = qMin(max_size.width(), w);
3459     h = qMin(max_size.height(), h);
3460     w = qMax(min_size.width(), w);
3461     h = qMax(min_size.height(), h);
3462 
3463     if (!rules()->checkStrictGeometry(!isFullScreen())) {
3464         // Disobey increments and aspect by explicit rule.
3465         return QSize(w, h);
3466     }
3467 
3468     int width_inc = m_geometryHints.resizeIncrements().width();
3469     int height_inc = m_geometryHints.resizeIncrements().height();
3470     int basew_inc = m_geometryHints.baseSize().width();
3471     int baseh_inc = m_geometryHints.baseSize().height();
3472     if (!m_geometryHints.hasBaseSize()) {
3473         basew_inc = m_geometryHints.minSize().width();
3474         baseh_inc = m_geometryHints.minSize().height();
3475     }
3476     w = int((w - basew_inc) / width_inc) * width_inc + basew_inc;
3477     h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc;
3478 // code for aspect ratios based on code from FVWM
3479     /*
3480      * The math looks like this:
3481      *
3482      * minAspectX    dwidth     maxAspectX
3483      * ---------- <= ------- <= ----------
3484      * minAspectY    dheight    maxAspectY
3485      *
3486      * If that is multiplied out, then the width and height are
3487      * invalid in the following situations:
3488      *
3489      * minAspectX * dheight > minAspectY * dwidth
3490      * maxAspectX * dheight < maxAspectY * dwidth
3491      *
3492      */
3493     if (m_geometryHints.hasAspect()) {
3494         double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT
3495         double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise
3496         double max_aspect_w = m_geometryHints.maxAspect().width();
3497         double max_aspect_h = m_geometryHints.maxAspect().height();
3498         // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments,
3499         // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time,
3500         // and I have no idea how it works, let's hope nobody relies on that.
3501         const QSize baseSize = m_geometryHints.baseSize();
3502         w -= baseSize.width();
3503         h -= baseSize.height();
3504         int max_width = max_size.width() - baseSize.width();
3505         int min_width = min_size.width() - baseSize.width();
3506         int max_height = max_size.height() - baseSize.height();
3507         int min_height = min_size.height() - baseSize.height();
3508 #define ASPECT_CHECK_GROW_W \
3509     if ( min_aspect_w * h > min_aspect_h * w ) \
3510     { \
3511         int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \
3512         if ( w + delta <= max_width ) \
3513             w += delta; \
3514     }
3515 #define ASPECT_CHECK_SHRINK_H_GROW_W \
3516     if ( min_aspect_w * h > min_aspect_h * w ) \
3517     { \
3518         int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \
3519         if ( h - delta >= min_height ) \
3520             h -= delta; \
3521         else \
3522         { \
3523             int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \
3524             if ( w + delta <= max_width ) \
3525                 w += delta; \
3526         } \
3527     }
3528 #define ASPECT_CHECK_GROW_H \
3529     if ( max_aspect_w * h < max_aspect_h * w ) \
3530     { \
3531         int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \
3532         if ( h + delta <= max_height ) \
3533             h += delta; \
3534     }
3535 #define ASPECT_CHECK_SHRINK_W_GROW_H \
3536     if ( max_aspect_w * h < max_aspect_h * w ) \
3537     { \
3538         int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \
3539         if ( w - delta >= min_width ) \
3540             w -= delta; \
3541         else \
3542         { \
3543             int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \
3544             if ( h + delta <= max_height ) \
3545                 h += delta; \
3546         } \
3547     }
3548         switch(mode) {
3549         case SizeModeAny:
3550 #if 0 // make SizeModeAny equal to SizeModeFixedW - prefer keeping fixed width,
3551             // so that changing aspect ratio to a different value and back keeps the same size (#87298)
3552             {
3553                 ASPECT_CHECK_SHRINK_H_GROW_W
3554                 ASPECT_CHECK_SHRINK_W_GROW_H
3555                 ASPECT_CHECK_GROW_H
3556                 ASPECT_CHECK_GROW_W
3557                 break;
3558             }
3559 #endif
3560         case SizeModeFixedW: {
3561             // the checks are order so that attempts to modify height are first
3562             ASPECT_CHECK_GROW_H
3563             ASPECT_CHECK_SHRINK_H_GROW_W
3564             ASPECT_CHECK_SHRINK_W_GROW_H
3565             ASPECT_CHECK_GROW_W
3566             break;
3567         }
3568         case SizeModeFixedH: {
3569             ASPECT_CHECK_GROW_W
3570             ASPECT_CHECK_SHRINK_W_GROW_H
3571             ASPECT_CHECK_SHRINK_H_GROW_W
3572             ASPECT_CHECK_GROW_H
3573             break;
3574         }
3575         case SizeModeMax: {
3576             // first checks that try to shrink
3577             ASPECT_CHECK_SHRINK_H_GROW_W
3578             ASPECT_CHECK_SHRINK_W_GROW_H
3579             ASPECT_CHECK_GROW_W
3580             ASPECT_CHECK_GROW_H
3581             break;
3582         }
3583         }
3584 #undef ASPECT_CHECK_SHRINK_H_GROW_W
3585 #undef ASPECT_CHECK_SHRINK_W_GROW_H
3586 #undef ASPECT_CHECK_GROW_W
3587 #undef ASPECT_CHECK_GROW_H
3588         w += baseSize.width();
3589         h += baseSize.height();
3590     }
3591 
3592     return QSize(w, h);
3593 }
3594 
3595 /**
3596  * Gets the client's normal WM hints and reconfigures itself respectively.
3597  */
getWmNormalHints()3598 void X11Client::getWmNormalHints()
3599 {
3600     const bool hadFixedAspect = m_geometryHints.hasAspect();
3601     // roundtrip to X server
3602     m_geometryHints.fetch();
3603     m_geometryHints.read();
3604 
3605     if (!hadFixedAspect && m_geometryHints.hasAspect()) {
3606         // align to eventual new constraints
3607         maximize(max_mode);
3608     }
3609     if (isManaged()) {
3610         // update to match restrictions
3611         QSize new_size = adjustedSize();
3612         if (new_size != size() && !isFullScreen()) {
3613             QRect origClientGeometry = m_clientGeometry;
3614             resizeWithChecks(new_size);
3615             if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
3616                 // try to keep the window in its xinerama screen if possible,
3617                 // if that fails at least keep it visible somewhere
3618                 QRect area = workspace()->clientArea(MovementArea, this);
3619                 if (area.contains(origClientGeometry))
3620                     keepInArea(area);
3621                 area = workspace()->clientArea(WorkArea, this);
3622                 if (area.contains(origClientGeometry))
3623                     keepInArea(area);
3624             }
3625         }
3626     }
3627     updateAllowedActions(); // affects isResizeable()
3628 }
3629 
minSize() const3630 QSize X11Client::minSize() const
3631 {
3632     return rules()->checkMinSize(m_geometryHints.minSize());
3633 }
3634 
maxSize() const3635 QSize X11Client::maxSize() const
3636 {
3637     return rules()->checkMaxSize(m_geometryHints.maxSize());
3638 }
3639 
basicUnit() const3640 QSize X11Client::basicUnit() const
3641 {
3642     return m_geometryHints.resizeIncrements();
3643 }
3644 
3645 /**
3646  * Auxiliary function to inform the client about the current window
3647  * configuration.
3648  */
sendSyntheticConfigureNotify()3649 void X11Client::sendSyntheticConfigureNotify()
3650 {
3651     // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy
3652     // 32 unconditionally. Use a union to ensure we don't disclose stack memory.
3653     union {
3654         xcb_configure_notify_event_t event;
3655         char buffer[32];
3656     } u;
3657     static_assert(sizeof(u.event) < 32, "wouldn't need the union otherwise");
3658     memset(&u, 0, sizeof(u));
3659     xcb_configure_notify_event_t &c = u.event;
3660     u.event.response_type = XCB_CONFIGURE_NOTIFY;
3661     u.event.event = window();
3662     u.event.window = window();
3663     u.event.x = m_clientGeometry.x();
3664     u.event.y = m_clientGeometry.y();
3665     u.event.width = m_clientGeometry.width();
3666     u.event.height = m_clientGeometry.height();
3667     u.event.border_width = 0;
3668     u.event.above_sibling = XCB_WINDOW_NONE;
3669     u.event.override_redirect = 0;
3670     xcb_send_event(connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast<const char*>(&u));
3671     xcb_flush(connection());
3672 }
3673 
gravityAdjustment(xcb_gravity_t gravity) const3674 QPoint X11Client::gravityAdjustment(xcb_gravity_t gravity) const
3675 {
3676     int dx = 0;
3677     int dy = 0;
3678 
3679     // dx, dy specify how the client window moves to make space for the frame.
3680     // In general we have to compute the reference point and from that figure
3681     // out how much we need to shift the client, however given that we ignore
3682     // the border width attribute and the extents of the server-side decoration
3683     // are known in advance, we can simplify the math quite a bit and express
3684     // the required window gravity adjustment in terms of border sizes.
3685     switch(gravity) {
3686     case XCB_GRAVITY_NORTH_WEST: // move down right
3687     default:
3688         dx = borderLeft();
3689         dy = borderTop();
3690         break;
3691     case XCB_GRAVITY_NORTH: // move right
3692         dx = 0;
3693         dy = borderTop();
3694         break;
3695     case XCB_GRAVITY_NORTH_EAST: // move down left
3696         dx = -borderRight();
3697         dy = borderTop();
3698         break;
3699     case XCB_GRAVITY_WEST: // move right
3700         dx = borderLeft();
3701         dy = 0;
3702         break;
3703     case XCB_GRAVITY_CENTER:
3704         dx = (borderLeft() - borderRight()) / 2;
3705         dy = (borderTop() - borderBottom()) / 2;
3706         break;
3707     case XCB_GRAVITY_STATIC: // don't move
3708         dx = 0;
3709         dy = 0;
3710         break;
3711     case XCB_GRAVITY_EAST: // move left
3712         dx = -borderRight();
3713         dy = 0;
3714         break;
3715     case XCB_GRAVITY_SOUTH_WEST: // move up right
3716         dx = borderLeft() ;
3717         dy = -borderBottom();
3718         break;
3719     case XCB_GRAVITY_SOUTH: // move up
3720         dx = 0;
3721         dy = -borderBottom();
3722         break;
3723     case XCB_GRAVITY_SOUTH_EAST: // move up left
3724         dx = -borderRight();
3725         dy = -borderBottom();
3726         break;
3727     }
3728 
3729     return QPoint(dx, dy);
3730 }
3731 
calculateGravitation(bool invert) const3732 const QPoint X11Client::calculateGravitation(bool invert) const
3733 {
3734     const QPoint adjustment = gravityAdjustment(m_geometryHints.windowGravity());
3735 
3736     // translate from client movement to frame movement
3737     const int dx = adjustment.x() - borderLeft();
3738     const int dy = adjustment.y() - borderTop();
3739 
3740     if (!invert)
3741         return QPoint(x() + dx, y() + dy);
3742     else
3743         return QPoint(x() - dx, y() - dy);
3744 }
3745 
configureRequest(int value_mask,int rx,int ry,int rw,int rh,int gravity,bool from_tool)3746 void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool)
3747 {
3748     const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
3749     const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
3750     const int configureGeometryMask = configurePositionMask | configureSizeMask;
3751 
3752     // "maximized" is a user setting -> we do not allow the client to resize itself
3753     // away from this & against the users explicit wish
3754     qCDebug(KWIN_CORE) << this << bool(value_mask & configureGeometryMask) <<
3755                             bool(maximizeMode() & MaximizeVertical) <<
3756                             bool(maximizeMode() & MaximizeHorizontal);
3757 
3758     // we want to (partially) ignore the request when the window is somehow maximized or quicktiled
3759     bool ignore = !app_noborder && (quickTileMode() != QuickTileMode(QuickTileFlag::None) || maximizeMode() != MaximizeRestore);
3760     // however, the user shall be able to force obedience despite and also disobedience in general
3761     ignore = rules()->checkIgnoreGeometry(ignore);
3762     if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it.
3763         updateQuickTileMode(QuickTileFlag::None);
3764         max_mode = MaximizeRestore;
3765         Q_EMIT quickTileModeChanged();
3766     } else if (!app_noborder && quickTileMode() == QuickTileMode(QuickTileFlag::None) &&
3767         (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) {
3768         // ignoring can be, because either we do, or the user does explicitly not want it.
3769         // for partially maximized windows we want to allow configures in the other dimension.
3770         // so we've to ask the user again - to know whether we just ignored for the partial maximization.
3771         // the problem here is, that the user can explicitly permit configure requests - even for maximized windows!
3772         // we cannot distinguish that from passing "false" for partially maximized windows.
3773         ignore = rules()->checkIgnoreGeometry(false);
3774         if (!ignore) { // the user is not interested, so we fix up dimensions
3775             if (maximizeMode() == MaximizeVertical)
3776                 value_mask &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT);
3777             if (maximizeMode() == MaximizeHorizontal)
3778                 value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH);
3779             if (!(value_mask & configureGeometryMask)) {
3780                 ignore = true; // the modification turned the request void
3781             }
3782         }
3783     }
3784 
3785     if (ignore) {
3786         qCDebug(KWIN_CORE) << "DENIED";
3787         return; // nothing to (left) to do for use - bugs #158974, #252314, #321491
3788     }
3789 
3790     qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & configureGeometryMask);
3791 
3792     if (gravity == 0)   // default (nonsense) value for the argument
3793         gravity = m_geometryHints.windowGravity();
3794     if (value_mask & configurePositionMask) {
3795         QPoint new_pos = framePosToClientPos(pos());
3796         new_pos -= gravityAdjustment(xcb_gravity_t(gravity));
3797         if (value_mask & XCB_CONFIG_WINDOW_X) {
3798             new_pos.setX(rx);
3799         }
3800         if (value_mask & XCB_CONFIG_WINDOW_Y) {
3801             new_pos.setY(ry);
3802         }
3803         // clever(?) workaround for applications like xv that want to set
3804         // the location to the current location but miscalculate the
3805         // frame size due to kwin being a double-reparenting window
3806         // manager
3807         if (new_pos.x() == m_clientGeometry.x() && new_pos.y() == m_clientGeometry.y()
3808                 && gravity == XCB_GRAVITY_NORTH_WEST && !from_tool) {
3809             new_pos.setX(x());
3810             new_pos.setY(y());
3811         }
3812         new_pos += gravityAdjustment(xcb_gravity_t(gravity));
3813         new_pos = clientPosToFramePos(new_pos);
3814 
3815         int nw = clientSize().width();
3816         int nh = clientSize().height();
3817         if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
3818             nw = rw;
3819         }
3820         if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
3821             nh = rh;
3822         }
3823         const QSize requestedClientSize = constrainClientSize(QSize(nw, nh));
3824         QSize requestedFrameSize = clientSizeToFrameSize(requestedClientSize);
3825         requestedFrameSize = rules()->checkSize(requestedFrameSize);
3826         new_pos = rules()->checkPosition(new_pos);
3827 
3828         AbstractOutput *newOutput = kwinApp()->platform()->outputAt(QRect(new_pos, requestedFrameSize).center());
3829         if (newOutput != rules()->checkOutput(newOutput)) {
3830             return; // not allowed by rule
3831         }
3832 
3833         QRect origClientGeometry = m_clientGeometry;
3834         GeometryUpdatesBlocker blocker(this);
3835         move(new_pos);
3836         resize(requestedFrameSize);
3837         QRect area = workspace()->clientArea(WorkArea, this);
3838         if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()
3839                 && area.contains(origClientGeometry))
3840             keepInArea(area);
3841 
3842         // this is part of the kicker-xinerama-hack... it should be
3843         // safe to remove when kicker gets proper ExtendedStrut support;
3844         // see Workspace::updateClientArea() and
3845         // X11Client::adjustedClientArea()
3846         if (hasStrut())
3847             workspace() -> updateClientArea();
3848     }
3849 
3850     if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) {     // pure resize
3851         int nw = clientSize().width();
3852         int nh = clientSize().height();
3853         if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
3854             nw = rw;
3855         }
3856         if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
3857             nh = rh;
3858         }
3859 
3860         const QSize requestedClientSize = constrainClientSize(QSize(nw, nh));
3861         QSize requestedFrameSize = clientSizeToFrameSize(requestedClientSize);
3862         requestedFrameSize = rules()->checkSize(requestedFrameSize);
3863 
3864         if (requestedFrameSize != size()) { // don't restore if some app sets its own size again
3865             QRect origClientGeometry = m_clientGeometry;
3866             GeometryUpdatesBlocker blocker(this);
3867             resizeWithChecks(requestedFrameSize, xcb_gravity_t(gravity));
3868             if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
3869                 // try to keep the window in its xinerama screen if possible,
3870                 // if that fails at least keep it visible somewhere
3871                 QRect area = workspace()->clientArea(MovementArea, this);
3872                 if (area.contains(origClientGeometry))
3873                     keepInArea(area);
3874                 area = workspace()->clientArea(WorkArea, this);
3875                 if (area.contains(origClientGeometry))
3876                     keepInArea(area);
3877             }
3878         }
3879     }
3880     setGeometryRestore(frameGeometry());
3881     // No need to send synthetic configure notify event here, either it's sent together
3882     // with geometry change, or there's no need to send it.
3883     // Handling of the real ConfigureRequest event forces sending it, as there it's necessary.
3884 }
3885 
resizeWithChecks(int w,int h,xcb_gravity_t gravity)3886 void X11Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity)
3887 {
3888     Q_ASSERT(!shade_geometry_change);
3889     if (isShade()) {
3890         if (h == borderTop() + borderBottom()) {
3891             qCWarning(KWIN_CORE) << "Shaded geometry passed for size:" ;
3892         }
3893     }
3894     int newx = x();
3895     int newy = y();
3896     QRect area = workspace()->clientArea(WorkArea, this);
3897     // don't allow growing larger than workarea
3898     if (w > area.width())
3899         w = area.width();
3900     if (h > area.height())
3901         h = area.height();
3902     QSize tmp = constrainFrameSize(QSize(w, h));    // checks size constraints, including min/max size
3903     w = tmp.width();
3904     h = tmp.height();
3905     if (gravity == 0) {
3906         gravity = m_geometryHints.windowGravity();
3907     }
3908     switch(gravity) {
3909     case XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move
3910     default:
3911         break;
3912     case XCB_GRAVITY_NORTH: // middle of top border doesn't move
3913         newx = (newx + width() / 2) - (w / 2);
3914         break;
3915     case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move
3916         newx = newx + width() - w;
3917         break;
3918     case XCB_GRAVITY_WEST: // middle of left border doesn't move
3919         newy = (newy + height() / 2) - (h / 2);
3920         break;
3921     case XCB_GRAVITY_CENTER: // middle point doesn't move
3922         newx = (newx + width() / 2) - (w / 2);
3923         newy = (newy + height() / 2) - (h / 2);
3924         break;
3925     case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move
3926         // since decoration doesn't change, equal to NorthWestGravity
3927         break;
3928     case XCB_GRAVITY_EAST: // // middle of right border doesn't move
3929         newx = newx + width() - w;
3930         newy = (newy + height() / 2) - (h / 2);
3931         break;
3932     case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move
3933         newy = newy + height() - h;
3934         break;
3935     case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move
3936         newx = (newx + width() / 2) - (w / 2);
3937         newy = newy + height() - h;
3938         break;
3939     case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move
3940         newx = newx + width() - w;
3941         newy = newy + height() - h;
3942         break;
3943     }
3944     moveResize(QRect{newx, newy, w, h});
3945 }
3946 
3947 // _NET_MOVERESIZE_WINDOW
NETMoveResizeWindow(int flags,int x,int y,int width,int height)3948 void X11Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height)
3949 {
3950     int gravity = flags & 0xff;
3951     int value_mask = 0;
3952     if (flags & (1 << 8)) {
3953         value_mask |= XCB_CONFIG_WINDOW_X;
3954     }
3955     if (flags & (1 << 9)) {
3956         value_mask |= XCB_CONFIG_WINDOW_Y;
3957     }
3958     if (flags & (1 << 10)) {
3959         value_mask |= XCB_CONFIG_WINDOW_WIDTH;
3960     }
3961     if (flags & (1 << 11)) {
3962         value_mask |= XCB_CONFIG_WINDOW_HEIGHT;
3963     }
3964     configureRequest(value_mask, x, y, width, height, gravity, true);
3965 }
3966 
isMovable() const3967 bool X11Client::isMovable() const
3968 {
3969     if (!hasNETSupport() && !m_motif.move()) {
3970         return false;
3971     }
3972     if (isFullScreen())
3973         return false;
3974     if (isSpecialWindow() && !isSplash() && !isToolbar())  // allow moving of splashscreens :)
3975         return false;
3976     if (rules()->checkPosition(invalidPoint) != invalidPoint)     // forced position
3977         return false;
3978     return true;
3979 }
3980 
isMovableAcrossScreens() const3981 bool X11Client::isMovableAcrossScreens() const
3982 {
3983     if (!hasNETSupport() && !m_motif.move()) {
3984         return false;
3985     }
3986     if (isSpecialWindow() && !isSplash() && !isToolbar())  // allow moving of splashscreens :)
3987         return false;
3988     if (rules()->checkPosition(invalidPoint) != invalidPoint)     // forced position
3989         return false;
3990     return true;
3991 }
3992 
isResizable() const3993 bool X11Client::isResizable() const
3994 {
3995     if (!hasNETSupport() && !m_motif.resize()) {
3996         return false;
3997     }
3998     if (isFullScreen())
3999         return false;
4000     if (isSpecialWindow() || isSplash() || isToolbar())
4001         return false;
4002     if (rules()->checkSize(QSize()).isValid())   // forced size
4003         return false;
4004     const Position mode = interactiveMoveResizePointerMode();
4005     if ((mode == PositionTop || mode == PositionTopLeft || mode == PositionTopRight ||
4006          mode == PositionLeft || mode == PositionBottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint)
4007         return false;
4008 
4009     QSize min = minSize();
4010     QSize max = maxSize();
4011     return min.width() < max.width() || min.height() < max.height();
4012 }
4013 
isMaximizable() const4014 bool X11Client::isMaximizable() const
4015 {
4016     if (!isResizable() || isToolbar())  // SELI isToolbar() ?
4017         return false;
4018     if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore)
4019         return true;
4020     return false;
4021 }
4022 
4023 
4024 /**
4025  * Reimplemented to inform the client about the new window position.
4026  */
moveResizeInternal(const QRect & rect,MoveResizeMode mode)4027 void X11Client::moveResizeInternal(const QRect &rect, MoveResizeMode mode)
4028 {
4029     // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry,
4030     // simply because there are too many places dealing with geometry. Those places
4031     // ignore shaded state and use normal geometry, which they usually should get
4032     // from adjustedSize(). Such geometry comes here, and if the window is shaded,
4033     // the geometry is used only for client_size, since that one is not used when
4034     // shading. Then the frame geometry is adjusted for the shaded geometry.
4035     // This gets more complicated in the case the code does only something like
4036     // setGeometry( geometry()) - geometry() will return the shaded frame geometry.
4037     // Such code is wrong and should be changed to handle the case when the window is shaded,
4038     // for example using X11Client::clientSize()
4039 
4040     QRect frameGeometry = rect;
4041 
4042     if (shade_geometry_change)
4043         ; // nothing
4044     else if (isShade()) {
4045         if (frameGeometry.height() == borderTop() + borderBottom()) {
4046             qCDebug(KWIN_CORE) << "Shaded geometry passed for size:";
4047         } else {
4048             m_clientGeometry = frameRectToClientRect(frameGeometry);
4049             frameGeometry.setHeight(borderTop() + borderBottom());
4050         }
4051     } else {
4052         m_clientGeometry = frameRectToClientRect(frameGeometry);
4053     }
4054     m_frameGeometry = frameGeometry;
4055     m_bufferGeometry = frameRectToBufferRect(frameGeometry);
4056 
4057     if (pendingMoveResizeMode() == MoveResizeMode::None &&
4058             m_lastBufferGeometry == m_bufferGeometry &&
4059             m_lastFrameGeometry == m_frameGeometry &&
4060             m_lastClientGeometry == m_clientGeometry) {
4061         return;
4062     }
4063     if (areGeometryUpdatesBlocked()) {
4064         setPendingMoveResizeMode(mode);
4065         return;
4066     }
4067 
4068     const QRect oldBufferGeometry = m_lastBufferGeometry;
4069     const QRect oldFrameGeometry = m_lastFrameGeometry;
4070     const QRect oldClientGeometry = m_lastClientGeometry;
4071 
4072     updateServerGeometry();
4073     updateWindowRules(Rules::Position|Rules::Size);
4074 
4075     m_lastBufferGeometry = m_bufferGeometry;
4076     m_lastFrameGeometry = m_frameGeometry;
4077     m_lastClientGeometry = m_clientGeometry;
4078 
4079     if (isActive()) {
4080         workspace()->setActiveOutput(output());
4081     }
4082     workspace()->updateStackingOrder();
4083 
4084     if (oldBufferGeometry != m_bufferGeometry) {
4085         Q_EMIT bufferGeometryChanged(this, oldBufferGeometry);
4086     }
4087     if (oldClientGeometry != m_clientGeometry) {
4088         Q_EMIT clientGeometryChanged(this, oldClientGeometry);
4089     }
4090     if (oldFrameGeometry != m_frameGeometry) {
4091         Q_EMIT frameGeometryChanged(this, oldFrameGeometry);
4092     }
4093     Q_EMIT geometryShapeChanged(this, oldFrameGeometry);
4094 }
4095 
updateServerGeometry()4096 void X11Client::updateServerGeometry()
4097 {
4098     const QRect oldBufferGeometry = m_lastBufferGeometry;
4099 
4100     // Compute the old client rect, the client geometry is always inside the buffer geometry.
4101     QRect oldClientRect = m_lastClientGeometry;
4102     oldClientRect.translate(-m_lastBufferGeometry.topLeft());
4103 
4104     if (oldBufferGeometry.size() != m_bufferGeometry.size() ||
4105             oldClientRect != QRect(clientPos(), clientSize())) {
4106         resizeDecoration();
4107         // If the client is being interactively resized, then the frame window, the wrapper window,
4108         // and the client window have correct geometry at this point, so we don't have to configure
4109         // them again.
4110         if (m_frame.geometry() != m_bufferGeometry) {
4111             m_frame.setGeometry(m_bufferGeometry);
4112         }
4113         if (!isShade()) {
4114             const QRect requestedWrapperGeometry(clientPos(), clientSize());
4115             if (m_wrapper.geometry() != requestedWrapperGeometry) {
4116                 m_wrapper.setGeometry(requestedWrapperGeometry);
4117             }
4118             const QRect requestedClientGeometry(QPoint(0, 0), clientSize());
4119             if (m_client.geometry() != requestedClientGeometry) {
4120                 m_client.setGeometry(requestedClientGeometry);
4121             }
4122             // SELI - won't this be too expensive?
4123             // THOMAS - yes, but gtk+ clients will not resize without ...
4124             sendSyntheticConfigureNotify();
4125         }
4126         updateShape();
4127     } else {
4128         if (isInteractiveMoveResize()) {
4129             if (Compositor::compositing()) { // Defer the X update until we leave this mode
4130                 needsXWindowMove = true;
4131             } else {
4132                 m_frame.move(m_bufferGeometry.topLeft()); // sendSyntheticConfigureNotify() on finish shall be sufficient
4133             }
4134         } else {
4135             m_frame.move(m_bufferGeometry.topLeft());
4136             sendSyntheticConfigureNotify();
4137         }
4138         // Unconditionally move the input window: it won't affect rendering
4139         m_decoInputExtent.move(pos() + inputPos());
4140     }
4141 }
4142 
4143 static bool changeMaximizeRecursion = false;
changeMaximize(bool horizontal,bool vertical,bool adjust)4144 void X11Client::changeMaximize(bool horizontal, bool vertical, bool adjust)
4145 {
4146     if (changeMaximizeRecursion)
4147         return;
4148 
4149     if (!isResizable() || isToolbar())  // SELI isToolbar() ?
4150         return;
4151 
4152     QRect clientArea;
4153     if (isElectricBorderMaximizing())
4154         clientArea = workspace()->clientArea(MaximizeArea, this, Cursors::self()->mouse()->pos());
4155     else
4156         clientArea = workspace()->clientArea(MaximizeArea, this, moveResizeGeometry().center());
4157 
4158     MaximizeMode old_mode = max_mode;
4159     // 'adjust == true' means to update the size only, e.g. after changing workspace size
4160     if (!adjust) {
4161         if (vertical)
4162             max_mode = MaximizeMode(max_mode ^ MaximizeVertical);
4163         if (horizontal)
4164             max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal);
4165     }
4166 
4167     // if the client insist on a fix aspect ratio, we check whether the maximizing will get us
4168     // out of screen bounds and take that as a "full maximization with aspect check" then
4169     if (m_geometryHints.hasAspect() && // fixed aspect
4170         (max_mode == MaximizeVertical || max_mode == MaximizeHorizontal) && // ondimensional maximization
4171         rules()->checkStrictGeometry(true)) { // obey aspect
4172         const QSize minAspect = m_geometryHints.minAspect();
4173         const QSize maxAspect = m_geometryHints.maxAspect();
4174         if (max_mode == MaximizeVertical || (old_mode & MaximizeVertical)) {
4175             const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT
4176             const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT
4177             if (fx*clientArea.height()/fy > clientArea.width()) // too big
4178                 max_mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull;
4179         } else { // max_mode == MaximizeHorizontal
4180             const double fx = maxAspect.width();
4181             const double fy = minAspect.height();
4182             if (fy*clientArea.width()/fx > clientArea.height()) // too big
4183                 max_mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull;
4184         }
4185     }
4186 
4187     max_mode = rules()->checkMaximize(max_mode);
4188     if (!adjust && max_mode == old_mode)
4189         return;
4190 
4191     GeometryUpdatesBlocker blocker(this);
4192 
4193     // maximing one way and unmaximizing the other way shouldn't happen,
4194     // so restore first and then maximize the other way
4195     if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal)
4196             || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) {
4197         changeMaximize(false, false, false);   // restore
4198     }
4199 
4200     // save sizes for restoring, if maximalizing
4201     QSize sz;
4202     if (isShade())
4203         sz = adjustedSize();
4204     else
4205         sz = size();
4206 
4207     if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
4208         QRect savedGeometry = geometryRestore();
4209         if (!adjust && !(old_mode & MaximizeVertical)) {
4210             savedGeometry.setTop(y());
4211             savedGeometry.setHeight(sz.height());
4212         }
4213         if (!adjust && !(old_mode & MaximizeHorizontal)) {
4214             savedGeometry.setLeft(x());
4215             savedGeometry.setWidth(sz.width());
4216         }
4217         setGeometryRestore(savedGeometry);
4218     }
4219 
4220     // call into decoration update borders
4221     if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) {
4222         changeMaximizeRecursion = true;
4223         const auto c = decoration()->client().toStrongRef();
4224         if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) {
4225             Q_EMIT c->maximizedVerticallyChanged(max_mode & MaximizeVertical);
4226         }
4227         if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) {
4228             Q_EMIT c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal);
4229         }
4230         if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) {
4231             Q_EMIT c->maximizedChanged(max_mode == MaximizeFull);
4232         }
4233         changeMaximizeRecursion = false;
4234     }
4235 
4236     if (options->borderlessMaximizedWindows()) {
4237         // triggers a maximize change.
4238         // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry
4239         changeMaximizeRecursion = true;
4240         setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull));
4241         changeMaximizeRecursion = false;
4242     }
4243 
4244     // Conditional quick tiling exit points
4245     if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
4246         if (old_mode == MaximizeFull &&
4247                 !clientArea.contains(geometryRestore().center())) {
4248             // Not restoring on the same screen
4249             // TODO: The following doesn't work for some reason
4250             //quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually
4251         } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) ||
4252                   (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) {
4253             // Modifying geometry of a tiled window
4254             updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry
4255         }
4256     }
4257 
4258     switch(max_mode) {
4259 
4260     case MaximizeVertical: {
4261         if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull
4262             if (geometryRestore().width() == 0 || !clientArea.contains(geometryRestore().center())) {
4263                 // needs placement
4264                 resize(constrainFrameSize(QSize(width() * 2 / 3, clientArea.height()), SizeModeFixedH));
4265                 Placement::self()->placeSmart(this, clientArea);
4266             } else {
4267                 moveResize(QRect(QPoint(geometryRestore().x(), clientArea.top()),
4268                                  constrainFrameSize(QSize(geometryRestore().width(), clientArea.height()), SizeModeFixedH)));
4269             }
4270         } else {
4271             QRect r(x(), clientArea.top(), width(), clientArea.height());
4272             r.setTopLeft(rules()->checkPosition(r.topLeft()));
4273             r.setSize(constrainFrameSize(r.size(), SizeModeFixedH));
4274             moveResize(r);
4275         }
4276         info->setState(NET::MaxVert, NET::Max);
4277         break;
4278     }
4279 
4280     case MaximizeHorizontal: {
4281         if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull
4282             if (geometryRestore().height() == 0 || !clientArea.contains(geometryRestore().center())) {
4283                 // needs placement
4284                 resize(constrainFrameSize(QSize(clientArea.width(), height() * 2 / 3), SizeModeFixedW));
4285                 Placement::self()->placeSmart(this, clientArea);
4286             } else {
4287                 moveResize(QRect(QPoint(clientArea.left(), geometryRestore().y()),
4288                                  constrainFrameSize(QSize(clientArea.width(), geometryRestore().height()), SizeModeFixedW)));
4289             }
4290         } else {
4291             QRect r(clientArea.left(), y(), clientArea.width(), height());
4292             r.setTopLeft(rules()->checkPosition(r.topLeft()));
4293             r.setSize(constrainFrameSize(r.size(), SizeModeFixedW));
4294             moveResize(r);
4295         }
4296         info->setState(NET::MaxHoriz, NET::Max);
4297         break;
4298     }
4299 
4300     case MaximizeRestore: {
4301         QRect restore = frameGeometry();
4302         // when only partially maximized, geom_restore may not have the other dimension remembered
4303         if (old_mode & MaximizeVertical) {
4304             restore.setTop(geometryRestore().top());
4305             restore.setBottom(geometryRestore().bottom());
4306         }
4307         if (old_mode & MaximizeHorizontal) {
4308             restore.setLeft(geometryRestore().left());
4309             restore.setRight(geometryRestore().right());
4310         }
4311         if (!restore.isValid()) {
4312             QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3);
4313             if (geometryRestore().width() > 0) {
4314                 s.setWidth(geometryRestore().width());
4315             }
4316             if (geometryRestore().height() > 0) {
4317                 s.setHeight(geometryRestore().height());
4318             }
4319             resize(constrainFrameSize(s));
4320             Placement::self()->placeSmart(this, clientArea);
4321             restore = frameGeometry();
4322             if (geometryRestore().width() > 0) {
4323                 restore.moveLeft(geometryRestore().x());
4324             }
4325             if (geometryRestore().height() > 0) {
4326                 restore.moveTop(geometryRestore().y());
4327             }
4328             setGeometryRestore(restore); // relevant for mouse pos calculation, bug #298646
4329         }
4330         if (m_geometryHints.hasAspect()) {
4331             restore.setSize(constrainFrameSize(restore.size(), SizeModeAny));
4332         }
4333         moveResize(restore);
4334         if (!clientArea.contains(geometryRestore().center())) { // Not restoring to the same screen
4335             Placement::self()->place(this, clientArea);
4336         }
4337         info->setState(NET::States(), NET::Max);
4338         updateQuickTileMode(QuickTileFlag::None);
4339         break;
4340     }
4341 
4342     case MaximizeFull: {
4343         QRect r(clientArea);
4344         r.setTopLeft(rules()->checkPosition(r.topLeft()));
4345         r.setSize(constrainFrameSize(r.size(), SizeModeMax));
4346         if (r.size() != clientArea.size()) { // to avoid off-by-one errors...
4347             if (isElectricBorderMaximizing() && r.width() < clientArea.width()) {
4348                 r.moveLeft(qMax(clientArea.left(), Cursors::self()->mouse()->pos().x() - r.width()/2));
4349                 r.moveRight(qMin(clientArea.right(), r.right()));
4350             } else {
4351                 r.moveCenter(clientArea.center());
4352                 const bool closeHeight = r.height() > 97*clientArea.height()/100;
4353                 const bool closeWidth  = r.width()  > 97*clientArea.width() /100;
4354                 const bool overHeight = r.height() > clientArea.height();
4355                 const bool overWidth  = r.width()  > clientArea.width();
4356                 if (closeWidth || closeHeight) {
4357                     Position titlePos = titlebarPosition();
4358                     const QRect screenArea = workspace()->clientArea(ScreenArea, this, clientArea.center());
4359                     if (closeHeight) {
4360                         bool tryBottom = titlePos == PositionBottom;
4361                         if ((overHeight && titlePos == PositionTop) ||
4362                             screenArea.top() == clientArea.top())
4363                             r.setTop(clientArea.top());
4364                         else
4365                             tryBottom = true;
4366                         if (tryBottom &&
4367                             (overHeight || screenArea.bottom() == clientArea.bottom()))
4368                             r.setBottom(clientArea.bottom());
4369                     }
4370                     if (closeWidth) {
4371                         bool tryLeft = titlePos == PositionLeft;
4372                         if ((overWidth && titlePos == PositionRight) ||
4373                             screenArea.right() == clientArea.right())
4374                             r.setRight(clientArea.right());
4375                         else
4376                             tryLeft = true;
4377                         if (tryLeft && (overWidth || screenArea.left() == clientArea.left()))
4378                             r.setLeft(clientArea.left());
4379                     }
4380                 }
4381             }
4382             r.moveTopLeft(rules()->checkPosition(r.topLeft()));
4383         }
4384         moveResize(r);
4385         if (options->electricBorderMaximize() && r.top() == clientArea.top())
4386             updateQuickTileMode(QuickTileFlag::Maximize);
4387         else
4388             updateQuickTileMode(QuickTileFlag::None);
4389         info->setState(NET::Max, NET::Max);
4390         break;
4391     }
4392     default:
4393         break;
4394     }
4395 
4396     updateAllowedActions();
4397     updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size);
4398     Q_EMIT quickTileModeChanged();
4399 }
4400 
userCanSetFullScreen() const4401 bool X11Client::userCanSetFullScreen() const
4402 {
4403     if (!isFullScreenable()) {
4404         return false;
4405     }
4406     return isNormalWindow() || isDialog();
4407 }
4408 
setFullScreen(bool set,bool user)4409 void X11Client::setFullScreen(bool set, bool user)
4410 {
4411     set = rules()->checkFullScreen(set);
4412 
4413     const bool wasFullscreen = isFullScreen();
4414     if (wasFullscreen == set) {
4415         return;
4416     }
4417     if (user && !userCanSetFullScreen()) {
4418         return;
4419     }
4420 
4421     setShade(ShadeNone);
4422 
4423     if (wasFullscreen) {
4424         workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event
4425     } else {
4426         setFullscreenGeometryRestore(frameGeometry());
4427     }
4428 
4429     if (set) {
4430         m_fullscreenMode = FullScreenNormal;
4431         workspace()->raiseClient(this);
4432     } else {
4433         m_fullscreenMode = FullScreenNone;
4434     }
4435 
4436     StackingUpdatesBlocker blocker1(workspace());
4437     GeometryUpdatesBlocker blocker2(this);
4438 
4439     // active fullscreens get different layer
4440     updateLayer();
4441 
4442     info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen);
4443     updateDecoration(false, false);
4444 
4445     if (set) {
4446         if (info->fullscreenMonitors().isSet()) {
4447             moveResize(fullscreenMonitorsArea(info->fullscreenMonitors()));
4448         } else {
4449             moveResize(workspace()->clientArea(FullScreenArea, this));
4450         }
4451     } else {
4452         Q_ASSERT(!fullscreenGeometryRestore().isNull());
4453         AbstractOutput *currentOutput = output();
4454         moveResize(QRect(fullscreenGeometryRestore().topLeft(), constrainFrameSize(fullscreenGeometryRestore().size())));
4455         if (currentOutput != output()) {
4456             workspace()->sendClientToOutput(this, currentOutput);
4457         }
4458     }
4459 
4460     updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size);
4461     Q_EMIT clientFullScreenSet(this, set, user);
4462     Q_EMIT fullScreenChanged();
4463 }
4464 
4465 
updateFullscreenMonitors(NETFullscreenMonitors topology)4466 void X11Client::updateFullscreenMonitors(NETFullscreenMonitors topology)
4467 {
4468     const int outputCount = kwinApp()->platform()->enabledOutputs().count();
4469 
4470 //    qDebug() << "incoming request with top: " << topology.top << " bottom: " << topology.bottom
4471 //                   << " left: " << topology.left << " right: " << topology.right
4472 //                   << ", we have: " << nscreens << " screens.";
4473 
4474     if (topology.top >= outputCount ||
4475             topology.bottom >= outputCount ||
4476             topology.left >= outputCount ||
4477             topology.right >= outputCount) {
4478         qCWarning(KWIN_CORE) << "fullscreenMonitors update failed. request higher than number of screens.";
4479         return;
4480     }
4481 
4482     info->setFullscreenMonitors(topology);
4483     if (isFullScreen())
4484         moveResize(fullscreenMonitorsArea(topology));
4485 }
4486 
4487 /**
4488  * Calculates the bounding rectangle defined by the 4 monitor indices indicating the
4489  * top, bottom, left, and right edges of the window when the fullscreen state is enabled.
4490  */
fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const4491 QRect X11Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const
4492 {
4493     QRect total;
4494 
4495     if (auto output = kwinApp()->platform()->findOutput(requestedTopology.top)) {
4496         total = total.united(output->geometry());
4497     }
4498     if (auto output = kwinApp()->platform()->findOutput(requestedTopology.bottom)) {
4499         total = total.united(output->geometry());
4500     }
4501     if (auto output = kwinApp()->platform()->findOutput(requestedTopology.left)) {
4502         total = total.united(output->geometry());
4503     }
4504     if (auto output = kwinApp()->platform()->findOutput(requestedTopology.right)) {
4505         total = total.united(output->geometry());
4506     }
4507 
4508     return total;
4509 }
4510 
4511 static GeometryTip* geometryTip    = nullptr;
4512 
positionGeometryTip()4513 void X11Client::positionGeometryTip()
4514 {
4515     Q_ASSERT(isInteractiveMove() || isInteractiveResize());
4516     // Position and Size display
4517     if (effects && static_cast<EffectsHandlerImpl*>(effects)->provides(Effect::GeometryTip))
4518         return; // some effect paints this for us
4519     if (options->showGeometryTip()) {
4520         if (!geometryTip) {
4521             geometryTip = new GeometryTip(&m_geometryHints);
4522         }
4523         QRect wgeom(moveResizeGeometry());   // position of the frame, size of the window itself
4524         wgeom.setWidth(wgeom.width() - (width() - clientSize().width()));
4525         wgeom.setHeight(wgeom.height() - (height() - clientSize().height()));
4526         if (isShade())
4527             wgeom.setHeight(0);
4528         geometryTip->setGeometry(wgeom);
4529         if (!geometryTip->isVisible())
4530             geometryTip->show();
4531         geometryTip->raise();
4532     }
4533 }
4534 
doStartInteractiveMoveResize()4535 bool X11Client::doStartInteractiveMoveResize()
4536 {
4537     bool has_grab = false;
4538     // This reportedly improves smoothness of the moveresize operation,
4539     // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug*
4540     // (https://lists.kde.org/?t=107302193400001&r=1&w=2)
4541     QRect r = workspace()->clientArea(FullArea, this);
4542     m_moveResizeGrabWindow.create(r, XCB_WINDOW_CLASS_INPUT_ONLY, 0, nullptr, rootWindow());
4543     m_moveResizeGrabWindow.map();
4544     m_moveResizeGrabWindow.raise();
4545     updateXTime();
4546     const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(connection(), false, m_moveResizeGrabWindow,
4547         XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
4548         XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW,
4549         XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursors::self()->mouse()->x11Cursor(cursor()), xTime());
4550     ScopedCPointer<xcb_grab_pointer_reply_t> pointerGrab(xcb_grab_pointer_reply(connection(), cookie, nullptr));
4551     if (!pointerGrab.isNull() && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) {
4552         has_grab = true;
4553     }
4554     if (!has_grab && grabXKeyboard(frameId()))
4555         has_grab = move_resize_has_keyboard_grab = true;
4556     if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize
4557         m_moveResizeGrabWindow.reset();
4558         return false;
4559     }
4560     return true;
4561 }
4562 
leaveInteractiveMoveResize()4563 void X11Client::leaveInteractiveMoveResize()
4564 {
4565     if (needsXWindowMove) {
4566         // Do the deferred move
4567         m_frame.move(m_bufferGeometry.topLeft());
4568         needsXWindowMove = false;
4569     }
4570     if (!isInteractiveResize())
4571         sendSyntheticConfigureNotify(); // tell the client about it's new final position
4572     if (geometryTip) {
4573         geometryTip->hide();
4574         delete geometryTip;
4575         geometryTip = nullptr;
4576     }
4577     if (move_resize_has_keyboard_grab)
4578         ungrabXKeyboard();
4579     move_resize_has_keyboard_grab = false;
4580     xcb_ungrab_pointer(connection(), xTime());
4581     m_moveResizeGrabWindow.reset();
4582     if (m_syncRequest.counter == XCB_NONE) { // don't forget to sanitize since the timeout will no more fire
4583         m_syncRequest.isPending = false;
4584     }
4585     delete m_syncRequest.timeout;
4586     m_syncRequest.timeout = nullptr;
4587     AbstractClient::leaveInteractiveMoveResize();
4588 }
4589 
isWaitingForInteractiveMoveResizeSync() const4590 bool X11Client::isWaitingForInteractiveMoveResizeSync() const
4591 {
4592     return m_syncRequest.isPending && isInteractiveResize();
4593 }
4594 
doInteractiveResizeSync()4595 void X11Client::doInteractiveResizeSync()
4596 {
4597     if (!m_syncRequest.timeout) {
4598         m_syncRequest.timeout = new QTimer(this);
4599         connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Client::handleSyncTimeout);
4600         m_syncRequest.timeout->setSingleShot(true);
4601     }
4602     if (m_syncRequest.counter != XCB_NONE) {
4603         m_syncRequest.timeout->start(250);
4604         sendSyncRequest();
4605     } else {                              // for clients not supporting the XSYNC protocol, we
4606         m_syncRequest.isPending = true;   // limit the resizes to 30Hz to take pointless load from X11
4607         m_syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed
4608     }                                     // and no human can control faster resizes anyway
4609     const QRect moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry());
4610     const QRect moveResizeBufferGeometry = frameRectToBufferRect(moveResizeGeometry());
4611 
4612     // According to the Composite extension spec, a window will get a new pixmap allocated each time
4613     // it is mapped or resized. Given that we redirect frame windows and not client windows, we have
4614     // to resize the frame window in order to forcefully reallocate offscreen storage. If we don't do
4615     // this, then we might render partially updated client window. I know, it sucks.
4616     m_frame.setGeometry(moveResizeBufferGeometry);
4617     m_wrapper.setGeometry(QRect(clientPos(), moveResizeClientGeometry.size()));
4618     m_client.setGeometry(QRect(QPoint(0, 0), moveResizeClientGeometry.size()));
4619 }
4620 
handleSyncTimeout()4621 void X11Client::handleSyncTimeout()
4622 {
4623     if (m_syncRequest.counter == XCB_NONE) { // client w/o XSYNC support. allow the next resize event
4624         m_syncRequest.isPending = false;     // NEVER do this for clients with a valid counter
4625     }                                        // (leads to sync request races in some clients)
4626     performInteractiveMoveResize();
4627 }
4628 
strut() const4629 NETExtendedStrut X11Client::strut() const
4630 {
4631     NETExtendedStrut ext = info->extendedStrut();
4632     NETStrut str = info->strut();
4633     const QSize displaySize = workspace()->geometry().size();
4634     if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0
4635             && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) {
4636         // build extended from simple
4637         if (str.left != 0) {
4638             ext.left_width = str.left;
4639             ext.left_start = 0;
4640             ext.left_end = displaySize.height();
4641         }
4642         if (str.right != 0) {
4643             ext.right_width = str.right;
4644             ext.right_start = 0;
4645             ext.right_end = displaySize.height();
4646         }
4647         if (str.top != 0) {
4648             ext.top_width = str.top;
4649             ext.top_start = 0;
4650             ext.top_end = displaySize.width();
4651         }
4652         if (str.bottom != 0) {
4653             ext.bottom_width = str.bottom;
4654             ext.bottom_start = 0;
4655             ext.bottom_end = displaySize.width();
4656         }
4657     }
4658     return ext;
4659 }
4660 
strutRect(StrutArea area) const4661 StrutRect X11Client::strutRect(StrutArea area) const
4662 {
4663     Q_ASSERT(area != StrutAreaAll);   // Not valid
4664     const QSize displaySize = workspace()->geometry().size();
4665     NETExtendedStrut strutArea = strut();
4666     switch(area) {
4667     case StrutAreaTop:
4668         if (strutArea.top_width != 0)
4669             return StrutRect(QRect(
4670                                  strutArea.top_start, 0,
4671                                  strutArea.top_end - strutArea.top_start, strutArea.top_width
4672                              ), StrutAreaTop);
4673         break;
4674     case StrutAreaRight:
4675         if (strutArea.right_width != 0)
4676             return StrutRect(QRect(
4677                                  displaySize.width() - strutArea.right_width, strutArea.right_start,
4678                                  strutArea.right_width, strutArea.right_end - strutArea.right_start
4679                              ), StrutAreaRight);
4680         break;
4681     case StrutAreaBottom:
4682         if (strutArea.bottom_width != 0)
4683             return StrutRect(QRect(
4684                                  strutArea.bottom_start, displaySize.height() - strutArea.bottom_width,
4685                                  strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width
4686                              ), StrutAreaBottom);
4687         break;
4688     case StrutAreaLeft:
4689         if (strutArea.left_width != 0)
4690             return StrutRect(QRect(
4691                                  0, strutArea.left_start,
4692                                  strutArea.left_width, strutArea.left_end - strutArea.left_start
4693                              ), StrutAreaLeft);
4694         break;
4695     default:
4696         abort(); // Not valid
4697     }
4698     return StrutRect(); // Null rect
4699 }
4700 
hasStrut() const4701 bool X11Client::hasStrut() const
4702 {
4703     NETExtendedStrut ext = strut();
4704     if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0)
4705         return false;
4706     return true;
4707 }
4708 
applyWindowRules()4709 void X11Client::applyWindowRules()
4710 {
4711     AbstractClient::applyWindowRules();
4712     updateAllowedActions();
4713     setBlockingCompositing(info->isBlockingCompositing());
4714 }
4715 
supportsWindowRules() const4716 bool X11Client::supportsWindowRules() const
4717 {
4718     return true;
4719 }
4720 
damageNotifyEvent()4721 void X11Client::damageNotifyEvent()
4722 {
4723     Q_ASSERT(kwinApp()->operationMode() == Application::OperationModeX11);
4724 
4725     if (!readyForPainting()) { // avoid "setReadyForPainting()" function calling overhead
4726         if (m_syncRequest.counter == XCB_NONE) {  // cannot detect complete redraw, consider done now
4727             setReadyForPainting();
4728             setupWindowManagementInterface();
4729         }
4730     }
4731 
4732     SurfaceItemX11 *item = static_cast<SurfaceItemX11 *>(surfaceItem());
4733     if (item) {
4734         item->processDamage();
4735     }
4736 }
4737 
discardWindowPixmap()4738 void X11Client::discardWindowPixmap()
4739 {
4740     if (auto item = surfaceItem()) {
4741         item->discardPixmap();
4742     }
4743 }
4744 
updateWindowPixmap()4745 void X11Client::updateWindowPixmap()
4746 {
4747     if (auto item = surfaceItem()) {
4748         item->updatePixmap();
4749     }
4750 }
4751 
4752 } // namespace
4753