/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the tools module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qvfbx11view.h"
#include "x11keyfaker.h"
#include <qevent.h>
#include <QX11Info>
#include <QTimer>
#include <QProcess>
#include <QDebug>
#include <QUuid>
#include <QDataStream>
#include <QTemporaryFile>
#include <X11/Xlib.h>

QT_BEGIN_NAMESPACE

QVFbX11View::QVFbX11View
	(int id, int w, int h, int d, Rotation r, QWidget *parent)
    : QVFbAbstractView(parent)
{
    this->id = id;
    this->w = w;
    this->h = h;
    this->d = d;
    this->rotation = r;
    this->gr = 1.0;
    this->gg = 1.0;
    this->gb = 1.0;
    this->touchscreen = false;
    this->lcd = false;
    this->keyFaker = 0;
    this->xnest = 0;
    this->serverAuthFile = 0;
    this->shutdown = false;

    // Try to find Xephyr, as it is better than Xnest in many ways.
    if (QFile::exists("/usr/bin/Xephyr"))
        xserver = "/usr/bin/Xephyr";
    else if (QFile::exists("/usr/local/bin/Xephyr"))
        xserver = "/usr/local/bin/Xephyr";
    else if (QFile::exists("/usr/X11R6/bin/Xephyr"))
        xserver = "/usr/X11R6/bin/Xephyr";
    else
        xserver = "Xnest";
}

QVFbX11View::~QVFbX11View()
{
    shutdown = true;
    if (xnest) {
	xnest->terminate();
	xnestStopped();
    }
}

int QVFbX11View::displayId() const
{
    return id;
}

int QVFbX11View::displayWidth() const
{
    return ( (int)rotation & 0x01 ) ? h : w;
}

int QVFbX11View::displayHeight() const
{
    return ( (int)rotation & 0x01 ) ? w : h;
}

int QVFbX11View::displayDepth() const
{
    return d;
}

QVFbX11View::Rotation QVFbX11View::displayRotation() const
{
    return rotation;
}

void QVFbX11View::skinKeyPressEvent(int code, const QString&, bool)
{
    if (keyFaker)
	keyFaker->sendKeyEvent(code, true);
}

void QVFbX11View::skinKeyReleaseEvent(int code, const QString&, bool)
{
    if (keyFaker)
	keyFaker->sendKeyEvent(code, false);
}

void QVFbX11View::setGamma(double gr, double gg, double gb)
{
    // We remember the values, but don't do anything with them.
    this->gr = gr;
    this->gg = gg;
    this->gb = gb;
}

double QVFbX11View::gammaRed() const
{
    return gr;
}

double QVFbX11View::gammaGreen() const
{
    return gg;
}

double QVFbX11View::gammaBlue() const
{
    return gb;
}

void QVFbX11View::getGamma(int, QRgb& rgb)
{
    rgb = qRgb(255, 255, 255);
}

bool QVFbX11View::touchScreenEmulation() const
{
    return touchscreen;
}

bool QVFbX11View::lcdScreenEmulation() const
{
    return lcd;
}

int QVFbX11View::rate()
{
    // We don't support refresh rates, so return a default value.
    return 30;
}

bool QVFbX11View::animating() const
{
    // We don't support animation.
    return false;
}

QImage QVFbX11View::image() const
{
    // We don't support image capture.
    return QImage();
}

void QVFbX11View::setRate(int)
{
    // We don't support rate adjustments.
}


double QVFbX11View::zoomH() const
{
    // Zoom is not possible with Xnest.
    return 1.0;
}

double QVFbX11View::zoomV() const
{
    // Zoom is not possible with Xnest.
    return 1.0;
}

QSize QVFbX11View::sizeHint() const
{
    return QSize(w, h);
}

void QVFbX11View::setTouchscreenEmulation( bool flag )
{
    touchscreen = flag;
}

void QVFbX11View::setLcdScreenEmulation( bool flag )
{
    lcd = flag;
}

void QVFbX11View::setZoom( double, double )
{
    // Zoom is not possible with Xnest.
}

void QVFbX11View::setRotation( Rotation )
{
    // Rotation is not possible with Xnest.
}

void QVFbX11View::startAnimation( const QString& )
{
    // Animation is not supported.
}

void QVFbX11View::stopAnimation()
{
    // Animation is not supported.
}

// Generate a 16-byte magic cookie string.
static QString generateMagicCookie()
{
    static const char hexchars[] = "0123456789abcdef";
    QUuid uuid = QUuid::createUuid();
    QByteArray ba;
    QDataStream stream(&ba, QIODevice::WriteOnly);
    stream << uuid;
    QString value;
    foreach ( char ch, ba ) {
	value += QChar( hexchars[(ch >> 4) & 0x0F] );
	value += QChar( hexchars[ch & 0x0F] );
    }
    return value;
}

void QVFbX11View::showEvent(QShowEvent *e)
{
    if (!xnest)
	startXnest();
    QWidget::showEvent(e);
}

void QVFbX11View::keyPressEvent(QKeyEvent *e)
{
    if (keyFaker)
	keyFaker->sendKeyEvent(e->key(), true);
    QWidget::keyPressEvent(e);
}

void QVFbX11View::keyReleaseEvent(QKeyEvent *e)
{
    if (keyFaker)
	keyFaker->sendKeyEvent(e->key(), false);
    QWidget::keyReleaseEvent(e);
}

void QVFbX11View::startXnest()
{
    // Add authentication credentials to the XAUTHORITY file.
    QString cookie = generateMagicCookie();
    QStringList xauthargs;
    xauthargs += "add";
    xauthargs += ":" + QString::number(displayId());
    xauthargs += "MIT-MAGIC-COOKIE-1";
    xauthargs += cookie;
    if (QProcess::execute("xauth", xauthargs) != 0)
	qWarning() << "xauth: failed to add Xnest client authentication credentials";

    // Write the credentials to another authentication file for the server.
    serverAuthFile = new QTemporaryFile(this);
    QString authFilename;
    if (serverAuthFile->open()) {
	authFilename = serverAuthFile->fileName();
	serverAuthFile->close();
	xauthargs.clear();
	xauthargs += "-f";
	xauthargs += authFilename;
	xauthargs += "add";
	xauthargs += ":" + QString::number(displayId());
	xauthargs += "MIT-MAGIC-COOKIE-1";
	xauthargs += cookie;
	if (QProcess::execute("xauth", xauthargs) != 0)
	    qWarning() << "xauth: failed to add Xnest server authentication credentials";
    }

    // Create a raw X11 window to act as the Xnest's root window.
    // We cannot use winId() directly because qvfb is already
    // selecting for events that Xnest wants to select for.
    WId root = XCreateSimpleWindow
	(QX11Info::display(), winId(), 0, 0, w, h, 0,
	 BlackPixel(QX11Info::display(), QX11Info::appScreen()),
	 BlackPixel(QX11Info::display(), QX11Info::appScreen()));
    XMapWindow(QX11Info::display(), root);

    // Warn the user if the visual number looks wrong.  Xnest expects
    // its root window to be on the default visual.
    if (QX11Info::appVisual() != DefaultVisual(QX11Info::display(), QX11Info::appScreen())) {
	qWarning() << "*** Qt is not using the default visual.  Xnest may fail "
		      "with a BadMatch error.";
	qWarning() << "*** If it fails, then restart qvfb with \" -visual"
		   << DefaultVisual(QX11Info::display(), QX11Info::appScreen())
			    ->visualid << "\"";
    }

    // Make sure the root window is in the X server before Xnest starts.
    XSync(QX11Info::display(), False);

    // Start the Xnest process.
    xnest = new QProcess(this);
    connect(xnest, SIGNAL(error(QProcess::ProcessError)),
	    this, SLOT(xnestStopped()));
    connect(xnest, SIGNAL(finished(int,QProcess::ExitStatus)),
	    this, SLOT(xnestStopped()));
    QStringList args;
    args += "-auth";
    args += authFilename;
    if (!xserver.contains("Xephyr")) {
        args += "-geometry";
        args += QString::number(w) + "x" + QString::number(h) + "+0+0";
        args += "-depth";
        args += QString::number(d);
    }
    args += "-br";	    // Start Xnest with a black background.
    args += "-parent";
    args += "0x" + QString::number(root, 16);
    args += ":" + QString::number(displayId());
    xnest->setProcessChannelMode(QProcess::ForwardedChannels);
    xnest->start(xserver, args);
    //qDebug() << args;

    QTimer::singleShot(200, this, SLOT(startKeyFaker()));
}

void QVFbX11View::xnestStopped()
{
    if (!shutdown) {
	if (xnest && xnest->error() == QProcess::FailedToStart)
	    qWarning() << xserver << "could not be started";
	else
	    qWarning() << xserver << "stopped unexpectedly";
    }
    if (keyFaker) {
	delete keyFaker;
	keyFaker = 0;
    }
    if (xnest) {
	xnest->deleteLater();
	xnest = 0;

	QStringList xauthargs;
	xauthargs += "remove";
	xauthargs += ":" + QString::number(displayId());
	if (QProcess::execute("xauth", xauthargs) != 0)
	    qWarning() << "xauth: failed to remove Xnest authentication credentials";
    }
    if (serverAuthFile) {
	delete serverAuthFile;
	serverAuthFile = 0;
    }
}

void QVFbX11View::startKeyFaker()
{
    if (!keyFaker && xnest)
	keyFaker = new X11KeyFaker(":" + QString::number(displayId()), this);
}

QT_END_NAMESPACE
