/*
 *
 * Copyright (C) 2002-2003 George Staikos <staikos@kde.org>
 * Copyright (C) 2002 Richard Moore <rich@kde.org>
 * Copyright (C) 2004 Dirk Ziegelmeier <dziegel@gmx.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <qwidget.h>
#include <qspinbox.h>
#include <qradiobutton.h>
#include <qcheckbox.h>
#include <qevent.h>
#include <qdir.h>
#include <qsize.h>
#include <klocale.h>
#include <kdebug.h>
#include <assert.h>
#include <qcolor.h>
#include <qimage.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>

#include "kdetv_v4l.h"
#include "kdetv_grabber.h"
#include "channel.h"
#include "overlaycontroller.h"

#include <X11/Xlib.h>

#ifdef HAVE_XRANDR
#include <X11/extensions/Xrandr.h>
#endif

#include <kconfig.h>
#include <kscreensaver_vroot.h>

#include "v4ldev.h"
#include "v4ldevtuner.h"
#include "v4ldevcamera.h"

#include "qvideostream.h"

#include "kdetv.h"
#include "kdetvvideo/kdetvimage.h"
#include "kdetvvideo/kdetvimagefilter.h"
#include "kdetvvideo/kdetvformatconversionfilter.h"
#include "filtermanager.h"

#include "kdetv_v4l.moc"

static KdetvImage::ImageFormat qvideoformat2kdetvimageformat(QVideo::ImageFormat fmt);
static QVideo::ImageFormat kdetvimageformat2qvideoformat(KdetvImage::ImageFormat fmt);

// Dirk: Hmmm... Polymorphism using pointer-to-member functions... Ugly.
class V4LIntegerControl : public IntegerControl
{
public:
    V4LIntegerControl(const QString& uiname, const QString& internalName,
                      KdetvV4L* drv, int (V4LDev::* set)(int), int (V4LDev::* get)() const)
        : IntegerControl(uiname, internalName),
          _drv(drv),
          _set(set),
          _get(get)
    {
        advanced     = false;
        minimumValue = 0;
        maximumValue = 65535;
        defaultValue = 32768;
        step         = 1;
    }

    virtual ~V4LIntegerControl()
    {
    }

    virtual bool doSetValue(int value)
    {
        if(_drv->dev) {
            V4LGrabberLocker(_drv->g);
            return (_drv->dev->*(_set))(value) == 0;
        } else {
            return -1;
        }
    }

    virtual int value() const
    {
        if(_drv->dev) {
            V4LGrabberLocker(_drv->g);
            return (_drv->dev->*(_get))();
        } else {
            return -1;
        }
    }


private:
    KdetvV4L* _drv;
    int (V4LDev::* _set)(int);
    int (V4LDev::* _get)() const;
};

class KDWidget : public QWidget
{
public:
    KDWidget()
        : QWidget()
    {
        create(DefaultRootWindow(qt_xdisplay()), false, true);
    }

    virtual ~KDWidget()
    {
        destroy(false, false);
    }
};

KdetvV4L::KdetvV4L(Kdetv *ktv, QWidget *parent, const char *name)
    : KdetvSourcePlugin(ktv, "v4l", parent, name),
      _w(parent),
      _dtReg(new KDWidget),
      _winReg(parent),
      dev(0),
      g(0),
      _probed(false),
      _capturing(false),
      _gsn(0),
      _fieldTime(20000),
      _mostRecentField(KdetvImage::TYPE_INTERLACED_ODD)
{
    _vs = new QVideoStream(_w);
    _formatConversionFilter = new KdetvFormatConversionFilter();

    // figure out best method available
    QVideo::VideoMethod bestAvailable = QVideo::METHOD_XVSHM;
    if (!_vs->haveMethod(bestAvailable)) {
        bestAvailable = QVideo::METHOD_XV;
    }
    /* too unstable
    if (!_vs->haveMethod(bestAvailable)) {
        bestAvailable = QVideo::METHOD_GL;
    }
    */
    if (!_vs->haveMethod(bestAvailable)) {
        bestAvailable = QVideo::METHOD_XSHM;
    }
    if (!_vs->haveMethod(bestAvailable)) {
        bestAvailable = QVideo::METHOD_X11;
    }

    _cfg->setGroup("V4L Plugin");

    // don't annoy users that already configured plugin
    if (_cfg->hasKey("GD Method")) {
        _autoConfig = _cfg->readBoolEntry("Autoconfigure", false);
    } else {
        _autoConfig = _cfg->readBoolEntry("Autoconfigure", true);
    }

    if (_autoConfig) {
        _qvsMethod = bestAvailable;

        if ( (_qvsMethod == QVideo::METHOD_X11)  ||
             (_qvsMethod == QVideo::METHOD_XSHM)    ) {
            _useOverlay = true;
        } else {
            _useOverlay = false;
        }
        _changeRes = false;
        _fullFrameRate = false;
    } else {
        _qvsMethod = (QVideo::VideoMethod)_cfg->readNumEntry("GD Method", bestAvailable);

        // saved config may be invalid due to graphics card change
        if (!_vs->haveMethod(_qvsMethod)) {
            _qvsMethod = bestAvailable;
        }

        // Try to use overlay if we only have X11 display method
        if ( (_qvsMethod == QVideo::METHOD_X11)  ||
             (_qvsMethod == QVideo::METHOD_XSHM)    ) {
            _useOverlay = _cfg->readBoolEntry("Use Overlay", true);
        } else {
            _useOverlay = _cfg->readBoolEntry("Use Overlay", false);
        }

        _changeRes = _cfg->readBoolEntry("Change Screen Resolution", false);
        _fullFrameRate = _cfg->readBoolEntry("Full Frame Rate", false);
    }

    _vs->setMethod(_qvsMethod);

    /*
     * The signal to stop capture must be delivered while qApp is in its event loop
     * because the shutdown relies on a working Qt Library Mutex handshake.
     * When qApp is destroyed (at the end of main()), it destroys all QObjects,
     * but without locking the mutex.
     * This makes the thread shutdown crash because the _stop flag is set during object deletion,
     * so the thread can paint to a widget that is being deleted using a buffer that is being
     * deleted (deletion of tv screen and V4LGrabber).
     */
    connect(qApp, SIGNAL( aboutToQuit() ),
            this, SLOT( stopVideo() ) );

    connect( parent, SIGNAL( resized(int, int) ),
             this, SLOT( viewResized() ) );
    connect( parent, SIGNAL( moved(int, int) ),
             this, SLOT( viewMoved() ) );

    _controls.append(new V4LIntegerControl(i18n("Brightness"),
                                           "Brightness",
                                           this,
                                           &V4LDev::setBrightness,
                                           &V4LDev::brightness));
    _controls.append(new V4LIntegerControl(i18n("Contrast"),
                                           "Contrast",
                                           this,
                                           &V4LDev::setContrast,
                                           &V4LDev::contrast));
    _controls.append(new V4LIntegerControl(i18n("Hue"),
                                           "Hue",
                                           this,
                                           &V4LDev::setHue,
                                           &V4LDev::hue));
    _controls.append(new V4LIntegerControl(i18n("Saturation"),
                                           "Saturation",
                                           this,
                                           &V4LDev::setColour,
                                           &V4LDev::colour));
    _controls.append(new V4LIntegerControl(i18n("Whiteness"),
                                           "Whiteness",
                                           this,
                                           &V4LDev::setWhiteness,
                                           &V4LDev::whiteness));
    _controls.append(new V4LIntegerControl(i18n("Bass"),
                                           "Bass",
                                           this,
                                           &V4LDev::setBass,
                                           &V4LDev::bass));
    _controls.append(new V4LIntegerControl(i18n("Treble"),
                                           "Treble",
                                           this,
                                           &V4LDev::setTreble,
                                           &V4LDev::treble));

    kdDebug() << "Kdetv V4L plugin loaded successfully." << endl;
}


KdetvV4L::~KdetvV4L()
{
    stopVideo();
    delete dev;
    dev = 0;
    delete _vs;
    delete _dtReg;
}

// ---------------------------------------------------- Tuner

int KdetvV4L::setChannelProperties(const Channel::PropertyList& properties)
{
    setSource(properties["source"].toString());
    setEncoding(properties["encoding"].toString());
    setFrequency(properties["frequency"].toULongLong());
    return 0;
}

bool KdetvV4L::isTuner()
{
    V4LGrabberLocker l(g);
    return dev ? dev->isTuner() : false;
}

int KdetvV4L::signal()
{
    V4LGrabberLocker l(g);
    return dev ? dev->signal() : -1;
}

int KdetvV4L::frequency()
{
    V4LGrabberLocker l(g);
    if (dev && dev->isTuner())
        return 125 * static_cast<V4LTuner*>(dev)->freq() / 2;
    return -1;
}

void KdetvV4L::setFrequency( int freq )
{
    V4LGrabberLocker l(g);
    if ( !dev || !dev->isTuner() )
        return;
    static_cast<V4LTuner*>(dev)->setFreq(freq * 2 / 125);
}

int KdetvV4L::setSource( const QString &src )
{
    V4LGrabberLocker l(g);
    if ( !dev )
        return -1;

    int rc = dev->setSource( src );
    _source = dev->source();
    return rc;
}

int KdetvV4L::setEncoding( const QString &encoding )
{
    V4LGrabberLocker l(g);
    if ( !dev )
        return -1;

    int rc = dev->setEncoding( encoding );
    _encoding = dev->encoding();

    if(_encoding == "ntsc"     ||
       _encoding == "ntsc-jp"  ||
       _encoding == "pal-m"    ) {
        _fieldTime = 16683;
        // NTSC and PAL-M transmit ODD fields first
        _mostRecentField = KdetvImage::TYPE_INTERLACED_EVEN;
    } else {
        _fieldTime = 20000;
        // all others EVEN fields first
        _mostRecentField = KdetvImage::TYPE_INTERLACED_ODD;
    }

    if(g) {
        g->_fieldTime       = _fieldTime;
        g->_mostRecentField = _mostRecentField;
    }

    return rc;
}

// ---------------------------------------------------- Audio

/* ARGH, this doesn't work on my card.  It always returns 0! */
bool KdetvV4L::muted()
{
    V4LGrabberLocker l(g);
    if (dev)
        return dev->audioEnabled();
    return false;
}

void KdetvV4L::setMuted( bool muted )
{
    V4LGrabberLocker l(g);
    if ( !dev )
        return;

    if ( muted )
        dev->disableAudio();
    else
        dev->enableAudio();
}

bool KdetvV4L::setVolume( int left, int right )
{
    V4LGrabberLocker l(g);
    if ( !dev )
        return false;

    return dev->setVolume( (left+right)/2 ) == 0;
}

const QStringList& KdetvV4L::broadcastedAudioModes()
{
    V4LGrabberLocker l(g);
    static QStringList empty;

    if ( !dev )
        return empty;

    return dev->broadcastedAudioModes();
}

const QString& KdetvV4L::defaultAudioMode()
{
    const QStringList& modes = broadcastedAudioModes();

    // FIXME: a more intelligent and general algorithm would not hurt...
    if (modes.contains(i18n("Stereo"))) {
        return *modes.at(modes.findIndex(i18n("Stereo")));
    }

    if (modes.contains(i18n("Language 1"))) {
        return *modes.at(modes.findIndex(i18n("Language 1")));
    }

    return broadcastedAudioModes().first();
}

int KdetvV4L::setAudioMode( const QString& audioMode )
{
    V4LGrabberLocker l(g);
    if ( !dev )
        return -1;

    return dev->setAudioMode(audioMode);
}

// ---------------------------------------------------- Device

QColor KdetvV4L::colourKey()
{
    V4LGrabberLocker l(g);
    return dev ? dev->colourKey() : QColor();
}

Control::ControlList& KdetvV4L::controls()
{
    return _controls;
}

int KdetvV4L::setDevice( const QString &name )
{
    if (!_probed)
        probeDevices();

    if (dev) {
        stopVideo();
        delete dev;
    }

    _device = name;
    _currentDev = _devNames[name];
    kdDebug() << "V4L: setDevice [" << name
              << "] which maps to " << _currentDev << endl;

    dev = V4LDev::getDevice( _currentDev );
    kdDebug() << "V4L: Success? " << (dev ? "true" : "false") << endl;

    _audioModes.clear();
    if (dev) {
        _audioModes += dev->audioModes();
    }

    return dev ? 0 : -1;
}

int KdetvV4L::probeDevices()
{
    QString dev;
    struct stat sb;


    // don't probe multiple times, it's unnecessary and might yield incorrect
    // results if plugin is currently using the device
    if (_probed) return 0;

    int rc = stat("/dev/v4l", &sb);
    if (!rc && S_ISDIR(sb.st_mode) && !access("/dev/v4l", R_OK|X_OK)) {
        // DEVFS
        dev = "/dev/v4l/video%1";
    } else {
        // Normal V4L case
        dev = "/dev/video%1";
    }

    _devices.clear();
    _sources.clear();
    _tuners.clear();
    _encodings.clear();
    _devNames.clear();

    QString mainVideoDev = QString::null;
    // FIXME: when we test for devices, we must make sure that we can
    // actually use them too.  This fails for cases where something else
    // is using the device currently.
    if (!access("/dev/video", R_OK|W_OK)) {
        V4LDev *vd = V4LDev::getDevice("/dev/video");
        if (vd) {
            QString name = "Video4Linux: " + vd->name();
            _tuners[name] = vd->isTuner();
            _sources[name] = vd->sources();
            _encodings[name] = vd->encodings();
            _devices << name;
            _devNames[name] = "/dev/video";
            mainVideoDev = QDir("/dev/video").canonicalPath();
            delete vd;
        }
    }

    for (int i = 0; i <= 9; i++) {
        QString dn = dev.arg(i);
        if (dn != mainVideoDev && !access(dn.local8Bit(), R_OK|W_OK)) {
            V4LDev *vd = V4LDev::getDevice(dn);
            if (vd) {
                QString name = i18n("Video4Linux: ") + vd->name();
                _tuners[name] = vd->isTuner();
                _sources[name] = vd->sources();
                _encodings[name] = vd->encodings();
                _devices << name;
                _devNames[name] = dn;
                delete vd;
            }
        }
    }

    _probed = true;
    return 0;
}

// ---------------------------------------------------- Display

void KdetvV4L::viewMoved()
{
    V4LGrabberLocker l(g);
    if (!dev)
        return;

    if (dev->overlayOn()) {
        // Center the overlay video on the widget if
        // the widget is larger than the maximum capture size
        QSize maxSize = dev->getMaxImageSize();
        QSize wSize   = _w->size();
        int dx, dy;

        if (maxSize.width() < wSize.width()) {
            dx = (wSize.width() - maxSize.width()) / 2;
            wSize.setWidth(maxSize.width());
        } else {
            dx = 0;
        }

        if (maxSize.height() < wSize.height()) {
            dy = (wSize.height() - maxSize.height()) / 2;
            wSize.setHeight(maxSize.height());
        } else {
            dy = 0;
        }

        QRect scn = QApplication::desktop()->screenGeometry(_w);
        QRect g;
        g.moveTopLeft(_w->mapToGlobal(QPoint(dx, dy)));
        g.setSize(wSize);
        dev->setCaptureGeometry(g);
    }
}

void KdetvV4L::viewResized()
{
    V4LGrabberLocker l(g);
    if (!dev)
        return;

    if (dev->overlayOn()) {
        viewMoved();
    } else {
        dev->setImageSize(_w->width(), _w->height());
    }

    _vs->setSize(QSize(_w->width(), _w->height()));
}

void KdetvV4L::setFullscreen(bool fs)
{
    if ( !dev || !dev->overlayOn() || !_changeRes)
        return;

    if(fs) {
        _previousSize = setScreenResolution(dev->getMaxImageSize());
    } else {
        setScreenResolution(_previousSize);
    }
}

#ifdef HAVE_XRANDR
QSize KdetvV4L::setScreenResolution(const QSize& size)
{
    Display* dpy  = qt_xdisplay();
    int      scn  = QApplication::desktop()->screenNumber(_w);
    Window   root = QApplication::desktop()->screen(scn)->winId();

    // First check for XRANDR extension and refresh sizes array
    int foo, bar;
    int nrandr = 0;
    XRRScreenSize* randr = 0;

    if (XRRQueryExtension(dpy, &foo, &bar)) {
        randr = XRRSizes(dpy, scn, &nrandr);
    }
    if (nrandr == 0) {
        kdWarning() << "KdetvV4L: No XRANDR available. Cannot change resolution." << endl;
        return QSize();
    }

    XRRScreenConfiguration* sc = XRRGetScreenInfo(dpy, root);
    Rotation rotation;
    SizeID current = XRRConfigCurrentConfiguration(sc, &rotation);
    SizeID newRes = current;

    // Find closest match for the desired size
    int mindist = 1000000;
    for (SizeID i=0; i<nrandr; i++) {
        int d1   = randr[i].width  - size.width();
        int d2   = randr[i].height - size.height();
        int dist = d1 + d2;

        if( (d1>=0) && (d2>=0) && (dist<mindist) ) {
            mindist = dist;
            newRes  = i;
        }
    }
    if (newRes != current) {
        kdDebug() << "KdetvV4L: XRANDR: switch to " << randr[newRes].width << "x" << randr[newRes].height << endl;
        XRRSetScreenConfig(dpy, sc, root, newRes, rotation, CurrentTime);
    }

    XRRFreeScreenConfigInfo(sc);

    return QSize(randr[current].width, randr[current].height);
}
#else // HAVE_XRANDR
QSize KdetvV4L::setScreenResolution(const QSize&)
{
    return QSize();
}
#endif // HAVE_XRANDR

// ---------------------------------------------------- Playback

int KdetvV4L::startVideo()
{
    int rc = 0;
    if (!dev || g || _capturing) {
        kdWarning() << "Error starting video: " << (void *)dev << " " << (void*)g << endl;
        return -1;
    }

    dev->setImageSize(_w->width(), _w->height());
    if (_useOverlay && dev->canOverlay()) {
        dev->setInputFormat(QVideo::FORMAT_YUYV);
        dev->setColourKey(0x0000ff00); // FIXME: we need to do proper colorkey for devices that support it.

        _overlayController = new OverlayController(_w);
        connect(_overlayController, SIGNAL( updateClipping() ),
                this, SLOT( updateClipping() ));
        connect(_overlayController, SIGNAL( moved() ),
                this, SLOT( viewMoved() ));
        connect(_overlayController, SIGNAL( repaintScreen() ),
                this, SLOT( repaintScreen() ));
        connect(_overlayController, SIGNAL( enableVideo(bool) ),
                this, SLOT( enableOverlay(bool) ));

        rc = enableOverlay(true);
    } else {
        KdetvImageFilterChain* c = driver()->filterManager()->filterChain();
        _vs->setMethod(_qvsMethod);
        _vs->setSize(_w->size());

        calculateGrabFormat(c, _formatConversionFilter);

        kdDebug() << c->filterChainStatus() << endl;

        g = new V4LGrabber(this, dev, _vs, ++_gsn);
        g->_f               = qvideoformat2kdetvimageformat(dev->inputFormat());
        g->_flt             = c;
        g->_fmtConv         = _formatConversionFilter;
        g->_fieldTime       = _fieldTime;
        g->_mostRecentField = _mostRecentField;
        g->_fullFrameRate   = _fullFrameRate;
        g->start();
    }

    if (rc == 0)
        setMuted(false);

    _capturing = true;
    return rc;
}

int KdetvV4L::stopVideo()
{
    if (!_capturing)
        return -1;

    setMuted(true);

    if (g) {
        g->stop();
        g = 0;
    } else {  // overlay
        delete _overlayController;
    }
    _capturing = false;
    return 0;
}

bool KdetvV4L::videoPlaying() const
{
    return _capturing;
}

void KdetvV4L::calculateGrabFormat(KdetvImageFilterChain* c, KdetvFormatConversionFilter* f)
{
    KdetvImage::ImageFormat outputFormat = qvideoformat2kdetvimageformat(_vs->formatsForMethod(_qvsMethod));

    kdDebug() << "Trying to build output chain without conversion..." << endl;
    c->setOutputFormat(outputFormat);

    // Try grabbing the format the display needs (-> hopefully no conversion necessary)
    if(dev->setInputFormat(_vs->formatsForMethod(_qvsMethod))) {
        c->setInputFormat(qvideoformat2kdetvimageformat(dev->inputFormat()));
        if(c->isValid()) {
            f->setInputFormat(outputFormat);
            f->setOutputFormat(outputFormat);
            kdDebug() << "... successful." << endl;
            return;
        }
    }

    // No success, try using a use format conversion filter
    kdDebug() << "... failed. Trying to use format converter..." << endl;
    KdetvImage::ImageFormat supportedFormats = f->inputFormats();
    KdetvImage::ImageFormat format = (KdetvImage::ImageFormat)1;
    do {
        if(supportedFormats & format) {
            f->setInputFormat(format);

            KdetvImage::ImageFormat fmt = (KdetvImage::ImageFormat)1;
            do {
                if(f->outputFormats() & c->inputFormats() & fmt) {
                    if(dev->setInputFormat(kdetvimageformat2qvideoformat(format))) {
                        kdDebug() << "Trying to grab " << KdetvImage::toString(format)
                                  << " and convert it to " << KdetvImage::toString(fmt) << endl;
                        f->setOutputFormat(fmt);
                        c->setInputFormat(fmt);
                        if(c->isValid()) {
                            return;
                        }
                    }
                }
                fmt = (KdetvImage::ImageFormat)(fmt<<1);
            } while((unsigned)fmt<0x80000000);
        }
        format = (KdetvImage::ImageFormat)(format<<1);
    } while((unsigned)format<0x80000000);

    // No success. Use display format for grabbing and pray it works.
    kdWarning() << "... failed. kdetv likely does not to work with your device and/or your current filter config." << endl;
    dev->setInputFormat(_vs->formatsForMethod(_qvsMethod));
    c->setInputFormat(qvideoformat2kdetvimageformat(dev->inputFormat()));
}

// ---------------------------------------------------- View modes

bool KdetvV4L::canVideoDesktop() const
{
    return true;
}

int KdetvV4L::setVideoDesktop(bool on)
{
    if (!dev)
        return -1;

    V4LGrabberLocker l(g);
    if (on) {
        _wReg = _vs->width();
        _hReg = _vs->height();
        stopVideo();
        _w = _dtReg;
        delete _vs;
        _vs = new QVideoStream(_w);
        _vs->setMethod(_qvsMethod);
        viewResized();
        _isVideoDesktop = true;
        startVideo();
        setMuted(false);
        _capturing = true;
    } else {
        if (!_isVideoDesktop)
            return -1;
        _isVideoDesktop = false;
        stopVideo();
        setMuted(true);
        _dtReg->update();
        _w = _winReg;
        delete _vs;
        _vs = new QVideoStream(_w);
        _vs->setMethod(_qvsMethod);
        viewResized();
        return startVideo();
    }

    return 0;
}

// ---------------------------------------------------- Snapshots

bool KdetvV4L::canGrabStill() const
{
    // Is this dangerous?  We can't use a mutex here though...
    return dev && dev->canGrab();
}

bool KdetvV4L::grabStill(QImage* img)
{
    V4LGrabberLocker l(g);
    if (dev && dev->canGrab()) {
        KdetvImage im;
        im.createBuffer(img->width() * img->height() * 4);
        im.setFormat(KdetvImage::FORMAT_BGR24);

        bool overlayWasOn = _capturing && !g;

        if (overlayWasOn) {
            enableOverlay(false);
        }

        dev->setInputFormat(QVideo::FORMAT_BGR24);
        dev->setImageSize(img->size());

        // Capture two consecutive frames without failure.
        // Necessary for good grab image quality with bt8x8 cards.
        bool rc      = false;
        bool lastrc  = false;
        bool newrc   = false;
        int  timeout = 20;
        do {
            im.setSize(dev->grab(im.buffer()));
            newrc = im.size().isValid();
            if (lastrc && newrc) {
                rc = true;
            }
            lastrc = newrc;
        } while (!rc && (timeout-- > 0));

        if (!rc || !im.toQImage(*img)) {
            rc = false;
        }

        dev->setInputFormat(_vs->formatsForMethod(_qvsMethod));
        dev->setImageSize(_w->size());

        if (overlayWasOn) {
            enableOverlay(true);
        }

        if (rc) {
            return true;
        }
    }
    return false;
}

// ---------------------------------------------------- Overlay

void KdetvV4L::updateClipping()
{
    Display *dpy = qt_xdisplay();
    Window win = _w->winId();
    Window root;
    Window rroot, parent, *children;
    unsigned int nchildren = 0;
    unsigned int i = 0;

    int scn = QApplication::desktop()->screenNumber(_w);
    root    = QApplication::desktop()->screen(scn)->winId();

    // Find the window just below the root.
    for (;;) {
        if (!XQueryTree(dpy, win, &rroot, &parent, &children, &nchildren))
            return;
        XFree(children);

        if (root == parent)
            break;
        win = parent;
    }

    if (!XQueryTree(dpy, root, &rroot, &parent, &children, &nchildren)) {
        return;
    }

    // Find the root children that come after the window we just found
    for  (i = 0; i < nchildren; i++ ) {
        if ( children[i] == win ) {
            break;
        }
    }

    QRect wgeom = _w->geometry();
    QRect geom;
    QPoint ul = _w->mapToGlobal(wgeom.topLeft());
    QPoint lr = _w->mapToGlobal(wgeom.bottomRight());

    dev->clearClips();

    // Go through the rest and see if they overlap.  If so, clip.
    for (i++; i < nchildren; i++) {
        XWindowAttributes wattrs;

        XGetWindowAttributes(dpy, children[i], &wattrs);
        if (!(wattrs.map_state & IsViewable))
            continue;

        if (wattrs.x + wattrs.width < ul.x()  || wattrs.x > lr.x() ||
            wattrs.y + wattrs.height < ul.y() || wattrs.y > lr.y())
            continue;

        geom = QApplication::desktop()->screenGeometry(QRect(wattrs.x, wattrs.y, wattrs.width, wattrs.height).center());
        wattrs.x -= geom.x();
        wattrs.y -= geom.y();
        //        printf("Found a sibling of the view: %d %d %dx%d\n", wattrs.x, wattrs.y, wattrs.width, wattrs.height);

        dev->addClip(QRect(wattrs.x, wattrs.y, wattrs.width, wattrs.height));
    }

    XFree(children);

    if (XQueryTree(dpy, _w->winId(), &rroot, &parent, &children, &nchildren)) {
        // Find the root children that come after the window we just found
        for  (i = 0; i < nchildren; i++ ) {
            XWindowAttributes wattrs;

            XGetWindowAttributes(dpy, children[i], &wattrs);
            if (!(wattrs.map_state & IsViewable))
                continue;

            QPoint wpt(wattrs.x, wattrs.y);
            wpt = _w->mapToGlobal(wpt);
            wattrs.x = wpt.x();
            wattrs.y = wpt.y();
            if (wattrs.x + wattrs.width  < ul.x() || wattrs.x > lr.x() ||
                wattrs.y + wattrs.height < ul.y() || wattrs.y > lr.y())
                continue;

            geom = QApplication::desktop()->screenGeometry(QRect(wattrs.x, wattrs.y, wattrs.width, wattrs.height).center());
            wattrs.x -= geom.x();
            wattrs.y -= geom.y();
            //            printf("Found a child of the view: %d %d %dx%d\n", wattrs.x, wattrs.y, wattrs.width, wattrs.height);

            dev->addClip(QRect(wattrs.x, wattrs.y, wattrs.width, wattrs.height));
        }
        XFree(children);
    }

    dev->reClip();
}

void KdetvV4L::repaintScreen()
{
    QRect   geom = QApplication::desktop()->screenGeometry(_w);
    Display *dpy = qt_xdisplay();

    // repaint the region to clean it up
    XSetWindowAttributes xwattr;
    xwattr.override_redirect = true;
    xwattr.backing_store = NotUseful;
    xwattr.save_under = false;
    unsigned long mask = CWSaveUnder | CWBackingStore | CWOverrideRedirect;

    int scn = QApplication::desktop()->screenNumber(_w);
    Window pwin = XCreateWindow(dpy,
                                QApplication::desktop()->screen(scn)->winId(),
                                geom.x(), geom.y(),
                                geom.width(), geom.height(),
                                0,
                                CopyFromParent, InputOutput, CopyFromParent,
                                mask, &xwattr);
    XMapWindow(dpy, pwin);
    XUnmapWindow(dpy, pwin);
    XDestroyWindow(dpy, pwin);

    QApplication::flushX();
}

int KdetvV4L::enableOverlay(bool enable)
{
    if (enable) {
        QPoint qp = _w->mapToGlobal(QPoint(0, 0));
        qp -= QApplication::desktop()->screenGeometry(_w).topLeft();
        int rc = dev->startCapture(qp.x(), qp.y());
        viewMoved();
        return rc;
    } else {
        return dev->stopCapture();
    }
}

// ---------------------------------------------------- Configuration

QWidget *KdetvV4L::configWidget(QWidget *parent, const char *name)
{
    _cfgWidget = new V4LPluginCfg(parent, name);

    _cfgWidget->_xvideo->setEnabled(_vs->haveMethod(QVideo::METHOD_XV));
    _cfgWidget->_xvshm->setEnabled (_vs->haveMethod(QVideo::METHOD_XVSHM));
    _cfgWidget->_x11->setEnabled   (_vs->haveMethod(QVideo::METHOD_X11));
    _cfgWidget->_x11shm->setEnabled(_vs->haveMethod(QVideo::METHOD_XSHM));
    _cfgWidget->_gl->setEnabled    (_vs->haveMethod(QVideo::METHOD_GL));

    switch(_qvsMethod) {
    case QVideo::METHOD_XV:
        _cfgWidget->_xvideo->setChecked(true);
        break;
    case QVideo::METHOD_XVSHM:
        _cfgWidget->_xvshm->setChecked(true);
        break;
    case QVideo::METHOD_X11:
        _cfgWidget->_x11->setChecked(true);
        break;
    case QVideo::METHOD_XSHM:
        _cfgWidget->_x11shm->setChecked(true);
        break;
    case QVideo::METHOD_GL:
        _cfgWidget->_gl->setChecked(true);
        break;
    default:
        assert(0);
        break;
    }

    _cfgWidget->_autoConfig->setChecked(_autoConfig);
    _cfgWidget->_changeRes->setChecked(_changeRes);
    _cfgWidget->_overlay->setChecked(_useOverlay);
    if(_fullFrameRate) {
        _cfgWidget->_frameRateFull->setChecked(true);
    } else {
        _cfgWidget->_frameRateHalf->setChecked(true);
    }

    return _cfgWidget;
}

void KdetvV4L::saveConfig()
{
    _changeRes     = _cfgWidget->_changeRes->isChecked();
    _useOverlay    = _cfgWidget->_overlay->isChecked();
    _autoConfig    = _cfgWidget->_autoConfig->isChecked();
    _fullFrameRate = _cfgWidget->_frameRateFull->isChecked();

    _qvsMethod = QVideo::METHOD_NONE;
    if (_cfgWidget->_xvideo->isChecked()) {
        _qvsMethod = QVideo::METHOD_XV;
    } else if (_cfgWidget->_xvshm->isChecked()) {
        _qvsMethod = QVideo::METHOD_XVSHM;
    } else if (_cfgWidget->_x11shm->isChecked()) {
        _qvsMethod = QVideo::METHOD_XSHM;
    } else if (_cfgWidget->_x11->isChecked()) {
        _qvsMethod = QVideo::METHOD_X11;
    } else if (_cfgWidget->_gl->isChecked()) {
        _qvsMethod = QVideo::METHOD_GL;
    }

    _cfg->writeEntry("Change Screen Resolution", _changeRes);
    _cfg->writeEntry("Use Overlay", _useOverlay);
    _cfg->writeEntry("GD Method", _qvsMethod);
    _cfg->writeEntry("Autoconfigure", _autoConfig);
    _cfg->writeEntry("Full Frame Rate", _fullFrameRate);

    if (_capturing) {
        stopVideo();
        _vs->setMethod(_qvsMethod);
        startVideo();
    } else {
        _vs->setMethod(_qvsMethod);
    }

    _cfg->sync();
}

// ---------------------------------------------------- Misc

static KdetvImage::ImageFormat qvideoformat2kdetvimageformat(QVideo::ImageFormat fmt)
{
    if(fmt & QVideo::FORMAT_GREY) {
        return KdetvImage::FORMAT_GREY;
    }
    if(fmt & QVideo::FORMAT_HI240) {
        return KdetvImage::FORMAT_HI240;
    }
    if(fmt & QVideo::FORMAT_RGB15_LE) {
        return KdetvImage::FORMAT_RGB15_LE;
    }
    if(fmt & QVideo::FORMAT_RGB16_LE) {
        return KdetvImage::FORMAT_RGB16_LE;
    }
    if(fmt & QVideo::FORMAT_RGB15_BE) {
        return KdetvImage::FORMAT_RGB15_BE;
    }
    if(fmt & QVideo::FORMAT_RGB16_BE) {
        return KdetvImage::FORMAT_RGB16_BE;
    }
    if(fmt & QVideo::FORMAT_RGB24) {
        return KdetvImage::FORMAT_RGB24;
    }
    if(fmt & QVideo::FORMAT_RGB32) {
        return KdetvImage::FORMAT_RGB32;
    }
    if(fmt & QVideo::FORMAT_BGR24) {
        return KdetvImage::FORMAT_BGR24;
    }
    if(fmt & QVideo::FORMAT_BGR32) {
        return KdetvImage::FORMAT_BGR32;
    }
    if(fmt & QVideo::FORMAT_YUYV) {
        return KdetvImage::FORMAT_YUYV;
    }
    if(fmt & QVideo::FORMAT_UYVY) {
        return KdetvImage::FORMAT_UYVY;
    }
    if(fmt & QVideo::FORMAT_YUV422P) {
        return KdetvImage::FORMAT_YUV422P;
    }
    if(fmt & QVideo::FORMAT_YUV420P) {
        return KdetvImage::FORMAT_YUV420P;
    }
    return KdetvImage::FORMAT_NONE;
}

static QVideo::ImageFormat kdetvimageformat2qvideoformat(KdetvImage::ImageFormat fmt)
{
    if(fmt & KdetvImage::FORMAT_GREY) {
        return QVideo::FORMAT_GREY;
    }
    if(fmt & KdetvImage::FORMAT_HI240) {
        return QVideo::FORMAT_HI240;
    }
    if(fmt & KdetvImage::FORMAT_RGB15_LE) {
        return QVideo::FORMAT_RGB15_LE;
    }
    if(fmt & KdetvImage::FORMAT_RGB16_LE) {
        return QVideo::FORMAT_RGB16_LE;
    }
    if(fmt & KdetvImage::FORMAT_RGB15_BE) {
        return QVideo::FORMAT_RGB15_BE;
    }
    if(fmt & KdetvImage::FORMAT_RGB16_BE) {
        return QVideo::FORMAT_RGB16_BE;
    }
    if(fmt & KdetvImage::FORMAT_RGB24) {
        return QVideo::FORMAT_RGB24;
    }
    if(fmt & KdetvImage::FORMAT_RGB32) {
        return QVideo::FORMAT_RGB32;
    }
    if(fmt & KdetvImage::FORMAT_BGR24) {
        return QVideo::FORMAT_BGR24;
    }
    if(fmt & KdetvImage::FORMAT_BGR32) {
        return QVideo::FORMAT_BGR32;
    }
    if(fmt & KdetvImage::FORMAT_YUYV) {
        return QVideo::FORMAT_YUYV;
    }
    if(fmt & KdetvImage::FORMAT_UYVY) {
        return QVideo::FORMAT_UYVY;
    }
    if(fmt & KdetvImage::FORMAT_YUV422P) {
        return QVideo::FORMAT_YUV422P;
    }
    if(fmt & KdetvImage::FORMAT_YUV420P) {
        return QVideo::FORMAT_YUV420P;
    }
    return QVideo::FORMAT_NONE;
}

bool KdetvV4L::event(QEvent* e)
{
    if (e->type() == QEvent::User) {
        emit errorMessage( ((V4LErrorEvent*)e)->_msg );
        stopVideo();
        return true;
    } else {
        return KdetvSourcePlugin::event(e);
    }
}

// ---------------------------------------------------- Factory

extern "C" {
    KDETV_EXPORT KdetvV4L* create_v4l(Kdetv *ktv, QWidget *w) {
        return new KdetvV4L(ktv, w, "v4l plugin");
    }
}
