diff options
author | kali kaneko (leap communications) <kali@leap.se> | 2020-06-04 11:39:41 +0200 |
---|---|---|
committer | kali kaneko (leap communications) <kali@leap.se> | 2020-06-12 20:02:45 +0200 |
commit | 9b88f3ce47d090df167263ce2b4b6b430694da72 (patch) | |
tree | 3435b273857cc6bd26009767fa701673358d57cb /gui | |
parent | 719413cad922e1d34f2ea495bae0165cae53b22f (diff) |
[feat] add qt gui
Signed-off-by: kali kaneko (leap communications) <kali@leap.se>
Diffstat (limited to 'gui')
35 files changed, 939 insertions, 0 deletions
diff --git a/gui/assets/icon/ico/black/vpn_blocked.ico b/gui/assets/icon/ico/black/vpn_blocked.ico Binary files differnew file mode 100644 index 0000000..cbd319a --- /dev/null +++ b/gui/assets/icon/ico/black/vpn_blocked.ico diff --git a/gui/assets/icon/ico/black/vpn_off.ico b/gui/assets/icon/ico/black/vpn_off.ico Binary files differnew file mode 100644 index 0000000..5c63c42 --- /dev/null +++ b/gui/assets/icon/ico/black/vpn_off.ico diff --git a/gui/assets/icon/ico/black/vpn_on.ico b/gui/assets/icon/ico/black/vpn_on.ico Binary files differnew file mode 100644 index 0000000..4df0355 --- /dev/null +++ b/gui/assets/icon/ico/black/vpn_on.ico diff --git a/gui/assets/icon/ico/black/vpn_wait_0.ico b/gui/assets/icon/ico/black/vpn_wait_0.ico Binary files differnew file mode 100644 index 0000000..159cdf7 --- /dev/null +++ b/gui/assets/icon/ico/black/vpn_wait_0.ico diff --git a/gui/assets/icon/ico/black/vpn_wait_1.ico b/gui/assets/icon/ico/black/vpn_wait_1.ico Binary files differnew file mode 100644 index 0000000..4fee453 --- /dev/null +++ b/gui/assets/icon/ico/black/vpn_wait_1.ico diff --git a/gui/assets/icon/ico/black/vpn_wait_2.ico b/gui/assets/icon/ico/black/vpn_wait_2.ico Binary files differnew file mode 100644 index 0000000..4f01e75 --- /dev/null +++ b/gui/assets/icon/ico/black/vpn_wait_2.ico diff --git a/gui/assets/icon/ico/black/vpn_wait_3.ico b/gui/assets/icon/ico/black/vpn_wait_3.ico Binary files differnew file mode 100644 index 0000000..0f65749 --- /dev/null +++ b/gui/assets/icon/ico/black/vpn_wait_3.ico diff --git a/gui/assets/icon/ico/white/vpn_blocked.ico b/gui/assets/icon/ico/white/vpn_blocked.ico Binary files differnew file mode 100644 index 0000000..4bc27c5 --- /dev/null +++ b/gui/assets/icon/ico/white/vpn_blocked.ico diff --git a/gui/assets/icon/ico/white/vpn_off.ico b/gui/assets/icon/ico/white/vpn_off.ico Binary files differnew file mode 100644 index 0000000..66b3459 --- /dev/null +++ b/gui/assets/icon/ico/white/vpn_off.ico diff --git a/gui/assets/icon/ico/white/vpn_on.ico b/gui/assets/icon/ico/white/vpn_on.ico Binary files differnew file mode 100644 index 0000000..0fdbaad --- /dev/null +++ b/gui/assets/icon/ico/white/vpn_on.ico diff --git a/gui/assets/icon/ico/white/vpn_wait_0.ico b/gui/assets/icon/ico/white/vpn_wait_0.ico Binary files differnew file mode 100644 index 0000000..efee63c --- /dev/null +++ b/gui/assets/icon/ico/white/vpn_wait_0.ico diff --git a/gui/assets/icon/ico/white/vpn_wait_1.ico b/gui/assets/icon/ico/white/vpn_wait_1.ico Binary files differnew file mode 100644 index 0000000..5f76622 --- /dev/null +++ b/gui/assets/icon/ico/white/vpn_wait_1.ico diff --git a/gui/assets/icon/ico/white/vpn_wait_2.ico b/gui/assets/icon/ico/white/vpn_wait_2.ico Binary files differnew file mode 100644 index 0000000..2c7f417 --- /dev/null +++ b/gui/assets/icon/ico/white/vpn_wait_2.ico diff --git a/gui/assets/icon/ico/white/vpn_wait_3.ico b/gui/assets/icon/ico/white/vpn_wait_3.ico Binary files differnew file mode 100644 index 0000000..b7c168b --- /dev/null +++ b/gui/assets/icon/ico/white/vpn_wait_3.ico diff --git a/gui/assets/icon/png/black/vpn_blocked.png b/gui/assets/icon/png/black/vpn_blocked.png Binary files differnew file mode 100644 index 0000000..c3ce8b9 --- /dev/null +++ b/gui/assets/icon/png/black/vpn_blocked.png diff --git a/gui/assets/icon/png/black/vpn_off.png b/gui/assets/icon/png/black/vpn_off.png Binary files differnew file mode 100644 index 0000000..3f69c73 --- /dev/null +++ b/gui/assets/icon/png/black/vpn_off.png diff --git a/gui/assets/icon/png/black/vpn_on.png b/gui/assets/icon/png/black/vpn_on.png Binary files differnew file mode 100644 index 0000000..4fc2a77 --- /dev/null +++ b/gui/assets/icon/png/black/vpn_on.png diff --git a/gui/assets/icon/png/black/vpn_wait_0.png b/gui/assets/icon/png/black/vpn_wait_0.png Binary files differnew file mode 100644 index 0000000..0c09358 --- /dev/null +++ b/gui/assets/icon/png/black/vpn_wait_0.png diff --git a/gui/assets/icon/png/black/vpn_wait_1.png b/gui/assets/icon/png/black/vpn_wait_1.png Binary files differnew file mode 100644 index 0000000..5b45e39 --- /dev/null +++ b/gui/assets/icon/png/black/vpn_wait_1.png diff --git a/gui/assets/icon/png/black/vpn_wait_2.png b/gui/assets/icon/png/black/vpn_wait_2.png Binary files differnew file mode 100644 index 0000000..6920e88 --- /dev/null +++ b/gui/assets/icon/png/black/vpn_wait_2.png diff --git a/gui/assets/icon/png/black/vpn_wait_3.png b/gui/assets/icon/png/black/vpn_wait_3.png Binary files differnew file mode 100644 index 0000000..af759cf --- /dev/null +++ b/gui/assets/icon/png/black/vpn_wait_3.png diff --git a/gui/assets/icon/png/white/vpn_blocked.png b/gui/assets/icon/png/white/vpn_blocked.png Binary files differnew file mode 100644 index 0000000..bb3ae91 --- /dev/null +++ b/gui/assets/icon/png/white/vpn_blocked.png diff --git a/gui/assets/icon/png/white/vpn_off.png b/gui/assets/icon/png/white/vpn_off.png Binary files differnew file mode 100644 index 0000000..2fd06ab --- /dev/null +++ b/gui/assets/icon/png/white/vpn_off.png diff --git a/gui/assets/icon/png/white/vpn_on.png b/gui/assets/icon/png/white/vpn_on.png Binary files differnew file mode 100644 index 0000000..4e94c64 --- /dev/null +++ b/gui/assets/icon/png/white/vpn_on.png diff --git a/gui/assets/icon/png/white/vpn_wait_0.png b/gui/assets/icon/png/white/vpn_wait_0.png Binary files differnew file mode 100644 index 0000000..a4745dd --- /dev/null +++ b/gui/assets/icon/png/white/vpn_wait_0.png diff --git a/gui/assets/icon/png/white/vpn_wait_1.png b/gui/assets/icon/png/white/vpn_wait_1.png Binary files differnew file mode 100644 index 0000000..cd8ffe1 --- /dev/null +++ b/gui/assets/icon/png/white/vpn_wait_1.png diff --git a/gui/assets/icon/png/white/vpn_wait_2.png b/gui/assets/icon/png/white/vpn_wait_2.png Binary files differnew file mode 100644 index 0000000..f9d88bb --- /dev/null +++ b/gui/assets/icon/png/white/vpn_wait_2.png diff --git a/gui/assets/icon/png/white/vpn_wait_3.png b/gui/assets/icon/png/white/vpn_wait_3.png Binary files differnew file mode 100644 index 0000000..809e8ee --- /dev/null +++ b/gui/assets/icon/png/white/vpn_wait_3.png diff --git a/gui/gui.qrc b/gui/gui.qrc new file mode 100644 index 0000000..705c2b7 --- /dev/null +++ b/gui/gui.qrc @@ -0,0 +1,21 @@ +<RCC> + <qresource prefix="/"> + <file>qml/main.qml</file> + + <file>assets/icon/png/black/vpn_off.png</file> + <file>assets/icon/png/black/vpn_on.png</file> + <file>assets/icon/png/black/vpn_wait_0.png</file> + <file>assets/icon/png/black/vpn_wait_1.png</file> + <file>assets/icon/png/black/vpn_wait_2.png</file> + <file>assets/icon/png/black/vpn_wait_3.png</file> + + <file>assets/icon/png/white/vpn_off.png</file> + <file>assets/icon/png/white/vpn_on.png</file> + <file>assets/icon/png/white/vpn_wait_0.png</file> + <file>assets/icon/png/white/vpn_wait_1.png</file> + <file>assets/icon/png/white/vpn_wait_2.png</file> + <file>assets/icon/png/white/vpn_wait_3.png</file> + </qresource> +</RCC> + + diff --git a/gui/handlers.cpp b/gui/handlers.cpp new file mode 100644 index 0000000..79fbb58 --- /dev/null +++ b/gui/handlers.cpp @@ -0,0 +1,30 @@ +#include <QTimer> +#include <QDebug> + +#include "handlers.h" +#include "lib/libgoshim.h" + +Backend::Backend(QObject *parent) : QObject(parent) +{ +} + +void Backend::switchOn() +{ + SwitchOn(); +} + +void Backend::switchOff() +{ + SwitchOff(); +} + +void Backend::unblock() +{ + Unblock(); +} + +void Backend::quit() +{ + Quit(); + emit this->quitDone(); +} diff --git a/gui/handlers.h b/gui/handlers.h new file mode 100644 index 0000000..0a8782d --- /dev/null +++ b/gui/handlers.h @@ -0,0 +1,39 @@ +#ifndef HANDLERS_H +#define HANDLERS_H + +#include <QDebug> +#include <QObject> +#include "qjsonmodel.h" + +class QJsonWatch : public QObject { + + Q_OBJECT + + QJsonModel *model; + +public: + +signals: + + void jsonChanged(QString json); + +}; + +class Backend : public QObject { + + Q_OBJECT + +public: + explicit Backend(QObject *parent = 0); + +signals: + void quitDone(); + +public slots: + void switchOn(); + void switchOff(); + void unblock(); + void quit(); +}; + +#endif // HANDLERS_H diff --git a/gui/main.cpp b/gui/main.cpp new file mode 100644 index 0000000..f2545d3 --- /dev/null +++ b/gui/main.cpp @@ -0,0 +1,111 @@ +#include <QGuiApplication> +#include <QQmlApplicationEngine> +#include <QQuickWindow> +#include <QTimer> +#include <QtQml> +#include <string> + +#include "handlers.h" +#include "qjsonmodel.h" +#include "lib/libgoshim.h" + +/* Hi! I'm Troy McClure and I'll be your guide today. You probably remember me + from blockbusters like "here be dragons" and "darling, I wrote a little + contraption". */ + +/* Our glorious global object state. In here we store a serialized snapshot of + the context from the application "backend", living in the linked Go-land + lib. */ + +static char *json; + +/* We are interested in observing changes to this global json variable. + The jsonWatchdog bridges the gap from pure c callbacks to the rest of the c++ + logic. QJsonWatch comes from QObject so it can emit signals. */ + +QJsonWatch *qw; + +struct jsonWatchdog { + jsonWatchdog() { qw = new QJsonWatch; } + void changed() { emit qw->jsonChanged(QString(json)); } +}; + +/* we need C wrappers around every C++ object, so that we can invoke their methods + from the function pointers passed as callbacks to CGO. */ +extern "C" { +static void *newWatchdog(void) { return (void *)(new jsonWatchdog); } +static void jsonChanged(void *ptr) { + if (ptr != NULL) { + jsonWatchdog *klsPtr = static_cast<jsonWatchdog *>(ptr); + klsPtr->changed(); + } +} +} + +void *wd = newWatchdog(); + +/* onStatusChanged is the C function that we register as a callback with CGO, + to be called from the Go side. It pulls a string serialization of the + context object, than we then pass along to Qt objects and to Qml. */ +void onStatusChanged() { + char *ctx = RefreshContext(); + json = ctx; + /* the method wrapped emits a qt signal */ + jsonChanged(wd); + free(ctx); +} + +std::string getEnv(std::string const& key) +{ + char const* val = getenv(key.c_str()); + return val == NULL ? std::string() : std::string(val); +} + +int main(int argc, char **argv) { + + bool debugQml = getEnv("DEBUG_QML_DATA") == "yes"; + + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + app.setQuitOnLastWindowClosed(false); + QQmlApplicationEngine engine; + QQmlContext *ctx = engine.rootContext(); + + QJsonModel *model = new QJsonModel; + std::string json = R"({"appName": "unknown", "provider": "unknown"})"; + model->loadJson(QByteArray::fromStdString(json)); + + /* the backend handler has slots for calling back to Go when triggered by + signals in Qml. */ + Backend backend; + ctx->setContextProperty("backend", &backend); + + /* set the json model, load the qml */ + ctx->setContextProperty("jsonModel", model); + ctx->setContextProperty("debugQml", debugQml); + + engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); + + /* connect the jsonChanged signal explicitely. + In the lambda, we reload the json in the model every time we receive an + update from Go */ + QObject::connect(qw, &QJsonWatch::jsonChanged, [ctx, model](QString js) { + model->loadJson(js.toUtf8()); + }); + + /* connect quitDone signal, exit app */ + QObject::connect(&backend, &Backend::quitDone, []() { + QGuiApplication::quit(); + }); + + /* register statusChanged callback with CGO */ + const char *stCh = "OnStatusChanged"; + GoString statusChangedEvt = {stCh, (long int)strlen(stCh)}; + SubscribeToEvent(statusChangedEvt, (void *)onStatusChanged); + + /* let the Go side initialize its internal state */ + InitializeBitmaskContext(); + + /* kick off your shoes, put your feet up */ + return app.exec(); +} diff --git a/gui/qjsonmodel.cpp b/gui/qjsonmodel.cpp new file mode 100644 index 0000000..4e36eb8 --- /dev/null +++ b/gui/qjsonmodel.cpp @@ -0,0 +1,419 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2011 SCHUTZ Sacha + * Copyright (c) 2020 Kali Kaneko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <QFile> +#include <QDebug> +#include <QFont> + +#include "qjsonmodel.h" + + +QJsonTreeItem::QJsonTreeItem(QJsonTreeItem *parent) +{ + mParent = parent; +} + +QJsonTreeItem::~QJsonTreeItem() +{ + qDeleteAll(mChilds); +} + +void QJsonTreeItem::appendChild(QJsonTreeItem *item) +{ + mChilds.append(item); +} + +QJsonTreeItem *QJsonTreeItem::child(int row) +{ + return mChilds.value(row); +} + +QJsonTreeItem *QJsonTreeItem::parent() +{ + return mParent; +} + +int QJsonTreeItem::childCount() const +{ + return mChilds.count(); +} + +int QJsonTreeItem::row() const +{ + if (mParent) + return mParent->mChilds.indexOf(const_cast<QJsonTreeItem*>(this)); + + return 0; +} + +void QJsonTreeItem::setKey(const QString &key) +{ + mKey = key; +} + +void QJsonTreeItem::setValue(const QString &value) +{ + mValue = value; +} + +void QJsonTreeItem::setType(const QJsonValue::Type &type) +{ + mType = type; +} + +QString QJsonTreeItem::key() const +{ + return mKey; +} + +QString QJsonTreeItem::value() const +{ + return mValue; +} + +QJsonValue::Type QJsonTreeItem::type() const +{ + return mType; +} + +QJsonTreeItem* QJsonTreeItem::load(const QJsonValue& value, QJsonTreeItem* parent) +{ + QJsonTreeItem * rootItem = new QJsonTreeItem(parent); + rootItem->setKey("root"); + + if ( value.isObject()) + { + + //Get all QJsonValue childs + for (QString key : value.toObject().keys()){ + QJsonValue v = value.toObject().value(key); + QJsonTreeItem * child = load(v,rootItem); + child->setKey(key); + child->setType(v.type()); + rootItem->appendChild(child); + + } + + } + + else if ( value.isArray()) + { + //Get all QJsonValue childs + int index = 0; + for (QJsonValue v : value.toArray()){ + + QJsonTreeItem * child = load(v,rootItem); + child->setKey(QString::number(index)); + child->setType(v.type()); + rootItem->appendChild(child); + ++index; + } + } + else + { + rootItem->setValue(value.toVariant().toString()); + rootItem->setType(value.type()); + } + + return rootItem; +} + +//========================================================================= + +QJsonModel::QJsonModel(QObject *parent) + : QAbstractItemModel(parent) + , mRootItem{new QJsonTreeItem} +{ + mHeaders.append("key"); + mHeaders.append("value"); +} + +QJsonModel::QJsonModel(const QString& fileName, QObject *parent) + : QAbstractItemModel(parent) + , mRootItem{new QJsonTreeItem} +{ + mHeaders.append("key"); + mHeaders.append("value"); + load(fileName); +} + +QJsonModel::QJsonModel(QIODevice * device, QObject *parent) + : QAbstractItemModel(parent) + , mRootItem{new QJsonTreeItem} +{ + mHeaders.append("key"); + mHeaders.append("value"); + load(device); +} + +QJsonModel::QJsonModel(const QByteArray& json, QObject *parent) + : QAbstractItemModel(parent) + , mRootItem{new QJsonTreeItem} +{ + mHeaders.append("key"); + mHeaders.append("value"); + loadJson(json); +} + +QJsonModel::~QJsonModel() +{ + delete mRootItem; +} + +bool QJsonModel::load(const QString &fileName) +{ + QFile file(fileName); + bool success = false; + if (file.open(QIODevice::ReadOnly)) { + success = load(&file); + file.close(); + } + else success = false; + + return success; +} + +bool QJsonModel::load(QIODevice *device) +{ + return loadJson(device->readAll()); +} + +bool QJsonModel::loadJson(const QByteArray &json) +{ + auto const& jdoc = QJsonDocument::fromJson(json); + + if (!jdoc.isNull()) + { + beginResetModel(); + + delete mRootItem; + + if (jdoc.isArray()) { + mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.array())); + mRootItem->setType(QJsonValue::Array); + } else { + mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.object())); + mRootItem->setType(QJsonValue::Object); + } + endResetModel(); + + // ??? + emit dataChanged(QModelIndex(), QModelIndex(), {}); + return true; + } + + qDebug()<<Q_FUNC_INFO<<"cannot load json"; + return false; +} + + +QVariant QJsonModel::data(const QModelIndex &index, int role) const +{ + + if (!index.isValid()) + return QVariant(); + + + QJsonTreeItem *item = static_cast<QJsonTreeItem*>(index.internalPointer()); + + + switch (role) { + case Roles::KeyRole: + return item->key(); + case Roles::ValueRole: + return item->value(); + case Qt::DisplayRole: + { + if (index.column() == 0) + return QString("%1").arg(item->key()); + else if (index.column() == 1) + return QString("%1").arg(item->value()); + else + return QString(""); + + } + case Qt::EditRole: + { + if (index.column() == 1) + return QString("%1").arg(item->value()); + else + return QString(""); + } + default: + return QVariant(); + } + return QVariant(); + +} + +bool QJsonModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + int col = index.column(); + if (Qt::EditRole == role) { + if (col == 1) { + QJsonTreeItem *item = static_cast<QJsonTreeItem*>(index.internalPointer()); + item->setValue(value.toString()); + emit dataChanged(index, index, {Qt::EditRole}); + return true; + } + } + + return false; +} + +QVariant QJsonModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) { + + return mHeaders.value(section); + } + else + return QVariant(); +} + +QModelIndex QJsonModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + QJsonTreeItem *parentItem; + + if (!parent.isValid()) + parentItem = mRootItem; + else + parentItem = static_cast<QJsonTreeItem*>(parent.internalPointer()); + + QJsonTreeItem *childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +QModelIndex QJsonModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + QJsonTreeItem *childItem = static_cast<QJsonTreeItem*>(index.internalPointer()); + QJsonTreeItem *parentItem = childItem->parent(); + + if (parentItem == mRootItem) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} + +int QJsonModel::rowCount(const QModelIndex &parent) const +{ + QJsonTreeItem *parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parentItem = mRootItem; + else + parentItem = static_cast<QJsonTreeItem*>(parent.internalPointer()); + + return parentItem->childCount(); +} + +int QJsonModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 2; +} + +Qt::ItemFlags QJsonModel::flags(const QModelIndex &index) const +{ + int col = index.column(); + auto item = static_cast<QJsonTreeItem*>(index.internalPointer()); + + auto isArray = QJsonValue::Array == item->type(); + auto isObject = QJsonValue::Object == item->type(); + + if ((col == 1) && !(isArray || isObject)) { + return Qt::ItemIsEditable | QAbstractItemModel::flags(index); + } else { + return QAbstractItemModel::flags(index); + } +} + +QJsonDocument QJsonModel::json() const +{ + + auto v = genJson(mRootItem); + QJsonDocument doc; + + if (v.isObject()) { + doc = QJsonDocument(v.toObject()); + } else { + doc = QJsonDocument(v.toArray()); + } + + return doc; +} + +QJsonValue QJsonModel::genJson(QJsonTreeItem * item) const +{ + auto type = item->type(); + int nchild = item->childCount(); + + if (QJsonValue::Object == type) { + QJsonObject jo; + for (int i = 0; i < nchild; ++i) { + auto ch = item->child(i); + auto key = ch->key(); + jo.insert(key, genJson(ch)); + } + return jo; + } else if (QJsonValue::Array == type) { + QJsonArray arr; + for (int i = 0; i < nchild; ++i) { + auto ch = item->child(i); + arr.append(genJson(ch)); + } + return arr; + } else { + QJsonValue va(item->value()); + return va; + } +} + +QHash<int, QByteArray> QJsonModel::roleNames() const +{ + QHash<int, QByteArray> roles; + roles[Roles::KeyRole] = "key"; + roles[Roles::ValueRole] = "value"; + return roles; +} + +QByteArray QJsonModel::getJson() +{ + return ((QJsonDocument)(this->json())).toJson(); +} diff --git a/gui/qjsonmodel.h b/gui/qjsonmodel.h new file mode 100644 index 0000000..c7d25bb --- /dev/null +++ b/gui/qjsonmodel.h @@ -0,0 +1,111 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2011 SCHUTZ Sacha + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef QJSONMODEL_H +#define QJSONMODEL_H + +#include <QAbstractItemModel> +#include <QHash> +#include <QIcon> +#include <QJsonDocument> +#include <QJsonValue> +#include <QJsonArray> +#include <QJsonObject> + +class QJsonModel; +class QJsonItem; + +class QJsonTreeItem +{ +public: + QJsonTreeItem(QJsonTreeItem * parent = nullptr); + ~QJsonTreeItem(); + void appendChild(QJsonTreeItem * item); + QJsonTreeItem *child(int row); + QJsonTreeItem *parent(); + int childCount() const; + int row() const; + void setKey(const QString& key); + void setValue(const QString& value); + void setType(const QJsonValue::Type& type); + QString key() const; + QString value() const; + QJsonValue::Type type() const; + + static QJsonTreeItem* load(const QJsonValue& value, QJsonTreeItem * parent = 0); + +protected: + + +private: + QString mKey; + QString mValue; + QJsonValue::Type mType; + QList<QJsonTreeItem*> mChilds; + QJsonTreeItem * mParent; + + +}; + +//--------------------------------------------------- + +class QJsonModel : public QAbstractItemModel +{ + Q_OBJECT + enum Roles { + KeyRole = Qt::UserRole + 1, + ValueRole + }; + +public: + explicit QJsonModel(QObject *parent = nullptr); + QJsonModel(const QString& fileName, QObject *parent = nullptr); + QJsonModel(QIODevice * device, QObject *parent = nullptr); + QJsonModel(const QByteArray& json, QObject *parent = nullptr); + ~QJsonModel(); + bool load(const QString& fileName); + bool load(QIODevice * device); + bool loadJson(const QByteArray& json); + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE; + QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column,const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; + QJsonDocument json() const; + QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; + Q_INVOKABLE QByteArray getJson(); + +private: + QJsonValue genJson(QJsonTreeItem *) const; + + QJsonTreeItem * mRootItem; + QStringList mHeaders; + + +}; + +#endif // QJSONMODEL_H diff --git a/gui/qml/main.qml b/gui/qml/main.qml new file mode 100644 index 0000000..38f5331 --- /dev/null +++ b/gui/qml/main.qml @@ -0,0 +1,208 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Extras 1.2 +import Qt.labs.platform 1.1 + +ApplicationWindow { + + id: app + visible: false + + property var ctx + + Connections { + target: jsonModel + onDataChanged: { + ctx = JSON.parse(jsonModel.getJson()); + } + } + + Component.onCompleted: { + /* stupid as it sounds, windows doesn't like to have the systray icon + not being attached to an actual application window. + We can still use this quirk, and can use the AppWindow with deferred + Loaders as a placeholder for all the many dialogs, or to load + a nice splash screen etc... */ + app.visible = true; + hide(); + } + + function toHuman(st) { + switch(st) { + case "off": + // TODO improve string interpolation, give context to translators etc + return qsTr(ctx.appName + " off"); + case "on": + return qsTr(ctx.appName + " on"); + case "connecting": + return qsTr("Connecting to " + ctx.appName); + case "stopping": + return qsTr("Stopping " + ctx.appName); + case "failed": + return qsTr(ctx.appName + " blocking internet"); // TODO failed is not handed yet + } + } + + property var icons: { + "off": "qrc:/assets/icon/png/black/vpn_off.png", + "on": "qrc:/assets/icon/png/black/vpn_on.png", + "wait": "qrc:/assets/icon/png/black/vpn_wait_0.png", + "blocked": "qrc:/assets/icon/png/black/vpn_blocked.png", + } + + + SystemTrayIcon { + + id: systray + visible: true + onActivated: { + console.debug("app is", ctx.appName) + menu.open() + } + + Component.onCompleted: { + icon.source = icons["off"] + tooltip = qsTr("Checking status...") + console.debug("systray init completed") + show(); + } + + + menu: Menu { + StateGroup { + id: vpn + state: ctx ? ctx.status : "" + states: [ + State { name: "initializing" }, + State { + name: "off" + PropertyChanges { target: systray; tooltip: toHuman("off"); icon.source: icons["off"] } + PropertyChanges { target: statusItem; text: toHuman("off") } + }, + State { + name: "on" + PropertyChanges { target: systray; tooltip: toHuman("on"); icon.source: icons["on"] } + PropertyChanges { target: statusItem; text: toHuman("on") } + }, + State { + name: "starting" + PropertyChanges { target: systray; tooltip: toHuman("connecting"); icon.source: icons["wait"] } + PropertyChanges { target: statusItem; text: toHuman("connecting") } + }, + State { + name: "stopping" + PropertyChanges { target: systray; tooltip: toHuman["stopping"]; icon.source: icons["wait"] } + PropertyChanges { target: statusItem; text: toHuman["stopping"] } + }, + State { + name: "failed" + PropertyChanges { target: systray; tooltip: toHuman["failed"]; icon.source: icons["wait"] } + PropertyChanges { target: statusItem; text: toHuman["failed"] } + } + ] + } + + /* + LoginDialog { + id: login + } + DonateDialog { + id: donate + } + MessageDialog { + id: about + buttons: MessageDialog.Ok + title: "About" + text: "<p>%1 is an easy, fast, and secure VPN service from %2. %1 does not require a user account, keep logs, or track you in any way.</p> + <p>This service is paid for entirely by donations from users like you. <a href=\"%3\">Please donate</a>.</p> + <p>By using this application, you agree to the <a href=\"%4\">Terms of Service</a>. This service is provided as-is, without any warranty, and is intended for people who work to make the world a better place.</p>".arg(ctxSystray.applicationName).arg(ctxSystray.provider).arg(ctxSystray.donateURL).arg(ctxSystray.tosURL) + informativeText: "%1 version: %2".arg(ctxSystray.applicationName).arg(ctxSystray.version) + } + MessageDialog { + id: errorStartingVPN + buttons: MessageDialog.Ok + modality: Qt.NonModal + title: "Error starting VPN" + text: "Can't connect to %1".arg(ctxSystray.applicationName) + detailedText: ctxSystray.errorStartingMsg + visible: ctxSystray.errorStartingMsg != "" + } + MessageDialog { + id: authAgent + buttons: MessageDialog.Ok + modality: Qt.NonModal + title: "Missing authentication agent" + text: "Could not find a polkit authentication agent. Please run one and try again." + visible: ctxSystray.authAgent == true + } + MessageDialog { + id: initFailure + buttons: MessageDialog.Ok + modality: Qt.NonModal + title: "Initialization Error" + text: ctxSystray.errorInitMsg + visible: ctxSystray.errorInitMsg != "" + } + */ + + MenuItem { + id: statusItem + text: qsTr("Checking status...") + enabled: false + } + + MenuItem { + text: { + if (vpn.state == "failed") + qsTr("Reconnect") + else + qsTr("Turn on") + } + onTriggered: { + backend.switchOn() + } + visible: ctx ? (ctx.status == "off" || ctx.status == "failed") : false + } + + MenuItem { + text: { + if (ctx && ctx.status == "starting") + qsTr("Cancel") + else + qsTr("Turn off") + } + onTriggered: { + backend.switchOff() + } + visible: ctx ? (ctx.status == "on" || ctx.status == "starting" || ctx.status == "failed") : false + } + + MenuSeparator {} + + MenuItem { + text: qsTr("Help...") + //onTriggered: ctxSystray.help() + } + + MenuItem { + text: qsTr("Donate...") + //onTriggered: ctxSystray.donate() + visible: true + //visible: ctx.showDonate + } + + MenuItem { + text: qsTr("About...") + //onTriggered: about.open() + } + + MenuSeparator {} + + MenuItem { + text: qsTr("Quit") + onTriggered: backend.quit() + } + } + } +} |