1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 #include "toplevel.h"
10 
11 #include "abstract_client.h"
12 #include "abstract_output.h"
13 #ifdef KWIN_BUILD_ACTIVITIES
14 #include "activities.h"
15 #endif
16 #include "atoms.h"
17 #include "client_machine.h"
18 #include "composite.h"
19 #include "effects.h"
20 #include "platform.h"
21 #include "screens.h"
22 #include "shadow.h"
23 #include "shadowitem.h"
24 #include "surfaceitem_x11.h"
25 #include "virtualdesktops.h"
26 #include "windowitem.h"
27 #include "workspace.h"
28 
29 #include <KWaylandServer/surface_interface.h>
30 
31 #include <QDebug>
32 
33 namespace KWin
34 {
35 
Toplevel()36 Toplevel::Toplevel()
37     : m_visual(XCB_NONE)
38     , bit_depth(24)
39     , info(nullptr)
40     , ready_for_painting(false)
41     , m_internalId(QUuid::createUuid())
42     , m_client()
43     , is_shape(false)
44     , effect_window(nullptr)
45     , m_clientMachine(new ClientMachine(this))
46     , m_wmClientLeader(XCB_WINDOW_NONE)
47     , m_skipCloseAnimation(false)
48 {
49     connect(screens(), &Screens::changed, this, &Toplevel::screenChanged);
50     setupCheckOutputConnection();
51     connect(this, &Toplevel::bufferGeometryChanged, this, &Toplevel::inputTransformationChanged);
52 
53     // Only for compatibility reasons, drop in the next major release.
54     connect(this, &Toplevel::frameGeometryChanged, this, &Toplevel::geometryChanged);
55     connect(this, &Toplevel::geometryShapeChanged, this, &Toplevel::discardShapeRegion);
56 }
57 
~Toplevel()58 Toplevel::~Toplevel()
59 {
60     delete info;
61 }
62 
operator <<(QDebug debug,const Toplevel * toplevel)63 QDebug operator<<(QDebug debug, const Toplevel *toplevel)
64 {
65     QDebugStateSaver saver(debug);
66     debug.nospace();
67     if (toplevel) {
68         debug << toplevel->metaObject()->className() << '(' << static_cast<const void *>(toplevel);
69         if (toplevel->window()) {
70             debug << ", windowId=0x" << Qt::hex << toplevel->window() << Qt::dec;
71         }
72         if (const KWaylandServer::SurfaceInterface *surface = toplevel->surface()) {
73             debug << ", surface=" << surface;
74         }
75         const AbstractClient *client = qobject_cast<const AbstractClient *>(toplevel);
76         if (client) {
77             if (!client->isPopupWindow()) {
78                 debug << ", caption=" << client->caption();
79             }
80             if (client->transientFor()) {
81                 debug << ", transientFor=" << client->transientFor();
82             }
83         }
84         if (debug.verbosity() > 2) {
85             debug << ", frameGeometry=" << toplevel->frameGeometry();
86             debug << ", resourceName=" << toplevel->resourceName();
87             debug << ", resourceClass=" << toplevel->resourceClass();
88         }
89         debug << ')';
90     } else {
91         debug << "Toplevel(0x0)";
92     }
93     return debug;
94 }
95 
detectShape(xcb_window_t id)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()
copyToDeleted(Toplevel * c)106 void Toplevel::copyToDeleted(Toplevel* c)
107 {
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     bit_depth = c->bit_depth;
114     info = c->info;
115     m_client.reset(c->m_client, false);
116     ready_for_painting = c->ready_for_painting;
117     is_shape = c->is_shape;
118     effect_window = c->effect_window;
119     if (effect_window != nullptr)
120         effect_window->setWindow(this);
121     m_shadow = c->m_shadow;
122     if (m_shadow) {
123         m_shadow->setToplevel(this);
124     }
125     resource_name = c->resourceName();
126     resource_class = c->resourceClass();
127     m_clientMachine = c->m_clientMachine;
128     m_clientMachine->setParent(this);
129     m_wmClientLeader = c->wmClientLeader();
130     opaque_region = c->opaqueRegion();
131     m_output = c->m_output;
132     m_skipCloseAnimation = c->m_skipCloseAnimation;
133     m_internalFBO = c->m_internalFBO;
134     m_internalImage = c->m_internalImage;
135     m_opacity = c->m_opacity;
136     m_shapeRegionIsValid = c->m_shapeRegionIsValid;
137     m_shapeRegion = c->m_shapeRegion;
138     m_stackingOrder = c->m_stackingOrder;
139 }
140 
141 // before being deleted, remove references to everything that's now
142 // owner by Deleted
disownDataPassedToDeleted()143 void Toplevel::disownDataPassedToDeleted()
144 {
145     info = nullptr;
146 }
147 
visibleGeometry() const148 QRect Toplevel::visibleGeometry() const
149 {
150     if (const WindowItem *item = windowItem()) {
151         return item->mapToGlobal(item->boundingRect());
152     }
153     return QRect();
154 }
155 
fetchWmClientLeader() const156 Xcb::Property Toplevel::fetchWmClientLeader() const
157 {
158     return Xcb::Property(false, window(), atoms->wm_client_leader, XCB_ATOM_WINDOW, 0, 10000);
159 }
160 
readWmClientLeader(Xcb::Property & prop)161 void Toplevel::readWmClientLeader(Xcb::Property &prop)
162 {
163     m_wmClientLeader = prop.value<xcb_window_t>(window());
164 }
165 
getWmClientLeader()166 void Toplevel::getWmClientLeader()
167 {
168     auto prop = fetchWmClientLeader();
169     readWmClientLeader(prop);
170 }
171 
172 /**
173  * Returns sessionId for this client,
174  * taken either from its window or from the leader window.
175  */
sessionId() const176 QByteArray Toplevel::sessionId() const
177 {
178     QByteArray result = Xcb::StringProperty(window(), atoms->sm_client_id);
179     if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) {
180         result = Xcb::StringProperty(m_wmClientLeader, atoms->sm_client_id);
181     }
182     return result;
183 }
184 
185 /**
186  * Returns command property for this client,
187  * taken either from its window or from the leader window.
188  */
wmCommand()189 QByteArray Toplevel::wmCommand()
190 {
191     QByteArray result = Xcb::StringProperty(window(), XCB_ATOM_WM_COMMAND);
192     if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) {
193         result = Xcb::StringProperty(m_wmClientLeader, XCB_ATOM_WM_COMMAND);
194     }
195     result.replace(0, ' ');
196     return result;
197 }
198 
getWmClientMachine()199 void Toplevel::getWmClientMachine()
200 {
201     m_clientMachine->resolve(window(), wmClientLeader());
202 }
203 
204 /**
205  * Returns client machine for this client,
206  * taken either from its window or from the leader window.
207  */
wmClientMachine(bool use_localhost) const208 QByteArray Toplevel::wmClientMachine(bool use_localhost) const
209 {
210     if (!m_clientMachine) {
211         // this should never happen
212         return QByteArray();
213     }
214     if (use_localhost && m_clientMachine->isLocal()) {
215         // special name for the local machine (localhost)
216         return ClientMachine::localhost();
217     }
218     return m_clientMachine->hostName();
219 }
220 
221 /**
222  * Returns client leader window for this client.
223  * Returns the client window itself if no leader window is defined.
224  */
wmClientLeader() const225 xcb_window_t Toplevel::wmClientLeader() const
226 {
227     if (m_wmClientLeader != XCB_WINDOW_NONE) {
228         return m_wmClientLeader;
229     }
230     return window();
231 }
232 
getResourceClass()233 void Toplevel::getResourceClass()
234 {
235     if (!info) {
236         return;
237     }
238     setResourceClass(QByteArray(info->windowClassName()).toLower(), QByteArray(info->windowClassClass()).toLower());
239 }
240 
setResourceClass(const QByteArray & name,const QByteArray & className)241 void Toplevel::setResourceClass(const QByteArray &name, const QByteArray &className)
242 {
243     resource_name  = name;
244     resource_class = className;
245     Q_EMIT windowClassChanged();
246 }
247 
resourceMatch(const Toplevel * c1,const Toplevel * c2)248 bool Toplevel::resourceMatch(const Toplevel *c1, const Toplevel *c2)
249 {
250     return c1->resourceClass() == c2->resourceClass();
251 }
252 
opacity() const253 qreal Toplevel::opacity() const
254 {
255     return m_opacity;
256 }
257 
setOpacity(qreal opacity)258 void Toplevel::setOpacity(qreal opacity)
259 {
260     opacity = qBound(0.0, opacity, 1.0);
261     if (m_opacity == opacity) {
262         return;
263     }
264     const qreal oldOpacity = m_opacity;
265     m_opacity = opacity;
266     if (Compositor::compositing()) {
267         addRepaintFull();
268         Q_EMIT opacityChanged(this, oldOpacity);
269     }
270 }
271 
setupCompositing()272 bool Toplevel::setupCompositing()
273 {
274     if (!Compositor::compositing())
275         return false;
276 
277     effect_window = new EffectWindowImpl(this);
278     updateShadow();
279     Compositor::self()->scene()->addToplevel(this);
280 
281     connect(windowItem(), &WindowItem::positionChanged, this, &Toplevel::visibleGeometryChanged);
282     connect(windowItem(), &WindowItem::boundingRectChanged, this, &Toplevel::visibleGeometryChanged);
283 
284     return true;
285 }
286 
finishCompositing(ReleaseReason releaseReason)287 void Toplevel::finishCompositing(ReleaseReason releaseReason)
288 {
289     // If the X11 window has been destroyed, avoid calling XDamageDestroy.
290     if (releaseReason != ReleaseReason::Destroyed) {
291         if (SurfaceItemX11 *item = qobject_cast<SurfaceItemX11 *>(surfaceItem())) {
292             item->destroyDamage();
293         }
294     }
295     if (m_shadow && m_shadow->toplevel() == this) { // otherwise it's already passed to Deleted, don't free data
296         deleteShadow();
297     }
298     if (effect_window && effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data
299         deleteEffectWindow();
300     }
301 }
302 
addRepaint(const QRect & rect)303 void Toplevel::addRepaint(const QRect &rect)
304 {
305     addRepaint(QRegion(rect));
306 }
307 
addRepaint(int x,int y,int width,int height)308 void Toplevel::addRepaint(int x, int y, int width, int height)
309 {
310     addRepaint(QRegion(x, y, width, height));
311 }
312 
addRepaint(const QRegion & region)313 void Toplevel::addRepaint(const QRegion &region)
314 {
315     if (auto item = windowItem()) {
316         item->scheduleRepaint(region);
317     }
318 }
319 
addLayerRepaint(const QRect & rect)320 void Toplevel::addLayerRepaint(const QRect &rect)
321 {
322     addLayerRepaint(QRegion(rect));
323 }
324 
addLayerRepaint(int x,int y,int width,int height)325 void Toplevel::addLayerRepaint(int x, int y, int width, int height)
326 {
327     addLayerRepaint(QRegion(x, y, width, height));
328 }
329 
addLayerRepaint(const QRegion & region)330 void Toplevel::addLayerRepaint(const QRegion &region)
331 {
332     addRepaint(region.translated(-pos()));
333 }
334 
addRepaintFull()335 void Toplevel::addRepaintFull()
336 {
337     addLayerRepaint(visibleGeometry());
338 }
339 
addWorkspaceRepaint(int x,int y,int w,int h)340 void Toplevel::addWorkspaceRepaint(int x, int y, int w, int h)
341 {
342     addWorkspaceRepaint(QRect(x, y, w, h));
343 }
344 
addWorkspaceRepaint(const QRect & r2)345 void Toplevel::addWorkspaceRepaint(const QRect& r2)
346 {
347     if (!Compositor::compositing())
348         return;
349     Compositor::self()->addRepaint(r2);
350 }
351 
addWorkspaceRepaint(const QRegion & region)352 void Toplevel::addWorkspaceRepaint(const QRegion &region)
353 {
354     if (Compositor::compositing()) {
355         Compositor::self()->addRepaint(region);
356     }
357 }
358 
setReadyForPainting()359 void Toplevel::setReadyForPainting()
360 {
361     if (!ready_for_painting) {
362         ready_for_painting = true;
363         if (Compositor::compositing()) {
364             addRepaintFull();
365             Q_EMIT windowShown(this);
366         }
367     }
368 }
369 
deleteShadow()370 void Toplevel::deleteShadow()
371 {
372     delete m_shadow;
373     m_shadow = nullptr;
374 }
375 
deleteEffectWindow()376 void Toplevel::deleteEffectWindow()
377 {
378     delete effect_window;
379     effect_window = nullptr;
380 }
381 
checkOutput()382 void Toplevel::checkOutput()
383 {
384     setOutput(kwinApp()->platform()->outputAt(frameGeometry().center()));
385 }
386 
setupCheckOutputConnection()387 void Toplevel::setupCheckOutputConnection()
388 {
389     connect(this, &Toplevel::frameGeometryChanged, this, &Toplevel::checkOutput);
390     checkOutput();
391 }
392 
removeCheckOutputConnection()393 void Toplevel::removeCheckOutputConnection()
394 {
395     disconnect(this, &Toplevel::frameGeometryChanged, this, &Toplevel::checkOutput);
396 }
397 
screen() const398 int Toplevel::screen() const
399 {
400     return kwinApp()->platform()->enabledOutputs().indexOf(m_output);
401 }
402 
output() const403 AbstractOutput *Toplevel::output() const
404 {
405     return m_output;
406 }
407 
setOutput(AbstractOutput * output)408 void Toplevel::setOutput(AbstractOutput *output)
409 {
410     if (m_output != output) {
411         m_output = output;
412         Q_EMIT screenChanged();
413     }
414 
415     qreal newScale = m_output->scale();
416     if (newScale != m_screenScale) {
417         m_screenScale = newScale;
418         Q_EMIT screenScaleChanged();
419     }
420 }
421 
screenScale() const422 qreal Toplevel::screenScale() const
423 {
424     return m_screenScale;
425 }
426 
bufferScale() const427 qreal Toplevel::bufferScale() const
428 {
429     return surface() ? surface()->bufferScale() : 1;
430 }
431 
isOnActiveOutput() const432 bool Toplevel::isOnActiveOutput() const
433 {
434     return isOnOutput(workspace()->activeOutput());
435 }
436 
isOnOutput(AbstractOutput * output) const437 bool Toplevel::isOnOutput(AbstractOutput *output) const
438 {
439     return output->geometry().intersects(frameGeometry());
440 }
441 
shadow() const442 Shadow *Toplevel::shadow() const
443 {
444     return m_shadow;
445 }
446 
updateShadow()447 void Toplevel::updateShadow()
448 {
449     if (!Compositor::compositing()) {
450         return;
451     }
452     if (m_shadow) {
453         if (!m_shadow->updateShadow()) {
454             deleteShadow();
455         }
456         Q_EMIT shadowChanged();
457     } else {
458         m_shadow = Shadow::createShadow(this);
459         if (m_shadow) {
460             Q_EMIT shadowChanged();
461         }
462     }
463 }
464 
surfaceItem() const465 SurfaceItem *Toplevel::surfaceItem() const
466 {
467     if (effectWindow() && effectWindow()->sceneWindow()) {
468         return effectWindow()->sceneWindow()->surfaceItem();
469     }
470     return nullptr;
471 }
472 
windowItem() const473 WindowItem *Toplevel::windowItem() const
474 {
475     if (effectWindow() && effectWindow()->sceneWindow()) {
476         return effectWindow()->sceneWindow()->windowItem();
477     }
478     return nullptr;
479 }
480 
wantsShadowToBeRendered() const481 bool Toplevel::wantsShadowToBeRendered() const
482 {
483     return true;
484 }
485 
getWmOpaqueRegion()486 void Toplevel::getWmOpaqueRegion()
487 {
488     if (!info) {
489         return;
490     }
491 
492     const auto rects = info->opaqueRegion();
493     QRegion new_opaque_region;
494     for (const auto &r : rects) {
495         new_opaque_region += QRect(r.pos.x, r.pos.y, r.size.width, r.size.height);
496     }
497 
498     opaque_region = new_opaque_region;
499 }
500 
shapeRegion() const501 QRegion Toplevel::shapeRegion() const
502 {
503     if (m_shapeRegionIsValid) {
504         return m_shapeRegion;
505     }
506 
507     const QRect bufferGeometry = this->bufferGeometry();
508 
509     if (shape()) {
510         auto cookie = xcb_shape_get_rectangles_unchecked(connection(), frameId(), XCB_SHAPE_SK_BOUNDING);
511         ScopedCPointer<xcb_shape_get_rectangles_reply_t> reply(xcb_shape_get_rectangles_reply(connection(), cookie, nullptr));
512         if (!reply.isNull()) {
513             m_shapeRegion = QRegion();
514             const xcb_rectangle_t *rects = xcb_shape_get_rectangles_rectangles(reply.data());
515             const int rectCount = xcb_shape_get_rectangles_rectangles_length(reply.data());
516             for (int i = 0; i < rectCount; ++i) {
517                 m_shapeRegion += QRegion(rects[i].x, rects[i].y, rects[i].width, rects[i].height);
518             }
519             // make sure the shape is sane (X is async, maybe even XShape is broken)
520             m_shapeRegion &= QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height());
521         } else {
522             m_shapeRegion = QRegion();
523         }
524     } else {
525         m_shapeRegion = QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height());
526     }
527 
528     m_shapeRegionIsValid = true;
529     return m_shapeRegion;
530 }
531 
discardShapeRegion()532 void Toplevel::discardShapeRegion()
533 {
534     m_shapeRegionIsValid = false;
535     m_shapeRegion = QRegion();
536 }
537 
isClient() const538 bool Toplevel::isClient() const
539 {
540     return false;
541 }
542 
isDeleted() const543 bool Toplevel::isDeleted() const
544 {
545     return false;
546 }
547 
isOnCurrentActivity() const548 bool Toplevel::isOnCurrentActivity() const
549 {
550 #ifdef KWIN_BUILD_ACTIVITIES
551     if (!Activities::self()) {
552         return true;
553     }
554     return isOnActivity(Activities::self()->current());
555 #else
556     return true;
557 #endif
558 }
559 
elevate(bool elevate)560 void Toplevel::elevate(bool elevate)
561 {
562     if (!effectWindow()) {
563         return;
564     }
565     effectWindow()->elevate(elevate);
566     addWorkspaceRepaint(visibleGeometry());
567 }
568 
pid() const569 pid_t Toplevel::pid() const
570 {
571     if (!info) {
572         return -1;
573     }
574     return info->pid();
575 }
576 
frameId() const577 xcb_window_t Toplevel::frameId() const
578 {
579     return m_client;
580 }
581 
fetchSkipCloseAnimation() const582 Xcb::Property Toplevel::fetchSkipCloseAnimation() const
583 {
584     return Xcb::Property(false, window(), atoms->kde_skip_close_animation, XCB_ATOM_CARDINAL, 0, 1);
585 }
586 
readSkipCloseAnimation(Xcb::Property & property)587 void Toplevel::readSkipCloseAnimation(Xcb::Property &property)
588 {
589     setSkipCloseAnimation(property.toBool());
590 }
591 
getSkipCloseAnimation()592 void Toplevel::getSkipCloseAnimation()
593 {
594     Xcb::Property property = fetchSkipCloseAnimation();
595     readSkipCloseAnimation(property);
596 }
597 
skipsCloseAnimation() const598 bool Toplevel::skipsCloseAnimation() const
599 {
600     return m_skipCloseAnimation;
601 }
602 
setSkipCloseAnimation(bool set)603 void Toplevel::setSkipCloseAnimation(bool set)
604 {
605     if (set == m_skipCloseAnimation) {
606         return;
607     }
608     m_skipCloseAnimation = set;
609     Q_EMIT skipCloseAnimationChanged();
610 }
611 
surface() const612 KWaylandServer::SurfaceInterface *Toplevel::surface() const
613 {
614     return m_surface;
615 }
616 
setSurface(KWaylandServer::SurfaceInterface * surface)617 void Toplevel::setSurface(KWaylandServer::SurfaceInterface *surface)
618 {
619     if (m_surface == surface) {
620         return;
621     }
622     m_surface = surface;
623     m_pendingSurfaceId = 0;
624     Q_EMIT surfaceChanged();
625 }
626 
stackingOrder() const627 int Toplevel::stackingOrder() const
628 {
629     return m_stackingOrder;
630 }
631 
setStackingOrder(int order)632 void Toplevel::setStackingOrder(int order)
633 {
634     if (m_stackingOrder != order) {
635         m_stackingOrder = order;
636         Q_EMIT stackingOrderChanged();
637     }
638 }
639 
windowRole() const640 QByteArray Toplevel::windowRole() const
641 {
642     if (!info) {
643         return {};
644     }
645     return QByteArray(info->windowRole());
646 }
647 
setDepth(int depth)648 void Toplevel::setDepth(int depth)
649 {
650     if (bit_depth == depth) {
651         return;
652     }
653     const bool oldAlpha = hasAlpha();
654     bit_depth = depth;
655     if (oldAlpha != hasAlpha()) {
656         Q_EMIT hasAlphaChanged();
657     }
658 }
659 
inputShape() const660 QRegion Toplevel::inputShape() const
661 {
662     if (m_surface) {
663         return m_surface->input();
664     } else {
665         // TODO: maybe also for X11?
666         return QRegion();
667     }
668 }
669 
inputTransformation() const670 QMatrix4x4 Toplevel::inputTransformation() const
671 {
672     QMatrix4x4 m;
673     m.translate(-x(), -y());
674     return m;
675 }
676 
hitTest(const QPoint & point) const677 bool Toplevel::hitTest(const QPoint &point) const
678 {
679     if (m_surface && m_surface->isMapped()) {
680         return m_surface->inputSurfaceAt(mapToLocal(point));
681     }
682     return inputGeometry().contains(point);
683 }
684 
mapToFrame(const QPoint & point) const685 QPoint Toplevel::mapToFrame(const QPoint &point) const
686 {
687     return point - frameGeometry().topLeft();
688 }
689 
mapToLocal(const QPoint & point) const690 QPoint Toplevel::mapToLocal(const QPoint &point) const
691 {
692     return point - bufferGeometry().topLeft();
693 }
694 
mapToLocal(const QPointF & point) const695 QPointF Toplevel::mapToLocal(const QPointF &point) const
696 {
697     return point - bufferGeometry().topLeft();
698 }
699 
mapFromLocal(const QPointF & point) const700 QPointF Toplevel::mapFromLocal(const QPointF &point) const
701 {
702     return point + bufferGeometry().topLeft();
703 }
704 
inputGeometry() const705 QRect Toplevel::inputGeometry() const
706 {
707     return frameGeometry();
708 }
709 
isLocalhost() const710 bool Toplevel::isLocalhost() const
711 {
712     if (!m_clientMachine) {
713         return true;
714     }
715     return m_clientMachine->isLocal();
716 }
717 
frameMargins() const718 QMargins Toplevel::frameMargins() const
719 {
720     return QMargins();
721 }
722 
isOnDesktop(VirtualDesktop * desktop) const723 bool Toplevel::isOnDesktop(VirtualDesktop *desktop) const
724 {
725     return isOnAllDesktops() || desktops().contains(desktop);
726 }
727 
isOnDesktop(int d) const728 bool Toplevel::isOnDesktop(int d) const
729 {
730     return isOnDesktop(VirtualDesktopManager::self()->desktopForX11Id(d));
731 }
732 
isOnCurrentDesktop() const733 bool Toplevel::isOnCurrentDesktop() const
734 {
735     return isOnDesktop(VirtualDesktopManager::self()->currentDesktop());
736 }
737 
738 } // namespace
739 
740