summaryrefslogtreecommitdiff
path: root/gui
diff options
context:
space:
mode:
authorkali kaneko (leap communications) <kali@leap.se>2021-08-27 19:45:41 +0200
committerkali kaneko (leap communications) <kali@leap.se>2021-11-23 21:50:58 +0100
commitcd1d46a26b923260b6c87cc93a0723b8166c609e (patch)
tree81d0fafeaeda6f5d72ec03f589cf8560cea32ed8 /gui
parent76585f828a36c08614df84f43d64e20fb0e7a425 (diff)
[ui] refactor ui
Diffstat (limited to 'gui')
-rw-r--r--gui/backend.go2
-rw-r--r--gui/components/BoldLabel.qml21
-rw-r--r--gui/components/Footer.qml63
-rw-r--r--gui/components/Header.qml32
-rw-r--r--gui/components/Help.qml32
-rw-r--r--gui/components/Home.qml8
-rw-r--r--gui/components/Icon.qml9
-rw-r--r--gui/components/LightLabel.qml19
-rw-r--r--gui/components/Locations.qml63
-rw-r--r--gui/components/MainView.qml131
-rw-r--r--gui/components/MaterialButton.qml84
-rw-r--r--gui/components/Preferences.qml23
-rw-r--r--gui/components/Spinner.qml63
-rw-r--r--gui/components/Splash.qml64
-rw-r--r--gui/components/StatusBox.qml125
-rw-r--r--gui/components/Style.qml12
-rw-r--r--gui/components/Systray.qml27
-rw-r--r--gui/components/VPNButtonBase.qml56
-rw-r--r--gui/components/VPNMouseArea.qml37
-rw-r--r--gui/components/VPNState.qml193
-rw-r--r--gui/components/VPNToggle.qml338
-rw-r--r--gui/components/VerticalSpacer.qml6
-rw-r--r--gui/gui.qrc46
-rw-r--r--gui/main.cpp21
-rw-r--r--gui/main.qml116
-rw-r--r--gui/providers/providers.json2
-rw-r--r--gui/qml/BackgroundImage.qml16
-rw-r--r--gui/qml/BridgesItem.qml16
-rw-r--r--gui/qml/VPNSwitch.qml1
-rw-r--r--gui/qml/VpnState.qml13
-rw-r--r--gui/qml/main.qml65
-rw-r--r--gui/qml/readme.txt1
-rw-r--r--gui/resources/about.svg62
-rw-r--r--gui/resources/arrow-left.svg3
-rw-r--r--gui/resources/birds.svg1
-rw-r--r--gui/resources/bridge.pngbin0 -> 5543 bytes
-rw-r--r--gui/resources/close.svg3
-rw-r--r--gui/resources/donate.svg4
-rw-r--r--gui/resources/gear-fill.svg3
-rw-r--r--gui/resources/globe.svg3
-rw-r--r--gui/resources/help.svg4
-rw-r--r--gui/resources/icon-noshield.svg68
-rw-r--r--gui/resources/location.svg58
-rw-r--r--gui/resources/power.svg10
-rw-r--r--gui/resources/quit.svg4
-rw-r--r--gui/resources/reception-0.svg3
-rw-r--r--gui/resources/reception-2.svg3
-rw-r--r--gui/resources/reception-4.svg3
-rw-r--r--gui/resources/riseup-icon.svg80
-rw-r--r--gui/resources/riseup-logo.pngbin0 -> 4943 bytes
-rw-r--r--gui/resources/settings.svg62
-rw-r--r--gui/resources/speed-green.svg72
-rw-r--r--gui/resources/speed-red.svg72
-rw-r--r--gui/resources/speed-yellow.svg72
-rw-r--r--gui/resources/spy.gifbin0 -> 2662 bytes
-rw-r--r--gui/resources/tools.svg4
-rw-r--r--gui/themes/themes.js51
57 files changed, 2282 insertions, 68 deletions
diff --git a/gui/backend.go b/gui/backend.go
index 04d1293..bc056b9 100644
--- a/gui/backend.go
+++ b/gui/backend.go
@@ -83,7 +83,7 @@ func InitializeBitmaskContext(provider string,
opts.Obfs4 = obfs4
opts.DisableAutostart = disableAutostart
opts.StartVPN = startVPN
- backend.InitializeBitmaskContext(opts)
+ go backend.InitializeBitmaskContext(opts)
}
//export InitializeTestBitmaskContext
diff --git a/gui/components/BoldLabel.qml b/gui/components/BoldLabel.qml
new file mode 100644
index 0000000..6c6c3c3
--- /dev/null
+++ b/gui/components/BoldLabel.qml
@@ -0,0 +1,21 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.14
+import "../themes/themes.js" as Theme
+import "../themes"
+
+Label {
+ FontLoader {
+ id: boldFont
+ source: "qrc:/oxanium-bold.ttf"
+ }
+
+ font.pixelSize: Theme.fontSize * 1.55555
+ //font.family: boldFont.name
+ font.bold: true
+ //color: Theme.fontColorDark
+ color: "black"
+ text: parent.text
+ //wrapMode: Text.WordWrap
+ Accessible.name: text
+ Accessible.role: Accessible.StaticText
+}
diff --git a/gui/components/Footer.qml b/gui/components/Footer.qml
new file mode 100644
index 0000000..2c7c875
--- /dev/null
+++ b/gui/components/Footer.qml
@@ -0,0 +1,63 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.4
+import QtQuick.Controls.Material 2.1
+import QtQuick.Layouts 1.14
+
+ToolBar {
+
+ Material.background: Material.backgroundColor
+ Material.foreground: "black"
+ Material.elevation: 0
+ visible: stackView.depth > 1 && ctx !== undefined ? false : true
+
+ Item {
+
+ id: footerRow
+ width: root.width
+
+ ToolButton {
+ id: gwButton
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 10
+ anchors.left: parent.left
+ anchors.verticalCenterOffset: 5
+ icon.source: stackView.depth > 1 ? "" : "../resources/globe.svg"
+ onClicked: stackView.push("Locations.qml")
+ }
+
+ Label {
+ id: locationLabel
+ anchors.left: gwButton.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.verticalCenterOffset: 5
+ text: "Seattle"
+ }
+
+ Item {
+ Layout.fillWidth: true
+ height: gwButton.implicitHeight
+ }
+
+ Image {
+ id: bridge
+ height: 24
+ width: 24
+ source: "../resources/bridge.png"
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.verticalCenterOffset: 5
+ anchors.right: gwQuality.left
+ anchors.rightMargin: 10
+ }
+
+ Image {
+ id: gwQuality
+ height: 24
+ width: 24
+ source: "../resources/reception-0.svg"
+ anchors.right: parent.right
+ anchors.rightMargin: 20
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.verticalCenterOffset: 5
+ }
+ }
+}
diff --git a/gui/components/Header.qml b/gui/components/Header.qml
new file mode 100644
index 0000000..92f4bdd
--- /dev/null
+++ b/gui/components/Header.qml
@@ -0,0 +1,32 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.4
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls.Material 2.1
+
+ToolBar {
+ visible: stackView.depth > 1
+ Material.foreground: Material.Black
+ Material.background: "#ffffff"
+ Material.elevation: 0
+
+ contentHeight: settingsButton.implicitHeight
+
+ ToolButton {
+ id: settingsButton
+ anchors.left: parent.left
+ font.pixelSize: Qt.application.font.pixelSize * 1.6
+ icon.source: "../resources/arrow-left.svg"
+ onClicked: {
+ if (stackView.depth > 1) {
+ stackView.pop()
+ } else {
+ settingsDrawer.open()
+ }
+ }
+ }
+
+ Label {
+ text: stackView.currentItem.title
+ anchors.centerIn: parent
+ }
+}
diff --git a/gui/components/Help.qml b/gui/components/Help.qml
new file mode 100644
index 0000000..aced273
--- /dev/null
+++ b/gui/components/Help.qml
@@ -0,0 +1,32 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+
+Page {
+ title: qsTr("Help")
+
+ Column {
+ anchors.centerIn: parent
+ spacing: 10
+
+ Button {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: qsTr("Donate")
+ onClicked: stackView.push("Donate.qml")
+ }
+ Button {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: qsTr("Terms of Service")
+ onClicked: stackView.push("Donate.qml")
+ }
+ Button {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: qsTr("Contact Support")
+ onClicked: stackView.push("Donate.qml")
+ }
+ Button {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: qsTr("Report bug")
+ onClicked: stackView.push("Donate.qml")
+ }
+ }
+}
diff --git a/gui/components/Home.qml b/gui/components/Home.qml
new file mode 100644
index 0000000..c9eab2a
--- /dev/null
+++ b/gui/components/Home.qml
@@ -0,0 +1,8 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+
+Page {
+
+ StatusBox {}
+}
diff --git a/gui/components/Icon.qml b/gui/components/Icon.qml
new file mode 100644
index 0000000..5dfd6ee
--- /dev/null
+++ b/gui/components/Icon.qml
@@ -0,0 +1,9 @@
+import QtQuick 2.5
+import QtQuick.Layouts 1.14
+import "../themes/themes.js" as Theme
+
+Image {
+ sourceSize.height: 24
+ sourceSize.width: 24
+ fillMode: Image.PreserveAspectFit
+}
diff --git a/gui/components/LightLabel.qml b/gui/components/LightLabel.qml
new file mode 100644
index 0000000..78f82b6
--- /dev/null
+++ b/gui/components/LightLabel.qml
@@ -0,0 +1,19 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.14
+import "../themes/themes.js" as Theme
+
+Text {
+ font.pixelSize: Theme.fontSize - 2
+ font.family: Theme.fontFamily
+ color: Theme.fontColor
+ width: parent.width * 0.80
+ text: parent.text
+
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ //lineHeightMode: Text.FixedHeight
+ wrapMode: Text.Wrap
+
+ Accessible.role: Accessible.StaticText
+ Accessible.name: text
+}
diff --git a/gui/components/Locations.qml b/gui/components/Locations.qml
new file mode 100644
index 0000000..d3e0f5a
--- /dev/null
+++ b/gui/components/Locations.qml
@@ -0,0 +1,63 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+
+import "../themes/themes.js" as Theme
+
+Page {
+ title: qsTr("Select Location")
+
+ ListView {
+ id: gwList
+ focus: true
+ currentIndex: -1
+ anchors.fill: parent
+ spacing: 1
+
+ delegate: ItemDelegate {
+ id: loc
+ Rectangle {
+ width: parent.width
+ height: 1
+ color: Theme.borderColor
+ }
+ width: parent.width
+ text: model.text
+ highlighted: ListView.isCurrentItem
+ icon.color: "transparent"
+ icon.source: model.icon
+ onClicked: {
+ model.triggered()
+ stackView.pop()
+ }
+ MouseArea {
+ property var onMouseAreaClicked: function () {
+ parent.clicked()
+ }
+ id: mouseArea
+ anchors.fill: loc
+ cursorShape: Qt.PointingHandCursor
+ onReleased: {
+ onMouseAreaClicked()
+ }
+ }
+ }
+
+ model: ListModel {
+ ListElement {
+ text: qsTr("Paris")
+ triggered: function () {}
+ icon: "../resources/reception-4.svg"
+ }
+ ListElement {
+ text: qsTr("Montreal")
+ triggered: function () {}
+ icon: "../resources/reception-4.svg"
+ }
+ ListElement {
+ text: qsTr("Seattle")
+ triggered: function () {}
+ icon: "../resources/reception-2.svg"
+ }
+ }
+ }
+}
diff --git a/gui/components/MainView.qml b/gui/components/MainView.qml
new file mode 100644
index 0000000..7ce723d
--- /dev/null
+++ b/gui/components/MainView.qml
@@ -0,0 +1,131 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.4
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls.Material 2.1
+import QtQuick.Layouts 1.14
+
+Page {
+
+ StackView {
+ id: stackView
+ anchors.fill: parent
+ initialItem: Home {}
+ }
+
+ Drawer {
+ id: settingsDrawer
+
+ width: Math.min(root.width, root.height) / 3 * 2
+ height: root.height
+
+ ListView {
+ focus: true
+ currentIndex: -1
+ anchors.fill: parent
+
+ delegate: ItemDelegate {
+ width: parent.width
+ text: model.text
+ highlighted: ListView.isCurrentItem
+ icon.color: "transparent"
+ icon.source: model.icon
+ onClicked: {
+ settingsDrawer.close()
+ model.triggered()
+ }
+ }
+
+ model: ListModel {
+ ListElement {
+ text: qsTr("Preferences")
+ icon: "../resources/tools.svg"
+ triggered: function () {
+ stackView.push("Preferences.qml")
+ }
+ }
+ ListElement {
+ text: qsTr("Donate")
+ icon: "../resources/donate.svg"
+ triggered: function () {
+ aboutDialog.open()
+ }
+ }
+ ListElement {
+ text: qsTr("Help")
+ icon: "../resources/help.svg"
+ triggered: function () {
+ stackView.push("Help.qml")
+ settingsDrawer.close()
+ }
+ } // -> can link to another dialog with report bug / support / contribute / FAQ
+ ListElement {
+ text: qsTr("About")
+ icon: "../resources/about.svg"
+ triggered: function () {
+ aboutDialog.open()
+ }
+ }
+ ListElement {
+ text: qsTr("Quit")
+ icon: "../resources/quit.svg"
+ triggered: function () {
+ Qt.callLater(backend.quit)
+ }
+ }
+ }
+
+ ScrollIndicator.vertical: ScrollIndicator {}
+ }
+ }
+
+ Drawer {
+ id: locationsDrawer
+
+ width: root.width
+ height: root.height
+
+ ListView {
+ focus: true
+ currentIndex: -1
+ anchors.fill: parent
+
+ delegate: ItemDelegate {
+ width: parent.width
+ text: model.text
+ highlighted: ListView.isCurrentItem
+ onClicked: {
+ locationsDrawer.close()
+ model.triggered()
+ }
+ }
+
+ model: ListModel {
+ ListElement {
+ text: qsTr("Montreal, CA")
+ triggered: function () {}
+ }
+ ListElement {
+ text: qsTr("Paris, FR")
+ triggered: function () {}
+ }
+ }
+
+ ScrollIndicator.vertical: ScrollIndicator {}
+ }
+ }
+
+ header: Header {}
+ footer: Footer {}
+
+ Dialog {
+ id: aboutDialog
+ title: qsTr("About")
+ Label {
+ anchors.fill: parent
+ text: qsTr("RiseupVPN\nhttps://riseupvpn.net/vpn")
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ standardButtons: StandardButton.Ok
+ }
+}
diff --git a/gui/components/MaterialButton.qml b/gui/components/MaterialButton.qml
new file mode 100644
index 0000000..25911f3
--- /dev/null
+++ b/gui/components/MaterialButton.qml
@@ -0,0 +1,84 @@
+import QtQuick 2.12
+import QtQuick.Templates 2.12 as T
+import QtQuick.Controls 2.12
+import QtQuick.Controls.impl 2.12
+import QtQuick.Controls.Material 2.12
+import QtQuick.Controls.Material.impl 2.12
+
+T.Button {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ topInset: 6
+ bottomInset: 6
+ padding: 12
+ horizontalPadding: padding - 4
+ spacing: 6
+
+ icon.width: 24
+ icon.height: 24
+ icon.color: !enabled ? Material.hintTextColor : flat
+ && highlighted ? Material.accentColor : highlighted ? Material.primaryHighlightedTextColor : Material.foreground
+
+ Material.elevation: flat ? control.down
+ || control.hovered ? 2 : 0 : control.down ? 8 : 2
+ Material.background: flat ? "transparent" : undefined
+
+ contentItem: IconLabel {
+ spacing: control.spacing
+ mirrored: control.mirrored
+ display: control.display
+
+ icon: control.icon
+ text: control.text
+ font: control.font
+
+ color: !control.enabled ? control.Material.hintTextColor : control.flat
+ && control.highlighted ? control.Material.accentColor : control.highlighted ? control.Material.primaryHighlightedTextColor : control.Material.foreground
+ }
+
+ background: Rectangle {
+ implicitWidth: 64
+ implicitHeight: control.Material.buttonHeight
+
+ radius: 4
+ border.color: "black"
+ border.width: 1
+ color: !control.enabled ? control.Material.buttonDisabledColor : control.highlighted ? control.Material.highlightedButtonColor : control.Material.buttonColor
+
+ PaddedRectangle {
+ y: parent.height - 4
+ width: parent.width
+ height: 4
+ radius: 2
+ topPadding: -2
+ clip: true
+ visible: control.checkable && (!control.highlighted || control.flat)
+ color: control.checked
+ && control.enabled ? control.Material.accentColor : control.Material.secondaryTextColor
+ }
+
+ // The layer is disabled when the button color is transparent so you can do
+ // Material.background: "transparent" and get a proper flat button without needing
+ // to set Material.elevation as well
+ layer.enabled: control.enabled && control.Material.buttonColor.a > 0
+ layer.effect: ElevationEffect {
+ elevation: control.Material.elevation
+ }
+
+ Ripple {
+ clipRadius: 2
+ width: parent.width
+ height: parent.height
+ pressed: control.pressed
+ anchor: control
+ active: control.down || control.visualFocus || control.hovered
+ color: control.flat
+ && control.highlighted ? control.Material.highlightedRippleColor : control.Material.rippleColor
+ }
+ }
+}
diff --git a/gui/components/Preferences.qml b/gui/components/Preferences.qml
new file mode 100644
index 0000000..e5c4828
--- /dev/null
+++ b/gui/components/Preferences.qml
@@ -0,0 +1,23 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+
+Page {
+ title: qsTr("Preferences")
+
+ Column {
+ spacing: 2
+ topPadding: root.width * 0.2
+ leftPadding: root.width * 0.15
+ rightPadding: root.width * 0.15
+
+ Label {
+ text: qsTr("Anti-censorship")
+ font.bold: true
+ }
+
+ CheckBox {
+ checked: false
+ text: qsTr("Use Bridges")
+ }
+ }
+}
diff --git a/gui/components/Spinner.qml b/gui/components/Spinner.qml
new file mode 100644
index 0000000..9fc535f
--- /dev/null
+++ b/gui/components/Spinner.qml
@@ -0,0 +1,63 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+
+BusyIndicator {
+
+ id: control
+
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ contentItem: Item {
+ implicitWidth: 64
+ implicitHeight: 64
+
+ Item {
+ id: item
+ x: parent.width / 2 - 32
+ y: parent.height / 2 - 32
+ width: 64
+ height: 64
+ opacity: control.running ? 1 : 0
+
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 250
+ }
+ }
+
+ RotationAnimator {
+ target: item
+ running: control.visible && control.running
+ from: 0
+ to: 360
+ loops: Animation.Infinite
+ duration: 6200
+ }
+
+ Repeater {
+ id: repeater
+ model: 6
+
+ Rectangle {
+ x: item.width / 2 - width / 2
+ y: item.height / 2 - height / 2
+ implicitWidth: 10
+ implicitHeight: 10
+ radius: 5
+ color: "#21be2b"
+ transform: [
+ Translate {
+ y: -Math.min(item.width, item.height) * 0.5 + 5
+ },
+ Rotation {
+ angle: index / repeater.count * 360
+ origin.x: 5
+ origin.y: 5
+ }
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/gui/components/Splash.qml b/gui/components/Splash.qml
new file mode 100644
index 0000000..b494494
--- /dev/null
+++ b/gui/components/Splash.qml
@@ -0,0 +1,64 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+
+Page {
+ id: splash
+ property int timeoutInterval: 1600
+
+ Column {
+ width: parent.width * 0.8
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.topMargin: 24
+
+ VerticalSpacer {
+ visible: true
+ height: root.height * 0.10
+ }
+
+ Image {
+ id: connectionImage
+ height: 200
+ anchors.horizontalCenter: parent.horizontalCenter
+ source: "../resources/icon-noshield.svg"
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Spinner {}
+ }
+
+ Timer {
+ id: splashTimer
+ }
+
+ function delay(delayTime, cb) {
+ splashTimer.interval = delayTime
+ splashTimer.repeat = false
+ splashTimer.triggered.connect(cb)
+ splashTimer.start()
+ }
+
+ function loadMainViewWhenReady() {
+ console.debug("ready?")
+ if (ctx && ctx.isReady) {
+ console.debug("ready?", ctx.isReady)
+ // FIXME check errors == None
+ loader.source = "MainView.qml"
+ } else {
+ delay(100, loadMainViewWhenReady)
+ }
+ }
+
+ Timer {
+ interval: timeoutInterval
+ running: true
+ repeat: false
+ onTriggered: {
+ loadMainViewWhenReady()
+ }
+ }
+
+ Component.onCompleted: {
+
+ }
+}
diff --git a/gui/components/StatusBox.qml b/gui/components/StatusBox.qml
new file mode 100644
index 0000000..fa24cd8
--- /dev/null
+++ b/gui/components/StatusBox.qml
@@ -0,0 +1,125 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtGraphicalEffects 1.14
+import QtQuick.Layouts 1.14
+
+import QtQuick.Templates 2.12 as T
+import QtQuick.Controls.impl 2.12
+import QtQuick.Controls.Material 2.12
+import QtQuick.Controls.Material.impl 2.12
+import "../themes/themes.js" as Theme
+
+Item {
+ id: statusbox
+ anchors.fill: parent
+
+ VPNState {
+ id: vpn
+ }
+
+ Rectangle {
+ id: statusBoxBackground
+ anchors.fill: parent
+ anchors.margins: 20
+ anchors.bottomMargin: 30
+ height: 300
+ radius: 10
+ color: Theme.bgColor
+ border.color: Theme.accentOff
+ border.width: 2
+ antialiasing: true
+ }
+
+ ToolButton {
+ id: settingsButton
+ objectName: "settingsButton"
+ opacity: 1
+
+ font.pixelSize: Qt.application.font.pixelSize * 1.6
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.topMargin: Theme.windowMargin + 10
+ anchors.leftMargin: Theme.windowMargin + 10
+
+ onClicked: {
+ if (stackView.depth > 1) {
+ stackView.pop()
+ } else {
+ settingsDrawer.open()
+ }
+ }
+
+ Icon {
+ id: settingsImage
+ width: 24
+ height: 24
+ // TODO move arrow left to toolbar top
+ source: stackView.depth
+ > 1 ? "../resources/arrow-left.svg" : "../resources/gear-fill.svg"
+ anchors.centerIn: settingsButton
+ }
+ }
+
+ Column {
+ id: col
+ anchors.centerIn: parent
+ anchors.topMargin: 24
+ width: parent.width * 0.8
+
+ BoldLabel {
+ id: connectionState
+ text: ""
+ anchors.horizontalCenter: parent.horizontalCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ VerticalSpacer {
+ id: spacerPreImg
+ visible: false
+ height: 40
+ }
+
+ Image {
+ id: connectionImage
+ height: 200
+ source: "../resources/spy.gif"
+ fillMode: Image.PreserveAspectFit
+ }
+
+ VerticalSpacer {
+ id: spacerPostImg
+ visible: false
+ height: 35
+ }
+
+ MaterialButton {
+ id: toggleVPN
+ anchors.horizontalCenter: parent.horizontalCenter
+ Layout.alignment: Qt.AlignBottom
+ font.capitalization: Font.Capitalize
+ spacing: 8
+
+ onClicked: {
+ if (vpn.state === "on") {
+ console.debug("should turn off")
+ backend.switchOff()
+ } else if (vpn.state === "off") {
+ console.debug("should turn on")
+ backend.switchOn()
+ } else {
+ console.debug("unknown state")
+ }
+ }
+
+
+ /*
+ XXX this hijacks click events, so better no pointing for now.
+ MouseArea {
+ anchors.fill: toggleVPN
+ hoverEnabled: true
+ cursorShape: !hoverEnabled ? Qt.ForbiddenCursor : Qt.PointingHandCursor
+ }
+ */
+ }
+ }
+}
diff --git a/gui/components/Style.qml b/gui/components/Style.qml
new file mode 100644
index 0000000..acd6036
--- /dev/null
+++ b/gui/components/Style.qml
@@ -0,0 +1,12 @@
+import "themes.js" as Theme
+
+Item {
+ property alias fontFoo: fontFooLoader.name
+ readonly property color colourBlackground: "#efefef"
+
+ // TODO use theme.background
+ FontLoader {
+ id: fontFooLoader
+ source: "qrc:/resources/fonts/Oxanium-Bold.ttf"
+ }
+}
diff --git a/gui/components/Systray.qml b/gui/components/Systray.qml
new file mode 100644
index 0000000..6fb1046
--- /dev/null
+++ b/gui/components/Systray.qml
@@ -0,0 +1,27 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.4
+import Qt.labs.platform 1.0 as Labs
+
+Labs.SystemTrayIcon {
+
+ visible: systrayVisible
+ property alias statusItem: statusItem
+
+ menu: Labs.Menu {
+
+ id: systrayMenu
+
+ Labs.MenuItem {
+ id: statusItem
+ text: qsTr("Checking status…")
+ enabled: false
+ }
+
+ Labs.MenuSeparator {}
+
+ Labs.MenuItem {
+ text: qsTr("Quit")
+ onTriggered: backend.quit()
+ }
+ }
+}
diff --git a/gui/components/VPNButtonBase.qml b/gui/components/VPNButtonBase.qml
new file mode 100644
index 0000000..6f67710
--- /dev/null
+++ b/gui/components/VPNButtonBase.qml
@@ -0,0 +1,56 @@
+
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+import QtQuick 2.0
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.14
+import "../themes/themes.js" as Theme
+
+RoundButton {
+ id: root
+
+ property var visualStateItem: root
+ property var uiState: Theme.uiState
+ property var loaderVisible: false
+ property var handleKeyClick: function () {
+ clicked()
+ }
+
+ focusPolicy: Qt.StrongFocus
+ Keys.onPressed: {
+ if (loaderVisible) {
+ return
+ }
+
+ if (event.key === Qt.Key_Return || event.key === Qt.Key_Space)
+ visualStateItem.state = uiState.statePressed
+ }
+ Keys.onReleased: {
+ if (loaderVisible) {
+ return
+ }
+ if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) {
+ visualStateItem.state = uiState.stateDefault
+ }
+ if (event.key === Qt.Key_Return)
+ handleKeyClick()
+ }
+
+ Accessible.role: Accessible.Button
+ Accessible.onPressAction: handleKeyClick()
+ Accessible.focusable: true
+
+ onActiveFocusChanged: {
+ if (!activeFocus)
+ return visualStateItem.state = uiState.stateDefault
+
+ if (vpnFlickable && typeof (vpnFlickable.ensureVisible) !== "undefined")
+ return vpnFlickable.ensureVisible(visualStateItem)
+ }
+
+ background: Rectangle {
+ color: "transparent"
+ }
+}
diff --git a/gui/components/VPNMouseArea.qml b/gui/components/VPNMouseArea.qml
new file mode 100644
index 0000000..44d6465
--- /dev/null
+++ b/gui/components/VPNMouseArea.qml
@@ -0,0 +1,37 @@
+import QtQuick 2.5
+import "../themes/themes.js" as Theme
+
+MouseArea {
+ id: mouseArea
+
+ property var targetEl: parent
+ property var uiState: Theme.uiState
+ property var onMouseAreaClicked: function () {
+ parent.clicked()
+ }
+
+ //function changeState(stateName) {
+ // if (mouseArea.hoverEnabled)
+ // targetEl.state = stateName;
+ //}
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: !hoverEnabled ? Qt.ForbiddenCursor : Qt.PointingHandCursor
+ //onPressed: {
+ // console.debug("button pressed")
+ //changeState(uiState.statePressed)
+ //}
+ //onEntered: changeState(uiState.stateHovered)
+ //onExited: changeState(uiState.stateDefault)
+ //onCanceled: changeState(uiState.stateDefault)
+
+
+ /*
+ onReleased: {
+ if (hoverEnabled) {
+ changeState(uiState.stateDefault);
+ onMouseAreaClicked();
+ }
+ }
+*/
+}
diff --git a/gui/components/VPNState.qml b/gui/components/VPNState.qml
new file mode 100644
index 0000000..8856ad4
--- /dev/null
+++ b/gui/components/VPNState.qml
@@ -0,0 +1,193 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.12
+
+import "../themes/themes.js" as Theme
+
+StateGroup {
+
+ state: ctx ? ctx.status : "off"
+
+ states: [
+ State {
+ name: "initializing"
+ },
+ State {
+ name: "off"
+ PropertyChanges {
+ target: connectionState
+ text: qsTr("Connection\nUnsecured")
+ }
+ PropertyChanges {
+ target: statusBoxBackground
+ border.color: Theme.accentOff
+ }
+ PropertyChanges {
+ target: connectionImage
+ source: "../resources/spy.gif"
+ //anchors.right: parent.right
+ //anchors.rightMargin: -8
+ // XXX need to nulify horizontalcenter somehow,
+ // it gets fixed to parent.center
+ }
+ PropertyChanges {
+ target: toggleVPN
+ text: qsTr("Turn on")
+ }
+ PropertyChanges {
+ target: systray
+ icon.source: icons["off"]
+ }
+ PropertyChanges {
+ target: systray.statusItem
+ text: toHuman("off")
+ }
+ StateChangeScript {
+ script: {
+
+ }
+ }
+ },
+ State {
+ name: "on"
+ PropertyChanges {
+ target: connectionState
+ text: qsTr("Connection\nSecure")
+ }
+ PropertyChanges {
+ target: statusBoxBackground
+ border.color: Theme.accentOn
+ }
+ PropertyChanges {
+ target: connectionImage
+ source: "../resources/riseup-icon.svg"
+ // TODO need to offset the logo or increase the image
+ // to fixed height
+ height: 120
+ }
+ PropertyChanges {
+ target: spacerPreImg
+ visible: true
+ }
+ PropertyChanges {
+ target: spacerPostImg
+ visible: true
+ }
+ PropertyChanges {
+ target: toggleVPN
+ text: qsTr("Turn off")
+ }
+ PropertyChanges {
+ target: systray
+ tooltip: toHuman("on")
+ icon.source: icons["on"]
+ }
+ PropertyChanges {
+ target: systray.statusItem
+ text: toHuman("on")
+ }
+ StateChangeScript {
+ script: {
+
+ // TODO check donation
+ //if (needsDonate && !shownDonate) {
+ // donate.visible = true;
+ // shownDonate = true;
+ // backend.donateSeen();
+ //}
+ }
+ }
+ },
+ State {
+ name: "starting"
+ //when: toggleVPN.pressed == true
+ PropertyChanges {
+ target: connectionState
+ text: qsTr("Connecting")
+ }
+ PropertyChanges {
+ target: statusBoxBackground
+ border.color: Theme.accentConnecting
+ }
+ PropertyChanges {
+ target: connectionImage
+ source: "../resources/birds.svg"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ PropertyChanges {
+ target: toggleVPN
+ text: qsTr("Cancel")
+ }
+ PropertyChanges {
+ target: systray
+ tooltip: toHuman("connecting")
+ icon.source: icons["wait"]
+ }
+ PropertyChanges {
+ target: systray.statusItem
+ text: toHuman("connecting")
+ }
+ StateChangeScript {
+ script: {
+
+ }
+ }
+ },
+ State {
+ name: "stopping"
+ PropertyChanges {
+ target: connectionState
+ text: "Switching\nOff"
+ }
+ PropertyChanges {
+ target: statusBoxBackground
+ border.color: Theme.accentConnecting
+ }
+ PropertyChanges {
+ target: systray
+ tooltip: toHuman("stopping")
+ icon.source: icons["wait"]
+ }
+ PropertyChanges {
+ target: systray.statusItem
+ text: toHuman("stopping")
+ }
+ },
+ State {
+ name: "failed"
+ // TODO
+ }
+ ]
+
+
+ /*
+ transitions: Transition {
+ from: "off"
+ to: "starting"
+ reversible: true
+
+ ParallelAnimation {
+ ColorAnimation { duration: 500 }
+ }
+ }
+ */
+ function toHuman(st) {
+ switch (st) {
+ case "off":
+ //: %1 -> application name
+ return ctx ? qsTr("%1 off").arg(ctx.appName) : qsTr("off")
+ case "on":
+ //: %1 -> application name
+ return qsTr("%1 on").arg(ctx.appName)
+ case "connecting":
+ //: %1 -> application name
+ return qsTr("Connecting to %1").arg(ctx.appName)
+ 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 handled yet
+ }
+ }
+}
diff --git a/gui/components/VPNToggle.qml b/gui/components/VPNToggle.qml
new file mode 100644
index 0000000..4773869
--- /dev/null
+++ b/gui/components/VPNToggle.qml
@@ -0,0 +1,338 @@
+
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+import QtQuick 2.5
+import QtQuick.Controls 2.5
+
+import "../themes/themes.js" as Theme
+
+VPNButtonBase {
+ id: toggleButton
+
+ property var connectionRetryOverX: VPNController.connectionRetry > 1
+ property var toggleColor: Theme.vpnToggleDisconnected
+ property var toolTipTitle: ""
+ Accessible.name: toolTipTitle
+
+ function handleClick() {
+ toolTip.close()
+ if (VPNController.state !== VPNController.StateOff) {
+ return VPN.deactivate()
+ }
+
+ return VPN.activate()
+ }
+
+ onClicked: handleClick()
+
+ // property in VPNButtonBase {}
+ visualStateItem: toggle
+ //state: VPNController.state
+ height: 32
+ width: 60
+ radius: 16
+ hoverEnabled: false
+
+ onActiveFocusChanged: {
+ if (!focus && toolTip.visible) {
+ toolTip.close()
+ }
+ }
+
+
+ /*
+ states: [
+ State {
+ name: VPNController.StateInitializing
+
+ PropertyChanges {
+ target: cursor
+ anchors.leftMargin: 4
+ }
+
+ PropertyChanges {
+ target: toggle
+ color: "#9E9E9E"
+ border.color: Theme.white
+ }
+
+ PropertyChanges {
+ target: toggleButton
+ toggleColor: Theme.vpnToggleDisconnected
+ }
+
+ },
+ State {
+ name: VPNController.StateOff
+
+ PropertyChanges {
+ target: cursor
+ anchors.leftMargin: 4
+ }
+
+ PropertyChanges {
+ target: toggle
+ color: "#9E9E9E"
+ border.color: Theme.white
+ }
+
+ PropertyChanges {
+ target: toggleButton
+ //% "Turn VPN on"
+ toolTipTitle: qsTrId("vpn.toggle.on")
+ }
+
+ },
+ State {
+ name: VPNController.StateConnecting
+
+ PropertyChanges {
+ target: cursor
+ anchors.leftMargin: 32
+ color: "#998DB2"
+ }
+
+ PropertyChanges {
+ target: toggle
+ color: "#387E8A"
+ border.color: Theme.ink
+ }
+
+ PropertyChanges {
+ target: toggleButton
+ //% "Turn VPN off"
+ toolTipTitle: qsTrId("vpn.toggle.off")
+ toggleColor: Theme.vpnToggleConnected
+ }
+
+ },
+ State {
+ name: VPNController.StateConfirming
+
+ PropertyChanges {
+ target: cursor
+ anchors.leftMargin: 32
+ color: connectionRetryOverX ? "#FFFFFF" : "#998DB2"
+ }
+
+ PropertyChanges {
+ target: toggle
+ color: "#387E8A"
+ border.color: Theme.ink
+ }
+
+ PropertyChanges {
+ target: toggleButton
+ //% "Turn VPN off"
+ toolTipTitle: qsTrId("vpn.toggle.off")
+ toggleColor: Theme.vpnToggleConnected
+ }
+
+ },
+ State {
+ name: VPNController.StateOn
+
+ PropertyChanges {
+ target: cursor
+ anchors.leftMargin: 32
+ }
+
+ PropertyChanges {
+ target: toggle
+ color: "#3FE1B0"
+ border.color: Theme.ink
+ }
+
+ PropertyChanges {
+ target: toggleButton
+ toolTipTitle: qsTrId("vpn.toggle.off")
+ toggleColor: Theme.vpnToggleConnected
+ }
+
+ },
+ State {
+ name: VPNController.StateDisconnecting
+
+ PropertyChanges {
+ target: cursor
+ anchors.leftMargin: 4
+ }
+
+ PropertyChanges {
+ target: toggle
+ color: "#CECECE"
+ border.color: Theme.white
+ }
+
+ PropertyChanges {
+ target: toggleButton
+ toolTipTitle: qsTrId("vpn.toggle.on")
+ toggleColor: Theme.vpnToggleDisconnected
+ }
+
+ },
+ State {
+ name: VPNController.StateSwitching
+
+ PropertyChanges {
+ target: cursor
+ anchors.leftMargin: 32
+ color: "#998DB2"
+ }
+
+ PropertyChanges {
+ target: toggle
+ color: "#387E8A"
+ border.color: Theme.ink
+ }
+
+ PropertyChanges {
+ target: toggleButton
+ toggleColor: Theme.vpnToggleConnected
+ }
+
+ }
+ ]
+ transitions: [
+ Transition {
+ ParallelAnimation {
+ NumberAnimation {
+ target: cursor
+ property: "anchors.leftMargin"
+ duration: 200
+ }
+
+ ColorAnimation {
+ target: cursor
+ duration: 200
+ }
+
+ }
+
+ }
+ ]
+
+ // Focus rings
+ VPNFocusBorder {
+ id: focusHandler
+
+ anchors.fill: toggle
+ anchors.margins: -4
+ radius: height / 2
+ border.color: toggleColor.focusBorder
+ color: "transparent"
+ opacity: toggleButton.activeFocus && (VPNController.state === VPNController.StateOn || VPNController.state === VPNController.StateOff) ? 1 : 0
+
+ VPNFocusOutline {
+ id: vpnFocusOutline
+
+ anchors.fill: focusHandler
+ focusedComponent: focusHandler
+ setMargins: -6
+ radius: height / 2
+ border.width: 7
+ color: "transparent"
+ border.color: toggleColor.focusOutline
+ opacity: 0.25
+ }
+
+ }
+
+ // Faint outline visible on hover and press
+ Rectangle {
+ id: hoverPressHandler
+
+ color: "#C2C2C2"
+ state: toggle.state
+ opacity: {
+ if (state === uiState.stateDefault || toggleButton.activeFocus)
+ return 0;
+
+ if (state === uiState.stateHovered)
+ return 0.2;
+
+ if (state === uiState.statePressed)
+ return 0.3;
+
+ }
+ z: -1
+ anchors.fill: toggle
+ radius: height / 2
+ anchors.margins: -5
+
+ PropertyAnimation on opacity {
+ duration: 200
+ }
+
+ }
+ */
+ function toggleClickable() {
+ return VPN.state === VPN.StateMain
+ && (VPNController.state === VPNController.StateOn
+ || VPNController.state === VPNController.StateOff
+ || (VPNController.state === VPNController.StateConfirming
+ && connectionRetryOverX))
+ }
+
+ // Toggle background color changes on hover and press
+ VPNUIStates {
+ itemToFocus: toggleButton
+ itemToAnchor: toggle
+ colorScheme: toggleColor
+ radius: height / 2
+ setMargins: -7
+ showFocusRings: false
+ opacity: toggleClickable() ? 1 : 0
+ z: 1
+
+ Behavior on opacity {
+ PropertyAnimation {
+ property: "opacity"
+ duration: 100
+ }
+ }
+ }
+
+ Rectangle {
+ id: cursor
+
+ height: 24
+ width: 24
+ radius: 12
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.topMargin: 4
+ z: 2
+ }
+
+ VPNMouseArea {
+ id: mouseArea
+
+ targetEl: toggle
+ anchors.fill: toggle
+ hoverEnabled: toggleClickable()
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ VPNToolTip {
+ id: toolTip
+ text: toolTipTitle
+ }
+
+ background: Rectangle {
+ id: toggle
+
+ Component.onCompleted: state = uiState.stateDefault
+ border.width: 0
+ anchors.fill: toggleButton
+ radius: height / 2
+
+ Behavior on color {
+ ColorAnimation {
+ duration: 200
+ }
+ }
+ }
+}
diff --git a/gui/components/VerticalSpacer.qml b/gui/components/VerticalSpacer.qml
new file mode 100644
index 0000000..455e4da
--- /dev/null
+++ b/gui/components/VerticalSpacer.qml
@@ -0,0 +1,6 @@
+import QtQuick 2.0
+
+Rectangle {
+ color: "transparent"
+ width: parent.width
+}
diff --git a/gui/gui.qrc b/gui/gui.qrc
index 3099727..dde4c8f 100644
--- a/gui/gui.qrc
+++ b/gui/gui.qrc
@@ -1,7 +1,50 @@
<RCC>
<qresource prefix="/">
- <file>qml/main.qml</file>
+ <file>main.qml</file>
+
+ <file>themes/themes.js</file>
+ <file>components/MainView.qml</file>
+ <file>components/Splash.qml</file>
+ <file>components/Home.qml</file>
+ <file>components/Header.qml</file>
+ <file>components/Footer.qml</file>
+ <file>components/StatusBox.qml</file>
+ <file>components/Spinner.qml</file>
+ <file>components/Systray.qml</file>
+ <file>components/Help.qml</file>
+ <file>components/Locations.qml</file>
+ <file>components/Preferences.qml</file>
+ <file>components/BoldLabel.qml</file>
+ <file>components/LightLabel.qml</file>
+ <file>components/VPNButtonBase.qml</file>
+ <file>components/VPNMouseArea.qml</file>
+ <file>components/VerticalSpacer.qml</file>
+ <file>components/Icon.qml</file>
+ <file>components/MaterialButton.qml</file>
+ <file>components/VPNState.qml</file>
+ <file>resources/icon-noshield.svg</file>
+ <file>resources/location.svg</file>
+ <file>resources/settings.svg</file>
+ <file>resources/power.svg</file>
+ <file>resources/close.svg</file>
+ <file>resources/donate.svg</file>
+ <file>resources/tools.svg</file>
+ <file>resources/help.svg</file>
+ <file>resources/about.svg</file>
+ <file>resources/bridge.png</file>
+ <file>resources/gear-fill.svg</file>
+ <file>resources/reception-0.svg</file>
+ <file>resources/reception-2.svg</file>
+ <file>resources/reception-4.svg</file>
+ <file>resources/arrow-left.svg</file>
+ <file>resources/globe.svg</file>
+ <file>resources/birds.svg</file>
+ <file>resources/riseup-icon.svg</file>
+ <file>resources/spy.gif</file>
+ <file>resources/quit.svg</file>
+
+ <!-- old, to remove -->
<file>qml/VpnState.qml</file>
<file>qml/AboutDialog.qml</file>
<file>qml/DonateDialog.qml</file>
@@ -14,6 +57,7 @@
<file>qml/VPNSwitch.qml</file>
<file>qml/BridgesItem.qml</file>
<file>qml/logic.js</file>
+ <!-- to remove -->
<file>assets/icon/png/black/vpn_off.png</file>
<file>assets/icon/png/black/vpn_on.png</file>
diff --git a/gui/main.cpp b/gui/main.cpp
index d10cb51..d136c3c 100644
--- a/gui/main.cpp
+++ b/gui/main.cpp
@@ -5,6 +5,7 @@
#include <QTranslator>
#include <QCommandLineParser>
#include <QQuickWindow>
+#include <QQuickStyle>
#include <QSystemTrayIcon>
#include <QtQml>
@@ -137,9 +138,8 @@ int main(int argc, char **argv) {
exit(0);
}
- if (hideSystray) {
+ if (hideSystray)
qDebug() << "Not showing systray icon because --no-systray option is set.";
- }
if (installHelpers) {
qDebug() << "Will try to install helpers with sudo";
@@ -159,11 +159,13 @@ int main(int argc, char **argv) {
availableSystray = false;
}
+ /* set window icon */
+ app.setWindowIcon(QIcon(":/vendor/icon.svg"));
+
+ /* load translations */
QTranslator translator;
translator.load(QLocale(), QLatin1String("main"), QLatin1String("_"), QLatin1String(":/i18n"));
app.installTranslator(&translator);
- /* set window icon */
- app.setWindowIcon(QIcon(":/vendor/icon.svg"));
QQmlApplicationEngine engine;
@@ -172,6 +174,7 @@ int main(int argc, char **argv) {
QJsonModel *model = new QJsonModel;
QString desktop = QString::fromStdString(getEnv("XDG_CURRENT_DESKTOP"));
+ QString debug = QString::fromStdString(getEnv("DEBUG"));
/* the backend handler has slots for calling back to Go when triggered by
signals in Qml. */
@@ -185,8 +188,10 @@ int main(int argc, char **argv) {
/* set some useful flags */
ctx->setContextProperty("systrayVisible", !hideSystray);
ctx->setContextProperty("systrayAvailable", availableSystray);
+ ctx->setContextProperty("qmlDebug", debug == "1");
- engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
+ QQuickStyle::setStyle("Material");
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
/* connect the jsonChanged signal explicitely.
In the lambda, we reload the json in the model every time we receive an
@@ -215,9 +220,11 @@ int main(int argc, char **argv) {
obfs4, disAutostart, toGoStr(startVPN));
/* if requested, enable web api for controlling the VPN */
- if (webAPI) {
+ if (webAPI)
EnableWebAPI(toGoStr(webPort));
- };
+
+ if (engine.rootObjects().isEmpty())
+ return -1;
/* kick off your shoes, put your feet up */
return app.exec();
diff --git a/gui/main.qml b/gui/main.qml
new file mode 100644
index 0000000..16677a2
--- /dev/null
+++ b/gui/main.qml
@@ -0,0 +1,116 @@
+/*
+ TODO (ui rewrite)
+ - [x] add systray
+ - [x] systray status
+ - [x] splash screen
+ - [ ] splash delay/transitions
+ - [ ] nested states
+ - [ ] splash init errors
+ - [ ] font: monserrat
+ - [ ] donation dialog
+ - [ ] add gateway to systray
+ - [ ] control actions from systray
+ - [ ] minimize/hide from systray
+ - [ ] parse ctx flags (need dialog, etc)
+ - [ ] gateway selector
+ - [ ] bridges
+*/
+
+import QtQuick 2.0
+import QtQuick.Controls 2.4
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls.Material 2.1
+import QtQuick.Layouts 1.14
+
+import "./components"
+
+ApplicationWindow {
+
+ id: root
+
+ visible: true
+ width: 360
+ height: 520
+ minimumWidth: 300
+ maximumWidth: 300
+ minimumHeight: 500
+ maximumHeight: 500
+
+ title: ctx ? ctx.appName : "VPN"
+ Material.accent: Material.Green
+
+ property var ctx
+
+ property var icons: {
+ "off": "qrc:/assets/icon/png/white/vpn_off.png",
+ "on": "qrc:/assets/icon/png/white/vpn_on.png",
+ "wait": "qrc:/assets/icon/png/white/vpn_wait_0.png",
+ "blocked": "qrc:/assets/icon/png/white/vpn_blocked.png"
+ }
+
+ Loader {
+ id: loader
+ asynchronous: true
+ anchors.fill: parent
+ }
+
+ Systray {
+ id: systray
+ }
+
+ Connections {
+ target: jsonModel
+ function onDataChanged() {
+ ctx = JSON.parse(jsonModel.getJson())
+ if (qmlDebug) {
+ console.debug(jsonModel.getJson())
+ }
+
+ // FIXME -- use nested state machines for all these cases.
+
+ //gwSelector.model = Object.keys(ctx.locations)
+
+ /*
+ if (ctx.donateDialog == 'true') {
+ Logic.setNeedsDonate(true);
+ }
+ if (ctx.loginDialog == 'true') {
+ console.debug(jsonModel.getJson())
+ console.debug("DEBUG: should display login")
+ login.visible = true
+ }
+ if (ctx.loginOk == 'true') {
+ loginOk.visible = true
+ }
+ if (ctx.errors) {
+ login.visible = false
+ if (ctx.errors == "nohelpers") {
+ showInitFailure(
+ qsTr("Could not find helpers. Please check your installation"))
+ } else if (ctx.errors == "nopolkit") {
+ showInitFailure(qsTr("Could not find polkit agent."))
+ } else {
+ showInitFailure()
+ }
+ }
+ if (ctx.donateURL) {
+ donateItem.visible = true
+ }
+
+ if (ctx.status == "on") {
+ gwNextConnectionText.visible = false
+ gwReconnectText.visible = false
+ }
+ */
+ }
+ }
+
+ onSceneGraphError: function(error, msg) {
+ console.debug("ERROR while initializing scene")
+ console.debug(msg)
+ }
+
+ Component.onCompleted: {
+ loader.source = "components/Splash.qml"
+ }
+}
diff --git a/gui/providers/providers.json b/gui/providers/providers.json
index 8592ba0..52fcbf9 100644
--- a/gui/providers/providers.json
+++ b/gui/providers/providers.json
@@ -15,7 +15,7 @@
"apiURL": "https://api.black.riseup.net/",
"geolocationAPI": "https://api.black.riseup.net:9001/json",
"caCertString": "-----BEGIN CERTIFICATE-----\nMIIFjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBZMRgwFgYDVQQKDA9SaXNl\ndXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UE\nAwwXUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EwHhcNMTQwNDI4MDAwMDAwWhcNMjQw\nNDI4MDAwMDAwWjBZMRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsM\nEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UEAwwXUmlzZXVwIE5ldHdvcmtzIFJv\nb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC76J4ciMJ8Sg0m\nTP7DF2DT9zNe0Csk4myoMFC57rfJeqsAlJCv1XMzBmXrw8wq/9z7XHv6n/0sWU7a\n7cF2hLR33ktjwODlx7vorU39/lXLndo492ZBhXQtG1INMShyv+nlmzO6GT7ESfNE\nLliFitEzwIegpMqxCIHXFuobGSCWF4N0qLHkq/SYUMoOJ96O3hmPSl1kFDRMtWXY\niw1SEKjUvpyDJpVs3NGxeLCaA7bAWhDY5s5Yb2fA1o8ICAqhowurowJpW7n5ZuLK\n5VNTlNy6nZpkjt1QycYvNycffyPOFm/Q/RKDlvnorJIrihPkyniV3YY5cGgP+Qkx\nHUOT0uLA6LHtzfiyaOqkXwc4b0ZcQD5Vbf6Prd20Ppt6ei0zazkUPwxld3hgyw58\nm/4UIjG3PInWTNf293GngK2Bnz8Qx9e/6TueMSAn/3JBLem56E0WtmbLVjvko+LF\nPM5xA+m0BmuSJtrD1MUCXMhqYTtiOvgLBlUm5zkNxALzG+cXB28k6XikXt6MRG7q\nhzIPG38zwkooM55yy5i1YfcIi5NjMH6A+t4IJxxwb67MSb6UFOwg5kFokdONZcwj\nshczHdG9gLKSBIvrKa03Nd3W2dF9hMbRu//STcQxOailDBQCnXXfAATj9pYzdY4k\nha8VCAREGAKTDAex9oXf1yRuktES4QIDAQABo2AwXjAdBgNVHQ4EFgQUC4tdmLVu\nf9hwfK4AGliaet5KkcgwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYD\nVR0jBBgwFoAUC4tdmLVuf9hwfK4AGliaet5KkcgwDQYJKoZIhvcNAQENBQADggIB\nAGzL+GRnYu99zFoy0bXJKOGCF5XUXP/3gIXPRDqQf5g7Cu/jYMID9dB3No4Zmf7v\nqHjiSXiS8jx1j/6/Luk6PpFbT7QYm4QLs1f4BlfZOti2KE8r7KRDPIecUsUXW6P/\n3GJAVYH/+7OjA39za9AieM7+H5BELGccGrM5wfl7JeEz8in+V2ZWDzHQO4hMkiTQ\n4ZckuaL201F68YpiItBNnJ9N5nHr1MRiGyApHmLXY/wvlrOpclh95qn+lG6/2jk7\n3AmihLOKYMlPwPakJg4PYczm3icFLgTpjV5sq2md9bRyAg3oPGfAuWHmKj2Ikqch\nTd5CHKGxEEWbGUWEMP0s1A/JHWiCbDigc4Cfxhy56CWG4q0tYtnc2GMw8OAUO6Wf\nXu5pYKNkzKSEtT/MrNJt44tTZWbKV/Pi/N2Fx36my7TgTUj7g3xcE9eF4JV2H/sg\ntsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF\ntGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ\nUN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp\n0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO\n-----END CERTIFICATE-----",
- "timeStamp": "2021-07-09 20:10:58"
+ "timeStamp": "2021-08-25 00:56:22"
}
]
} \ No newline at end of file
diff --git a/gui/qml/BackgroundImage.qml b/gui/qml/BackgroundImage.qml
index 3071bf4..ba2a8af 100644
--- a/gui/qml/BackgroundImage.qml
+++ b/gui/qml/BackgroundImage.qml
@@ -3,22 +3,22 @@ import QtQuick.Controls 2.4
Rectangle {
- anchors.fill: parent;
- anchors.topMargin: 40;
+ anchors.fill: parent
+ anchors.topMargin: 40
property var backgroundSrc
property var backgroundVisible
Image {
- source: parent.backgroundSrc;
- visible: parent.backgroundVisible;
- fillMode: Image.PreserveAspectCrop;
- anchors.fill: parent;
- opacity: 0.8;
+ source: parent.backgroundSrc
+ visible: parent.backgroundVisible
+ fillMode: Image.PreserveAspectCrop
+ anchors.fill: parent
+ opacity: 0.8
}
Component.onCompleted: {
/* default for riseup, needs customizing */
- backgroundSrc = "qrc:/assets/img/bird.jpg";
+ backgroundSrc = "qrc:/assets/img/bird.jpg"
}
}
diff --git a/gui/qml/BridgesItem.qml b/gui/qml/BridgesItem.qml
index 1343403..f7ccb0f 100644
--- a/gui/qml/BridgesItem.qml
+++ b/gui/qml/BridgesItem.qml
@@ -26,15 +26,15 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
if (checked) {
- Logic.setNeedsReconnect(true);
- bridgeReconnect.visible = true;
- app.useBridges(true);
+ Logic.setNeedsReconnect(true)
+ bridgeReconnect.visible = true
+ app.useBridges(true)
} else {
// This would also need a "needs reconnect" for de-selecting bridges the next time.
// better to wait and see the new connection widgets though
- Logic.setNeedsReconnect(false);
- bridgeReconnect.visible = false;
- app.useBridges(false);
+ Logic.setNeedsReconnect(false)
+ bridgeReconnect.visible = false
+ app.useBridges(false)
}
}
}
@@ -45,7 +45,7 @@ Item {
color: "grey"
text: qsTr("Select a bridge only if you know that you need it to evade censorship in your country or local network.")
anchors.horizontalCenter: parent.horizontalCenter
- wrapMode: Text.WordWrap
+ wrapMode: Text.WordWrap
visible: !bridgeReconnect.visible
}
@@ -56,7 +56,7 @@ Item {
color: "red"
text: qsTr("An obfs4 bridge will be used the next time you connect to the VPN.")
anchors.horizontalCenter: parent.horizontalCenter
- wrapMode: Text.WordWrap
+ wrapMode: Text.WordWrap
visible: false
}
}
diff --git a/gui/qml/VPNSwitch.qml b/gui/qml/VPNSwitch.qml
index 89c455f..c58836e 100644
--- a/gui/qml/VPNSwitch.qml
+++ b/gui/qml/VPNSwitch.qml
@@ -2,7 +2,6 @@ import QtQuick 2.9
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.4
-
SwitchDelegate {
text: qsTr("")
diff --git a/gui/qml/VpnState.qml b/gui/qml/VpnState.qml
index a4bb779..31910c7 100644
--- a/gui/qml/VpnState.qml
+++ b/gui/qml/VpnState.qml
@@ -14,7 +14,7 @@ StateGroup {
State {
name: "off"
StateChangeScript {
- script: Logic.setStatus("off");
+ script: Logic.setStatus("off")
}
PropertyChanges {
target: systray
@@ -47,16 +47,15 @@ StateGroup {
name: "on"
StateChangeScript {
script: {
- Logic.setNeedsReconnect(false);
- brReconnect = false;
+ Logic.setNeedsReconnect(false)
+ brReconnect = false
if (needsDonate && !shownDonate) {
- donate.visible = true;
- shownDonate = true;
- backend.donateSeen();
+ donate.visible = true
+ shownDonate = true
+ backend.donateSeen()
}
}
-
}
PropertyChanges {
target: systray
diff --git a/gui/qml/main.qml b/gui/qml/main.qml
index fef68cf..2c94c80 100644
--- a/gui/qml/main.qml
+++ b/gui/qml/main.qml
@@ -1,9 +1,10 @@
import QtQuick 2.9
-import QtQuick.Dialogs 1.2 // TODO use native dialogs in labs.platform
+import QtQuick.Dialogs 1.2
+// TODO use native dialogs in labs.platform
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.4
-import Qt.labs.platform 1.0
+import Qt.labs.platform 1.0 as Labs
import "logic.js" as Logic
@@ -24,7 +25,7 @@ ApplicationWindow {
property var needsDonate
property var shownDonate
- onSceneGraphError: function(error, msg) {
+ onSceneGraphError: function (error, msg) {
console.debug("ERROR while initializing scene")
console.debug(msg)
}
@@ -116,8 +117,8 @@ ApplicationWindow {
anchors.centerIn: parent
spacing: 10
- //width: parent.width
+ //width: parent.width
RadioButton {
id: autoSelectionButton
checked: !isManualLocation()
@@ -161,6 +162,7 @@ ApplicationWindow {
color: {
"#ffffff"
// FIXME locations is not defined when we launch
+
/*
const fullness = ctx.locations[modelData]
if (fullness >= 0 && fullness < 0.4) {
@@ -181,7 +183,6 @@ ApplicationWindow {
color: "#000000"
}
}
-
}
Text {
@@ -190,7 +191,7 @@ ApplicationWindow {
width: 180
font.pixelSize: 12
color: "green"
- wrapMode: Text.WordWrap
+ wrapMode: Text.WordWrap
text: qsTr("Reconnecting to the selected gateway…")
visible: false
}
@@ -201,31 +202,29 @@ ApplicationWindow {
width: 180
font.pixelSize: 12
color: "green"
- wrapMode: Text.WordWrap
+ wrapMode: Text.WordWrap
text: qsTr("This gateway will be used for next connection.")
visible: false
}
-
} // end column
} // end item
BridgesItem {
id: bridgesTab
}
-
} // end stacklayout
Connections {
target: jsonModel
function onDataChanged() {
ctx = JSON.parse(jsonModel.getJson())
+
// TODO pass QML_DEBUG variable to be hyper-verbose
//console.debug(jsonModel.getJson())
-
gwSelector.model = Object.keys(ctx.locations)
if (ctx.donateDialog == 'true') {
- Logic.setNeedsDonate(true);
+ Logic.setNeedsDonate(true)
}
if (ctx.loginDialog == 'true') {
console.debug(jsonModel.getJson())
@@ -294,11 +293,11 @@ ApplicationWindow {
function hasMultipleGateways() {
// could also count the gateways
- let provider = Logic.getSelectedProvider(providers);
+ let provider = Logic.getSelectedProvider(providers)
if (provider == "riseup") {
- return true;
+ return true
} else {
- return false;
+ return false
}
}
@@ -319,35 +318,34 @@ ApplicationWindow {
}
function showMainWindow() {
- bar.currentIndex = 0
- app.visible = true
- app.show()
- app.raise()
+ bar.currentIndex = 0
+ app.visible = true
+ app.show()
+ app.raise()
}
Component.onCompleted: {
Logic.debugInit()
loginDone = false
allowEmptyPass = Logic.shouldAllowEmptyPass(providers)
- needsRestart = false;
- shownDonate = false;
+ needsRestart = false
+ shownDonate = false
/* this is a temporary workaround until general GUI revamp for 0.21.8 */
- let provider = Logic.getSelectedProvider(providers);
+ let provider = Logic.getSelectedProvider(providers)
if (provider == "calyx") {
- background.color = "#8EA844";
- background.backgroundVisible = false;
- gwSelector.visible = false;
- manualSelectionButton.visible = false;
+ background.color = "#8EA844"
+ background.backgroundVisible = false
+ gwSelector.visible = false
+ manualSelectionButton.visible = false
}
if (!systrayAvailable) {
- app.visible = true
- app.raise()
+ app.visible = true
+ app.raise()
}
}
-
property var icons: {
"off": "qrc:/assets/icon/png/white/vpn_off.png",
"on": "qrc:/assets/icon/png/white/vpn_on.png",
@@ -364,6 +362,7 @@ ApplicationWindow {
id: systray
visible: systrayVisible
+
/* the systray menu cannot be buried in a child qml file because
* otherwise the ids are not available
* from other components
@@ -501,9 +500,8 @@ ApplicationWindow {
if (Qt.platform.os === "windows") {
let appname = ctx ? ctx.appName : "VPN"
Logic.showNotification(
- ctx,
- appname
- + " is up and running. Please use system tray icon to control it.")
+ ctx, appname
+ + " is up and running. Please use system tray icon to control it.")
}
}
}
@@ -560,13 +558,12 @@ ApplicationWindow {
}
function useBridges(value) {
- if (value==true) {
+ if (value == true) {
backend.setTransport("obfs4")
} else {
backend.setTransport("openvpn")
}
}
- property alias brReconnect:bridgesTab.displayReconnect
-
+ property alias brReconnect: bridgesTab.displayReconnect
}
diff --git a/gui/qml/readme.txt b/gui/qml/readme.txt
new file mode 100644
index 0000000..e7d468c
--- /dev/null
+++ b/gui/qml/readme.txt
@@ -0,0 +1 @@
+This folder will be deprecated.
diff --git a/gui/resources/about.svg b/gui/resources/about.svg
new file mode 100644
index 0000000..9a47ce3
--- /dev/null
+++ b/gui/resources/about.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="90.946px" height="90.945px" viewBox="0 0 90.946 90.945" style="enable-background:new 0 0 90.946 90.945;"
+ xml:space="preserve">
+<g>
+ <g>
+ <polygon points="68.801,9.457 67.946,12.356 69.498,11.851 "/>
+ <polygon points="46.716,25.955 47.535,28.557 48.722,27.438 "/>
+ <path d="M79.788,35.755c-2.208,1.979-5.452,4.616-8.333,7.148c-0.317-4.402-1.093-8.953-1.093-12.952
+ c0.009-6.419,0.688-15.586-0.876-16.621l-1.574,0.004c-1.254,1.424-0.876,8.642-1.177,14.274
+ c-0.217,4.113-0.603,9.976-0.784,15.173c-2.353-1.979-4.863-3.852-6.821-5.812c-3.533-3.542-8.271-8.913-9.739-8.579L48.484,29.3
+ c0.062,1.509,4.896,5.131,7.826,8.408c2.861,3.194,6.629,8.42,9.923,11.714c0.232,0.23,0.511,0.374,0.803,0.46
+ c0.471,0.319,1.045,0.516,1.67,0.516c0.584,0,1.088-0.205,1.528-0.492c0.356-0.078,0.698-0.21,0.981-0.466
+ c3.325-3.013,5.982-7.323,9.005-10.343c3.541-3.532,9.07-8.318,8.739-9.787l-0.91-0.904
+ C86.543,28.471,83.063,32.829,79.788,35.755z"/>
+ <polygon points="68.361,55.795 70.085,51.603 67.378,51.762 "/>
+ <polygon points="88.666,27.691 90.061,28.529 90.946,26.198 "/>
+ <polygon points="22.144,35.151 21.449,37.545 23,38.05 "/>
+ <polygon points="43.411,54.25 44.23,51.647 42.224,53.131 "/>
+ <path d="M31.815,62.665c-1.958,1.96-4.471,3.835-6.823,5.812c-0.18-5.196-0.568-11.059-0.783-15.174
+ c-0.303-5.63,0.078-12.848-1.179-14.272l-1.572-0.005c-1.566,1.032-0.886,10.203-0.879,16.622c0.003,3.997-0.772,8.55-1.09,12.952
+ c-2.882-2.532-6.125-5.169-8.333-7.148c-3.272-2.927-6.755-7.286-8.262-7.35l-0.911,0.905c-0.332,1.469,5.202,6.255,8.739,9.788
+ c3.022,3.016,5.681,7.329,9.007,10.343c0.28,0.25,0.624,0.388,0.979,0.465c0.441,0.287,0.943,0.493,1.527,0.493
+ c0.626,0,1.201-0.196,1.671-0.516c0.294-0.087,0.573-0.23,0.803-0.461c3.296-3.294,7.063-8.518,9.925-11.714
+ c2.929-3.277,7.76-6.899,7.825-8.407l-0.907-0.91C40.084,53.753,35.348,59.125,31.815,62.665z"/>
+ <polygon points="22.585,81.488 23.568,77.456 20.86,77.296 "/>
+ <polygon points="0,51.892 0.885,54.223 2.281,53.386 "/>
+ </g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/gui/resources/arrow-left.svg b/gui/resources/arrow-left.svg
new file mode 100644
index 0000000..9d88501
--- /dev/null
+++ b/gui/resources/arrow-left.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
+ <path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"/>
+</svg> \ No newline at end of file
diff --git a/gui/resources/birds.svg b/gui/resources/birds.svg
new file mode 100644
index 0000000..cb2a1df
--- /dev/null
+++ b/gui/resources/birds.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 174.92 102.4"><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="C0eYCH.tif"><image id="Layer_1-3" data-name="Layer 1" width="277" height="129" transform="translate(12.45) rotate(9.34) scale(0.59)" xlink:href="data:image/png;base64,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"/></g></g></g></svg> \ No newline at end of file
diff --git a/gui/resources/bridge.png b/gui/resources/bridge.png
new file mode 100644
index 0000000..d0a2c80
--- /dev/null
+++ b/gui/resources/bridge.png
Binary files differ
diff --git a/gui/resources/close.svg b/gui/resources/close.svg
new file mode 100644
index 0000000..ead8e16
--- /dev/null
+++ b/gui/resources/close.svg
@@ -0,0 +1,3 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.22566 4.81096C5.83514 4.42044 5.20197 4.42044 4.81145 4.81096C4.42092 5.20148 4.42092 5.83465 4.81145 6.22517L10.5862 11.9999L4.81151 17.7746C4.42098 18.1651 4.42098 18.7983 4.81151 19.1888C5.20203 19.5793 5.8352 19.5793 6.22572 19.1888L12.0004 13.4141L17.7751 19.1888C18.1656 19.5793 18.7988 19.5793 19.1893 19.1888C19.5798 18.7983 19.5798 18.1651 19.1893 17.7746L13.4146 11.9999L19.1893 6.22517C19.5799 5.83465 19.5799 5.20148 19.1893 4.81096C18.7988 4.42044 18.1657 4.42044 17.7751 4.81096L12.0004 10.5857L6.22566 4.81096Z" fill="black"/>
+</svg>
diff --git a/gui/resources/donate.svg b/gui/resources/donate.svg
new file mode 100644
index 0000000..35d7c2b
--- /dev/null
+++ b/gui/resources/donate.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" class="icon">
+ <path d="M880 310H732.4c13.6-21.4 21.6-46.8 21.6-74 0-76.1-61.9-138-138-138-41.4 0-78.7 18.4-104 47.4-25.3-29-62.6-47.4-104-47.4-76.1 0-138 61.9-138 138 0 27.2 7.9 52.6 21.6 74H144c-17.7 0-32 14.3-32 32v200c0 4.4 3.6 8 8 8h40v344c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V550h40c4.4 0 8-3.6 8-8V342c0-17.7-14.3-32-32-32zm-334-74c0-38.6 31.4-70 70-70s70 31.4 70 70-31.4 70-70 70h-70v-70zm-138-70c38.6 0 70 31.4 70 70v70h-70c-38.6 0-70-31.4-70-70s31.4-70 70-70zM180 482V378h298v104H180zm48 68h250v308H228V550zm568 308H546V550h250v308zm48-376H546V378h298v104z"/>
+</svg>
diff --git a/gui/resources/gear-fill.svg b/gui/resources/gear-fill.svg
new file mode 100644
index 0000000..2aa36a1
--- /dev/null
+++ b/gui/resources/gear-fill.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear-fill" viewBox="0 0 16 16">
+ <path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
+</svg> \ No newline at end of file
diff --git a/gui/resources/globe.svg b/gui/resources/globe.svg
new file mode 100644
index 0000000..150a01e
--- /dev/null
+++ b/gui/resources/globe.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-globe2" viewBox="0 0 16 16">
+ <path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/>
+</svg> \ No newline at end of file
diff --git a/gui/resources/help.svg b/gui/resources/help.svg
new file mode 100644
index 0000000..06569c8
--- /dev/null
+++ b/gui/resources/help.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" class="icon">
+ <path d="M824.2 699.9a301.55 301.55 0 0 0-86.4-60.4C783.1 602.8 812 546.8 812 484c0-110.8-92.4-201.7-203.2-200-109.1 1.7-197 90.6-197 200 0 62.8 29 118.8 74.2 155.5a300.95 300.95 0 0 0-86.4 60.4C345 754.6 314 826.8 312 903.8a8 8 0 0 0 8 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5A226.62 226.62 0 0 1 612 684c60.9 0 118.2 23.7 161.3 66.8C814.5 792 838 846.3 840 904.3c.1 4.3 3.7 7.7 8 7.7h56a8 8 0 0 0 8-8.2c-2-77-33-149.2-87.8-203.9zM612 612c-34.2 0-66.4-13.3-90.5-37.5a126.86 126.86 0 0 1-37.5-91.8c.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4 0 34.2-13.3 66.3-37.5 90.5A127.3 127.3 0 0 1 612 612zM361.5 510.4c-.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1a127.54 127.54 0 0 1-38.7-95.4c.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204a8 8 0 0 0 8 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7z"/>
+</svg>
diff --git a/gui/resources/icon-noshield.svg b/gui/resources/icon-noshield.svg
new file mode 100644
index 0000000..815a0d6
--- /dev/null
+++ b/gui/resources/icon-noshield.svg
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="128"
+ height="128"
+ viewBox="0 0 33.866666 33.866668"
+ version="1.1"
+ id="svg896"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
+ sodipodi:docname="icon-noshield.svg">
+ <defs
+ id="defs890" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#d7d7d7"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="3.3834377"
+ inkscape:cx="-34.426658"
+ inkscape:cy="43.45762"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ units="px"
+ inkscape:window-width="1920"
+ inkscape:window-height="1032"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:document-rotation="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid1471"
+ empspacing="4" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata893">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-263.13332)">
+ <path
+ style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ d="m 17.51831,273.60678 c 0,0 -0.315707,-0.61165 -0.336761,-0.69601 -0.02106,-0.0844 -0.189431,-0.94909 -0.189431,-0.94909 l 0.210485,-0.37963 0.463046,-0.696 0.673531,-0.52727 0.168377,-0.0633 1.620684,-0.61164 0.147331,-0.14764 -0.168385,-0.25308 -0.231523,-0.12655 -0.424879,-0.10907 -1.064407,-0.10907 -0.825967,0.007 -0.568293,0.12654 -0.505146,-0.18981 -0.63143,-0.0844 -0.9261,-0.0211 -0.463054,-0.0211 -0.778761,0.29527 -1.178677,1.37091 -0.147339,0.52727 -0.08418,0.464 -0.399908,0.86473 -0.252577,0.82254 -0.126285,0.88582 v 0.18982 l -0.610384,0.92799 -0.336761,0.69601 v 0.99126 l 0.10523,2.04581 0.189431,0.73821 0.378861,0.6749 0.4841,0.44292 0.210477,0.8647 0.799815,1.77155 0.947154,1.13886 0.06311,0.0633 0.336761,1.70835 -1.157623,2.10911 -0.363408,0.28169 -0.272426,0.22054 -0.467179,-0.0129 -0.260603,0.13452 -0.316122,0.0134 -0.190994,0.19047 -0.408963,-0.0616 -0.404409,-0.0196 -0.3119827,0.24606 -0.04492,0.36556 0.2073657,-0.23472 0.348864,-0.10158 0.03654,0.33552 0.551268,-0.18726 0.316122,-0.0134 0.557721,0.0667 c 0,0 0.483518,-0.0316 0.567216,-0.0407 0.0837,-0.009 0.597276,0.0411 0.597276,0.0411 l 0.376644,-0.041 0.199723,0.0844 0.267456,-0.0716 0.290265,0.13818 0.418488,-0.0455 0.05401,-0.28169 -0.01596,-0.14678 0.28571,0.0962 0.232049,0.18692 0.150642,0.21701 -0.0088,-0.27495 -0.182953,-0.31946 c 0,0 -0.125159,-0.17737 -0.208857,-0.16824 -0.0837,0.009 -0.408971,-0.0616 -0.408971,-0.0616 l -0.223321,0.088 -0.243857,-0.10085 -0.239677,0.13226 -0.220665,-0.0822 -0.106491,-0.20049 -0.153322,-0.0471 0.164763,-0.42962 0.378861,-0.52859 0.505146,-0.84364 0.442,-0.5273 0.168385,-0.21088 c 0,0 0.210477,-0.31633 0.210477,-0.40071 0,-0.0844 0.04212,-0.97016 0.04212,-0.97016 l -0.273623,-0.56947 -0.126285,-0.46402 2.441546,0.59059 h 0.147331 l 2.083731,1.49744 0.294669,0.37959 0.10523,1.49729 -0.04212,0.33746 -0.147338,0.27419 -0.105358,0.16875 -0.09101,0.23675 -0.06215,0.1583 -0.386712,0.11988 -0.393086,-0.0709 -0.238209,-0.0294 -0.437428,0.0633 -0.184118,0.19713 -0.07084,0.24475 0.05712,0.24744 0.09172,-0.24769 0.124641,-0.16724 0.121984,-0.0388 0.0094,0.21174 0.163957,0.10406 0.338995,-0.15563 0.166646,-0.0242 0.05409,0.22658 0.508999,-0.01 0.345035,-0.11394 0.282886,0.0443 0.410893,0.047 0.142823,-0.0417 0.112881,-0.10156 0.182442,0.37841 0.0024,-0.42654 -0.176722,-0.48581 -0.256358,-0.15463 0.04324,-0.12712 -0.189423,-0.35849 0.02106,-1.37079 1.052392,1.2444 1.389154,1.09674 c 0,0 0.947146,0.33749 1.03133,0.33749 0.08419,0 0.736677,-0.0844 0.736677,-0.0844 l 0.420954,-0.69598 -0.315715,-0.94908 0.126284,-0.31642 -0.673531,-0.88574 -1.426794,-1.45279 -0.341213,-1.14141 0.02106,-0.78037 -0.315707,-0.82257 -0.357816,-0.65376 0.210477,-0.65377 -0.168377,-0.94904 -0.717785,-1.61568 0.265151,-0.0448 -0.444984,-0.77034 0.289101,-0.0918 -0.508218,-0.82847 0.213764,-0.11524 -0.380944,-0.62543 0.124202,-0.0696 -0.460971,-0.37337 0.168384,-0.0875 -1.199723,-0.84051 z"
+ id="path5892"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csccccccccccccccccccccccccccccccccccccccccccccccccccccccscccccccccccccsccccccccccccscccccccccccccccccccccccccccccccccccccccccccscccccccccccccccccccccccc" />
+ </g>
+</svg>
diff --git a/gui/resources/location.svg b/gui/resources/location.svg
new file mode 100644
index 0000000..f738eb6
--- /dev/null
+++ b/gui/resources/location.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="31.603px" height="31.603px" viewBox="0 0 31.603 31.603" style="enable-background:new 0 0 31.603 31.603;"
+ xml:space="preserve">
+<g>
+ <g>
+ <path d="M7.703,15.973c0,0,5.651-5.625,5.651-10.321C13.354,2.53,10.824,0,7.703,0S2.052,2.53,2.052,5.652
+ C2.052,10.614,7.703,15.973,7.703,15.973z M4.758,5.652c0-1.628,1.319-2.946,2.945-2.946s2.945,1.318,2.945,2.946
+ c0,1.626-1.319,2.944-2.945,2.944S4.758,7.278,4.758,5.652z"/>
+ <path d="M28.59,7.643l-0.459,0.146l-2.455,0.219l-0.692,1.106l-0.501-0.16l-1.953-1.76l-0.285-0.915l-0.377-0.977L20.639,4.2
+ l-1.446-0.283L19.159,4.58l1.418,1.384l0.694,0.817l-0.782,0.408l-0.636-0.188l-0.951-0.396l0.033-0.769l-1.25-0.514L17.27,7.126
+ l-1.258,0.286l0.125,1.007l1.638,0.316l0.284-1.609l1.353,0.201l0.629,0.368h1.011l0.69,1.384l1.833,1.859l-0.134,0.723
+ l-1.478-0.189l-2.553,1.289l-1.838,2.205l-0.239,0.976h-0.661l-1.229-0.566l-1.194,0.566l0.297,1.261l0.52-0.602l0.913-0.027
+ l-0.064,1.132l0.757,0.22l0.756,0.85l1.234-0.347l1.41,0.222l1.636,0.441l0.819,0.095l1.384,1.573l2.675,1.574l-1.729,3.306
+ l-1.826,0.849l-0.693,1.889l-2.643,1.765l-0.282,1.019c6.753-1.627,11.779-7.693,11.779-14.95
+ C31.194,13.038,30.234,10.09,28.59,7.643z"/>
+ <path d="M17.573,24.253l-1.12-2.078l1.028-2.146l-1.028-0.311l-1.156-1.159l-2.56-0.573l-0.85-1.779v1.057h-0.375l-1.625-2.203
+ c-0.793,0.949-1.395,1.555-1.47,1.629L7.72,17.384l-0.713-0.677c-0.183-0.176-3.458-3.315-5.077-7.13
+ c-0.966,2.009-1.52,4.252-1.52,6.63c0,8.502,6.891,15.396,15.393,15.396c0.654,0,1.296-0.057,1.931-0.135l-0.161-1.864
+ c0,0,0.707-2.77,0.707-2.863C18.28,26.646,17.573,24.253,17.573,24.253z"/>
+ <path d="M14.586,3.768l1.133,0.187l2.75-0.258l0.756-0.834l1.068-0.714l1.512,0.228l0.551-0.083
+ c-1.991-0.937-4.207-1.479-6.553-1.479c-1.096,0-2.16,0.128-3.191,0.345c0.801,0.875,1.377,1.958,1.622,3.163L14.586,3.768z
+ M16.453,2.343l1.573-0.865l1.009,0.582l-1.462,1.113l-1.394,0.141L15.55,2.907L16.453,2.343z"/>
+ </g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/gui/resources/power.svg b/gui/resources/power.svg
new file mode 100644
index 0000000..158bc3c
--- /dev/null
+++ b/gui/resources/power.svg
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 512 512">
+ <g>
+ <g>
+ <path d="m256,501c-120.6,0-218.7-98.1-218.7-218.7 0-91.6 57.9-174.1 144.2-205.4 10.6-3.9 22.3,1.6 26.2,12.2 3.8,10.6-1.6,22.3-12.2,26.2-70.1,25.5-117.3,92.6-117.3,167 0,98.1 79.8,177.8 177.8,177.8 98.1,0 177.8-79.8 177.8-177.8 0-74.4-47.1-141.6-117.3-167-10.6-3.8-16.1-15.6-12.2-26.2 3.8-10.6 15.6-16.1 26.2-12.2 86.2,31.3 144.2,113.8 144.2,205.4 0,120.6-98.1,218.7-218.7,218.7z"/>
+ <path d="m256,308.8c-11.3,0-20.4-9.1-20.4-20.4v-257c0-11.3 9.1-20.4 20.4-20.4 11.3,0 20.4,9.1 20.4,20.4v256.9c0,11.3-9.1,20.5-20.4,20.5z"/>
+ </g>
+ </g>
+</svg>
diff --git a/gui/resources/quit.svg b/gui/resources/quit.svg
new file mode 100644
index 0000000..2fbd520
--- /dev/null
+++ b/gui/resources/quit.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" class="icon">
+ <path d="M705.6 124.9a8 8 0 0 0-11.6 7.2v64.2c0 5.5 2.9 10.6 7.5 13.6a352.2 352.2 0 0 1 62.2 49.8c32.7 32.8 58.4 70.9 76.3 113.3a355 355 0 0 1 27.9 138.7c0 48.1-9.4 94.8-27.9 138.7a355.92 355.92 0 0 1-76.3 113.3 353.06 353.06 0 0 1-113.2 76.4c-43.8 18.6-90.5 28-138.5 28s-94.7-9.4-138.5-28a353.06 353.06 0 0 1-113.2-76.4A355.92 355.92 0 0 1 184 650.4a355 355 0 0 1-27.9-138.7c0-48.1 9.4-94.8 27.9-138.7 17.9-42.4 43.6-80.5 76.3-113.3 19-19 39.8-35.6 62.2-49.8 4.7-2.9 7.5-8.1 7.5-13.6V132c0-6-6.3-9.8-11.6-7.2C178.5 195.2 82 339.3 80 506.3 77.2 745.1 272.5 943.5 511.2 944c239 .5 432.8-193.3 432.8-432.4 0-169.2-97-315.7-238.4-386.7zM480 560h64c4.4 0 8-3.6 8-8V88c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v464c0 4.4 3.6 8 8 8z"/>
+</svg>
diff --git a/gui/resources/reception-0.svg b/gui/resources/reception-0.svg
new file mode 100644
index 0000000..885bf3b
--- /dev/null
+++ b/gui/resources/reception-0.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-reception-0" viewBox="0 0 16 16">
+ <path d="M0 13.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zm4 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zm4 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zm4 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/>
+</svg> \ No newline at end of file
diff --git a/gui/resources/reception-2.svg b/gui/resources/reception-2.svg
new file mode 100644
index 0000000..7dca57a
--- /dev/null
+++ b/gui/resources/reception-2.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-reception-2" viewBox="0 0 16 16">
+ <path d="M0 11.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2zm4-3a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-5zm4 5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zm4 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/>
+</svg> \ No newline at end of file
diff --git a/gui/resources/reception-4.svg b/gui/resources/reception-4.svg
new file mode 100644
index 0000000..611bdf1
--- /dev/null
+++ b/gui/resources/reception-4.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-reception-4" viewBox="0 0 16 16">
+ <path d="M0 11.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2zm4-3a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-5zm4-3a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-8zm4-3a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-11z"/>
+</svg> \ No newline at end of file
diff --git a/gui/resources/riseup-icon.svg b/gui/resources/riseup-icon.svg
new file mode 100644
index 0000000..a19c6c6
--- /dev/null
+++ b/gui/resources/riseup-icon.svg
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="128"
+ height="128"
+ viewBox="0 0 33.866666 33.866668"
+ version="1.1"
+ id="svg896"
+ inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
+ sodipodi:docname="riseupvpn-launcher.svg">
+ <defs
+ id="defs890" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#d7d7d7"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.12"
+ inkscape:cx="26.899914"
+ inkscape:cy="56.22909"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ units="px"
+ inkscape:window-width="1869"
+ inkscape:window-height="1025"
+ inkscape:window-x="51"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid1471"
+ empspacing="4" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata893">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-263.13332)">
+ <path
+ style="fill:#006cb2;fill-opacity:1;stroke-width:0.26458335"
+ d="M 15.617988,295.66418 C 9.9401302,292.50672 5.6959906,287.52131 4.7342129,282.87951 4.4622971,281.56718 4.4516145,281.24899 4.4519256,274.47112 l 2.394e-4,-6.47461 0.5616386,-0.36177 c 2.3734615,-1.52883 4.8841786,-2.55103 7.4419094,-3.06699 5.221864,-1.05337 10.639669,0.003 15.485448,3.16684 l 0.398232,0.25998 v 6.50684 c 0,6.8044 -0.01093,7.13134 -0.280843,8.43104 -0.956329,4.6043 -4.632206,9.09553 -10.057782,12.28874 -1.576255,0.9277 -1.527525,0.91863 -2.382844,0.44299 z"
+ id="path5137"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csccssscssscc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#007cc3;fill-opacity:1;stroke-width:0.26458335"
+ d="m 16.396917,264.17336 c -1.317958,-4.7e-4 -2.635612,0.13103 -3.941076,0.39437 -2.5577318,0.51596 -5.068528,1.53812 -7.4419895,3.06696 l -0.5615828,0.36177 -4.707e-4,6.47466 c -2.394e-4,6.77787 0.010371,7.096 0.2822554,8.40832 0.9617776,4.64181 5.2059168,9.62724 10.8837756,12.7847 h 1.19e-4 c 0.368953,0.20517 0.569745,0.32326 0.805249,0.31706 v -31.80747 c -0.0088,-2e-5 -0.01755,-4.8e-4 -0.02641,-4.8e-4 z"
+ id="path887" />
+ <path
+ style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458338"
+ d="m 16.989143,272.54844 c 0,0 -0.315707,-0.61165 -0.336761,-0.69601 -0.02106,-0.0844 -0.189431,-0.94909 -0.189431,-0.94909 l 0.210485,-0.37963 0.463046,-0.696 0.673531,-0.52727 0.168377,-0.0633 1.620684,-0.61164 0.147331,-0.14764 -0.168385,-0.25308 -0.231523,-0.12655 -0.424879,-0.10907 -1.064407,-0.10907 -0.825967,0.007 -0.568293,0.12654 -0.505146,-0.18981 -0.63143,-0.0844 -0.9261,-0.0211 -0.463054,-0.0211 -0.778761,0.29527 -1.178677,1.37091 -0.147339,0.52727 -0.08418,0.464 -0.399908,0.86473 -0.252577,0.82254 -0.126285,0.88582 v 0.18982 l -0.610384,0.92799 -0.336761,0.69601 v 0.99126 l 0.10523,2.04581 0.189431,0.73821 0.378861,0.6749 0.4841,0.44292 0.210477,0.8647 0.799815,1.77155 0.947154,1.13886 0.06311,0.0633 0.336761,1.70835 -1.157623,2.10911 -0.363408,0.28169 -0.272426,0.22054 -0.467179,-0.0129 -0.260603,0.13452 -0.316122,0.0134 -0.190994,0.19047 -0.408963,-0.0616 -0.4044091,-0.0196 -0.311982,0.24606 -0.044916,0.36556 0.207365,-0.23472 0.3488649,-0.10158 0.03654,0.33552 0.5512672,-0.18726 0.316122,-0.0134 0.557721,0.0667 c 0,0 0.483518,-0.0316 0.567216,-0.0407 0.0837,-0.009 0.597276,0.0411 0.597276,0.0411 l 0.376644,-0.041 0.199723,0.0844 0.267456,-0.0716 0.290265,0.13818 0.418488,-0.0455 0.05401,-0.28169 -0.01596,-0.14678 0.28571,0.0962 0.232049,0.18692 0.150642,0.21701 -0.0088,-0.27495 -0.182953,-0.31946 c 0,0 -0.125159,-0.17737 -0.208857,-0.16824 -0.0837,0.009 -0.408971,-0.0616 -0.408971,-0.0616 l -0.223321,0.088 -0.243857,-0.10085 -0.239677,0.13226 -0.220665,-0.0822 -0.106491,-0.20049 -0.153322,-0.0471 0.164763,-0.42962 0.378861,-0.52859 0.505146,-0.84364 0.442,-0.5273 0.168385,-0.21088 c 0,0 0.210477,-0.31633 0.210477,-0.40071 0,-0.0844 0.04212,-0.97016 0.04212,-0.97016 l -0.273623,-0.56947 -0.126285,-0.46402 2.441546,0.59059 h 0.147331 l 2.083731,1.49744 0.294669,0.37959 0.10523,1.49729 -0.04212,0.33746 -0.147338,0.27419 -0.105358,0.16875 -0.09101,0.23675 -0.06215,0.1583 -0.386712,0.11988 -0.393086,-0.0709 -0.238209,-0.0294 -0.437428,0.0633 -0.184118,0.19713 -0.07084,0.24475 0.05712,0.24744 0.09172,-0.24769 0.124641,-0.16724 0.121984,-0.0388 0.0094,0.21174 0.163957,0.10406 0.338995,-0.15563 0.166646,-0.0242 0.05409,0.22658 0.508999,-0.01 0.345035,-0.11394 0.282886,0.0443 0.410893,0.047 0.142823,-0.0417 0.112881,-0.10156 0.182442,0.37841 0.0024,-0.42654 -0.176722,-0.48581 -0.256358,-0.15463 0.04324,-0.12712 -0.189423,-0.35849 0.02106,-1.37079 1.052392,1.2444 1.389154,1.09674 c 0,0 0.947146,0.33749 1.03133,0.33749 0.08419,0 0.736677,-0.0844 0.736677,-0.0844 l 0.420954,-0.69598 -0.315715,-0.94908 0.126284,-0.31642 -0.673531,-0.88574 -1.426794,-1.45279 -0.341213,-1.14141 0.02106,-0.78037 -0.315707,-0.82257 -0.357816,-0.65376 0.210477,-0.65377 -0.168377,-0.94904 -0.717785,-1.61568 0.265151,-0.0448 -0.444984,-0.77034 0.289101,-0.0918 -0.508218,-0.82847 0.213764,-0.11524 -0.380944,-0.62543 0.124202,-0.0696 -0.460971,-0.37337 0.168384,-0.0875 -1.199723,-0.84051 z"
+ id="path5892"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csccccccccccccccccccccccccccccccccccccccccccccccccccccccscccccccccccccsccccccccccccscccccccccccccccccccccccccccccccccccccccccccscccccccccccccccccccccccc" />
+ </g>
+</svg>
diff --git a/gui/resources/riseup-logo.png b/gui/resources/riseup-logo.png
new file mode 100644
index 0000000..e68cfe2
--- /dev/null
+++ b/gui/resources/riseup-logo.png
Binary files differ
diff --git a/gui/resources/settings.svg b/gui/resources/settings.svg
new file mode 100644
index 0000000..0469ed4
--- /dev/null
+++ b/gui/resources/settings.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 478.703 478.703" style="enable-background:new 0 0 478.703 478.703;" xml:space="preserve">
+<g>
+ <g>
+ <path d="M454.2,189.101l-33.6-5.7c-3.5-11.3-8-22.2-13.5-32.6l19.8-27.7c8.4-11.8,7.1-27.9-3.2-38.1l-29.8-29.8
+ c-5.6-5.6-13-8.7-20.9-8.7c-6.2,0-12.1,1.9-17.1,5.5l-27.8,19.8c-10.8-5.7-22.1-10.4-33.8-13.9l-5.6-33.2
+ c-2.4-14.3-14.7-24.7-29.2-24.7h-42.1c-14.5,0-26.8,10.4-29.2,24.7l-5.8,34c-11.2,3.5-22.1,8.1-32.5,13.7l-27.5-19.8
+ c-5-3.6-11-5.5-17.2-5.5c-7.9,0-15.4,3.1-20.9,8.7l-29.9,29.8c-10.2,10.2-11.6,26.3-3.2,38.1l20,28.1
+ c-5.5,10.5-9.9,21.4-13.3,32.7l-33.2,5.6c-14.3,2.4-24.7,14.7-24.7,29.2v42.1c0,14.5,10.4,26.8,24.7,29.2l34,5.8
+ c3.5,11.2,8.1,22.1,13.7,32.5l-19.7,27.4c-8.4,11.8-7.1,27.9,3.2,38.1l29.8,29.8c5.6,5.6,13,8.7,20.9,8.7c6.2,0,12.1-1.9,17.1-5.5
+ l28.1-20c10.1,5.3,20.7,9.6,31.6,13l5.6,33.6c2.4,14.3,14.7,24.7,29.2,24.7h42.2c14.5,0,26.8-10.4,29.2-24.7l5.7-33.6
+ c11.3-3.5,22.2-8,32.6-13.5l27.7,19.8c5,3.6,11,5.5,17.2,5.5l0,0c7.9,0,15.3-3.1,20.9-8.7l29.8-29.8c10.2-10.2,11.6-26.3,3.2-38.1
+ l-19.8-27.8c5.5-10.5,10.1-21.4,13.5-32.6l33.6-5.6c14.3-2.4,24.7-14.7,24.7-29.2v-42.1
+ C478.9,203.801,468.5,191.501,454.2,189.101z M451.9,260.401c0,1.3-0.9,2.4-2.2,2.6l-42,7c-5.3,0.9-9.5,4.8-10.8,9.9
+ c-3.8,14.7-9.6,28.8-17.4,41.9c-2.7,4.6-2.5,10.3,0.6,14.7l24.7,34.8c0.7,1,0.6,2.5-0.3,3.4l-29.8,29.8c-0.7,0.7-1.4,0.8-1.9,0.8
+ c-0.6,0-1.1-0.2-1.5-0.5l-34.7-24.7c-4.3-3.1-10.1-3.3-14.7-0.6c-13.1,7.8-27.2,13.6-41.9,17.4c-5.2,1.3-9.1,5.6-9.9,10.8l-7.1,42
+ c-0.2,1.3-1.3,2.2-2.6,2.2h-42.1c-1.3,0-2.4-0.9-2.6-2.2l-7-42c-0.9-5.3-4.8-9.5-9.9-10.8c-14.3-3.7-28.1-9.4-41-16.8
+ c-2.1-1.2-4.5-1.8-6.8-1.8c-2.7,0-5.5,0.8-7.8,2.5l-35,24.9c-0.5,0.3-1,0.5-1.5,0.5c-0.4,0-1.2-0.1-1.9-0.8l-29.8-29.8
+ c-0.9-0.9-1-2.3-0.3-3.4l24.6-34.5c3.1-4.4,3.3-10.2,0.6-14.8c-7.8-13-13.8-27.1-17.6-41.8c-1.4-5.1-5.6-9-10.8-9.9l-42.3-7.2
+ c-1.3-0.2-2.2-1.3-2.2-2.6v-42.1c0-1.3,0.9-2.4,2.2-2.6l41.7-7c5.3-0.9,9.6-4.8,10.9-10c3.7-14.7,9.4-28.9,17.1-42
+ c2.7-4.6,2.4-10.3-0.7-14.6l-24.9-35c-0.7-1-0.6-2.5,0.3-3.4l29.8-29.8c0.7-0.7,1.4-0.8,1.9-0.8c0.6,0,1.1,0.2,1.5,0.5l34.5,24.6
+ c4.4,3.1,10.2,3.3,14.8,0.6c13-7.8,27.1-13.8,41.8-17.6c5.1-1.4,9-5.6,9.9-10.8l7.2-42.3c0.2-1.3,1.3-2.2,2.6-2.2h42.1
+ c1.3,0,2.4,0.9,2.6,2.2l7,41.7c0.9,5.3,4.8,9.6,10,10.9c15.1,3.8,29.5,9.7,42.9,17.6c4.6,2.7,10.3,2.5,14.7-0.6l34.5-24.8
+ c0.5-0.3,1-0.5,1.5-0.5c0.4,0,1.2,0.1,1.9,0.8l29.8,29.8c0.9,0.9,1,2.3,0.3,3.4l-24.7,34.7c-3.1,4.3-3.3,10.1-0.6,14.7
+ c7.8,13.1,13.6,27.2,17.4,41.9c1.3,5.2,5.6,9.1,10.8,9.9l42,7.1c1.3,0.2,2.2,1.3,2.2,2.6v42.1H451.9z"/>
+ <path d="M239.4,136.001c-57,0-103.3,46.3-103.3,103.3s46.3,103.3,103.3,103.3s103.3-46.3,103.3-103.3S296.4,136.001,239.4,136.001
+ z M239.4,315.601c-42.1,0-76.3-34.2-76.3-76.3s34.2-76.3,76.3-76.3s76.3,34.2,76.3,76.3S281.5,315.601,239.4,315.601z"/>
+ </g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/gui/resources/speed-green.svg b/gui/resources/speed-green.svg
new file mode 100644
index 0000000..a25de12
--- /dev/null
+++ b/gui/resources/speed-green.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 512.001 512.001" style="enable-background:new 0 0 512.001 512.001;" xml:space="preserve">
+<polyline style="fill:#CFF09E;" points="233.225,420.726 270.89,383.061 288.971,401.143 310.88,303.847 213.584,325.756
+ 231.665,343.837 174.547,400.955 "/>
+<g>
+ <path style="fill:#507C5C;" d="M233.226,435.168c-3.697,0-7.391-1.41-10.212-4.23c-5.64-5.64-5.64-14.784,0-20.424l37.663-37.665
+ c5.489-5.488,14.293-5.635,19.962-0.443l11.135-49.454l-49.452,11.135c2.431,2.656,3.787,6.132,3.787,9.748
+ c0,3.83-1.522,7.504-4.23,10.212l-57.119,57.118c-5.641,5.64-14.783,5.641-20.425,0c-5.64-5.64-5.64-14.784,0-20.424l46.906-46.906
+ l-7.869-7.869c-3.722-3.722-5.124-9.175-3.66-14.231c1.466-5.056,5.564-8.915,10.7-10.072l97.296-21.909
+ c4.824-1.086,9.881,0.375,13.385,3.878c3.502,3.502,4.965,8.554,3.878,13.385l-21.908,97.296
+ c-1.157,5.136-5.016,9.234-10.072,10.699c-5.058,1.464-10.508,0.062-14.231-3.66l-7.869-7.869l-27.451,27.453
+ C240.618,433.758,236.922,435.168,233.226,435.168z"/>
+ <path style="fill:#507C5C;" d="M256,512.001c-141.159,0-256-114.841-256-256s114.841-256,256-256
+ c53.115,0,104.062,16.122,147.333,46.62c6.519,4.595,8.079,13.606,3.483,20.125c-4.595,6.519-13.607,8.08-20.125,3.485
+ C348.318,43.181,303.126,28.885,256,28.885c-125.232,0-227.116,101.884-227.116,227.116S130.768,483.117,256,483.117
+ s227.116-101.884,227.116-227.116c0-34.43-7.503-67.493-22.3-98.275c-3.456-7.189-0.43-15.817,6.759-19.273
+ c7.191-3.453,15.817-0.429,19.274,6.759c16.691,34.719,25.152,71.993,25.152,110.789C512,397.16,397.159,512.001,256,512.001z"/>
+ <path style="fill:#507C5C;" d="M230.725,434.831c-0.728,0-1.464-0.055-2.205-0.169c-42.217-6.469-80.975-27.931-109.135-60.43
+ c-28.458-32.843-44.132-74.831-44.132-118.232c0-99.664,81.083-180.747,180.747-180.747s180.747,81.083,180.747,180.747
+ c0,7.976-6.467,14.442-14.442,14.442c-7.975,0-14.442-6.466-14.442-14.442c0-83.737-68.126-151.863-151.863-151.863
+ s-151.863,68.126-151.863,151.863c0,74.329,55.353,138.863,128.757,150.11c7.884,1.209,13.295,8.579,12.088,16.462
+ C243.889,429.718,237.735,434.831,230.725,434.831z"/>
+</g>
+<path style="fill:#CFF09E;" d="M256,164.947c50.208,0,91.054,40.846,91.054,91.054h75.253c0-91.701-74.604-166.305-166.305-166.305
+ S89.695,164.3,89.695,256.001h75.253C164.946,205.793,205.792,164.947,256,164.947z"/>
+<g>
+ <path style="fill:#507C5C;" d="M422.334,270.443c-0.007,0-0.017,0-0.029,0h-75.251c-7.975,0-14.442-6.466-14.442-14.442
+ c0-42.243-34.368-76.612-76.612-76.612s-76.612,34.368-76.612,76.612c0,7.976-6.467,14.442-14.442,14.442H89.695
+ c-7.975,0-14.442-6.466-14.442-14.442c0-99.664,81.083-180.747,180.747-180.747c99.342,0,180.223,80.566,180.744,179.788
+ c0.022,0.318,0.03,0.637,0.03,0.96C436.776,263.977,430.31,270.443,422.334,270.443z M360.511,241.559h46.671
+ c-7.298-76.989-72.315-137.421-151.183-137.421S112.113,164.57,104.816,241.559h46.671c7.065-51.36,51.24-91.054,104.511-91.054
+ S353.447,190.199,360.511,241.559z"/>
+ <path style="fill:#507C5C;" d="M216.293,187.292c-3.238,0-6.496-1.083-9.188-3.309l-59.817-49.406
+ c-6.149-5.079-7.017-14.182-1.938-20.331c5.079-6.149,14.181-7.019,20.331-1.938l59.817,49.406
+ c6.149,5.079,7.017,14.182,1.938,20.331C224.58,185.502,220.453,187.292,216.293,187.292z"/>
+ <path style="fill:#507C5C;" d="M295.707,187.292c-4.161,0-8.287-1.788-11.143-5.245c-5.081-6.149-4.211-15.252,1.938-20.331
+ l59.817-49.406c6.151-5.081,15.252-4.211,20.331,1.938c5.081,6.149,4.211,15.252-1.938,20.331l-59.817,49.406
+ C302.203,186.207,298.946,187.292,295.707,187.292z"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/gui/resources/speed-red.svg b/gui/resources/speed-red.svg
new file mode 100644
index 0000000..8ceb858
--- /dev/null
+++ b/gui/resources/speed-red.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 512.001 512.001" style="enable-background:new 0 0 512.001 512.001;" xml:space="preserve">
+<polyline style="fill:#F4B2B0;" points="233.225,420.726 270.89,383.061 288.971,401.143 310.88,303.847 213.584,325.756
+ 231.665,343.837 174.547,400.955 "/>
+<g>
+ <path style="fill:#B3404A;" d="M233.226,435.168c-3.697,0-7.391-1.41-10.212-4.23c-5.64-5.64-5.64-14.784,0-20.424l37.663-37.665
+ c5.489-5.488,14.293-5.635,19.962-0.443l11.135-49.454l-49.452,11.135c2.431,2.656,3.787,6.132,3.787,9.748
+ c0,3.83-1.522,7.504-4.23,10.212l-57.119,57.118c-5.641,5.64-14.783,5.641-20.425,0c-5.64-5.64-5.64-14.784,0-20.424l46.906-46.906
+ l-7.869-7.869c-3.722-3.722-5.124-9.175-3.66-14.231c1.466-5.056,5.564-8.915,10.7-10.072l97.296-21.909
+ c4.824-1.086,9.881,0.375,13.385,3.878c3.502,3.502,4.965,8.554,3.878,13.385l-21.908,97.296
+ c-1.157,5.136-5.016,9.234-10.072,10.699c-5.058,1.464-10.508,0.062-14.231-3.66l-7.869-7.869l-27.451,27.453
+ C240.618,433.758,236.922,435.168,233.226,435.168z"/>
+ <path style="fill:#B3404A;" d="M256,512.001c-141.159,0-256-114.841-256-256s114.841-256,256-256
+ c53.115,0,104.062,16.122,147.333,46.62c6.519,4.595,8.079,13.606,3.483,20.125c-4.595,6.519-13.607,8.08-20.125,3.485
+ C348.318,43.181,303.126,28.885,256,28.885c-125.232,0-227.116,101.884-227.116,227.116S130.768,483.117,256,483.117
+ s227.116-101.884,227.116-227.116c0-34.43-7.503-67.493-22.3-98.275c-3.456-7.189-0.43-15.817,6.759-19.273
+ c7.191-3.453,15.817-0.429,19.274,6.759c16.691,34.719,25.152,71.993,25.152,110.789C512,397.16,397.159,512.001,256,512.001z"/>
+ <path style="fill:#B3404A;" d="M230.725,434.831c-0.728,0-1.464-0.055-2.205-0.169c-42.217-6.469-80.975-27.931-109.135-60.43
+ c-28.458-32.843-44.132-74.831-44.132-118.232c0-99.664,81.083-180.747,180.747-180.747s180.747,81.083,180.747,180.747
+ c0,7.976-6.467,14.442-14.442,14.442c-7.975,0-14.442-6.466-14.442-14.442c0-83.737-68.126-151.863-151.863-151.863
+ s-151.863,68.126-151.863,151.863c0,74.329,55.353,138.863,128.757,150.11c7.884,1.209,13.295,8.579,12.088,16.462
+ C243.889,429.718,237.735,434.831,230.725,434.831z"/>
+</g>
+<path style="fill:#F4B2B0;" d="M256,164.947c50.208,0,91.054,40.846,91.054,91.054h75.253c0-91.701-74.604-166.305-166.305-166.305
+ S89.695,164.3,89.695,256.001h75.253C164.946,205.793,205.792,164.947,256,164.947z"/>
+<g>
+ <path style="fill:#B3404A;" d="M422.334,270.443c-0.007,0-0.017,0-0.029,0h-75.251c-7.975,0-14.442-6.466-14.442-14.442
+ c0-42.243-34.368-76.612-76.612-76.612s-76.612,34.368-76.612,76.612c0,7.976-6.467,14.442-14.442,14.442H89.695
+ c-7.975,0-14.442-6.466-14.442-14.442c0-99.664,81.083-180.747,180.747-180.747c99.342,0,180.223,80.566,180.744,179.788
+ c0.022,0.318,0.03,0.637,0.03,0.96C436.776,263.977,430.31,270.443,422.334,270.443z M360.511,241.559h46.671
+ c-7.298-76.989-72.315-137.421-151.183-137.421S112.113,164.57,104.816,241.559h46.671c7.065-51.36,51.24-91.054,104.511-91.054
+ S353.447,190.199,360.511,241.559z"/>
+ <path style="fill:#B3404A;" d="M216.293,187.292c-3.238,0-6.496-1.083-9.188-3.309l-59.817-49.406
+ c-6.149-5.079-7.017-14.182-1.938-20.331c5.079-6.149,14.181-7.019,20.331-1.938l59.817,49.406
+ c6.149,5.079,7.017,14.182,1.938,20.331C224.58,185.502,220.453,187.292,216.293,187.292z"/>
+ <path style="fill:#B3404A;" d="M295.707,187.292c-4.161,0-8.287-1.788-11.143-5.245c-5.081-6.149-4.211-15.252,1.938-20.331
+ l59.817-49.406c6.151-5.081,15.252-4.211,20.331,1.938c5.081,6.149,4.211,15.252-1.938,20.331l-59.817,49.406
+ C302.203,186.207,298.946,187.292,295.707,187.292z"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/gui/resources/speed-yellow.svg b/gui/resources/speed-yellow.svg
new file mode 100644
index 0000000..377a2d2
--- /dev/null
+++ b/gui/resources/speed-yellow.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 512.001 512.001" style="enable-background:new 0 0 512.001 512.001;" xml:space="preserve">
+<polyline style="fill:#FEE187;" points="233.225,420.726 270.89,383.061 288.971,401.143 310.88,303.847 213.584,325.756
+ 231.665,343.837 174.547,400.955 "/>
+<g>
+ <path style="fill:#FFC61B;" d="M233.226,435.168c-3.697,0-7.391-1.41-10.212-4.23c-5.64-5.64-5.64-14.784,0-20.424l37.663-37.665
+ c5.489-5.488,14.293-5.635,19.962-0.443l11.135-49.454l-49.452,11.135c2.431,2.656,3.787,6.132,3.787,9.748
+ c0,3.83-1.522,7.504-4.23,10.212l-57.119,57.118c-5.641,5.64-14.783,5.641-20.425,0c-5.64-5.64-5.64-14.784,0-20.424l46.906-46.906
+ l-7.869-7.869c-3.722-3.722-5.124-9.175-3.66-14.231c1.466-5.056,5.564-8.915,10.7-10.072l97.296-21.909
+ c4.824-1.086,9.881,0.375,13.385,3.878c3.502,3.502,4.965,8.554,3.878,13.385l-21.908,97.296
+ c-1.157,5.136-5.016,9.234-10.072,10.699c-5.058,1.464-10.508,0.062-14.231-3.66l-7.869-7.869l-27.451,27.453
+ C240.618,433.758,236.922,435.168,233.226,435.168z"/>
+ <path style="fill:#FFC61B;" d="M256,512.001c-141.159,0-256-114.841-256-256s114.841-256,256-256
+ c53.115,0,104.062,16.122,147.333,46.62c6.519,4.595,8.079,13.606,3.483,20.125c-4.595,6.519-13.607,8.08-20.125,3.485
+ C348.318,43.181,303.126,28.885,256,28.885c-125.232,0-227.116,101.884-227.116,227.116S130.768,483.117,256,483.117
+ s227.116-101.884,227.116-227.116c0-34.43-7.503-67.493-22.3-98.275c-3.456-7.189-0.43-15.817,6.759-19.273
+ c7.191-3.453,15.817-0.429,19.274,6.759c16.691,34.719,25.152,71.993,25.152,110.789C512,397.16,397.159,512.001,256,512.001z"/>
+ <path style="fill:#FFC61B;" d="M230.725,434.831c-0.728,0-1.464-0.055-2.205-0.169c-42.217-6.469-80.975-27.931-109.135-60.43
+ c-28.458-32.843-44.132-74.831-44.132-118.232c0-99.664,81.083-180.747,180.747-180.747s180.747,81.083,180.747,180.747
+ c0,7.976-6.467,14.442-14.442,14.442c-7.975,0-14.442-6.466-14.442-14.442c0-83.737-68.126-151.863-151.863-151.863
+ s-151.863,68.126-151.863,151.863c0,74.329,55.353,138.863,128.757,150.11c7.884,1.209,13.295,8.579,12.088,16.462
+ C243.889,429.718,237.735,434.831,230.725,434.831z"/>
+</g>
+<path style="fill:#FEE187;" d="M256,164.947c50.208,0,91.054,40.846,91.054,91.054h75.253c0-91.701-74.604-166.305-166.305-166.305
+ S89.695,164.3,89.695,256.001h75.253C164.946,205.793,205.792,164.947,256,164.947z"/>
+<g>
+ <path style="fill:#FFC61B;" d="M422.334,270.443c-0.007,0-0.017,0-0.029,0h-75.251c-7.975,0-14.442-6.466-14.442-14.442
+ c0-42.243-34.368-76.612-76.612-76.612s-76.612,34.368-76.612,76.612c0,7.976-6.467,14.442-14.442,14.442H89.695
+ c-7.975,0-14.442-6.466-14.442-14.442c0-99.664,81.083-180.747,180.747-180.747c99.342,0,180.223,80.566,180.744,179.788
+ c0.022,0.318,0.03,0.637,0.03,0.96C436.776,263.977,430.31,270.443,422.334,270.443z M360.511,241.559h46.671
+ c-7.298-76.989-72.315-137.421-151.183-137.421S112.113,164.57,104.816,241.559h46.671c7.065-51.36,51.24-91.054,104.511-91.054
+ S353.447,190.199,360.511,241.559z"/>
+ <path style="fill:#FFC61B;" d="M216.293,187.292c-3.238,0-6.496-1.083-9.188-3.309l-59.817-49.406
+ c-6.149-5.079-7.017-14.182-1.938-20.331c5.079-6.149,14.181-7.019,20.331-1.938l59.817,49.406
+ c6.149,5.079,7.017,14.182,1.938,20.331C224.58,185.502,220.453,187.292,216.293,187.292z"/>
+ <path style="fill:#FFC61B;" d="M295.707,187.292c-4.161,0-8.287-1.788-11.143-5.245c-5.081-6.149-4.211-15.252,1.938-20.331
+ l59.817-49.406c6.151-5.081,15.252-4.211,20.331,1.938c5.081,6.149,4.211,15.252-1.938,20.331l-59.817,49.406
+ C302.203,186.207,298.946,187.292,295.707,187.292z"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/gui/resources/spy.gif b/gui/resources/spy.gif
new file mode 100644
index 0000000..733d3eb
--- /dev/null
+++ b/gui/resources/spy.gif
Binary files differ
diff --git a/gui/resources/tools.svg b/gui/resources/tools.svg
new file mode 100644
index 0000000..41c63a1
--- /dev/null
+++ b/gui/resources/tools.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" class="icon">
+ <path d="M876.6 239.5c-.5-.9-1.2-1.8-2-2.5-5-5-13.1-5-18.1 0L684.2 409.3l-67.9-67.9L788.7 169c.8-.8 1.4-1.6 2-2.5 3.6-6.1 1.6-13.9-4.5-17.5-98.2-58-226.8-44.7-311.3 39.7-67 67-89.2 162-66.5 247.4l-293 293c-3 3-2.8 7.9.3 11l169.7 169.7c3.1 3.1 8.1 3.3 11 .3l292.9-292.9c85.5 22.8 180.5.7 247.6-66.4 84.4-84.5 97.7-213.1 39.7-311.3zM786 499.8c-58.1 58.1-145.3 69.3-214.6 33.6l-8.8 8.8-.1-.1-274 274.1-79.2-79.2 230.1-230.1s0 .1.1.1l52.8-52.8c-35.7-69.3-24.5-156.5 33.6-214.6a184.2 184.2 0 0 1 144-53.5L537 318.9a32.05 32.05 0 0 0 0 45.3l124.5 124.5a32.05 32.05 0 0 0 45.3 0l132.8-132.8c3.7 51.8-14.4 104.8-53.6 143.9z"/>
+</svg>
diff --git a/gui/themes/themes.js b/gui/themes/themes.js
new file mode 100644
index 0000000..265af9a
--- /dev/null
+++ b/gui/themes/themes.js
@@ -0,0 +1,51 @@
+.pragma library
+
+const borderColor = "#6D6D6E";
+const fontColor = "#6D6D6E";
+const fontColorDark = "#3D3D3D";
+const fontFamily = "Metropolis";
+const fontBoldFamily = "OxaniumBold";
+const fontInterFamily = "InterUI";
+const fontSize = 15;
+const fontSizeLarge = 22;
+const fontSizeSmall = 13;
+const fontSizeSmallest = 11;
+const fontWeightBold = 600;
+const iconSize = 16;
+const labelLineHeight = 22;
+const cityListTopMargin = 18;
+const controllerInterLineHeight = 18;
+const hSpacing = 20;
+const vSpacing = 24;
+const vSpacingSmall = 16;
+const listSpacing = 8;
+const maxTextWidth = 296;
+const windowMargin = 16;
+const popupMargin = 24;
+const desktopAppHeight = 520;
+const desktopAppWidth = 360;
+const darkFocusBorder = fontColor;
+const lightFocusBorder = "#d5d3e0";
+
+const accentOff = "#af0909";
+const accentConnecting = "#FFCC33";
+const accentOn = "#669933";
+
+const blue = "#0060DF";
+const blueHovered = "#0250BB";
+const bluePressed = "#054096";
+const blueDisabled = "#a3c0f3";
+const blueFocusOutline = "#4d0a84ff";
+const blueFocusBorder = "#0a84ff";
+
+const blueButton = {
+ "defaultColor" : blue,
+ "buttonHovered": blueHovered,
+ "buttonPressed": bluePressed,
+ "buttonDisabled": blueDisabled,
+ "focusBgColor": blue,
+ "focusOutline": blueFocusOutline,
+ "focusBorder": blueFocusBorder,
+};
+
+const bgColor = "white";