toplevel.cpp 16.7 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
    SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
6

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
7
8
    SPDX-License-Identifier: GPL-2.0-or-later
*/
9
10
#include "toplevel.h"

11
#include "abstract_client.h"
12
#include "abstract_output.h"
13
14
15
#ifdef KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
16
#include "atoms.h"
17
#include "client_machine.h"
18
#include "composite.h"
19
#include "effects.h"
20
#include "screens.h"
21
#include "shadow.h"
22
#include "shadowitem.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
23
#include "surfaceitem_x11.h"
24
#include "windowitem.h"
25
#include "workspace.h"
26

27
#include <KWaylandServer/surface_interface.h>
28

Martin Flöser's avatar
Martin Flöser committed
29
30
#include <QDebug>

31
32
33
namespace KWin
{

34
Toplevel::Toplevel()
35
    : m_visual(XCB_NONE)
36
    , bit_depth(24)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
37
    , info(nullptr)
38
    , ready_for_painting(false)
39
    , m_internalId(QUuid::createUuid())
40
    , m_client()
41
    , is_shape(false)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
42
    , effect_window(nullptr)
43
    , m_clientMachine(new ClientMachine(this))
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
44
    , m_wmClientLeader(XCB_WINDOW_NONE)
45
    , m_screen(0)
46
    , m_skipCloseAnimation(false)
47
{
48
49
    connect(screens(), &Screens::changed, this, &Toplevel::checkScreen);
    connect(screens(), &Screens::countChanged, this, &Toplevel::checkScreen);
50
    setupCheckScreenConnection();
51
    connect(this, &Toplevel::bufferGeometryChanged, this, &Toplevel::inputTransformationChanged);
52
53
54

    // Only for compatibility reasons, drop in the next major release.
    connect(this, &Toplevel::frameGeometryChanged, this, &Toplevel::geometryChanged);
55
    connect(this, &Toplevel::geometryShapeChanged, this, &Toplevel::discardShapeRegion);
56
}
57
58

Toplevel::~Toplevel()
59
{
60
    delete info;
61
}
62

63
QDebug operator<<(QDebug debug, const Toplevel *toplevel)
64
{
65
66
67
68
    QDebugStateSaver saver(debug);
    debug.nospace();
    if (toplevel) {
        debug << toplevel->metaObject()->className() << '(' << static_cast<const void *>(toplevel);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
69
70
71
        if (toplevel->window()) {
            debug << ", windowId=0x" << Qt::hex << toplevel->window() << Qt::dec;
        }
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
        if (const KWaylandServer::SurfaceInterface *surface = toplevel->surface()) {
            debug << ", surface=" << surface;
        }
        const AbstractClient *client = qobject_cast<const AbstractClient *>(toplevel);
        if (client) {
            if (!client->isPopupWindow()) {
                debug << ", caption=" << client->caption();
            }
            if (client->transientFor()) {
                debug << ", transientFor=" << client->transientFor();
            }
        }
        if (debug.verbosity() > 2) {
            debug << ", frameGeometry=" << toplevel->frameGeometry();
            debug << ", resourceName=" << toplevel->resourceName();
            debug << ", resourceClass=" << toplevel->resourceClass();
        }
        debug << ')';
    } else {
        debug << "Toplevel(0x0)";
    }
    return debug;
94
}
95

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
96
void Toplevel::detectShape(xcb_window_t id)
97
{
98
    const bool wasShape = is_shape;
99
    is_shape = Xcb::Extensions::self()->hasShape(id);
100
    if (wasShape != is_shape) {
101
        Q_EMIT shapedChanged();
102
    }
103
}
104
105

// used only by Deleted::copy()
106
107
void Toplevel::copyToDeleted(Toplevel* c)
{
108
    m_internalId = c->internalId();
109
    m_bufferGeometry = c->m_bufferGeometry;
110
    m_frameGeometry = c->m_frameGeometry;
111
    m_clientGeometry = c->m_clientGeometry;
112
    m_visual = c->m_visual;
113
114
    bit_depth = c->bit_depth;
    info = c->info;
115
    m_client.reset(c->m_client, false);
116
    ready_for_painting = c->ready_for_painting;
117
118
    is_shape = c->is_shape;
    effect_window = c->effect_window;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
119
    if (effect_window != nullptr)
120
        effect_window->setWindow(this);
121
122
    resource_name = c->resourceName();
    resource_class = c->resourceClass();
123
124
    m_clientMachine = c->m_clientMachine;
    m_clientMachine->setParent(this);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
125
    m_wmClientLeader = c->wmClientLeader();
126
    opaque_region = c->opaqueRegion();
127
    m_screen = c->m_screen;
128
    m_skipCloseAnimation = c->m_skipCloseAnimation;
129
    m_internalFBO = c->m_internalFBO;
130
    m_internalImage = c->m_internalImage;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
131
    m_opacity = c->m_opacity;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
132
133
    m_shapeRegionIsValid = c->m_shapeRegionIsValid;
    m_shapeRegion = c->m_shapeRegion;
134
}
135
136
137
138

// before being deleted, remove references to everything that's now
// owner by Deleted
void Toplevel::disownDataPassedToDeleted()
139
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
140
    info = nullptr;
141
}
142

143
QRect Toplevel::visibleGeometry() const
144
{
145
146
    if (const WindowItem *item = windowItem()) {
        return item->mapToGlobal(item->boundingRect());
147
    }
148
    return QRect();
149
}
150

151
152
153
154
155
156
Xcb::Property Toplevel::fetchWmClientLeader() const
{
    return Xcb::Property(false, window(), atoms->wm_client_leader, XCB_ATOM_WINDOW, 0, 10000);
}

void Toplevel::readWmClientLeader(Xcb::Property &prop)
157
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
158
    m_wmClientLeader = prop.value<xcb_window_t>(window());
159
}
160

161
162
163
164
165
166
void Toplevel::getWmClientLeader()
{
    auto prop = fetchWmClientLeader();
    readWmClientLeader(prop);
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
167
168
169
/**
 * Returns sessionId for this client,
 * taken either from its window or from the leader window.
170
 */
171
QByteArray Toplevel::sessionId() const
172
{
173
    QByteArray result = Xcb::StringProperty(window(), atoms->sm_client_id);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
174
175
176
    if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) {
        result = Xcb::StringProperty(m_wmClientLeader, atoms->sm_client_id);
    }
177
    return result;
178
}
179

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
180
181
182
/**
 * Returns command property for this client,
 * taken either from its window or from the leader window.
183
 */
184
185
186
QByteArray Toplevel::wmCommand()
{
    QByteArray result = Xcb::StringProperty(window(), XCB_ATOM_WM_COMMAND);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
187
188
189
    if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) {
        result = Xcb::StringProperty(m_wmClientLeader, XCB_ATOM_WM_COMMAND);
    }
190
191
192
193
    result.replace(0, ' ');
    return result;
}

194
void Toplevel::getWmClientMachine()
195
{
196
    m_clientMachine->resolve(window(), wmClientLeader());
197
}
198

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
199
200
201
/**
 * Returns client machine for this client,
 * taken either from its window or from the leader window.
202
 */
203
204
QByteArray Toplevel::wmClientMachine(bool use_localhost) const
{
205
206
207
208
209
    if (!m_clientMachine) {
        // this should never happen
        return QByteArray();
    }
    if (use_localhost && m_clientMachine->isLocal()) {
210
        // special name for the local machine (localhost)
211
        return ClientMachine::localhost();
212
    }
213
    return m_clientMachine->hostName();
214
}
215

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
216
217
218
/**
 * Returns client leader window for this client.
 * Returns the client window itself if no leader window is defined.
219
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
220
xcb_window_t Toplevel::wmClientLeader() const
221
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
222
223
224
    if (m_wmClientLeader != XCB_WINDOW_NONE) {
        return m_wmClientLeader;
    }
225
    return window();
226
}
227
228

void Toplevel::getResourceClass()
229
{
230
231
232
    if (!info) {
        return;
    }
233
234
235
236
237
238
239
    setResourceClass(QByteArray(info->windowClassName()).toLower(), QByteArray(info->windowClassClass()).toLower());
}

void Toplevel::setResourceClass(const QByteArray &name, const QByteArray &className)
{
    resource_name  = name;
    resource_class = className;
240
    Q_EMIT windowClassChanged();
241
}
242

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
243
244
245
246
247
bool Toplevel::resourceMatch(const Toplevel *c1, const Toplevel *c2)
{
    return c1->resourceClass() == c2->resourceClass();
}

248
qreal Toplevel::opacity() const
249
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
250
    return m_opacity;
251
}
252

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
253
void Toplevel::setOpacity(qreal opacity)
254
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
255
256
    opacity = qBound(0.0, opacity, 1.0);
    if (m_opacity == opacity) {
257
258
        return;
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
259
260
    const qreal oldOpacity = m_opacity;
    m_opacity = opacity;
261
    if (Compositor::compositing()) {
262
        addRepaintFull();
263
        Q_EMIT opacityChanged(this, oldOpacity);
264
    }
265
}
266

267
268
bool Toplevel::setupCompositing()
{
269
    if (!Compositor::compositing())
270
271
272
273
274
        return false;

    effect_window = new EffectWindowImpl(this);
    Compositor::self()->scene()->addToplevel(this);

275
    connect(windowItem(), &WindowItem::positionChanged, this, &Toplevel::visibleGeometryChanged);
276
277
    connect(windowItem(), &WindowItem::boundingRectChanged, this, &Toplevel::visibleGeometryChanged);

278
279
280
    return true;
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
281
void Toplevel::finishCompositing(ReleaseReason releaseReason)
282
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
283
284
285
286
287
288
    // If the X11 window has been destroyed, avoid calling XDamageDestroy.
    if (releaseReason != ReleaseReason::Destroyed) {
        if (SurfaceItemX11 *item = qobject_cast<SurfaceItemX11 *>(surfaceItem())) {
            item->destroyDamage();
        }
    }
289
290
    if (effect_window && effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data
        deleteEffectWindow();
291
292
293
    }
}

294
void Toplevel::addRepaint(const QRect &rect)
295
{
296
    addRepaint(QRegion(rect));
297
298
}

299
void Toplevel::addRepaint(int x, int y, int width, int height)
300
{
301
    addRepaint(QRegion(x, y, width, height));
302
303
}

304
void Toplevel::addRepaint(const QRegion &region)
305
{
306
307
    if (auto item = windowItem()) {
        item->scheduleRepaint(region);
308
309
310
    }
}

311
void Toplevel::addLayerRepaint(const QRect &rect)
312
{
313
    addLayerRepaint(QRegion(rect));
314
315
}

316
void Toplevel::addLayerRepaint(int x, int y, int width, int height)
317
{
318
    addLayerRepaint(QRegion(x, y, width, height));
319
320
}

321
void Toplevel::addLayerRepaint(const QRegion &region)
322
{
323
    addRepaint(region.translated(-pos()));
324
325
326
327
}

void Toplevel::addRepaintFull()
{
328
    addLayerRepaint(visibleGeometry());
329
330
331
332
333
334
335
336
337
}

void Toplevel::addWorkspaceRepaint(int x, int y, int w, int h)
{
    addWorkspaceRepaint(QRect(x, y, w, h));
}

void Toplevel::addWorkspaceRepaint(const QRect& r2)
{
338
    if (!Compositor::compositing())
339
340
341
342
        return;
    Compositor::self()->addRepaint(r2);
}

343
344
void Toplevel::addWorkspaceRepaint(const QRegion &region)
{
345
    if (Compositor::compositing()) {
346
347
348
349
        Compositor::self()->addRepaint(region);
    }
}

350
351
352
353
void Toplevel::setReadyForPainting()
{
    if (!ready_for_painting) {
        ready_for_painting = true;
354
        if (Compositor::compositing()) {
355
            addRepaintFull();
356
            Q_EMIT windowShown(this);
357
358
359
360
        }
    }
}

361
void Toplevel::deleteEffectWindow()
362
{
363
    delete effect_window;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
364
    effect_window = nullptr;
365
}
366

367
void Toplevel::checkScreen()
368
{
369
    if (screens()->count() == 1) {
370
371
        if (m_screen != 0) {
            m_screen = 0;
372
            Q_EMIT screenChanged();
373
        }
374
    } else {
375
        const int s = screens()->number(frameGeometry().center());
376
377
        if (s != m_screen) {
            m_screen = s;
378
            Q_EMIT screenChanged();
379
        }
380
    }
381
382
383
    qreal newScale = screens()->scale(m_screen);
    if (newScale != m_screenScale) {
        m_screenScale = newScale;
384
        Q_EMIT screenScaleChanged();
385
386
387
388
389
    }
}

void Toplevel::setupCheckScreenConnection()
{
390
    connect(this, &Toplevel::frameGeometryChanged, this, &Toplevel::checkScreen);
391
392
393
394
395
    checkScreen();
}

void Toplevel::removeCheckScreenConnection()
{
396
    disconnect(this, &Toplevel::frameGeometryChanged, this, &Toplevel::checkScreen);
397
398
399
400
401
}

int Toplevel::screen() const
{
    return m_screen;
402
}
403

404
405
406
407
408
qreal Toplevel::screenScale() const
{
    return m_screenScale;
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
409
410
qreal Toplevel::bufferScale() const
{
411
    return surface() ? surface()->bufferScale() : 1;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
412
413
}

414
415
bool Toplevel::isOnScreen(int screen) const
{
416
    return screens()->geometry(screen).intersects(frameGeometry());
417
418
419
420
421
}

bool Toplevel::isOnActiveScreen() const
{
    return isOnScreen(screens()->current());
422
}
423

424
425
426
427
428
bool Toplevel::isOnOutput(AbstractOutput *output) const
{
    return output->geometry().intersects(frameGeometry());
}

429
void Toplevel::updateShadow()
430
{
431
432
433
434
435
436
437
    WindowItem *windowItem = this->windowItem();
    if (!windowItem) {
        return;
    }
    if (auto shadowItem = windowItem->shadowItem()) {
        if (!shadowItem->shadow()->updateShadow()) {
            windowItem->setShadow(nullptr);
438
        }
439
        Q_EMIT shadowChanged();
440
    } else {
441
442
443
        Shadow *shadow = Shadow::createShadow(this);
        if (shadow) {
            windowItem->setShadow(shadow);
444
            Q_EMIT shadowChanged();
445
        }
446
    }
447
}
448

449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
SurfaceItem *Toplevel::surfaceItem() const
{
    if (effectWindow() && effectWindow()->sceneWindow()) {
        return effectWindow()->sceneWindow()->surfaceItem();
    }
    return nullptr;
}

WindowItem *Toplevel::windowItem() const
{
    if (effectWindow() && effectWindow()->sceneWindow()) {
        return effectWindow()->sceneWindow()->windowItem();
    }
    return nullptr;
}

465
466
467
468
469
bool Toplevel::wantsShadowToBeRendered() const
{
    return true;
}

470
471
void Toplevel::getWmOpaqueRegion()
{
472
473
474
475
    if (!info) {
        return;
    }

Martin Flöser's avatar
Martin Flöser committed
476
    const auto rects = info->opaqueRegion();
477
    QRegion new_opaque_region;
Martin Flöser's avatar
Martin Flöser committed
478
479
480
    for (const auto &r : rects) {
        new_opaque_region += QRect(r.pos.x, r.pos.y, r.size.width, r.size.height);
    }
481
482
483
484

    opaque_region = new_opaque_region;
}

485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
QRegion Toplevel::shapeRegion() const
{
    if (m_shapeRegionIsValid) {
        return m_shapeRegion;
    }

    const QRect bufferGeometry = this->bufferGeometry();

    if (shape()) {
        auto cookie = xcb_shape_get_rectangles_unchecked(connection(), frameId(), XCB_SHAPE_SK_BOUNDING);
        ScopedCPointer<xcb_shape_get_rectangles_reply_t> reply(xcb_shape_get_rectangles_reply(connection(), cookie, nullptr));
        if (!reply.isNull()) {
            m_shapeRegion = QRegion();
            const xcb_rectangle_t *rects = xcb_shape_get_rectangles_rectangles(reply.data());
            const int rectCount = xcb_shape_get_rectangles_rectangles_length(reply.data());
            for (int i = 0; i < rectCount; ++i) {
                m_shapeRegion += QRegion(rects[i].x, rects[i].y, rects[i].width, rects[i].height);
            }
            // make sure the shape is sane (X is async, maybe even XShape is broken)
            m_shapeRegion &= QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height());
        } else {
            m_shapeRegion = QRegion();
        }
    } else {
        m_shapeRegion = QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height());
    }

    m_shapeRegionIsValid = true;
    return m_shapeRegion;
}

void Toplevel::discardShapeRegion()
{
    m_shapeRegionIsValid = false;
    m_shapeRegion = QRegion();
}

522
523
524
525
526
527
528
529
530
531
bool Toplevel::isClient() const
{
    return false;
}

bool Toplevel::isDeleted() const
{
    return false;
}

532
533
534
bool Toplevel::isOnCurrentActivity() const
{
#ifdef KWIN_BUILD_ACTIVITIES
535
536
537
    if (!Activities::self()) {
        return true;
    }
538
539
540
541
542
543
    return isOnActivity(Activities::self()->current());
#else
    return true;
#endif
}

544
545
546
547
548
549
void Toplevel::elevate(bool elevate)
{
    if (!effectWindow()) {
        return;
    }
    effectWindow()->elevate(elevate);
550
    addWorkspaceRepaint(visibleGeometry());
551
552
}

Martin Flöser's avatar
Martin Flöser committed
553
554
pid_t Toplevel::pid() const
{
555
556
557
    if (!info) {
        return -1;
    }
Martin Flöser's avatar
Martin Flöser committed
558
559
560
    return info->pid();
}

561
562
563
564
565
xcb_window_t Toplevel::frameId() const
{
    return m_client;
}

566
567
568
569
570
571
Xcb::Property Toplevel::fetchSkipCloseAnimation() const
{
    return Xcb::Property(false, window(), atoms->kde_skip_close_animation, XCB_ATOM_CARDINAL, 0, 1);
}

void Toplevel::readSkipCloseAnimation(Xcb::Property &property)
572
{
573
    setSkipCloseAnimation(property.toBool());
574
575
}

576
577
578
579
580
581
void Toplevel::getSkipCloseAnimation()
{
    Xcb::Property property = fetchSkipCloseAnimation();
    readSkipCloseAnimation(property);
}

582
583
584
585
586
587
588
589
590
591
592
bool Toplevel::skipsCloseAnimation() const
{
    return m_skipCloseAnimation;
}

void Toplevel::setSkipCloseAnimation(bool set)
{
    if (set == m_skipCloseAnimation) {
        return;
    }
    m_skipCloseAnimation = set;
593
    Q_EMIT skipCloseAnimationChanged();
594
595
}

596
void Toplevel::setSurface(KWaylandServer::SurfaceInterface *surface)
597
598
599
600
601
{
    if (m_surface == surface) {
        return;
    }
    m_surface = surface;
602
603
604
605
    connect(m_surface, &KWaylandServer::SurfaceInterface::destroyed, this, [this]() {
        m_surface = nullptr;
        m_surfaceId = 0;
    });
Gang Wu's avatar
Gang Wu committed
606
    m_surfaceId = surface->id();
607
    Q_EMIT surfaceChanged();
608
609
}

610
611
QByteArray Toplevel::windowRole() const
{
612
613
614
    if (!info) {
        return {};
    }
615
616
617
    return QByteArray(info->windowRole());
}

618
619
620
621
622
623
624
625
void Toplevel::setDepth(int depth)
{
    if (bit_depth == depth) {
        return;
    }
    const bool oldAlpha = hasAlpha();
    bit_depth = depth;
    if (oldAlpha != hasAlpha()) {
626
        Q_EMIT hasAlphaChanged();
627
628
629
    }
}

630
631
632
633
QRegion Toplevel::inputShape() const
{
    if (m_surface) {
        return m_surface->input();
Martin Flöser's avatar
Martin Flöser committed
634
635
636
637
    } else {
        // TODO: maybe also for X11?
        return QRegion();
    }
638
639
}

640
641
642
643
644
645
646
QMatrix4x4 Toplevel::inputTransformation() const
{
    QMatrix4x4 m;
    m.translate(-x(), -y());
    return m;
}

647
648
649
650
651
652
653
654
bool Toplevel::hitTest(const QPoint &point) const
{
    if (m_surface && m_surface->isMapped()) {
        return m_surface->inputSurfaceAt(mapToLocal(point));
    }
    return inputGeometry().contains(point);
}

655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
QPoint Toplevel::mapToFrame(const QPoint &point) const
{
    return point - frameGeometry().topLeft();
}

QPoint Toplevel::mapToLocal(const QPoint &point) const
{
    return point - bufferGeometry().topLeft();
}

QPointF Toplevel::mapToLocal(const QPointF &point) const
{
    return point - bufferGeometry().topLeft();
}

670
671
672
673
674
QPointF Toplevel::mapFromLocal(const QPointF &point) const
{
    return point + bufferGeometry().topLeft();
}

675
676
QRect Toplevel::inputGeometry() const
{
677
    return frameGeometry();
678
679
}

680
681
682
683
684
685
686
687
bool Toplevel::isLocalhost() const
{
    if (!m_clientMachine) {
        return true;
    }
    return m_clientMachine->isLocal();
}

688
689
690
691
692
QMargins Toplevel::frameMargins() const
{
    return QMargins();
}

693
694
} // namespace