/****************************************************************************
**
** 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 QtDeclarative 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 "private/qdeclarativedebugservice_p.h"
#include "private/qpacketprotocol_p.h"
#include <QtCore/qdebug.h>
#include <QtNetwork/qtcpserver.h>
#include <QtNetwork/qtcpsocket.h>
#include <QtCore/qstringlist.h>
#include <private/qobject_p.h>
QT_BEGIN_NAMESPACE
class QDeclarativeDebugServerPrivate;
class QDeclarativeDebugServer : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QDeclarativeDebugServer)
Q_DISABLE_COPY(QDeclarativeDebugServer)
public:
static QDeclarativeDebugServer *instance();
void listen();
void waitForConnection();
bool hasDebuggingClient() const;
private Q_SLOTS:
void readyRead();
void newConnection();
private:
friend class QDeclarativeDebugService;
friend class QDeclarativeDebugServicePrivate;
QDeclarativeDebugServer(int);
};
class QDeclarativeDebugServerPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QDeclarativeDebugServer)
public:
QDeclarativeDebugServerPrivate();
int port;
QTcpSocket *connection;
QPacketProtocol *protocol;
QHash<QString, QDeclarativeDebugService *> plugins;
QStringList enabledPlugins;
QTcpServer *tcpServer;
bool gotHello;
};
class QDeclarativeDebugServicePrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QDeclarativeDebugService)
public:
QDeclarativeDebugServicePrivate();
QString name;
QDeclarativeDebugServer *server;
};
QDeclarativeDebugServerPrivate::QDeclarativeDebugServerPrivate()
: connection(0), protocol(0), gotHello(false)
{
}
void QDeclarativeDebugServer::listen()
{
Q_D(QDeclarativeDebugServer);
d->tcpServer = new QTcpServer(this);
QObject::connect(d->tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
if (d->tcpServer->listen(QHostAddress::Any, d->port))
qWarning("QDeclarativeDebugServer: Waiting for connection on port %d...", d->port);
else
qWarning("QDeclarativeDebugServer: Unable to listen on port %d", d->port);
}
void QDeclarativeDebugServer::waitForConnection()
{
Q_D(QDeclarativeDebugServer);
d->tcpServer->waitForNewConnection(-1);
}
void QDeclarativeDebugServer::newConnection()
{
Q_D(QDeclarativeDebugServer);
if (d->connection) {
qWarning("QDeclarativeDebugServer error: another client is already connected");
QTcpSocket *faultyConnection = d->tcpServer->nextPendingConnection();
delete faultyConnection;
return;
}
d->connection = d->tcpServer->nextPendingConnection();
d->connection->setParent(this);
d->protocol = new QPacketProtocol(d->connection, this);
QObject::connect(d->protocol, SIGNAL(readyRead()), this, SLOT(readyRead()));
}
bool QDeclarativeDebugServer::hasDebuggingClient() const
{
Q_D(const QDeclarativeDebugServer);
return d->gotHello;
}
QDeclarativeDebugServer *QDeclarativeDebugServer::instance()
{
static bool envTested = false;
static QDeclarativeDebugServer *server = 0;
if (!envTested) {
envTested = true;
QByteArray env = qgetenv("QML_DEBUG_SERVER_PORT");
QByteArray block = qgetenv("QML_DEBUG_SERVER_BLOCK");
bool ok = false;
int port = env.toInt(&ok);
if (ok && port > 1024) {
server = new QDeclarativeDebugServer(port);
server->listen();
if (!block.isEmpty()) {
server->waitForConnection();
}
}
}
return server;
}
QDeclarativeDebugServer::QDeclarativeDebugServer(int port)
: QObject(*(new QDeclarativeDebugServerPrivate))
{
Q_D(QDeclarativeDebugServer);
d->port = port;
}
void QDeclarativeDebugServer::readyRead()
{
Q_D(QDeclarativeDebugServer);
if (!d->gotHello) {
QPacket hello = d->protocol->read();
QString name;
hello >> name >> d->enabledPlugins;
if (name != QLatin1String("QDeclarativeDebugServer")) {
qWarning("QDeclarativeDebugServer: Invalid hello message");
QObject::disconnect(d->protocol, SIGNAL(readyRead()), this, SLOT(readyRead()));
d->protocol->deleteLater();
d->protocol = 0;
d->connection->deleteLater();
d->connection = 0;
return;
}
d->gotHello = true;
qWarning("QDeclarativeDebugServer: Connection established");
}
QString debugServer(QLatin1String("QDeclarativeDebugServer"));
while (d->protocol->packetsAvailable()) {
QPacket pack = d->protocol->read();
QString name;
pack >> name;
if (name == debugServer) {
int op = -1; QString plugin;
pack >> op >> plugin;
if (op == 1) {
// Enable
if (!d->enabledPlugins.contains(plugin)) {
d->enabledPlugins.append(plugin);
QHash<QString, QDeclarativeDebugService *>::Iterator iter =
d->plugins.find(plugin);
if (iter != d->plugins.end())
(*iter)->enabledChanged(true);
}
} else if (op == 2) {
// Disable
if (d->enabledPlugins.contains(plugin)) {
d->enabledPlugins.removeAll(plugin);
QHash<QString, QDeclarativeDebugService *>::Iterator iter =
d->plugins.find(plugin);
if (iter != d->plugins.end())
(*iter)->enabledChanged(false);
}
} else {
qWarning("QDeclarativeDebugServer: Invalid control message %d", op);
}
} else {
QByteArray message;
pack >> message;
QHash<QString, QDeclarativeDebugService *>::Iterator iter =
d->plugins.find(name);
if (iter == d->plugins.end()) {
qWarning() << "QDeclarativeDebugServer: Message received for missing plugin" << name;
} else {
(*iter)->messageReceived(message);
}
}
}
}
QDeclarativeDebugServicePrivate::QDeclarativeDebugServicePrivate()
: server(0)
{
}
QDeclarativeDebugService::QDeclarativeDebugService(const QString &name, QObject *parent)
: QObject(*(new QDeclarativeDebugServicePrivate), parent)
{
Q_D(QDeclarativeDebugService);
d->name = name;
d->server = QDeclarativeDebugServer::instance();
if (!d->server)
return;
if (d->server->d_func()->plugins.contains(name)) {
qWarning() << "QDeclarativeDebugService: Conflicting plugin name" << name;
d->server = 0;
} else {
d->server->d_func()->plugins.insert(name, this);
}
}
QString QDeclarativeDebugService::name() const
{
Q_D(const QDeclarativeDebugService);
return d->name;
}
bool QDeclarativeDebugService::isEnabled() const
{
Q_D(const QDeclarativeDebugService);
return (d->server && d->server->d_func()->enabledPlugins.contains(d->name));
}
namespace {
struct ObjectReference
{
QPointer<QObject> object;
int id;
};
struct ObjectReferenceHash
{
ObjectReferenceHash() : nextId(0) {}
QHash<QObject *, ObjectReference> objects;
QHash<int, QObject *> ids;
int nextId;
};
}
Q_GLOBAL_STATIC(ObjectReferenceHash, objectReferenceHash);
/*!
Returns a unique id for \a object. Calling this method multiple times
for the same object will return the same id.
*/
int QDeclarativeDebugService::idForObject(QObject *object)
{
if (!object)
return -1;
ObjectReferenceHash *hash = objectReferenceHash();
QHash<QObject *, ObjectReference>::Iterator iter =
hash->objects.find(object);
if (iter == hash->objects.end()) {
int id = hash->nextId++;
hash->ids.insert(id, object);
iter = hash->objects.insert(object, ObjectReference());
iter->object = object;
iter->id = id;
} else if (iter->object != object) {
int id = hash->nextId++;
hash->ids.remove(iter->id);
hash->ids.insert(id, object);
iter->object = object;
iter->id = id;
}
return iter->id;
}
/*!
Returns the object for unique \a id. If the object has not previously been
assigned an id, through idForObject(), then 0 is returned. If the object
has been destroyed, 0 is returned.
*/
QObject *QDeclarativeDebugService::objectForId(int id)
{
ObjectReferenceHash *hash = objectReferenceHash();
QHash<int, QObject *>::Iterator iter = hash->ids.find(id);
if (iter == hash->ids.end())
return 0;
QHash<QObject *, ObjectReference>::Iterator objIter =
hash->objects.find(*iter);
Q_ASSERT(objIter != hash->objects.end());
if (objIter->object == 0) {
hash->ids.erase(iter);
hash->objects.erase(objIter);
return 0;
} else {
return *iter;
}
}
bool QDeclarativeDebugService::isDebuggingEnabled()
{
return QDeclarativeDebugServer::instance() != 0;
}
bool QDeclarativeDebugService::hasDebuggingClient()
{
return QDeclarativeDebugServer::instance() != 0
&& QDeclarativeDebugServer::instance()->hasDebuggingClient();
}
QString QDeclarativeDebugService::objectToString(QObject *obj)
{
if(!obj)
return QLatin1String("NULL");
QString objectName = obj->objectName();
if(objectName.isEmpty())
objectName = QLatin1String("<unnamed>");
QString rv = QString::fromUtf8(obj->metaObject()->className()) +
QLatin1String(": ") + objectName;
return rv;
}
void QDeclarativeDebugService::sendMessage(const QByteArray &message)
{
Q_D(QDeclarativeDebugService);
if (!d->server || !d->server->d_func()->connection)
return;
QPacket pack;
pack << d->name << message;
d->server->d_func()->protocol->send(pack);
d->server->d_func()->connection->flush();
}
void QDeclarativeDebugService::enabledChanged(bool)
{
}
void QDeclarativeDebugService::messageReceived(const QByteArray &)
{
}
QT_END_NAMESPACE
#include <qdeclarativedebugservice.moc>