aboutsummaryrefslogtreecommitdiff
path: root/mthemedaemon
diff options
context:
space:
mode:
authorTomas Junnonen <tomas.junnonen@nokia.com>2010-04-12 13:50:25 +0300
committerTomas Junnonen <tomas.junnonen@nokia.com>2010-04-12 13:52:31 +0300
commitda73676c8a5af66b55523a9cdfbfbea2baa88a2a (patch)
tree0a3b8933a1817c152116da5fa8a7b5cdd8102e60 /mthemedaemon
parent8832674482d3b9a7fcf77b0cfdcb8e6fe4960b4d (diff)
Changes: Renamed dui to meegotouch
By: Holger, Daniel, Janne RevBy: Tomas, Holger
Diffstat (limited to 'mthemedaemon')
-rw-r--r--mthemedaemon/.gitignore2
-rw-r--r--mthemedaemon/keypresswaiter.h41
-rw-r--r--mthemedaemon/main.cpp57
-rw-r--r--mthemedaemon/mthemedaemon.pro62
-rw-r--r--mthemedaemon/mthemedaemonserver.cpp409
-rw-r--r--mthemedaemon/mthemedaemonserver.h93
-rw-r--r--mthemedaemon/test/client.cpp421
-rw-r--r--mthemedaemon/test/client.h109
-rw-r--r--mthemedaemon/test/clientmanager.cpp352
-rw-r--r--mthemedaemon/test/clientmanager.h68
-rw-r--r--mthemedaemon/test/main.cpp65
-rw-r--r--mthemedaemon/test/mthemedaemontest.pro50
-rw-r--r--mthemedaemon/test/testdaemon/testdaemon.pro45
-rw-r--r--mthemedaemon/test/themes/base/index.theme7
-rw-r--r--mthemedaemon/test/themes/base/m/libduicore/style/libdui.css1
-rw-r--r--mthemedaemon/test/themes/theme_one/index.theme8
-rw-r--r--mthemedaemon/test/themes/theme_one/m/constants.ini34
-rw-r--r--mthemedaemon/test/themes/theme_one/m/locale/fi/constants.ini34
18 files changed, 1858 insertions, 0 deletions
diff --git a/mthemedaemon/.gitignore b/mthemedaemon/.gitignore
new file mode 100644
index 00000000..a6ec32e5
--- /dev/null
+++ b/mthemedaemon/.gitignore
@@ -0,0 +1,2 @@
+duithemedaemon
+
diff --git a/mthemedaemon/keypresswaiter.h b/mthemedaemon/keypresswaiter.h
new file mode 100644
index 00000000..0ddcd959
--- /dev/null
+++ b/mthemedaemon/keypresswaiter.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (directui@nokia.com)
+**
+** This file is part of libmeegotouch.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at directui@nokia.com.
+**
+** This library is free software; you can redistribute it and/or
+** modify it 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.
+**
+****************************************************************************/
+
+#ifndef KEYPRESSWAITER_H
+#define KEYPRESSWAITER_H
+
+#include <QApplication>
+#include <QThread>
+#include <QTextStream>
+#include <QFile>
+#include <QtDebug>
+
+class KeyPressWaiter : public QThread
+{
+ Q_OBJECT
+public:
+
+ void run() {
+ qDebug() << "Hit enter to exit..";
+ QTextStream qin(stdin, QFile::ReadOnly);
+ qin.readLine();
+ }
+};
+
+#endif
diff --git a/mthemedaemon/main.cpp b/mthemedaemon/main.cpp
new file mode 100644
index 00000000..b58cd2c5
--- /dev/null
+++ b/mthemedaemon/main.cpp
@@ -0,0 +1,57 @@
+/***************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (directui@nokia.com)
+**
+** This file is part of libmeegotouch.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at directui@nokia.com.
+**
+** This library is free software; you can redistribute it and/or
+** modify it 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.
+**
+****************************************************************************/
+
+#include "mthemedaemonserver.h"
+#include <QDebug>
+#include <QApplication>
+#include <signal.h>
+
+#ifdef CLOSE_ON_ENTER
+#include "keypresswaiter.h"
+#endif
+
+void sigclose(int)
+{
+ // kill the daemon so that it can save it's current state (caches, refcounts, etc)
+ qApp->quit();
+}
+
+int main(int argc, char **argv)
+{
+ signal(SIGTERM, sigclose);
+ signal(SIGINT, sigclose);
+
+ QApplication::setGraphicsSystem(QLatin1String("native"));
+
+ QApplication app(argc, argv);
+
+ MThemeDaemonServer server;
+
+#ifdef CLOSE_ON_ENTER
+ KeyPressWaiter keyWaiter;
+ QObject::connect(&keyWaiter, SIGNAL(finished()), qApp, SLOT(quit()));
+ keyWaiter.start();
+#endif
+
+ if (app.arguments().indexOf("-quitimmediately") >= 0) {
+ QTimer::singleShot(0, &app, SLOT(quit()));
+ }
+
+ return app.exec();
+}
diff --git a/mthemedaemon/mthemedaemon.pro b/mthemedaemon/mthemedaemon.pro
new file mode 100644
index 00000000..e1871173
--- /dev/null
+++ b/mthemedaemon/mthemedaemon.pro
@@ -0,0 +1,62 @@
+include(../mkspecs/common.pri)
+
+INCLUDEPATH += \
+ . \
+ ../src/include \
+ ../src/corelib \
+ ../src/corelib/core \
+
+DEPENDPATH += $$INCLUDEPATH
+QMAKE_LIBDIR += ../lib
+TEMPLATE = app
+DEPENDPATH += .
+
+QT += svg network
+
+DEFINES += MTHEME_PRINT_DEBUG
+#DEFINES += CLOSE_ON_ENTER
+
+# enable QString optimizations
+DEFINES += QT_USE_FAST_CONCATENATION QT_USE_FAST_OPERATOR_PLUS
+
+# Check for mixing of const and non-const iterators,
+# which can cause problems when built with some compilers:
+DEFINES += QT_STRICT_ITERATORS
+
+!win32:CONFIG += link_pkgconfig
+PKGCONFIG += gconf-2.0
+
+# Input
+SOURCES += main.cpp \
+ mthemedaemonserver.cpp \
+ ../src/corelib/theme/mthemedaemon.cpp \
+ ../src/corelib/theme/mcommonpixmaps.cpp \
+ ../src/corelib/theme/mimagedirectory.cpp \
+ ../src/corelib/theme/mthemedaemonclient.cpp \
+ ../src/corelib/theme/mthemedaemonprotocol.cpp \
+ ../src/corelib/theme/mthemeresourcemanager.cpp \
+ ../src/corelib/core/mgconfitem.cpp \
+ ../src/corelib/core/mcpumonitor.cpp \
+
+HEADERS += \
+ mthemedaemonserver.h \
+ ../src/corelib/theme/mthemedaemon.h \
+ ../src/corelib/theme/mcommonpixmaps.h \
+ ../src/corelib/theme/mimagedirectory.h \
+ ../src/corelib/theme/mthemedaemonclient.h \
+ ../src/corelib/theme/mthemedaemonprotocol.h \
+ ../src/corelib/theme/mthemeresourcemanager.h \
+ ../src/corelib/core/mgconfitem.h \
+ ../src/corelib/core/mcpumonitor.h \
+ keypresswaiter.h \
+
+QMAKE_EXTRA_TARGETS += check
+check.depends = $$TARGET
+check.commands = $$system(true)
+
+QMAKE_EXTRA_TARGETS += check-xml
+check-xml.depends = $$TARGET
+check-xml.commands = $$system(true)
+
+target.path = $$M_INSTALL_BIN
+INSTALLS += target \
diff --git a/mthemedaemon/mthemedaemonserver.cpp b/mthemedaemon/mthemedaemonserver.cpp
new file mode 100644
index 00000000..9a632171
--- /dev/null
+++ b/mthemedaemon/mthemedaemonserver.cpp
@@ -0,0 +1,409 @@
+/***************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (directui@nokia.com)
+**
+** This file is part of libmeegotouch.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at directui@nokia.com.
+**
+** This library is free software; you can redistribute it and/or
+** modify it 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.
+**
+****************************************************************************/
+
+#include "mthemedaemonserver.h"
+#include <theme/mthemedaemonclient.h>
+#include <MDebug>
+#include <MNamespace>
+
+#include <QLocalSocket>
+#include <QDir>
+
+
+using namespace M::MThemeDaemonProtocol;
+
+MThemeDaemonServer::MThemeDaemonServer() :
+ currentTheme("/M/theme/name"),
+ currentLocale("/M/i18n/Language")
+{
+ const QString defaultTheme("devel");
+
+ // 1) make sure that gconf has some value for the current theme
+ if (currentTheme.value().isNull())
+ currentTheme.set(defaultTheme);
+
+ // 2) activate the current theme
+ QHash<MThemeDaemonClient *, QList<PixmapIdentifier> > pixmapsToReload;
+ if (!daemon.activateTheme(currentTheme.value().toString(), currentLocale.value().toString(), QList<MThemeDaemonClient *>(), pixmapsToReload)) {
+ // could not activate current theme, change to devel
+ if (!daemon.activateTheme(defaultTheme, currentLocale.value().toString(), QList<MThemeDaemonClient *>(), pixmapsToReload)) {
+ qFatal("MThemeDaemonServer - Could not find themes, aborting!");
+ }
+ currentTheme.set(defaultTheme);
+ }
+ Q_ASSERT(pixmapsToReload.isEmpty());
+
+ // 3) make sure we're notified if locale or theme changes
+ connect(&currentTheme, SIGNAL(valueChanged()), SLOT(themeChanged()));
+ connect(&currentLocale, SIGNAL(valueChanged()), SLOT(localeChanged()));
+
+ // 4) make sure we have a themedaemon directory in /var/cache/m/
+ QDir cacheDir(MThemeDaemon::systemThemeCacheDirectory());
+ if(!cacheDir.exists()) {
+ if(!cacheDir.mkpath(MThemeDaemon::systemThemeCacheDirectory())) {
+ qFatal("MThemeDaemonServer - Could not create cache directory for themedaemon. No write permissions to %s",
+ qPrintable(cacheDir.absolutePath()));
+ }
+ }
+
+ // 5) make sure we have a cache directory for the current theme
+ if(!cacheDir.exists(currentTheme.value().toString())) {
+ if(!cacheDir.mkdir(currentTheme.value().toString())) {
+ qFatal("MThemeDaemonServer - Could not create cache directory for current theme. No write permissions to %s",
+ qPrintable(cacheDir.absolutePath()));
+ }
+ }
+
+
+ // start socket server for client registeration
+ // first remove the old one, if there's such
+ QLocalServer::removeServer(M::MThemeDaemonProtocol::ServerAddress);
+ connect(&server, SIGNAL(newConnection()), SLOT(clientConnected()));
+ if (!server.listen(M::MThemeDaemonProtocol::ServerAddress)) {
+ mWarning("MThemeDaemonServer") << "Failed to start socket server.";
+ }
+
+ processQueueTimer.setInterval(0);
+ processQueueTimer.setSingleShot(false);
+ connect(&processQueueTimer, SIGNAL(timeout()), SLOT(processOneQueueItem()));
+}
+
+MThemeDaemonServer::~MThemeDaemonServer()
+{
+ processQueueTimer.stop();
+
+ // close socket server
+ server.close();
+
+ // remove all registered clients
+ while (registeredClients.count() > 0) {
+ MThemeDaemonClient *client = registeredClients.begin().value();
+ daemon.removeClient(client);
+ delete client;
+ registeredClients.erase(registeredClients.begin());
+ }
+
+ loadPixmapsQueue.clear();
+ releasePixmapsQueue.clear();
+}
+
+// gets called when a new client(socket) connects to the daemon
+void MThemeDaemonServer::clientConnected()
+{
+ while (server.hasPendingConnections()) {
+ QLocalSocket *socket = server.nextPendingConnection();
+ QObject::connect(socket, SIGNAL(disconnected()), SLOT(clientDisconnected()));
+ QObject::connect(socket, SIGNAL(readyRead()), SLOT(clientDataAvailable()));
+ }
+}
+
+// gets called when any client has disconnected
+void MThemeDaemonServer::clientDisconnected()
+{
+ QLocalSocket *socket = qobject_cast<QLocalSocket *>(sender());
+ if (socket) {
+ MThemeDaemonClient *client = registeredClients.value(socket, NULL);
+ if (client) {
+ daemon.removeClient(client);
+ registeredClients.remove(socket);
+
+ // remove all queued pixmap requests
+ QMutableListIterator<QueueItem> pi = loadPixmapsQueue;
+ while (pi.hasNext()) {
+ if (pi.next().client == client)
+ pi.remove();
+ }
+ // remove all queued pixmap releases
+ pi = releasePixmapsQueue;
+ while (pi.hasNext()) {
+ if (pi.next().client == client)
+ pi.remove();
+ }
+
+ delete client;
+ }
+ socket->deleteLater();
+ }
+}
+
+
+// gets called when any client has sent data over socket
+void MThemeDaemonServer::clientDataAvailable()
+{
+ QLocalSocket *socket = qobject_cast<QLocalSocket *>(sender());
+ if (!socket) {
+ return;
+ }
+
+ // has the client registered?
+ MThemeDaemonClient *client = registeredClients.value(socket, NULL);
+ if (!client) {
+
+ // create a temporary data stream to read from the socket
+ QDataStream stream(socket);
+
+ // loop as long as the socket has some data left
+ while (socket->bytesAvailable()) {
+
+ // read one packet from the socket
+ Packet packet;
+ stream >> packet;
+ // and check if it's a registration request
+ if (packet.type() != Packet::RequestRegistrationPacket) {
+ // reply error
+ stream << Packet(Packet::ErrorPacket, packet.sequenceNumber(),
+ new String("You must send registration packet before requesting anything else!"));
+ } else {
+ // we got the registration packet so register the client, and continue normally
+ client = new MThemeDaemonClient(socket, static_cast<const String *>(packet.data())->string, daemon.themeInheritanceChain());
+ registeredClients.insert(socket, client);
+ daemon.addClient(client);
+ client->stream() << Packet(Packet::ThemeChangedPacket, packet.sequenceNumber(),
+ new ThemeChangeInfo(daemon.themeInheritanceChain(), daemon.themeLibraryNames()));
+ break;
+ }
+ }
+ }
+
+ // client has registered, so we'll read all packets that are currently available from the socket
+ while (socket->bytesAvailable()) {
+
+ // read packet from socket
+ Packet packet;
+ client->stream() >> packet;
+
+ // process it according to packet type
+ switch (packet.type()) {
+
+ case Packet::RequestRegistrationPacket: {
+ // client tried to register a second time
+ client->stream() << Packet(Packet::ErrorPacket, packet.sequenceNumber(),
+ new String("You have already registered!"));
+ mWarning("MThemeDaemonServer") << "Client with name" << client->name()
+ << "tried to register a second time!";
+ } break;
+
+ case Packet::RequestPixmapPacket: {
+ // client requested a pixmap
+ const PixmapIdentifier *id = static_cast<const PixmapIdentifier *>(packet.data());
+ pixmapRequested(client, *id, packet.sequenceNumber());
+ } break;
+
+ case Packet::ReleasePixmapPacket: {
+ // client requested a pixmap release
+ const PixmapIdentifier *id = static_cast<const PixmapIdentifier *>(packet.data());
+ pixmapReleaseRequested(client, *id, packet.sequenceNumber());
+ } break;
+
+ case Packet::RequestClearPixmapDirectoriesPacket: {
+ client->removeAddedImageDirectories();
+ } break;
+
+ case Packet::RequestNewPixmapDirectoryPacket: {
+ const StringBool *sb = static_cast<const StringBool *>(packet.data());
+ client->addCustomImageDirectory(sb->string, sb->b ? M::Recursive : M::NonRecursive);
+ } break;
+
+
+ case Packet::QueryThemeDaemonStatusPacket: {
+ themeDaemonStatus(client, packet.sequenceNumber());
+ } break;
+
+
+ default: {
+ mWarning("MThemeDaemonServer") << "unknown packet received:" << packet.type();
+ } break;
+ }
+ }
+}
+
+void MThemeDaemonServer::themeChanged()
+{
+ if (daemon.currentTheme() == currentTheme.value().toString())
+ return;
+
+ QHash<MThemeDaemonClient *, QList<PixmapIdentifier> > pixmapsToReload;
+ if (daemon.activateTheme(currentTheme.value().toString(), currentLocale.value().toString(), registeredClients.values(), pixmapsToReload)) {
+ // theme change succeeded, let's inform all clients + add the pixmaps to load-list
+ Packet themeChangedPacket(Packet::ThemeChangedPacket, 0, new ThemeChangeInfo(daemon.themeInheritanceChain(), daemon.themeLibraryNames()));
+
+ QHash<MThemeDaemonClient *, QList<PixmapIdentifier> >::iterator i = pixmapsToReload.begin();
+ QHash<MThemeDaemonClient *, QList<PixmapIdentifier> >::iterator end = pixmapsToReload.end();
+ for (; i != end; ++i) {
+ MThemeDaemonClient *client = i.key();
+ const QList<PixmapIdentifier> &ids = i.value();
+
+ client->stream() << themeChangedPacket;
+
+ const QList<PixmapIdentifier>::const_iterator idsEnd = ids.end();
+ for (QList<PixmapIdentifier>::const_iterator iId = ids.begin(); iId != idsEnd; ++iId) {
+
+ const QueueItem item (client, *iId);
+ if (!releasePixmapsQueue.removeOne(item)) {
+ loadPixmapsQueue.enqueue(item);
+ }
+ }
+ }
+
+ // make sure we have a cache directory for the current theme
+ QDir cacheDir(MThemeDaemon::systemThemeCacheDirectory());
+ if(!cacheDir.exists(currentTheme.value().toString())) {
+ if(!cacheDir.mkdir(currentTheme.value().toString())) {
+ qFatal("MThemeDaemonServer - Could not create cache directory for current theme. No write permissions to %s",
+ qPrintable(cacheDir.absolutePath()));
+ }
+ }
+
+ if (!loadPixmapsQueue.isEmpty() && !processQueueTimer.isActive())
+ processQueueTimer.start();
+
+ } else {
+ // theme change failed, so change the theme back also in gconf.
+ mWarning("MThemeDaemonServer") << "Could not change theme to" << currentTheme.value().toString();
+ currentTheme.set(daemon.currentTheme());
+ }
+}
+
+void MThemeDaemonServer::localeChanged()
+{
+ QHash<MThemeDaemonClient *, QList<PixmapIdentifier> > pixmapsToReload;
+ daemon.changeLocale(currentLocale.value().toString(), registeredClients.values(), pixmapsToReload);
+
+ QHash<MThemeDaemonClient *, QList<PixmapIdentifier> >::iterator i = pixmapsToReload.begin();
+ QHash<MThemeDaemonClient *, QList<PixmapIdentifier> >::iterator end = pixmapsToReload.end();
+ for (; i != end; ++i) {
+ MThemeDaemonClient *client = i.key();
+ const QList<PixmapIdentifier> &ids = i.value();
+
+ const QList<PixmapIdentifier>::const_iterator idsEnd = ids.end();
+ for (QList<PixmapIdentifier>::const_iterator iId = ids.begin(); iId != idsEnd; ++iId) {
+
+ const QueueItem item (client, *iId);
+ if (!releasePixmapsQueue.removeOne(item)) {
+ loadPixmapsQueue.enqueue(item);
+ }
+ }
+ }
+ if (!loadPixmapsQueue.isEmpty() && !processQueueTimer.isActive())
+ processQueueTimer.start();
+}
+
+void MThemeDaemonServer::processOneQueueItem()
+{
+ if (!loadPixmapsQueue.isEmpty()) {
+ const QueueItem item = loadPixmapsQueue.dequeue();
+ Qt::HANDLE handle = 0;
+ if (daemon.pixmap(item.client, item.pixmapId, handle)) {
+ item.client->stream() << Packet(Packet::PixmapUpdatedPacket, item.sequenceNumber,
+ new PixmapHandle(item.pixmapId, handle));
+ } else {
+ const QString message =
+ QString::fromLatin1("requested pixmap '%1' %2x%3 already acquired by client").arg(
+ item.pixmapId.imageId,
+ QString::number(item.pixmapId.size.width()),
+ QString::number(item.pixmapId.size.height()));
+ item.client->stream() << Packet(Packet::ErrorPacket, item.sequenceNumber,
+ new String(message));
+ }
+ } else if (!releasePixmapsQueue.isEmpty()) {
+ const QueueItem item = releasePixmapsQueue.dequeue();
+ if (!daemon.releasePixmap(item.client, item.pixmapId)) {
+ const QString message =
+ QString::fromLatin1("pixmap to release '%1' %2x%3 not acquired by client").arg(
+ item.pixmapId.imageId,
+ QString::number(item.pixmapId.size.width()),
+ QString::number(item.pixmapId.size.height()));
+ item.client->stream() << Packet(Packet::ErrorPacket, item.sequenceNumber,
+ new String(message));
+ }
+ }
+
+ if (loadPixmapsQueue.isEmpty() && releasePixmapsQueue.isEmpty()) {
+ processQueueTimer.stop();
+ }
+}
+
+void MThemeDaemonServer::pixmapRequested(MThemeDaemonClient *client,
+ const PixmapIdentifier &id, quint64 sequenceNumber)
+{
+ // if the client has requested a release for this pixmap, we'll remove the
+ // release request, and reply with the existing pixmap handle
+ const QueueItem item (client, id, sequenceNumber);
+ if (releasePixmapsQueue.removeOne(item)) {
+ // removeOne succeeds if there was a release request still on queue
+ // find the resource for the requested pixmap
+ ImageResource *resource = client->pixmaps.value(id);
+ // in case the resource is NULL, the pixmap is not loaded (it's not found from the current theme)
+ if (!resource) {
+ client->stream() << Packet(Packet::PixmapUpdatedPacket, sequenceNumber,
+ new PixmapHandle(id, 0));
+ } else {
+#ifndef Q_WS_MAC
+ client->stream() << Packet(Packet::PixmapUpdatedPacket, sequenceNumber,
+ new PixmapHandle(id, resource->pixmapHandle(id.size)));
+#endif
+ }
+ } else {
+ // the requested pixmap was not in release queue, so we'll queue the load
+ loadPixmapsQueue.enqueue(item);
+ if (!processQueueTimer.isActive())
+ processQueueTimer.start();
+ }
+}
+
+void MThemeDaemonServer::pixmapReleaseRequested(MThemeDaemonClient *client,
+ const PixmapIdentifier &id,
+ quint64 sequenceNumber)
+{
+ // if the pixmap request is in queue, we can just remove it from there
+ const QueueItem item (client, id, sequenceNumber);
+ if (!loadPixmapsQueue.removeOne(item)) {
+ // in case the removeOne fails, we need to queue the release request.
+ releasePixmapsQueue.enqueue(item);
+ if (!processQueueTimer.isActive())
+ processQueueTimer.start();
+ }
+}
+
+void MThemeDaemonServer::themeDaemonStatus(MThemeDaemonClient *client,
+ quint64 sequenceNumber) const
+{
+ QList<ClientInfo> clientList;
+
+ foreach(const MThemeDaemonClient * c, registeredClients.values()) {
+ ClientInfo info;
+ info.name = c->name();
+ info.pixmaps = c->pixmaps.keys();
+
+ foreach(const QueueItem &item, loadPixmapsQueue) {
+ if (item.client == c)
+ info.requestedPixmaps.append(item.pixmapId);
+ }
+ foreach(const QueueItem &item, releasePixmapsQueue) {
+ if (item.client == c)
+ info.releasedPixmaps.append(item.pixmapId);
+ }
+
+ clientList.append(info);
+ }
+
+ client->stream() << Packet(Packet::ThemeDaemonStatusPacket, sequenceNumber,
+ new ClientList(clientList));
+}
+
diff --git a/mthemedaemon/mthemedaemonserver.h b/mthemedaemon/mthemedaemonserver.h
new file mode 100644
index 00000000..f919929f
--- /dev/null
+++ b/mthemedaemon/mthemedaemonserver.h
@@ -0,0 +1,93 @@
+/***************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (directui@nokia.com)
+**
+** This file is part of libmeegotouch.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at directui@nokia.com.
+**
+** This library is free software; you can redistribute it and/or
+** modify it 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.
+**
+****************************************************************************/
+
+#ifndef MTHEMEDAEMONSERVER_H
+#define MTHEMEDAEMONSERVER_H
+
+#include <QObject>
+#include <QQueue>
+#include <QLocalServer>
+#include <MGConfItem>
+#include <QTimer>
+
+
+#include <mthemedaemon.h>
+
+class QLocalSocket;
+class MThemeDaemonClient;
+
+//! \internal
+class MThemeDaemonServer : public QObject
+{
+ Q_OBJECT
+public:
+ MThemeDaemonServer();
+ virtual ~MThemeDaemonServer();
+
+private slots:
+ void clientConnected();
+ void clientDisconnected();
+ void clientDataAvailable();
+
+ void themeChanged();
+ void localeChanged();
+
+ void processOneQueueItem();
+
+private:
+ void pixmapRequested(MThemeDaemonClient *client,
+ const M::MThemeDaemonProtocol::PixmapIdentifier &id,
+ quint64 sequenceNumber);
+ void pixmapReleaseRequested(MThemeDaemonClient *client,
+ const M::MThemeDaemonProtocol::PixmapIdentifier &id,
+ quint64 sequenceNumber);
+ void themeDaemonStatus(MThemeDaemonClient *client, quint64 sequenceNumber) const;
+
+private:
+ struct QueueItem
+ {
+ quint64 sequenceNumber;
+ MThemeDaemonClient * client;
+ M::MThemeDaemonProtocol::PixmapIdentifier pixmapId;
+
+ QueueItem(MThemeDaemonClient *c, M::MThemeDaemonProtocol::PixmapIdentifier p,
+ quint64 s = 0) : sequenceNumber(s), client(c), pixmapId(p) {}
+
+ // Ignore the sequence number when testing for equality. A custom
+ // predicate would be more appropriate, if Qt would support that.
+ bool operator==(const QueueItem &other) const
+ { return (client == other.client && pixmapId == other.pixmapId); }
+ bool operator!=(const QueueItem &other) const
+ { return (client != other.client || pixmapId != other.pixmapId); }
+ };
+
+ QLocalServer server;
+ QHash<QLocalSocket *, MThemeDaemonClient *> registeredClients;
+ MThemeDaemon daemon;
+
+ MGConfItem currentTheme;
+ MGConfItem currentLocale;
+
+ QQueue<QueueItem> loadPixmapsQueue;
+ QQueue<QueueItem> releasePixmapsQueue;
+ QTimer processQueueTimer;
+};
+//! \internal_end
+#endif
+
diff --git a/mthemedaemon/test/client.cpp b/mthemedaemon/test/client.cpp
new file mode 100644
index 00000000..a27e73e3
--- /dev/null
+++ b/mthemedaemon/test/client.cpp
@@ -0,0 +1,421 @@
+/***************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (directui@nokia.com)
+**
+** This file is part of libmeegotouch.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at directui@nokia.com.
+**
+** This library is free software; you can redistribute it and/or
+** modify it 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.
+**
+****************************************************************************/
+
+#include <stdlib.h>
+#include <QTimer>
+#include <QDebug>
+#include <QPainter>
+#include <QImage>
+#include <MGConfItem>
+#include "client.h"
+#include "clientmanager.h"
+#include "mthemedaemon.h"
+#include "mthemedaemonprotocol.h"
+
+using namespace M::MThemeDaemonProtocol;
+
+ClientThread::ClientThread(ClientManager* manager) : manager(manager)
+{
+}
+
+void ClientThread::run()
+{
+ Client client(identifier);
+ connect(&client,
+ SIGNAL(pixmapReady(const QString&, Client*, quint32, const QString&, const QSize&)),
+ manager,
+ SLOT(pixmapReady(const QString&, Client*, quint32, const QString&, const QSize&)));
+
+ exec();
+}
+
+void ClientThread::setId(const QString &id)
+{
+ identifier = id;
+}
+
+const QString &ClientThread::getId() const
+{
+ return identifier;
+}
+
+Client::Client(const QString &identifier) : identifier(identifier), registered(false), packetsSent(0)
+{
+ operationCount = rand() % MAX_OPERATION_COUNT;
+
+ int count = 50;
+ // list all themes
+ QDir themeDirectory(THEME_ROOT_DIRECTORY);
+ QStringList list = themeDirectory.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
+ for(int i=0; i<list.size(); i++) {
+ QString themeName = list.at(i);
+
+ // create path for images
+ QString imagesPath = themeName + QDir::separator() + getImageDirectory();
+ if(themeDirectory.mkpath(imagesPath))
+ {
+ // create imagery
+ //int count = rand() % 200;
+ // create imagery
+ for (int i = 0; i < count; i++) {
+ QImage image(64, 64, QImage::Format_ARGB32);
+ image.fill(rand());
+
+ QString filename = themeDirectory.absolutePath() + QDir::separator() + imagesPath + QDir::separator() + QString::number(i) + ".png";
+ image.save(filename, "PNG");
+ }
+ }
+ }
+
+ stream.setDevice(&socket);
+
+ connect(&socket, SIGNAL(connected()), SLOT(connected()));
+ connect(&socket, SIGNAL(disconnected()), SLOT(disconnected()));
+ connect(&socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
+
+ // connect to server
+ socket.connectToServer(M::MThemeDaemonProtocol::ServerAddress);
+
+ if (!socket.waitForConnected(10000)) {
+ qDebug() << "ERROR:" << identifier << "- failed to connect server:" << socket.error();
+ } else {
+ // perform some task once we get into event loop
+ QTimer::singleShot(0, this, SLOT(sendPacket()));
+ }
+}
+
+Client::~Client()
+{
+ if (socket.state() == QLocalSocket::ConnectedState) {
+ socket.disconnectFromServer();
+ }
+}
+
+const QString &Client::getId() const
+{
+ return identifier;
+}
+
+void Client::connected()
+{
+// QLocalSocket* socket = qobject_cast<QLocalSocket*>(sender());
+}
+
+void Client::disconnected()
+{
+// QLocalSocket* socket = qobject_cast<QLocalSocket*>(sender());
+}
+
+M::MThemeDaemonProtocol::Packet Client::processOnePacket()
+{
+ Packet packet;
+ stream >> packet;
+
+ switch (packet.type()) {
+ case Packet::PixmapUpdatedPacket: {
+ const PixmapHandle *handle = static_cast<const PixmapHandle *>(packet.data());
+
+ if(handle->pixmapHandle) {
+ emit pixmapReady(currentTheme, this, handle->pixmapHandle, handle->identifier.imageId, handle->identifier.size);
+ waitVerify.acquire();
+ } else {
+ qDebug() << "ERROR: daemon returned null handle for" << handle->identifier.imageId;
+ }
+ } break;
+
+ case Packet::ThemeChangedPacket: {
+ const M::MThemeDaemonProtocol::ThemeChangeInfo *data = static_cast<const M::MThemeDaemonProtocol::ThemeChangeInfo *>(packet.data());
+ currentTheme = data->themeInheritance.at(0);
+ }break;
+
+ case Packet::ThemeDaemonStatusPacket:
+ break;
+
+ default:
+ break;
+ }
+
+ return packet;
+}
+
+void Client::pixmapVerified(const QString& imageId, const QSize& size)
+{
+ PixmapIdentifier identifier(imageId, size);
+
+ if (requestedPixmaps.contains(identifier)) {
+ // pixmap found from requested list -> new one
+ requestedPixmaps.remove(identifier);
+ readyPixmaps.insert(identifier);
+ } else if(readyPixmaps.contains(identifier)) {
+ // this pixmap was already ready, so it is just updated (probably due to theme change)
+ } else {
+ qDebug() << "ERROR:" << imageId << "- pixmap reply to unknown request";
+ }
+
+ waitVerify.release();
+}
+
+
+void Client::readyRead()
+{
+ while (socket.bytesAvailable() > 0) {
+ processOnePacket();
+ }
+}
+
+void Client::sendPacket()
+{
+ if (operationCount > 0) {
+
+ // randomize a task to us
+ Task task = (Task)(rand() % NumberOfTasks);
+
+ switch (task) {
+ case RegisterToServer:
+ registerToServer();
+ break;
+
+ case RequestPixmap: {
+ // this directory contains all icons for this theme
+ QDir themeIconDirectory = currentTheme + QDir::separator() + QString("meegotouch") + QDir::separator() + QString("icons");
+ QStringList iconList = themeIconDirectory.entryList(QDir::Files);
+
+ // this directory contains all images for this client
+ QDir imageDirectory = currentTheme + QDir::separator() + getImageDirectory();
+ QStringList imageList = imageDirectory.entryList(QDir::Files);
+
+ // combine both lists as one, we'll request something from this list
+ QStringList list;
+ list.append(iconList);
+ list.append(imageList);
+
+ if (list.count() > 0) {
+ // select image
+ int index = rand() % list.count();
+
+ // get pixmap identifier
+ QFileInfo info(list[index]);
+ PixmapIdentifier pixmapIdentifier(info.baseName(), QSize(64, 64));
+
+ // make sure this has not been requested already
+ if (!requestedPixmaps.contains(pixmapIdentifier) && !readyPixmaps.contains(pixmapIdentifier)) {
+ requestPixmap(pixmapIdentifier);
+ } else {
+ // it was already requested, should we try to request some other pixmap instead?
+ // nah, go away and do something else next time..
+ }
+ }
+ } break;
+
+ case ReleasePixmap: {
+ // make sure that we have something to release
+ if (readyPixmaps.count() > 0) {
+ // release the first pixmap we have, this could have better logic..
+ PixmapIdentifier toRemove = *readyPixmaps.begin();
+ releasePixmap(toRemove);
+ }
+ } break;
+
+ case CheckConsistency:
+ checkConsistency();
+ break;
+
+ default:
+ break;
+ }
+ // do something else next time
+ operationCount--;
+ QTimer::singleShot(TASK_EXECUTION_INTERVAL, this, SLOT(sendPacket()));
+ } else if (readyPixmaps.count() > 0 || requestedPixmaps.count() > 0) {
+
+ while (readyPixmaps.count() > 0) {
+ PixmapIdentifier toRemove = *readyPixmaps.begin();
+ releasePixmap(toRemove);
+ }
+
+ while (requestedPixmaps.count() > 0) {
+ PixmapIdentifier toRemove = *requestedPixmaps.begin();
+ releasePixmap(toRemove);
+ }
+
+ QTimer::singleShot(1000, this, SLOT(sendPacket()));
+ } else {
+ checkConsistency();
+ QThread::currentThread()->quit();
+ }
+}
+
+void Client::registerToServer()
+{
+#ifdef PRINT_INFO_MESSAGES
+ qDebug() << "INFO:" << identifier << "- registering to server";
+#endif
+ Packet registration(Packet::RequestRegistrationPacket, packetsSent++);
+ registration.setData(new String(identifier));
+ stream << registration;
+
+ registered = true;
+}
+
+void Client::requestPixmap(M::MThemeDaemonProtocol::PixmapIdentifier &pixmapIdentifier)
+{
+#ifdef PRINT_INFO_MESSAGES
+ qDebug() << "INFO:" << identifier << "- requesting pixmap" << pixmapIdentifier.imageId << pixmapIdentifier.size;
+#endif
+ Packet packet(Packet::RequestPixmapPacket, packetsSent++);
+ packet.setData(new PixmapIdentifier(pixmapIdentifier));
+ stream << packet;
+
+ if (registered) {
+ requestedPixmaps.insert(pixmapIdentifier);
+ }
+}
+
+void Client::releasePixmap(M::MThemeDaemonProtocol::PixmapIdentifier &pixmapIdentifier)
+{
+#ifdef PRINT_INFO_MESSAGES
+ qDebug() << "INFO:" << identifier << "- releasing pixmap" << pixmapIdentifier.imageId << pixmapIdentifier.size;
+#endif
+ Packet packet(Packet::ReleasePixmapPacket, packetsSent++);
+ packet.setData(new PixmapIdentifier(pixmapIdentifier));
+ stream << packet;
+ readyPixmaps.remove(pixmapIdentifier);
+ requestedPixmaps.remove(pixmapIdentifier);
+}
+
+void Client::checkConsistency()
+{
+ if (!registered)
+ return;
+
+ bool consistency = false;
+
+ // disconnect signal
+ disconnect(&socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
+
+ // send status query packet
+ Packet packet(Packet::QueryThemeDaemonStatusPacket, packetsSent++);
+ stream << packet;
+ if (!socket.flush()) {
+ qDebug() << "ERROR:" << identifier << "- failed to write to socket" << socket.error();
+ }
+
+ bool done = false;
+ while (!done) {
+ // wait for response
+ if (socket.waitForReadyRead(10000)) {
+ while (socket.bytesAvailable() > 0) {
+ // handle packet that was received
+ Packet packet = processOnePacket();
+
+ // check whether it's correct type
+ if (packet.type() == Packet::ThemeDaemonStatusPacket) {
+
+ // extract reply and perform consistency check
+ const ClientList *list = static_cast<const ClientList *>(packet.data());
+ consistency = isDataConsistent(list);
+ done = true;
+ break;
+ }
+ }
+ } else {
+ // something bad happened, we did not get reply
+ qDebug() << "ERROR:" << identifier << "- failed to receive data from socket" << socket.error();
+ break;
+ }
+ }
+
+ // reconnect signal back
+ connect(&socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
+
+ // process rest of the packets
+ readyRead();
+
+ if (!consistency) {
+ qDebug() << "ERROR:" << identifier << "- Consistency check FAILED!";
+ }
+}
+
+bool Client::isDataConsistent(const M::MThemeDaemonProtocol::ClientList *list)
+{
+ foreach(ClientInfo info, list->clients) {
+ // find correct info
+ if (info.name == identifier) {
+
+ // check that we're really registered
+ if (!registered) {
+ // we're not registered but our name is still in the client list
+ return false;
+ }
+
+ // theme change may cause pixmaps to move from loaded list to request list in daemon side,
+ // so we'll compare both lists as one
+
+ // create list of daemon-side pixmaps (requests & loaded)
+ QList<PixmapIdentifier> daemon;
+ daemon.append(info.requestedPixmaps);
+ daemon.append(info.pixmaps);
+
+ // create list of client-side pixmaps (requests & loaded)
+ QList<PixmapIdentifier> client;
+ client.append(requestedPixmaps.toList());
+ client.append(readyPixmaps.toList());
+
+ // check that the daemon has correct amount of pixmaps in load queue
+ if (daemon.count() != client.count()) {
+ qDebug() << "ERROR:" << identifier << "- incorrect pixmap count, Themedaemon says:" << daemon.count() << "and client says:" << client.count();
+ break;
+ }
+
+ // check that we can find all daemon-side pixmaps from the client-side list
+ foreach(const PixmapIdentifier & pixmapIdentifier, daemon) {
+ if (!client.contains(pixmapIdentifier)) {
+ // pixmap not found from client, but themedaemon reported it -> inconsistent state
+ qDebug() << "ERROR:" << identifier << "- pixmap not found from client-side list:" << pixmapIdentifier.imageId << '(' << pixmapIdentifier.size << ')';
+ break;
+ } else {
+ // found, we can remove this one from client list
+ client.removeOne(pixmapIdentifier);
+ }
+ }
+
+ // check that we can find all client-side pixmaps from the daemon-side list
+ foreach(const PixmapIdentifier & pixmapIdentifier, client) {
+ if (!daemon.contains(pixmapIdentifier)) {
+ // pixmap not found from daemon-side list, but exists in client-side list -> inconsistent state
+ qDebug() << "ERROR:" << identifier << "- pixmap not found from daemon-side list:" << pixmapIdentifier.imageId << '(' << pixmapIdentifier.size << ')';
+ break;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ // if we're registered, we shouldn't end up being here and vice versa
+ return registered ? false : true;
+}
+
+QString Client::getImageDirectory() const
+{
+ return QString("meegotouch") + QDir::separator() +
+ QString(identifier) + QDir::separator() +
+ QString("images");
+}
+
diff --git a/mthemedaemon/test/client.h b/mthemedaemon/test/client.h
new file mode 100644
index 00000000..01749c43
--- /dev/null
+++ b/mthemedaemon/test/client.h
@@ -0,0 +1,109 @@
+/***************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (directui@nokia.com)
+**
+** This file is part of libmeegotouch.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at directui@nokia.com.
+**
+** This library is free software; you can redistribute it and/or
+** modify it 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.
+**
+****************************************************************************/
+
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include <QThread>
+#include <QDataStream>
+#include <QSize>
+#include <QDir>
+#include <QLocalSocket>
+#include <QSemaphore>
+
+#include "mthemedaemonclient.h"
+
+class Client;
+class ClientManager;
+
+class ClientThread : public QThread
+{
+public:
+ ClientThread(ClientManager* manager);
+ void setId(const QString &id);
+ const QString &getId() const;
+
+protected:
+ virtual void run();
+private:
+ ClientManager* manager;
+ QString identifier;
+};
+
+class Client : public QObject
+{
+ Q_OBJECT
+
+ static const int MAX_OPERATION_COUNT = 100;
+ static const int TASK_EXECUTION_INTERVAL = 50;
+
+ enum Task {
+ RegisterToServer,
+ RequestPixmap,
+ ReleasePixmap,
+ CheckConsistency,
+
+ NumberOfTasks
+ };
+
+public:
+ Client(const QString &identifier);
+ ~Client();
+
+ const QString& getId() const;
+
+ QString getImageDirectory() const;
+ void pixmapVerified(const QString& imageId, const QSize& size);
+
+signals:
+ void pixmapReady(const QString& theme, Client* client, quint32 handle, const QString&, const QSize&);
+
+
+protected:
+
+ void registerToServer();
+ void requestPixmap(M::MThemeDaemonProtocol::PixmapIdentifier &);
+ void releasePixmap(M::MThemeDaemonProtocol::PixmapIdentifier &);
+ void checkConsistency();
+ M::MThemeDaemonProtocol::Packet processOnePacket();
+
+private slots:
+ void connected();
+ void disconnected();
+ void sendPacket();
+ void readyRead();
+
+private:
+
+ bool isDataConsistent(const M::MThemeDaemonProtocol::ClientList *reply);
+ void quit();
+
+ QLocalSocket socket;
+ QDataStream stream;
+ QSet<M::MThemeDaemonProtocol::PixmapIdentifier> requestedPixmaps;
+ QSet<M::MThemeDaemonProtocol::PixmapIdentifier> readyPixmaps;
+ QString identifier;
+ bool registered;
+ int operationCount;
+ quint64 packetsSent;
+ QString currentTheme;
+ QSemaphore waitVerify;
+};
+
+#endif
diff --git a/mthemedaemon/test/clientmanager.cpp b/mthemedaemon/test/clientmanager.cpp
new file mode 100644
index 00000000..a277970b
--- /dev/null
+++ b/mthemedaemon/test/clientmanager.cpp
@@ -0,0 +1,352 @@
+/***************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (directui@nokia.com)
+**
+** This file is part of libmeegotouch.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at directui@nokia.com.
+**
+** This library is free software; you can redistribute it and/or
+** modify it 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.
+**
+****************************************************************************/
+
+#include <QDebug>
+#include <QTimer>
+#include <QApplication>
+#include <MGConfItem>
+#include <QDir>
+#include <QFile>
+#include <QPainter>
+#include <QSvgGenerator>
+#include <QSvgRenderer>
+#include "clientmanager.h"
+#include "client.h"
+
+#define CLIENT_ID(client) "Client (0x" + QString::number((quint32) client, 16) + ')'
+
+void removeDirectoryRecursive(const QString &path)
+{
+ QDir root(path);
+ if (root.exists()) {
+ QFileInfoList entries = root.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files);
+ for (int i = 0; i < entries.count(); i++) {
+ if (entries[i].isDir()) {
+ removeDirectoryRecursive(entries[i].filePath());
+ root.rmdir(entries[i].absoluteFilePath());
+ } else {
+ root.remove(entries[i].absoluteFilePath());
+ }
+ }
+ }
+}
+
+void ClientManager::cleanup()
+{
+ QDir themeDirectory(THEME_ROOT_DIRECTORY);
+ QStringList list = themeDirectory.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
+ for(int i=0; i<list.size(); i++) {
+ QString path = list.at(i);
+
+ QDir theme(themeDirectory.absolutePath() + QDir::separator() + path + QDir::separator() + QString("meegotouch"));
+ // find all client directories
+ QStringList directories = theme.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
+ for(int j=0; j<directories.size(); j++) {
+ if(directories.at(j).startsWith("client") || directories.at(j) == QString("icons"))
+ {
+ removeDirectoryRecursive(theme.absolutePath() + QDir::separator() + directories.at(j));
+ theme.rmdir(directories.at(j));
+ }
+ }
+ // loop all locales
+ for(int localeIndex=1; localeIndex<locales.size(); localeIndex++)
+ {
+ // remove all locale-specific icons
+ QString localeSpecificIconsDirectory = theme.absolutePath() + QDir::separator() +
+ "locale" + QDir::separator() +
+ locales[localeIndex] + QDir::separator() +
+ QString("icons");
+ removeDirectoryRecursive(localeSpecificIconsDirectory);
+ theme.rmdir(localeSpecificIconsDirectory);
+ }
+ }
+}
+
+void createSVGFile(const QString& path, int index)
+{
+ QString filename = path + QDir::separator() + QString("icon-") + QString::number(index) + ".svg";
+
+ QFile file(filename);
+ file.open(QIODevice::WriteOnly);
+
+ QSvgGenerator generator;
+ generator.setFileName(filename);
+ generator.setSize(QSize(200, 200));
+ generator.setViewBox(QRect(0, 0, 200, 200));
+ generator.setOutputDevice(&file);
+
+ QPainter painter;
+ painter.begin(&generator);
+
+ QColor color(rand() % 255, rand() % 255, rand() % 255);
+ painter.fillRect(generator.viewBox(), color);
+ painter.end();
+
+ file.close();
+}
+
+ClientManager::ClientManager(QProcess &process) : shutdown(false), themedaemon(process)
+{
+ locales.append("en");
+ locales.append("fi");
+
+ cleanup();
+
+ int count = 50;
+ // create svg images for icons for all themes & locales
+ QDir themeDirectory(THEME_ROOT_DIRECTORY);
+ QStringList list = themeDirectory.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
+ for(int i=0; i<list.size(); i++) {
+ QString themeName = list.at(i);
+
+ // create path for images
+ QString iconsPath = themeName + QDir::separator() + QString("meegotouch") + QDir::separator() + QString("icons");
+ if(themeDirectory.mkpath(iconsPath))
+ {
+ // create svg icon
+ //int count = (rand() + 10) % 200;
+
+ // create imagery to theme's root icons dir
+ for (int imageIndex = 0; imageIndex < count; imageIndex++) {
+ createSVGFile(themeDirectory.absolutePath() + QDir::separator() + iconsPath, imageIndex);
+ }
+
+ // create locales
+ QString localeRoot = themeName + QDir::separator() + QString("meegotouch") + QDir::separator() + QString("locale");
+ for(int localeIndex=1; localeIndex<locales.size(); localeIndex++) {
+ QString localeIconPath = localeRoot + QDir::separator() + locales[localeIndex] + QDir::separator() + QString("icons");
+ if(themeDirectory.mkpath(localeIconPath)) {
+ // create svg imagery
+ for (int imageIndex = 0; imageIndex < count; imageIndex++) {
+ createSVGFile(themeDirectory.absolutePath() + QDir::separator() + localeIconPath, imageIndex);
+ }
+ }
+ }
+ }
+ }
+
+ // start the test after 1 sec (allow themedaemon to get online)
+ QTimer::singleShot(1000, this, SLOT(start()));
+}
+
+ClientManager::~ClientManager()
+{
+ // perform cleanup
+ cleanup();
+}
+
+void ClientManager::spawnClient()
+{
+ static unsigned int clientId = 0;
+ ClientThread *client = new ClientThread(this);
+ client->setId("client" + QString::number(clientId++));
+
+ // generate imagery for testing purposes
+ connect(client, SIGNAL(started()), this, SLOT(clientStarted()));
+ connect(client, SIGNAL(finished()), this, SLOT(clientFinished()));
+ clients.insert(client);
+
+ client->start();
+#ifdef PRINT_INFO_MESSAGES
+ qDebug() << "INFO: ClientManager - Client started, number of active clients:" << clients.count();
+#endif
+}
+
+void ClientManager::start()
+{
+#ifdef PRINT_INFO_MESSAGES
+ qDebug() << "INFO: ClientManager - Starting up...";
+#endif
+ // change theme & begin theme switching
+ changeThemeAndLocale();
+
+ // start performing consistency checks & spawn new clients
+ QTimer::singleShot(0, this, SLOT(checkConsistency()));
+}
+
+void ClientManager::stop()
+{
+ shutdown = true;
+ qDebug() << "INFO: ClientManager - Shutting down...";
+
+ if (clients.count() == 0) {
+ qApp->quit();
+ }
+}
+
+void ClientManager::clientStarted()
+{
+ Client *client = static_cast<Client *>(sender());
+ Q_UNUSED(client);
+}
+
+
+void ClientManager::clientFinished()
+{
+ ClientThread *client = static_cast<ClientThread *>(sender());
+
+ removeDirectoryRecursive(QString(IMAGESDIR) + QDir::separator() + client->getId());
+ clients.remove(client);
+ client->exit();
+ client->wait();
+ delete client;
+#ifdef PRINT_INFO_MESSAGES
+ qDebug() << "INFO: ClientManager - Client finished, number of active clients:" << clients.count();
+#endif
+ if (shutdown) {
+ if (clients.count() == 0) {
+ qApp->quit();
+ }
+ }
+}
+
+void ClientManager::checkConsistency()
+{
+ // check that themedaemon has not crashed
+ if (themedaemon.state() != QProcess::Running) {
+ qDebug() << "ERROR: ClientManager (consistency check)- Themedaemon is not running";
+ qDebug() << themedaemon.readAllStandardError();
+ qDebug() << themedaemon.readAllStandardOutput();
+
+ shutdown = true;
+ foreach(ClientThread * thread, clients) {
+ thread->exit();
+ thread->wait();
+ delete thread;
+ }
+ qApp->quit();
+ }
+
+ if (shutdown == false) {
+ // spawn new clients if there is room
+ while (clients.count() < ClientManager::MAX_CLIENT_COUNT) {
+ spawnClient();
+ }
+ // get ready to perform another consistency check
+ QTimer::singleShot(5000, this, SLOT(checkConsistency()));
+ }
+}
+
+void ClientManager::changeThemeAndLocale()
+{
+#ifdef HAVE_GCONF
+ // get list of themes
+ QDir themeDirectory(THEME_ROOT_DIRECTORY);
+ QStringList list = themeDirectory.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
+
+ if(list.size() == 0)
+ return;
+
+ int themeIndex = rand() % list.size();
+
+#ifdef PRINT_INFO_MESSAGES
+ qDebug() << "INFO: Changing theme to:" << list[themeIndex];
+#endif
+ MGConfItem themeName("/M/theme/name");
+ //themeName.set(list[themeIndex]);
+
+// int localeIndex = rand() % locales.size();
+// MGConfItem localeName("/M/i18n/Language");
+// localeName.set(locales[localeIndex]);
+
+ // change theme and locale again after a short period of time
+ QTimer::singleShot(2000, this, SLOT(changeThemeAndLocale()));
+#endif
+}
+
+void ClientManager::pixmapReady(const QString& theme, Client* client, quint32 handle, const QString& imageId, const QSize& size)
+{
+ if(!verifyPixmap(theme, client, handle, imageId, size)) {
+ qDebug() << "ERROR:" << client->getId() << "- incorrect color found when verifying returned pixmap (" << imageId << ')';
+ } else {
+#ifdef PRINT_INFO_MESSAGES
+ qDebug() << "INFO:" << client->getId() << "- pixmap comparison OK (" << imageId << ')';
+#endif
+ }
+ client->pixmapVerified(imageId, size);
+}
+
+bool ClientManager::verifyPixmap(const QString& theme, Client* client, quint32 handle, const QString& imageId, const QSize& size)
+{
+ // this is what we got from daemon
+ QPixmap daemon = QPixmap::fromX11Pixmap(handle, QPixmap::ImplicitlyShared);
+
+ // this is what we have
+ QPixmap clientPixmap(size);
+
+ QDir themeDirectory(THEME_ROOT_DIRECTORY);
+
+ // either icon-<xx> or just <xx>
+ if(imageId.startsWith("icon"))
+ {
+#ifdef HAVE_GCONF
+// MGConfItem localeName("/M/i18n/Language");
+// QString locale = localeName.value().toString();
+ QDir iconDirectory;
+// if(locale.isEmpty())
+ {
+ iconDirectory = QDir(theme + QDir::separator() +
+ QString("meegotouch") + QDir::separator() + QString("icons"));
+ }
+// else
+// {
+// iconDirectory = QDir(theme + QDir::separator() +
+// QString("meegotouch") + QDir::separator() +
+// QString("locale") + QDir::separator() +
+// locale + QDir::separator() +
+// QString("icons"));
+// }
+
+ // create svg renderer
+ QString filename = iconDirectory.absolutePath() + QDir::separator() + imageId + ".svg";
+ QSvgRenderer renderer(filename);
+ if(!renderer.isValid())
+ {
+ qDebug() << "ERROR: Failed to construct SVG renderer for:" << filename;
+ return false;
+ }
+ // render pixmap
+ QPainter painter(&clientPixmap);
+ renderer.render(&painter);
+#else
+ return true;
+#endif
+ }
+ else
+ {
+ QDir imageDirectory = theme + QDir::separator() + client->getImageDirectory();
+ QString filename = imageDirectory.absolutePath() + QDir::separator() + imageId + ".png";
+ if(!clientPixmap.load(filename, "PNG"))
+ qDebug() << "ERROR: Failed to construct PNG image:" << filename;
+ }
+
+ // make sure that the pixel in the center of the pixmap is equal (these are always one-color images)
+ QImage d = daemon.toImage();
+ QImage c = clientPixmap.toImage();
+
+ QRgb color = d.pixel(size.width() / 2, size.height() / 2);
+ QRgb color2 = c.pixel(size.width() / 2, size.height() / 2);
+
+ if(color != color2) {
+ qDebug() << "ERROR: Colors don't match:" << theme << QColor(color) << QColor(color2);
+ return false;
+ }
+
+ return true;
+}
diff --git a/mthemedaemon/test/clientmanager.h b/mthemedaemon/test/clientmanager.h
new file mode 100644
index 00000000..7b565f59
--- /dev/null
+++ b/mthemedaemon/test/clientmanager.h
@@ -0,0 +1,68 @@
+/***************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (directui@nokia.com)
+**
+** This file is part of libmeegotouch.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at directui@nokia.com.
+**
+** This library is free software; you can redistribute it and/or
+** modify it 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.
+**
+****************************************************************************/
+
+#ifndef CLIENTMANAGER_H
+#define CLIENTMANAGER_H
+
+#include <QObject>
+#include <QProcess>
+#include <QSet>
+#include <QSize>
+
+class ClientThread;
+class Client;
+
+static const QString THEME_ROOT_DIRECTORY = QString("themes");
+
+class ClientManager : public QObject
+{
+ Q_OBJECT
+
+ static const int MAX_CLIENT_COUNT = 10;
+
+public:
+ ClientManager(QProcess &process);
+ ~ClientManager();
+
+protected:
+ void spawnClient();
+
+private slots:
+ void start();
+ void stop();
+
+ void checkConsistency();
+
+ void clientStarted();
+ void clientFinished();
+ void changeThemeAndLocale();
+
+ void pixmapReady(const QString& theme, Client* client, quint32 handle, const QString& imageId, const QSize& size);
+
+private:
+ void cleanup();
+ bool verifyPixmap(const QString& theme, Client* client, quint32 handle, const QString& imageId, const QSize& size);
+
+ QStringList locales;
+ QSet<ClientThread *> clients;
+ bool shutdown;
+ QProcess &themedaemon;
+};
+
+#endif
diff --git a/mthemedaemon/test/main.cpp b/mthemedaemon/test/main.cpp
new file mode 100644
index 00000000..0c1c9532
--- /dev/null
+++ b/mthemedaemon/test/main.cpp
@@ -0,0 +1,65 @@
+/***************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (directui@nokia.com)
+**
+** This file is part of libmeegotouch.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at directui@nokia.com.
+**
+** This library is free software; you can redistribute it and/or
+** modify it 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.
+**
+****************************************************************************/
+
+#include <QDebug>
+#include <QApplication>
+#include <QDir>
+#include <MGConfItem>
+#include <theme/mthemedaemon.h>
+#include "keypresswaiter.h"
+#include "clientmanager.h"
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+
+#ifdef HAVE_GCONF
+ const QString themeName = "theme_one";
+ QDir themeDirectory(THEME_ROOT_DIRECTORY + '/' + themeName);
+ if (themeDirectory.exists()) {
+ MGConfItem theme("/M/theme/name");
+ theme.set(themeName);
+ }
+#endif
+
+ QProcess td;
+ td.start("./testdaemon/testdaemon", QStringList());
+ // start theme daemon
+ if (td.waitForStarted()) {
+ // This is the class that handles the test
+ ClientManager manager(td);
+
+ // close on enter
+ KeyPressWaiter keyWaiter;
+ QObject::connect(&keyWaiter, SIGNAL(finished()), &manager, SLOT(stop()));
+ keyWaiter.start();
+
+ // event loop
+ int result = app.exec();
+
+ // stop theme daemon
+ if (td.state() == QProcess::Running) {
+ td.close();
+ }
+ return result;
+ }
+
+ qDebug() << "Theme daemon failed to start";
+ return 0;
+}
diff --git a/mthemedaemon/test/mthemedaemontest.pro b/mthemedaemon/test/mthemedaemontest.pro
new file mode 100644
index 00000000..69c9b5a0
--- /dev/null
+++ b/mthemedaemon/test/mthemedaemontest.pro
@@ -0,0 +1,50 @@
+include(../../mkspecs/common.pri)
+
+INCLUDEPATH += . ../ ../../src/include ../../src ../../src/corelib ../../src/corelib/core ../../src/corelib/theme
+DEPENDPATH += $$INCLUDEPATH
+QMAKE_LIBDIR += ../lib
+TEMPLATE = app
+DEPENDPATH += .
+
+QT += svg network
+
+#DEFINES += PRINT_INFO_MESSAGES
+
+# Check for mixing of const and non-const iterators,
+# which can cause problems when built with some compilers:
+DEFINES += QT_STRICT_ITERATORS
+
+# override theme directory
+DEFINES += IMAGESDIR=\\\"./images\\\"
+
+!win32:CONFIG += link_pkgconfig
+PKGCONFIG += gconf-2.0
+
+# Input
+SOURCES += main.cpp \
+ clientmanager.cpp \
+ client.cpp \
+ ../../src/corelib/theme/mthemedaemon.cpp \
+ ../../src/corelib/theme/mcommonpixmaps.cpp \
+ ../../src/corelib/theme/mimagedirectory.cpp \
+ ../../src/corelib/theme/mthemedaemonclient.cpp \
+ ../../src/corelib/theme/mthemedaemonprotocol.cpp \
+ ../../src/corelib/theme/mthemeresourcemanager.cpp \
+ ../../src/corelib/core/mgconfitem.cpp \
+ ../../src/corelib/core/mcpumonitor.cpp \
+
+
+HEADERS += clientmanager.h \
+ client.h \
+ ../keypresswaiter.h \
+ ../../src/corelib/theme/imthemedaemon.h \
+ ../../src/corelib/theme/mthemedaemon.h \
+ ../../src/corelib/theme/mcommonpixmaps.h \
+ ../../src/corelib/theme/mimagedirectory.h \
+ ../../src/corelib/theme/mthemedaemonclient.h \
+ ../../src/corelib/theme/mthemedaemonprotocol.h \
+ ../../src/corelib/theme/mthemeresourcemanager.h \
+ ../../src/corelib/core/mgconfitem.h \
+ ../../src/corelib/core/mcpumonitor.h \
+
+SUBDIRS += testdaemodrgtrgn
diff --git a/mthemedaemon/test/testdaemon/testdaemon.pro b/mthemedaemon/test/testdaemon/testdaemon.pro
new file mode 100644
index 00000000..a3a98663
--- /dev/null
+++ b/mthemedaemon/test/testdaemon/testdaemon.pro
@@ -0,0 +1,45 @@
+INCLUDEPATH += . ../../../src/include ../../../src ../../../src/corelib/core ../../../src/corelib/
+
+TEMPLATE = app
+DEPENDPATH += .
+
+QT += svg network
+
+#DEFINES += MTHEME_PRINT_DEBUG
+#DEFINES += CLOSE_ON_ENTER
+
+DEFINES += THEMEDIR=\\\"\"themes\"\\\"
+
+# enable QString optimizations
+DEFINES += QT_USE_FAST_CONCATENATION QT_USE_FAST_OPERATOR_PLUS
+
+# Check for mixing of const and non-const iterators,
+# which can cause problems when built with some compilers:
+DEFINES += QT_STRICT_ITERATORS
+
+CONFIG += link_pkgconfig
+PKGCONFIG += gconf-2.0
+
+# Input
+SOURCES += ../../main.cpp \
+ ../../mthemedaemonserver.cpp \
+ ../../../src/corelib/theme/mthemedaemon.cpp \
+ ../../../src/corelib/theme/mcommonpixmaps.cpp \
+ ../../../src/corelib/theme/mimagedirectory.cpp \
+ ../../../src/corelib/theme/mthemedaemonclient.cpp \
+ ../../../src/corelib/theme/mthemedaemonprotocol.cpp \
+ ../../../src/corelib/theme/mthemeresourcemanager.cpp \
+ ../../../src/corelib/core/mgconfitem.cpp \
+ ../../../src/corelib/core/mcpumonitor.cpp \
+
+HEADERS += \
+ ../../mthemedaemonserver.h \
+ ../../../src/corelib/theme/mthemedaemon.h \
+ ../../../src/corelib/theme/mcommonpixmaps.h \
+ ../../../src/corelib/theme/mimagedirectory.h \
+ ../../../src/corelib/theme/mthemedaemonclient.h \
+ ../../../src/corelib/theme/mthemedaemonprotocol.h \
+ ../../../src/corelib/theme/mthemeresourcemanager.h \
+ ../../../src/corelib/core/mgconfitem.h \
+ ../../../src/corelib/core/mcpumonitor.h \
+
diff --git a/mthemedaemon/test/themes/base/index.theme b/mthemedaemon/test/themes/base/index.theme
new file mode 100644
index 00000000..2c5cca43
--- /dev/null
+++ b/mthemedaemon/test/themes/base/index.theme
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Type=X-DUI-Metatheme
+Name=Base
+Encoding=UTF-8
+
+[X-DUI-Metatheme]
+X-Visible=false
diff --git a/mthemedaemon/test/themes/base/m/libduicore/style/libdui.css b/mthemedaemon/test/themes/base/m/libduicore/style/libdui.css
new file mode 100644
index 00000000..4bd8276f
--- /dev/null
+++ b/mthemedaemon/test/themes/base/m/libduicore/style/libdui.css
@@ -0,0 +1 @@
+// empty file
diff --git a/mthemedaemon/test/themes/theme_one/index.theme b/mthemedaemon/test/themes/theme_one/index.theme
new file mode 100644
index 00000000..1949ed97
--- /dev/null
+++ b/mthemedaemon/test/themes/theme_one/index.theme
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Type=X-DUI-Metatheme
+Name=One
+Encoding=UTF-8
+
+[X-DUI-Metatheme]
+X-Visible=true
+X-Inherits=base
diff --git a/mthemedaemon/test/themes/theme_one/m/constants.ini b/mthemedaemon/test/themes/theme_one/m/constants.ini
new file mode 100644
index 00000000..3599b2b3
--- /dev/null
+++ b/mthemedaemon/test/themes/theme_one/m/constants.ini
@@ -0,0 +1,34 @@
+[Palette]
+COLOR_FOREGROUND = #000000 ;text color
+COLOR_SECONDARY_FOREGROUND = #cc6633 ;secondary text
+COLOR_BACKGROUND = #ffffff ;background
+
+;reversed elements
+COLOR_INVERTED_FOREGROUND = #ffffff ;text color
+COLOR_INVERTED_SECONDARY_FOREGROUND = #CC6633 ;secondary text
+COLOR_INVERTED_BACKGROUND = #FFFFFF ;background
+
+;selection
+COLOR_SELECT = #CC6633 ;selected item background
+
+;notification colors
+COLOR_WARNING = #CC0000 ;"red" attention color
+COLOR_ATTENTION = #CC9900 ;"yellow" attention color
+COLOR_NOTIFICATION = #C3F500 ;"green" attention color
+
+COLOR_LINK = #3465a4;
+COLOR_LINK_ACTIVE = #f5bf00;
+
+COLOR_ACCENT1 = #aad400 ;crayon #1 for applications
+COLOR_ACCENT2 = #69bfde ;crayon #2 for applications
+COLOR_ACCENT3 = #ff9955 ;crayon #3 for applications
+COLOR_ACCENT4 = #de87cd ;crayon #4 for applications
+COLOR_ACCENT5 = #d8b427 ;crayon #5 for applications
+
+[Fonts]
+FONT_XLARGE = "Nokia Sans" 3.2mm ;32px
+FONT_LARGE = "Nokia Sans" 2.8mm ;28px
+FONT_DEFAULT = "Nokia Sans" 2.4mm ;24px
+FONT_SMALL = "Nokia Sans" 2.0mm ;20px
+FONT_XSMALL = "Nokia Sans" 1.6mm ;16px
+
diff --git a/mthemedaemon/test/themes/theme_one/m/locale/fi/constants.ini b/mthemedaemon/test/themes/theme_one/m/locale/fi/constants.ini
new file mode 100644
index 00000000..73dd058d
--- /dev/null
+++ b/mthemedaemon/test/themes/theme_one/m/locale/fi/constants.ini
@@ -0,0 +1,34 @@
+[Palette]
+COLOR_FOREGROUND = #ffffff ;text color
+COLOR_SECONDARY_FOREGROUND = #cc6633 ;secondary text
+COLOR_BACKGROUND = #000000 ;background
+
+;reversed elements
+COLOR_INVERTED_FOREGROUND = #000000 ;text color
+COLOR_INVERTED_SECONDARY_FOREGROUND = #CC6633 ;secondary text
+COLOR_INVERTED_BACKGROUND = #FFFFFF ;background
+
+;selection
+COLOR_SELECT = #CC6633 ;selected item background
+
+;notification colors
+COLOR_WARNING = #CC0000 ;"red" attention color
+COLOR_ATTENTION = #CC9900 ;"yellow" attention color
+COLOR_NOTIFICATION = #C3F500 ;"green" attention color
+
+COLOR_LINK = #3465a4;
+COLOR_LINK_ACTIVE = #f5bf00;
+
+COLOR_ACCENT1 = #aad400 ;crayon #1 for applications
+COLOR_ACCENT2 = #69bfde ;crayon #2 for applications
+COLOR_ACCENT3 = #ff9955 ;crayon #3 for applications
+COLOR_ACCENT4 = #de87cd ;crayon #4 for applications
+COLOR_ACCENT5 = #d8b427 ;crayon #5 for applications
+
+[Fonts]
+FONT_XLARGE = "Arial" 3.2mm ;32px
+FONT_LARGE = "Arial" 2.8mm ;28px
+FONT_DEFAULT = "Arial" 2.4mm ;24px
+FONT_SMALL = "Arial" 2.0mm ;20px
+FONT_XSMALL = "Arial" 1.6mm ;16px
+