workspace.cpp 102 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
7
    SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
    SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
    SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
8

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
9
10
    SPDX-License-Identifier: GPL-2.0-or-later
*/
11
// own
12
#include "workspace.h"
13
14
15
// kwin libs
#include <kwinglplatform.h>
// kwin
16
#include "abstract_wayland_output.h"
17
18
19
#ifdef KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
20
#include "appmenu.h"
21
#include "atoms.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
22
#include "x11client.h"
23
#include "composite.h"
24
25
26
27
#include "cursor.h"
#include "dbusinterface.h"
#include "deleted.h"
#include "effects.h"
28
#include "focuschain.h"
29
#include "group.h"
30
#include "input.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
31
#include "internal_client.h"
32
#include "moving_client_x11_filter.h"
33
#include "killwindow.h"
34
#include "netinfo.h"
35
#include "outline.h"
36
#include "placement.h"
37
#include "pluginmanager.h"
38
#include "rules.h"
39
#include "screenedge.h"
40
#include "screens.h"
David Edmundson's avatar
David Edmundson committed
41
#include "platform.h"
42
#include "scripting/scripting.h"
43
#include "syncalarmx11filter.h"
44
45
#ifdef KWIN_BUILD_TABBOX
#include "tabbox.h"
46
#endif
47
48
49
#include "unmanaged.h"
#include "useractions.h"
#include "virtualdesktops.h"
50
#include "was_user_interaction_x11_filter.h"
51
#include "wayland_server.h"
52
#include "xcbutils.h"
53
#include "main.h"
54
#include "decorations/decorationbridge.h"
55
#include "xwaylandclient.h"
56
// KDE
57
58
59
60
#include <KConfig>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KStartupInfo>
61
62
// Qt
#include <QtConcurrentRun>
Benjamin Port's avatar
Benjamin Port committed
63

64
65
66
67
namespace KWin
{

extern int screen_number;
68
extern bool is_multihead;
69

70
71
72
73
74
75
76
77
78
79
X11EventFilterContainer::X11EventFilterContainer(X11EventFilter *filter)
    : m_filter(filter)
{
}

X11EventFilter *X11EventFilterContainer::filter() const
{
    return m_filter;
}

80
81
ColorMapper::ColorMapper(QObject *parent)
    : QObject(parent)
82
83
    , m_default(kwinApp()->x11DefaultScreen()->default_colormap)
    , m_installed(kwinApp()->x11DefaultScreen()->default_colormap)
84
85
86
87
88
89
90
91
92
93
{
}

ColorMapper::~ColorMapper()
{
}

void ColorMapper::update()
{
    xcb_colormap_t cmap = m_default;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
94
    if (X11Client *c = dynamic_cast<X11Client *>(Workspace::self()->activeClient())) {
95
96
97
98
99
100
101
102
103
104
        if (c->colormap() != XCB_COLORMAP_NONE) {
            cmap = c->colormap();
        }
    }
    if (cmap != m_installed) {
        xcb_install_colormap(connection(), cmap);
        m_installed = cmap;
    }
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
105
Workspace* Workspace::_self = nullptr;
106

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
107
Workspace::Workspace()
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
108
109
    : QObject(nullptr)
    , m_compositor(nullptr)
110
    // Unsorted
111
    , m_quickTileCombineTimer(nullptr)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
112
113
    , active_popup(nullptr)
    , active_popup_client(nullptr)
114
    , m_initialDesktop(1)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
115
116
117
118
    , active_client(nullptr)
    , last_active_client(nullptr)
    , movingClient(nullptr)
    , delayfocus_client(nullptr)
119
120
121
122
    , force_restacking(false)
    , showing_desktop(false)
    , was_user_interaction(false)
    , block_focus(0)
123
    , m_userActionsMenu(new UserActionsMenu(this))
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
124
125
    , client_keys_dialog(nullptr)
    , client_keys_client(nullptr)
126
127
128
129
    , global_shortcuts_disabled_for_client(false)
    , workspaceInit(true)
    , set_active_client_recursion(0)
    , block_stacking_updates(0)
Adrien Faveraux's avatar
Adrien Faveraux committed
130
    , m_sessionManager(new SessionManager(this))
131
{
132
133
134
    // If KWin was already running it saved its configuration after loosing the selection -> Reread
    QFuture<void> reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration);

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
135
136
    ApplicationMenu::create(this);

137
    _self = this;
138

139
#ifdef KWIN_BUILD_ACTIVITIES
140
    Activities *activities = nullptr;
141
    if (kwinApp()->usesKActivities()) {
142
143
144
        activities = Activities::create(this);
    }
    if (activities) {
145
        connect(activities, &Activities::currentChanged, this, &Workspace::updateCurrentActivity);
146
    }
147
148
#endif

149
150
    // PluginMgr needs access to the config file, so we need to wait for it for finishing
    reparseConfigFuture.waitForFinished();
151

152
153
    options->loadConfig();
    options->loadCompositingConfig(false);
154

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
155
    delayFocusTimer = nullptr;
156

157
158
159
    m_quickTileCombineTimer = new QTimer(this);
    m_quickTileCombineTimer->setSingleShot(true);

160
    RuleBook::create(this)->load();
161

162
    kwinApp()->createScreens();
163
164
    ScreenEdges::create(this);

165
166
167
168
    // VirtualDesktopManager needs to be created prior to init shortcuts
    // and prior to TabBox, due to TabBox connecting to signals
    // actual initialization happens in init()
    VirtualDesktopManager::create(this);
169
170
    //dbus interface
    new VirtualDesktopManagerDBusInterface(VirtualDesktopManager::self());
171

172
#ifdef KWIN_BUILD_TABBOX
173
    // need to create the tabbox before compositing scene is setup
174
    TabBox::TabBox::create(this);
175
#endif
176

177
178
179
    if (Compositor::self()) {
        m_compositor = Compositor::self();
    } else {
180
181
        Q_ASSERT(kwinApp()->operationMode() == Application::OperationMode::OperationModeX11);
        m_compositor = X11Compositor::create(this);
182
    }
183
    connect(this, &Workspace::currentDesktopChanged, m_compositor, &Compositor::addRepaintFull);
184
    connect(m_compositor, &QObject::destroyed, this, [this] { m_compositor = nullptr; });
185

186
187
188
    auto decorationBridge = Decoration::DecorationBridge::create(this);
    decorationBridge->init();
    connect(this, &Workspace::configChanged, decorationBridge, &Decoration::DecorationBridge::reconfigure);
189

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
190
191
192
193
194
195
196
197
198
    connect(m_sessionManager, &SessionManager::loadSessionRequested, this, &Workspace::loadSessionInfo);

    connect(m_sessionManager, &SessionManager::prepareSessionSaveRequested, this, [this](const QString &name) {
        storeSession(name, SMSavePhase0);
    });
    connect(m_sessionManager, &SessionManager::finishSessionSaveRequested, this, [this](const QString &name) {
        storeSession(name, SMSavePhase2);
    });

199
    new DBusInterface(this);
Martin Flöser's avatar
Martin Flöser committed
200
    Outline::create(this);
Arthur Arlt's avatar
Arthur Arlt committed
201
202

    initShortcuts();
203
204

    init();
205
206
}

207
void Workspace::init()
208
{
209
    KSharedConfigPtr config = kwinApp()->config();
210
    ScreenEdges *screenEdges = ScreenEdges::self();
211
    screenEdges->setConfig(config);
212
    screenEdges->init();
213
214
    connect(options, &Options::configChanged, screenEdges, &ScreenEdges::reconfigure);
    connect(VirtualDesktopManager::self(), &VirtualDesktopManager::layoutChanged, screenEdges, &ScreenEdges::updateLayout);
215
    connect(this, &Workspace::clientActivated, screenEdges, &ScreenEdges::checkBlocking);
216

217
    FocusChain *focusChain = FocusChain::create(this);
218
219
    connect(this, &Workspace::clientRemoved, focusChain, &FocusChain::remove);
    connect(this, &Workspace::clientActivated, focusChain, &FocusChain::setActiveClient);
220
221
222
    connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, focusChain, [focusChain]() {
        focusChain->setCurrentDesktop(VirtualDesktopManager::self()->currentDesktop());
    });
223
    connect(options, &Options::separateScreenFocusChanged, focusChain, &FocusChain::setSeparateScreenFocus);
224
225
    focusChain->setSeparateScreenFocus(options->isSeparateScreenFocus());

226
227
228
229
230
231
232
233
234
    Platform *platform = kwinApp()->platform();
    connect(platform, &Platform::outputEnabled, this, &Workspace::slotOutputEnabled);
    connect(platform, &Platform::outputDisabled, this, &Workspace::slotOutputDisabled);

    const QVector<AbstractOutput *> outputs = platform->enabledOutputs();
    for (AbstractOutput *output : outputs) {
        slotOutputEnabled(output);
    }

235
236
    // create VirtualDesktopManager and perform dependency injection
    VirtualDesktopManager *vds = VirtualDesktopManager::self();
237
238
    connect(vds, &VirtualDesktopManager::desktopCreated, this, &Workspace::slotDesktopAdded);
    connect(vds, &VirtualDesktopManager::desktopRemoved, this, &Workspace::slotDesktopRemoved);
239
    connect(vds, &VirtualDesktopManager::currentChanged, this, &Workspace::slotCurrentDesktopChanged);
240
    vds->setNavigationWrappingAround(options->isRollOverDesktops());
241
    connect(options, &Options::rollOverDesktopsChanged, vds, &VirtualDesktopManager::setNavigationWrappingAround);
242
    vds->setConfig(config);
243

244
    // Now we know how many desktops we'll have, thus we initialize the positioning object
245
    Placement::create(this);
246

247
248
249
    // positioning object needs to be created before the virtual desktops are loaded.
    vds->load();
    vds->updateLayout();
250
251
252
    //makes sure any autogenerated id is saved, necessary as in case of xwayland, load will be called 2 times
    // load is needed to be called again when starting xwayalnd to sync to RootInfo, see BUG 385260
    vds->save();
253

254
    if (!VirtualDesktopManager::self()->setCurrent(m_initialDesktop))
255
        VirtualDesktopManager::self()->setCurrent(1);
256

257
258
    reconfigureTimer.setSingleShot(true);
    updateToolWindowsTimer.setSingleShot(true);
259

260
261
    connect(&reconfigureTimer, &QTimer::timeout, this, &Workspace::slotReconfigure);
    connect(&updateToolWindowsTimer, &QTimer::timeout, this, &Workspace::slotUpdateToolWindows);
262

263
264
265
266
267
268
269
270
    // TODO: do we really need to reconfigure everything when fonts change?
    // maybe just reconfigure the decorations? Move this into libkdecoration?
    QDBusConnection::sessionBus().connect(QString(),
                                          QStringLiteral("/KDEPlatformTheme"),
                                          QStringLiteral("org.kde.KDEPlatformTheme"),
                                          QStringLiteral("refreshFonts"),
                                          this, SLOT(reconfigure()));

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
271
    active_client = nullptr;
272

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
273
274
275
276
277
    // We want to have some xcb connection while tearing down X11 components. We don't really
    // care if the xcb connection is broken or has an error.
    connect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initializeX11);
    connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &Workspace::cleanupX11);
    initializeX11();
278

279
    Scripting::create(this);
280

281
282
283
    if (auto server = waylandServer()) {
        connect(server, &WaylandServer::shellClientAdded, this, &Workspace::addShellClient);
        connect(server, &WaylandServer::shellClientRemoved, this, &Workspace::removeShellClient);
284
285
    }

286
    // SELI TODO: This won't work with unreasonable focus policies,
287
288
289
    // and maybe in rare cases also if the selected client doesn't
    // want focus
    workspaceInit = false;
290

291
292
293
    // broadcast that Workspace is ready, but first process all events.
    QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection);

294
    // TODO: ungrabXServer()
295
}
296

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
297
void Workspace::initializeX11()
298
299
300
301
302
303
304
305
306
{
    if (!kwinApp()->x11Connection()) {
        return;
    }

    atoms->retrieveHelpers();

    // first initialize the extensions
    Xcb::Extensions::self();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
307
308
    m_colorMapper.reset(new ColorMapper(this));
    connect(this, &Workspace::clientActivated, m_colorMapper.data(), &ColorMapper::update);
309
310

    // Call this before XSelectInput() on the root window
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
311
312
    m_startup.reset(new KStartupInfo(
        KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this));
313
314
315
316
317
318
319
320
321
322
323
324
325
326

    // Select windowmanager privileges
    selectWmInputEventMask();

    // Compatibility
    int32_t data = 1;

    xcb_change_property(connection(), XCB_PROP_MODE_APPEND, rootWindow(), atoms->kwin_running,
                        atoms->kwin_running, 32, 1, &data);

    if (kwinApp()->operationMode() == Application::OperationModeX11) {
        m_wasUserInteractionFilter.reset(new WasUserInteractionX11Filter);
        m_movingClientFilter.reset(new MovingClientX11Filter);
    }
327
328
329
    if (Xcb::Extensions::self()->isSyncAvailable()) {
        m_syncAlarmFilter.reset(new SyncAlarmX11Filter);
    }
330
331
332
333
334
335
336
    updateXTime(); // Needed for proper initialization of user_time in Client ctor

    const uint32_t nullFocusValues[] = {true};
    m_nullFocus.reset(new Xcb::Window(QRect(-1, -1, 1, 1), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, nullFocusValues));
    m_nullFocus->map();

    RootInfo *rootInfo = RootInfo::create();
337
338
    const auto vds = VirtualDesktopManager::self();
    vds->setRootInfo(rootInfo);
339
    rootInfo->activate();
340
341
342
343

    // TODO: only in X11 mode
    // Extra NETRootInfo instance in Client mode is needed to get the values of the properties
    NETRootInfo client_info(connection(), NET::ActiveWindow | NET::CurrentDesktop);
344
    if (!qApp->isSessionRestored()) {
345
        m_initialDesktop = client_info.currentDesktop();
346
347
        vds->setCurrent(m_initialDesktop);
    }
348
349

    // TODO: better value
350
    rootInfo->setActiveWindow(XCB_WINDOW_NONE);
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
    focusToNull();

    if (!qApp->isSessionRestored())
        ++block_focus; // Because it will be set below

    {
        // Begin updates blocker block
        StackingUpdatesBlocker blocker(this);

        Xcb::Tree tree(rootWindow());
        xcb_window_t *wins = xcb_query_tree_children(tree.data());

        QVector<Xcb::WindowAttributes> windowAttributes(tree->children_len);
        QVector<Xcb::WindowGeometry> windowGeometries(tree->children_len);

        // Request the attributes and geometries of all toplevel windows
        for (int i = 0; i < tree->children_len; i++) {
            windowAttributes[i] = Xcb::WindowAttributes(wins[i]);
            windowGeometries[i] = Xcb::WindowGeometry(wins[i]);
        }

        // Get the replies
        for (int i = 0; i < tree->children_len; i++) {
            Xcb::WindowAttributes attr(windowAttributes.at(i));

            if (attr.isNull()) {
                continue;
            }

            if (attr->override_redirect) {
                if (attr->map_state == XCB_MAP_STATE_VIEWABLE &&
                    attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY)
                    // ### This will request the attributes again
                    createUnmanaged(wins[i]);
            } else if (attr->map_state != XCB_MAP_STATE_UNMAPPED) {
                if (Application::wasCrash()) {
                    fixPositionAfterCrash(wins[i], windowGeometries.at(i).data());
                }

                // ### This will request the attributes again
                createClient(wins[i], true);
            }
        }

        // Propagate clients, will really happen at the end of the updates blocker block
        updateStackingOrder(true);

        saveOldScreenSizes();
        updateClientArea();

        // NETWM spec says we have to set it to (0,0) if we don't support it
        NETPoint* viewports = new NETPoint[VirtualDesktopManager::self()->count()];
        rootInfo->setDesktopViewport(VirtualDesktopManager::self()->count(), *viewports);
        delete[] viewports;
405

406
        NETSize desktop_geometry;
407
408
        desktop_geometry.width = m_geometry.width();
        desktop_geometry.height = m_geometry.height();
409
410
411
412
413
414
415
416
417
418
419
        rootInfo->setDesktopGeometry(desktop_geometry);
        setShowingDesktop(false);

    } // End updates blocker block

    // TODO: only on X11?
    AbstractClient* new_active_client = nullptr;
    if (!qApp->isSessionRestored()) {
        --block_focus;
        new_active_client = findClient(Predicate::WindowMatch, client_info.activeWindow());
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
420
421
    if (new_active_client == nullptr
            && activeClient() == nullptr && should_get_focus.count() == 0) {
422
        // No client activated in manage()
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
423
        if (new_active_client == nullptr)
424
            new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->currentDesktop());
425
        if (new_active_client == nullptr)
426
            new_active_client = findDesktop(true, VirtualDesktopManager::self()->currentDesktop());
427
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
428
    if (new_active_client != nullptr)
429
430
431
        activateClient(new_active_client);
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
432
void Workspace::cleanupX11()
433
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
434
435
    // We expect that other components will unregister their X11 event filters after the
    // connection to the X server has been lost.
436

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
437
    StackingUpdatesBlocker blocker(this);
438

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
439
    // Use stacking_order, so that kwin --replace keeps stacking order.
440
    const QList<X11Client *> orderedClients = ensureStackingOrder(m_x11Clients);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
441
442
    for (X11Client *client : orderedClients) {
        client->releaseWindow(true);
443
        removeFromStack(client);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
444
    }
445

446
447
448
    // We need a shadow copy because windows get removed as we go through them.
    const QList<Unmanaged *> unmanaged = m_unmanaged;
    for (Unmanaged *overrideRedirect : unmanaged) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
449
        overrideRedirect->release(ReleaseReason::KWinShutsDown);
450
        removeFromStack(overrideRedirect);
451
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
452
453
454
455
456
457

    manual_overlays.clear();

    VirtualDesktopManager *desktopManager = VirtualDesktopManager::self();
    desktopManager->setRootInfo(nullptr);

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
458
    X11Client::cleanupX11();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
    RootInfo::destroy();
    Xcb::Extensions::destroy();

    if (xcb_connection_t *connection = kwinApp()->x11Connection()) {
        xcb_delete_property(connection, kwinApp()->x11RootWindow(), atoms->kwin_running);
    }

    m_colorMapper.reset();
    m_movingClientFilter.reset();
    m_startup.reset();
    m_nullFocus.reset();
    m_syncAlarmFilter.reset();
    m_wasUserInteractionFilter.reset();
    m_xStackingQueryTree.reset();
}

Workspace::~Workspace()
{
    blockStackingUpdates(true);

    cleanupX11();
480
481

    if (waylandServer()) {
482
483
        const QList<AbstractClient *> shellClients = waylandServer()->clients();
        for (AbstractClient *client : shellClients) {
484
            client->destroyClient();
485
486
487
        }
    }

488
489
490
    // We need a shadow copy because clients get removed as we go through them.
    const QList<InternalClient *> internalClients = m_internalClients;
    for (InternalClient *client : internalClients) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
491
492
493
        client->destroyClient();
    }

494
    for (auto it = deleted.begin(); it != deleted.end();) {
495
        Q_EMIT deletedRemoved(*it);
496
        (*it)->finishCompositing();
497
498
499
        it = deleted.erase(it);
    }

500
    delete RuleBook::self();
501
    kwinApp()->config()->sync();
502

503
    delete Placement::self();
504
    delete client_keys_dialog;
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
505
    qDeleteAll(session);
506

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
507
    _self = nullptr;
508
}
509

510
511
512
void Workspace::setupClientConnections(AbstractClient *c)
{
    connect(c, &AbstractClient::desktopPresenceChanged, this, &Workspace::desktopPresenceChanged);
513
    connect(c, &AbstractClient::minimizedChanged, this, std::bind(&Workspace::clientMinimizedChanged, this, c));
514
515
}

516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
void Workspace::constrain(AbstractClient *below, AbstractClient *above)
{
    if (below == above) {
        return;
    }

    QList<Constraint *> parents;
    QList<Constraint *> children;
    for (Constraint *constraint : qAsConst(m_constraints)) {
        if (constraint->below == below && constraint->above == above) {
            return;
        }
        if (constraint->below == above) {
            children << constraint;
        } else if (constraint->above == below) {
            parents << constraint;
        }
    }

    Constraint *constraint = new Constraint();
    constraint->parents = parents;
    constraint->below = below;
    constraint->above = above;
    constraint->children = children;
    m_constraints << constraint;

    for (Constraint *parent : qAsConst(parents)) {
        parent->children << constraint;
    }

    for (Constraint *child : qAsConst(children)) {
        child->parents << constraint;
    }

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
550
    updateStackingOrder();
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
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
}

void Workspace::unconstrain(AbstractClient *below, AbstractClient *above)
{
    Constraint *constraint = nullptr;
    for (int i = 0; i < m_constraints.count(); ++i) {
        if (m_constraints[i]->below == below && m_constraints[i]->above == above) {
            constraint = m_constraints.takeAt(i);
            break;
        }
    }

    if (!constraint) {
        return;
    }

    const QList<Constraint *> parents = constraint->parents;
    for (Constraint *parent : parents) {
        parent->children.removeOne(constraint);
    }

    const QList<Constraint *> children = constraint->children;
    for (Constraint *child : children) {
        child->parents.removeOne(constraint);
    }

    delete constraint;
    updateStackingOrder();
}

void Workspace::addToStack(Toplevel *toplevel)
{
    // If the stacking order of a window has been restored from the session, that
    // toplevel will already be in the stack when Workspace::addClient() is called.
    if (!unconstrained_stacking_order.contains(toplevel)) {
        unconstrained_stacking_order.append(toplevel);
    }
    if (!stacking_order.contains(toplevel)) {
        stacking_order.append(toplevel);
    }
}

void Workspace::replaceInStack(Toplevel *original, Deleted *deleted)
{
    const int unconstraintedIndex = unconstrained_stacking_order.indexOf(original);
    if (unconstraintedIndex != -1) {
        unconstrained_stacking_order.replace(unconstraintedIndex, deleted);
    } else {
        // This can be the case only if an override-redirect window is unmapped.
        unconstrained_stacking_order.append(deleted);
    }

    const int index = stacking_order.indexOf(original);
    if (index != -1) {
        stacking_order.replace(index, deleted);
    } else {
        // This can be the case only if an override-redirect window is unmapped.
        stacking_order.append(deleted);
    }

    for (Constraint *constraint : qAsConst(m_constraints)) {
        if (constraint->below == original) {
            constraint->below = deleted;
        } else if (constraint->above == original) {
            constraint->above = deleted;
        }
    }
}

void Workspace::removeFromStack(Toplevel *toplevel)
{
    unconstrained_stacking_order.removeAll(toplevel);
    stacking_order.removeAll(toplevel);

    for (int i = m_constraints.count() - 1; i >= 0; --i) {
        Constraint *constraint = m_constraints[i];
        const bool isBelow = (constraint->below == toplevel);
        const bool isAbove = (constraint->above == toplevel);
        if (!isBelow && !isAbove) {
            continue;
        }
        if (isBelow) {
            for (Constraint *child : qAsConst(constraint->children)) {
                child->parents.removeOne(constraint);
            }
        } else {
            for (Constraint *parent : qAsConst(constraint->parents)) {
                parent->children.removeOne(constraint);
            }
        }
        delete m_constraints.takeAt(i);
    }
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
645
X11Client *Workspace::createClient(xcb_window_t w, bool is_mapped)
646
647
{
    StackingUpdatesBlocker blocker(this);
648
649
650
651
652
653
    X11Client *c = nullptr;
    if (kwinApp()->operationMode() == Application::OperationModeX11) {
        c = new X11Client();
    } else {
        c = new XwaylandClient();
    }
654
    setupClientConnections(c);
655
    if (X11Compositor *compositor = X11Compositor::self()) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
656
        connect(c, &X11Client::blockingCompositingChanged, compositor, &X11Compositor::updateClientCompositeBlocking);
657
    }
658
    connect(c, &X11Client::clientFullScreenSet, ScreenEdges::self(), &ScreenEdges::checkBlocking);
659
    if (!c->manage(w, is_mapped)) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
660
        X11Client::deleteClient(c);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
661
        return nullptr;
662
    }
663
    addClient(c);
664
    return c;
665
}
666

667
Unmanaged* Workspace::createUnmanaged(xcb_window_t w)
668
{
669
670
671
672
673
    if (X11Compositor *compositor = X11Compositor::self()) {
        if (compositor->checkForOverlayWindow(w)) {
            return nullptr;
        }
    }
674
    Unmanaged* c = new Unmanaged();
675
    if (!c->track(w)) {
676
        Unmanaged::deleteUnmanaged(c);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
677
        return nullptr;
678
    }
679
    addUnmanaged(c);
680
    Q_EMIT unmanagedAdded(c);
681
682
683
    return c;
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
684
void Workspace::addClient(X11Client *c)
685
686
{
    Group* grp = findGroup(c->window());
687

688
    Q_EMIT clientAdded(c);
689

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
690
    if (grp != nullptr)
691
692
693
        grp->gotLeader(c);

    if (c->isDesktop()) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
694
        if (active_client == nullptr && should_get_focus.isEmpty() && c->isOnCurrentDesktop())
695
696
            requestFocus(c);   // TODO: Make sure desktop is active after startup if there's no other window active
    } else {
697
        FocusChain::self()->update(c, FocusChain::Update);
698
    }
699
    m_x11Clients.append(c);
700
    m_allClients.append(c);
701
    addToStack(c);
702
    markXStackingOrderAsDirty();
703
    updateClientArea(); // This cannot be in manage(), because the client got added only now
704
    c->updateLayer();
705
706
    if (c->isDesktop()) {
        raiseClient(c);
707
        // If there's no active client, make this desktop the active one
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
708
        if (activeClient() == nullptr && should_get_focus.count() == 0)
709
            activateClient(findDesktop(true, VirtualDesktopManager::self()->currentDesktop()));
710
    }
711
    c->checkActiveModal();
712
713
714
715
    checkTransients(c->window());   // SELI TODO: Does this really belong here?
    updateStackingOrder(true);   // Propagate new client
    if (c->isUtility() || c->isMenu() || c->isToolbar())
        updateToolWindows(true);
716
    updateTabbox();
717
}
718

719
void Workspace::addUnmanaged(Unmanaged* c)
720
{
721
    m_unmanaged.append(c);
722
    markXStackingOrderAsDirty();
723
}
724

725
726
/**
 * Destroys the client \a c
727
 */
728
void Workspace::removeX11Client(X11Client *c)
729
{
730
731
    if (c == active_popup_client)
        closeActivePopup();
732
733
734
    if (m_userActionsMenu->isMenuClient(c)) {
        m_userActionsMenu->close();
    }
735

736
    Q_ASSERT(m_x11Clients.contains(c));
Nikhil Marathe's avatar
Nikhil Marathe committed
737
    // TODO: if marked client is removed, notify the marked list
738
    m_x11Clients.removeAll(c);
739
740
741
    Group* group = findGroup(c->window());
    if (group != nullptr)
        group->lostLeader();
742
    removeAbstractClient(c);
743
}
744

745
void Workspace::removeUnmanaged(Unmanaged* c)
746
{
747
748
    Q_ASSERT(m_unmanaged.contains(c));
    m_unmanaged.removeAll(c);
749
    Q_EMIT unmanagedRemoved(c);
750
    markXStackingOrderAsDirty();
751
}
752

753
void Workspace::addDeleted(Deleted* c, Toplevel *orig)
754
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
755
    Q_ASSERT(!deleted.contains(c));
756
    deleted.append(c);
757
    replaceInStack(orig, c);
758
    markXStackingOrderAsDirty();
759
}
760

761
void Workspace::removeDeleted(Deleted* c)
762
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
763
    Q_ASSERT(deleted.contains(c));
764
    Q_EMIT deletedRemoved(c);
765
    deleted.removeAll(c);
766
    removeFromStack(c);
767
    markXStackingOrderAsDirty();
768
769
770
771
772
    if (!c->wasClient()) {
        return;
    }
    if (X11Compositor *compositor = X11Compositor::self()) {
        compositor->updateClientCompositeBlocking();
773
    }
774
}
775

776
777
778
void Workspace::addShellClient(AbstractClient *client)
{
    setupClientConnections(client);
779
    client->updateLayer();
780

781
    if (client->isPlaceable()) {
782
        const QRect area = clientArea(PlacementArea, client, activeOutput());
783
        bool placementDone = false;
784
        if (client->isRequestedFullScreen()) {
785
786
            placementDone = true;
        }
787
        if (client->requestedMaximizeMode() == MaximizeMode::MaximizeFull) {
788
789
790
791
792
793
794
795
            placementDone = true;
        }
        if (client->rules()->checkPosition(invalidPoint, true) != invalidPoint) {
            placementDone = true;
        }
        if (!placementDone) {
            client->placeIn(area);
        }
796
797
    }
    m_allClients.append(client);
798
    addToStack(client);
799
800
801
802
803
804
805
806
807

    markXStackingOrderAsDirty();
    updateStackingOrder(true);
    updateClientArea();
    if (client->wantsInput() && !client->isMinimized()) {
        activateClient(client);
    }
    updateTabbox();
    connect(client, &AbstractClient::windowShown, this, [this, client] {
808
        client->updateLayer();
809
810
811
812
813
814
815
816
817
818
819
820
821
        markXStackingOrderAsDirty();
        updateStackingOrder(true);
        updateClientArea();
        if (client->wantsInput()) {
            activateClient(client);
        }
    });
    connect(client, &AbstractClient::windowHidden, this, [this] {
        // TODO: update tabbox if it's displayed
        markXStackingOrderAsDirty();
        updateStackingOrder(true);
        updateClientArea();
    });
822
    Q_EMIT clientAdded(client);
823
824
825
826
}

void Workspace::removeShellClient(AbstractClient *client)
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
827
    clientHidden(client);
828
829
830
831
832
    removeAbstractClient(client);
}

void Workspace::removeAbstractClient(AbstractClient *client)
{
833
834
835
836
    m_allClients.removeAll(client);
    if (client == delayfocus_client) {
        cancelDelayFocus();
    }
837
838
    attention_chain.removeAll(client);
    should_get_focus.removeAll(client);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
839
840
841
    if (client == active_client) {
        active_client = nullptr;
    }
842
843
844
845
846
847
848
849
    if (client == last_active_client) {
        last_active_client = nullptr;
    }
    if (client_keys_client == client) {
        setupWindowShortcutDone(false);
    }
    if (!client->shortcut().isEmpty()) {
        client->setShortcut(QString());   // Remove from client_keys
850
        clientShortcutUpdated(client);    // Needed, since this is otherwise delayed by setShortcut() and wouldn't run
851
    }
852

853
    Q_EMIT clientRemoved(client);
854
    markXStackingOrderAsDirty();
855

856
857
858
859
860
    updateStackingOrder(true);
    updateClientArea();
    updateTabbox();
}

861
862
void Workspace::updateToolWindows(bool also_hide)
{
863
    // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?)
864
    if (!options->isHideUtilityWindowsForInactive()) {
865
        for (auto it = m_x11Clients.constBegin(); it != m_x11Clients.constEnd(); ++it)
866
            (*it)->hideClient(false);
867
        return;
868
    }
869
870
    const Group* group = nullptr;
    auto client = active_client;
871
872
    // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow
    // will be shown; if a group transient is group, all tools in the group will be shown
873
    while (client != nullptr) {
874
        if (!client->isTransient())
875
            break;
876
        if (client->groupTransient()) {
877
878
879
            group = client->group();
            break;
        }
880
        client = client->transientFor();
881
    }
882
883
    // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0,
    // I.e. if it's not up to date
884

885
    // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet?
886
    QVector<AbstractClient*> to_show, to_hide;
887
    for (auto it = stacking_order.constBegin();
888
889
            it != stacking_order.constEnd();
            ++it) {
890
        auto c = qobject_cast<AbstractClient*>(*it);
891
892
893
894
        if (!c) {
            continue;
        }
        if (c->isUtility() || c->isMenu() || c->isToolbar()) {
895
            bool show = true;
896
            if (!c->isTransient()) {
897
                if (!c->group() || c->group()->members().count() == 1)   // Has its own group, keep always visible
898
                    show = true;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
899
                else if (client != nullptr && c->group() == client->group())
900
901
902
                    show = true;
                else
                    show = false;
903
            } else {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
904
                if (group != nullptr && c->group() == group)
905
                    show = true;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
906
                else if (client != nullptr && client->hasTransient(c, true))
907
908
909
                    show = true;
                else
                    show = false;
910
911
            }
            if (!show && also_hide) {
912
                const auto mainclients = c->mainClients();
913
                // Don't hide utility windows which are standalone(?) or
914
                // have e.g. kicker as mainwindow
915
                if (mainclients.isEmpty())
916
                    show = true;
917
                for (auto it2 = mainclients.constBegin();
918
919
                        it2 != mainclients.constEnd();
                        ++it2) {
920
                    if ((*it2)->isSpecialWindow())
921
922
                        show = true;
                }
923
                if (!show)
924
                    to_hide.append(c);
925
            }
926
            if (show)
927
                to_show.append(c);
928
929
930
931
932
        }
    } // First show new ones, then hide
    for (int i = to_show.size() - 1;
            i >= 0;
            --i)  // From topmost
933
        // TODO: Since this is in stacking order, the order of taskbar entries changes :(
934
935
        to_show.at(i)->hideClient(false);
    if (also_hide) {
936
        for (auto it = to_hide.constBegin();
937
938
939
                it != to_hide.constEnd();
                ++it)  // From bottommost
            (*it)->hideClient(true);
940
        updateToolWindowsTimer.stop();
941
942
    } else // setActiveClient() is after called with NULL client, quickly followed
        // by setting a new client, which would result in flickering
943
        resetUpdateToolWindowsTimer();
944
}
945
946
947


void Workspace::resetUpdateToolWindowsTimer()
948
949
950
{
    updateToolWindowsTimer.start(200);
}
951
952

void Workspace::slotUpdateToolWindows()
953
954
955
{
    updateToolWindows(true);
}
956

957
void Workspace::slotReloadConfig()
958
{
959
    reconfigure();
960
}
Cedric Bellegarde's avatar
Cedric Bellegarde committed
961

962
void Workspace::reconfigure()
963
964
965
{
    reconfigureTimer.start(200);
}
966

967
968
/**
 * Reread settings
969
 */
970
971

void Workspace::slotReconfigure()
972
{
973
    qCDebug(KWIN_CORE) << "Workspace::slotReconfigure()";
974
975
    reconfigureTimer.stop();

976
977
    bool borderlessMaximizedWindows = options->borderlessMaximizedWindows();

978
    kwinApp()<