/********************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 2006 Lubos Lunak <l.lunak@kde.org>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/

/*
 Code related to compositing (redirecting windows to pixmaps and tracking
 window damage).

 Docs:

 XComposite (the protocol, but the function calls map to it):
 http://gitweb.freedesktop.org/?p=xorg/proto/compositeproto.git;a=blob_plain;hb=HEAD;f=compositeproto.txt

 XDamage (again the protocol):
 http://gitweb.freedesktop.org/?p=xorg/proto/damageproto.git;a=blob_plain;hb=HEAD;f=damageproto.txt

 Paper including basics on compositing, XGL vs AIGLX, XRender vs OpenGL, etc.:
 http://www.vis.uni-stuttgart.de/~hopf/pub/LinuxTag2007_compiz_NextGenerationDesktop_Paper.pdf

 Composite HOWTO from Fredrik:
 http://ktown.kde.org/~fredrik/composite_howto.html

*/

#include <config-X11.h>

#include "utils.h"
#include <QTextStream>
#include "workspace.h"
#include "client.h"
#include "unmanaged.h"
#include "deleted.h"
#include "effects.h"
#include "overlaywindow.h"
#include "scene.h"
#include "scene_xrender.h"
#include "scene_opengl.h"
#include "shadow.h"
#include "compositingprefs.h"
#include "notifications.h"

#include <stdio.h>

#include <QtCore/QtConcurrentRun>
#include <QtCore/QFutureWatcher>
#include <QMenu>
#include <QTimerEvent>
#include <kaction.h>
#include <kactioncollection.h>
#include <klocale.h>
#include <kxerrorhandler.h>

#include <X11/extensions/shape.h>

#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xrandr.h>

namespace KWin
{

extern int currentRefreshRate();

//****************************************
// Workspace
//****************************************

void Workspace::setupCompositing()
{
    if (scene != NULL)
        return;
    if (compositingSuspended) {
        kDebug(1212) << "Compositing is suspended";
        return;
    } else if (!CompositingPrefs::compositingPossible()) {
        kError(1212) << "Compositing is not possible";
        return;
    }

    if (!options->isCompositingInitialized()) {
#ifndef KWIN_HAVE_OPENGLES
        // options->reloadCompositingSettings(true) initializes the CompositingPrefs which calls an
        // external program in turn
        // run this in an external thread to make startup faster.
        QFutureWatcher<void> *compositingPrefsFuture = new QFutureWatcher<void>();
        connect(compositingPrefsFuture, SIGNAL(finished()), this, SLOT(slotCompositingOptionsInitialized()));
        connect(compositingPrefsFuture, SIGNAL(finished()), compositingPrefsFuture, SLOT(deleteLater()));
        compositingPrefsFuture->setFuture(QtConcurrent::run(options, &Options::reloadCompositingSettings, true));
#else
        // OpenGL ES does not call the external program, so no need to create a thread
        options->reloadCompositingSettings(true);
        slotCompositingOptionsInitialized();
#endif
    } else {
        slotCompositingOptionsInitialized();
    }
}

void Workspace::slotCompositingOptionsInitialized()
{
    char selection_name[ 100 ];
    sprintf(selection_name, "_NET_WM_CM_S%d", DefaultScreen(display()));
    cm_selection = new KSelectionOwner(selection_name);
    connect(cm_selection, SIGNAL(lostOwnership()), SLOT(lostCMSelection()));
    cm_selection->claim(true);   // force claiming

    switch(options->compositingMode()) {
    case OpenGLCompositing: {
        kDebug(1212) << "Initializing OpenGL compositing";

        // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes:
        KSharedConfigPtr unsafeConfigPtr = KGlobal::config();
        KConfigGroup unsafeConfig(unsafeConfigPtr, "Compositing");
        if (unsafeConfig.readEntry("OpenGLIsUnsafe", false))
            kWarning(1212) << "KWin has detected that your OpenGL library is unsafe to use";
        else {
            unsafeConfig.writeEntry("OpenGLIsUnsafe", true);
            unsafeConfig.sync();
#ifndef KWIN_HAVE_OPENGLES
            if (!CompositingPrefs::hasGlx()) {
                unsafeConfig.writeEntry("OpenGLIsUnsafe", false);
                unsafeConfig.sync();
                kDebug(1212) << "No glx extensions available";
                break;
            }
#endif

            scene = new SceneOpenGL(this);

            // TODO: Add 30 second delay to protect against screen freezes as well
            unsafeConfig.writeEntry("OpenGLIsUnsafe", false);
            unsafeConfig.sync();

            if (!scene->initFailed())
                break; // -->
            delete scene;
            scene = NULL;
        }

        // Do not Fall back to XRender - it causes problems when selfcheck fails during startup, but works later on
        break;
    }
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    case XRenderCompositing:
        kDebug(1212) << "Initializing XRender compositing";
        scene = new SceneXrender(this);
        break;
#endif
    default:
        kDebug(1212) << "No compositing enabled";
        delete cm_selection;
        return;
    }
    if (scene == NULL || scene->initFailed()) {
        kError(1212) << "Failed to initialize compositing, compositing disabled";
        kError(1212) << "Consult http://techbase.kde.org/Projects/KWin/4.0-release-notes#Setting_up";
        delete scene;
        scene = NULL;
        delete cm_selection;
        return;
    }
    xrrRefreshRate = KWin::currentRefreshRate();
    fpsInterval = (options->maxFpsInterval() << 10);
    if (scene->waitSyncAvailable()) {  // if we do vsync, set the fps to the next multiple of the vblank rate
        vBlankInterval = (1000 << 10) / xrrRefreshRate;
        fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval);
    } else
        vBlankInterval = 1 << 10; // no sync - DO NOT set "0", would cause div-by-zero segfaults.
    m_timeSinceLastVBlank = fpsInterval - 1; // means "start now" - we dont't have even a slight idea when the first vsync will occur
    checkCompositeTimer();
    XCompositeRedirectSubwindows(display(), rootWindow(), CompositeRedirectManual);
    new EffectsHandlerImpl(scene->compositingType());   // sets also the 'effects' pointer
    addRepaintFull();
    foreach (Client * c, clients)
        c->setupCompositing();
    foreach (Client * c, desktops)
        c->setupCompositing();
    foreach (Unmanaged * c, unmanaged)
        c->setupCompositing();
    discardPopup(); // force re-creation of the Alt+F3 popup (opacity option)

    // render at least once
    compositeTimer.stop();
    performCompositing();
}

void Workspace::finishCompositing()
{
    if (scene == NULL)
        return;
    m_finishingCompositing = true;
    delete cm_selection;
    foreach (Client * c, clients)
    scene->windowClosed(c, NULL);
    foreach (Client * c, desktops)
    scene->windowClosed(c, NULL);
    foreach (Unmanaged * c, unmanaged)
    scene->windowClosed(c, NULL);
    foreach (Deleted * c, deleted)
    scene->windowDeleted(c);
    foreach (Client * c, clients)
    c->finishCompositing();
    foreach (Client * c, desktops)
    c->finishCompositing();
    foreach (Unmanaged * c, unmanaged)
    c->finishCompositing();
    foreach (Deleted * c, deleted)
    c->finishCompositing();
    XCompositeUnredirectSubwindows(display(), rootWindow(), CompositeRedirectManual);
    delete effects;
    effects = NULL;
    delete scene;
    scene = NULL;
    compositeTimer.stop();
    mousePollingTimer.stop();
    repaints_region = QRegion();
    for (ClientList::ConstIterator it = clients.constBegin();
            it != clients.constEnd();
            ++it) {
        // forward all opacity values to the frame in case there'll be other CM running
        if ((*it)->opacity() != 1.0) {
            NETWinInfo2 i(display(), (*it)->frameId(), rootWindow(), 0);
            i.setOpacity(static_cast< unsigned long >((*it)->opacity() * 0xffffffff));
        }
    }
    discardPopup(); // force re-creation of the Alt+F3 popup (opacity option)
    // discard all Deleted windows (#152914)
    while (!deleted.isEmpty())
        deleted.first()->discard(Allowed);
    m_finishingCompositing = false;
}

// OpenGL self-check failed, fallback to XRender
void Workspace::fallbackToXRenderCompositing()
{
    finishCompositing();
    KConfigGroup config(KGlobal::config(), "Compositing");
    config.writeEntry("Backend", "XRender");
    config.writeEntry("GraphicsSystem", "native");
    config.sync();
    if (Extensions::nonNativePixmaps()) { // must restart to change the graphicssystem
        restartKWin("automatic graphicssystem change for XRender backend");
        return;
    } else {
        options->setCompositingMode(XRenderCompositing);
        setupCompositing();
    }
}

void Workspace::lostCMSelection()
{
    kDebug(1212) << "Lost compositing manager selection";
    finishCompositing();
}

// for the shortcut
void Workspace::slotToggleCompositing()
{
    suspendCompositing(!compositingSuspended);
}

// for the dbus call
void Workspace::toggleCompositing()
{
    slotToggleCompositing();
    if (compositingSuspended) {
        // when disabled show a shortcut how the user can get back compositing
        QString shortcut, message;
        if (KAction* action = qobject_cast<KAction*>(keys->action("Suspend Compositing")))
            shortcut = action->globalShortcut().primary().toString(QKeySequence::NativeText);
        if (!shortcut.isEmpty()) {
            // display notification only if there is the shortcut
            message = i18n("Desktop effects have been suspended by another application.<br/>"
                           "You can resume using the '%1' shortcut.", shortcut);
            Notify::raise(Notify::CompositingSuspendedDbus, message);
        }
    }
}

QStringList Workspace::activeEffects() const
{
    if (effects)
        return static_cast< EffectsHandlerImpl* >(effects)->activeEffects();
    return QStringList();
}

void Workspace::updateCompositeBlocking(Client *c)
{
    if (c) { // if c == 0 we just check if we can resume
        if (c->isBlockingCompositing()) {
            if (!compositingBlocked) // do NOT attempt to call suspendCompositing(true); from within the eventchain!
                QMetaObject::invokeMethod(this, "suspendCompositing", Qt::QueuedConnection, Q_ARG(bool, true));
            compositingBlocked = true;
        }
    }
    else if (compositingBlocked) {  // lost a client and we're blocked - can we resume?
        // NOTICE do NOT check for "compositingSuspended" or "!compositing()"
        // only "resume" if it was really disabled for a block
        bool resume = true;
        for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) {
            if ((*it)->isBlockingCompositing()) {
                resume = false;
                break;
            }
        }
        if (resume) { // do NOT attempt to call suspendCompositing(false); from within the eventchain!
            compositingBlocked = false;
            QMetaObject::invokeMethod(this, "suspendCompositing", Qt::QueuedConnection, Q_ARG(bool, false));
        }
    }
}

void Workspace::suspendCompositing()
{
    suspendCompositing(true);
}

void Workspace::suspendCompositing(bool suspend)
{
    if (compositingSuspended == suspend) {
        // nothing actually changes
        return;
    }
    compositingSuspended = suspend;
    finishCompositing();
    setupCompositing(); // will do nothing if suspended
    // notify decorations that composition state has changed
    if (hasDecorationPlugin()) {
        KDecorationFactory* factory = mgr->factory();
        factory->reset(SettingCompositing);
    }
    emit compositingToggled(!compositingSuspended);
}

void Workspace::resetCompositing()
{
    if (compositing()) {
        finishCompositing();
        QTimer::singleShot(0, this, SLOT(setupCompositing()));
    }
}

void Workspace::addRepaint(int x, int y, int w, int h)
{
    if (!compositing())
        return;
    repaints_region += QRegion(x, y, w, h);
    checkCompositeTimer();
}

void Workspace::addRepaint(const QRect& r)
{
    if (!compositing())
        return;
    repaints_region += r;
    checkCompositeTimer();
}

void Workspace::addRepaint(const QRegion& r)
{
    if (!compositing())
        return;
    repaints_region += r;
    checkCompositeTimer();
}

void Workspace::addRepaintFull()
{
    if (!compositing())
        return;
    repaints_region = QRegion(0, 0, displayWidth(), displayHeight());
    checkCompositeTimer();
}

void Workspace::timerEvent(QTimerEvent *te)
{
    if (te->timerId() == compositeTimer.timerId()) {
        compositeTimer.stop();
        performCompositing();
    } else
        QObject::timerEvent(te);
}

static int s_pendingFlushes = 0;

void Workspace::performCompositing()
{
    if (!scene->overlayWindow()->isVisible())
        return; // nothing is visible anyway

    bool pending = !repaints_region.isEmpty() || windowRepaintsPending();
    if (pending)
        s_pendingFlushes = 3;
    else if (scene->hasPendingFlush())
        --s_pendingFlushes;
    else
        s_pendingFlushes = 0;
    if (s_pendingFlushes < 1) {
        s_pendingFlushes = 0;
        scene->idle();
        // Note: It would seem here we should undo suspended unredirect, but when scenes need
        // it for some reason, e.g. transformations or translucency, the next pass that does not
        // need this anymore and paints normally will also reset the suspended unredirect.
        // Otherwise the window would not be painted normally anyway.
        return;
    }

    // create a list of all windows in the stacking order
    ToplevelList windows = xStackingOrder();
    foreach (EffectWindow *c, static_cast< EffectsHandlerImpl* >(effects)->elevatedWindows()) {
        Toplevel* t = static_cast< EffectWindowImpl* >(c)->window();
        windows.removeAll(t);
        windows.append(t);
    }

    // skip windows that are not yet ready for being painted
    // TODO ?
    // this cannot be used so carelessly - needs protections against broken clients, the window
    // should not get focus before it's displayed, handle unredirected windows properly and so on.
    foreach (Toplevel *t, windows)
        if (!t->readyForPainting())
            windows.removeAll(t);

    QRegion repaints = repaints_region;
    // clear all repaints, so that post-pass can add repaints for the next repaint
    repaints_region = QRegion();

    m_timeSinceLastVBlank = scene->paint(repaints, windows);

    // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle()
    // is called the next time. If there would be nothing pending, it will not restart the timer and
    // checkCompositeTime() would restart it again somewhen later, called from functions that
    // would again add something pending.
    checkCompositeTimer();
}

void Workspace::performMousePoll()
{
    checkCursorPos();
}

bool Workspace::windowRepaintsPending() const
{
    foreach (Toplevel * c, clients)
    if (!c->repaints().isEmpty())
        return true;
    foreach (Toplevel * c, desktops)
    if (!c->repaints().isEmpty())
        return true;
    foreach (Toplevel * c, unmanaged)
    if (!c->repaints().isEmpty())
        return true;
    foreach (Toplevel * c, deleted)
    if (!c->repaints().isEmpty())
        return true;
    return false;
}

void Workspace::setCompositeTimer()
{
    if (!compositing())  // should not really happen, but there may be e.g. some damage events still pending
        return;

    uint padding = m_timeSinceLastVBlank << 10;

    if (scene->waitSyncAvailable()) {

        // TODO: make vBlankTime dynamic?!
        // It's required because glXWaitVideoSync will *likely* block a full frame if one enters
        // a retrace pass which can last a variable amount of time, depending on the actual screen
        // Now, my ooold 19" CRT can do such retrace so that 2ms are entirely sufficient,
        // while another ooold 15" TFT requires about 6ms

        if (padding > fpsInterval) {
            // we're at low repaints or spent more time in painting than the user wanted to wait for that frame
            padding = vBlankInterval - (padding%vBlankInterval); // -> align to next vblank
        } else {  // -> align to the next maxFps tick
            padding = ((vBlankInterval - padding%vBlankInterval) + (fpsInterval/vBlankInterval-1)*vBlankInterval);
            //               "remaining time of the first vsync" + "time for the other vsyncs of the frame"
        }

        if (padding < options->vBlankTime()) { // we'll likely miss this frame
            m_nextFrameDelay = (padding + vBlankInterval) >> 10;
            padding = (padding + vBlankInterval - options->vBlankTime()) >> 10; // so we add one
//             qDebug() << "WE LOST A FRAME";
        } else {
            m_nextFrameDelay = padding >> 10;
            padding = (padding - options->vBlankTime()) >> 10;
        }
    }
    else // w/o vsync we just jump to the next demanded tick
        // the "1" will ensure we don't block out the eventloop - the system's just not faster
        // "0" would be sufficient, but the compositor isn't the WMs only task
        m_nextFrameDelay = padding = (padding > (int)fpsInterval) ? 1 : ((fpsInterval - padding) >> 10);
    compositeTimer.start(qMin(padding, 250u), this); // force 4fps minimum
}

void Workspace::startMousePolling()
{
    mousePollingTimer.start(20);   // 50Hz. TODO: How often do we really need to poll?
}

void Workspace::stopMousePolling()
{
    mousePollingTimer.stop();
}

bool Workspace::compositingActive()
{
    return !m_finishingCompositing && compositing();
}

// force is needed when the list of windows changes (e.g. a window goes away)
void Workspace::checkUnredirect(bool force)
{
    if (!compositing() || scene->overlayWindow()->window() == None || !options->isUnredirectFullscreen())
        return;
    if (force)
        forceUnredirectCheck = true;
    if (!unredirectTimer.isActive())
        unredirectTimer.start(0);
}

void Workspace::delayedCheckUnredirect()
{
    if (!compositing() || scene->overlayWindow()->window() == None || !options->isUnredirectFullscreen())
        return;
    ToplevelList list;
    bool changed = forceUnredirectCheck;
    foreach (Client * c, clients)
    list.append(c);
    foreach (Unmanaged * c, unmanaged)
    list.append(c);
    foreach (Toplevel * c, list) {
        if (c->updateUnredirectedState())
            changed = true;
    }
    // no desktops, no Deleted ones
    if (!changed)
        return;
    forceUnredirectCheck = false;
    // Cut out parts from the overlay window where unredirected windows are,
    // so that they are actually visible.
    QRegion reg(0, 0, displayWidth(), displayHeight());
    foreach (Toplevel * c, list) {
        if (c->unredirected())
            reg -= c->geometry();
    }
    scene->overlayWindow()->setShape(reg);
}


bool Workspace::compositingPossible() const
{
    return CompositingPrefs::compositingPossible();
}

QString Workspace::compositingNotPossibleReason() const
{
    return CompositingPrefs::compositingNotPossibleReason();
}

bool Workspace::openGLIsBroken() const
{
    return CompositingPrefs::openGlIsBroken();
}

QString Workspace::compositingType()
{
    // the returned strings are considered as identifiers and may not be translated
    if (!effects) {
        return "none";
    }
    if (effects->compositingType() == XRenderCompositing) {
        return "xrender";
    } else if (effects->compositingType() == OpenGLCompositing) {
#ifdef KWIN_HAVE_OPENGLES
        return "gles";
#else
        if (ShaderManager::instance()->isValid()) {
            return "gl2";
        } else {
            return "gl1";
        }
#endif
    }
    return "none";
}

//****************************************
// Toplevel
//****************************************

bool Toplevel::setupCompositing()
{
    if (!compositing())
        return false;
    damageRatio = 0.0;
    if (damage_handle != None)
        return false;
    damage_handle = XDamageCreate(display(), frameId(), XDamageReportRawRectangles);
    damage_region = QRegion(0, 0, width(), height());
    effect_window = new EffectWindowImpl(this);
    unredirect = false;
    workspace()->checkUnredirect(true);
    scene->windowAdded(this);
    return true;
}

void Toplevel::finishCompositing()
{
    damageRatio = 0.0;
    if (damage_handle == None)
        return;
    workspace()->checkUnredirect(true);
    if (effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data
        discardWindowPixmap();
        delete effect_window;
    }
    XDamageDestroy(display(), damage_handle);
    damage_handle = None;
    damage_region = QRegion();
    repaints_region = QRegion();
    effect_window = NULL;
}

void Toplevel::discardWindowPixmap()
{
    damageRatio = 0.0;
    addDamageFull();
    if (window_pix == None)
        return;
    XFreePixmap(display(), window_pix);
    window_pix = None;
    if (effectWindow() != NULL && effectWindow()->sceneWindow() != NULL)
        effectWindow()->sceneWindow()->pixmapDiscarded();
}

Pixmap Toplevel::createWindowPixmap()
{
    assert(compositing());
    if (unredirected())
        return None;
    damageRatio = 0.0;
    grabXServer();
    KXErrorHandler err;
    Pixmap pix = XCompositeNameWindowPixmap(display(), frameId());
    // check that the received pixmap is valid and actually matches what we
    // know about the window (i.e. size)
    XWindowAttributes attrs;
    if (!XGetWindowAttributes(display(), frameId(), &attrs)
            || err.error(false)
            || attrs.width != width() || attrs.height != height() || attrs.map_state != IsViewable) {
        kDebug(1212) << "Creating window pixmap failed: " << this;
        XFreePixmap(display(), pix);
        pix = None;
    }
    ungrabXServer();
    return pix;
}

// We must specify that the two events are a union so the compiler doesn't
// complain about strict aliasing rules.
typedef union {
    XEvent e;
    XDamageNotifyEvent de;
} EventUnion;

static QVector<QRect> damageRects;

void Toplevel::damageNotifyEvent(XDamageNotifyEvent* e)
{
    if (damageRatio == 1.0) { // we know that we're completely damaged, no need to tell us again
        while (XPending(display())) { // drop events
            EventUnion e2;
            if (XPeekEvent(display(), &e2.e) && e2.e.type == Extensions::damageNotifyEvent() &&
                    e2.e.xany.window == frameId()) {
                XNextEvent(display(), &e2.e);
                continue;
            }
            break;
        }

        return;
    }

    const float area = rect().width()*rect().height();
    damageRects.reserve(16);
    damageRects.erase(damageRects.begin(), damageRects.end());
    damageRects << QRect(e->area.x, e->area.y, e->area.width, e->area.height);

    // we can not easily say anything about the overall ratio since the new rects may intersect the present
    float newDamageRatio = damageRects.last().width()*damageRects.last().height() / area;

    // compress
    while (XPending(display())) {
        EventUnion e2;
        if (XPeekEvent(display(), &e2.e) && e2.e.type == Extensions::damageNotifyEvent()
                && e2.e.xany.window == frameId()) {
            XNextEvent(display(), &e2.e);
            if (damageRatio >= 0.8 || newDamageRatio > 0.8 || damageRects.count() > 15) {
                // If there are too many damage events in the queue, just discard them
                // and damage the whole window. Otherwise the X server can just overload
                // us with a flood of damage events. Should be probably optimized
                // in the X server, as this is rather lame.
                newDamageRatio = 1.0;
                damageRects.clear();
                continue;
            }
            damageRects << QRect(e2.de.area.x, e2.de.area.y, e2.de.area.width, e2.de.area.height);
            newDamageRatio += damageRects.last().width()*damageRects.last().height() / area;
            continue;
        }
        break;
    }


    if ((damageRects.count() == 1 && damageRects.last() == rect()) ||
        (damageRects.isEmpty() && newDamageRatio == 1.0)) {
        addDamageFull();
    } else {
        foreach (const QRect &r, damageRects)
            addDamage(r);
    }
}

void Client::damageNotifyEvent(XDamageNotifyEvent* e)
{
#ifdef HAVE_XSYNC
    if (syncRequest.isPending && isResize())
        return;
    if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead
        if (syncRequest.counter == None)   // cannot detect complete redraw, consider done now
            setReadyForPainting();
    }
#else
    if (!ready_for_painting) {
        setReadyForPainting();
    }
#endif

    Toplevel::damageNotifyEvent(e);
}

void Unmanaged::damageNotifyEvent(XDamageNotifyEvent* e)
{
    if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead
        setReadyForPainting();
    }
    Toplevel::damageNotifyEvent(e);
}

void Toplevel::addDamage(const QRect& r)
{
    addDamage(r.x(), r.y(), r.width(), r.height());
}

void Toplevel::addDamage(int x, int y, int w, int h)
{
    if (!compositing())
        return;
    QRect r(x, y, w, h);
    // resizing the decoration may lag behind a bit and when shrinking there
    // may be a damage event coming with size larger than the current window size
    r &= rect();
    if (r.isEmpty())
        return;
    damage_region += r;
    int damageArea = 0;
    foreach (const QRect &r2, damage_region.rects())
        damageArea += r2.width()*r2.height();
    damageRatio = float(damageArea) / float(rect().width()*rect().height());
    repaints_region += r;
    emit damaged(this, r);
    // discard lanczos texture
    if (effect_window) {
        QVariant cachedTextureVariant = effect_window->data(LanczosCacheRole);
        if (cachedTextureVariant.isValid()) {
            GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value<void*>());
            delete cachedTexture;
            cachedTexture = 0;
            effect_window->setData(LanczosCacheRole, QVariant());
        }
    }
    workspace()->checkCompositeTimer();
}

void Toplevel::addDamageFull()
{
    if (!compositing())
        return;
    damage_region = rect();
    repaints_region = rect();
    damageRatio = 1.0;
    emit damaged(this, rect());
    // discard lanczos texture
    if (effect_window) {
        QVariant cachedTextureVariant = effect_window->data(LanczosCacheRole);
        if (cachedTextureVariant.isValid()) {
            GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value<void*>());
            delete cachedTexture;
            cachedTexture = 0;
            effect_window->setData(LanczosCacheRole, QVariant());
        }
    }
    workspace()->checkCompositeTimer();
}

void Toplevel::resetDamage(const QRect& r)
{
    damage_region -= r;
    int damageArea = 0;
    foreach (const QRect &r2, damage_region.rects())
        damageArea += r2.width()*r2.height();
    damageRatio = float(damageArea) / float(rect().width()*rect().height());
}

void Toplevel::addRepaint(const QRect& r)
{
    if (!compositing()) {
        return;
    }
    repaints_region += r;
    workspace()->checkCompositeTimer();
}

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

void Toplevel::addRepaint(const QRegion& r)
{
    if (!compositing()) {
        return;
    }
    repaints_region += r;
    workspace()->checkCompositeTimer();
}

void Toplevel::addLayerRepaint(const QRect& r)
{
    if (!compositing()) {
        return;
    }
    layer_repaints_region += r;
    workspace()->checkCompositeTimer();
}

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

void Toplevel::addLayerRepaint(const QRegion& r)
{
    if (!compositing())
        return;
    layer_repaints_region += r;
    workspace()->checkCompositeTimer();
}

void Toplevel::addRepaintFull()
{
    repaints_region = visibleRect().translated(-pos());
    workspace()->checkCompositeTimer();
}

void Toplevel::resetRepaints()
{
    repaints_region = QRegion();
    layer_repaints_region = QRegion();
}

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

void Toplevel::addWorkspaceRepaint(const QRect& r2)
{
    if (!compositing())
        return;
    workspace()->addRepaint(r2);
}

bool Toplevel::updateUnredirectedState()
{
    assert(compositing());
    bool should = shouldUnredirect() && !unredirectSuspend && !shape() && !hasAlpha() && opacity() == 1.0 &&
                  !static_cast<EffectsHandlerImpl*>(effects)->activeFullScreenEffect();
    if (should && !unredirect) {
        unredirect = true;
        kDebug(1212) << "Unredirecting:" << this;
        XCompositeUnredirectWindow(display(), frameId(), CompositeRedirectManual);
        return true;
    } else if (!should && unredirect) {
        unredirect = false;
        kDebug(1212) << "Redirecting:" << this;
        XCompositeRedirectWindow(display(), frameId(), CompositeRedirectManual);
        discardWindowPixmap();
        return true;
    }
    return false;
}

void Toplevel::suspendUnredirect(bool suspend)
{
    if (unredirectSuspend == suspend)
        return;
    unredirectSuspend = suspend;
    workspace()->checkUnredirect();
}

//****************************************
// Client
//****************************************

bool Client::setupCompositing()
{
    if (!Toplevel::setupCompositing()){
        return false;
    }
    updateVisibility(); // for internalKeep()
    if (isManaged()) {
        // only create the decoration when a client is managed
        updateDecoration(true, true);
    }
    return true;
}

void Client::finishCompositing()
{
    Toplevel::finishCompositing();
    updateVisibility();
    if (!deleting) {
        // only recreate the decoration if we are not shutting down completely
        updateDecoration(true, true);
    }
    // for safety in case KWin is just resizing the window
    s_haveResizeEffect = false;
}

bool Client::shouldUnredirect() const
{
    if (isActiveFullScreen()) {
        ToplevelList stacking = workspace()->xStackingOrder();
        for (int pos = stacking.count() - 1;
                pos >= 0;
                --pos) {
            Toplevel* c = stacking.at(pos);
            if (c == this)   // is not covered by any other window, ok to unredirect
                return true;
            if (c->geometry().intersects(geometry()))
                return false;
        }
        abort();
    }
    return false;
}


//****************************************
// Unmanaged
//****************************************

bool Unmanaged::shouldUnredirect() const
{
    // the pixmap is needed for the login effect, a nicer solution would be the login effect increasing
    // refcount for the window pixmap (which would prevent unredirect), avoiding this hack
    if (resourceClass() == "ksplashx"
            || resourceClass() == "ksplashsimple"
            || resourceClass() == "ksplashqml"
            )
        return false;
// it must cover whole display or one xinerama screen, and be the topmost there
    if (geometry() == workspace()->clientArea(FullArea, geometry().center(), workspace()->currentDesktop())
            || geometry() == workspace()->clientArea(ScreenArea, geometry().center(), workspace()->currentDesktop())) {
        ToplevelList stacking = workspace()->xStackingOrder();
        for (int pos = stacking.count() - 1;
                pos >= 0;
                --pos) {
            Toplevel* c = stacking.at(pos);
            if (c == this)   // is not covered by any other window, ok to unredirect
                return true;
            if (c->geometry().intersects(geometry()))
                return false;
        }
        abort();
    }
    return false;
}

//****************************************
// Deleted
//****************************************

bool Deleted::shouldUnredirect() const
{
    return false;
}


} // namespace
