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 ®ion)
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 ®ion)
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 ®ion)
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