diff options
author | kali kaneko (leap communications) <kali@leap.se> | 2020-09-02 23:47:05 +0200 |
---|---|---|
committer | kali kaneko (leap communications) <kali@leap.se> | 2021-05-04 14:58:39 +0200 |
commit | 4bd6bbd788454367cc89d78543312f333051b840 (patch) | |
tree | 6a3d01bf804ea4bd841c013dbe96ffe3df29323a /gui | |
parent | 335bb742b957370bbf40ae77a661559805ab307f (diff) |
[feat] expose gateway selector in gui
Diffstat (limited to 'gui')
-rw-r--r-- | gui/gui.qrc | 3 | ||||
-rw-r--r-- | gui/handlers.cpp | 5 | ||||
-rw-r--r-- | gui/handlers.h | 1 | ||||
-rw-r--r-- | gui/js/maps.js | 83 | ||||
-rw-r--r-- | gui/qml/VpnState.qml | 64 | ||||
-rw-r--r-- | gui/qml/main.qml | 238 |
6 files changed, 289 insertions, 105 deletions
diff --git a/gui/gui.qrc b/gui/gui.qrc index 5e0d4ae..fdea109 100644 --- a/gui/gui.qrc +++ b/gui/gui.qrc @@ -2,6 +2,7 @@ <qresource prefix="/"> <file>qml/main.qml</file> + <file>qml/VpnState.qml</file> <file>qml/AboutDialog.qml</file> <file>qml/DonateDialog.qml</file> <file>qml/LoginDialog.qml</file> @@ -24,5 +25,7 @@ <file alias="providers.json">providers/providers.json</file> <file>assets/svg/world.svg</file> + <file>js/maps.js</file> + </qresource> </RCC> diff --git a/gui/handlers.cpp b/gui/handlers.cpp index 8f0e0d0..370c67c 100644 --- a/gui/handlers.cpp +++ b/gui/handlers.cpp @@ -42,6 +42,11 @@ void Backend::donateSeen() DonateSeen(); } +void Backend::useGateway(QString label) +{ + UseGateway(toGoStr(label)); +} + void Backend::login(QString username, QString password) { Login(toGoStr(username), toGoStr(password)); diff --git a/gui/handlers.h b/gui/handlers.h index 8283645..a783207 100644 --- a/gui/handlers.h +++ b/gui/handlers.h @@ -36,6 +36,7 @@ public slots: void switchOff(); void donateAccepted(); void donateSeen(); + void useGateway(QString username); void login(QString username, QString password); void resetError(QString errlabel); void resetNotification(QString label); diff --git a/gui/js/maps.js b/gui/js/maps.js new file mode 100644 index 0000000..c6f44d2 --- /dev/null +++ b/gui/js/maps.js @@ -0,0 +1,83 @@ +// Robinson projection calculation + +// Written by Niklas Bichinger (bichinger.de). This code is Public Domain - use as you like. + +// source of robinson numbers: https://simplemaps.com/static/img/flash/robinson_projection_table.jpg +var robinsonAA = [ + 0.84870000, + 0.84751182, + 0.84479598, + 0.84021300, + 0.83359314, + 0.82578510, + 0.81475200, + 0.80006949, + 0.78216192, + 0.76060494, + 0.73658673, + 0.70866450, + 0.67777182, + 0.64475739, + 0.60987582, + 0.57134484, + 0.52729731, + 0.48562614, + 0.45167814 +]; +var robinsonBB = [ + 0.00000000, + 0.08384260, + 0.16768520, + 0.25152780, + 0.33537040, + 0.41921300, + 0.50305560, + 0.58689820, + 0.67047034, + 0.75336633, + 0.83518048, + 0.91537187, + 0.99339958, + 1.06872269, + 1.14066505, + 1.20841528, + 1.27035062, + 1.31998003, + 1.35230000 +]; + +function project(latitude, longitude, mapWidth, heightFactor, mapOffsetX, mapOffsetY) { + if (typeof heightFactor === 'undefined') { heightFactor = 1; } + if (typeof mapOffsetX === 'undefined') { mapOffsetX = 0; } + if (typeof mapOffsetY === 'undefined') { mapOffsetY = 0; } + + // Robinson's latitude interpolation points are in 5-degree-steps + var latitudeAbs = Math.abs(latitude); + var latitudeStepFloor = Math.floor(latitudeAbs / 5); + var latitudeStepCeil = Math.ceil(latitudeAbs / 5); + // calc interpolation factor (>=0 to <1) between two steps + var latitudeInterpolation = (latitudeAbs - latitudeStepFloor * 5) / 5; + + // interpolate robinson table values + var AA = robinsonAA[latitudeStepFloor] + (robinsonAA[latitudeStepCeil] - robinsonAA[latitudeStepFloor]) * latitudeInterpolation; + var BB = robinsonBB[latitudeStepFloor] + (robinsonBB[latitudeStepCeil] - robinsonBB[latitudeStepFloor]) * latitudeInterpolation; + + var robinsonWidth = 2 * Math.PI * robinsonAA[0]; + var widthFactor = mapWidth / robinsonWidth; + var latitudeSign = Math.sign(latitude) || 1; + var x = (widthFactor * AA * longitude * Math.PI) / 180 + mapOffsetX; + var y = widthFactor * BB * latitudeSign * heightFactor + mapOffsetY; + + return {x: x, y: y}; +} + +function projectAbsolute(latitude, longitude, mapWidth, heightFactor, mapOffsetX, mapOffsetY) { + if (typeof heightFactor === 'undefined') { heightFactor = 1; } + + var relative = project(latitude, longitude, mapWidth, heightFactor, mapOffsetX, mapOffsetY); + var widthHeightRatio = Math.PI * robinsonAA[0] / robinsonBB[18]; + var x = mapWidth / 2 + relative.x; + var y = mapWidth / widthHeightRatio * heightFactor / 2 - relative.y; + + return {x: x, y: y}; +} diff --git a/gui/qml/VpnState.qml b/gui/qml/VpnState.qml new file mode 100644 index 0000000..ea2a3b1 --- /dev/null +++ b/gui/qml/VpnState.qml @@ -0,0 +1,64 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.4 + +StateGroup { + + 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") } + PropertyChanges { target: mainStatus; text: toHuman("off") } + PropertyChanges { target: mainCurrentGateway; text: "" } + PropertyChanges { target: mainOnBtn; visible: true } + PropertyChanges { target: mainOffBtn; visible: false } + PropertyChanges { target: gwMarker; color: "red"} + }, + State { + name: "on" + StateChangeScript { + script: displayGatewayMarker() + } + PropertyChanges { target: systray; tooltip: toHuman("on"); icon.source: icons["on"] } + PropertyChanges { target: statusItem; text: toHumanWithLocation("on") } + PropertyChanges { target: mainStatus; text: toHuman("on") } + PropertyChanges { target: mainCurrentGateway; text: qsTr("Connected to ") + ctx.currentGateway } + PropertyChanges { target: mainOnBtn; visible: false } + PropertyChanges { target: mainOffBtn; visible: true } + PropertyChanges { target: gwMarker; color: "green"} + }, + State { + name: "starting" + PropertyChanges { target: systray; tooltip: toHuman("connecting"); icon.source: icons["wait"] } + PropertyChanges { target: statusItem; text: toHumanWithLocation("connecting") } + PropertyChanges { target: mainStatus; text: qsTr("Connecting...") } + PropertyChanges { target: mainCurrentGateway; text: "" } + PropertyChanges { target: mainOnBtn; visible: false } + PropertyChanges { target: mainOffBtn; visible: true } + PropertyChanges { target: gwMarker; color: "orange"} + }, + State { + name: "stopping" + PropertyChanges { target: systray; tooltip: toHuman("stopping"); icon.source: icons["wait"] } + PropertyChanges { target: statusItem; text: toHuman("stopping") } + PropertyChanges { target: mainStatus; text: toHuman("stopping") } + PropertyChanges { target: mainCurrentGateway; text: "" } + PropertyChanges { target: mainOnBtn; visible: true } + PropertyChanges { target: mainOffBtn; visible: false } + PropertyChanges { target: gwMarker; color: "orange"} + }, + State { + name: "failed" + PropertyChanges { target: systray; tooltip: toHuman("failed"); icon.source: icons["wait"] } + PropertyChanges { target: statusItem; text: toHuman("failed") } + PropertyChanges { target: mainStatus; text: toHuman("failed") } + PropertyChanges { target: mainCurrentGateway; text: "" } + PropertyChanges { target: mainOnBtn; visible: true } + PropertyChanges { target: mainOffBtn; visible: false } + PropertyChanges { target: gwMarker; color: "red"} + } + ] +} diff --git a/gui/qml/main.qml b/gui/qml/main.qml index 98a4445..bbc3f69 100644 --- a/gui/qml/main.qml +++ b/gui/qml/main.qml @@ -6,12 +6,19 @@ import QtQuick.Extras 1.2 import Qt.labs.platform 1.1 as LabsPlatform +import "qrc:/js/maps.js" as Maps + ApplicationWindow { id: app visible: true - width: 700 - height: 700 + width: 300 + height: 600 + maximumWidth: 300 + minimumWidth: 300 + maximumHeight: 600 + minimumHeight: 600 + // TODO get a nice background color flags: Qt.WindowsStaysOnTopHint | Qt.Popup @@ -19,52 +26,121 @@ ApplicationWindow { property var loginDone property var allowEmptyPass - ColumnLayout{ - anchors.centerIn: parent - width: parent.width - Layout.preferredHeight: parent.height + onWidthChanged: displayGatewayMarker() + onHeightChanged: displayGatewayMarker() + + GridLayout { + visible: true + columns: 3 - Text{ - id: mainStatus - text: "Status: off" - font.pixelSize: 22 - Layout.preferredWidth: parent.width - horizontalAlignment: Text.AlignHCenter - } + Item { + Layout.column: 2 + Layout.topMargin: app.height * 0.15 + Layout.leftMargin: app.width * 0.10 - Label { - text: "gateway selection:" - font.pixelSize: 20 - } + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + + Text{ + id: mainStatus + text: "off" + font.pixelSize: 26 + Layout.alignment: Text.AlignHCenter + } + + Text{ + id: mainCurrentGateway + text: "" + font.pixelSize: 20 + Layout.alignment: Text.AlignHCenter + } - ComboBox { - id: comboGw - editable: false - model: [ qsTr("Automatic"), qsTr("Paris"), qsTr("Amsterdam") ] - onAccepted: { - if (combo.find(currentText) === -1) { - currentIndex = combo.find(editText) - } - } + Button { + id: mainOnBtn + x: 80 + y: 200 + text: qsTr("on") + visible: true + onClicked: backend.switchOn() + } + + Button { + id: mainOffBtn + x: 180 + y: 200 + text: qsTr("off") + visible: false + onClicked: backend.switchOff() + } + + ComboBox { + id: gwSelector + editable: false + model: [qsTr("Automatic")] + onActivated: { + console.debug("Selected gateway:", currentText); + backend.useGateway(currentText.toString()); + } + } + } } - ColumnLayout{ - width: parent.width + Item { + Layout.topMargin: app.height * 0.40 + Layout.row: 3 + Layout.column: 1 + Layout.columnSpan: 3 Image { id: worldMap + width: app.width source: "qrc:/assets/svg/world.svg" - fillMode: Image.PreserveAspectCrop + fillMode: Image.PreserveAspectFit + smooth: true } - } + + Rectangle { + id: gwMarker + x: worldMap.width * 0.5 + y: worldMap.height * 0.5 + width: 10 + height: 10 + radius: 10 + color: "red" + z: worldMap.z + 1 + } + + } + } + + + function displayGatewayMarker() { + let coords = { + 'paris': {'x': 48, 'y': 2}, + 'miami': {'x': 25.7 , 'y': -80.2 }, + 'amsterdam': {'x': 52.4, 'y': 4.9 }, + 'montreal': {'x': 45.3, 'y': -73.4 }, + 'seattle': {'x': 47.4, 'y': -122.2 }, + } + let city = ctx.currentGateway.split('-')[0] + let coord = coords[city] + + // TODO the Robinson projection does not seem to fit super-nicely with + // our map, and this offset doesn't work with bigg-ish sizes. But good + // enough for a proof of concept - if we avoid resizing the window. + let xOffset = -1 * 0.10 * worldMap.width + let p = Maps.projectAbsolute(coord.x, coord.y, worldMap.width, 1, xOffset) + gwMarker.x = p.x + gwMarker.y = p.y } Connections { target: jsonModel onDataChanged: { - ctx = JSON.parse(jsonModel.getJson()) + ctx = JSON.parse(jsonModel.getJson()); + gwSelector.model = Object.keys(ctx.gateways) if (ctx.donateDialog == 'true') { console.debug(jsonModel.getJson()) @@ -170,6 +246,28 @@ ApplicationWindow { } } + function toHumanWithLocation(st) { + switch(st) { + case "off": + //: %1 -> application name + return qsTr("%1 off").arg(ctx.appName); + case "on": + //: %1 -> application name + //: %2 -> current gateway + return qsTr("%1 on - %2").arg(ctx.appName).arg(ctx.currentGateway); + case "connecting": + //: %1 -> application name + //: %2 -> current gateway + return qsTr("Connecting to %1 - %2").arg(ctx.appName).arg(ctx.currentGateway); + case "stopping": + //: %1 -> application name + return qsTr("Stopping %1").arg(ctx.appName); + case "failed": + //: %1 -> application name + return qsTr("%1 blocking internet").arg(ctx.appName); // 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", @@ -177,8 +275,11 @@ ApplicationWindow { "blocked": "qrc:/assets/icon/png/black/vpn_blocked.png" } + VpnState { + id: vpn + } - + SystemTrayIcon { LabsPlatform.SystemTrayIcon { id: systray @@ -203,76 +304,6 @@ ApplicationWindow { } } - 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["blocked"] - } - PropertyChanges { - target: statusItem - text: toHuman("failed") - } - } - ] - } LabsPlatform.MenuItem { id: statusItem @@ -385,8 +416,6 @@ ApplicationWindow { console.log("System doesn't support systray notifications") } } - - } DonateDialog { @@ -432,5 +461,4 @@ ApplicationWindow { id: initFailure visible: false } - } |