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

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

52
#include <QFileInfo>
53

54
55
#include <sys/types.h>
#include <unistd.h>
56
57

#include <csignal>
58

59
60
Q_DECLARE_METATYPE(NET::WindowType)

61
using namespace KWaylandServer;
62
63
64
65

namespace KWin
{

66
XdgShellClient::XdgShellClient(XdgShellSurfaceInterface *surface)
67
    : AbstractClient()
68
    , m_xdgShellToplevel(surface)
69
70
71
72
73
74
    , m_xdgShellPopup(nullptr)
{
    setSurface(surface->surface());
    init();
}

75
XdgShellClient::XdgShellClient(XdgShellPopupInterface *surface)
76
    : AbstractClient()
77
    , m_xdgShellToplevel(nullptr)
78
    , m_xdgShellPopup(surface)
79
80
{
    setSurface(surface->surface());
81
82
83
    init();
}

84
XdgShellClient::~XdgShellClient() = default;
85

86
void XdgShellClient::init()
87
{
88
89
    m_requestGeometryBlockCounter++;

90
    connect(this, &XdgShellClient::desktopFileNameChanged, this, &XdgShellClient::updateIcon);
91
    createWindowId();
92
    setupCompositing();
93
    updateIcon();
94
95

    // TODO: Initialize with null rect.
96
    m_frameGeometry = QRect(0, 0, -1, -1);
97
    m_windowGeometry = QRect(0, 0, -1, -1);
98
99

    if (waylandServer()->inputMethodConnection() == surface()->client()) {
100
101
        m_windowType = NET::OnScreenDisplay;
    }
102

103
104
105
    connect(surface(), &SurfaceInterface::unmapped, this, &XdgShellClient::unmap);
    connect(surface(), &SurfaceInterface::unbound, this, &XdgShellClient::destroyClient);
    connect(surface(), &SurfaceInterface::destroyed, this, &XdgShellClient::destroyClient);
106

107
108
109
    if (m_xdgShellToplevel) {
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::destroyed, this, &XdgShellClient::destroyClient);
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::configureAcknowledged, this, &XdgShellClient::handleConfigureAcknowledged);
110

111
112
        m_caption = m_xdgShellToplevel->title().simplified();
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::titleChanged, this, &XdgShellClient::handleWindowTitleChanged);
113
        QTimer::singleShot(0, this, &XdgShellClient::updateCaption);
114

115
116
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::moveRequested, this, &XdgShellClient::handleMoveRequested);
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::resizeRequested, this, &XdgShellClient::handleResizeRequested);
117
118
119

        // Determine the resource name, this is inspired from ICCCM 4.1.2.5
        // the binary name of the invoked client.
120
        QFileInfo info{m_xdgShellToplevel->client()->executablePath()};
121
122
123
124
        QByteArray resourceName;
        if (info.exists()) {
            resourceName = info.fileName().toUtf8();
        }
125
126
127
        setResourceClass(resourceName, m_xdgShellToplevel->windowClass());
        setDesktopFileName(m_xdgShellToplevel->windowClass());
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::windowClassChanged, this, &XdgShellClient::handleWindowClassChanged);
128

129
130
131
132
133
134
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::minimizeRequested, this, &XdgShellClient::handleMinimizeRequested);
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::maximizedChanged, this, &XdgShellClient::handleMaximizeRequested);
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::fullscreenChanged, this, &XdgShellClient::handleFullScreenRequested);
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::windowMenuRequested, this, &XdgShellClient::handleWindowMenuRequested);
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::transientForChanged, this, &XdgShellClient::handleTransientForChanged);
        connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::windowGeometryChanged, this, &XdgShellClient::handleWindowGeometryChanged);
David Edmundson's avatar
David Edmundson committed
135

136
        auto global = static_cast<XdgShellInterface *>(m_xdgShellToplevel->global());
137
138
139
140
        connect(global, &XdgShellInterface::pingDelayed, this, &XdgShellClient::handlePingDelayed);
        connect(global, &XdgShellInterface::pingTimeout, this, &XdgShellClient::handlePingTimeout);
        connect(global, &XdgShellInterface::pongReceived, this, &XdgShellClient::handlePongReceived);

141
142
143
        auto configure = [this] {
            if (m_closing) {
                return;
144
            }
145
146
147
            if (m_requestGeometryBlockCounter != 0 || areGeometryUpdatesBlocked()) {
                return;
            }
148
            m_xdgShellToplevel->configure(xdgSurfaceStates(), m_requestedClientSize);
149
150
151
152
        };
        connect(this, &AbstractClient::activeChanged, this, configure);
        connect(this, &AbstractClient::clientStartUserMovedResized, this, configure);
        connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure);
153

154
        connect(this, &XdgShellClient::frameGeometryChanged, this, &XdgShellClient::updateClientOutputs);
155
156
157
158
        connect(screens(), &Screens::changed, this, &XdgShellClient::updateClientOutputs);
    } else if (m_xdgShellPopup) {
        connect(m_xdgShellPopup, &XdgShellPopupInterface::configureAcknowledged, this, &XdgShellClient::handleConfigureAcknowledged);
        connect(m_xdgShellPopup, &XdgShellPopupInterface::grabRequested, this, &XdgShellClient::handleGrabRequested);
159
        connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &XdgShellClient::destroyClient);
160
        connect(m_xdgShellPopup, &XdgShellPopupInterface::windowGeometryChanged, this, &XdgShellClient::handleWindowGeometryChanged);
161
    }
162

163
    // set initial desktop
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
164
    setDesktop(VirtualDesktopManager::self()->current());
165

166
    // setup shadow integration
167
168
    updateShadow();
    connect(surface(), &SurfaceInterface::shadowChanged, this, &Toplevel::updateShadow);
169

170
    connect(waylandServer(), &WaylandServer::foreignTransientChanged, this, [this](KWaylandServer::SurfaceInterface *child) {
Marco Martin's avatar
Marco Martin committed
171
        if (child == surface()) {
172
            handleTransientForChanged();
Marco Martin's avatar
Marco Martin committed
173
174
        }
    });
175
    handleTransientForChanged();
176

177
    AbstractClient::updateColorScheme(QString());
178
179

    connect(surface(), &SurfaceInterface::committed, this, &XdgShellClient::finishInit);
180
181
}

182
183
184
void XdgShellClient::finishInit()
{
    disconnect(surface(), &SurfaceInterface::committed, this, &XdgShellClient::finishInit);
185

186
187
    connect(surface(), &SurfaceInterface::committed, this, &XdgShellClient::handleCommitted);

188
189
    bool needsPlacement = !isInitialPositionSet();

190
191
192
    if (supportsWindowRules()) {
        setupWindowRules(false);

193
        const QRect originalGeometry = frameGeometry();
194
195
        const QRect ruledGeometry = rules()->checkGeometry(originalGeometry, true);
        if (originalGeometry != ruledGeometry) {
196
            setFrameGeometry(ruledGeometry);
197
198
        }

199
200
        maximize(rules()->checkMaximize(maximizeMode(), true));

201
202
203
204
205
206
207
208
209
210
211
212
213
        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();

214
215
216
217
218
        // Don't place the client if its position is set by a rule.
        if (rules()->checkPosition(invalidPoint, true) != invalidPoint) {
            needsPlacement = false;
        }

219
220
221
222
223
        // Don't place the client if the maximize state is set by a rule.
        if (requestedMaximizeMode() != MaximizeRestore) {
            needsPlacement = false;
        }

224
225
226
227
228
        discardTemporaryRules();
        RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules.
        updateWindowRules(Rules::All);
    }

229
230
231
232
    if (isFullScreen()) {
        needsPlacement = false;
    }

233
    if (needsPlacement) {
234
        const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop());
235
236
237
238
239
240
241
        placeIn(area);
    }

    m_requestGeometryBlockCounter--;
    if (m_requestGeometryBlockCounter == 0) {
        requestGeometry(m_blockedRequestGeometry);
    }
242
243

    m_isInitialized = true;
244
245
}

246
void XdgShellClient::destroyClient()
247
{
248
    m_closing = true;
249
250
#ifdef KWIN_BUILD_TABBOX
    TabBox::TabBox *tabBox = TabBox::TabBox::self();
251
    if (tabBox->isDisplayed() && tabBox->currentClient() == this) {
252
253
254
        tabBox->nextPrev(true);
    }
#endif
255
256
257
    if (isMoveResize()) {
        leaveMoveResize();
    }
258
259
260
261

    // Replace ShellClient with an instance of Deleted in the stacking order.
    Deleted *deleted = Deleted::create(this);
    emit windowClosed(this, deleted);
262
263
264
265

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

266
    destroyWindowManagementInterface();
267
    destroyDecoration();
268

269
270
271
272
273
274
275
276
277
278
    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;
279
280
        }
    }
281

282
283
    waylandServer()->removeClient(this);

284
285
    deleted->unrefWindow();

286
    m_xdgShellToplevel = nullptr;
287
    m_xdgShellPopup = nullptr;
288
289
290
    deleteClient(this);
}

291
void XdgShellClient::deleteClient(XdgShellClient *c)
292
293
294
295
{
    delete c;
}

296
297
298
299
300
301
302
303
304
QRect XdgShellClient::inputGeometry() const
{
    if (isDecorated()) {
        return AbstractClient::inputGeometry();
    }
    // TODO: What about sub-surfaces sticking outside the main surface?
    return m_bufferGeometry;
}

305
QRect XdgShellClient::bufferGeometry() const
306
{
307
    return m_bufferGeometry;
308
309
}

310
QStringList XdgShellClient::activities() const
311
312
313
314
315
{
    // TODO: implement
    return QStringList();
}

316
QPoint XdgShellClient::clientContentPos() const
317
318
319
320
{
    return -1 * clientPos();
}

321
QSize XdgShellClient::clientSize() const
322
{
323
    const QRect boundingRect = surface()->boundingRect();
324
    return m_windowGeometry.size().boundedTo(boundingRect.size());
325
326
}

327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
QSize XdgShellClient::minSize() const
{
    if (m_xdgShellToplevel) {
        return rules()->checkMinSize(m_xdgShellToplevel->minimumSize());
    }
    return QSize(0, 0);
}

QSize XdgShellClient::maxSize() const
{
    if (m_xdgShellToplevel) {
        return rules()->checkMaxSize(m_xdgShellToplevel->maximumSize());
    }
    return QSize(INT_MAX, INT_MAX);
}

343
void XdgShellClient::debug(QDebug &stream) const
344
{
Roman Gilg's avatar
Roman Gilg committed
345
    stream.nospace();
346
    stream << "\'XdgShellClient:" << surface() << ";WMCLASS:" << resourceClass() << ":"
Roman Gilg's avatar
Roman Gilg committed
347
           << resourceName() << ";Caption:" << caption() << "\'";
348
349
}

350
bool XdgShellClient::belongsToDesktop() const
351
352
353
354
{
    const auto clients = waylandServer()->clients();

    return std::any_of(clients.constBegin(), clients.constEnd(),
355
        [this](const AbstractClient *client) {
356
357
358
359
360
361
362
363
            if (belongsToSameApplication(client, SameApplicationChecks())) {
                return client->isDesktop();
            }
            return false;
        }
    );
}

364
Layer XdgShellClient::layerForDock() const
365
{
366
367
368
    if (m_plasmaShellSurface) {
        switch (m_plasmaShellSurface->panelBehavior()) {
        case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover:
369
            return NormalLayer;
370
        case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide:
371
            return AboveLayer;
372
373
374
375
376
377
378
        case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow:
        case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible:
            return DockLayer;
        default:
            Q_UNREACHABLE();
            break;
        }
379
    }
380
    return AbstractClient::layerForDock();
381
382
}

383
QRect XdgShellClient::transparentRect() const
384
385
386
387
388
{
    // TODO: implement
    return QRect();
}

389
NET::WindowType XdgShellClient::windowType(bool direct, int supported_types) const
390
391
392
393
{
    // TODO: implement
    Q_UNUSED(direct)
    Q_UNUSED(supported_types)
394
    return m_windowType;
395
396
}

397
double XdgShellClient::opacity() const
398
{
399
    return m_opacity;
400
401
}

402
void XdgShellClient::setOpacity(double opacity)
403
{
404
405
406
407
408
409
410
411
    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);
412
413
}

414
void XdgShellClient::addDamage(const QRegion &damage)
415
{
416
417
418
419
    const int offsetX = m_bufferGeometry.x() - frameGeometry().x();
    const int offsetY = m_bufferGeometry.y() - frameGeometry().y();
    repaints_region += damage.translated(offsetX, offsetY);

420
421
422
    Toplevel::addDamage(damage);
}

423
void XdgShellClient::markAsMapped()
424
425
426
427
{
    if (!m_unmapped) {
        return;
    }
428

429
    m_unmapped = false;
430
431
432
433
434
435
    if (!ready_for_painting) {
        setReadyForPainting();
    } else {
        addRepaintFull();
        emit windowShown(this);
    }
436
437
438
    if (shouldExposeToWindowManagement()) {
        setupWindowManagementInterface();
    }
439
    updateShowOnScreenEdge();
440
441
}

442
void XdgShellClient::updateDecoration(bool check_workspace_pos, bool force)
443
444
445
446
{
    if (!force &&
            ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder())))
        return;
447
    QRect oldgeom = frameGeometry();
448
449
450
451
452
453
454
455
456
    QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom());
    blockGeometryUpdates(true);
    if (force)
        destroyDecoration();
    if (!noBorder()) {
        createDecoration(oldgeom);
    } else
        destroyDecoration();
    if (m_serverDecoration && isDecorated()) {
457
        m_serverDecoration->setMode(KWaylandServer::ServerSideDecorationManagerInterface::Mode::Server);
458
    }
459
    if (m_xdgDecoration) {
460
        auto mode = isDecorated() || m_userNoBorder ? XdgDecorationInterface::Mode::ServerSide: XdgDecorationInterface::Mode::ClientSide;
461
        m_xdgDecoration->configure(mode);
462
        if (m_requestGeometryBlockCounter == 0) {
463
            m_xdgShellToplevel->configure(xdgSurfaceStates(), m_requestedClientSize);
464
        }
465
    }
466
    updateShadow();
467
468
469
470
471
    if (check_workspace_pos)
        checkWorkspacePosition(oldgeom, -2, oldClientGeom);
    blockGeometryUpdates(false);
}

472
void XdgShellClient::setFrameGeometry(const QRect &rect, ForceGeometry_t force)
473
{
474
    const QRect newGeometry = rules()->checkGeometry(rect);
475

476
477
478
    if (areGeometryUpdatesBlocked()) {
        // when the GeometryUpdateBlocker exits the current geom is passed to setGeometry
        // thus we need to set it here.
479
        m_frameGeometry = newGeometry;
480
481
482
        if (pendingGeometryUpdate() == PendingGeometryForced) {
            // maximum, nothing needed
        } else if (force == ForceGeometrySet) {
483
            setPendingGeometryUpdate(PendingGeometryForced);
484
        } else {
485
            setPendingGeometryUpdate(PendingGeometryNormal);
486
        }
487
488
        return;
    }
489

490
491
    if (pendingGeometryUpdate() != PendingGeometryNone) {
        // reset geometry to the one before blocking, so that we can compare properly
492
        m_frameGeometry = frameGeometryBeforeUpdateBlocking();
493
    }
494

495
    const QSize requestedClientSize = newGeometry.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom());
496

497
498
    if (requestedClientSize == m_windowGeometry.size() &&
        (m_requestedClientSize.isEmpty() || requestedClientSize == m_requestedClientSize)) {
499
        // size didn't change, and we don't need to explicitly request a new size
500
        doSetGeometry(newGeometry);
501
        updateMaximizeMode(m_requestedMaximizeMode);
502
503
    } else {
        // size did change, Client needs to provide a new buffer
504
        requestGeometry(newGeometry);
505
    }
506
507
}

508
509
510
511
512
513
514
515
516
517
518
519
520
521
QRect XdgShellClient::determineBufferGeometry() const
{
    // Offset of the main surface relative to the frame rect.
    const int offsetX = borderLeft() - m_windowGeometry.left();
    const int offsetY = borderTop() - m_windowGeometry.top();

    QRect bufferGeometry;
    bufferGeometry.setX(x() + offsetX);
    bufferGeometry.setY(y() + offsetY);
    bufferGeometry.setSize(surface()->size());

    return bufferGeometry;
}

522
void XdgShellClient::doSetGeometry(const QRect &rect)
523
{
524
525
526
    bool frameGeometryIsChanged = false;
    bool bufferGeometryIsChanged = false;

527
528
    if (m_frameGeometry != rect) {
        m_frameGeometry = rect;
529
        frameGeometryIsChanged = true;
530
    }
531
532
533
534
535

    const QRect bufferGeometry = determineBufferGeometry();
    if (m_bufferGeometry != bufferGeometry) {
        m_bufferGeometry = bufferGeometry;
        bufferGeometryIsChanged = true;
536
    }
537

538
539
540
    if (!frameGeometryIsChanged && !bufferGeometryIsChanged) {
        return;
    }
541

542
    if (m_unmapped && geometryRestore().isEmpty() && !m_frameGeometry.isEmpty()) {
543
        // use first valid geometry as restore geometry
544
        setGeometryRestore(m_frameGeometry);
545
546
    }

547
548
549
550
551
    if (frameGeometryIsChanged) {
        if (hasStrut()) {
            workspace()->updateClientArea();
        }
        updateWindowRules(Rules::Position | Rules::Size);
552
        emit frameGeometryChanged(this, frameGeometryBeforeUpdateBlocking());
553
    }
554

555
556
    emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking());

557
    addRepaintDuringGeometryUpdates();
558
    updateGeometryBeforeUpdateBlocking();
559
560
561
562

    if (isResize()) {
        performMoveResize();
    }
563
564
}

565
566
567
568
569
570
571
void XdgShellClient::doMove(int x, int y)
{
    Q_UNUSED(x)
    Q_UNUSED(y)
    m_bufferGeometry = determineBufferGeometry();
}

572
QByteArray XdgShellClient::windowRole() const
573
574
575
576
{
    return QByteArray();
}

577
bool XdgShellClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const
578
{
579
580
581
582
583
    if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) {
        if (other->desktopFileName() == desktopFileName()) {
            return true;
        }
    }
584
585
586
587
588
589
    if (auto s = other->surface()) {
        return s->client() == surface()->client();
    }
    return false;
}

590
void XdgShellClient::blockActivityUpdates(bool b)
591
592
593
594
{
    Q_UNUSED(b)
}

595
596
597
598
599
600
601
602
603
604
QString XdgShellClient::captionNormal() const
{
    return m_caption;
}

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

605
void XdgShellClient::updateCaption()
606
607
{
    const QString oldSuffix = m_captionSuffix;
608
609
610
611
612
613
614
615
616
    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());
    }
617
618
619
    if (m_captionSuffix != oldSuffix) {
        emit captionChanged();
    }
620
621
}

622
void XdgShellClient::closeWindow()
623
{
624
625
    if (m_xdgShellToplevel && isCloseable()) {
        m_xdgShellToplevel->close();
626
        ping(PingReason::CloseWindow);
627
    }
628
629
}

630
AbstractClient *XdgShellClient::findModal(bool allow_itself)
631
632
633
634
635
{
    Q_UNUSED(allow_itself)
    return nullptr;
}

636
bool XdgShellClient::isCloseable() const
637
{
638
639
640
    if (m_windowType == NET::Desktop || m_windowType == NET::Dock) {
        return false;
    }
641
    if (m_xdgShellToplevel) {
642
643
        return true;
    }
644
    return false;
645
646
}

647
bool XdgShellClient::isFullScreen() const
648
{
649
    return m_fullScreen;
650
651
}

652
bool XdgShellClient::isMaximizable() const
653
{
654
655
656
    if (!isResizable()) {
        return false;
    }
657
658
659
    if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || rules()->checkMaximize(MaximizeFull) != MaximizeFull) {
        return false;
    }
660
    return true;
661
662
}

663
bool XdgShellClient::isMinimizable() const
664
{
665
666
667
    if (!rules()->checkMinimize(true)) {
        return false;
    }
Marco Martin's avatar
Marco Martin committed
668
    return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal);
669
670
}

671
bool XdgShellClient::isMovable() const
672
{
673
674
675
    if (isFullScreen()) {
        return false;
    }
676
677
678
    if (rules()->checkPosition(invalidPoint) != invalidPoint) {
        return false;
    }
679
680
681
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
    }
682
683
684
    if (m_xdgShellPopup) {
        return false;
    }
685
    return true;
686
687
}

688
bool XdgShellClient::isMovableAcrossScreens() const
689
{
690
691
692
    if (rules()->checkPosition(invalidPoint) != invalidPoint) {
        return false;
    }
693
694
695
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
    }
696
697
698
    if (m_xdgShellPopup) {
        return false;
    }
699
    return true;
700
701
}

702
bool XdgShellClient::isResizable() const
703
{
704
705
706
    if (isFullScreen()) {
        return false;
    }
707
708
709
    if (rules()->checkSize(QSize()).isValid()) {
        return false;
    }
710
711
712
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
    }
713
714
715
    if (m_xdgShellPopup) {
        return false;
    }
716
    return true;
717
718
}

719
bool XdgShellClient::isShown(bool shaded_is_shown) const
720
721
{
    Q_UNUSED(shaded_is_shown)
722
    return !m_closing && !m_unmapped && !isMinimized() && !m_hidden;
723
724
}

725
726
727
728
729
bool XdgShellClient::isHiddenInternal() const
{
    return m_unmapped || m_hidden;
}

730
void XdgShellClient::hideClient(bool hide)
731
{
732
733
734
735
736
737
738
739
740
741
742
    if (m_hidden == hide) {
        return;
    }
    m_hidden = hide;
    if (hide) {
        addWorkspaceRepaint(visibleRect());
        workspace()->clientHidden(this);
        emit windowHidden(this);
    } else {
        emit windowShown(this);
    }
743
744
}

745
static bool changeMaximizeRecursion = false;
746
void XdgShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust)
747
{
748
749
750
    if (changeMaximizeRecursion) {
        return;
    }
751
752
753
754
755
756

    if (!isResizable()) {
        return;
    }

    const QRect clientArea = isElectricBorderMaximizing() ?
757
        workspace()->clientArea(MaximizeArea, Cursors::self()->mouse()->pos(), desktop()) :
758
759
        workspace()->clientArea(MaximizeArea, this);

760
    const MaximizeMode oldMode = m_requestedMaximizeMode;
761
    const QRect oldGeometry = frameGeometry();
762

763
764
765
    // 'adjust == true' means to update the size only, e.g. after changing workspace size
    if (!adjust) {
        if (vertical)
766
            m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeVertical);
767
        if (horizontal)
768
            m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal);
769
    }
770

771
772
    m_requestedMaximizeMode = rules()->checkMaximize(m_requestedMaximizeMode);
    if (!adjust && m_requestedMaximizeMode == oldMode) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
773
774
775
        return;
    }

776
777
778
    StackingUpdatesBlocker blocker(workspace());
    RequestGeometryBlocker geometryBlocker(this);

779
    // call into decoration update borders
780
    if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) {
781
        changeMaximizeRecursion = true;
782
        const auto c = decoration()->client().toStrongRef();
783
784
        if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) {
            emit c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical);
785
        }
786
787
        if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) {
            emit c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal);
788
        }
789
790
        if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) {
            emit c->maximizedChanged(m_requestedMaximizeMode & MaximizeFull);
791
792
793
        }
        changeMaximizeRecursion = false;
    }
794

795
796
797
798
    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;
799
        setNoBorder(rules()->checkNoBorder(m_requestedMaximizeMode == MaximizeFull));
800
801
        changeMaximizeRecursion = false;
    }
802
803
804

    // Conditional quick tiling exit points
    const auto oldQuickTileMode = quickTileMode();
805
    if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
806
        if (oldMode == MaximizeFull &&
807
                !clientArea.contains(geometryRestore().center())) {
808
809
810
            // 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
811
812
        } else if ((oldMode == MaximizeVertical && m_requestedMaximizeMode == MaximizeRestore) ||
                  (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) {
813
            // Modifying geometry of a tiled window
814
            updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry
815
816
817
        }
    }

818
    if (m_requestedMaximizeMode == MaximizeFull) {
819
        setGeometryRestore(oldGeometry);
820
821
        // TODO: Client has more checks
        if (options->electricBorderMaximize()) {
822
            updateQuickTileMode(QuickTileFlag::Maximize);
823
        } else {
824
            updateQuickTileMode(QuickTileFlag::None);
825
826
827
828
        }
        if (quickTileMode() != oldQuickTileMode) {
            emit quickTileModeChanged();
        }
829
        setFrameGeometry(workspace()->clientArea(MaximizeArea, this));
830
831
        workspace()->raiseClient(this);
    } else {
832
        if (m_requestedMaximizeMode == MaximizeRestore) {
833
            updateQuickTileMode(QuickTileFlag::None);
834
835
836
837
838
        }
        if (quickTileMode() != oldQuickTileMode) {
            emit quickTileModeChanged();
        }

839
840
        if (geometryRestore().isValid()) {
            setFrameGeometry(geometryRestore());
841
        } else {
842
            setFrameGeometry(workspace()->clientArea(PlacementArea, this));
843
844
        }
    }
845
846
}

847
MaximizeMode XdgShellClient::maximizeMode() const
848
{
849
    return m_maximizeMode;
850
851
}

852
MaximizeMode XdgShellClient::requestedMaximizeMode() const
853
854
855
856
{
    return m_requestedMaximizeMode;
}

857
bool XdgShellClient::noBorder() const
858
{
859
860
    if (m_serverDecoration) {
        if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) {
861
            return m_userNoBorder || isFullScreen();
862
863
        }
    }
864
865
866
    if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) {
        return m_userNoBorder || isFullScreen();
    }
867
868
869
    return true;
}

870
bool XdgShellClient::isFullScreenable() const
871
872
873
874
875
876
877
{
    if (!rules()->checkFullScreen(true)) {
        return false;
    }
    return !isSpecialWindow();
}

878
void XdgShellClient::setFullScreen(bool set, bool user)
879
{
880
881
882
883
884
885
886
    set = rules()->checkFullScreen(set);

    const bool wasFullscreen = isFullScreen();
    if (wasFullscreen == set) {
        return;
    }
    if (isSpecialWindow()) {
887
        return;
888
889
    }
    if (user && !userCanSetFullScreen()) {
890
        return;
891
892
893
    }

    if (wasFullscreen) {
894
        workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event
895
    } else {
896
        m_geomFsRestore = frameGeometry();
897
    }
898
    m_fullScreen = set;
899

900
901
902
903
904
    if (set) {
        workspace()->raiseClient(this);
    }
    RequestGeometryBlocker requestBlocker(this);
    StackingUpdatesBlocker blocker1(workspace());
905
    GeometryUpdatesBlocker blocker2(this);
906

907
908
    workspace()->updateClientLayer(this);   // active fullscreens get different layer
    updateDecoration(false, false);
909
910

    if (set) {
911
        setFrameGeometry(workspace()->clientArea(FullScreenArea, this));
912
    } else {
913
        if (m_geomFsRestore.isValid()) {
914
            int currentScreen = screen();
915
            setFrameGeometry(QRect(m_geomFsRestore.topLeft(), constrainFrameSize(m_geomFsRestore.size())));
916
917
918
            if( currentScreen != screen())
                workspace()->sendClientToScreen( this, currentScreen );
        } else {
919
920
            // this can happen when the window was first shown already fullscreen,
            // so let the client set the size by itself
921
            setFrameGeometry(QRect(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0)));
922
923
924
        }
    }

925
926
    updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size);
    emit fullScreenChanged();
927
928
}

929
void XdgShellClient::setNoBorder(bool set)
930
{
931
932
933
934
935
936
937
938
939
940
    if (!userCanSetNoBorder()) {
        return;
    }
    set = rules()->checkNoBorder(set);
    if (m_userNoBorder == set) {
        return;
    }
    m_userNoBorder = set;
    updateDecoration(true, false);
    updateWindowRules(Rules::NoBorder);
941
942
}

943
void XdgShellClient::setOnAllActivities(bool set)
944
945
946
947
{
    Q_UNUSED(set)
}

948
void XdgShellClient::takeFocus()
949
{
950
    if (rules()->checkAcceptFocus(wantsInput())) {
951
        if (m_xdgShellToplevel) {
952
            ping(PingReason::FocusWindow);
David Edmundson's avatar
David Edmundson committed
953
        }
954
955
        setActive(true);
    }
956

957
    if (!keepAbove() && !isOnScreenDisplay() && !belongsToDesktop()) {
958
        workspace()->setShowingDesktop(false);
959
    }
960
961
}

962
void XdgShellClient::doSetActive()
963
{