layers.cpp 29.3 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1 2 3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5 6
    SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
    SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
7

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
8 9
    SPDX-License-Identifier: GPL-2.0-or-later
*/
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

// 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.

30
 Every window has one layer assigned in which it is. There are 7 layers,
31
 from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer, NotificationLayer,
32 33 34 35
 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.
36
 The NoficationLayer contains notification windows which are kept above all windows except the active
37 38 39 40
 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.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

 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
57
 sure it's moved to the appropriate layer QList<X11Client *> if needed.
58 59 60 61 62 63 64 65 66 67 68 69 70 71

 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
72
#include "x11client.h"
73
#include "focuschain.h"
74
#include "netinfo.h"
75 76 77 78
#include "workspace.h"
#include "tabbox.h"
#include "group.h"
#include "rules.h"
79
#include "screens.h"
80
#include "unmanaged.h"
81
#include "deleted.h"
Martin Flöser's avatar
Martin Flöser committed
82
#include "effects.h"
83
#include "composite.h"
84
#include "screenedge.h"
85
#include "wayland_server.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
86
#include "internal_client.h"
87

Martin Flöser's avatar
Martin Flöser committed
88 89
#include <QDebug>

90 91 92 93 94 95 96
namespace KWin
{

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

97
void Workspace::updateClientLayer(AbstractClient* c)
98
{
99 100
    if (c)
        c->updateLayer();
101
}
102

103 104 105 106
void Workspace::updateStackingOrder(bool propagate_new_clients)
{
    if (block_stacking_updates > 0) {
        if (propagate_new_clients)
107 108
            blocked_propagating_new_clients = true;
        return;
109
    }
110
    QList<Toplevel *> new_stacking_order = constrainedStackingOrder();
111
    bool changed = (force_restacking || new_stacking_order != stacking_order);
112
    force_restacking = false;
113
    stacking_order = new_stacking_order;
114 115
    if (changed || propagate_new_clients) {
        propagateClients(propagate_new_clients);
116
        markXStackingOrderAsDirty();
117
        emit stackingOrderChanged();
118 119 120 121
        if (m_compositor) {
            m_compositor->addRepaintFull();
        }

122
        if (active_client)
Luboš Luňák's avatar
Luboš Luňák committed
123
            active_client->updateMouseGrab();
124
    }
125
}
126

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
127
/**
128 129 130 131 132
 * 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.
133
 */
134 135
void Workspace::stackScreenEdgesUnderOverrideRedirect()
{
136 137 138
    if (!rootInfo()) {
        return;
    }
139
    Xcb::restackWindows(QVector<xcb_window_t>() << rootInfo()->supportWindow() << ScreenEdges::self()->windows());
140 141
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
142 143 144
/**
 * Propagates the managed clients to the world.
 * Called ONLY from updateStackingOrder().
145
 */
146 147
void Workspace::propagateClients(bool propagate_new_clients)
{
148 149 150
    if (!rootInfo()) {
        return;
    }
151
    // restack the windows according to the stacking order
Thomas Lübking's avatar
Thomas Lübking committed
152 153 154
    // supportWindow > electric borders > clients > hidden clients
    QVector<xcb_window_t> newWindowStack;

155 156 157 158 159
    // 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).
160
    newWindowStack << rootInfo()->supportWindow();
Thomas Lübking's avatar
Thomas Lübking committed
161 162 163

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

164 165
    newWindowStack << manual_overlays;

Thomas Lübking's avatar
Thomas Lübking committed
166 167 168
    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
169
        X11Client *client = qobject_cast<X11Client *>(stacking_order.at(i));
170
        if (!client || client->hiddenPreview()) {
171
            continue;
172
        }
173 174 175

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

Thomas Lübking's avatar
Thomas Lübking committed
178
        newWindowStack << client->frameId();
179
    }
180 181 182 183

    // 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
184
    for (int i = stacking_order.size() - 1; i >= 0; --i) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
185
        X11Client *client = qobject_cast<X11Client *>(stacking_order.at(i));
186
        if (!client || !client->hiddenPreview())
187
            continue;
Thomas Lübking's avatar
Thomas Lübking committed
188
        newWindowStack << client->frameId();
189
    }
190 191
    // 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
192
    Q_ASSERT(newWindowStack.at(0) == rootInfo()->supportWindow());
Thomas Lübking's avatar
Thomas Lübking committed
193
    Xcb::restackWindows(newWindowStack);
194

195
    int pos = 0;
196
    xcb_window_t *cl(nullptr);
197
    if (propagate_new_clients) {
198
        cl = new xcb_window_t[ manual_overlays.count() + clients.count()];
199 200 201
        for (const auto win : manual_overlays) {
            cl[pos++] = win;
        }
202
        for (auto it = clients.constBegin(); it != clients.constEnd(); ++it)
203
            cl[pos++] = (*it)->window();
204
        rootInfo()->setClientList(cl, pos);
205
        delete [] cl;
206
    }
207

208
    cl = new xcb_window_t[ manual_overlays.count() + stacking_order.count()];
209
    pos = 0;
210
    for (auto it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) {
211 212 213 214
        X11Client *client = qobject_cast<X11Client *>(*it);
        if (client) {
            cl[pos++] = client->window();
        }
215
    }
216 217 218
    for (const auto win : manual_overlays) {
        cl[pos++] = win;
    }
219
    rootInfo()->setClientListStacking(cl, pos);
220
    delete [] cl;
221
}
222

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
223 224 225 226
/**
 * 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.
227
 */
228
// TODO misleading name for this method, too many slightly different ways to use it
229
AbstractClient* Workspace::topClientOnDesktop(int desktop, int screen, bool unconstrained, bool only_normal) const
230
{
231
// TODO    Q_ASSERT( block_stacking_updates == 0 );
232
    QList<Toplevel *> list;
233
    if (!unconstrained)
Luboš Luňák's avatar
Luboš Luňák committed
234
        list = stacking_order;
235
    else
Luboš Luňák's avatar
Luboš Luňák committed
236
        list = unconstrained_stacking_order;
237 238 239
    for (int i = list.size() - 1;
            i >= 0;
            --i) {
240
        AbstractClient *c = qobject_cast<AbstractClient*>(list.at(i));
241 242 243 244 245
        if (!c) {
            continue;
        }
        if (c->isOnDesktop(desktop) && c->isShown(false) && c->isOnCurrentActivity()) {
            if (screen != -1 && c->screen() != screen)
246
                continue;
247
            if (!only_normal)
248 249 250
                return c;
            if (c->wantsTabFocus() && !c->isSpecialWindow())
                return c;
251 252
        }
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
253
    return nullptr;
254
}
255

256
AbstractClient* Workspace::findDesktop(bool topmost, int desktop) const
257
{
258
// TODO    Q_ASSERT( block_stacking_updates == 0 );
259 260
    if (topmost) {
        for (int i = stacking_order.size() - 1; i >= 0; i--) {
261
            AbstractClient *c = qobject_cast<AbstractClient*>(stacking_order.at(i));
262
            if (c && c->isOnDesktop(desktop) && c->isDesktop()
263
                    && c->isShown(true))
264 265
                return c;
        }
266 267
    } else { // bottom-most
        foreach (Toplevel * c, stacking_order) {
268
            AbstractClient *client = qobject_cast<AbstractClient*>(c);
269 270 271 272
            if (client && c->isOnDesktop(desktop) && c->isDesktop()
                    && client->isShown(true))
                return client;
        }
273
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
274
    return nullptr;
275
}
276

277
void Workspace::raiseOrLowerClient(AbstractClient *c)
278
{
279
    if (!c) return;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
280
    AbstractClient* topmost = nullptr;
281
// TODO    Q_ASSERT( block_stacking_updates == 0 );
282 283
    if (most_recently_raised && stacking_order.contains(most_recently_raised) &&
            most_recently_raised->isShown(true) && c->isOnCurrentDesktop())
284 285
        topmost = most_recently_raised;
    else
286
        topmost = topClientOnDesktop(c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(),
287
                                     options->isSeparateScreenFocus() ? c->screen() : -1);
288

289
    if (c == topmost)
290 291 292
        lowerClient(c);
    else
        raiseClient(c);
293
}
294 295


296
void Workspace::lowerClient(AbstractClient* c, bool nogroup)
297 298
{
    if (!c)
299 300 301 302
        return;

    c->cancelAutoRaise();

303
    StackingUpdatesBlocker blocker(this);
304

305 306 307
    unconstrained_stacking_order.removeAll(c);
    unconstrained_stacking_order.prepend(c);
    if (!nogroup && c->isTransient()) {
308
        // lower also all windows in the group, in their reversed stacking order
309
        QList<X11Client *> wins;
310 311
        if (auto group = c->group()) {
            wins = ensureStackingOrder(group->members());
312
        }
313 314 315 316 317
        for (int i = wins.size() - 1;
                i >= 0;
                --i) {
            if (wins[ i ] != c)
                lowerClient(wins[ i ], true);
318
        }
319
    }
320

321
    if (c == most_recently_raised)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
322
        most_recently_raised = nullptr;
323
}
324

325
void Workspace::lowerClientWithinApplication(AbstractClient* c)
326 327
{
    if (!c)
328 329 330 331
        return;

    c->cancelAutoRaise();

332
    StackingUpdatesBlocker blocker(this);
333

334
    unconstrained_stacking_order.removeAll(c);
335 336
    bool lowered = false;
    // first try to put it below the bottom-most window of the application
337
    for (auto it = unconstrained_stacking_order.begin();
338
            it != unconstrained_stacking_order.end();
339
            ++it) {
340
        AbstractClient *client = qobject_cast<AbstractClient*>(*it);
341 342 343
        if (!client) {
            continue;
        }
344
        if (AbstractClient::belongToSameApplication(client, c)) {
345
            unconstrained_stacking_order.insert(it, c);
346 347
            lowered = true;
            break;
348
        }
349
    }
350 351
    if (!lowered)
        unconstrained_stacking_order.prepend(c);
352
    // ignore mainwindows
353
}
354

355
void Workspace::raiseClient(AbstractClient* c, bool nogroup)
356 357
{
    if (!c)
358 359 360 361
        return;

    c->cancelAutoRaise();

362
    StackingUpdatesBlocker blocker(this);
363

364
    if (!nogroup && c->isTransient()) {
365 366
        QList<AbstractClient*> transients;
        AbstractClient *transient_parent = c;
367 368
        while ((transient_parent = transient_parent->transientFor()))
            transients << transient_parent;
369
        foreach (transient_parent, transients)
370
            raiseClient(transient_parent, true);
371
    }
372

373 374
    unconstrained_stacking_order.removeAll(c);
    unconstrained_stacking_order.append(c);
375

376
    if (!c->isSpecialWindow()) {
377 378
        most_recently_raised = c;
    }
379
}
380

381
void Workspace::raiseClientWithinApplication(AbstractClient* c)
382 383
{
    if (!c)
384 385 386 387
        return;

    c->cancelAutoRaise();

388
    StackingUpdatesBlocker blocker(this);
389
    // ignore mainwindows
390

391
    // first try to put it above the top-most window of the application
392
    for (int i = unconstrained_stacking_order.size() - 1; i > -1 ; --i) {
393
        AbstractClient *other = qobject_cast<AbstractClient*>(unconstrained_stacking_order.at(i));
394 395 396
        if (!other) {
            continue;
        }
397
        if (other == c)     // don't lower it just because it asked to be raised
398
            return;
399
        if (AbstractClient::belongToSameApplication(other, c)) {
400
            unconstrained_stacking_order.removeAll(c);
401 402
            unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(other) + 1, c);   // insert after the found one
            break;
403 404
        }
    }
405
}
406

407
void Workspace::raiseClientRequest(KWin::AbstractClient *c, NET::RequestSource src, xcb_timestamp_t timestamp)
408 409 410 411 412
{
    if (src == NET::FromTool || allowFullClientRaising(c, timestamp))
        raiseClient(c);
    else {
        raiseClientWithinApplication(c);
413 414
        c->demandAttention();
    }
415
}
416

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
417
void Workspace::lowerClientRequest(KWin::X11Client *c, NET::RequestSource src, xcb_timestamp_t /*timestamp*/)
418
{
419 420 421 422
    // 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.
423 424
    if (src == NET::FromTool || !c->hasUserTimeSupport())
        lowerClient(c);
425
    else
426 427
        lowerClientWithinApplication(c);
}
428

429 430 431 432
void Workspace::lowerClientRequest(KWin::AbstractClient *c)
{
    lowerClientWithinApplication(c);
}
433

434
void Workspace::restack(AbstractClient* c, AbstractClient* under, bool force)
435
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
436
    Q_ASSERT(unconstrained_stacking_order.contains(under));
437
    if (!force && !AbstractClient::belongToSameApplication(under, c)) {
438
         // put in the stacking order below _all_ windows belonging to the active application
439
        for (int i = 0; i < unconstrained_stacking_order.size(); ++i) {
440 441
            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
442
                under = (c == other) ? nullptr : other;
443
                break;
444 445
            }
        }
446
    }
447 448 449 450 451
    if (under) {
        unconstrained_stacking_order.removeAll(c);
        unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(under), c);
    }

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
452
    Q_ASSERT(unconstrained_stacking_order.contains(c));
453
    FocusChain::self()->moveAfterClient(c, under);
454 455
    updateStackingOrder();
}
456

457
void Workspace::restackClientUnderActive(AbstractClient* c)
458
{
459
    if (!active_client || active_client == c || active_client->layer() != c->layer()) {
460
        raiseClient(c);
461 462
        return;
    }
463 464 465
    restack(c, active_client);
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
466
void Workspace::restoreSessionStackingOrder(X11Client *c)
467 468
{
    if (c->sessionStackingOrder() < 0)
469
        return;
470 471
    StackingUpdatesBlocker blocker(this);
    unconstrained_stacking_order.removeAll(c);
472
    for (auto it = unconstrained_stacking_order.begin();  // from bottom
473 474
            it != unconstrained_stacking_order.end();
            ++it) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
475
        X11Client *current = qobject_cast<X11Client *>(*it);
476 477 478 479
        if (!current) {
            continue;
        }
        if (current->sessionStackingOrder() > c->sessionStackingOrder()) {
480
            unconstrained_stacking_order.insert(it, c);
481 482 483
            return;
        }
    }
484 485
    unconstrained_stacking_order.append(c);
}
486

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
487 488
/**
 * Returns a stacking order based upon \a list that fulfills certain contained.
489
 */
490
QList<Toplevel *> Workspace::constrainedStackingOrder()
491
{
492
    QList<Toplevel *> layer[ NumLayers ];
493 494

    // build the order from layers
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
495
    QVector< QMultiMap<Group*, Layer> > minimum_layer(screens()->count());
496
    for (auto it = unconstrained_stacking_order.constBegin(),
497
                                  end = unconstrained_stacking_order.constEnd(); it != end; ++it) {
498
        Layer l = (*it)->layer();
499 500

        const int screen = (*it)->screen();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
501
        X11Client *c = qobject_cast<X11Client *>(*it);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
502
        QMap< Group*, Layer >::iterator mLayer = minimum_layer[screen].find(c ? c->group() : nullptr);
503 504 505 506
        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).
507
            if (*mLayer == ActiveLayer && (l > BelowLayer))
508 509
                l = ActiveLayer;
            *mLayer = l;
510
        } else if (c) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
511
            minimum_layer[screen].insert(c->group(), l);
512
        }
513 514
        layer[ l ].append(*it);
    }
515
    QList<Toplevel *> stacking;
516 517 518
    for (int lay = FirstLayer; lay < NumLayers; ++lay) {
        stacking += layer[lay];
    }
519 520
    // now keep transients above their mainwindows
    // TODO this could(?) use some optimization
521 522
    for (int i = stacking.size() - 1; i >= 0;) {
        // Index of the main window for the current transient window.
523
        int i2 = -1;
524 525 526 527 528 529 530 531 532 533

        // 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;
534 535
                continue;
            }
536 537 538 539 540 541 542 543 544 545 546 547 548
            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;
                }
549
            }
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583

            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;
                }
584
            }
585
            hasTransients = !deleted->transients().isEmpty();
586
        }
587

588
        if (i2 == -1) {
589 590
            --i;
            continue;
591
        }
592 593 594

        Toplevel *current = stacking[i];

595 596
        stacking.removeAt(i);
        --i; // move onto the next item (for next for () iteration)
597
        --i2; // adjust index of the mainwindow after the remove above
598
        if (hasTransients) {  // this one now can be possibly above its transients,
599
            i = i2; // so go again higher in the stack order and possibly move those transients again
600
        }
601
        ++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end()
602 603
        stacking.insert(i2, current);
    }
604
    return stacking;
605
}
606

607 608 609 610
void Workspace::blockStackingUpdates(bool block)
{
    if (block) {
        if (block_stacking_updates == 0)
611 612
            blocked_propagating_new_clients = false;
        ++block_stacking_updates;
613 614 615 616 617
    } else // !block
        if (--block_stacking_updates == 0) {
            updateStackingOrder(blocked_propagating_new_clients);
            if (effects)
                static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowStacking();
618
        }
619
}
620

621 622
namespace {
template <class T>
623
QList<T*> ensureStackingOrderInList(const QList<Toplevel *> &stackingOrder, const QList<T*> &list)
624
{
625 626
    static_assert(std::is_base_of<Toplevel, T>::value,
                 "U must be derived from T");
627
// TODO    Q_ASSERT( block_stacking_updates == 0 );
628
    if (list.count() < 2)
629 630
        return list;
    // TODO is this worth optimizing?
631 632 633
    QList<T*> result = list;
    for (auto it = stackingOrder.begin();
            it != stackingOrder.end();
634
            ++it) {
635
        T *c = qobject_cast<T*>(*it);
636 637 638 639 640 641
        if (!c) {
            continue;
        }
        if (result.removeAll(c) != 0)
            result.append(c);
    }
642
    return result;
643
}
644 645 646
}

// Ensure list is in stacking order
647
QList<X11Client *> Workspace::ensureStackingOrder(const QList<X11Client *> &list) const
648 649 650 651 652 653 654 655
{
    return ensureStackingOrderInList(stacking_order, list);
}

QList<AbstractClient*> Workspace::ensureStackingOrder(const QList<AbstractClient*> &list) const
{
    return ensureStackingOrderInList(stacking_order, list);
}
656 657 658

// check whether a transient should be actually kept above its mainwindow
// there may be some special cases where this rule shouldn't be enfored
659
bool Workspace::keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient)
660
{
661
    // #93832 - don't keep splashscreens above dialogs
662
    if (transient->isSplash() && mainwindow->isDialog())
663 664 665 666 667
        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.
668 669
    if (transient->isDialog() && !transient->isModal() && transient->groupTransient())
        return false;
670 671
    // #63223 - don't keep transients above docks, because the dock is kept high,
    // and e.g. dialogs for them would be too high too
672 673
    // ignore this if the transient has a placement hint which indicates it should go above it's parent
    if (mainwindow->isDock() && !transient->hasTransientPlacementHint())
674 675
        return false;
    return true;
676
}
677

678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
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;
}

712
// Returns all windows in their stacking order on the root window.
713
QList<Toplevel *> Workspace::xStackingOrder() const
714
{
715
    if (m_xStackingDirty) {
716 717 718 719 720 721 722
        const_cast<Workspace*>(this)->updateXStackingOrder();
    }
    return x_stacking;
}

void Workspace::updateXStackingOrder()
{
723
    // use our own stacking order, not the X one, as they may differ
724
    x_stacking = stacking_order;
725

726 727
    if (m_xStackingQueryTree && !m_xStackingQueryTree->isNull()) {
        std::unique_ptr<Xcb::Tree> tree{std::move(m_xStackingQueryTree)};
728 729
        xcb_window_t *windows = tree->children();
        const auto count = tree->data()->children_len;
730 731 732 733 734 735 736 737 738 739 740 741 742
        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) {
743 744 745
                break;
            }
        }
746
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
747 748 749 750

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

754
    m_xStackingDirty = false;
755
}
756

757 758 759 760
//*******************************
// Client
//*******************************

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
761
void X11Client::restackWindow(xcb_window_t above, int detail, NET::RequestSource src, xcb_timestamp_t timestamp, bool send_event)
762
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
763
    X11Client *other = nullptr;
764
    if (detail == XCB_STACK_MODE_OPPOSITE) {
765
        other = workspace()->findClient(Predicate::WindowMatch, above);
766 767 768 769
        if (!other) {
            workspace()->raiseOrLowerClient(this);
            return;
        }
770
        auto it = workspace()->stackingOrder().constBegin(),
771 772 773
                                    end = workspace()->stackingOrder().constEnd();
        while (it != end) {
            if (*it == this) {
774
                detail = XCB_STACK_MODE_ABOVE;
775 776
                break;
            } else if (*it == other) {
777
                detail = XCB_STACK_MODE_BELOW;
778 779 780 781
                break;
            }
            ++it;
        }
782
    }
783
    else if (detail == XCB_STACK_MODE_TOP_IF) {
784
        other = workspace()->findClient(Predicate::WindowMatch, above);
785
        if (other && other->frameGeometry().intersects(frameGeometry()))
786 787 788
            workspace()->raiseClientRequest(this, src, timestamp);
        return;
    }
789
    else if (detail == XCB_STACK_MODE_BOTTOM_IF) {
790
        other = workspace()->findClient(Predicate::WindowMatch, above);
791
        if (other && other->frameGeometry().intersects(frameGeometry()))
792 793 794 795 796
            workspace()->lowerClientRequest(this, src, timestamp);
        return;
    }

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

799
    if (other && detail == XCB_STACK_MODE_ABOVE) {
800
        auto it = workspace()->stackingOrder().constEnd(),