/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Qt Software Information (qt-info@nokia.com) ** ** This file is part of the WebClient project on Trolltech Labs. ** ** This file may be used under the terms of the GNU General Public ** License version 2.0 or 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of ** this file.  Please review the following information to ensure GNU ** General Public Licensing requirements will be met: ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and ** http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ****************************************************************************/ #include "eventqueue.h" #include "webclient.h" #include void encodeText(QString &text) { // encode "breakout" characters text.replace("&", "&"); text.replace("<", "<"); text.replace(">", ">"); text.replace("'", "'"); text.replace('"', """); text.replace("/", "/"); // white-list for safe tags text.replace("<b>", ""); text.replace("</b>", ""); } QString widgetGetText(QWidget *widget) { QString text; if (QLineEdit *lineEdit = qobject_cast(widget)) { text = lineEdit->text(); } else if (QTextEdit *textEdit = qobject_cast(widget)) { text = textEdit->toPlainText(); } else if (QPushButton *pushButton = qobject_cast(widget)) { text = pushButton->text(); } else if (QLabel *label = qobject_cast(widget)) { text = label->text(); } else { text = "unsupported text widget";; } encodeText(text); return text; } EventQueue::EventQueue(Session *session) :m_session(session) { reset(); } void EventQueue::setSession(Session *session) { m_session = session; reset(); } void EventQueue::reset() { DEBUG << "reset"; images.clear(); geometries.clear(); } void EventQueue::handleRequest(HttpRequest *request, HttpResponse *response) { // DEBUG << "handle request" << request->path(); const QByteArray path = request->path(); if (path == "/content") { DEBUG << "content"; response->setBody(queueToByteArray()); } else if (path == "/idle") { DEBUG << "idle"; response->setBody(queueToByteArray()); // If the part starts with "/json" then the rest of the string is an json-encoded // string object. } else if (path.startsWith("/json")) { QByteArray jsonText = path.mid(5); // remove "/json" handleJsonRequest(jsonText, response); } } void EventQueue::handleJsonRequest(QByteArray jsonText, HttpResponse *response) { // replace symbols that can't be a part of the url text. jsonText.replace("%7B", "{"); jsonText.replace("%7D", "}"); jsonText.replace("%22", "\""); DEBUG << "json request" << jsonText; json_object* jsonRequest = json_tokener_parse(jsonText.data()); if (!jsonRequest) return; json_object* type = json_object_object_get(jsonRequest, "type"); QByteArray typeText = json_object_get_string(type); if (typeText == "img") { handleImageRequest(jsonRequest, response); } else if (typeText == "img-static") { handleStaticImageRequest(jsonRequest, response); } json_object_put(jsonRequest); //free } void EventQueue::handleImageRequest(json_object* jsonRequest, HttpResponse *response) { json_object* idObject = json_object_object_get(jsonRequest, "id"); if (!idObject) return; const quintptr id = json_object_get_int(idObject); DEBUG << "id" << this << id << images.value(id).size(); // if (staticCompressedImages.contains(id)) // qFatal("serving dynamic image for static widdget"); // Save server memory and CPU time by storing images in the png // format after the first serve. The browser could request // the image again, for example if the user hits refresh. QByteArray pngImage; QImage uncompressedImage = images.value(id); if (uncompressedImage.isNull()== false) { images.remove(id); QBuffer buffer(&pngImage); buffer.open(QIODevice::WriteOnly); QImageWriter writer(&buffer, "png"); writer.write(uncompressedImage); compressedImages.insert(id, pngImage); } if (pngImage.isEmpty()) { pngImage = compressedImages.value(id); } response->setBody(pngImage); response->setContentType("image/png"); } void EventQueue::handleStaticImageRequest(json_object* jsonRequest, HttpResponse *response) { json_object* imageHashObject = json_object_object_get(jsonRequest, "imagehash"); DEBUG << "handleStaticImageRequest" << imageHashObject; if (!imageHashObject) return; const quintptr imageHash = json_object_get_int(imageHashObject); DEBUG << "imagehash" << this << imageHash; quintptr imageid = staticImageHashToWidgetId.value(imageHash); QByteArray pngImage = staticCompressedImages.value(imageid); DEBUG << imageid << pngImage.count(); response->setNeverExpires(); response->setBody(pngImage); response->setContentType("image/png"); } void EventQueue::addUpdateEvent(quintptr id, const QImage &image, QRect updateRect) { DEBUG << "addUpdateEvent" << this << id << image.size(); images.insert(id, image); addEvent(id, EventEntry::Update); } void EventQueue::addStaticUpdateEvent(quintptr id, uint hash, const QByteArray &pngImage) { DEBUG << "addStaticUpdateEvent" << this << hash << pngImage.size(); staticCompressedImages.insert(id, pngImage); staticImageHashToWidgetId.insert(hash, id); staticWidgetIdToImageHash.insert(id, hash); addEvent(id, EventEntry::StaticUpdate); } void EventQueue::addStaticUpdateEvent(quintptr id) { DEBUG << "addStaticUpdateEvent" << this << id; // assert staticCompressedImages.conrains(id); addEvent(id, EventEntry::StaticUpdate); } void EventQueue::addGeometryEvent(quintptr id, QRect globalGeometry) { if (geometries[id] == globalGeometry) { return; } geometries[id] = globalGeometry; addEvent(id, EventEntry::Geometry); } void EventQueue::addParentChangeEvent(quintptr id) { // DEBUG << "parentchanged"; addEvent(id, EventEntry::ParentChange); } void EventQueue::addEvent(quintptr id, EventEntry::Type type) { EventEntry entry(id, type); if (events.contains(entry)) { // DEBUG << "event already in queue"; return; } events.enqueue(entry); // DEBUG << json_object_to_json_string(toJson(entry)); if (m_session) m_session->contentAvailable(); } QByteArray EventQueue::queueToByteArray() { if (events.isEmpty()) return QByteArray(); json_object *array = json_object_new_array(); while (events.isEmpty() == false) { json_object_array_add(array, toJson(events.dequeue())); } QByteArray text(json_object_to_json_string(array)); DEBUG << "sending event text" << text; // json_free(array); return text; } json_object *EventQueue::toJson(const EventEntry &event) const { switch(event.type) { case EventEntry::Update: return jsonUpdateEvent(event); case EventEntry::StaticUpdate: return jsonStaticUpdateEvent(event); case EventEntry::Show: return jsonShowEvent(event); case EventEntry::ShowLineEdit: return jsonShowLineEditEvent(event); case EventEntry::Hide: return jsonHideEvent(event); case EventEntry::Geometry: return jsonGeometryEvent(event); case EventEntry::ParentChange: return jsonParentChangeEvent(event); case EventEntry::TextUpdate: return jsonTextUpdateEvent(event); default: return json_object_new_object(); break; } } json_object *EventQueue::jsonShowEvent(const EventEntry &event) const { struct json_object *obj = json_object_new_object(); QWidget *widget = (QWidget *)event.id; json_object_object_add(obj, "type", json_object_new_string("show")); json_object_object_add(obj, "id", json_object_new_int(event.id)); json_object_object_add(obj, "widgetType", toJsonWidgetType(widget)); json_object_object_add(obj, "className", json_object_new_string(const_cast(widget->metaObject()->className()))); json_object_object_add(obj, "objectName", json_object_new_string(widget->objectName().toAscii().data())); return obj; } json_object *EventQueue::jsonShowLineEditEvent(const EventEntry &event) const { DEBUG << "### Show line edit"; struct json_object *obj = json_object_new_object(); json_object_object_add(obj, "type", json_object_new_string("showLineEdit")); json_object_object_add(obj, "id", json_object_new_int(event.id)); return obj; } json_object *EventQueue::jsonHideEvent(const EventEntry &event) const { struct json_object *obj = json_object_new_object(); json_object_object_add(obj, "type", json_object_new_string("hide")); json_object_object_add(obj, "id", json_object_new_int(event.id)); json_object_object_add(obj, "widgetType", toJsonWidgetType((QWidget *)event.id)); return obj; } json_object *EventQueue::jsonUpdateEvent(const EventEntry &event) const { struct json_object *obj = json_object_new_object(); json_object_object_add(obj, "type", json_object_new_string("update")); json_object_object_add(obj, "id", json_object_new_int(event.id)); json_object_object_add(obj, "widgetType", toJsonWidgetType((QWidget *)event.id)); return obj; } json_object *EventQueue::jsonStaticUpdateEvent(const EventEntry &event) const { struct json_object *obj = json_object_new_object(); json_object_object_add(obj, "type", json_object_new_string("update")); json_object_object_add(obj, "id", json_object_new_int(event.id)); json_object_object_add(obj, "imagehash", json_object_new_string(QByteArray::number(staticWidgetIdToImageHash.value(event.id)).data())); json_object_object_add(obj, "widgetType", toJsonWidgetType((QWidget *)event.id)); return obj; } json_object *EventQueue::jsonGeometryEvent(const EventEntry &event) const { struct json_object *obj = json_object_new_object(); json_object_object_add(obj, "type", json_object_new_string("geometry")); json_object_object_add(obj, "id", json_object_new_int(event.id)); json_object_object_add(obj, "widgetType", toJsonWidgetType((QWidget *)event.id)); QRect geometry = geometries.value(event.id); json_object_object_add(obj, "x", json_object_new_int(geometry.x())); json_object_object_add(obj, "y", json_object_new_int(geometry.y())); json_object_object_add(obj, "w", json_object_new_int(geometry.width())); json_object_object_add(obj, "h", json_object_new_int(geometry.height())); return obj; } json_object *EventQueue::jsonParentChangeEvent(const EventEntry &event) const { // DEBUG << "parentchanged send"; QWidget *parent = ((QWidget *)event.id)->parentWidget(); struct json_object *obj = json_object_new_object(); json_object_object_add(obj, "type", json_object_new_string("parentChange")); json_object_object_add(obj, "id", json_object_new_int(event.id)); json_object_object_add(obj, "widgetType", toJsonWidgetType(parent)); json_object_object_add(obj, "parent", json_object_new_int((int)(parent))); return obj; } json_object *EventQueue::jsonTextUpdateEvent(const EventEntry &event) const { struct json_object *obj = json_object_new_object(); json_object_object_add(obj, "type", json_object_new_string("textUpdate")); json_object_object_add(obj, "id", json_object_new_int(event.id)); json_object_object_add(obj, "widgetType", toJsonWidgetType((QWidget *)event.id)); json_object_object_add(obj, "text", json_object_new_string(widgetGetText((QWidget *)event.id).toLocal8Bit().data())); return obj; } json_object *EventQueue::toJsonWidgetType(QWidget *widget) const { if (!widget || m_session && m_session->m_server->shouldSkipUpdate(widget->metaObject()->className())) return json_object_new_string("skippedwidget"); if (qobject_cast(widget)) return json_object_new_string("lineedit"); if (qobject_cast(widget)) return json_object_new_string("textedit"); if (qobject_cast(widget)) return json_object_new_string("label"); if (qobject_cast(widget)) return json_object_new_string("midisubwindow"); if (qobject_cast(widget)) return json_object_new_string("pusbutton"); else return json_object_new_string("generic"); } QByteArray EventQueue::pngCompress(const QImage &image) { QByteArray pngImage; QBuffer buffer(&pngImage); buffer.open(QIODevice::WriteOnly); QImageWriter writer(&buffer, "png"); writer.write(image); return pngImage; }