#include #include #include "msolimageiohandler.h" #include "cardmanageradaptor.h" #include "cardmanager.h" Card::Card(const QString &id, QObject *parent) : QObject(parent), _id(id), _vibrate(true), _open(false), _visible(false) { } QString Card::id() const { return _id; } QString Card::header() const { return _header; } void Card::setHeader(const QString &header) { if (_header != header) { _header = header; emit headerChanged(); } } QString Card::title() const { return _title; } void Card::setTitle(const QString &title) { if (_title != title) { _title = title; emit titleChanged(); } } QString Card::info() const { return _info; } void Card::setInfo(const QString &info) { if (_info != info) { _info = info; emit infoChanged(); } } QDateTime Card::dateTime() const { return _dateTime; } void Card::setDateTime(const QDateTime &dt) { if (_dateTime != dt) { _dateTime = dt; emit dateTimeChanged(); } } QString Card::icon() const { return _icon; } void Card::setIcon(const QString &url) { _icon = url; } QString Card::picture() const { return _picture; } void Card::setPicture(const QString &url) { _picture = url; } bool Card::isVibrate() const { return _vibrate; } void Card::setVibrate(bool vibrate) { if (_vibrate != vibrate) { _vibrate = vibrate; emit vibrateChanged(); } } QString Card::text() const { return _text; } void Card::setText(const QString &text) { if (_text != text) { _text = text; emit textChanged(); } } QStringList Card::menuOptions() const { return _options; } void Card::setMenuOptions(const QStringList &options) { if (_options != options) { _options = options; emit menuOptionsChanged(); } } bool Card::isOpen() const { return _open; } void Card::setOpen(bool open) { _open = open; } bool Card::isVisible() const { return _visible; } void Card::setVisible(bool visible) { _visible = visible; } CardDeck::CardDeck(const QString &package, const QString &application, QObject *parent) : QObject(parent), _package(package), _application(application), _refreshTimer(new QTimer(this)) { _refreshTimer->setSingleShot(true); _refreshTimer->setInterval(100); connect(_refreshTimer, &QTimer::timeout, this, &CardDeck::needsRefresh); } QString CardDeck::package() const { return _package; } QString CardDeck::application() const { return _application; } QList CardDeck::cards() const { return _deck; } Card* CardDeck::cardAt(int position) const { return _deck.at(position); } Card* CardDeck::cardAt(const QString &id) const { return _ids.value(id); } void CardDeck::appendCard(Card *card) { insertCard(_deck.size(), card); } void CardDeck::insertCard(int position, Card *card) { if (_ids.contains(card->id())) { qWarning() << "Card" << card->id() << "already present"; return; } _deck.insert(position, card); _ids.insert(card->id(), card); connect(card, &Card::headerChanged, this, &CardDeck::scheduleRefresh); connect(card, &Card::titleChanged, this, &CardDeck::scheduleRefresh); connect(card, &Card::infoChanged, this, &CardDeck::scheduleRefresh); connect(card, &Card::vibrateChanged, this, &CardDeck::scheduleRefresh); connect(card, &Card::textChanged, this, &CardDeck::scheduleRefresh); connect(card, &Card::menuOptionsChanged, this, &CardDeck::scheduleRefresh); emit cardAdded(card); scheduleRefresh(); } void CardDeck::removeCard(int position) { Card * card = _deck.takeAt(position); _ids.remove(card->id()); disconnect(card, 0, this, 0); emit cardRemoved(card); scheduleRefresh(); } void CardDeck::removeCard(Card *card) { int position = _deck.indexOf(card); if (position >= 0) { removeCard(position); } else { qWarning() << "Card not found"; } } void CardDeck::scheduleRefresh() { if (!_refreshTimer->isActive()) { _refreshTimer->start(); } } CardManager::CardManager(FmsManager *fms, ToqManager *toq) : QObject(toq), _toq(toq), _fms(fms) { _toq->setEndpointListener(ToqConnection::AppMessagingEndpoint, this); new CardManagerAdaptor(this); QDBusConnection::sessionBus().registerObject("/com/javispedro/saltoq/CardManager", this, QDBusConnection::ExportAdaptors | QDBusConnection::ExportChildObjects); } void CardManager::handleMessage(const ToqConnection::Message &msg) { QPair p = unpackMessage(msg.payload); CardDeck *deck = p.first; if (!deck) { return; } QHash dict = unpackDictionary(p.second); qDebug() << dict; QString id = dict["id"]; Card *card = deck->cardAt(id); if (card) { QString event = dict["event"]; if (event == "open") { card->setOpen(true); } else if (event == "closed") { card->setOpen(false); } else if (event == "visible") { card->setVisible(true); } else if (event == "invisible") { card->setVisible(false); } else if (event == "selected") { emit card->optionSelected(dict["eventdata"]); } else { qWarning() << "Unknown card event" << event; } } else { qWarning() << "Card" << id << "not found in deck" << deck->package(); } } void CardManager::installDeck(CardDeck *deck) { if (_decks.contains(deck->package())) { qWarning() << "Deck" << deck->package() << "is already installed"; return; } _decks.insert(deck->package(), deck); connect(deck, &CardDeck::needsRefresh, this, &CardManager::handleDeckNeedsRefresh); connect(deck, &CardDeck::cardAdded, this, &CardManager::handleCardAdded); connect(deck, &CardDeck::cardRemoved, this, &CardManager::handleCardRemoved); } void CardManager::uninstallDeck(CardDeck *deck) { if (!_decks.contains(deck->package())) { qWarning() << "Deck" << deck->package() << "is not installed"; } _decks.remove(deck->package()); disconnect(deck, 0, this, 0); } QString CardManager::sendImage(CardDeck *deck, const QString &iconName, const QImage &image) { QString remoteFile = QString("/packages/%1/%2.img").arg(deck->package()).arg(iconName); _fms->updateFile(remoteFile, convertImageToMsol(image)); return QString("fms:/%1.img").arg(iconName); } QString CardManager::escapeString(const QString &s) { QString e(s); e.replace('\\', "\\\\"); e.replace('\n', "\\n"); e.replace('"', "\\\""); return e; } QString CardManager::generateCardDescription(const QString &verb, Card *card) const { QString desc = verb + " { "; desc += QString("id = \"%1\", ").arg(card->id()); desc += QString("version = 2, cardevents = \"true\", "); if (!card->header().isEmpty()) { desc += QString("header = \"%1\", ").arg(escapeString(card->header())); } if (!card->title().isEmpty()) { desc += QString("title = \"%1\", ").arg(escapeString(card->title())); } if (card->dateTime().isValid()) { desc += QString("time = \"%1\", ").arg(card->dateTime().toMSecsSinceEpoch()); } if (!card->info().isEmpty()) { desc += QString("info = \"%1\", ").arg(escapeString(card->info())); } if (!card->icon().isEmpty()) { desc += QString("icon = \"%1\", ").arg(escapeString(card->icon())); } if (!card->isVibrate()) { desc += QString("suppressvibe = \"true\", "); } if (!card->text().isEmpty()) { desc += QString("detail = { \"%1\" }").arg(escapeString(card->text())); } desc += QString("}\n"); return desc; } QByteArray CardManager::packMessage(CardDeck *deck, const QString &msg) const { QByteArray data; QByteArray target = QString("%1/%2").arg(deck->package()).arg(deck->application()).toUtf8(); QByteArray payload = msg.toUtf8(); target.append('\0'); // Null terminated data.reserve(1 + target.size() + payload.size()); data.append(char(target.size())); data.append(target); data.append(payload); return data; } QPair CardManager::unpackMessage(const QByteArray &data) const { CardDeck *deck = 0; QString payload; int id_size = data.at(0); QStringList id = QString::fromUtf8(data.mid(1, id_size)).split('/'); deck = _decks.value(id.at(0)); payload = QString::fromUtf8(data.mid(1 + id_size)); if (!deck) { qWarning() << "Deck with id" << id[0] << "not found"; } return QPair(deck, payload); } QHash CardManager::unpackDictionary(const QString &data) const { QHash dict; const QRegularExpression re("(\\w+) = \"([^\"]*)\""); QRegularExpressionMatchIterator it = re.globalMatch(data); while (it.hasNext()) { QRegularExpressionMatch m = it.next(); dict.insert(m.captured(1), m.captured(2)); } return dict; } void CardManager::refreshDeck(CardDeck *deck) { Q_ASSERT(deck); Q_ASSERT(_toq->isConnected()); qDebug() << "Refreshing deck" << deck->package(); QList cards = deck->cards(); QString data; for (Card * card : cards) { data += generateCardDescription("NotifyCard", card); } qDebug() << data; QString cardsFile = QString("/packages/%1/cards.dat").arg(deck->package()); if (!data.isEmpty()) { _fms->updateFile(cardsFile, data.toUtf8()); } else { _fms->deleteFile(cardsFile); } } void CardManager::handleToqConnected() { for (const QString &package : _pending) { refreshDeck(_decks.value(package)); } _pending.clear(); } void CardManager::handleDeckNeedsRefresh() { CardDeck *deck = static_cast(sender()); qDebug() << deck->package() << "needs refresh"; if (_toq->isConnected()) { refreshDeck(deck); } else { _pending.insert(deck->package()); } } void CardManager::handleCardAdded(Card *card) { CardDeck *deck = static_cast(sender()); qDebug() << "Card added"; if (_toq->isConnected()) { QString cmd = generateCardDescription("PopupCard", card); QByteArray payload = packMessage(deck, cmd); qDebug() << cmd; _toq->sendMessage(ToqConnection::AppMessagingEndpoint, ToqConnection::AppMessagingEndpoint + 1, 0x8000, payload); } } void CardManager::handleCardRemoved(Card *card) { CardDeck *deck = static_cast(sender()); if (_toq->isConnected()) { QString cmd = QString("DeleteCard { id = \"%1\" }\n").arg(card->id()); QByteArray payload = packMessage(deck, cmd); qDebug() << cmd; _toq->sendMessage(ToqConnection::AppMessagingEndpoint, ToqConnection::AppMessagingEndpoint + 1, 0x8000, payload); } }