This commit is contained in:
Domipoke
2026-06-25 19:58:23 +02:00
parent 372c622025
commit 955801c666
147 changed files with 18829 additions and 0 deletions
@@ -0,0 +1,80 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Modules.Bar.Extras
import qs.Services.UI
import qs.Widgets
Item {
id: root
property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
readonly property var pluginSettings: pluginApi?.pluginSettings ?? ({})
readonly property var main: pluginApi?.mainInstance ?? ({})
readonly property string screenName: screen?.name ?? ""
readonly property string barPosition: Settings.getBarPositionForScreen(screenName)
readonly property bool isBarVertical: barPosition === "left" || barPosition === "right"
readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName)
readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName)
readonly property string displayMode: root.pluginSettings.displayMode ?? "onhover"
readonly property string connectedColor: root.pluginSettings.connectedColor
readonly property string disconnectedColor: root.pluginSettings.disconnectedColor
readonly property var vpnList: root.main.vpnList ?? []
readonly property real connectedCount: root.main.connectedCount ?? 0
readonly property bool isLoading: root.main.isLoading ?? false
implicitWidth: pill.width
implicitHeight: pill.height
Component.onCompleted: {
Logger.i("NetworkManagerVPN", "Bar widget loaded");
}
NPopupContextMenu {
id: contextMenu
model: [{
"label": pluginApi?.tr("settings.pluginSettings"),
"action": "plugin-settings",
"icon": "settings"
}]
onTriggered: (action) => {
contextMenu.close();
PanelService.closeContextMenu(screen);
if (action === "plugin-settings")
BarService.openPluginSettings(screen, pluginApi.manifest);
}
}
BarPill {
id: pill
screen: root.screen
oppositeDirection: BarService.getPillDirection(root)
autoHide: false
text: root.connectedCount > 0 ? pluginApi?.tr("common.connected") : pluginApi?.tr("common.disconnected")
icon: root.isLoading ? "reload" : root.connectedCount > 0 ? "shield-lock" : "shield"
onClicked: {
if (pluginApi)
pluginApi.openPanel(root.screen, root);
}
onRightClicked: {
if (pluginApi)
pluginApi.closePanel(root.screen);
PanelService.showContextMenu(contextMenu, pill, screen);
}
customIconColor: Color.resolveColorKeyOptional(root.connectedCount > 0 ? root.connectedColor : root.disconnectedColor)
customTextColor: Color.resolveColorKeyOptional(root.connectedCount > 0 ? root.connectedColor : root.disconnectedColor)
forceOpen: root.displayMode === "alwaysShow"
forceClose: root.displayMode === "alwaysHide"
}
}
@@ -0,0 +1,29 @@
import QtQuick
import Quickshell
import qs.Widgets
import qs.Commons
NIconButtonHot {
property ShellScreen screen
property var pluginApi: null
readonly property var main: pluginApi?.mainInstance ?? ({})
readonly property real connectedCount: main.connectedCount ?? 0
readonly property bool isLoading: main.isLoading ?? false
readonly property var pluginSettings: pluginApi?.pluginSettings ?? ({})
readonly property string connectedColor: pluginSettings.connectedColor
readonly property string disconnectedColor: pluginSettings.disconnectedColor
icon: isLoading ? "reload" : connectedCount > 0 ? "shield-lock" : "shield"
tooltipText: connectedCount > 0
? pluginApi?.tr("common.connected")
: pluginApi?.tr("common.disconnected")
colorFg: {
const key = connectedCount > 0 ? connectedColor : disconnectedColor;
if (!key || key === "none") return Color.mPrimary;
return Color.resolveColorKeyOptional(key) ?? Color.mPrimary;
}
onClicked: pluginApi?.togglePanel(screen, this)
}
@@ -0,0 +1,231 @@
import QtQuick
import Quickshell.Io
import qs.Commons
import qs.Services.UI
QtObject {
id: root
readonly property var pluginSettings: pluginApi?.pluginSettings ?? ({})
readonly property var toast: root.pluginSettings?.disableToastNotifications ? null : ToastService
property var pluginApi: null
property var vpnList: []
property real connectedCount: 0
readonly property bool isLoading: Object.keys(root._pending).length > 0
property var _pending: ({})
// Needed only to detect disconnection not initiated by the user
property var _pollTimer: Timer {
interval: 5000
running: true
repeat: true
onTriggered: root.refresh()
}
property var _lines: []
property var _listProc: Process {
command: ["nmcli", "-t", "-f", "NAME,TYPE,STATE,UUID", "connection", "show"]
running: true
stdout: SplitParser {
onRead: (line) => {
if (line.trim() !== "")
root._lines.push(line)
}
}
onExited: (exitCode) => {
if (exitCode === 0) {
const parsed = []
const newPending = Object.assign({}, root._pending)
for (const line of root._lines) {
const parts = line.split(":")
if (parts.length >= 4) {
const name = parts[0]
const type = parts[1]
const state = parts[2]
const uuid = parts[3]
if (type === "vpn" || type === "wireguard") {
if (newPending[uuid]) {
const wasConnecting = newPending[uuid] === "connect"
if (wasConnecting && state === "activated")
delete newPending[uuid]
else if (!wasConnecting && state !== "activated")
delete newPending[uuid]
}
parsed.push({
name,
type,
connected: state === "activated",
isLoading: !!newPending[uuid],
uuid
})
}
}
}
root._pending = newPending
root.vpnList = parsed
root.connectedCount = parsed.filter(v => v.connected).length
}
root._lines = []
}
}
property var _connectProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nmcli", "connection", "up", "uuid", targetUuid]
onExited: (exitCode) => {
if (exitCode === 0)
toast?.showNotice(t("toast.connectedTo", { name: targetName }))
else {
root.stopLoading(targetUuid)
toast?.showError(t("toast.connectionError", { name: targetName }))
}
root.refresh()
}
}
property var _disconnectProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nmcli", "connection", "down", "uuid", targetUuid]
onExited: (exitCode) => {
if (exitCode === 0)
toast?.showNotice(t("toast.disconnectedFrom", { name: targetName }))
else {
root.stopLoading(targetUuid)
toast?.showError(t("toast.disconnectionError", { name: targetName }))
}
root.refresh()
}
}
property var _addProc: Process {
property string targetType: ""
command: ["nm-connection-editor", "--create", "--type", targetType]
onExited: (exitCode) => {
root.refresh();
}
}
property var _editProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nm-connection-editor", "--edit", targetUuid]
onExited: (exitCode) => {
root.refresh();
}
}
property var _removeProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nmcli", "connection", "delete", "uuid", targetUuid]
onExited: (exitCode) => {
if (exitCode === 0)
toast?.showNotice(t("toast.vpnRemoved", { "name": targetName }));
else
toast?.showError(t("toast.vpnRemoveError", { "name": targetName }));
root.refresh();
}
}
function t(key: string, data) {
if (!pluginApi)
return null;
return pluginApi.tr(key, data);
}
function refresh() {
_listProc.running = true
}
function stopLoading(uuid) {
if (uuid && _pending[uuid]) {
const p = Object.assign({}, _pending)
delete p[uuid]
_pending = p
}
vpnList = vpnList.map(v => {
if (v.uuid !== uuid) {
return v
}
return Object.assign({}, v, { isLoading: false })
})
}
function connectTo(uuid) {
const p = Object.assign({}, _pending)
p[uuid] = "connect"
_pending = p
let name = ""
vpnList = vpnList.map(v => {
if (v.uuid !== uuid) {
return v
}
name = v.name
return Object.assign({}, v, { isLoading: true })
})
_connectProc.targetName = name
_connectProc.targetUuid = uuid
_connectProc.running = true
}
function disconnectFrom(uuid) {
const p = Object.assign({}, _pending)
p[uuid] = "disconnect"
_pending = p
let name = ""
vpnList = vpnList.map(v => {
if (v.uuid !== uuid) {
return v
}
name = v.name
return Object.assign({}, v, { isLoading: true })
})
_disconnectProc.targetName = name
_disconnectProc.targetUuid = uuid
_disconnectProc.running = true
}
function addConnection(type) {
_addProc.targetType = type || "vpn";
_addProc.running = true;
}
function editConnection(uuid) {
const vpn = vpnList.find((v) => {
return v.uuid === uuid;
});
_editProc.targetName = vpn ? vpn.name : uuid;
_editProc.targetUuid = uuid;
_editProc.running = true;
}
function removeConnection(uuid) {
const vpn = vpnList.find((v) => {
return v.uuid === uuid;
});
_removeProc.targetName = vpn ? vpn.name : uuid;
_removeProc.targetUuid = uuid;
_removeProc.running = true;
}
Component.onCompleted: {
Logger.i("NetworkManagerVPN", "Started")
}
}
@@ -0,0 +1,236 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
import qs.Services.UI
Item {
id: root
property var pluginApi: null
property ShellScreen screen
readonly property var geometryPlaceholder: panelContainer
readonly property bool allowAttach: true
readonly property var main: pluginApi?.mainInstance ?? null
readonly property var vpnList: main?.vpnList ?? []
readonly property var activeList: vpnList.filter(v => v.connected || v.isLoading)
readonly property var inactiveList: vpnList.filter(v => !v.connected && !v.isLoading)
property real contentPreferredWidth: Math.round(500 * Style.uiScaleRatio)
property real contentPreferredHeight: Math.min(500, mainColumn.implicitHeight + Style.marginL * 2)
Component.onCompleted: {
if (main) main.refresh()
}
Rectangle {
id: panelContainer
anchors.fill: parent
color: "transparent"
ColumnLayout {
id: mainColumn
anchors.fill: parent
anchors.margins: Style.marginL
spacing: Style.marginM
// HEADER
NBox {
Layout.fillWidth: true
Layout.preferredHeight: Math.round(header.implicitHeight + Style.marginM * 2 + 1)
ColumnLayout {
id: header
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
RowLayout {
NIcon {
icon: "shield"
pointSize: Style.fontSizeXXL
color: Color.mPrimary
}
NLabel {
label: pluginApi?.tr("common.vpn")
}
NBox {
Layout.fillWidth: true
}
NIconButton {
icon: "refresh"
tooltipText: pluginApi?.tr("common.refresh")
baseSize: Style.baseWidgetSize * 0.8
enabled: true
onClicked: {
if (main) {
main.refresh()
}
}
}
NIconButton {
icon: "close"
tooltipText: pluginApi?.tr("common.close")
baseSize: Style.baseWidgetSize * 0.8
onClicked: pluginApi.closePanel(pluginApi.panelOpenScreen)
}
}
}
}
NScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
reserveScrollbarSpace: false
ColumnLayout {
anchors.fill: parent
spacing: Style.marginM
// CONNECTED
NBox {
Layout.fillWidth: true
Layout.preferredHeight: Math.round(networksListActive.implicitHeight + Style.marginXL)
visible: activeList.length > 0
ColumnLayout {
id: networksListActive
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.marginS
spacing: Style.marginS
NLabel {
label: pluginApi?.tr("common.connected")
Layout.fillWidth: true
}
}
Repeater {
model: activeList
VpnListItem {
name: modelData.name
type: modelData.type
isConnected: true
isLoading: modelData.isLoading
onButtonClicked: {
if (!main) return
main.disconnectFrom(modelData.uuid)
}
}
}
}
}
// DISCONNECTED
NBox {
Layout.fillWidth: true
Layout.preferredHeight: Math.round(networksListInactive.implicitHeight + Style.marginXL)
visible: inactiveList.length > 0
ColumnLayout {
id: networksListInactive
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.marginS
spacing: Style.marginS
NLabel {
label: pluginApi?.tr("common.disconnected")
Layout.fillWidth: true
}
}
Repeater {
model: inactiveList
VpnListItem {
name: modelData.name
type: modelData.type
isConnected: false
isLoading: modelData.isLoading
onButtonClicked: {
if (!main) return
main.connectTo(modelData.uuid)
}
}
}
}
}
// EMPTY
NBox {
id: emptyBox
visible: vpnList.length < 1
Layout.fillWidth: true
Layout.preferredHeight: Math.round(emptyColumn.implicitHeight + Style.marginM * 2 + 1)
ColumnLayout {
id: emptyColumn
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginL
Item {
Layout.fillHeight: true
}
NIcon {
icon: "search"
pointSize: 48
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: pluginApi?.tr("panel.emptyTitle")
pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: pluginApi?.tr("panel.emptyDescription")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NButton {
text: pluginApi?.tr("common.refresh")
icon: "refresh"
Layout.alignment: Qt.AlignHCenter
onClicked: {
if (main) {
main.refresh()
}
}
}
Item {
Layout.fillHeight: true
}
}
}
}
}
}
}
}
@@ -0,0 +1,21 @@
# Network Manager VPN
![Preview](preview.png)
Plugin to connect to your VPN connections from the bar and Control Center. Supports OpenVPN and WireGuard connections managed by NetworkManager. Editing and creation are achieved using
nm-connection-editor
## Usage
Install the plugin and add it to your bar:
- **Bar widget** — A shield icon that shows if there is an active connection. Click it to open the VPN panel.
- **VPN panel** — Lists all configured VPN connections.
The panel refreshes automatically every 5 seconds. You can also trigger a manual refresh with the refresh button.
## Requirements
- **Noctalia Shell** ≥ 3.6.0
- **NetworkManager** with `nmcli`
- **nm-connection-editor** to edit and create connections
@@ -0,0 +1,256 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
property var pluginApi: null
readonly property var pluginSettings: pluginApi?.pluginSettings ?? pluginApi?.manifest?.metadata?.defaultSettings ?? ({})
readonly property var main: pluginApi?.mainInstance ?? null
readonly property var vpnList: main ? main.vpnList : []
// Local state
property string editDisplayMode: root.pluginSettings.displayMode ?? ""
property string editConnectedColor: root.pluginSettings.connectedColor ?? ""
property string editDisconnectedColor: root.pluginSettings.disconnectedColor ?? ""
property bool disableToastNotifications: root.pluginSettings?.disableToastNotifications ?? false
property string pendingDeleteUuid: ""
property string pendingDeleteName: ""
readonly property var displayModeModel: [{
"key": "onhover",
"name": I18n.tr("display-modes.on-hover")
}, {
"key": "alwaysShow",
"name": I18n.tr("display-modes.always-show")
}, {
"key": "alwaysHide",
"name": I18n.tr("display-modes.always-hide")
}]
function saveSettings() {
pluginApi.pluginSettings.displayMode = root.editDisplayMode;
pluginApi.pluginSettings.connectedColor = root.editConnectedColor;
pluginApi.pluginSettings.disconnectedColor = root.editDisconnectedColor;
pluginApi.pluginSettings.disableToastNotifications = root.disableToastNotifications;
pluginApi.saveSettings();
Logger.i("NetworkManagerVpn", "Settings saved successfully");
}
NComboBox {
label: I18n.tr("common.display-mode")
description: I18n.tr("bar.volume.display-mode-description")
minimumWidth: 200
model: root.displayModeModel
currentKey: root.editDisplayMode
onSelected: (key) => {
root.editDisplayMode = key;
}
}
NColorChoice {
label: pluginApi?.tr("settings.connectedColor")
description: pluginApi?.tr("settings.connectedColorDescription")
currentKey: root.editConnectedColor
onSelected: (key) => {
root.editConnectedColor = key;
}
}
NColorChoice {
label: pluginApi?.tr("settings.disconnectedColor")
description: pluginApi?.tr("settings.disconnectedColorDescription")
currentKey: root.editDisconnectedColor
onSelected: (key) => {
root.editDisconnectedColor = key;
}
}
NToggle {
Layout.fillWidth: true
label: pluginApi?.tr("settings.disableToastNotifications")
description: pluginApi?.tr("settings.disableToastNotificationsDescription")
checked: root.disableToastNotifications
onToggled: checked => {
root.disableToastNotifications = checked;
}
}
NBox {
Layout.fillWidth: true
Layout.topMargin: Style.marginM
Layout.preferredHeight: Math.round(vpnSection.implicitHeight + Style.marginL * 2)
ColumnLayout {
id: vpnSection
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
NLabel {
label: pluginApi?.tr("settings.vpnConnections")
Layout.fillWidth: true
}
NText {
visible: root.vpnList.length === 0
text: pluginApi?.tr("settings.noVpnConnections")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
Layout.leftMargin: Style.marginXS
}
Repeater {
model: root.vpnList
NBox {
id: vpnRow
readonly property bool confirmingDelete: root.pendingDeleteUuid === modelData.uuid
Layout.fillWidth: true
Layout.leftMargin: Style.marginXS
Layout.rightMargin: Style.marginXS
implicitHeight: Math.round(rowContent.implicitHeight + Style.marginL)
color: vpnRow.confirmingDelete ? Qt.alpha(Color.mError, 0.12) : Color.mSurface
ColumnLayout {
id: rowContent
anchors.fill: parent
anchors.leftMargin: Style.marginM
anchors.rightMargin: Style.marginM
anchors.topMargin: Style.marginS
anchors.bottomMargin: Style.marginS
spacing: Style.marginS
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NIcon {
icon: "router"
pointSize: Style.fontSizeXXL
color: modelData.connected ? Color.mPrimary : Color.mOnSurfaceVariant
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: modelData.name
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
elide: Text.ElideRight
Layout.fillWidth: true
}
NText {
text: modelData.type || "vpn"
pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
}
NIconButton {
visible: !vpnRow.confirmingDelete
icon: "edit"
tooltipText: pluginApi?.tr("common.edit")
baseSize: Style.baseWidgetSize * 0.75
onClicked: {
main.editConnection(modelData.uuid);
close();
}
}
NIconButton {
visible: !vpnRow.confirmingDelete
icon: "trash-x"
tooltipText: pluginApi?.tr("common.delete")
baseSize: Style.baseWidgetSize * 0.75
onClicked: {
root.pendingDeleteUuid = modelData.uuid;
root.pendingDeleteName = modelData.name;
}
}
NButton {
visible: vpnRow.confirmingDelete
text: pluginApi?.tr("common.delete")
fontSize: Style.fontSizeXS
backgroundColor: Color.mError
outlined: false
onClicked: {
main.removeConnection(root.pendingDeleteUuid);
root.pendingDeleteUuid = "";
root.pendingDeleteName = "";
}
}
NButton {
visible: vpnRow.confirmingDelete
text: pluginApi?.tr("common.cancel")
fontSize: Style.fontSizeXS
outlined: true
onClicked: {
root.pendingDeleteUuid = "";
root.pendingDeleteName = "";
}
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Style.marginXS
spacing: Style.marginS
Item {
Layout.fillWidth: true
}
NButton {
text: pluginApi?.tr("settings.addVpn")
icon: "add"
fontSize: Style.fontSizeS
outlined: true
onClicked: {
main.addConnection("vpn");
close();
}
}
NButton {
text: pluginApi?.tr("settings.addWireguard")
icon: "add"
fontSize: Style.fontSizeS
outlined: true
onClicked: {
main.addConnection("wireguard");
close();
}
}
}
}
}
}
@@ -0,0 +1,124 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
import qs.Services.UI
NBox {
id: root
property string name: ""
property string type: ""
property bool isConnected: false
property bool isLoading: false
signal buttonClicked
Layout.fillWidth: true
Layout.leftMargin: Style.marginXS
Layout.rightMargin: Style.marginXS
implicitHeight: Math.round(netColumn.implicitHeight + (Style.marginXL))
color: root.isConnected ? Qt.alpha(Color.mPrimary, 0.15) : Color.mSurface
ColumnLayout {
id: netColumn
width: parent.width - (Style.marginXL)
x: Style.marginM
y: Style.marginM
spacing: Style.marginS
// Main row
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NIcon {
icon: "router"
pointSize: Style.fontSizeXXL
color: root.isConnected ? Color.mPrimary : Color.mOnSurface
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: root.name
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
elide: Text.ElideRight
Layout.fillWidth: true
}
RowLayout {
spacing: Style.marginXS
NText {
text: root.type
pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
Item {
Layout.preferredWidth: Style.marginXXS
}
Rectangle {
visible: root.isConnected
color: Color.mPrimary
radius: height * 0.5
width: Math.round(connectedText.implicitWidth + (Style.marginS * 2))
height: Math.round(connectedText.implicitHeight + (Style.marginXS))
NText {
id: connectedText
anchors.centerIn: parent
text: pluginApi?.tr("common.connected")
pointSize: Style.fontSizeXXS
color: Color.mOnPrimary
}
}
}
}
// Action area
RowLayout {
spacing: Style.marginS
NBusyIndicator {
visible: root.isLoading
running: visible
color: Color.mPrimary
size: Style.baseWidgetSize * 0.5
}
NButton {
visible: root.isConnected
text: pluginApi?.tr("common.disconnect")
outlined: !hovered
fontSize: Style.fontSizeS
backgroundColor: Color.mError
enabled: !root.isLoading
onClicked: {
root.buttonClicked()
}
}
NButton {
visible: !root.isConnected
text: pluginApi?.tr("common.connect")
outlined: !hovered
fontSize: Style.fontSizeS
enabled: !root.isLoading
onClicked: {
root.buttonClicked()
}
}
}
}
}
}
@@ -0,0 +1,40 @@
{
"panel": {
"emptyTitle": "Kein VPN gefunden",
"emptyDescription": "Verwende den Netzwerk-Manager um ein VPN hinzuzufügen"
},
"toast": {
"connectedTo": "Verbunden mit {name}",
"disconnectedFrom": "Von {Name} getrennt",
"connectionError": "Verbindungsaufbau mit {name} fehlgeschlagen",
"disconnectionError": "Die Trennung von {name} ist fehlgeschlagen",
"vpnRemoved": "VPN \"{name}\" wurde entfernt",
"vpnRemoveError": "Der VPN \"{name}\" konnte nicht entfernt werden"
},
"settings": {
"pluginSettings": "Plugin Einstellungen",
"connectedColor": "Verknüpfte Farbe",
"connectedColorDescription": "Wähle die Farbe für das Symbol und den Text aus, die bei bestehender Verbindung angezeigt werden sollen",
"disconnectedColor": "Nicht verknüpfte Farbe",
"disconnectedColorDescription": "Wähle die Farbe für das Symbol und den Text bei unterbrochener Verbindung aus",
"vpnConnections": "VPN Verbindungen",
"noVpnConnections": "Keine VPN Verbindungen konfiguriert",
"addVpn": "VPN hinzufügen",
"addWireguard": "WireGuard hinzufügen",
"disableToastNotifications": "Toast-Benachrichtigungen deaktivieren",
"disableToastNotificationsDescription": "Keine Toast-Benachrichtigungen anzeigen, wenn eine VPN-Verbindung hergestellt oder getrennt wird"
},
"common": {
"vpn": "VPN",
"refresh": "Aktualisieren",
"connected": "Verbunden",
"disconnected": "Getrennt",
"connect": "Verbinden",
"disconnect": "Trennen",
"close": "Schließen",
"settings": "Einstellungen",
"edit": "Bearbeiten",
"delete": "Löschen",
"cancel": "Abbrechen"
}
}
@@ -0,0 +1,40 @@
{
"panel": {
"emptyTitle": "No VPN found",
"emptyDescription": "Use Network Manager to add a VPN"
},
"toast": {
"connectedTo": "Connected to {name}",
"disconnectedFrom": "Disconnected from {name}",
"connectionError": "Failed to connect {name}",
"disconnectionError": "Failed to disconnect {name}",
"vpnRemoved": "VPN \"{name}\" removed",
"vpnRemoveError": "Failed to remove VPN \"{name}\""
},
"settings": {
"pluginSettings": "Plugin settings",
"connectedColor": "Connected color",
"connectedColorDescription": "Chose the color to use for icon and text when connected",
"disconnectedColor": "Disconnected color",
"disconnectedColorDescription": "Chose the color to use for icon and text when disconnected",
"vpnConnections": "VPN Connections",
"noVpnConnections": "No VPN connections configured.",
"addVpn": "Add VPN",
"addWireguard": "Add WireGuard",
"disableToastNotifications": "Disable toast notifications",
"disableToastNotificationsDescription": "Do not show toast notifications when a VPN connects or disconnects"
},
"common": {
"vpn": "VPN",
"refresh": "Refresh",
"connected": "Connected",
"disconnected": "Disconnected",
"connect": "Connect",
"disconnect": "Disconnect",
"close": "Close",
"settings": "Settings",
"edit": "Edit",
"delete": "Delete",
"cancel": "Cancel"
}
}
@@ -0,0 +1,40 @@
{
"panel": {
"emptyTitle": "VPN не найден",
"emptyDescription": "Используйте Менеджер Подключений для добавления VPN"
},
"toast": {
"connectedTo": "Подключиться к {name}",
"disconnectedFrom": "Отключиться от {name}",
"connectionError": "Ошибка подключения к {name}",
"disconnectionError": "Ошибка отключения от {name}",
"vpnRemoved": "VPN \"{name}\" удалён",
"vpnRemoveError": "Ошибка удаления VPN \"{name}\""
},
"settings": {
"pluginSettings": "Настройка плагина",
"connectedColor": "Цвет активного подключения",
"connectedColorDescription": "Выберите цвет значка и текста при активном подключении",
"disconnectedColor": "Цвет отсутствия подключения",
"disconnectedColorDescription": "Выберите цвет значка и текста при отсутствии подключения",
"vpnConnections": "VPN-соединения",
"noVpnConnections": "VPN-подключения не настроены.",
"addVpn": "Добавить VPN",
"addWireguard": "Добавить WireGuard",
"disableToastNotifications": "Отключить уведомления",
"disableToastNotificationsDescription": "Не показывать уведомления о статусе VPN-соединения"
},
"common": {
"vpn": "VPN",
"refresh": "Обновить",
"connected": "Подключено",
"disconnected": "Отключено",
"connect": "Подключить",
"disconnect": "Отключить",
"close": "Закрыть",
"settings": "Настройки",
"edit": "Редактировать",
"delete": "Удалить",
"cancel": "Отмена"
}
}
@@ -0,0 +1,33 @@
{
"id": "network-manager-vpn",
"name": "Network Manager VPN",
"version": "1.3.0",
"minNoctaliaVersion": "3.6.0",
"author": "nZO",
"license": "MIT",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "Connect to VPN via NetworkManager",
"tags": [
"Bar",
"Panel",
"Network"
],
"entryPoints": {
"main": "Main.qml",
"barWidget": "BarWidget.qml",
"controlCenterWidget": "ControlCenterWidget.qml",
"panel": "Panel.qml",
"settings": "Settings.qml"
},
"dependencies": {
"plugins": []
},
"metadata": {
"defaultSettings": {
"displayMode": "onhover",
"disconnectedColor": "none",
"connectedColor": "primary",
"disableToastNotifications": false
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

@@ -0,0 +1,6 @@
{
"displayMode": "onhover",
"disconnectedColor": "none",
"connectedColor": "primary",
"disableToastNotifications": false
}