1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 2006 Lubos Lunak <l.lunak@kde.org>
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with this program. If not, see <http://www.gnu.org/licenses/>.
19*********************************************************************/
20
21/*
22 The base class for compositing, implementing shared functionality
23 between the OpenGL and XRender backends.
24
25 Design:
26
27 When compositing is turned on, XComposite extension is used to redirect
28 drawing of windows to pixmaps and XDamage extension is used to get informed
29 about damage (changes) to window contents. This code is mostly in composite.cpp .
30
31 Compositor::performCompositing() starts one painting pass. Painting is done
32 by painting the screen, which in turn paints every window. Painting can be affected
33 using effects, which are chained. E.g. painting a screen means that actually
34 paintScreen() of the first effect is called, which possibly does modifications
35 and calls next effect's paintScreen() and so on, until Scene::finalPaintScreen()
36 is called.
37
38 There are 3 phases of every paint (not necessarily done together):
39 The pre-paint phase, the paint phase and the post-paint phase.
40
41 The pre-paint phase is used to find out about how the painting will be actually
42 done (i.e. what the effects will do). For example when only a part of the screen
43 needs to be updated and no effect will do any transformation it is possible to use
44 an optimized paint function. How the painting will be done is controlled
45 by the mask argument, see PAINT_WINDOW_* and PAINT_SCREEN_* flags in scene.h .
46 For example an effect that decides to paint a normal windows as translucent
47 will need to modify the mask in its prePaintWindow() to include
48 the PAINT_WINDOW_TRANSLUCENT flag. The paintWindow() function will then get
49 the mask with this flag turned on and will also paint using transparency.
50
51 The paint pass does the actual painting, based on the information collected
52 using the pre-paint pass. After running through the effects' paintScreen()
53 either paintGenericScreen() or optimized paintSimpleScreen() are called.
54 Those call paintWindow() on windows (not necessarily all), possibly using
55 clipping to optimize performance and calling paintWindow() first with only
56 PAINT_WINDOW_OPAQUE to paint the opaque parts and then later
57 with PAINT_WINDOW_TRANSLUCENT to paint the transparent parts. Function
58 paintWindow() again goes through effects' paintWindow() until
59 finalPaintWindow() is called, which calls the window's performPaint() to
60 do the actual painting.
61
62 The post-paint can be used for cleanups and is also used for scheduling
63 repaints during the next painting pass for animations. Effects wanting to
64 repaint certain parts can manually damage them during post-paint and repaint
65 of these parts will be done during the next paint pass.
66
67*/
68
69#include "scene.h"
70
71#include <X11/extensions/shape.h>
72
73#include <QGraphicsScene>
74#include <QGraphicsView>
75#include <QVector2D>
76
77#include "client.h"
78#include "decorations.h"
79#include "deleted.h"
80#include "effects.h"
81#include "overlaywindow.h"
82#include "shadow.h"
83
84#include "thumbnailitem.h"
85#include "workspace.h"
86
87namespace KWin
88{
89
90//****************************************
91// Scene
92//****************************************
93
94Scene::Scene(Workspace* ws)
95 : QObject(ws)
96 , wspace(ws)
97{
98 last_time.invalidate(); // Initialize the timer
99 connect(Workspace::self(), SIGNAL(deletedRemoved(KWin::Deleted*)), SLOT(windowDeleted(KWin::Deleted*)));
100}
101
102Scene::~Scene()
103{
104}
105
106// returns mask and possibly modified region
107void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint,
108 QRegion *updateRegion, QRegion *validRegion)
109{
110 const QRegion displayRegion(0, 0, displayWidth(), displayHeight());
111 *mask = (damage == displayRegion) ? 0 : PAINT_SCREEN_REGION;
112
113 updateTimeDiff();
114 // preparation step
115 static_cast<EffectsHandlerImpl*>(effects)->startPaint();
116
117 QRegion region = damage;
118
119 ScreenPrePaintData pdata;
120 pdata.mask = *mask;
121 pdata.paint = region;
122
123 effects->prePaintScreen(pdata, time_diff);
124 *mask = pdata.mask;
125 region = pdata.paint;
126
127 if (*mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) {
128 // Region painting is not possible with transformations,
129 // because screen damage doesn't match transformed positions.
130 *mask &= ~PAINT_SCREEN_REGION;
131 region = infiniteRegion();
132 } else if (*mask & PAINT_SCREEN_REGION) {
133 // make sure not to go outside visible screen
134 region &= displayRegion;
135 } else {
136 // whole screen, not transformed, force region to be full
137 region = displayRegion;
138 }
139
140 painted_region = region;
141 repaint_region = repaint;
142
143 if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) {
144 paintBackground(region);
145 }
146
147 ScreenPaintData data;
148 effects->paintScreen(*mask, region, data);
149
150 foreach (Window *w, stacking_order) {
151 effects->postPaintWindow(effectWindow(w));
152 }
153
154 effects->postPaintScreen();
155
156 // make sure not to go outside of the screen area
157 *updateRegion = damaged_region;
158 *validRegion = (region | painted_region) & displayRegion;
159
160 repaint_region = QRegion();
161 damaged_region = QRegion();
162
163 // make sure all clipping is restored
164 Q_ASSERT(!PaintClipper::clip());
165}
166
167// Compute time since the last painting pass.
168void Scene::updateTimeDiff()
169{
170 if (!last_time.isValid()) {
171 // Painting has been idle (optimized out) for some time,
172 // which means time_diff would be huge and would break animations.
173 // Simply set it to one (zero would mean no change at all and could
174 // cause problems).
175 time_diff = 1;
176 last_time.start();
177 } else
178
179 time_diff = last_time.restart();
180
181 if (time_diff < 0) // check time rollback
182 time_diff = 1;
183}
184
185// Painting pass is optimized away.
186void Scene::idle()
187{
188 // Don't break time since last paint for the next pass.
189 last_time.invalidate();
190}
191
192// the function that'll be eventually called by paintScreen() above
193void Scene::finalPaintScreen(int mask, QRegion region, ScreenPaintData& data)
194{
195 if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS))
196 paintGenericScreen(mask, data);
197 else
198 paintSimpleScreen(mask, region);
199}
200
201// The generic painting code that can handle even transformations.
202// It simply paints bottom-to-top.
203void Scene::paintGenericScreen(int orig_mask, ScreenPaintData)
204{
205 if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) {
206 paintBackground(infiniteRegion());
207 }
208 QList< Phase2Data > phase2;
209 foreach (Window * w, stacking_order) { // bottom to top
210 Toplevel* topw = w->window();
211
212 // Reset the repaint_region.
213 // This has to be done here because many effects schedule a repaint for
214 // the next frame within Effects::prePaintWindow.
215 topw->resetRepaints();
216
217 WindowPrePaintData data;
218 data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT);
219 w->resetPaintingEnabled();
220 data.paint = infiniteRegion(); // no clipping, so doesn't really matter
221 data.clip = QRegion();
222 data.quads = w->buildQuads();
223 // preparation step
224 effects->prePaintWindow(effectWindow(w), data, time_diff);
225#ifndef NDEBUG
226 if (data.quads.isTransformed()) {
227 kFatal(1212) << "Pre-paint calls are not allowed to transform quads!" ;
228 }
229#endif
230 if (!w->isPaintingEnabled()) {
231 continue;
232 }
233 phase2.append(Phase2Data(w, infiniteRegion(), data.clip, data.mask, data.quads));
234 // transformations require window pixmap
235 w->suspendUnredirect(data.mask
236 & (PAINT_WINDOW_TRANSLUCENT | PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED));
237 }
238
239 foreach (const Phase2Data & d, phase2) {
240 paintWindow(d.window, d.mask, d.region, d.quads);
241 }
242
243 damaged_region = QRegion(0, 0, displayWidth(), displayHeight());
244}
245
246// The optimized case without any transformations at all.
247// It can paint only the requested region and can use clipping
248// to reduce painting and improve performance.
249void Scene::paintSimpleScreen(int orig_mask, QRegion region)
250{
251 assert((orig_mask & (PAINT_SCREEN_TRANSFORMED
252 | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) == 0);
253 QList< QPair< Window*, Phase2Data > > phase2data;
254
255 QRegion dirtyArea = region;
256 bool opaqueFullscreen(false);
257 for (int i = 0; // do prePaintWindow bottom to top
258 i < stacking_order.count();
259 ++i) {
260 Window* w = stacking_order[ i ];
261 Toplevel* topw = w->window();
262 WindowPrePaintData data;
263 data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT);
264 w->resetPaintingEnabled();
265 data.paint = region;
266 data.paint |= topw->repaints();
267 data.paint |= topw->decorationPendingRegion();
268
269 // Reset the repaint_region.
270 // This has to be done here because many effects schedule a repaint for
271 // the next frame within Effects::prePaintWindow.
272 topw->resetRepaints();
273
274 // Clip out the decoration for opaque windows; the decoration is drawn in the second pass
275 opaqueFullscreen = false; // TODO: do we care about unmanged windows here (maybe input windows?)
276 if (w->isOpaque()) {
277 Client *c = NULL;
278 if (topw->isClient()) {
279 c = static_cast<Client*>(topw);
280 opaqueFullscreen = c->isFullScreen();
281 }
282 // the window is fully opaque
283 if (c && c->decorationHasAlpha()) {
284 // decoration uses alpha channel, so we may not exclude it in clipping
285 data.clip = w->clientShape().translated(w->x(), w->y());
286 } else {
287 // decoration is fully opaque
288 if (c && c->isShade()) {
289 data.clip = QRegion();
290 } else {
291 data.clip = w->shape().translated(w->x(), w->y());
292 }
293 }
294 } else if (topw->hasAlpha() && topw->opacity() == 1.0) {
295 // the window is partially opaque
296 data.clip = (w->clientShape() & topw->opaqueRegion().translated(topw->clientPos())).translated(w->x(), w->y());
297 } else {
298 data.clip = QRegion();
299 }
300 data.quads = w->buildQuads();
301 // preparation step
302 effects->prePaintWindow(effectWindow(w), data, time_diff);
303#ifndef NDEBUG
304 if (data.quads.isTransformed()) {
305 kFatal(1212) << "Pre-paint calls are not allowed to transform quads!" ;
306 }
307#endif
308 if (!w->isPaintingEnabled()) {
309 w->suspendUnredirect(true);
310 continue;
311 }
312 dirtyArea |= data.paint;
313 // Schedule the window for painting
314 phase2data.append(QPair< Window*, Phase2Data >(w,Phase2Data(w, data.paint, data.clip,
315 data.mask, data.quads)));
316 // no transformations, but translucency requires window pixmap
317 w->suspendUnredirect(data.mask & PAINT_WINDOW_TRANSLUCENT);
318 }
319
320 // Save the part of the repaint region that's exclusively rendered to
321 // bring a reused back buffer up to date. Then union the dirty region
322 // with the repaint region.
323 const QRegion repaintClip = repaint_region - dirtyArea;
324 dirtyArea |= repaint_region;
325
326 const QRegion displayRegion(0, 0, displayWidth(), displayHeight());
327 bool fullRepaint(dirtyArea == displayRegion); // spare some expensive region operations
328 if (!fullRepaint) {
329 extendPaintRegion(dirtyArea, opaqueFullscreen);
330 fullRepaint = (dirtyArea == displayRegion);
331 }
332
333 QRegion allclips, upperTranslucentDamage;
334 upperTranslucentDamage = repaint_region;
335
336 // This is the occlusion culling pass
337 for (int i = phase2data.count() - 1; i >= 0; --i) {
338 QPair< Window*, Phase2Data > *entry = &phase2data[i];
339 Phase2Data *data = &entry->second;
340
341 if (fullRepaint)
342 data->region = displayRegion;
343 else
344 data->region |= upperTranslucentDamage;
345
346 // subtract the parts which will possibly been drawn as part of
347 // a higher opaque window
348 data->region -= allclips;
349
350 // Here we rely on WindowPrePaintData::setTranslucent() to remove
351 // the clip if needed.
352 if (!data->clip.isEmpty() && !(data->mask & PAINT_WINDOW_TRANSFORMED)) {
353 // clip away the opaque regions for all windows below this one
354 allclips |= data->clip;
355 // extend the translucent damage for windows below this by remaining (translucent) regions
356 if (!fullRepaint)
357 upperTranslucentDamage |= data->region - data->clip;
358 } else if (!fullRepaint) {
359 upperTranslucentDamage |= data->region;
360 }
361 }
362
363 QRegion paintedArea;
364 // Fill any areas of the root window not covered by opaque windows
365 if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) {
366 paintedArea = dirtyArea - allclips;
367 paintBackground(paintedArea);
368 }
369
370 // Now walk the list bottom to top and draw the windows.
371 for (int i = 0; i < phase2data.count(); ++i) {
372 Phase2Data *data = &phase2data[i].second;
373
374 // add all regions which have been drawn so far
375 paintedArea |= data->region;
376 data->region = paintedArea;
377
378 paintWindow(data->window, data->mask, data->region, data->quads);
379 }
380
381 if (fullRepaint) {
382 painted_region = displayRegion;
383 damaged_region = displayRegion;
384 } else {
385 painted_region |= paintedArea;
386
387 // Clip the repainted region from the damaged region.
388 // It's important that we don't add the union of the damaged region
389 // and the repainted region to the damage history. Otherwise the
390 // repaint region will grow with every frame until it eventually
391 // covers the whole back buffer, at which point we're always doing
392 // full repaints.
393 damaged_region = paintedArea - repaintClip;
394 }
395}
396
397static Scene::Window *s_recursionCheck = NULL;
398
399void Scene::paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads)
400{
401 // no painting outside visible screen (and no transformations)
402 region &= QRect(0, 0, displayWidth(), displayHeight());
403 if (region.isEmpty()) // completely clipped
404 return;
405 if (w->window()->isDeleted() && w->window()->skipsCloseAnimation()) {
406 // should not get painted
407 return;
408 }
409
410 if (s_recursionCheck == w) {
411 return;
412 }
413
414 WindowPaintData data(w->window()->effectWindow());
415 data.quads = quads;
416 effects->paintWindow(effectWindow(w), mask, region, data);
417 // paint thumbnails on top of window
418 paintWindowThumbnails(w, region, data.opacity(), data.brightness(), data.saturation());
419 // and desktop thumbnails
420 paintDesktopThumbnails(w);
421}
422
423void Scene::paintWindowThumbnails(Scene::Window *w, QRegion region, qreal opacity, qreal brightness, qreal saturation)
424{
425 EffectWindowImpl *wImpl = static_cast<EffectWindowImpl*>(effectWindow(w));
426 for (QHash<WindowThumbnailItem*, QWeakPointer<EffectWindowImpl> >::const_iterator it = wImpl->thumbnails().constBegin();
427 it != wImpl->thumbnails().constEnd();
428 ++it) {
429 if (it.value().isNull()) {
430 continue;
431 }
432 WindowThumbnailItem *item = it.key();
433 if (!item->isVisible()) {
434 continue;
435 }
436 EffectWindowImpl *thumb = it.value().data();
437 WindowPaintData thumbData(thumb);
438 thumbData.setOpacity(opacity);
439 thumbData.setBrightness(brightness * item->brightness());
440 thumbData.setSaturation(saturation * item->saturation());
441
442 const QRect visualThumbRect(thumb->expandedGeometry());
443
444 QSizeF size = QSizeF(visualThumbRect.size());
445 size.scale(QSizeF(item->width(), item->height()), Qt::KeepAspectRatio);
446 if (size.width() > visualThumbRect.width() || size.height() > visualThumbRect.height()) {
447 size = QSizeF(visualThumbRect.size());
448 }
449 thumbData.setXScale(size.width() / static_cast<qreal>(visualThumbRect.width()));
450 thumbData.setYScale(size.height() / static_cast<qreal>(visualThumbRect.height()));
451 // it can happen in the init/closing phase of the tabbox
452 // that the corresponding QGraphicsScene is not available
453 if (item->scene() == 0) {
454 continue;
455 }
456
457 QGraphicsView* declview = findViewForThumbnailItem(item, w);
458 if (declview == 0) {
459 continue;
460 }
461 QPoint viewPos = findOffsetInWindow(declview, w->window()->window());
462 const QPoint point = viewPos + declview->mapFromScene(item->scenePos());
463 qreal x = point.x() + w->x() + (item->width() - size.width())/2;
464 qreal y = point.y() + w->y() + (item->height() - size.height()) / 2;
465 x -= thumb->x();
466 y -= thumb->y();
467 // compensate shadow topleft padding
468 x += (thumb->x()-visualThumbRect.x())*thumbData.xScale();
469 y += (thumb->y()-visualThumbRect.y())*thumbData.yScale();
470 thumbData.setXTranslation(x);
471 thumbData.setYTranslation(y);
472 int thumbMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS;
473 if (thumbData.opacity() == 1.0) {
474 thumbMask |= PAINT_WINDOW_OPAQUE;
475 } else {
476 thumbMask |= PAINT_WINDOW_TRANSLUCENT;
477 }
478 QRegion clippingRegion = region;
479 clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height());
480 QPainterPath path = item->clipPath();
481 if (!path.isEmpty()) {
482 // here we assume that the clippath consists of a single rectangle
483 const QPolygonF sceneBounds = item->mapToScene(path.boundingRect());
484 const QRect viewBounds = declview->mapFromScene(sceneBounds).boundingRect();
485 // shrinking the rect due to rounding errors
486 clippingRegion &= viewBounds.adjusted(0,0,-1,-1).translated(viewPos + w->pos());
487 }
488 effects->drawWindow(thumb, thumbMask, clippingRegion, thumbData);
489 }
490}
491
492void Scene::paintDesktopThumbnails(Scene::Window *w)
493{
494 EffectWindowImpl *wImpl = static_cast<EffectWindowImpl*>(effectWindow(w));
495 for (QList<DesktopThumbnailItem*>::const_iterator it = wImpl->desktopThumbnails().constBegin();
496 it != wImpl->desktopThumbnails().constEnd();
497 ++it) {
498 DesktopThumbnailItem *item = *it;
499 if (!item->isVisible()) {
500 continue;
501 }
502 // it can happen in the init/closing phase of the tabbox
503 // that the corresponding QGraphicsScene is not available
504 if (item->scene() == 0) {
505 continue;
506 }
507 QGraphicsView* declview = findViewForThumbnailItem(item, w);
508 if (declview == 0) {
509 continue;
510 }
511 QPoint viewPos = findOffsetInWindow(declview, w->window()->window());
512 s_recursionCheck = w;
513
514 ScreenPaintData data;
515 QSize size = QSize(displayWidth(), displayHeight());
516
517 size.scale(item->width(), item->height(), Qt::KeepAspectRatio);
518 data *= QVector2D(size.width() / double(displayWidth()),
519 size.height() / double(displayHeight()));
520 const QPoint point = viewPos + declview->mapFromScene(item->scenePos());
521 const qreal x = point.x() + w->x() + (item->width() - size.width())/2;
522 const qreal y = point.y() + w->y() + (item->height() - size.height()) / 2;
523 const QRect region = QRect(x, y, item->width(), item->height());
524 QRegion clippingRegion = region;
525 clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height());
526 QPainterPath path = item->clipPath();
527 if (!path.isEmpty()) {
528 // here we assume that the clippath consists of a single rectangle
529 const QPolygonF sceneBounds = item->mapToScene(path.boundingRect());
530 const QRect viewBounds = declview->mapFromScene(sceneBounds).boundingRect();
531 // shrinking the rect due to rounding errors
532 clippingRegion &= viewBounds.adjusted(0,0,-1,-1).translated(viewPos + w->pos());
533 }
534 data += QPointF(x, y);
535 const int desktopMask = PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST;
536 paintDesktop(item->desktop(), desktopMask, clippingRegion, data);
537 s_recursionCheck = NULL;
538 }
539}
540
541QGraphicsView *Scene::findViewForThumbnailItem(AbstractThumbnailItem *item, Scene::Window *w)
542{
543 // in principle there could be more than one QGraphicsView per QGraphicsScene,
544 // although TabBox does not make use of it so far
545 QList<QGraphicsView*> views = item->scene()->views();
546 foreach (QGraphicsView* view, views) {
547 if (view->winId() == w->window()->window()) {
548 return view;
549 }
550 QWidget *parent = view;
551 while ((parent = parent->parentWidget())) {
552 // if the graphicsview is not the topmost widget we try to go up to the
553 // toplevel widget and check whether that is the window we are looking for.
554 if (parent->winId() == w->window()->window()) {
555 return view;
556 }
557 }
558 }
559 return NULL;
560}
561
562QPoint Scene::findOffsetInWindow(QWidget *view, xcb_window_t idOfTopmostWindow)
563{
564 if (view->winId() == idOfTopmostWindow) {
565 return QPoint();
566 }
567 QWidget *parent = view;
568 while ((parent = parent->parentWidget())) {
569 if (parent->winId() == idOfTopmostWindow) {
570 return view->mapTo(parent, QPoint());
571 }
572 }
573 return QPoint();
574}
575
576void Scene::paintDesktop(int desktop, int mask, const QRegion &region, ScreenPaintData &data)
577{
578 static_cast<EffectsHandlerImpl*>(effects)->paintDesktop(desktop, mask, region, data);
579}
580
581// the function that'll be eventually called by paintWindow() above
582void Scene::finalPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data)
583{
584 effects->drawWindow(w, mask, region, data);
585}
586
587// will be eventually called from drawWindow()
588void Scene::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data)
589{
590 w->sceneWindow()->performPaint(mask, region, data);
591}
592
593void Scene::extendPaintRegion(QRegion &region, bool opaqueFullscreen)
594{
595 Q_UNUSED(region);
596 Q_UNUSED(opaqueFullscreen);
597}
598
599bool Scene::blocksForRetrace() const
600{
601 return false;
602}
603
604bool Scene::syncsToVBlank() const
605{
606 return false;
607}
608
609void Scene::screenGeometryChanged(const QSize &size)
610{
611 overlayWindow()->resize(size);
612}
613
614//****************************************
615// Scene::Window
616//****************************************
617
618Scene::Window::Window(Toplevel * c)
619 : toplevel(c)
620 , filter(ImageFilterFast)
621 , m_shadow(NULL)
622 , m_currentPixmap()
623 , m_previousPixmap()
624 , m_referencePixmapCounter(0)
625 , disable_painting(0)
626 , shape_valid(false)
627 , cached_quad_list(NULL)
628{
629}
630
631Scene::Window::~Window()
632{
633 delete cached_quad_list;
634 delete m_shadow;
635}
636
637void Scene::Window::referencePreviousPixmap()
638{
639 if (!m_previousPixmap.isNull() && m_previousPixmap->isDiscarded()) {
640 m_referencePixmapCounter++;
641 }
642}
643
644void Scene::Window::unreferencePreviousPixmap()
645{
646 if (m_previousPixmap.isNull() || !m_previousPixmap->isDiscarded()) {
647 return;
648 }
649 m_referencePixmapCounter--;
650 if (m_referencePixmapCounter == 0) {
651 m_previousPixmap.reset();
652 }
653}
654
655void Scene::Window::pixmapDiscarded()
656{
657 if (!m_currentPixmap.isNull() && m_currentPixmap->isValid()) {
658 m_previousPixmap.reset(m_currentPixmap.take());
659 m_previousPixmap->markAsDiscarded();
660 }
661}
662
663void Scene::Window::discardShape()
664{
665 // it is created on-demand and cached, simply
666 // reset the flag
667 shape_valid = false;
668 delete cached_quad_list;
669 cached_quad_list = NULL;
670}
671
672// Find out the shape of the window using the XShape extension
673// or if shape is not set then simply it's the window geometry.
674const QRegion &Scene::Window::shape() const
675{
676 if (!shape_valid) {
677 Client* c = dynamic_cast< Client* >(toplevel);
678 if (toplevel->shape() || (c != NULL && !c->mask().isEmpty())) {
679 int count, order;
680 XRectangle* rects = XShapeGetRectangles(display(), toplevel->frameId(),
681 ShapeBounding, &count, &order);
682 if (rects) {
683 shape_region = QRegion();
684 for (int i = 0;
685 i < count;
686 ++i)
687 shape_region += QRegion(rects[ i ].x, rects[ i ].y,
688 rects[ i ].width, rects[ i ].height);
689 XFree(rects);
690 // make sure the shape is sane (X is async, maybe even XShape is broken)
691 shape_region &= QRegion(0, 0, width(), height());
692 } else
693 shape_region = QRegion();
694 } else
695 shape_region = QRegion(0, 0, width(), height());
696 shape_valid = true;
697 }
698 return shape_region;
699}
700
701QRegion Scene::Window::clientShape() const
702{
703 if (toplevel->isClient()) {
704 Client *c = static_cast< Client * > (toplevel);
705 if (c->isShade())
706 return QRegion();
707 }
708
709 // TODO: cache
710 const QRegion r = shape() & QRect(toplevel->clientPos(), toplevel->clientSize());
711 return r.isEmpty() ? QRegion() : r;
712}
713
714bool Scene::Window::isVisible() const
715{
716 if (toplevel->isDeleted())
717 return false;
718 if (!toplevel->isOnCurrentDesktop())
719 return false;
720 if (!toplevel->isOnCurrentActivity())
721 return false;
722 if (toplevel->isClient())
723 return (static_cast< Client *>(toplevel))->isShown(true);
724 return true; // Unmanaged is always visible
725}
726
727bool Scene::Window::isOpaque() const
728{
729 return toplevel->opacity() == 1.0 && !toplevel->hasAlpha();
730}
731
732bool Scene::Window::isPaintingEnabled() const
733{
734 return !disable_painting;
735}
736
737void Scene::Window::resetPaintingEnabled()
738{
739 disable_painting = 0;
740 if (toplevel->isDeleted())
741 disable_painting |= PAINT_DISABLED_BY_DELETE;
742 if (static_cast<EffectsHandlerImpl*>(effects)->isDesktopRendering()) {
743 if (!toplevel->isOnDesktop(static_cast<EffectsHandlerImpl*>(effects)->currentRenderedDesktop())) {
744 disable_painting |= PAINT_DISABLED_BY_DESKTOP;
745 }
746 } else {
747 if (!toplevel->isOnCurrentDesktop())
748 disable_painting |= PAINT_DISABLED_BY_DESKTOP;
749 }
750 if (!toplevel->isOnCurrentActivity())
751 disable_painting |= PAINT_DISABLED_BY_ACTIVITY;
752 if (toplevel->isClient()) {
753 Client *c = static_cast<Client*>(toplevel);
754 if (c->isMinimized())
755 disable_painting |= PAINT_DISABLED_BY_MINIMIZE;
756 if (c->tabGroup() && c != c->tabGroup()->current())
757 disable_painting |= PAINT_DISABLED_BY_TAB_GROUP;
758 else if (c->isHiddenInternal())
759 disable_painting |= PAINT_DISABLED;
760 }
761}
762
763void Scene::Window::enablePainting(int reason)
764{
765 disable_painting &= ~reason;
766}
767
768void Scene::Window::disablePainting(int reason)
769{
770 disable_painting |= reason;
771}
772
773WindowQuadList Scene::Window::buildQuads(bool force) const
774{
775 if (cached_quad_list != NULL && !force)
776 return *cached_quad_list;
777 WindowQuadList ret;
778 if (toplevel->clientPos() == QPoint(0, 0) && toplevel->clientSize() == toplevel->decorationRect().size())
779 ret = makeQuads(WindowQuadContents, shape()); // has no decoration
780 else {
781 Client *client = dynamic_cast<Client*>(toplevel);
782 QRegion contents = clientShape();
783 QRegion center = toplevel->transparentRect();
784 QRegion decoration = (client && decorationPlugin()->hasAlpha() ?
785 QRegion(client->decorationRect()) : shape()) - center;
786 ret = makeQuads(WindowQuadContents, contents);
787
788 QRect rects[4];
789 bool isShadedClient = false;
790
791 if (client) {
792 client->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3], Client::WindowRelative);
793 isShadedClient = client->isShade() || center.isEmpty();
794 }
795
796 if (isShadedClient) {
797 const QRect bounding = rects[0] | rects[1] | rects[2] | rects[3];
798 ret += makeDecorationQuads(rects, bounding);
799 } else {
800 ret += makeDecorationQuads(rects, decoration);
801 }
802
803 }
804 if (m_shadow) {
805 ret << m_shadow->shadowQuads();
806 }
807 effects->buildQuads(toplevel->effectWindow(), ret);
808 cached_quad_list = new WindowQuadList(ret);
809 return ret;
810}
811
812WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QRegion &region) const
813{
814 WindowQuadList list;
815
816 const QPoint offsets[4] = {
817 QPoint(-rects[0].x(), -rects[0].y()), // Left
818 QPoint(-rects[1].x(), -rects[1].y()), // Top
819 QPoint(-rects[2].x() + rects[0].width(), -rects[2].y()), // Right
820 QPoint(-rects[3].x(), -rects[3].y() + rects[1].height()) // Bottom
821 };
822
823 const WindowQuadType types[4] = {
824 WindowQuadDecorationLeftRight, // Left
825 WindowQuadDecorationTopBottom, // Top
826 WindowQuadDecorationLeftRight, // Right
827 WindowQuadDecorationTopBottom // Bottom
828 };
829
830 for (int i = 0; i < 4; i++) {
831 foreach (const QRect &r, (region & rects[i]).rects()) {
832 if (!r.isValid())
833 continue;
834
835 const int x0 = r.x();
836 const int y0 = r.y();
837 const int x1 = r.x() + r.width();
838 const int y1 = r.y() + r.height();
839
840 const int u0 = x0 + offsets[i].x();
841 const int v0 = y0 + offsets[i].y();
842 const int u1 = x1 + offsets[i].x();
843 const int v1 = y1 + offsets[i].y();
844
845 WindowQuad quad(types[i]);
846 quad[0] = WindowVertex(x0, y0, u0, v0); // Top-left
847 quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right
848 quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right
849 quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left
850 list.append(quad);
851 }
852 }
853
854 return list;
855}
856
857WindowQuadList Scene::Window::makeQuads(WindowQuadType type, const QRegion& reg) const
858{
859 WindowQuadList ret;
860 foreach (const QRect & r, reg.rects()) {
861 WindowQuad quad(type);
862 // TODO asi mam spatne pravy dolni roh - bud tady, nebo v jinych castech
863 quad[ 0 ] = WindowVertex(r.x(), r.y(), r.x(), r.y());
864 quad[ 1 ] = WindowVertex(r.x() + r.width(), r.y(), r.x() + r.width(), r.y());
865 quad[ 2 ] = WindowVertex(r.x() + r.width(), r.y() + r.height(), r.x() + r.width(), r.y() + r.height());
866 quad[ 3 ] = WindowVertex(r.x(), r.y() + r.height(), r.x(), r.y() + r.height());
867 ret.append(quad);
868 }
869 return ret;
870}
871
872//****************************************
873// WindowPixmap
874//****************************************
875WindowPixmap::WindowPixmap(Scene::Window *window)
876 : m_window(window)
877 , m_pixmap(XCB_PIXMAP_NONE)
878 , m_discarded(false)
879{
880}
881
882WindowPixmap::~WindowPixmap()
883{
884 if (isValid()) {
885 xcb_free_pixmap(connection(), m_pixmap);
886 }
887}
888
889void WindowPixmap::create()
890{
891 if (isValid() || toplevel()->isDeleted()) {
892 return;
893 }
894 XServerGrabber grabber();
895 xcb_pixmap_t pix = xcb_generate_id(connection());
896 xcb_void_cookie_t namePixmapCookie = xcb_composite_name_window_pixmap_checked(connection(), toplevel()->frameId(), pix);
897 Xcb::WindowAttributes windowAttributes(toplevel()->frameId());
898 Xcb::WindowGeometry windowGeometry(toplevel()->frameId());
899 if (xcb_generic_error_t *error = xcb_request_check(connection(), namePixmapCookie)) {
900 kDebug(1212) << "Creating window pixmap failed: " << error->error_code;
901 free(error);
902 return;
903 }
904 // check that the received pixmap is valid and actually matches what we
905 // know about the window (i.e. size)
906 if (!windowAttributes || windowAttributes->map_state != XCB_MAP_STATE_VIEWABLE) {
907 kDebug(1212) << "Creating window pixmap failed: " << this;
908 xcb_free_pixmap(connection(), pix);
909 return;
910 }
911 if (!windowGeometry ||
912 windowGeometry->width != toplevel()->width() || windowGeometry->height != toplevel()->height()) {
913 kDebug(1212) << "Creating window pixmap failed: " << this;
914 xcb_free_pixmap(connection(), pix);
915 return;
916 }
917 m_pixmap = pix;
918 m_pixmapSize = QSize(toplevel()->width(), toplevel()->height());
919 m_contentsRect = QRect(toplevel()->clientPos(), toplevel()->clientSize());
920 m_window->unreferencePreviousPixmap();
921}
922
923//****************************************
924// Scene::EffectFrame
925//****************************************
926Scene::EffectFrame::EffectFrame(EffectFrameImpl* frame)
927 : m_effectFrame(frame)
928{
929}
930
931Scene::EffectFrame::~EffectFrame()
932{
933}
934
935} // namespace
936