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

Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
6
Copyright (C) 2018 David Edmundson <davidedmundson@kde.org>
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
7
Copyright (C) 2019 Vlad Zagorodniy <vladzzag@gmail.com>
8
9
10
11
12
13
14
15
16
17
18
19
20
21

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/>.
*********************************************************************/
22
#include "xdgshellclient.h"
23
#include "composite.h"
24
#include "cursor.h"
25
#include "deleted.h"
26
#include "placement.h"
27
#include "screenedge.h"
28
#include "screens.h"
29
30
31
32
#ifdef KWIN_BUILD_TABBOX
#include "tabbox.h"
#endif
#include "virtualdesktops.h"
33
#include "wayland_server.h"
34
#include "workspace.h"
35
36
#include "decorations/decorationbridge.h"
#include "decorations/decoratedclient.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
37

38
39
#include <KDecoration2/Decoration>
#include <KDecoration2/DecoratedClient>
40
#include <KWayland/Server/display.h>
41
#include <KWayland/Server/clientconnection.h>
42
#include <KWayland/Server/seat_interface.h>
43
44
#include <KWayland/Server/surface_interface.h>
#include <KWayland/Server/buffer_interface.h>
45
#include <KWayland/Server/plasmashell_interface.h>
46
#include <KWayland/Server/shadow_interface.h>
47
#include <KWayland/Server/server_decoration_interface.h>
48
#include <KWayland/Server/qtsurfaceextension_interface.h>
49
#include <KWayland/Server/plasmawindowmanagement_interface.h>
David Edmundson's avatar
David Edmundson committed
50
#include <KWayland/Server/appmenu_interface.h>
51
#include <KWayland/Server/server_decoration_palette_interface.h>
52
#include <KWayland/Server/xdgdecoration_interface.h>
David Edmundson's avatar
David Edmundson committed
53

54
#include <QFileInfo>
55

56
57
#include <sys/types.h>
#include <unistd.h>
58
59

#include <csignal>
60

61
62
Q_DECLARE_METATYPE(NET::WindowType)

63
64
65
66
67
using namespace KWayland::Server;

namespace KWin
{

68
XdgShellClient::XdgShellClient(XdgShellSurfaceInterface *surface)
69
70
71
72
73
    : AbstractClient()
    , m_xdgShellSurface(surface)
    , m_xdgShellPopup(nullptr)
{
    setSurface(surface->surface());
74
    m_requestGeometryBlockCounter++;
75
    init();
76
    connect(surface->surface(), &SurfaceInterface::committed, this, &XdgShellClient::finishInit);
77
78
}

79
XdgShellClient::XdgShellClient(XdgShellPopupInterface *surface)
80
81
82
    : AbstractClient()
    , m_xdgShellSurface(nullptr)
    , m_xdgShellPopup(surface)
83
84
{
    setSurface(surface->surface());
85
    m_requestGeometryBlockCounter++;
86
    init();
87
    connect(surface->surface(), &SurfaceInterface::committed, this, &XdgShellClient::finishInit);
88
89
}

90
XdgShellClient::~XdgShellClient() = default;
91

92
template <class T>
93
void XdgShellClient::initSurface(T *shellSurface)
94
{
95
    m_caption = shellSurface->title().simplified();
96
    // delay till end of init
97
98
    QTimer::singleShot(0, this, &XdgShellClient::updateCaption);
    connect(shellSurface, &T::destroyed, this, &XdgShellClient::destroyClient);
99
100
    connect(shellSurface, &T::titleChanged, this,
        [this] (const QString &s) {
101
            const auto oldSuffix = m_captionSuffix;
102
            m_caption = s.simplified();
103
104
105
106
107
108
            updateCaption();
            if (m_captionSuffix == oldSuffix) {
                // don't emit caption change twice
                // it already got emitted by the changing suffix
                emit captionChanged();
            }
109
110
111
112
113
114
115
116
117
        }
    );
    connect(shellSurface, &T::moveRequested, this,
        [this] {
            // TODO: check the seat and serial
            performMouseCommand(Options::MouseMove, Cursor::pos());
        }
    );

118
119
120
121
122
123
124
125
    // determine the resource name, this is inspired from ICCCM 4.1.2.5
    // the binary name of the invoked client
    QFileInfo info{shellSurface->client()->executablePath()};
    QByteArray resourceName;
    if (info.exists()) {
        resourceName = info.fileName().toUtf8();
    }
    setResourceClass(resourceName, shellSurface->windowClass());
126
    setDesktopFileName(shellSurface->windowClass());
127
    connect(shellSurface, &T::windowClassChanged, this,
128
129
        [this, resourceName] (const QByteArray &windowClass) {
            setResourceClass(resourceName, windowClass);
130
            if (m_isInitialized && supportsWindowRules()) {
131
132
133
                setupWindowRules(true);
                applyWindowRules();
            }
134
            setDesktopFileName(windowClass);
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
        }
    );
    connect(shellSurface, &T::resizeRequested, this,
        [this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) {
            // TODO: check the seat and serial
            Q_UNUSED(seat)
            Q_UNUSED(serial)
            if (!isResizable() || isShade()) {
                return;
            }
            if (isMoveResize()) {
                finishMoveResize(false);
            }
            setMoveResizePointerButtonDown(true);
            setMoveOffset(Cursor::pos() - pos());  // map from global
            setInvertedMoveOffset(rect().bottomRight() - moveOffset());
            setUnrestrictedMoveResize(false);
            auto toPosition = [edges] {
                Position pos = PositionCenter;
                if (edges.testFlag(Qt::TopEdge)) {
                    pos = PositionTop;
                } else if (edges.testFlag(Qt::BottomEdge)) {
                    pos = PositionBottom;
                }
                if (edges.testFlag(Qt::LeftEdge)) {
                    pos = Position(pos | PositionLeft);
                } else if (edges.testFlag(Qt::RightEdge)) {
                    pos = Position(pos | PositionRight);
                }
                return pos;
            };
            setMoveResizePointerMode(toPosition());
            if (!startMoveResize())
                setMoveResizePointerButtonDown(false);
            updateCursor();
        }
    );
    connect(shellSurface, &T::maximizedChanged, this,
        [this] (bool maximized) {
174
175
176
177
178
            // If the maximized state of the client hasn't been changed due to a window
            // rule or because the requested state is the same as the current, then the
            // compositor still has to send a configure event.
            RequestGeometryBlocker blocker(this);

179
180
181
182
            maximize(maximized ? MaximizeFull : MaximizeRestore);
        }
    );
    // TODO: consider output!
183
    connect(shellSurface, &T::fullscreenChanged, this, &XdgShellClient::clientFullScreenChanged);
184

185
    connect(shellSurface, &T::transientForChanged, this, &XdgShellClient::setTransient);
186

187
188
    connect(this, &XdgShellClient::geometryChanged, this, &XdgShellClient::updateClientOutputs);
    connect(screens(), &Screens::changed, this, &XdgShellClient::updateClientOutputs);
189
190
}

191
void XdgShellClient::init()
192
{
193
    connect(this, &XdgShellClient::desktopFileNameChanged, this, &XdgShellClient::updateIcon);
194
    createWindowId();
195
    setupCompositing();
196
    updateIcon();
197
198
    SurfaceInterface *s = surface();
    Q_ASSERT(s);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
199
    doSetGeometry(QRect(QPoint(0, 0), m_clientSize));
200
    if (waylandServer()->inputMethodConnection() == s->client()) {
201
202
        m_windowType = NET::OnScreenDisplay;
    }
203

204
    connect(s, &SurfaceInterface::sizeChanged, this,
205
        [this] {
206
            m_clientSize = surface()->size();
207
            doSetGeometry(QRect(pos(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom())));
208
209
        }
    );
210
211
212
    connect(s, &SurfaceInterface::unmapped, this, &XdgShellClient::unmap);
    connect(s, &SurfaceInterface::unbound, this, &XdgShellClient::destroyClient);
    connect(s, &SurfaceInterface::destroyed, this, &XdgShellClient::destroyClient);
213
    if (m_xdgShellSurface) {
214
        initSurface(m_xdgShellSurface);
David Edmundson's avatar
David Edmundson committed
215
216
217
218
219
220
221
222
223
224
225

        auto global = static_cast<XdgShellInterface *>(m_xdgShellSurface->global());
        connect(global, &XdgShellInterface::pingDelayed,
            this, [this](qint32 serial) {
                auto it = m_pingSerials.find(serial);
                if (it != m_pingSerials.end()) {
                    qCDebug(KWIN_CORE) << "First ping timeout:" << caption();
                    setUnresponsive(true);
                }
            });

226
227
228
229
        connect(m_xdgShellSurface, &XdgShellSurfaceInterface::configureAcknowledged, this, [this](int serial) {
           m_lastAckedConfigureRequest = serial;
        });

David Edmundson's avatar
David Edmundson committed
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
        connect(global, &XdgShellInterface::pingTimeout,
            this, [this](qint32 serial) {
                auto it = m_pingSerials.find(serial);
                if (it != m_pingSerials.end()) {
                    if (it.value() == PingReason::CloseWindow) {
                        qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption();

                        //for internal windows, killing the window will delete this
                        QPointer<QObject> guard(this);
                        killWindow();
                        if (!guard) {
                            return;
                        }
                    }
                    m_pingSerials.erase(it);
                }
            });

        connect(global, &XdgShellInterface::pongReceived,
            this, [this](qint32 serial){
                auto it = m_pingSerials.find(serial);
                if (it != m_pingSerials.end()) {
                    setUnresponsive(false);
                    m_pingSerials.erase(it);
                }
            });

257
258
259
260
261
262
        connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowMenuRequested, this,
            [this] (SeatInterface *seat, quint32 serial, const QPoint &surfacePos) {
                // TODO: check serial on seat
                Q_UNUSED(seat)
                Q_UNUSED(serial)
                performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos);
263
264
            }
        );
265
        connect(m_xdgShellSurface, &XdgShellSurfaceInterface::minimizeRequested, this,
266
            [this] {
267
                performMouseCommand(Options::MouseMinimize, Cursor::pos());
268
269
            }
        );
270
271
272
        auto configure = [this] {
            if (m_closing) {
                return;
273
            }
274
275
276
            if (m_requestGeometryBlockCounter != 0 || areGeometryUpdatesBlocked()) {
                return;
            }
277
            m_xdgShellSurface->configure(xdgSurfaceStates(), m_requestedClientSize);
278
279
280
281
282
        };
        connect(this, &AbstractClient::activeChanged, this, configure);
        connect(this, &AbstractClient::clientStartUserMovedResized, this, configure);
        connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure);
    } else if (m_xdgShellPopup) {
David Edmundson's avatar
David Edmundson committed
283
284
285
286
287
288
289
        connect(m_xdgShellPopup, &XdgShellPopupInterface::grabRequested, this, [this](SeatInterface *seat, quint32 serial) {
            Q_UNUSED(seat)
            Q_UNUSED(serial)
            //TODO - should check the parent had focus
            m_hasPopupGrab = true;
        });

290
        connect(m_xdgShellPopup, &XdgShellPopupInterface::configureAcknowledged, this, [this](int serial) {
291
292
293
           m_lastAckedConfigureRequest = serial;
        });

294
        connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &XdgShellClient::destroyClient);
295
    }
296

297
    // set initial desktop
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
298
    setDesktop(VirtualDesktopManager::self()->current());
299

300
301
    // setup shadow integration
    getShadow();
302
    connect(s, &SurfaceInterface::shadowChanged, this, &Toplevel::getShadow);
303

Marco Martin's avatar
Marco Martin committed
304
305
306
307
308
    connect(waylandServer(), &WaylandServer::foreignTransientChanged, this, [this](KWayland::Server::SurfaceInterface *child) {
        if (child == surface()) {
            setTransient();
        }
    });
309
    setTransient();
310

311
    AbstractClient::updateColorScheme(QString());
312
313
}

314
void XdgShellClient::finishInit() {
315
    SurfaceInterface *s = surface();
316
    disconnect(s, &SurfaceInterface::committed, this, &XdgShellClient::finishInit);
317

318
319
    bool needsPlacement = !isInitialPositionSet();

320
321
322
    if (supportsWindowRules()) {
        setupWindowRules(false);

323
324
325
326
327
328
        const QRect originalGeometry = QRect(pos(), sizeForClientSize(clientSize()));
        const QRect ruledGeometry = rules()->checkGeometry(originalGeometry, true);
        if (originalGeometry != ruledGeometry) {
            setGeometry(ruledGeometry);
        }

329
330
        maximize(rules()->checkMaximize(maximizeMode(), true));

331
332
333
334
335
336
337
338
339
340
341
342
343
        setDesktop(rules()->checkDesktop(desktop(), true));
        setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true).toUtf8());
        if (rules()->checkMinimize(isMinimized(), true)) {
            minimize(true); // No animation.
        }
        setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true));
        setSkipPager(rules()->checkSkipPager(skipPager(), true));
        setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true));
        setKeepAbove(rules()->checkKeepAbove(keepAbove(), true));
        setKeepBelow(rules()->checkKeepBelow(keepBelow(), true));
        setShortcut(rules()->checkShortcut(shortcut().toString(), true));
        updateColorScheme();

344
345
346
347
348
        // Don't place the client if its position is set by a rule.
        if (rules()->checkPosition(invalidPoint, true) != invalidPoint) {
            needsPlacement = false;
        }

349
350
351
352
353
        // Don't place the client if the maximize state is set by a rule.
        if (requestedMaximizeMode() != MaximizeRestore) {
            needsPlacement = false;
        }

354
355
356
357
358
        discardTemporaryRules();
        RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules.
        updateWindowRules(Rules::All);
    }

359
    if (needsPlacement) {
360
        const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop());
361
362
363
364
365
366
367
        placeIn(area);
    }

    m_requestGeometryBlockCounter--;
    if (m_requestGeometryBlockCounter == 0) {
        requestGeometry(m_blockedRequestGeometry);
    }
368
369

    m_isInitialized = true;
370
371
}

372
void XdgShellClient::destroyClient()
373
{
374
    m_closing = true;
375
376
#ifdef KWIN_BUILD_TABBOX
    TabBox::TabBox *tabBox = TabBox::TabBox::self();
377
    if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) {
378
379
380
        tabBox->nextPrev(true);
    }
#endif
381
382
383
    if (isMoveResize()) {
        leaveMoveResize();
    }
384
385
386
387

    // Replace ShellClient with an instance of Deleted in the stacking order.
    Deleted *deleted = Deleted::create(this);
    emit windowClosed(this, deleted);
388
389
390
391

    // Remove Force Temporarily rules.
    RuleBook::self()->discardUsed(this, true);

392
    destroyWindowManagementInterface();
393
    destroyDecoration();
394

395
396
397
398
399
400
401
402
403
404
    StackingUpdatesBlocker blocker(workspace());
    if (transientFor()) {
        transientFor()->removeTransient(this);
    }
    for (auto it = transients().constBegin(); it != transients().constEnd();) {
        if ((*it)->transientFor() == this) {
            removeTransient(*it);
            it = transients().constBegin(); // restart, just in case something more has changed with the list
        } else {
            ++it;
405
406
        }
    }
407

408
409
    waylandServer()->removeClient(this);

410
411
    deleted->unrefWindow();

412
413
    m_xdgShellSurface = nullptr;
    m_xdgShellPopup = nullptr;
414
415
416
    deleteClient(this);
}

417
void XdgShellClient::deleteClient(XdgShellClient *c)
418
419
420
421
{
    delete c;
}

422
QSize XdgShellClient::toWindowGeometry(const QSize &size) const
423
424
425
426
427
428
429
430
431
{
    QSize adjustedSize = size - QSize(borderLeft() + borderRight(), borderTop() + borderBottom());
    // a client going fullscreen should have the window the contents size of the screen
    if (!isFullScreen() && requestedMaximizeMode() != MaximizeFull) {
        adjustedSize -= QSize(m_windowMargins.left() + m_windowMargins.right(), m_windowMargins.top() + m_windowMargins.bottom());
    }
    return adjustedSize;
}

432
QStringList XdgShellClient::activities() const
433
434
435
436
437
{
    // TODO: implement
    return QStringList();
}

438
QPoint XdgShellClient::clientContentPos() const
439
440
441
442
{
    return -1 * clientPos();
}

443
QSize XdgShellClient::clientSize() const
444
445
446
447
{
    return m_clientSize;
}

448
void XdgShellClient::debug(QDebug &stream) const
449
{
Roman Gilg's avatar
Roman Gilg committed
450
    stream.nospace();
451
    stream << "\'XdgShellClient:" << surface() << ";WMCLASS:" << resourceClass() << ":"
Roman Gilg's avatar
Roman Gilg committed
452
           << resourceName() << ";Caption:" << caption() << "\'";
453
454
}

455
bool XdgShellClient::belongsToDesktop() const
456
457
458
459
{
    const auto clients = waylandServer()->clients();

    return std::any_of(clients.constBegin(), clients.constEnd(),
460
        [this](const XdgShellClient *client) {
461
462
463
464
465
466
467
468
            if (belongsToSameApplication(client, SameApplicationChecks())) {
                return client->isDesktop();
            }
            return false;
        }
    );
}

469
Layer XdgShellClient::layerForDock() const
470
{
471
472
473
    if (m_plasmaShellSurface) {
        switch (m_plasmaShellSurface->panelBehavior()) {
        case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover:
474
            return NormalLayer;
475
        case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide:
476
            return AboveLayer;
477
478
479
480
481
482
483
        case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow:
        case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible:
            return DockLayer;
        default:
            Q_UNREACHABLE();
            break;
        }
484
    }
485
    return AbstractClient::layerForDock();
486
487
}

488
QRect XdgShellClient::transparentRect() const
489
490
491
492
493
{
    // TODO: implement
    return QRect();
}

494
NET::WindowType XdgShellClient::windowType(bool direct, int supported_types) const
495
496
497
498
{
    // TODO: implement
    Q_UNUSED(direct)
    Q_UNUSED(supported_types)
499
    return m_windowType;
500
501
}

502
double XdgShellClient::opacity() const
503
{
504
    return m_opacity;
505
506
}

507
void XdgShellClient::setOpacity(double opacity)
508
{
509
510
511
512
513
514
515
516
    const qreal newOpacity = qBound(0.0, opacity, 1.0);
    if (newOpacity == m_opacity) {
        return;
    }
    const qreal oldOpacity = m_opacity;
    m_opacity = newOpacity;
    addRepaintFull();
    emit opacityChanged(this, oldOpacity);
517
518
}

519
void XdgShellClient::addDamage(const QRegion &damage)
520
{
521
    auto s = surface();
522
523
    if (s->size().isValid()) {
        m_clientSize = s->size();
524
        updateWindowMargins();
525
        updatePendingGeometry();
526
    }
Martin Flöser's avatar
Martin Flöser committed
527
    markAsMapped();
528
    setDepth((s->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24);
529
    repaints_region += damage.translated(clientPos());
530
531
532
    Toplevel::addDamage(damage);
}

533
void XdgShellClient::markAsMapped()
534
535
536
537
{
    if (!m_unmapped) {
        return;
    }
538

539
    m_unmapped = false;
540
541
542
543
544
545
    if (!ready_for_painting) {
        setReadyForPainting();
    } else {
        addRepaintFull();
        emit windowShown(this);
    }
546
547
548
    if (shouldExposeToWindowManagement()) {
        setupWindowManagementInterface();
    }
549
    updateShowOnScreenEdge();
550
551
}

552
void XdgShellClient::createDecoration(const QRect &oldGeom)
553
554
555
556
557
558
559
560
{
    KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this);
    if (decoration) {
        QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection);
        connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow);
        connect(decoration, &KDecoration2::Decoration::bordersChanged, this,
            [this]() {
                GeometryUpdatesBlocker blocker(this);
561
                RequestGeometryBlocker requestBlocker(this);
562
563
564
565
566
567
568
569
                QRect oldgeom = geometry();
                if (!isShade())
                    checkWorkspacePosition(oldgeom);
                emit geometryShapeChanged(this, oldgeom);
            }
        );
    }
    setDecoration(decoration);
570
    // TODO: ensure the new geometry still fits into the client area (e.g. maximized windows)
571
572
    doSetGeometry(QRect(oldGeom.topLeft(), m_clientSize + (decoration ? QSize(decoration->borderLeft() + decoration->borderRight(),
                                                               decoration->borderBottom() + decoration->borderTop()) : QSize())));
573
574
575
576

    emit geometryShapeChanged(this, oldGeom);
}

577
void XdgShellClient::updateDecoration(bool check_workspace_pos, bool force)
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
{
    if (!force &&
            ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder())))
        return;
    QRect oldgeom = geometry();
    QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom());
    blockGeometryUpdates(true);
    if (force)
        destroyDecoration();
    if (!noBorder()) {
        createDecoration(oldgeom);
    } else
        destroyDecoration();
    if (m_serverDecoration && isDecorated()) {
        m_serverDecoration->setMode(KWayland::Server::ServerSideDecorationManagerInterface::Mode::Server);
    }
594
    if (m_xdgDecoration) {
595
        auto mode = isDecorated() || m_userNoBorder ? XdgDecorationInterface::Mode::ServerSide: XdgDecorationInterface::Mode::ClientSide;
596
        m_xdgDecoration->configure(mode);
597
598
599
        if (m_requestGeometryBlockCounter == 0) {
            m_xdgShellSurface->configure(xdgSurfaceStates(), m_requestedClientSize);
        }
600
    }
601
602
603
604
605
606
    getShadow();
    if (check_workspace_pos)
        checkWorkspacePosition(oldgeom, -2, oldClientGeom);
    blockGeometryUpdates(false);
}

607
void XdgShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force)
608
{
609
610
    const QRect newGeometry = rules()->checkGeometry(QRect(x, y, w, h));

611
612
613
    if (areGeometryUpdatesBlocked()) {
        // when the GeometryUpdateBlocker exits the current geom is passed to setGeometry
        // thus we need to set it here.
614
        geom = newGeometry;
615
616
617
618
619
620
621
622
623
624
625
626
        if (pendingGeometryUpdate() == PendingGeometryForced)
            {} // maximum, nothing needed
        else if (force == ForceGeometrySet)
            setPendingGeometryUpdate(PendingGeometryForced);
        else
            setPendingGeometryUpdate(PendingGeometryNormal);
        return;
    }
    if (pendingGeometryUpdate() != PendingGeometryNone) {
        // reset geometry to the one before blocking, so that we can compare properly
        geom = geometryBeforeUpdateBlocking();
    }
627
628
    const QSize requestedClientSize = newGeometry.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom());
    const QSize requestedWindowGeometrySize = toWindowGeometry(newGeometry.size());
629

630
    if (requestedClientSize == m_clientSize &&
631
        (m_requestedClientSize.isEmpty() || requestedWindowGeometrySize == m_requestedClientSize)) {
632
        // size didn't change, and we don't need to explicitly request a new size
633
        doSetGeometry(newGeometry);
634
        updateMaximizeMode(m_requestedMaximizeMode);
635
636
    } else {
        // size did change, Client needs to provide a new buffer
637
        requestGeometry(newGeometry);
638
    }
639
640
}

641
void XdgShellClient::doSetGeometry(const QRect &rect)
642
{
643
    if (geom == rect && pendingGeometryUpdate() == PendingGeometryNone) {
644
645
        return;
    }
646
647
648
    if (!m_unmapped) {
        addWorkspaceRepaint(visibleRect());
    }
649

650
    geom = rect;
651
    updateWindowRules(Rules::Position | Rules::Size);
652
653
654
655
656
657

    if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) {
        // use first valid geometry as restore geometry
        m_geomMaximizeRestore = geom;
    }

658
659
660
    if (!m_unmapped) {
        addWorkspaceRepaint(visibleRect());
    }
661
662
663
    if (hasStrut()) {
        workspace()->updateClientArea();
    }
664
665
    const auto old = geometryBeforeUpdateBlocking();
    updateGeometryBeforeUpdateBlocking();
666
    emit geometryShapeChanged(this, old);
667
668
669
670

    if (isResize()) {
        performMoveResize();
    }
671
672
}

673
QByteArray XdgShellClient::windowRole() const
674
675
676
677
{
    return QByteArray();
}

678
bool XdgShellClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const
679
{
680
681
682
683
684
    if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) {
        if (other->desktopFileName() == desktopFileName()) {
            return true;
        }
    }
685
686
687
688
689
690
    if (auto s = other->surface()) {
        return s->client() == surface()->client();
    }
    return false;
}

691
void XdgShellClient::blockActivityUpdates(bool b)
692
693
694
695
{
    Q_UNUSED(b)
}

696
697
698
699
700
701
702
703
704
705
QString XdgShellClient::captionNormal() const
{
    return m_caption;
}

QString XdgShellClient::captionSuffix() const
{
    return m_captionSuffix;
}

706
void XdgShellClient::updateCaption()
707
708
{
    const QString oldSuffix = m_captionSuffix;
709
710
711
712
713
714
715
716
717
    const auto shortcut = shortcutCaptionSuffix();
    m_captionSuffix = shortcut;
    if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) {
        int i = 2;
        do {
            m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>');
            i++;
        } while (findClientWithSameCaption());
    }
718
719
720
    if (m_captionSuffix != oldSuffix) {
        emit captionChanged();
    }
721
722
}

723
void XdgShellClient::closeWindow()
724
{
725
726
    if (m_xdgShellSurface && isCloseable()) {
        m_xdgShellSurface->close();
David Edmundson's avatar
David Edmundson committed
727
728
        const qint32 pingSerial = static_cast<XdgShellInterface *>(m_xdgShellSurface->global())->ping(m_xdgShellSurface);
        m_pingSerials.insert(pingSerial, PingReason::CloseWindow);
729
    }
730
731
}

732
AbstractClient *XdgShellClient::findModal(bool allow_itself)
733
734
735
736
737
{
    Q_UNUSED(allow_itself)
    return nullptr;
}

738
bool XdgShellClient::isCloseable() const
739
{
740
741
742
    if (m_windowType == NET::Desktop || m_windowType == NET::Dock) {
        return false;
    }
743
744
745
    if (m_xdgShellSurface) {
        return true;
    }
746
    return false;
747
748
}

749
bool XdgShellClient::isFullScreen() const
750
{
751
    return m_fullScreen;
752
753
}

754
bool XdgShellClient::isMaximizable() const
755
{
756
757
758
    if (!isResizable()) {
        return false;
    }
759
760
761
    if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || rules()->checkMaximize(MaximizeFull) != MaximizeFull) {
        return false;
    }
762
    return true;
763
764
}

765
bool XdgShellClient::isMinimizable() const
766
{
767
768
769
    if (!rules()->checkMinimize(true)) {
        return false;
    }
Marco Martin's avatar
Marco Martin committed
770
    return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal);
771
772
}

773
bool XdgShellClient::isMovable() const
774
{
775
776
777
    if (rules()->checkPosition(invalidPoint) != invalidPoint) {
        return false;
    }
778
779
780
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
    }
781
782
783
    if (m_xdgShellPopup) {
        return false;
    }
784
    return true;
785
786
}

787
bool XdgShellClient::isMovableAcrossScreens() const
788
{
789
790
791
    if (rules()->checkPosition(invalidPoint) != invalidPoint) {
        return false;
    }
792
793
794
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
    }
795
796
797
    if (m_xdgShellPopup) {
        return false;
    }
798
    return true;
799
800
}

801
bool XdgShellClient::isResizable() const
802
{
803
804
805
    if (rules()->checkSize(QSize()).isValid()) {
        return false;
    }
806
807
808
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
    }
809
810
811
    if (m_xdgShellPopup) {
        return false;
    }
812
    return true;
813
814
}

815
bool XdgShellClient::isShown(bool shaded_is_shown) const
816
817
{
    Q_UNUSED(shaded_is_shown)
818
    return !m_closing && !m_unmapped && !isMinimized() && !m_hidden;
819
820
}

821
822
823
824
825
bool XdgShellClient::isHiddenInternal() const
{
    return m_unmapped || m_hidden;
}

826
void XdgShellClient::hideClient(bool hide)
827
{
828
829
830
831
832
833
834
835
836
837
838
    if (m_hidden == hide) {
        return;
    }
    m_hidden = hide;
    if (hide) {
        addWorkspaceRepaint(visibleRect());
        workspace()->clientHidden(this);
        emit windowHidden(this);
    } else {
        emit windowShown(this);
    }
839
840
}

841
static bool changeMaximizeRecursion = false;
842
void XdgShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust)
843
{
844
845
846
    if (changeMaximizeRecursion) {
        return;
    }
847
848
849
850
851
852
853
854
855

    if (!isResizable()) {
        return;
    }

    const QRect clientArea = isElectricBorderMaximizing() ?
        workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()) :
        workspace()->clientArea(MaximizeArea, this);

856
857
858
    const MaximizeMode oldMode = m_requestedMaximizeMode;
    const QRect oldGeometry = geometry();

859
860
861
    // 'adjust == true' means to update the size only, e.g. after changing workspace size
    if (!adjust) {
        if (vertical)
862
            m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeVertical);
863
        if (horizontal)
864
            m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal);
865
    }
866

867
868
    m_requestedMaximizeMode = rules()->checkMaximize(m_requestedMaximizeMode);
    if (!adjust && m_requestedMaximizeMode == oldMode) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
869
870
871
        return;
    }

872
873
874
    StackingUpdatesBlocker blocker(workspace());
    RequestGeometryBlocker geometryBlocker(this);

875
    // call into decoration update borders
876
    if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) {
877
878
        changeMaximizeRecursion = true;
        const auto c = decoration()->client().data();
879
880
        if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) {
            emit c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical);
881
        }
882
883
        if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) {
            emit c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal);
884
        }
885
886
        if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) {
            emit c->maximizedChanged(m_requestedMaximizeMode & MaximizeFull);
887
888
889
        }
        changeMaximizeRecursion = false;
    }
890

891
892
893
894
    if (options->borderlessMaximizedWindows()) {
        // triggers a maximize change.
        // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry
        changeMaximizeRecursion = true;
895
        setNoBorder(rules()->checkNoBorder(m_requestedMaximizeMode == MaximizeFull));
896
897
        changeMaximizeRecursion = false;
    }
898
899
900

    // Conditional quick tiling exit points
    const auto oldQuickTileMode = quickTileMode();
901
    if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
902
903
904
905
906
        if (oldMode == MaximizeFull &&
                !clientArea.contains(m_geomMaximizeRestore.center())) {
            // Not restoring on the same screen
            // TODO: The following doesn't work for some reason
            //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually
907
908
        } else if ((oldMode == MaximizeVertical && m_requestedMaximizeMode == MaximizeRestore) ||
                  (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) {
909
            // Modifying geometry of a tiled window
910
            updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry
911
912
913
        }
    }

914
    if (m_requestedMaximizeMode == MaximizeFull) {
915
        m_geomMaximizeRestore = oldGeometry;
916
917
        // TODO: Client has more checks
        if (options->electricBorderMaximize()) {
918
            updateQuickTileMode(QuickTileFlag::Maximize);
919
        } else {
920
            updateQuickTileMode(QuickTileFlag::None);
921
922
923
924
        }
        if (quickTileMode() != oldQuickTileMode) {
            emit quickTileModeChanged();
        }
925
        setGeometry(workspace()->clientArea(MaximizeArea, this));
926
927
        workspace()->raiseClient(this);
    } else {
928
        if (m_requestedMaximizeMode == MaximizeRestore) {
929
            updateQuickTileMode(QuickTileFlag::None);
930
931
932
933
934
        }
        if (quickTileMode() != oldQuickTileMode) {
            emit quickTileModeChanged();
        }

935
        if (m_geomMaximizeRestore.isValid()) {
936
            setGeometry(m_geomMaximizeRestore);
937
        } else {
938
            setGeometry(workspace()->clientArea(PlacementArea, this));
939
940
        }
    }
941
942
}

943
944
945
946
947
void XdgShellClient::setGeometryRestore(const QRect &geo)
{
    m_geomMaximizeRestore = geo;
}

948
MaximizeMode XdgShellClient::maximizeMode() const
949
{
950
    return m_maximizeMode;
951
952
}

953
MaximizeMode XdgShellClient::requestedMaximizeMode() const
954
955
956
957
{
    return m_requestedMaximizeMode;
}

958
959
960
961
962
QRect XdgShellClient::geometryRestore() const
{
    return m_geomMaximizeRestore;
}

963
bool XdgShellClient::noBorder() const
964
{
965
966
    if (m_serverDecoration) {
        if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) {
967
            return m_userNoBorder || isFullScreen();
968
969
        }
    }
970
971
972
    if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) {
        return m_userNoBorder || isFullScreen();
    }
973
974
975
    return true;
}

976
bool XdgShellClient::isFullScreenable() const
977
978
979
980
981
982
983
{
    if (!rules()->checkFullScreen(true)) {
        return false;
    }
    return !isSpecialWindow();
}

984
void XdgShellClient::setFullScreen(bool set, bool user)
985
{
986
987
988
989
990
991
992
    set = rules()->checkFullScreen(set);

    const bool wasFullscreen = isFullScreen();
    if (wasFullscreen == set) {
        return;
    }
    if (isSpecialWindow()) {
993
        return;
994
995
    }
    if (user && !userCanSetFullScreen()) {
996
        return;
997
998
999
    }

    if (wasFullscreen) {
1000
        workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event
1001
    } else {
1002
        m_geomFsRestore = geometry();
1003
    }
1004
    m_fullScreen = set;
1005

1006
1007
1008
1009
1010
    if (set) {
        workspace()->raiseClient(this);
    }
    RequestGeometryBlocker requestBlocker(this);
    StackingUpdatesBlocker blocker1(workspace());
1011
    GeometryUpdatesBlocker blocker2(this);
1012

1013
1014
    workspace()->updateClientLayer(this);   // active fullscreens get different layer
    updateDecoration(false, false);
1015
1016

    if (set) {
1017
1018
        setGeometry(workspace()->clientArea(FullScreenArea, this));
    } else {
1019
        if (m_geomFsRestore.isValid()) {
1020
1021
1022
1023
1024
            int currentScreen = screen();
            setGeometry(QRect(m_geomFsRestore.topLeft(), adjustedSize(m_geomFsRestore.size())));
            if( currentScreen != screen())
                workspace()->sendClientToScreen( this, currentScreen );
        } else {
1025
1026
1027
            // this can happen when the window was first shown already fullscreen,
            // so let the client set the size by itself
            setGeometry(QRect(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0)));
1028
1029
1030
        }
    }

1031
1032
    updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size);
    emit fullScreenChanged();
1033
1034
}

1035
void XdgShellClient::setNoBorder(bool set)