layers.cpp 30.3 KB
Newer Older
1
/********************************************************************
2 3 4 5 6 7
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>

8 9 10 11 12 13 14 15 16 17 18 19 20
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

// SELI zmenit doc

/*

 This file contains things relevant to stacking order and layers.

 Design:

 Normal unconstrained stacking order, as requested by the user (by clicking
 on windows to raise them, etc.), is in Workspace::unconstrained_stacking_order.
 That list shouldn't be used at all, except for building
 Workspace::stacking_order. The building is done
 in Workspace::constrainedStackingOrder(). Only Workspace::stackingOrder() should
 be used to get the stacking order, because it also checks the stacking order
 is up to date.
 All clients are also stored in Workspace::clients (except for isDesktop() clients,
 as those are very special, and are stored in Workspace::desktops), in the order
 the clients were created.

41
 Every window has one layer assigned in which it is. There are 7 layers,
42
 from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer, NotificationLayer,
43 44 45 46
 ActiveLayer, CriticalNotificationLayer, and OnScreenDisplayLayer (see also NETWM sect.7.10.).
 The layer a window is in depends on the window type, and on other things like whether the window
 is active. We extend the layers provided in NETWM by the NotificationLayer, OnScreenDisplayLayer,
 and CriticalNotificationLayer.
47
 The NoficationLayer contains notification windows which are kept above all windows except the active
48 49 50 51
 fullscreen window. The CriticalNotificationLayer contains notification windows which are important
 enough to keep them even above fullscreen windows. The OnScreenDisplayLayer is used for eg. volume
 and brightness change feedback and is kept above all windows since it provides immediate response
 to a user action.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

 NET::Splash clients belong to the Normal layer. NET::TopMenu clients
 belong to Dock layer. Clients that are both NET::Dock and NET::KeepBelow
 are in the Normal layer in order to keep the 'allow window to cover
 the panel' Kicker setting to work as intended (this may look like a slight
 spec violation, but a) I have no better idea, b) the spec allows adjusting
 the stacking order if the WM thinks it's a good idea . We put all
 NET::KeepAbove above all Docks too, even though the spec suggests putting
 them in the same layer.

 Most transients are in the same layer as their mainwindow,
 see Workspace::constrainedStackingOrder(), they may also be in higher layers, but
 they should never be below their mainwindow.

 When some client attribute changes (above/below flag, transiency...),
 Workspace::updateClientLayer() should be called in order to make
68
 sure it's moved to the appropriate layer QList<X11Client *> if needed.
69 70 71 72 73 74 75 76 77 78 79 80 81 82

 Currently the things that affect client in which layer a client
 belongs: KeepAbove/Keep Below flags, window type, fullscreen
 state and whether the client is active, mainclient (transiency).

 Make sure updateStackingOrder() is called in order to make
 Workspace::stackingOrder() up to date and propagated to the world.
 Using Workspace::blockStackingUpdates() (or the StackingUpdatesBlocker
 helper class) it's possible to temporarily disable updates
 and the stacking order will be updated once after it's allowed again.

*/

#include "utils.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
83
#include "x11client.h"
84
#include "focuschain.h"
85
#include "netinfo.h"
86 87 88 89
#include "workspace.h"
#include "tabbox.h"
#include "group.h"
#include "rules.h"
90
#include "screens.h"
91
#include "unmanaged.h"
92
#include "deleted.h"
Martin Flöser's avatar
Martin Flöser committed
93
#include "effects.h"
94
#include "composite.h"
95
#include "screenedge.h"
96
#include "wayland_server.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
97
#include "internal_client.h"
98

Martin Flöser's avatar
Martin Flöser committed
99 100
#include <QDebug>

101 102 103 104 105 106 107
namespace KWin
{

//*******************************
// Workspace
//*******************************

108
void Workspace::updateClientLayer(AbstractClient* c)
109
{
110 111
    if (c)
        c->updateLayer();
112
}
113

114 115 116 117
void Workspace::updateStackingOrder(bool propagate_new_clients)
{
    if (block_stacking_updates > 0) {
        if (propagate_new_clients)
118 119
            blocked_propagating_new_clients = true;
        return;
120
    }
121
    QList<Toplevel *> new_stacking_order = constrainedStackingOrder();
122
    bool changed = (force_restacking || new_stacking_order != stacking_order);
123
    force_restacking = false;
124
    stacking_order = new_stacking_order;
125 126
    if (changed || propagate_new_clients) {
        propagateClients(propagate_new_clients);
127
        emit stackingOrderChanged();
128 129 130 131
        if (m_compositor) {
            m_compositor->addRepaintFull();
        }

132
        if (active_client)
Luboš Luňák's avatar
Luboš Luňák committed
133
            active_client->updateMouseGrab();
134
    }
135
}
136

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
137
/**
138 139 140 141 142
 * Some fullscreen effects have to raise the screenedge on top of an input window, thus all windows
 * this function puts them back where they belong for regular use and is some cheap variant of
 * the regular propagateClients function in that it completely ignores managed clients and everything
 * else and also does not update the NETWM property.
 * Called from Effects::destroyInputWindow so far.
143
 */
144 145
void Workspace::stackScreenEdgesUnderOverrideRedirect()
{
146 147 148
    if (!rootInfo()) {
        return;
    }
149
    Xcb::restackWindows(QVector<xcb_window_t>() << rootInfo()->supportWindow() << ScreenEdges::self()->windows());
150 151
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
152 153 154
/**
 * Propagates the managed clients to the world.
 * Called ONLY from updateStackingOrder().
155
 */
156 157
void Workspace::propagateClients(bool propagate_new_clients)
{
158 159 160
    if (!rootInfo()) {
        return;
    }
161
    // restack the windows according to the stacking order
Thomas Lübking's avatar
Thomas Lübking committed
162 163 164
    // supportWindow > electric borders > clients > hidden clients
    QVector<xcb_window_t> newWindowStack;

165 166 167 168 169
    // Stack all windows under the support window. The support window is
    // not used for anything (besides the NETWM property), and it's not shown,
    // but it was lowered after kwin startup. Stacking all clients below
    // it ensures that no client will be ever shown above override-redirect
    // windows (e.g. popups).
170
    newWindowStack << rootInfo()->supportWindow();
Thomas Lübking's avatar
Thomas Lübking committed
171 172 173

    newWindowStack << ScreenEdges::self()->windows();

174 175
    newWindowStack << manual_overlays;

Thomas Lübking's avatar
Thomas Lübking committed
176 177 178
    newWindowStack.reserve(newWindowStack.size() + 2*stacking_order.size()); // *2 for inputWindow

    for (int i = stacking_order.size() - 1; i >= 0; --i) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
179
        X11Client *client = qobject_cast<X11Client *>(stacking_order.at(i));
180
        if (!client || client->hiddenPreview()) {
181
            continue;
182
        }
183 184 185

        if (client->inputId())
            // Stack the input window above the frame
Thomas Lübking's avatar
Thomas Lübking committed
186
            newWindowStack << client->inputId();
187

Thomas Lübking's avatar
Thomas Lübking committed
188
        newWindowStack << client->frameId();
189
    }
190 191 192 193

    // when having hidden previews, stack hidden windows below everything else
    // (as far as pure X stacking order is concerned), in order to avoid having
    // these windows that should be unmapped to interfere with other windows
Thomas Lübking's avatar
Thomas Lübking committed
194
    for (int i = stacking_order.size() - 1; i >= 0; --i) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
195
        X11Client *client = qobject_cast<X11Client *>(stacking_order.at(i));
196
        if (!client || !client->hiddenPreview())
197
            continue;
Thomas Lübking's avatar
Thomas Lübking committed
198
        newWindowStack << client->frameId();
199
    }
200 201
    // TODO isn't it too inefficient to restack always all clients?
    // TODO don't restack not visible windows?
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
202
    Q_ASSERT(newWindowStack.at(0) == rootInfo()->supportWindow());
Thomas Lübking's avatar
Thomas Lübking committed
203
    Xcb::restackWindows(newWindowStack);
204

205
    int pos = 0;
206
    xcb_window_t *cl(nullptr);
207
    if (propagate_new_clients) {
208 209 210 211
        cl = new xcb_window_t[ manual_overlays.count() + desktops.count() + clients.count()];
        for (const auto win : manual_overlays) {
            cl[pos++] = win;
        }
212
        // TODO this is still not completely in the map order
213
        for (auto it = desktops.constBegin(); it != desktops.constEnd(); ++it)
214
            cl[pos++] = (*it)->window();
215
        for (auto it = clients.constBegin(); it != clients.constEnd(); ++it)
216
            cl[pos++] = (*it)->window();
217
        rootInfo()->setClientList(cl, pos);
218
        delete [] cl;
219
    }
220

221
    cl = new xcb_window_t[ manual_overlays.count() + stacking_order.count()];
222
    pos = 0;
223
    for (auto it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) {
224 225 226 227
        X11Client *client = qobject_cast<X11Client *>(*it);
        if (client) {
            cl[pos++] = client->window();
        }
228
    }
229 230 231
    for (const auto win : manual_overlays) {
        cl[pos++] = win;
    }
232
    rootInfo()->setClientListStacking(cl, pos);
233
    delete [] cl;
234

235 236
    // Make the cached stacking order invalid here, in case we need the new stacking order before we get
    // the matching event, due to X being asynchronous.
237
    markXStackingOrderAsDirty();
238
}
239

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
240 241 242 243
/**
 * Returns topmost visible client. Windows on the dock, the desktop
 * or of any other special kind are excluded. Also if the window
 * doesn't accept focus it's excluded.
244
 */
245
// TODO misleading name for this method, too many slightly different ways to use it
246
AbstractClient* Workspace::topClientOnDesktop(int desktop, int screen, bool unconstrained, bool only_normal) const
247
{
248
// TODO    Q_ASSERT( block_stacking_updates == 0 );
249
    QList<Toplevel *> list;
250
    if (!unconstrained)
Luboš Luňák's avatar
Luboš Luňák committed
251
        list = stacking_order;
252
    else
Luboš Luňák's avatar
Luboš Luňák committed
253
        list = unconstrained_stacking_order;
254 255 256
    for (int i = list.size() - 1;
            i >= 0;
            --i) {
257
        AbstractClient *c = qobject_cast<AbstractClient*>(list.at(i));
258 259 260 261 262
        if (!c) {
            continue;
        }
        if (c->isOnDesktop(desktop) && c->isShown(false) && c->isOnCurrentActivity()) {
            if (screen != -1 && c->screen() != screen)
263
                continue;
264
            if (!only_normal)
265 266 267
                return c;
            if (c->wantsTabFocus() && !c->isSpecialWindow())
                return c;
268 269
        }
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
270
    return nullptr;
271
}
272

273
AbstractClient* Workspace::findDesktop(bool topmost, int desktop) const
274
{
275
// TODO    Q_ASSERT( block_stacking_updates == 0 );
276 277
    if (topmost) {
        for (int i = stacking_order.size() - 1; i >= 0; i--) {
278
            AbstractClient *c = qobject_cast<AbstractClient*>(stacking_order.at(i));
279
            if (c && c->isOnDesktop(desktop) && c->isDesktop()
280
                    && c->isShown(true))
281 282
                return c;
        }
283 284
    } else { // bottom-most
        foreach (Toplevel * c, stacking_order) {
285
            AbstractClient *client = qobject_cast<AbstractClient*>(c);
286 287 288 289
            if (client && c->isOnDesktop(desktop) && c->isDesktop()
                    && client->isShown(true))
                return client;
        }
290
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
291
    return nullptr;
292
}
293

294
void Workspace::raiseOrLowerClient(AbstractClient *c)
295
{
296
    if (!c) return;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
297
    AbstractClient* topmost = nullptr;
298
// TODO    Q_ASSERT( block_stacking_updates == 0 );
299 300
    if (most_recently_raised && stacking_order.contains(most_recently_raised) &&
            most_recently_raised->isShown(true) && c->isOnCurrentDesktop())
301 302
        topmost = most_recently_raised;
    else
303
        topmost = topClientOnDesktop(c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(),
304
                                     options->isSeparateScreenFocus() ? c->screen() : -1);
305

306
    if (c == topmost)
307 308 309
        lowerClient(c);
    else
        raiseClient(c);
310
}
311 312


313
void Workspace::lowerClient(AbstractClient* c, bool nogroup)
314 315
{
    if (!c)
316 317 318 319
        return;

    c->cancelAutoRaise();

320
    StackingUpdatesBlocker blocker(this);
321

322 323 324
    unconstrained_stacking_order.removeAll(c);
    unconstrained_stacking_order.prepend(c);
    if (!nogroup && c->isTransient()) {
325
        // lower also all windows in the group, in their reversed stacking order
326
        QList<X11Client *> wins;
327 328
        if (auto group = c->group()) {
            wins = ensureStackingOrder(group->members());
329
        }
330 331 332 333 334
        for (int i = wins.size() - 1;
                i >= 0;
                --i) {
            if (wins[ i ] != c)
                lowerClient(wins[ i ], true);
335
        }
336
    }
337

338
    if (c == most_recently_raised)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
339
        most_recently_raised = nullptr;
340
}
341

342
void Workspace::lowerClientWithinApplication(AbstractClient* c)
343 344
{
    if (!c)
345 346 347 348
        return;

    c->cancelAutoRaise();

349
    StackingUpdatesBlocker blocker(this);
350

351
    unconstrained_stacking_order.removeAll(c);
352 353
    bool lowered = false;
    // first try to put it below the bottom-most window of the application
354
    for (auto it = unconstrained_stacking_order.begin();
355
            it != unconstrained_stacking_order.end();
356
            ++it) {
357
        AbstractClient *client = qobject_cast<AbstractClient*>(*it);
358 359 360
        if (!client) {
            continue;
        }
361
        if (AbstractClient::belongToSameApplication(client, c)) {
362
            unconstrained_stacking_order.insert(it, c);
363 364
            lowered = true;
            break;
365
        }
366
    }
367 368
    if (!lowered)
        unconstrained_stacking_order.prepend(c);
369
    // ignore mainwindows
370
}
371

372
void Workspace::raiseClient(AbstractClient* c, bool nogroup)
373 374
{
    if (!c)
375 376 377 378
        return;

    c->cancelAutoRaise();

379
    StackingUpdatesBlocker blocker(this);
380

381
    if (!nogroup && c->isTransient()) {
382 383
        QList<AbstractClient*> transients;
        AbstractClient *transient_parent = c;
384 385
        while ((transient_parent = transient_parent->transientFor()))
            transients << transient_parent;
386
        foreach (transient_parent, transients)
387
            raiseClient(transient_parent, true);
388
    }
389

390 391
    unconstrained_stacking_order.removeAll(c);
    unconstrained_stacking_order.append(c);
392

393
    if (!c->isSpecialWindow()) {
394 395
        most_recently_raised = c;
    }
396
}
397

398
void Workspace::raiseClientWithinApplication(AbstractClient* c)
399 400
{
    if (!c)
401 402 403 404
        return;

    c->cancelAutoRaise();

405
    StackingUpdatesBlocker blocker(this);
406
    // ignore mainwindows
407

408
    // first try to put it above the top-most window of the application
409
    for (int i = unconstrained_stacking_order.size() - 1; i > -1 ; --i) {
410
        AbstractClient *other = qobject_cast<AbstractClient*>(unconstrained_stacking_order.at(i));
411 412 413
        if (!other) {
            continue;
        }
414
        if (other == c)     // don't lower it just because it asked to be raised
415
            return;
416
        if (AbstractClient::belongToSameApplication(other, c)) {
417
            unconstrained_stacking_order.removeAll(c);
418 419
            unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(other) + 1, c);   // insert after the found one
            break;
420 421
        }
    }
422
}
423

424
void Workspace::raiseClientRequest(KWin::AbstractClient *c, NET::RequestSource src, xcb_timestamp_t timestamp)
425 426 427 428 429
{
    if (src == NET::FromTool || allowFullClientRaising(c, timestamp))
        raiseClient(c);
    else {
        raiseClientWithinApplication(c);
430 431
        c->demandAttention();
    }
432
}
433

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
434
void Workspace::lowerClientRequest(KWin::X11Client *c, NET::RequestSource src, xcb_timestamp_t /*timestamp*/)
435
{
436 437 438 439
    // If the client has support for all this focus stealing prevention stuff,
    // do only lowering within the application, as that's the more logical
    // variant of lowering when application requests it.
    // No demanding of attention here of course.
440 441
    if (src == NET::FromTool || !c->hasUserTimeSupport())
        lowerClient(c);
442
    else
443 444
        lowerClientWithinApplication(c);
}
445

446 447 448 449
void Workspace::lowerClientRequest(KWin::AbstractClient *c)
{
    lowerClientWithinApplication(c);
}
450

451
void Workspace::restack(AbstractClient* c, AbstractClient* under, bool force)
452
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
453
    Q_ASSERT(unconstrained_stacking_order.contains(under));
454
    if (!force && !AbstractClient::belongToSameApplication(under, c)) {
455
         // put in the stacking order below _all_ windows belonging to the active application
456
        for (int i = 0; i < unconstrained_stacking_order.size(); ++i) {
457 458
            AbstractClient *other = qobject_cast<AbstractClient*>(unconstrained_stacking_order.at(i));
            if (other && other->layer() == c->layer() && AbstractClient::belongToSameApplication(under, other)) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
459
                under = (c == other) ? nullptr : other;
460
                break;
461 462
            }
        }
463
    }
464 465 466 467 468
    if (under) {
        unconstrained_stacking_order.removeAll(c);
        unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(under), c);
    }

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
469
    Q_ASSERT(unconstrained_stacking_order.contains(c));
470
    FocusChain::self()->moveAfterClient(c, under);
471 472
    updateStackingOrder();
}
473

474
void Workspace::restackClientUnderActive(AbstractClient* c)
475
{
476
    if (!active_client || active_client == c || active_client->layer() != c->layer()) {
477
        raiseClient(c);
478 479
        return;
    }
480 481 482
    restack(c, active_client);
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
483
void Workspace::restoreSessionStackingOrder(X11Client *c)
484 485
{
    if (c->sessionStackingOrder() < 0)
486
        return;
487 488
    StackingUpdatesBlocker blocker(this);
    unconstrained_stacking_order.removeAll(c);
489
    for (auto it = unconstrained_stacking_order.begin();  // from bottom
490 491
            it != unconstrained_stacking_order.end();
            ++it) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
492
        X11Client *current = qobject_cast<X11Client *>(*it);
493 494 495 496
        if (!current) {
            continue;
        }
        if (current->sessionStackingOrder() > c->sessionStackingOrder()) {
497
            unconstrained_stacking_order.insert(it, c);
498 499 500
            return;
        }
    }
501 502
    unconstrained_stacking_order.append(c);
}
503

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
504 505
/**
 * Returns a stacking order based upon \a list that fulfills certain contained.
506
 */
507
QList<Toplevel *> Workspace::constrainedStackingOrder()
508
{
509
    QList<Toplevel *> layer[ NumLayers ];
510 511

    // build the order from layers
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
512
    QVector< QMultiMap<Group*, Layer> > minimum_layer(screens()->count());
513
    for (auto it = unconstrained_stacking_order.constBegin(),
514
                                  end = unconstrained_stacking_order.constEnd(); it != end; ++it) {
515
        Layer l = (*it)->layer();
516 517

        const int screen = (*it)->screen();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
518
        X11Client *c = qobject_cast<X11Client *>(*it);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
519
        QMap< Group*, Layer >::iterator mLayer = minimum_layer[screen].find(c ? c->group() : nullptr);
520 521 522 523
        if (mLayer != minimum_layer[screen].end()) {
            // If a window is raised above some other window in the same window group
            // which is in the ActiveLayer (i.e. it's fulscreened), make sure it stays
            // above that window (see #95731).
524
            if (*mLayer == ActiveLayer && (l > BelowLayer))
525 526
                l = ActiveLayer;
            *mLayer = l;
527
        } else if (c) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
528
            minimum_layer[screen].insert(c->group(), l);
529
        }
530 531
        layer[ l ].append(*it);
    }
532
    QList<Toplevel *> stacking;
533 534 535
    for (int lay = FirstLayer; lay < NumLayers; ++lay) {
        stacking += layer[lay];
    }
536 537
    // now keep transients above their mainwindows
    // TODO this could(?) use some optimization
538 539
    for (int i = stacking.size() - 1; i >= 0;) {
        // Index of the main window for the current transient window.
540
        int i2 = -1;
541 542 543 544 545 546 547 548 549 550

        // If the current transient has "child" transients, we'd like to restart
        // construction of the constrained stacking order from the position where
        // the current transient will be moved.
        bool hasTransients = false;

        // Find topmost client this one is transient for.
        if (auto *client = qobject_cast<AbstractClient *>(stacking[i])) {
            if (!client->isTransient()) {
                --i;
551 552
                continue;
            }
553 554 555 556 557 558 559 560 561 562 563 564 565
            for (i2 = stacking.size() - 1; i2 >= 0; --i2) {
                auto *c2 = qobject_cast<AbstractClient *>(stacking[i2]);
                if (!c2) {
                    continue;
                }
                if (c2 == client) {
                    i2 = -1; // Don't reorder, already on top of its main window.
                    break;
                }
                if (c2->hasTransient(client, true)
                        && keepTransientAbove(c2, client)) {
                    break;
                }
566
            }
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600

            hasTransients = !client->transients().isEmpty();

            // If the current transient doesn't have any "alive" transients, check
            // whether it has deleted transients that have to be raised.
            const bool searchForDeletedTransients = !hasTransients
                && !deletedList().isEmpty();
            if (searchForDeletedTransients) {
                for (int j = i + 1; j < stacking.count(); ++j) {
                    auto *deleted = qobject_cast<Deleted *>(stacking[j]);
                    if (!deleted) {
                        continue;
                    }
                    if (deleted->wasTransientFor(client)) {
                        hasTransients = true;
                        break;
                    }
                }
            }
        } else if (auto *deleted = qobject_cast<Deleted *>(stacking[i])) {
            if (!deleted->wasTransient()) {
                --i;
                continue;
            }
            for (i2 = stacking.size() - 1; i2 >= 0; --i2) {
                Toplevel *c2 = stacking[i2];
                if (c2 == deleted) {
                    i2 = -1; // Don't reorder, already on top of its main window.
                    break;
                }
                if (deleted->wasTransientFor(c2)
                        && keepDeletedTransientAbove(c2, deleted)) {
                    break;
                }
601
            }
602
            hasTransients = !deleted->transients().isEmpty();
603
        }
604

605
        if (i2 == -1) {
606 607
            --i;
            continue;
608
        }
609 610 611

        Toplevel *current = stacking[i];

612 613
        stacking.removeAt(i);
        --i; // move onto the next item (for next for () iteration)
614
        --i2; // adjust index of the mainwindow after the remove above
615
        if (hasTransients) {  // this one now can be possibly above its transients,
616
            i = i2; // so go again higher in the stack order and possibly move those transients again
617
        }
618
        ++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end()
619 620
        stacking.insert(i2, current);
    }
621
    return stacking;
622
}
623

624 625 626 627
void Workspace::blockStackingUpdates(bool block)
{
    if (block) {
        if (block_stacking_updates == 0)
628 629
            blocked_propagating_new_clients = false;
        ++block_stacking_updates;
630 631 632 633 634
    } else // !block
        if (--block_stacking_updates == 0) {
            updateStackingOrder(blocked_propagating_new_clients);
            if (effects)
                static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowStacking();
635
        }
636
}
637

638 639
namespace {
template <class T>
640
QList<T*> ensureStackingOrderInList(const QList<Toplevel *> &stackingOrder, const QList<T*> &list)
641
{
642 643
    static_assert(std::is_base_of<Toplevel, T>::value,
                 "U must be derived from T");
644
// TODO    Q_ASSERT( block_stacking_updates == 0 );
645
    if (list.count() < 2)
646 647
        return list;
    // TODO is this worth optimizing?
648 649 650
    QList<T*> result = list;
    for (auto it = stackingOrder.begin();
            it != stackingOrder.end();
651
            ++it) {
652
        T *c = qobject_cast<T*>(*it);
653 654 655 656 657 658
        if (!c) {
            continue;
        }
        if (result.removeAll(c) != 0)
            result.append(c);
    }
659
    return result;
660
}
661 662 663
}

// Ensure list is in stacking order
664
QList<X11Client *> Workspace::ensureStackingOrder(const QList<X11Client *> &list) const
665 666 667 668 669 670 671 672
{
    return ensureStackingOrderInList(stacking_order, list);
}

QList<AbstractClient*> Workspace::ensureStackingOrder(const QList<AbstractClient*> &list) const
{
    return ensureStackingOrderInList(stacking_order, list);
}
673 674 675

// check whether a transient should be actually kept above its mainwindow
// there may be some special cases where this rule shouldn't be enfored
676
bool Workspace::keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient)
677
{
678
    // #93832 - don't keep splashscreens above dialogs
679
    if (transient->isSplash() && mainwindow->isDialog())
680 681 682 683 684
        return false;
    // This is rather a hack for #76026. Don't keep non-modal dialogs above
    // the mainwindow, but only if they're group transient (since only such dialogs
    // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker)
    // needs to be found.
685 686
    if (transient->isDialog() && !transient->isModal() && transient->groupTransient())
        return false;
687 688
    // #63223 - don't keep transients above docks, because the dock is kept high,
    // and e.g. dialogs for them would be too high too
689 690
    // ignore this if the transient has a placement hint which indicates it should go above it's parent
    if (mainwindow->isDock() && !transient->hasTransientPlacementHint())
691 692
        return false;
    return true;
693
}
694

695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
bool Workspace::keepDeletedTransientAbove(const Toplevel *mainWindow, const Deleted *transient) const
{
    // #93832 - Don't keep splashscreens above dialogs.
    if (transient->isSplash() && mainWindow->isDialog()) {
        return false;
    }

    if (transient->wasX11Client()) {
        // If a group transient was active, we should keep it above no matter
        // what, because at the time when the transient was closed, it was above
        // the main window.
        if (transient->wasGroupTransient() && transient->wasActive()) {
            return true;
        }

        // This is rather a hack for #76026. Don't keep non-modal dialogs above
        // the mainwindow, but only if they're group transient (since only such
        // dialogs have taskbar entry in Kicker). A proper way of doing this
        // (both kwin and kicker) needs to be found.
        if (transient->wasGroupTransient() && transient->isDialog()
                && !transient->isModal()) {
            return false;
        }

        // #63223 - Don't keep transients above docks, because the dock is kept
        // high, and e.g. dialogs for them would be too high too.
        if (mainWindow->isDock()) {
            return false;
        }
    }

    return true;
}

729
// Returns all windows in their stacking order on the root window.
730
QList<Toplevel *> Workspace::xStackingOrder() const
731
{
732
    if (m_xStackingDirty) {
733 734 735 736 737 738 739
        const_cast<Workspace*>(this)->updateXStackingOrder();
    }
    return x_stacking;
}

void Workspace::updateXStackingOrder()
{
740
    x_stacking.clear();
741
    std::unique_ptr<Xcb::Tree> tree{std::move(m_xStackingQueryTree)};
742
    // use our own stacking order, not the X one, as they may differ
743
    foreach (Toplevel * c, stacking_order)
744
    x_stacking.append(c);
745

746
    if (tree && !tree->isNull()) {
747 748
        xcb_window_t *windows = tree->children();
        const auto count = tree->data()->children_len;
749 750 751 752 753 754 755 756 757 758 759 760 761
        int foundUnmanagedCount = unmanaged.count();
        for (unsigned int i = 0;
                i < count;
                ++i) {
            for (auto it = unmanaged.constBegin(); it != unmanaged.constEnd(); ++it) {
                Unmanaged *u = *it;
                if (u->window() == windows[i]) {
                    x_stacking.append(u);
                    foundUnmanagedCount--;
                    break;
                }
            }
            if (foundUnmanagedCount == 0) {
762 763 764
                break;
            }
        }
765
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
766 767 768 769

    for (InternalClient *client : workspace()->internalClients()) {
        if (client->isShown(false)) {
            x_stacking.append(client);
770 771
        }
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
772

773
    m_xStackingDirty = false;
774
}
775

776 777 778 779
//*******************************
// Client
//*******************************

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
780
void X11Client::restackWindow(xcb_window_t above, int detail, NET::RequestSource src, xcb_timestamp_t timestamp, bool send_event)
781
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
782
    X11Client *other = nullptr;
783
    if (detail == XCB_STACK_MODE_OPPOSITE) {
784
        other = workspace()->findClient(Predicate::WindowMatch, above);
785 786 787 788
        if (!other) {
            workspace()->raiseOrLowerClient(this);
            return;
        }
789
        auto it = workspace()->stackingOrder().constBegin(),
790 791 792
                                    end = workspace()->stackingOrder().constEnd();
        while (it != end) {
            if (*it == this) {
793
                detail = XCB_STACK_MODE_ABOVE;
794 795
                break;
            } else if (*it == other) {
796
                detail = XCB_STACK_MODE_BELOW;
797 798 799 800
                break;
            }
            ++it;
        }
801
    }
802
    else if (detail == XCB_STACK_MODE_TOP_IF) {
803
        other = workspace()->findClient(Predicate::WindowMatch, above);
804
        if (other && other->frameGeometry().intersects(frameGeometry()))
805 806 807
            workspace()->raiseClientRequest(this, src, timestamp);
        return;
    }
808
    else if (detail == XCB_STACK_MODE_BOTTOM_IF) {
809
        other = workspace()->findClient(Predicate::WindowMatch, above);
810
        if (other && other->frameGeometry().intersects(frameGeometry()))
811 812 813 814 815
            workspace()->lowerClientRequest(this, src, timestamp);
        return;
    }

    if (!other)
816
        other = workspace()->findClient(Predicate::WindowMatch, above);
817

818
    if (other && detail == XCB_STACK_MODE_ABOVE) {
819
        auto it = workspace()->stackingOrder().constEnd(),
820 821 822 823 824 825 826 827
                                    begin = workspace()->stackingOrder().constBegin();
        while (--it != begin) {

            if (*it == other) { // the other one is top on stack
                it = begin; // invalidate
                src = NET::FromTool; // force
                break;
            }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
828
            X11Client *c = qobject_cast<X11Client *>(*it);
829

830
            if (!c || !(  (*it)->isNormalWindow() && c->isShown(true) &&
831 832 833 834 835 836 837 838
                    (*it)->isOnCurrentDesktop() && (*it)->isOnCurrentActivity() && (*it)->isOnScreen(screen()) ))
                continue; // irrelevant clients

            if (*(it - 1) == other)
                break; // "it" is the one above the target one, stack below "it"
        }

        if (it != begin && (*(it - 1) == other))
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
839
            other = qobject_cast<X11Client *>(*it);
840
        else
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
841
            other = nullptr;
842 843 844 845
    }

    if (other)
        workspace()->restack(this, other);
846
    else if (detail == XCB_STACK_MODE_BELOW)
847
        workspace()->lowerClientRequest(this, src, timestamp);
848
    else if (detail == XCB_STACK_MODE_ABOVE)
849 850
        workspace()->raiseClientRequest(this, src, timestamp);

851 852 853 854
    if (send_event)
        sendSyntheticConfigureNotify();
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
855
bool X11Client::belongsToDesktop() const
856
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
857
    foreach (const X11Client *c, group()->members()) {
858 859
        if (c->isDesktop())
            return true;
860
    }
861
    return false;
862 863
}

864
} // namespace