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#include "composite.h"
21#include "compositingadaptor.h"
22
23#include <config-X11.h>
24
25#include "utils.h"
26#include <QTextStream>
27#include "workspace.h"
28#include "client.h"
29#include "unmanaged.h"
30#include "deleted.h"
31#include "effects.h"
32#include "overlaywindow.h"
33#include "scene.h"
34#include "scene_xrender.h"
35#include "scene_opengl.h"
36#include "shadow.h"
37#include "useractions.h"
38#include "compositingprefs.h"
39#include "xcbutils.h"
40
41#include <stdio.h>
42
43#include <QtConcurrentRun>
44#include <QFutureWatcher>
45#include <QMenu>
46#include <QTimerEvent>
47#include <QDateTime>
48#include <QDBusConnection>
49#include <kaction.h>
50#include <kactioncollection.h>
51#include <KDE/KGlobal>
52#include <KDE/KLocalizedString>
53#include <KDE/KNotification>
54#include <KDE/KSelectionWatcher>
55
56#include <xcb/composite.h>
57#include <xcb/damage.h>
58
59Q_DECLARE_METATYPE(KWin::Compositor::SuspendReason)
60
61namespace KWin
62{
63
64extern int currentRefreshRate();
65
66CompositorSelectionOwner::CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection), owning(false)
67{
68 connect (this, SIGNAL(lostOwnership()), SLOT(looseOwnership()));
69}
70
71void CompositorSelectionOwner::looseOwnership()
72{
73 owning = false;
74}
75
76KWIN_SINGLETON_FACTORY_VARIABLE(Compositor, s_compositor)
77
78static inline qint64 milliToNano(int milli) { return milli * 1000 * 1000; }
79static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); }
80
81Compositor::Compositor(QObject* workspace)
82 : QObject(workspace)
83 , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend)
84 , cm_selection(NULL)
85 , vBlankInterval(0)
86 , fpsInterval(0)
87 , m_xrrRefreshRate(0)
88 , forceUnredirectCheck(false)
89 , m_finishing(false)
90 , m_timeSinceLastVBlank(0)
91 , m_scene(NULL)
92{
93 qRegisterMetaType<Compositor::SuspendReason>("Compositor::SuspendReason");
94 new CompositingAdaptor(this);
95 QDBusConnection dbus = QDBusConnection::sessionBus();
96 dbus.registerObject("/Compositor", this);
97 dbus.registerService("org.kde.kwin.Compositing");
98 connect(&unredirectTimer, SIGNAL(timeout()), SLOT(delayedCheckUnredirect()));
99 connect(&compositeResetTimer, SIGNAL(timeout()), SLOT(restart()));
100 connect(workspace, SIGNAL(configChanged()), SLOT(slotConfigChanged()));
101 connect(options, SIGNAL(unredirectFullscreenChanged()), SLOT(delayedCheckUnredirect()));
102 unredirectTimer.setSingleShot(true);
103 compositeResetTimer.setSingleShot(true);
104 nextPaintReference.invalidate(); // Initialize the timer
105
106 // 2 sec which should be enough to restart the compositor
107 static const int compositorLostMessageDelay = 2000;
108
109 m_releaseSelectionTimer.setSingleShot(true);
110 m_releaseSelectionTimer.setInterval(compositorLostMessageDelay);
111 connect(&m_releaseSelectionTimer, SIGNAL(timeout()), SLOT(releaseCompositorSelection()));
112
113 m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay);
114 m_unusedSupportPropertyTimer.setSingleShot(true);
115 connect(&m_unusedSupportPropertyTimer, SIGNAL(timeout()), SLOT(deleteUnusedSupportProperties()));
116
117 // delay the call to setup by one event cycle
118 // The ctor of this class is invoked from the Workspace ctor, that means before
119 // Workspace is completely constructed, so calling Workspace::self() would result
120 // in undefined behavior. This is fixed by using a delayed invocation.
121 QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection);
122}
123
124Compositor::~Compositor()
125{
126 finish();
127 deleteUnusedSupportProperties();
128 delete cm_selection;
129 s_compositor = NULL;
130}
131
132
133void Compositor::setup()
134{
135 if (hasScene())
136 return;
137 if (m_suspended) {
138 kDebug(1212) << "Compositing is suspended, reason:" << m_suspended;
139 return;
140 } else if (!CompositingPrefs::compositingPossible()) {
141 kError(1212) << "Compositing is not possible";
142 return;
143 }
144 m_starting = true;
145
146 if (!options->isCompositingInitialized()) {
147#ifndef KWIN_HAVE_OPENGLES
148 // options->reloadCompositingSettings(true) initializes the CompositingPrefs which calls an
149 // external program in turn
150 // run this in an external thread to make startup faster.
151 QFutureWatcher<void> *compositingPrefsFuture = new QFutureWatcher<void>();
152 connect(compositingPrefsFuture, SIGNAL(finished()), this, SLOT(slotCompositingOptionsInitialized()));
153 connect(compositingPrefsFuture, SIGNAL(finished()), compositingPrefsFuture, SLOT(deleteLater()));
154 compositingPrefsFuture->setFuture(QtConcurrent::run(options, &Options::reloadCompositingSettings, true));
155#else
156 // OpenGL ES does not call the external program, so no need to create a thread
157 options->reloadCompositingSettings(true);
158 slotCompositingOptionsInitialized();
159#endif
160 } else {
161 slotCompositingOptionsInitialized();
162 }
163}
164
165extern int screen_number; // main.cpp
166extern bool is_multihead;
167
168void Compositor::slotCompositingOptionsInitialized()
169{
170 char selection_name[ 100 ];
171 sprintf(selection_name, "_NET_WM_CM_S%d", DefaultScreen(display()));
172 if (!cm_selection) {
173 cm_selection = new CompositorSelectionOwner(selection_name);
174 connect(cm_selection, SIGNAL(lostOwnership()), SLOT(finish()));
175 }
176 if (!cm_selection->owning) {
177 cm_selection->claim(true); // force claiming
178 cm_selection->owning = true;
179 }
180
181 // There might still be a deleted around, needs to be cleared before creating the scene (BUG 333275)
182 while (!Workspace::self()->deletedList().isEmpty()) {
183 Workspace::self()->deletedList().first()->discard();
184 }
185
186 switch(options->compositingMode()) {
187 case OpenGLCompositing: {
188 kDebug(1212) << "Initializing OpenGL compositing";
189
190 // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes:
191 KSharedConfigPtr unsafeConfigPtr = KGlobal::config();
192 KConfigGroup unsafeConfig(unsafeConfigPtr, "Compositing");
193 const QString openGLIsUnsafe = "OpenGLIsUnsafe" + (is_multihead ? QString::number(screen_number) : "");
194 if (unsafeConfig.readEntry(openGLIsUnsafe, false))
195 kWarning(1212) << "KWin has detected that your OpenGL library is unsafe to use";
196 else {
197 unsafeConfig.writeEntry(openGLIsUnsafe, true);
198 unsafeConfig.sync();
199#ifndef KWIN_HAVE_OPENGLES
200 if (!CompositingPrefs::hasGlx()) {
201 unsafeConfig.writeEntry(openGLIsUnsafe, false);
202 unsafeConfig.sync();
203 kDebug(1212) << "No glx extensions available";
204 break;
205 }
206#endif
207
208 m_scene = SceneOpenGL::createScene();
209 connect(m_scene, SIGNAL(resetCompositing()), SLOT(restart()));
210
211 // TODO: Add 30 second delay to protect against screen freezes as well
212 unsafeConfig.writeEntry(openGLIsUnsafe, false);
213 unsafeConfig.sync();
214
215 if (m_scene && !m_scene->initFailed())
216 break; // -->
217 delete m_scene;
218 m_scene = NULL;
219 }
220
221 // Do not Fall back to XRender - it causes problems when selfcheck fails during startup, but works later on
222 break;
223 }
224#ifdef KWIN_HAVE_XRENDER_COMPOSITING
225 case XRenderCompositing:
226 kDebug(1212) << "Initializing XRender compositing";
227 m_scene = new SceneXrender(Workspace::self());
228 break;
229#endif
230 default:
231 kDebug(1212) << "No compositing enabled";
232 m_starting = false;
233 cm_selection->owning = false;
234 cm_selection->release();
235 return;
236 }
237 if (m_scene == NULL || m_scene->initFailed()) {
238 kError(1212) << "Failed to initialize compositing, compositing disabled";
239 kError(1212) << "Consult http://techbase.kde.org/Projects/KWin/4.0-release-notes#Setting_up";
240 delete m_scene;
241 m_scene = NULL;
242 m_starting = false;
243 cm_selection->owning = false;
244 cm_selection->release();
245 return;
246 }
247 m_xrrRefreshRate = KWin::currentRefreshRate();
248 fpsInterval = options->maxFpsInterval();
249 if (m_scene->syncsToVBlank()) { // if we do vsync, set the fps to the next multiple of the vblank rate
250 vBlankInterval = milliToNano(1000) / m_xrrRefreshRate;
251 fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval);
252 } else
253 vBlankInterval = milliToNano(1); // no sync - DO NOT set "0", would cause div-by-zero segfaults.
254 m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" - we don't have even a slight idea when the first vsync will occur
255 scheduleRepaint();
256 xcb_composite_redirect_subwindows(connection(), rootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL);
257 new EffectsHandlerImpl(this, m_scene); // sets also the 'effects' pointer
258 connect(effects, SIGNAL(screenGeometryChanged(QSize)), SLOT(addRepaintFull()));
259 addRepaintFull();
260 foreach (Client * c, Workspace::self()->clientList()) {
261 c->setupCompositing();
262 c->getShadow();
263 }
264 foreach (Client * c, Workspace::self()->desktopList())
265 c->setupCompositing();
266 foreach (Unmanaged * c, Workspace::self()->unmanagedList()) {
267 c->setupCompositing();
268 c->getShadow();
269 }
270
271 emit compositingToggled(true);
272
273 m_starting = false;
274 if (m_releaseSelectionTimer.isActive()) {
275 m_releaseSelectionTimer.stop();
276 }
277
278 // render at least once
279 performCompositing();
280}
281
282void Compositor::scheduleRepaint()
283{
284 if (!compositeTimer.isActive())
285 setCompositeTimer();
286}
287
288void Compositor::finish()
289{
290 if (!hasScene())
291 return;
292 m_finishing = true;
293 m_releaseSelectionTimer.start();
294 foreach (Client * c, Workspace::self()->clientList())
295 m_scene->windowClosed(c, NULL);
296 foreach (Client * c, Workspace::self()->desktopList())
297 m_scene->windowClosed(c, NULL);
298 foreach (Unmanaged * c, Workspace::self()->unmanagedList())
299 m_scene->windowClosed(c, NULL);
300 foreach (Deleted * c, Workspace::self()->deletedList())
301 m_scene->windowDeleted(c);
302 foreach (Client * c, Workspace::self()->clientList())
303 c->finishCompositing();
304 foreach (Client * c, Workspace::self()->desktopList())
305 c->finishCompositing();
306 foreach (Unmanaged * c, Workspace::self()->unmanagedList())
307 c->finishCompositing();
308 foreach (Deleted * c, Workspace::self()->deletedList())
309 c->finishCompositing();
310 xcb_composite_unredirect_subwindows(connection(), rootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL);
311 delete effects;
312 effects = NULL;
313 delete m_scene;
314 m_scene = NULL;
315 compositeTimer.stop();
316 repaints_region = QRegion();
317 for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin();
318 it != Workspace::self()->clientList().constEnd();
319 ++it) {
320 // forward all opacity values to the frame in case there'll be other CM running
321 if ((*it)->opacity() != 1.0) {
322 NETWinInfo2 i(display(), (*it)->frameId(), rootWindow(), 0);
323 i.setOpacity(static_cast< unsigned long >((*it)->opacity() * 0xffffffff));
324 }
325 }
326 // discard all Deleted windows (#152914)
327 while (!Workspace::self()->deletedList().isEmpty())
328 Workspace::self()->deletedList().first()->discard();
329 m_finishing = false;
330 emit compositingToggled(false);
331}
332
333void Compositor::releaseCompositorSelection()
334{
335 if (hasScene() && !m_finishing) {
336 // compositor is up and running again, no need to release the selection
337 return;
338 }
339 if (m_starting) {
340 // currently still starting the compositor, it might fail, so restart the timer to test again
341 m_releaseSelectionTimer.start();
342 return;
343 }
344
345 if (m_finishing) {
346 // still shutting down, a restart might follow, so restart the timer to test again
347 m_releaseSelectionTimer.start();
348 return;
349 }
350 kDebug(1212) << "Releasing compositor selection";
351 cm_selection->owning = false;
352 cm_selection->release();
353}
354
355void Compositor::keepSupportProperty(xcb_atom_t atom)
356{
357 m_unusedSupportProperties.removeAll(atom);
358}
359
360void Compositor::removeSupportProperty(xcb_atom_t atom)
361{
362 m_unusedSupportProperties << atom;
363 m_unusedSupportPropertyTimer.start();
364}
365
366void Compositor::deleteUnusedSupportProperties()
367{
368 if (m_starting) {
369 // currently still starting the compositor
370 m_unusedSupportPropertyTimer.start();
371 return;
372 }
373 if (m_finishing) {
374 // still shutting down, a restart might follow
375 m_unusedSupportPropertyTimer.start();
376 return;
377 }
378 foreach (const xcb_atom_t &atom, m_unusedSupportProperties) {
379 // remove property from root window
380 XDeleteProperty(QX11Info::display(), rootWindow(), atom);
381 }
382}
383
384// OpenGL self-check failed, fallback to XRender
385void Compositor::fallbackToXRenderCompositing()
386{
387 finish();
388 KConfigGroup config(KGlobal::config(), "Compositing");
389 config.writeEntry("Backend", "XRender");
390 config.writeEntry("GraphicsSystem", "native");
391 config.sync();
392 if (Extensions::nonNativePixmaps()) { // must restart to change the graphicssystem
393 restartKWin("automatic graphicssystem change for XRender backend");
394 return;
395 } else {
396 options->setCompositingMode(XRenderCompositing);
397 setup();
398 }
399}
400
401void Compositor::slotConfigChanged()
402{
403 if (!m_suspended) {
404 setup();
405 if (effects) // setupCompositing() may fail
406 effects->reconfigure();
407 addRepaintFull();
408 } else
409 finish();
410}
411
412void Compositor::slotReinitialize()
413{
414 // Reparse config. Config options will be reloaded by setup()
415 KGlobal::config()->reparseConfiguration();
416 const QString graphicsSystem = KConfigGroup(KGlobal::config(), "Compositing").readEntry("GraphicsSystem", "");
417 if ((Extensions::nonNativePixmaps() && graphicsSystem == "native") ||
418 (!Extensions::nonNativePixmaps() && (graphicsSystem == "raster" || graphicsSystem == "opengl")) ) {
419 restartKWin("explicitly reconfigured graphicsSystem change");
420 return;
421 }
422
423 // Restart compositing
424 finish();
425 // resume compositing if suspended
426 m_suspended = NoReasonSuspend;
427 options->setCompositingInitialized(false);
428 setup();
429
430 if (effects) { // setup() may fail
431 effects->reconfigure();
432 }
433}
434
435// for the shortcut
436void Compositor::slotToggleCompositing()
437{
438 if (m_suspended) { // direct user call; clear all bits
439 resume(AllReasonSuspend);
440 } else { // but only set the user one (sufficient to suspend)
441 suspend(UserSuspend);
442 }
443}
444
445// for the dbus call
446void Compositor::toggleCompositing()
447{
448 slotToggleCompositing(); // TODO only operate on script level here?
449 if (m_suspended) {
450 // when disabled show a shortcut how the user can get back compositing
451 QString shortcut, message;
452 if (KAction* action = qobject_cast<KAction*>(Workspace::self()->actionCollection()->action("Suspend Compositing")))
453 shortcut = action->globalShortcut().primary().toString(QKeySequence::NativeText);
454 if (!shortcut.isEmpty()) {
455 // display notification only if there is the shortcut
456 message = i18n("Desktop effects have been suspended by another application.<br/>"
457 "You can resume using the '%1' shortcut.", shortcut);
458 KNotification::event("compositingsuspendeddbus", message);
459 }
460 }
461}
462
463void Compositor::updateCompositeBlocking()
464{
465 updateCompositeBlocking(NULL);
466}
467
468void Compositor::updateCompositeBlocking(Client *c)
469{
470 if (c) { // if c == 0 we just check if we can resume
471 if (c->isBlockingCompositing()) {
472 if (!(m_suspended & BlockRuleSuspend)) // do NOT attempt to call suspend(true); from within the eventchain!
473 QMetaObject::invokeMethod(this, "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend));
474 }
475 }
476 else if (m_suspended & BlockRuleSuspend) { // lost a client and we're blocked - can we resume?
477 bool resume = true;
478 for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) {
479 if ((*it)->isBlockingCompositing()) {
480 resume = false;
481 break;
482 }
483 }
484 if (resume) { // do NOT attempt to call suspend(false); from within the eventchain!
485 QMetaObject::invokeMethod(this, "resume", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend));
486 }
487 }
488}
489
490void Compositor::suspend(Compositor::SuspendReason reason)
491{
492 Q_ASSERT(reason != NoReasonSuspend);
493 m_suspended |= reason;
494 finish();
495}
496
497void Compositor::resume(Compositor::SuspendReason reason)
498{
499 Q_ASSERT(reason != NoReasonSuspend);
500 m_suspended &= ~reason;
501 setup(); // signal "toggled" is eventually emitted from within setup
502}
503
504void Compositor::setCompositing(bool active)
505{
506 if (active) {
507 resume(ScriptSuspend);
508 } else {
509 suspend(ScriptSuspend);
510 }
511}
512
513void Compositor::restart()
514{
515 if (hasScene()) {
516 finish();
517 QTimer::singleShot(0, this, SLOT(setup()));
518 }
519}
520
521void Compositor::addRepaint(int x, int y, int w, int h)
522{
523 if (!hasScene())
524 return;
525 repaints_region += QRegion(x, y, w, h);
526 scheduleRepaint();
527}
528
529void Compositor::addRepaint(const QRect& r)
530{
531 if (!hasScene())
532 return;
533 repaints_region += r;
534 scheduleRepaint();
535}
536
537void Compositor::addRepaint(const QRegion& r)
538{
539 if (!hasScene())
540 return;
541 repaints_region += r;
542 scheduleRepaint();
543}
544
545void Compositor::addRepaintFull()
546{
547 if (!hasScene())
548 return;
549 repaints_region = QRegion(0, 0, displayWidth(), displayHeight());
550 scheduleRepaint();
551}
552
553void Compositor::timerEvent(QTimerEvent *te)
554{
555 if (te->timerId() == compositeTimer.timerId()) {
556 performCompositing();
557 } else
558 QObject::timerEvent(te);
559}
560
561void Compositor::performCompositing()
562{
563 if (!isOverlayWindowVisible())
564 return; // nothing is visible anyway
565
566 // Create a list of all windows in the stacking order
567 ToplevelList windows = Workspace::self()->xStackingOrder();
568 ToplevelList damaged;
569
570 // Reset the damage state of each window and fetch the damage region
571 // without waiting for a reply
572 foreach (Toplevel *win, windows) {
573 if (win->resetAndFetchDamage())
574 damaged << win;
575 }
576
577 if (damaged.count() > 0)
578 xcb_flush(connection());
579
580 // Move elevated windows to the top of the stacking order
581 foreach (EffectWindow *c, static_cast<EffectsHandlerImpl *>(effects)->elevatedWindows()) {
582 Toplevel* t = static_cast< EffectWindowImpl* >(c)->window();
583 windows.removeAll(t);
584 windows.append(t);
585 }
586
587 // Get the replies
588 foreach (Toplevel *win, damaged) {
589 // Discard the cached lanczos texture
590 if (win->effectWindow()) {
591 const QVariant texture = win->effectWindow()->data(LanczosCacheRole);
592 if (texture.isValid()) {
593 delete static_cast<GLTexture *>(texture.value<void*>());
594 win->effectWindow()->setData(LanczosCacheRole, QVariant());
595 }
596 }
597
598 win->getDamageRegionReply();
599 }
600
601 if (repaints_region.isEmpty() && !windowRepaintsPending()) {
602 m_scene->idle();
603 m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now"
604 // Note: It would seem here we should undo suspended unredirect, but when scenes need
605 // it for some reason, e.g. transformations or translucency, the next pass that does not
606 // need this anymore and paints normally will also reset the suspended unredirect.
607 // Otherwise the window would not be painted normally anyway.
608 compositeTimer.stop();
609 return;
610 }
611
612 // skip windows that are not yet ready for being painted
613 // TODO ?
614 // this cannot be used so carelessly - needs protections against broken clients, the window
615 // should not get focus before it's displayed, handle unredirected windows properly and so on.
616 foreach (Toplevel *t, windows)
617 if (!t->readyForPainting())
618 windows.removeAll(t);
619
620 QRegion repaints = repaints_region;
621 // clear all repaints, so that post-pass can add repaints for the next repaint
622 repaints_region = QRegion();
623
624 m_timeSinceLastVBlank = m_scene->paint(repaints, windows);
625
626 compositeTimer.stop(); // stop here to ensure *we* cause the next repaint schedule - not some effect through m_scene->paint()
627
628 // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle()
629 // is called the next time. If there would be nothing pending, it will not restart the timer and
630 // scheduleRepaint() would restart it again somewhen later, called from functions that
631 // would again add something pending.
632 scheduleRepaint();
633}
634
635bool Compositor::windowRepaintsPending() const
636{
637 foreach (Toplevel * c, Workspace::self()->clientList())
638 if (!c->repaints().isEmpty())
639 return true;
640 foreach (Toplevel * c, Workspace::self()->desktopList())
641 if (!c->repaints().isEmpty())
642 return true;
643 foreach (Toplevel * c, Workspace::self()->unmanagedList())
644 if (!c->repaints().isEmpty())
645 return true;
646 foreach (Toplevel * c, Workspace::self()->deletedList())
647 if (!c->repaints().isEmpty())
648 return true;
649 return false;
650}
651
652void Compositor::setCompositeResetTimer(int msecs)
653{
654 compositeResetTimer.start(msecs);
655}
656
657void Compositor::setCompositeTimer()
658{
659 if (!hasScene()) // should not really happen, but there may be e.g. some damage events still pending
660 return;
661
662 uint waitTime = 1;
663
664 if (m_scene->blocksForRetrace()) {
665
666 // TODO: make vBlankTime dynamic?!
667 // It's required because glXWaitVideoSync will *likely* block a full frame if one enters
668 // a retrace pass which can last a variable amount of time, depending on the actual screen
669 // Now, my ooold 19" CRT can do such retrace so that 2ms are entirely sufficient,
670 // while another ooold 15" TFT requires about 6ms
671
672 qint64 padding = m_timeSinceLastVBlank;
673 if (padding > fpsInterval) {
674 // we're at low repaints or spent more time in painting than the user wanted to wait for that frame
675 padding = vBlankInterval - (padding%vBlankInterval); // -> align to next vblank
676 } else { // -> align to the next maxFps tick
677 padding = ((vBlankInterval - padding%vBlankInterval) + (fpsInterval/vBlankInterval-1)*vBlankInterval);
678 // "remaining time of the first vsync" + "time for the other vsyncs of the frame"
679 }
680
681 if (padding < options->vBlankTime()) { // we'll likely miss this frame
682 waitTime = nanoToMilli(padding + vBlankInterval - options->vBlankTime()); // so we add one
683 } else {
684 waitTime = nanoToMilli(padding - options->vBlankTime());
685 }
686 }
687 else { // w/o blocking vsync we just jump to the next demanded tick
688 if (fpsInterval > m_timeSinceLastVBlank) {
689 waitTime = nanoToMilli(fpsInterval - m_timeSinceLastVBlank);
690 if (!waitTime) {
691 waitTime = 1; // will ensure we don't block out the eventloop - the system's just not faster ...
692 }
693 }/* else if (m_scene->syncsToVBlank() && m_timeSinceLastVBlank - fpsInterval < (vBlankInterval<<1)) {
694 // NOTICE - "for later" ------------------------------------------------------------------
695 // It can happen that we push two frames within one refresh cycle.
696 // Swapping will then block even with triple buffering when the GPU does not discard but
697 // queues frames
698 // now here's the mean part: if we take that as "OMG, we're late - next frame ASAP",
699 // there'll immediately be 2 frames in the pipe, swapping will block, we think we're
700 // late ... ewww
701 // so instead we pad to the clock again and add 2ms safety to ensure the pipe is really
702 // free
703 // NOTICE: obviously m_timeSinceLastVBlank can be too big because we're too slow as well
704 // So if this code was enabled, we'd needlessly half the framerate once more (15 instead of 30)
705 waitTime = nanoToMilli(vBlankInterval - (m_timeSinceLastVBlank - fpsInterval)%vBlankInterval) + 2;
706 }*/ else {
707 waitTime = 1; // ... "0" would be sufficient, but the compositor isn't the WMs only task
708 }
709 }
710 compositeTimer.start(qMin(waitTime, 250u), this); // force 4fps minimum
711}
712
713bool Compositor::isActive()
714{
715 return !m_finishing && hasScene();
716}
717
718void Compositor::checkUnredirect()
719{
720 checkUnredirect(false);
721}
722
723// force is needed when the list of windows changes (e.g. a window goes away)
724void Compositor::checkUnredirect(bool force)
725{
726 if (!hasScene() || m_scene->overlayWindow()->window() == None || !options->isUnredirectFullscreen())
727 return;
728 if (force)
729 forceUnredirectCheck = true;
730 if (!unredirectTimer.isActive())
731 unredirectTimer.start(0);
732}
733
734void Compositor::delayedCheckUnredirect()
735{
736 if (!hasScene() || m_scene->overlayWindow()->window() == None || !(options->isUnredirectFullscreen() || sender() == options))
737 return;
738 ToplevelList list;
739 bool changed = forceUnredirectCheck;
740 foreach (Client * c, Workspace::self()->clientList())
741 list.append(c);
742 foreach (Unmanaged * c, Workspace::self()->unmanagedList())
743 list.append(c);
744 foreach (Toplevel * c, list) {
745 if (c->updateUnredirectedState())
746 changed = true;
747 }
748 // no desktops, no Deleted ones
749 if (!changed)
750 return;
751 forceUnredirectCheck = false;
752 // Cut out parts from the overlay window where unredirected windows are,
753 // so that they are actually visible.
754 QRegion reg(0, 0, displayWidth(), displayHeight());
755 foreach (Toplevel * c, list) {
756 if (c->unredirected())
757 reg -= c->geometry();
758 }
759 m_scene->overlayWindow()->setShape(reg);
760}
761
762bool Compositor::checkForOverlayWindow(WId w) const
763{
764 if (!hasScene()) {
765 // no scene, so it cannot be the overlay window
766 return false;
767 }
768 if (!m_scene->overlayWindow()) {
769 // no overlay window, it cannot be the overlay
770 return false;
771 }
772 // and compare the window ID's
773 return w == m_scene->overlayWindow()->window();
774}
775
776WId Compositor::overlayWindow() const
777{
778 if (!hasScene()) {
779 return None;
780 }
781 if (!m_scene->overlayWindow()) {
782 return None;
783 }
784 return m_scene->overlayWindow()->window();
785}
786
787bool Compositor::isOverlayWindowVisible() const
788{
789 if (!hasScene()) {
790 return false;
791 }
792 if (!m_scene->overlayWindow()) {
793 return false;
794 }
795 return m_scene->overlayWindow()->isVisible();
796}
797
798void Compositor::setOverlayWindowVisibility(bool visible)
799{
800 if (hasScene() && m_scene->overlayWindow()) {
801 m_scene->overlayWindow()->setVisibility(visible);
802 }
803}
804
805void Compositor::restartKWin(const QString &reason)
806{
807 kDebug(1212) << "restarting kwin for:" << reason;
808 char cmd[1024]; // copied from crashhandler - maybe not the best way to do?
809 sprintf(cmd, "%s --replace &", QFile::encodeName(QCoreApplication::applicationFilePath()).constData());
810 system(cmd);
811}
812
813bool Compositor::isCompositingPossible() const
814{
815 return CompositingPrefs::compositingPossible();
816}
817
818QString Compositor::compositingNotPossibleReason() const
819{
820 return CompositingPrefs::compositingNotPossibleReason();
821}
822
823bool Compositor::isOpenGLBroken() const
824{
825 return CompositingPrefs::openGlIsBroken();
826}
827
828QString Compositor::compositingType() const
829{
830 if (!hasScene()) {
831 return "none";
832 }
833 switch (m_scene->compositingType()) {
834 case XRenderCompositing:
835 return "xrender";
836 case OpenGL1Compositing:
837 return "gl1";
838 case OpenGL2Compositing:
839#ifdef KWIN_HAVE_OPENGLES
840 return "gles";
841#else
842 return "gl2";
843#endif
844 case NoCompositing:
845 default:
846 return "none";
847 }
848}
849
850/*****************************************************
851 * Workspace
852 ****************************************************/
853
854bool Workspace::compositing() const
855{
856 return m_compositor && m_compositor->hasScene();
857}
858
859//****************************************
860// Toplevel
861//****************************************
862
863bool Toplevel::setupCompositing()
864{
865 if (!compositing())
866 return false;
867
868 if (damage_handle != XCB_NONE)
869 return false;
870
871 damage_handle = xcb_generate_id(connection());
872 xcb_damage_create(connection(), damage_handle, frameId(), XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
873
874 damage_region = QRegion(0, 0, width(), height());
875 effect_window = new EffectWindowImpl(this);
876 unredirect = false;
877
878 Compositor::self()->checkUnredirect(true);
879 Compositor::self()->scene()->windowAdded(this);
880
881 // With unmanaged windows there is a race condition between the client painting the window
882 // and us setting up damage tracking. If the client wins we won't get a damage event even
883 // though the window has been painted. To avoid this we mark the whole window as damaged
884 // and schedule a repaint immediately after creating the damage object.
885 if (dynamic_cast<Unmanaged*>(this))
886 addDamageFull();
887
888 return true;
889}
890
891void Toplevel::finishCompositing()
892{
893 if (damage_handle == XCB_NONE)
894 return;
895 Compositor::self()->checkUnredirect(true);
896 if (effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data
897 discardWindowPixmap();
898 delete effect_window;
899 }
900
901 xcb_damage_destroy(connection(), damage_handle);
902
903 damage_handle = XCB_NONE;
904 damage_region = QRegion();
905 repaints_region = QRegion();
906 effect_window = NULL;
907}
908
909void Toplevel::discardWindowPixmap()
910{
911 addDamageFull();
912 if (effectWindow() != NULL && effectWindow()->sceneWindow() != NULL)
913 effectWindow()->sceneWindow()->pixmapDiscarded();
914}
915
916void Toplevel::damageNotifyEvent()
917{
918 m_isDamaged = true;
919
920 // Note: The rect is supposed to specify the damage extents,
921 // but we don't know it at this point. No one who connects
922 // to this signal uses the rect however.
923 emit damaged(this, QRect());
924}
925
926bool Toplevel::compositing() const
927{
928 return Workspace::self()->compositing();
929}
930
931void Client::damageNotifyEvent()
932{
933#ifdef HAVE_XSYNC
934 if (syncRequest.isPending && isResize()) {
935 emit damaged(this, QRect());
936 m_isDamaged = true;
937 return;
938 }
939
940 if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead
941 if (syncRequest.counter == None) // cannot detect complete redraw, consider done now
942 setReadyForPainting();
943 }
944#else
945 if (!ready_for_painting)
946 setReadyForPainting();
947#endif
948
949 Toplevel::damageNotifyEvent();
950}
951
952bool Toplevel::resetAndFetchDamage()
953{
954 if (!m_isDamaged)
955 return false;
956
957 xcb_connection_t *conn = connection();
958
959 // Create a new region and copy the damage region to it,
960 // resetting the damaged state.
961 xcb_xfixes_region_t region = xcb_generate_id(conn);
962 xcb_xfixes_create_region(conn, region, 0, 0);
963 xcb_damage_subtract(conn, damage_handle, 0, region);
964
965 // Send a fetch-region request and destroy the region
966 m_regionCookie = xcb_xfixes_fetch_region_unchecked(conn, region);
967 xcb_xfixes_destroy_region(conn, region);
968
969 m_isDamaged = false;
970 m_damageReplyPending = true;
971
972 return m_damageReplyPending;
973}
974
975void Toplevel::getDamageRegionReply()
976{
977 if (!m_damageReplyPending)
978 return;
979
980 m_damageReplyPending = false;
981
982 // Get the fetch-region reply
983 xcb_xfixes_fetch_region_reply_t *reply =
984 xcb_xfixes_fetch_region_reply(connection(), m_regionCookie, 0);
985
986 if (!reply)
987 return;
988
989 // Convert the reply to a QRegion
990 int count = xcb_xfixes_fetch_region_rectangles_length(reply);
991 QRegion region;
992
993 if (count > 1 && count < 16) {
994 xcb_rectangle_t *rects = xcb_xfixes_fetch_region_rectangles(reply);
995
996 QVector<QRect> qrects;
997 qrects.reserve(count);
998
999 for (int i = 0; i < count; i++)
1000 qrects << QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height);
1001
1002 region.setRects(qrects.constData(), count);
1003 } else
1004 region += QRect(reply->extents.x, reply->extents.y,
1005 reply->extents.width, reply->extents.height);
1006
1007 damage_region += region;
1008 repaints_region += region;
1009
1010 free(reply);
1011}
1012
1013void Toplevel::addDamageFull()
1014{
1015 if (!compositing())
1016 return;
1017
1018 damage_region = rect();
1019 repaints_region |= rect();
1020
1021 emit damaged(this, rect());
1022}
1023
1024void Toplevel::resetDamage()
1025{
1026 damage_region = QRegion();
1027}
1028
1029void Toplevel::addRepaint(const QRect& r)
1030{
1031 if (!compositing()) {
1032 return;
1033 }
1034 repaints_region += r;
1035 emit needsRepaint();
1036}
1037
1038void Toplevel::addRepaint(int x, int y, int w, int h)
1039{
1040 QRect r(x, y, w, h);
1041 addRepaint(r);
1042}
1043
1044void Toplevel::addRepaint(const QRegion& r)
1045{
1046 if (!compositing()) {
1047 return;
1048 }
1049 repaints_region += r;
1050 emit needsRepaint();
1051}
1052
1053void Toplevel::addLayerRepaint(const QRect& r)
1054{
1055 if (!compositing()) {
1056 return;
1057 }
1058 layer_repaints_region += r;
1059 emit needsRepaint();
1060}
1061
1062void Toplevel::addLayerRepaint(int x, int y, int w, int h)
1063{
1064 QRect r(x, y, w, h);
1065 addLayerRepaint(r);
1066}
1067
1068void Toplevel::addLayerRepaint(const QRegion& r)
1069{
1070 if (!compositing())
1071 return;
1072 layer_repaints_region += r;
1073 emit needsRepaint();
1074}
1075
1076void Toplevel::addRepaintFull()
1077{
1078 repaints_region = visibleRect().translated(-pos());
1079 emit needsRepaint();
1080}
1081
1082void Toplevel::resetRepaints()
1083{
1084 repaints_region = QRegion();
1085 layer_repaints_region = QRegion();
1086}
1087
1088void Toplevel::addWorkspaceRepaint(int x, int y, int w, int h)
1089{
1090 addWorkspaceRepaint(QRect(x, y, w, h));
1091}
1092
1093void Toplevel::addWorkspaceRepaint(const QRect& r2)
1094{
1095 if (!compositing())
1096 return;
1097 Compositor::self()->addRepaint(r2);
1098}
1099
1100bool Toplevel::updateUnredirectedState()
1101{
1102 assert(compositing());
1103 bool should = options->isUnredirectFullscreen() && shouldUnredirect() && !unredirectSuspend &&
1104 !shape() && !hasAlpha() && opacity() == 1.0 &&
1105 !static_cast<EffectsHandlerImpl*>(effects)->activeFullScreenEffect();
1106 if (should == unredirect)
1107 return false;
1108 static QElapsedTimer lastUnredirect;
1109 static const qint64 msecRedirectInterval = 100;
1110 if (!lastUnredirect.hasExpired(msecRedirectInterval)) {
1111 QTimer::singleShot(msecRedirectInterval, Compositor::self(), SLOT(checkUnredirect()));
1112 return false;
1113 }
1114 lastUnredirect.start();
1115 unredirect = should;
1116 if (unredirect) {
1117 kDebug(1212) << "Unredirecting:" << this;
1118 xcb_composite_unredirect_window(connection(), frameId(), XCB_COMPOSITE_REDIRECT_MANUAL);
1119 } else {
1120 kDebug(1212) << "Redirecting:" << this;
1121 xcb_composite_redirect_window(connection(), frameId(), XCB_COMPOSITE_REDIRECT_MANUAL);
1122 discardWindowPixmap();
1123 }
1124 return true;
1125}
1126
1127void Toplevel::suspendUnredirect(bool suspend)
1128{
1129 if (unredirectSuspend == suspend)
1130 return;
1131 unredirectSuspend = suspend;
1132 Compositor::self()->checkUnredirect();
1133}
1134
1135//****************************************
1136// Client
1137//****************************************
1138
1139bool Client::setupCompositing()
1140{
1141 if (!Toplevel::setupCompositing()){
1142 return false;
1143 }
1144 updateVisibility(); // for internalKeep()
1145 if (isManaged()) {
1146 // only create the decoration when a client is managed
1147 updateDecoration(true, true);
1148 }
1149 return true;
1150}
1151
1152void Client::finishCompositing()
1153{
1154 Toplevel::finishCompositing();
1155 updateVisibility();
1156 if (!deleting) {
1157 // only recreate the decoration if we are not shutting down completely
1158 updateDecoration(true, true);
1159 }
1160 // for safety in case KWin is just resizing the window
1161 s_haveResizeEffect = false;
1162}
1163
1164bool Client::shouldUnredirect() const
1165{
1166 if (isActiveFullScreen()) {
1167 ToplevelList stacking = workspace()->xStackingOrder();
1168 for (int pos = stacking.count() - 1;
1169 pos >= 0;
1170 --pos) {
1171 Toplevel* c = stacking.at(pos);
1172 if (c == this) // is not covered by any other window, ok to unredirect
1173 return true;
1174 if (c->geometry().intersects(geometry()))
1175 return false;
1176 }
1177 abort();
1178 }
1179 return false;
1180}
1181
1182
1183//****************************************
1184// Unmanaged
1185//****************************************
1186
1187bool Unmanaged::shouldUnredirect() const
1188{
1189 // the pixmap is needed for the login effect, a nicer solution would be the login effect increasing
1190 // refcount for the window pixmap (which would prevent unredirect), avoiding this hack
1191 if (resourceClass() == "ksplashx"
1192 || resourceClass() == "ksplashsimple"
1193 || resourceClass() == "ksplashqml"
1194 )
1195 return false;
1196// it must cover whole display or one xinerama screen, and be the topmost there
1197 const int desktop = VirtualDesktopManager::self()->current();
1198 if (geometry() == workspace()->clientArea(FullArea, geometry().center(), desktop)
1199 || geometry() == workspace()->clientArea(ScreenArea, geometry().center(), desktop)) {
1200 ToplevelList stacking = workspace()->xStackingOrder();
1201 for (int pos = stacking.count() - 1;
1202 pos >= 0;
1203 --pos) {
1204 Toplevel* c = stacking.at(pos);
1205 if (c == this) // is not covered by any other window, ok to unredirect
1206 return true;
1207 if (c->geometry().intersects(geometry()))
1208 return false;
1209 }
1210 abort();
1211 }
1212 return false;
1213}
1214
1215//****************************************
1216// Deleted
1217//****************************************
1218
1219bool Deleted::shouldUnredirect() const
1220{
1221 return false;
1222}
1223
1224
1225} // namespace
1226