summaryrefslogtreecommitdiff
path: root/gui
diff options
context:
space:
mode:
authorkali kaneko (leap communications) <kali@leap.se>2020-06-04 11:39:41 +0200
committerkali kaneko (leap communications) <kali@leap.se>2020-06-12 20:02:45 +0200
commit9b88f3ce47d090df167263ce2b4b6b430694da72 (patch)
tree3435b273857cc6bd26009767fa701673358d57cb /gui
parent719413cad922e1d34f2ea495bae0165cae53b22f (diff)
[feat] add qt gui
Signed-off-by: kali kaneko (leap communications) <kali@leap.se>
Diffstat (limited to 'gui')
-rw-r--r--gui/assets/icon/ico/black/vpn_blocked.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/black/vpn_off.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/black/vpn_on.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/black/vpn_wait_0.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/black/vpn_wait_1.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/black/vpn_wait_2.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/black/vpn_wait_3.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/white/vpn_blocked.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/white/vpn_off.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/white/vpn_on.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/white/vpn_wait_0.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/white/vpn_wait_1.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/white/vpn_wait_2.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/ico/white/vpn_wait_3.icobin0 -> 5534 bytes
-rw-r--r--gui/assets/icon/png/black/vpn_blocked.pngbin0 -> 811 bytes
-rw-r--r--gui/assets/icon/png/black/vpn_off.pngbin0 -> 959 bytes
-rw-r--r--gui/assets/icon/png/black/vpn_on.pngbin0 -> 964 bytes
-rw-r--r--gui/assets/icon/png/black/vpn_wait_0.pngbin0 -> 1281 bytes
-rw-r--r--gui/assets/icon/png/black/vpn_wait_1.pngbin0 -> 1261 bytes
-rw-r--r--gui/assets/icon/png/black/vpn_wait_2.pngbin0 -> 1241 bytes
-rw-r--r--gui/assets/icon/png/black/vpn_wait_3.pngbin0 -> 1274 bytes
-rw-r--r--gui/assets/icon/png/white/vpn_blocked.pngbin0 -> 833 bytes
-rw-r--r--gui/assets/icon/png/white/vpn_off.pngbin0 -> 950 bytes
-rw-r--r--gui/assets/icon/png/white/vpn_on.pngbin0 -> 963 bytes
-rw-r--r--gui/assets/icon/png/white/vpn_wait_0.pngbin0 -> 1281 bytes
-rw-r--r--gui/assets/icon/png/white/vpn_wait_1.pngbin0 -> 1260 bytes
-rw-r--r--gui/assets/icon/png/white/vpn_wait_2.pngbin0 -> 1237 bytes
-rw-r--r--gui/assets/icon/png/white/vpn_wait_3.pngbin0 -> 1261 bytes
-rw-r--r--gui/gui.qrc21
-rw-r--r--gui/handlers.cpp30
-rw-r--r--gui/handlers.h39
-rw-r--r--gui/main.cpp111
-rw-r--r--gui/qjsonmodel.cpp419
-rw-r--r--gui/qjsonmodel.h111
-rw-r--r--gui/qml/main.qml208
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
new file mode 100644
index 0000000..cbd319a
--- /dev/null
+++ b/gui/assets/icon/ico/black/vpn_blocked.ico
Binary files differ
diff --git a/gui/assets/icon/ico/black/vpn_off.ico b/gui/assets/icon/ico/black/vpn_off.ico
new file mode 100644
index 0000000..5c63c42
--- /dev/null
+++ b/gui/assets/icon/ico/black/vpn_off.ico
Binary files differ
diff --git a/gui/assets/icon/ico/black/vpn_on.ico b/gui/assets/icon/ico/black/vpn_on.ico
new file mode 100644
index 0000000..4df0355
--- /dev/null
+++ b/gui/assets/icon/ico/black/vpn_on.ico
Binary files differ
diff --git a/gui/assets/icon/ico/black/vpn_wait_0.ico b/gui/assets/icon/ico/black/vpn_wait_0.ico
new file mode 100644
index 0000000..159cdf7
--- /dev/null
+++ b/gui/assets/icon/ico/black/vpn_wait_0.ico
Binary files differ
diff --git a/gui/assets/icon/ico/black/vpn_wait_1.ico b/gui/assets/icon/ico/black/vpn_wait_1.ico
new file mode 100644
index 0000000..4fee453
--- /dev/null
+++ b/gui/assets/icon/ico/black/vpn_wait_1.ico
Binary files differ
diff --git a/gui/assets/icon/ico/black/vpn_wait_2.ico b/gui/assets/icon/ico/black/vpn_wait_2.ico
new file mode 100644
index 0000000..4f01e75
--- /dev/null
+++ b/gui/assets/icon/ico/black/vpn_wait_2.ico
Binary files differ
diff --git a/gui/assets/icon/ico/black/vpn_wait_3.ico b/gui/assets/icon/ico/black/vpn_wait_3.ico
new file mode 100644
index 0000000..0f65749
--- /dev/null
+++ b/gui/assets/icon/ico/black/vpn_wait_3.ico
Binary files differ
diff --git a/gui/assets/icon/ico/white/vpn_blocked.ico b/gui/assets/icon/ico/white/vpn_blocked.ico
new file mode 100644
index 0000000..4bc27c5
--- /dev/null
+++ b/gui/assets/icon/ico/white/vpn_blocked.ico
Binary files differ
diff --git a/gui/assets/icon/ico/white/vpn_off.ico b/gui/assets/icon/ico/white/vpn_off.ico
new file mode 100644
index 0000000..66b3459
--- /dev/null
+++ b/gui/assets/icon/ico/white/vpn_off.ico
Binary files differ
diff --git a/gui/assets/icon/ico/white/vpn_on.ico b/gui/assets/icon/ico/white/vpn_on.ico
new file mode 100644
index 0000000..0fdbaad
--- /dev/null
+++ b/gui/assets/icon/ico/white/vpn_on.ico
Binary files differ
diff --git a/gui/assets/icon/ico/white/vpn_wait_0.ico b/gui/assets/icon/ico/white/vpn_wait_0.ico
new file mode 100644
index 0000000..efee63c
--- /dev/null
+++ b/gui/assets/icon/ico/white/vpn_wait_0.ico
Binary files differ
diff --git a/gui/assets/icon/ico/white/vpn_wait_1.ico b/gui/assets/icon/ico/white/vpn_wait_1.ico
new file mode 100644
index 0000000..5f76622
--- /dev/null
+++ b/gui/assets/icon/ico/white/vpn_wait_1.ico
Binary files differ
diff --git a/gui/assets/icon/ico/white/vpn_wait_2.ico b/gui/assets/icon/ico/white/vpn_wait_2.ico
new file mode 100644
index 0000000..2c7f417
--- /dev/null
+++ b/gui/assets/icon/ico/white/vpn_wait_2.ico
Binary files differ
diff --git a/gui/assets/icon/ico/white/vpn_wait_3.ico b/gui/assets/icon/ico/white/vpn_wait_3.ico
new file mode 100644
index 0000000..b7c168b
--- /dev/null
+++ b/gui/assets/icon/ico/white/vpn_wait_3.ico
Binary files differ
diff --git a/gui/assets/icon/png/black/vpn_blocked.png b/gui/assets/icon/png/black/vpn_blocked.png
new file mode 100644
index 0000000..c3ce8b9
--- /dev/null
+++ b/gui/assets/icon/png/black/vpn_blocked.png
Binary files differ
diff --git a/gui/assets/icon/png/black/vpn_off.png b/gui/assets/icon/png/black/vpn_off.png
new file mode 100644
index 0000000..3f69c73
--- /dev/null
+++ b/gui/assets/icon/png/black/vpn_off.png
Binary files differ
diff --git a/gui/assets/icon/png/black/vpn_on.png b/gui/assets/icon/png/black/vpn_on.png
new file mode 100644
index 0000000..4fc2a77
--- /dev/null
+++ b/gui/assets/icon/png/black/vpn_on.png
Binary files differ
diff --git a/gui/assets/icon/png/black/vpn_wait_0.png b/gui/assets/icon/png/black/vpn_wait_0.png
new file mode 100644
index 0000000..0c09358
--- /dev/null
+++ b/gui/assets/icon/png/black/vpn_wait_0.png
Binary files differ
diff --git a/gui/assets/icon/png/black/vpn_wait_1.png b/gui/assets/icon/png/black/vpn_wait_1.png
new file mode 100644
index 0000000..5b45e39
--- /dev/null
+++ b/gui/assets/icon/png/black/vpn_wait_1.png
Binary files differ
diff --git a/gui/assets/icon/png/black/vpn_wait_2.png b/gui/assets/icon/png/black/vpn_wait_2.png
new file mode 100644
index 0000000..6920e88
--- /dev/null
+++ b/gui/assets/icon/png/black/vpn_wait_2.png
Binary files differ
diff --git a/gui/assets/icon/png/black/vpn_wait_3.png b/gui/assets/icon/png/black/vpn_wait_3.png
new file mode 100644
index 0000000..af759cf
--- /dev/null
+++ b/gui/assets/icon/png/black/vpn_wait_3.png
Binary files differ
diff --git a/gui/assets/icon/png/white/vpn_blocked.png b/gui/assets/icon/png/white/vpn_blocked.png
new file mode 100644
index 0000000..bb3ae91
--- /dev/null
+++ b/gui/assets/icon/png/white/vpn_blocked.png
Binary files differ
diff --git a/gui/assets/icon/png/white/vpn_off.png b/gui/assets/icon/png/white/vpn_off.png
new file mode 100644
index 0000000..2fd06ab
--- /dev/null
+++ b/gui/assets/icon/png/white/vpn_off.png
Binary files differ
diff --git a/gui/assets/icon/png/white/vpn_on.png b/gui/assets/icon/png/white/vpn_on.png
new file mode 100644
index 0000000..4e94c64
--- /dev/null
+++ b/gui/assets/icon/png/white/vpn_on.png
Binary files differ
diff --git a/gui/assets/icon/png/white/vpn_wait_0.png b/gui/assets/icon/png/white/vpn_wait_0.png
new file mode 100644
index 0000000..a4745dd
--- /dev/null
+++ b/gui/assets/icon/png/white/vpn_wait_0.png
Binary files differ
diff --git a/gui/assets/icon/png/white/vpn_wait_1.png b/gui/assets/icon/png/white/vpn_wait_1.png
new file mode 100644
index 0000000..cd8ffe1
--- /dev/null
+++ b/gui/assets/icon/png/white/vpn_wait_1.png
Binary files differ
diff --git a/gui/assets/icon/png/white/vpn_wait_2.png b/gui/assets/icon/png/white/vpn_wait_2.png
new file mode 100644
index 0000000..f9d88bb
--- /dev/null
+++ b/gui/assets/icon/png/white/vpn_wait_2.png
Binary files differ
diff --git a/gui/assets/icon/png/white/vpn_wait_3.png b/gui/assets/icon/png/white/vpn_wait_3.png
new file mode 100644
index 0000000..809e8ee
--- /dev/null
+++ b/gui/assets/icon/png/white/vpn_wait_3.png
Binary files differ
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()
+ }
+ }
+ }
+}