diff --git a/CMakeLists.txt b/CMakeLists.txt index 6744c48..2c5534c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,10 +54,6 @@ set(SRCS src/touchpad.cpp src/touchscreen.cpp src/shortcuts/shortcutsmodel.cpp - src/vpn/vpn.cpp - src/vpn/nm-l2tp-service.h - src/vpn/nm-openvpn-service.h - src/vpn/nm-pptp-service.h src/networkproxy.cpp src/defaultapplications.cpp src/mimeapps.cpp diff --git a/src/qml/SideBar.qml b/src/qml/SideBar.qml index b9f4357..7571b1a 100644 --- a/src/qml/SideBar.qml +++ b/src/qml/SideBar.qml @@ -224,14 +224,15 @@ Item { // category: qsTr("Network") // } -// ListElement { -// title: qsTr("VPN") -// name: "vpn" -// page: "qrc:/qml/VPN/Main.qml" -// iconSource: "bluetooth.svg" -// iconColor: "#0067FF" -// category: qsTr("Network") -// } + ListElement { + title: qsTr("VPN") + name: "vpn" + page: "qrc:/qml/VPN/Main.qml" + iconSource: "qrc:/fishui/kit/images/icons/network-vpn.svg" + iconColor: "#0067FF" + category: qsTr("Network") + keywords: qsTr("VPN, WireGuard, OpenVPN, tunnel, network") + } ListElement { title: qsTr("Proxy") diff --git a/src/qml/VPN/AddVpnDialog.qml b/src/qml/VPN/AddVpnDialog.qml new file mode 100644 index 0000000..5dda514 --- /dev/null +++ b/src/qml/VPN/AddVpnDialog.qml @@ -0,0 +1,511 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Layouts +import Cutefish.Settings 1.0 +import FishUI 1.0 as FishUI +import Cutefish.Network 1.0 as NM + +// Sets up a VPN from what the provider or the network's administrator hands out: the +// server and how to sign in. A password left empty is asked for when connecting. +FishUI.Window { + id: control + + required property NM.Handler handler + + windowButtonsTopMargin: Metrics.dialogWindowButtonsMargin + windowButtonsRightMargin: Metrics.dialogWindowButtonsMargin + header.height: 44 + contentTopMargin: 0 + + width: contentWidth + height: contentHeight + minimumWidth: contentWidth + minimumHeight: contentHeight + maximumWidth: contentWidth + maximumHeight: contentHeight + modality: Qt.WindowModal + + property int contentWidth: 440 + property int contentHeight: _mainLayout.implicitHeight + 32 + + title: qsTr("Add VPN") + visible: false + minimizeButtonVisible: false + + background.color: FishUI.Theme.secondBackgroundColor + // The background item reaches a grown window a frame or two after its buffer does. + color: background.color + flags: Qt.Dialog | Qt.FramelessWindowHint + + // Read again each time the dialog opens, so a plugin installed meanwhile shows up. + property var kinds: [] + readonly property var kindInfo: kinds[_kindBox.currentIndex] ?? ({}) + readonly property string kind: kindInfo.kind ?? "" + readonly property bool installed: kindInfo.installed ?? false + readonly property bool wireGuard: kind === "wireguard" + readonly property bool certificates: kind === "openvpn" && _authenticationBox.currentValue === "certificates" + readonly property bool usesUserName: installed && !wireGuard && !certificates + + property bool adding: false + property string error + + readonly property bool canAdd: installed && !adding && filled(_serverTextField) + && (wireGuard ? filled(_privateKeyTextField) && filled(_addressTextField) + && filled(_publicKeyTextField) + : certificates ? filled(_certificateField.field) && filled(_keyField.field) + : filled(_userNameTextField)) + + function filled(field) { + return field.text.trim().length > 0 + } + + function add() { + if (!canAdd) + return + + adding = true + error = "" + handler.addVpn({ + kind: kind, + name: _nameTextField.text, + server: _serverTextField.text, + userName: _userNameTextField.text, + password: _passwordTextField.text, + authentication: _authenticationBox.currentValue, + caFile: _caField.field.text, + certFile: _certificateField.field.text, + keyFile: _keyField.field.text, + keyPassword: _keyPasswordTextField.text, + presharedKey: _presharedKeyTextField.text, + privateKey: _privateKeyTextField.text, + publicKey: _publicKeyTextField.text, + address: _addressTextField.text, + allowedIps: _allowedIpsTextField.text, + dns: _dnsTextField.text + }) + } + + function chooseFile(field, title) { + _fileDialog.field = field + _fileDialog.title = title + _fileDialog.open() + } + + onVisibleChanged: { + if (visible) { + kinds = handler.vpnKinds().map(function(item) { + item.text = item.installed ? item.label : qsTr("%1 (Not Installed)").arg(item.label) + return item + }) + _kindBox.currentIndex = 0 + _serverTextField.forceActiveFocus() + return + } + + for (const field of [_nameTextField, _serverTextField, _userNameTextField, _passwordTextField, + _presharedKeyTextField, _caField.field, _certificateField.field, _keyField.field, + _keyPasswordTextField, _privateKeyTextField, _publicKeyTextField, _addressTextField, + _allowedIpsTextField, _dnsTextField]) + field.clear() + _kindBox.currentIndex = 0 + _authenticationBox.currentIndex = 0 + adding = false + error = "" + } + + Connections { + target: control.handler + + function onVpnAdded() { + if (!control.adding) + return + control.adding = false + control.close() + } + + function onVpnAddFailed(message) { + if (!control.adding) + return + control.adding = false + control.error = message + } + } + + Shortcut { + sequence: "Escape" + onActivated: control.close() + } + + FileDialog { + id: _fileDialog + + property Item field + + onAccepted: field.text = decodeURIComponent(selectedFile.toString().replace("file://", "")) + } + + component FileField: RowLayout { + property alias field: _fileTextField + property alias placeholderText: _fileTextField.placeholderText + + signal choose() + signal accepted() + + Layout.fillWidth: true + spacing: Metrics.controlSpacing + + FishUI.TextField { + id: _fileTextField + Layout.fillWidth: true + onAccepted: parent.accepted() + } + + FishUI.Button { + Layout.fillHeight: true + text: qsTr("Choose…") + onClicked: parent.choose() + } + } + + ColumnLayout { + id: _mainLayout + // Not filled to the window: the window height follows this layout's implicit + // height a polish later, and a filled layout would be spread over the old height. + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Metrics.dialogPadding + spacing: Metrics.dialogSpacing + + FishUI.Label { + Layout.fillWidth: true + Layout.rightMargin: Metrics.xxl + text: control.title + textFormat: Text.PlainText + font.pixelSize: 14 + font.weight: Font.DemiBold + color: FishUI.Theme.textColor + wrapMode: Text.Wrap + } + + GridLayout { + id: _form + Layout.fillWidth: true + columns: 2 + columnSpacing: Metrics.dialogSpacing + rowSpacing: Metrics.formRowSpacing + + // Hidden rows leave the grid, so the fields would move as they appear. + readonly property real labelWidth: Math.max(_kindLabel.implicitWidth, + _nameLabel.implicitWidth, + _serverLabel.implicitWidth, + _authenticationLabel.implicitWidth, + _userNameLabel.implicitWidth, + _passwordLabel.implicitWidth, + _presharedKeyLabel.implicitWidth, + _certificateLabel.implicitWidth, + _keyLabel.implicitWidth, + _keyPasswordLabel.implicitWidth, + _caLabel.implicitWidth, + _publicKeyLabel.implicitWidth, + _privateKeyLabel.implicitWidth, + _addressLabel.implicitWidth, + _allowedIpsLabel.implicitWidth, + _dnsLabel.implicitWidth) + + FishUI.Label { + id: _kindLabel + Layout.preferredWidth: _form.labelWidth + text: qsTr("Type") + } + + FishUI.ComboBox { + id: _kindBox + Layout.fillWidth: true + textRole: "text" + valueRole: "kind" + model: control.kinds + } + + FishUI.Label { + Layout.columnSpan: 2 + Layout.fillWidth: true + visible: !control.installed && control.kind.length > 0 + text: qsTr("Install %1 to set up this kind of VPN.").arg(control.kindInfo.package ?? "") + textFormat: Text.PlainText + color: FishUI.Theme.disabledTextColor + wrapMode: Text.Wrap + } + + FishUI.Label { + id: _nameLabel + Layout.preferredWidth: _form.labelWidth + visible: control.installed + text: qsTr("Name") + } + + FishUI.TextField { + id: _nameTextField + Layout.fillWidth: true + visible: control.installed + placeholderText: qsTr("Optional") + onAccepted: control.add() + } + + FishUI.Label { + id: _serverLabel + Layout.preferredWidth: _form.labelWidth + visible: control.installed + text: qsTr("Server") + } + + FishUI.TextField { + id: _serverTextField + Layout.fillWidth: true + visible: control.installed + placeholderText: qsTr("Required") + inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase | Qt.ImhUrlCharactersOnly + onAccepted: control.add() + } + + FishUI.Label { + id: _authenticationLabel + Layout.preferredWidth: _form.labelWidth + visible: control.installed && control.kind === "openvpn" + text: qsTr("Sign In With") + } + + FishUI.ComboBox { + id: _authenticationBox + Layout.fillWidth: true + visible: control.installed && control.kind === "openvpn" + textRole: "text" + valueRole: "value" + model: [ + { text: qsTr("User Name and Password"), value: "password" }, + { text: qsTr("Certificate"), value: "certificates" } + ] + } + + FishUI.Label { + id: _userNameLabel + Layout.preferredWidth: _form.labelWidth + visible: control.usesUserName + text: qsTr("User Name") + } + + FishUI.TextField { + id: _userNameTextField + Layout.fillWidth: true + visible: control.usesUserName + placeholderText: qsTr("Required") + inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + onAccepted: control.add() + } + + FishUI.Label { + id: _passwordLabel + Layout.preferredWidth: _form.labelWidth + visible: control.usesUserName + text: qsTr("Password") + } + + FishUI.TextField { + id: _passwordTextField + Layout.fillWidth: true + visible: control.usesUserName + placeholderText: qsTr("Ask when connecting") + echoMode: TextInput.Password + inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText + onAccepted: control.add() + } + + FishUI.Label { + id: _presharedKeyLabel + Layout.preferredWidth: _form.labelWidth + visible: control.installed && control.kind === "l2tp" + text: qsTr("Pre-shared Key") + } + + FishUI.TextField { + id: _presharedKeyTextField + Layout.fillWidth: true + visible: control.installed && control.kind === "l2tp" + placeholderText: qsTr("Optional") + echoMode: TextInput.Password + inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText + onAccepted: control.add() + } + + FishUI.Label { + id: _certificateLabel + Layout.preferredWidth: _form.labelWidth + visible: control.certificates + text: qsTr("Certificate") + } + + FileField { + id: _certificateField + visible: control.certificates + placeholderText: qsTr("Required") + onChoose: control.chooseFile(field, qsTr("Choose Your Certificate")) + onAccepted: control.add() + } + + FishUI.Label { + id: _keyLabel + Layout.preferredWidth: _form.labelWidth + visible: control.certificates + text: qsTr("Private Key") + } + + FileField { + id: _keyField + visible: control.certificates + placeholderText: qsTr("Required") + onChoose: control.chooseFile(field, qsTr("Choose Your Private Key")) + onAccepted: control.add() + } + + FishUI.Label { + id: _keyPasswordLabel + Layout.preferredWidth: _form.labelWidth + visible: control.certificates + text: qsTr("Key Password") + } + + FishUI.TextField { + id: _keyPasswordTextField + Layout.fillWidth: true + visible: control.certificates + placeholderText: qsTr("Optional") + echoMode: TextInput.Password + inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText + onAccepted: control.add() + } + + FishUI.Label { + id: _caLabel + Layout.preferredWidth: _form.labelWidth + visible: control.installed && control.kind === "openvpn" + text: qsTr("CA Certificate") + } + + FileField { + id: _caField + visible: control.installed && control.kind === "openvpn" + placeholderText: qsTr("Optional") + onChoose: control.chooseFile(field, qsTr("Choose the CA Certificate")) + onAccepted: control.add() + } + + FishUI.Label { + id: _publicKeyLabel + Layout.preferredWidth: _form.labelWidth + visible: control.wireGuard + text: qsTr("Server Public Key") + } + + FishUI.TextField { + id: _publicKeyTextField + Layout.fillWidth: true + visible: control.wireGuard + placeholderText: qsTr("Required") + inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + onAccepted: control.add() + } + + FishUI.Label { + id: _privateKeyLabel + Layout.preferredWidth: _form.labelWidth + visible: control.wireGuard + text: qsTr("Private Key") + } + + FishUI.TextField { + id: _privateKeyTextField + Layout.fillWidth: true + visible: control.wireGuard + placeholderText: qsTr("Required") + echoMode: TextInput.Password + inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText + onAccepted: control.add() + } + + FishUI.Label { + id: _addressLabel + Layout.preferredWidth: _form.labelWidth + visible: control.wireGuard + text: qsTr("Address") + } + + FishUI.TextField { + id: _addressTextField + Layout.fillWidth: true + visible: control.wireGuard + placeholderText: "10.0.0.2/24" + inputMethodHints: Qt.ImhNoPredictiveText + onAccepted: control.add() + } + + FishUI.Label { + id: _allowedIpsLabel + Layout.preferredWidth: _form.labelWidth + visible: control.wireGuard + text: qsTr("Allowed IPs") + } + + FishUI.TextField { + id: _allowedIpsTextField + Layout.fillWidth: true + visible: control.wireGuard + placeholderText: qsTr("All traffic") + inputMethodHints: Qt.ImhNoPredictiveText + onAccepted: control.add() + } + + FishUI.Label { + id: _dnsLabel + Layout.preferredWidth: _form.labelWidth + visible: control.wireGuard + text: qsTr("DNS") + } + + FishUI.TextField { + id: _dnsTextField + Layout.fillWidth: true + visible: control.wireGuard + placeholderText: qsTr("Optional") + inputMethodHints: Qt.ImhNoPredictiveText + onAccepted: control.add() + } + } + + FishUI.Label { + Layout.fillWidth: true + visible: control.error.length > 0 + text: control.error + textFormat: Text.PlainText + color: FishUI.Theme.redColor + wrapMode: Text.Wrap + } + + RowLayout { + spacing: Metrics.dialogButtonSpacing + Layout.alignment: Qt.AlignRight + + FishUI.ConfirmDialogButton { + text: qsTr("Cancel") + onClicked: control.close() + } + + FishUI.ConfirmDialogButton { + enabled: control.canAdd + primary: true + text: qsTr("Add") + onClicked: control.add() + } + } + } +} diff --git a/src/qml/VPN/Main.qml b/src/qml/VPN/Main.qml index cafb8f9..0dfef4c 100644 --- a/src/qml/VPN/Main.qml +++ b/src/qml/VPN/Main.qml @@ -1,39 +1,60 @@ -/* - * Copyright (C) 2021 CutefishOS Team. - * - * Author: revenmartin - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Layouts import FishUI 1.0 as FishUI import Cutefish.Settings 1.0 +import Cutefish.Network 1.0 as NM import "../" +// The VPNs set up on this machine, each with its own switch, and ways to add one: by +// hand, or from the configuration file a VPN provider hands out. ItemPage { + id: control headerTitle: qsTr("VPN") - property bool vpnConnected: false + property bool importing: false + property string importError + + NM.Handler { + id: handler - function switchVPN(enabled) { - if (enabled) { - vpnConnected = true - } else { - vpnConnected = false + onVpnAdded: { + control.importing = false + control.importError = "" + } + onVpnAddFailed: (message) => { + // The Add VPN dialog shows its own failures. + if (!control.importing) + return + control.importing = false + control.importError = message + } + } + + AddVpnDialog { + id: addDialog + handler: handler + } + + NM.NetworkModel { + id: networkModel + } + + NM.AppletProxyModel { + id: vpnModel + type: NM.AppletProxyModel.VpnType + sourceModel: networkModel + } + + FileDialog { + id: fileDialog + title: qsTr("Import VPN Configuration") + nameFilters: [qsTr("VPN configurations (*.ovpn *.conf *.pcf)"), qsTr("All files (*)")] + onAccepted: { + control.importing = true + control.importError = "" + handler.importVpn(selectedFile) } } @@ -44,32 +65,39 @@ ItemPage { ColumnLayout { id: layout anchors.fill: parent - spacing: Metrics.sectionSpacing - - RoundedItem { - id: mainItem + spacing: Metrics.sectionLabelSpacing - RowLayout { - spacing: Metrics.rowSpacing - - FishUI.Label { - text: qsTr("VPN") - color: FishUI.Theme.disabledTextColor - } + ListCard { + Repeater { + model: vpnModel + delegate: VpnItem {} + } - Item { - Layout.fillWidth: true - } + // Declared after the Repeater, which inserts its rows before itself. + ListRow { + text: qsTr("Add VPN…") + enabled: !control.importing + onClicked: addDialog.show() + } - FishUI.Switch { - id: vpnSwitch - Layout.fillHeight: true - rightPadding: 0 - onCheckedChanged: {} - } + ListRow { + text: control.importing ? qsTr("Importing…") : qsTr("Import from File…") + enabled: !control.importing + onClicked: fileDialog.open() } } + FishUI.Label { + Layout.fillWidth: true + Layout.leftMargin: Metrics.cardHorizontalPadding + Layout.rightMargin: Metrics.cardHorizontalPadding + text: control.importError.length > 0 + ? control.importError + : qsTr("Import the configuration file your VPN provider gives you, such as an OpenVPN .ovpn or a WireGuard .conf file.") + textFormat: Text.PlainText + color: control.importError.length > 0 ? FishUI.Theme.redColor : FishUI.Theme.disabledTextColor + wrapMode: Text.Wrap + } } } } diff --git a/src/qml/VPN/VpnDetailsDialog.qml b/src/qml/VPN/VpnDetailsDialog.qml new file mode 100644 index 0000000..8f5e60e --- /dev/null +++ b/src/qml/VPN/VpnDetailsDialog.qml @@ -0,0 +1,139 @@ +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts +import Cutefish.Settings 1.0 +import FishUI 1.0 as FishUI +import Cutefish.Network 1.0 as NM +import "../" + +// One VPN's type and state, and the way to remove it. +FishUI.Window { + id: control + title: model.name + + parentWindow: rootWindow + x: parentWindow ? parentWindow.x + Math.round((parentWindow.width - width) / 2) : 0 + y: parentWindow ? parentWindow.y + Math.round((parentWindow.height - height) / 2) : 0 + width: 400 + property int contentHeight: detailsLayout.implicitHeight + header.height + 32 + height: contentHeight + minimumWidth: width + maximumWidth: width + minimumHeight: contentHeight + maximumHeight: contentHeight + flags: Qt.Dialog | Qt.FramelessWindowHint + modality: Qt.WindowModal + visible: false + minimizeButtonVisible: false + header.height: 44 + contentTopMargin: header.height + // The page colour, so the cards stand out as they do on a page. + background.color: FishUI.Theme.backgroundColor + // The background item reaches a grown window a frame or two after its buffer does. + color: background.color + + headerItem: FishUI.Label { + anchors.left: parent.left + anchors.leftMargin: Metrics.dialogPadding + anchors.verticalCenter: parent.verticalCenter + width: parent.width - 64 + text: control.title + textFormat: Text.PlainText + font.pixelSize: 14 + font.weight: Font.DemiBold + color: FishUI.Theme.textColor + elide: Text.ElideRight + } + + // Created per click, so each one goes away with its window. + onClosing: { + if (model.vpnUsesUserName) + handler.setVpnUserName(model.connectionPath, userNameField.text) + destroy() + } + + Component.onCompleted: { + if (model.vpnUsesUserName && model.vpnUserName.length === 0) + Qt.callLater(userNameField.forceActiveFocus) + } + + ColumnLayout { + id: detailsLayout + // Not filled to the window: the window height follows this layout's implicit + // height a polish later, and a filled layout would be spread over the old height. + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Metrics.dialogPadding + spacing: Metrics.dialogSpacing + + ListCard { + visible: model.vpnUsesUserName + + ListRow { + interactive: false + text: qsTr("User Name") + + FishUI.TextField { + id: userNameField + Layout.preferredWidth: 200 + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + text: model.vpnUserName + placeholderText: qsTr("Required") + inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + onAccepted: control.close() + } + } + } + + ListCard { + InfoRow { + text: qsTr("Type") + value: model.vpnType + } + + InfoRow { + text: qsTr("Status") + value: model.connectionState === NM.Enums.Activated ? qsTr("Connected") + : model.connectionState === NM.Enums.Activating ? qsTr("Connecting…") + : qsTr("Not connected") + } + + InfoRow { + visible: model.connectionState === NM.Enums.Activated && model.ipAddress.length > 0 + text: qsTr("IPv4 Address") + value: model.ipAddress + } + } + + FishUI.Label { + Layout.fillWidth: true + visible: model.statusMessage.length > 0 + text: model.statusMessage + textFormat: Text.PlainText + color: FishUI.Theme.disabledTextColor + wrapMode: Text.Wrap + } + + RowLayout { + spacing: Metrics.dialogButtonSpacing + Layout.alignment: Qt.AlignRight + + FishUI.ConfirmDialogButton { + text: qsTr("Close") + onClicked: control.close() + } + + FishUI.ConfirmDialogButton { + text: qsTr("Remove VPN") + primary: true + destructive: true + onClicked: { + handler.removeConnection(model.connectionPath) + control.close() + } + } + } + } +} diff --git a/src/qml/VPN/VpnItem.qml b/src/qml/VPN/VpnItem.qml new file mode 100644 index 0000000..49b98b1 --- /dev/null +++ b/src/qml/VPN/VpnItem.qml @@ -0,0 +1,100 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import FishUI 1.0 as FishUI +import Cutefish.Settings 1.0 +import Cutefish.Network 1.0 as NM +import "../" + +// One VPN: its switch connects and disconnects it; below its name, its type, or why it +// can't connect. +ListRow { + id: control + + readonly property bool busy: model.connectionState === NM.Enums.Activating + || model.connectionState === NM.Enums.Deactivating + readonly property bool up: model.connectionState === NM.Enums.Activated + || model.connectionState === NM.Enums.Activating + // Signing in takes a user name the imported file didn't have. + readonly property bool needsUserName: model.vpnUsesUserName && model.vpnUserName.length === 0 + + Accessible.name: model.name + onClicked: openDetails() + + leading: FishUI.Icon { + width: 18 + height: width + name: "network-vpn" + } + + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: Metrics.textSpacing + + FishUI.Label { + id: primaryLabel + Layout.fillWidth: true + text: model.name + textFormat: Text.PlainText + color: control.enabled ? FishUI.Theme.textColor : FishUI.Theme.disabledTextColor + elide: Text.ElideRight + } + + FishUI.Label { + Layout.fillWidth: true + text: model.statusMessage.length > 0 ? model.statusMessage + : model.connectionState === NM.Enums.Activating ? qsTr("Connecting…") + : control.needsUserName ? qsTr("Add your user name to connect.") + : model.vpnType + textFormat: Text.PlainText + color: model.statusMessage.length > 0 && model.canActivate ? FishUI.Theme.redColor + : FishUI.Theme.disabledTextColor + font.pixelSize: Math.max(1, primaryLabel.font.pixelSize - 1) + wrapMode: Text.Wrap + } + } + + FishUI.BusyIndicator { + width: 18 + height: width + visible: control.busy + running: visible + } + + FishUI.Switch { + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + rightPadding: 0 + rightInset: 0 + enabled: model.canActivate || control.up + checked: control.up + onToggled: { + if (checked && control.needsUserName) + control.openDetails() + else if (checked) + handler.activateConnection(model.connectionPath, "", "") + else + handler.deactivateConnection(model.connectionPath, "") + // The switch shows what NetworkManager reports, not what was asked for. + checked = Qt.binding(function() { return control.up }) + } + } + + IconButton { + width: 20 + Layout.rightMargin: Metrics.trailingGlyphMargin(iconName, width) + iconName: "info" + iconColor: FishUI.Theme.highlightColor + onClicked: control.openDetails() + } + + function openDetails() { + var component = Qt.createComponent("VpnDetailsDialog.qml") + if (component.status === Component.Ready) { + var dialog = component.createObject(rootWindow, { "parentWindow": rootWindow }) + dialog.show() + dialog.raise() + dialog.requestActivate() + } + } +} diff --git a/src/resources.qrc b/src/resources.qrc index a4f0d9a..0d49dc5 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -120,6 +120,9 @@ qml/Bluetooth/DeviceDetailsDialog.qml qml/Bluetooth/AdapterDetailsDialog.qml qml/VPN/Main.qml + qml/VPN/AddVpnDialog.qml + qml/VPN/VpnItem.qml + qml/VPN/VpnDetailsDialog.qml qml/Proxy/Main.qml images/sidebar/dark/proxy.svg qml/Sound/Main.qml diff --git a/src/vpn/nm-l2tp-service.h b/src/vpn/nm-l2tp-service.h deleted file mode 100644 index 994abbf..0000000 --- a/src/vpn/nm-l2tp-service.h +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* nm-l2tp-service - L2TP VPN integration with NetworkManager - * - * Dan Williams - * - * Copyright 2008, 2014 Red Hat, Inc. - */ - -#ifndef NM_L2TP_SERVICE_DEFINES_H -#define NM_L2TP_SERVICE_DEFINES_H - -#define NM_DBUS_SERVICE_L2TP "org.freedesktop.NetworkManager.l2tp" - -/* For the NM <-> VPN plugin service */ -#define NM_DBUS_INTERFACE_L2TP "org.freedesktop.NetworkManager.l2tp" -#define NM_DBUS_PATH_L2TP "/org/freedesktop/NetworkManager/l2tp" - -/* For the VPN plugin service <-> PPP plugin */ -#define NM_DBUS_INTERFACE_L2TP_PPP "org.freedesktop.NetworkManager.l2tp.ppp" -#define NM_DBUS_PATH_L2TP_PPP "/org/freedesktop/NetworkManager/l2tp/ppp" - -#define NM_L2TP_KEY_GATEWAY "gateway" -#define NM_L2TP_KEY_USER_AUTH_TYPE "user-auth-type" -#define NM_L2TP_KEY_USER "user" -#define NM_L2TP_KEY_PASSWORD "password" -#define NM_L2TP_KEY_DOMAIN "domain" -#define NM_L2TP_KEY_USER_CA "user-ca" -#define NM_L2TP_KEY_USER_CERT "user-cert" -#define NM_L2TP_KEY_USER_KEY "user-key" -#define NM_L2TP_KEY_USER_CERTPASS "user-certpass" -#define NM_L2TP_KEY_MTU "mtu" -#define NM_L2TP_KEY_MRU "mru" -#define NM_L2TP_KEY_REFUSE_EAP "refuse-eap" -#define NM_L2TP_KEY_REFUSE_PAP "refuse-pap" -#define NM_L2TP_KEY_REFUSE_CHAP "refuse-chap" -#define NM_L2TP_KEY_REFUSE_MSCHAP "refuse-mschap" -#define NM_L2TP_KEY_REFUSE_MSCHAPV2 "refuse-mschapv2" -#define NM_L2TP_KEY_REQUIRE_MPPE "require-mppe" -#define NM_L2TP_KEY_REQUIRE_MPPE_40 "require-mppe-40" -#define NM_L2TP_KEY_REQUIRE_MPPE_128 "require-mppe-128" -#define NM_L2TP_KEY_MPPE_STATEFUL "mppe-stateful" -#define NM_L2TP_KEY_NOBSDCOMP "nobsdcomp" -#define NM_L2TP_KEY_NODEFLATE "nodeflate" -#define NM_L2TP_KEY_NO_VJ_COMP "no-vj-comp" -#define NM_L2TP_KEY_NO_PCOMP "nopcomp" -#define NM_L2TP_KEY_NO_ACCOMP "noaccomp" -#define NM_L2TP_KEY_LCP_ECHO_FAILURE "lcp-echo-failure" -#define NM_L2TP_KEY_LCP_ECHO_INTERVAL "lcp-echo-interval" -#define NM_L2TP_KEY_UNIT_NUM "unit" -#define NM_L2TP_KEY_MACHINE_AUTH_TYPE "machine-auth-type" -#define NM_L2TP_KEY_MACHINE_CA "machine-ca" -#define NM_L2TP_KEY_MACHINE_CERT "machine-cert" -#define NM_L2TP_KEY_MACHINE_KEY "machine-key" -#define NM_L2TP_KEY_MACHINE_CERTPASS "machine-certpass" -#define NM_L2TP_KEY_IPSEC_ENABLE "ipsec-enabled" -#define NM_L2TP_KEY_IPSEC_REMOTE_ID "ipsec-remote-id" -#define NM_L2TP_KEY_IPSEC_GATEWAY_ID "ipsec-gateway-id" /* deprecated, use ipsec-remote-id */ -#define NM_L2TP_KEY_IPSEC_PSK "ipsec-psk" -#define NM_L2TP_KEY_IPSEC_IKE "ipsec-ike" -#define NM_L2TP_KEY_IPSEC_ESP "ipsec-esp" -#define NM_L2TP_KEY_IPSEC_IKELIFETIME "ipsec-ikelifetime" -#define NM_L2TP_KEY_IPSEC_SALIFETIME "ipsec-salifetime" -#define NM_L2TP_KEY_IPSEC_FORCEENCAPS "ipsec-forceencaps" -#define NM_L2TP_KEY_IPSEC_IPCOMP "ipsec-ipcomp" -#define NM_L2TP_KEY_IPSEC_IKEV2 "ipsec-ikev2" -#define NM_L2TP_KEY_IPSEC_PFS "ipsec-pfs" - -/* Internal auth-dialog -> service token indicating that no secrets are required - * for the connection if X.509 private keys are used with no password protection. - */ -#define NM_L2TP_KEY_NOSECRET "no-secret" - -#define NM_L2TP_AUTHTYPE_PASSWORD "password" -#define NM_L2TP_AUTHTYPE_TLS "tls" -#define NM_L2TP_AUTHTYPE_PSK "psk" -#endif /* NM_L2TP_SERVICE_DEFINES_H */ diff --git a/src/vpn/nm-openvpn-service.h b/src/vpn/nm-openvpn-service.h deleted file mode 100644 index b63ab5c..0000000 --- a/src/vpn/nm-openvpn-service.h +++ /dev/null @@ -1,111 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* nm-openvpn-service - openvpn integration with NetworkManager - * - * Copyright (C) 2005 - 2008 Tim Niemueller - * Copyright (C) 2005 - 2008 Dan Williams - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#ifndef NM_OPENVPN_SERVICE_H -#define NM_OPENVPN_SERVICE_H - -#define NM_DBUS_SERVICE_OPENVPN "org.freedesktop.NetworkManager.openvpn" -#define NM_DBUS_INTERFACE_OPENVPN "org.freedesktop.NetworkManager.openvpn" -#define NM_DBUS_PATH_OPENVPN "/org/freedesktop/NetworkManager/openvpn" - -#define NM_OPENVPN_KEY_AUTH "auth" -#define NM_OPENVPN_KEY_CA "ca" -#define NM_OPENVPN_KEY_CERT "cert" -#define NM_OPENVPN_KEY_CIPHER "cipher" -#define NM_OPENVPN_KEY_KEYSIZE "keysize" -#define NM_OPENVPN_KEY_COMP_LZO "comp-lzo" -#define NM_OPENVPN_KEY_CONNECTION_TYPE "connection-type" -#define NM_OPENVPN_KEY_FLOAT "float" -#define NM_OPENVPN_KEY_FRAGMENT_SIZE "fragment-size" -#define NM_OPENVPN_KEY_KEY "key" -#define NM_OPENVPN_KEY_LOCAL_IP "local-ip" /* ??? */ -#define NM_OPENVPN_KEY_MSSFIX "mssfix" -#define NM_OPENVPN_KEY_NS_CERT_TYPE "ns-cert-type" -#define NM_OPENVPN_KEY_PING "ping" -#define NM_OPENVPN_KEY_PING_EXIT "ping-exit" -#define NM_OPENVPN_KEY_PING_RESTART "ping-restart" -#define NM_OPENVPN_KEY_PORT "port" -#define NM_OPENVPN_KEY_PROTO_TCP "proto-tcp" -#define NM_OPENVPN_KEY_PROXY_TYPE "proxy-type" -#define NM_OPENVPN_KEY_PROXY_SERVER "proxy-server" -#define NM_OPENVPN_KEY_PROXY_PORT "proxy-port" -#define NM_OPENVPN_KEY_PROXY_RETRY "proxy-retry" -#define NM_OPENVPN_KEY_HTTP_PROXY_USERNAME "http-proxy-username" -#define NM_OPENVPN_KEY_REMOTE "remote" -#define NM_OPENVPN_KEY_REMOTE_RANDOM "remote-random" -#define NM_OPENVPN_KEY_REMOTE_IP "remote-ip" -#define NM_OPENVPN_KEY_STATIC_KEY "static-key" -#define NM_OPENVPN_KEY_STATIC_KEY_DIRECTION "static-key-direction" -#define NM_OPENVPN_KEY_TA "ta" -#define NM_OPENVPN_KEY_TA_DIR "ta-dir" -#define NM_OPENVPN_KEY_TUNNEL_MTU "tunnel-mtu" -#define NM_OPENVPN_KEY_USERNAME "username" -#define NM_OPENVPN_KEY_TAP_DEV "tap-dev" -#define NM_OPENVPN_KEY_DEV "dev" -#define NM_OPENVPN_KEY_DEV_TYPE "dev-type" -#define NM_OPENVPN_KEY_TUN_IPV6 "tun-ipv6" -#define NM_OPENVPN_KEY_TLS_CIPHER "tls-cipher" -#define NM_OPENVPN_KEY_TLS_CRYPT "tls-crypt" -#define NM_OPENVPN_KEY_TLS_REMOTE "tls-remote" -#define NM_OPENVPN_KEY_VERIFY_X509_NAME "verify-x509-name" -#define NM_OPENVPN_KEY_REMOTE_CERT_TLS "remote-cert-tls" -#define NM_OPENVPN_KEY_MAX_ROUTES "max-routes" - -#define NM_OPENVPN_KEY_PASSWORD "password" -#define NM_OPENVPN_KEY_CERTPASS "cert-pass" -#define NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD "http-proxy-password" -/* Internal auth-dialog -> service token indicating that no secrets are - * required for the connection. - */ -#define NM_OPENVPN_KEY_NOSECRET "no-secret" - -#define NM_OPENVPN_KEY_RENEG_SECONDS "reneg-seconds" - -#define NM_OPENVPN_AUTH_NONE "none" -#define NM_OPENVPN_AUTH_RSA_MD4 "RSA-MD4" -#define NM_OPENVPN_AUTH_MD5 "MD5" -#define NM_OPENVPN_AUTH_SHA1 "SHA1" -#define NM_OPENVPN_AUTH_SHA224 "SHA224" -#define NM_OPENVPN_AUTH_SHA256 "SHA256" -#define NM_OPENVPN_AUTH_SHA384 "SHA384" -#define NM_OPENVPN_AUTH_SHA512 "SHA512" -#define NM_OPENVPN_AUTH_RIPEMD160 "RIPEMD160" - -#define NM_OPENVPN_CONTYPE_TLS "tls" -#define NM_OPENVPN_CONTYPE_STATIC_KEY "static-key" -#define NM_OPENVPN_CONTYPE_PASSWORD "password" -#define NM_OPENVPN_CONTYPE_PASSWORD_TLS "password-tls" - -/* arguments of "--remote-cert-tls" */ -#define NM_OPENVPN_REM_CERT_TLS_CLIENT "client" -#define NM_OPENVPN_REM_CERT_TLS_SERVER "server" - -/* arguments of "--ns-cert-type" */ -#define NM_OPENVPN_NS_CERT_TYPE_CLIENT "client" -#define NM_OPENVPN_NS_CERT_TYPE_SERVER "server" - -/* possible types for verify-x509-name */ -#define NM_OPENVPN_VERIFY_X509_NAME_TYPE_SUBJECT "subject" -#define NM_OPENVPN_VERIFY_X509_NAME_TYPE_NAME "name" -#define NM_OPENVPN_VERIFY_X509_NAME_TYPE_NAME_PREFIX "name-prefix" - -#endif /* NM_OPENVPN_SERVICE_H */ diff --git a/src/vpn/nm-pptp-service.h b/src/vpn/nm-pptp-service.h deleted file mode 100644 index e2baf44..0000000 --- a/src/vpn/nm-pptp-service.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* nm-pptp-service - PPTP VPN integration with NetworkManager - * - * Dan Williams - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * (C) Copyright 2008 Red Hat, Inc. //krazy:exclude=copyright - */ - -#ifndef NM_PPTP_SERVICE_H -#define NM_PPTP_SERVICE_H - -#define NM_DBUS_SERVICE_PPTP_PPP "org.freedesktop.NetworkManager.pptp-ppp" -#define NM_DBUS_PATH_PPTP_PPP "/org/freedesktop/NetworkManager/pptp/ppp" -#define NM_DBUS_INTERFACE_PPTP_PPP "org.freedesktop.NetworkManager.pptp.ppp" - - -/* For the NM <-> VPN plugin service */ -#define NM_DBUS_SERVICE_PPTP "org.freedesktop.NetworkManager.pptp" -#define NM_DBUS_INTERFACE_PPTP "org.freedesktop.NetworkManager.pptp" -#define NM_DBUS_PATH_PPTP "/org/freedesktop/NetworkManager/pptp" - -#define NM_PPTP_KEY_GATEWAY "gateway" -#define NM_PPTP_KEY_USER "user" -#define NM_PPTP_KEY_PASSWORD "password" -#define NM_PPTP_KEY_DOMAIN "domain" -#define NM_PPTP_KEY_REFUSE_EAP "refuse-eap" -#define NM_PPTP_KEY_REFUSE_PAP "refuse-pap" -#define NM_PPTP_KEY_REFUSE_CHAP "refuse-chap" -#define NM_PPTP_KEY_REFUSE_MSCHAP "refuse-mschap" -#define NM_PPTP_KEY_REFUSE_MSCHAPV2 "refuse-mschapv2" -#define NM_PPTP_KEY_REQUIRE_MPPE "require-mppe" -#define NM_PPTP_KEY_REQUIRE_MPPE_40 "require-mppe-40" -#define NM_PPTP_KEY_REQUIRE_MPPE_128 "require-mppe-128" -#define NM_PPTP_KEY_MPPE_STATEFUL "mppe-stateful" -#define NM_PPTP_KEY_NOBSDCOMP "nobsdcomp" -#define NM_PPTP_KEY_NODEFLATE "nodeflate" -#define NM_PPTP_KEY_NO_VJ_COMP "no-vj-comp" -#define NM_PPTP_KEY_LCP_ECHO_FAILURE "lcp-echo-failure" -#define NM_PPTP_KEY_LCP_ECHO_INTERVAL "lcp-echo-interval" - - -#endif /* NM_PPTP_PLUGIN_H */ diff --git a/src/vpn/vpn.cpp b/src/vpn/vpn.cpp deleted file mode 100644 index 7819f5d..0000000 --- a/src/vpn/vpn.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "vpn.h" - -VPN::VPN(QObject *parent) - : QObject(parent) -{ -} - -void VPN::activateVPNConnection(const QString &connection, const QString &device, - const QString &specificObject) -{ -} diff --git a/src/vpn/vpn.h b/src/vpn/vpn.h deleted file mode 100644 index a71bf0e..0000000 --- a/src/vpn/vpn.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef VPN_H -#define VPN_H - -#include - -class VPN : public QObject -{ - Q_OBJECT - -public: - explicit VPN(QObject *parent = nullptr); - - Q_INVOKABLE void activateVPNConnection(const QString &connection, const QString &device, const QString &specificParameter); -}; - -#endif // VPN_H