VPN: Widget Implementation

This commit is contained in:
Georgi Velev
2025-11-20 15:31:06 +02:00
committed by Georgi Velev
parent 1a2ddbb9e3
commit 1cbc793087
14 changed files with 555 additions and 0 deletions
+7
View File
@@ -435,6 +435,8 @@
"enable-bluetooth": "Bluetooth aktivieren",
"enable-dnd": "Nicht stören aktivieren",
"enable-wifi": "WLAN aktivieren",
"connect-vpn": "Mit {name} verbinden",
"disconnect-vpn": "Verbindung zu {name} trennen",
"next": "Nächste/r/s",
"open-calendar": "Kalender öffnen",
"open-display-settings": "Anzeigeeinstellungen",
@@ -2053,6 +2055,10 @@
"disabled": "Deaktiviert",
"disconnected": "Getrennt von '{ssid}'",
"enabled": "Aktiviert"
},
"vpn": {
"connected": "Verbunden mit '{name}'",
"disconnected": "Verbindung zu '{name}' getrennt"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "Wach halten",
"keyboard-layout": "{layout} Tastaturlayout",
"manage-wifi": "WLAN verwalten",
"manage-vpn": "VPN-Verbindungen verwalten",
"microphone-volume-at": "Mikrofonlautstärke bei {volume}%.\nLinksklick für Einstellungen. Rechtsklick zum Stummschalten.\nScrollen zum Ändern der Lautstärke.",
"move-to-center-section": "Zur mittleren Sektion verschieben",
"move-to-left-section": "Zur linken Sektion verschieben",
+7
View File
@@ -435,6 +435,8 @@
"enable-bluetooth": "Enable Bluetooth",
"enable-dnd": "Enable Do Not Disturb",
"enable-wifi": "Enable Wi-Fi",
"connect-vpn": "Connect to {name}",
"disconnect-vpn": "Disconnect {name}",
"next": "Next",
"open-calendar": "Open calendar",
"open-display-settings": "Display settings",
@@ -2053,6 +2055,10 @@
"disabled": "Disabled",
"disconnected": "Disconnected from '{ssid}'",
"enabled": "Enabled"
},
"vpn": {
"connected": "Connected to '{name}'",
"disconnected": "Disconnected from '{name}'"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "Keep awake",
"keyboard-layout": "{layout} keyboard layout",
"manage-wifi": "Manage Wi-Fi",
"manage-vpn": "Manage VPN connections",
"microphone-volume-at": "Microphone volume at {volume}%\nScroll to modify volume",
"move-to-center-section": "Move to center section",
"move-to-left-section": "Move to left section",
+7
View File
@@ -435,6 +435,8 @@
"enable-bluetooth": "Activar Bluetooth",
"enable-dnd": "Activar No molestar",
"enable-wifi": "Activar Wi-Fi",
"connect-vpn": "Conectarse a {name}",
"disconnect-vpn": "Desconectar {name}",
"next": "Siguiente",
"open-calendar": "Abrir calendario",
"open-display-settings": "Configuración de pantalla",
@@ -2053,6 +2055,10 @@
"disabled": "Desactivado",
"disconnected": "Desconectado de '{ssid}'",
"enabled": "Activado"
},
"vpn": {
"connected": "Conectado a '{name}'",
"disconnected": "Desconectado de '{name}'"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "Mantener despierto",
"keyboard-layout": "Distribución de teclado {layout}",
"manage-wifi": "Gestionar Wi-Fi",
"manage-vpn": "Administrar conexiones VPN",
"microphone-volume-at": "Volumen del micrófono al {volume}%.\nClic izquierdo para ajustes. Clic derecho para activar/desactivar el silencio.\nDesplázate para modificar el volumen.",
"move-to-center-section": "Mover a la sección central",
"move-to-left-section": "Mover a la sección izquierda",
+7
View File
@@ -435,6 +435,8 @@
"enable-bluetooth": "Activer le Bluetooth",
"enable-dnd": "Activer le mode Ne pas déranger",
"enable-wifi": "Activer le Wi-Fi",
"connect-vpn": "Se connecter à {name}",
"disconnect-vpn": "Se déconnecter de {name}",
"next": "Suivant",
"open-calendar": "Ouvrir le calendrier",
"open-display-settings": "Paramètres d'affichage",
@@ -2053,6 +2055,10 @@
"disabled": "Désactivé",
"disconnected": "Déconnecté de '{ssid}'",
"enabled": "Activé"
},
"vpn": {
"connected": "Connecté à '{name}'",
"disconnected": "Déconnecté de '{name}'"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "Rester éveillé",
"keyboard-layout": "Disposition du clavier {layout}",
"manage-wifi": "Gérer le Wi-Fi",
"manage-vpn": "Gérer les connexions VPN",
"microphone-volume-at": "Volume du microphone à {volume}%.\nClic gauche pour les paramètres. Clic droit pour activer/désactiver le mode muet.\nFaites défiler pour modifier le volume.",
"move-to-center-section": "Déplacer vers la section centrale",
"move-to-left-section": "Déplacer vers la section de gauche",
+7
View File
@@ -435,6 +435,8 @@
"enable-bluetooth": "Bluetooth inschakelen",
"enable-dnd": "Niet Storen inschakelen",
"enable-wifi": "Wi-Fi inschakelen",
"connect-vpn": "Verbinding maken met {name}",
"disconnect-vpn": "Verbinding met {name} verbreken",
"next": "Volgende",
"open-calendar": "Open agenda",
"open-display-settings": "Beeldscherminstellingen",
@@ -2053,6 +2055,10 @@
"disabled": "Wi-Fi uitgeschakeld",
"disconnected": "Verbinding met '{ssid}' verbroken",
"enabled": "Wi-Fi ingeschakeld"
},
"vpn": {
"connected": "Verbonden met '{name}'",
"disconnected": "Verbinding met '{name}' verbroken"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "Wakker houden",
"keyboard-layout": "{layout}-toetsenbordindeling",
"manage-wifi": "Wi-Fi beheren",
"manage-vpn": "VPN-verbindingen beheren",
"microphone-volume-at": "Microfoonvolume {volume}%\nLinks klikken voor instellingen. Rechts klikken om te dempen.\nScroll om het volume aan te passen.",
"move-to-center-section": "Verplaatsen naar middelste sectie",
"move-to-left-section": "Verplaatsen naar linker sectie",
+7
View File
@@ -435,6 +435,8 @@
"enable-bluetooth": "Ativar Bluetooth",
"enable-dnd": "Ativar Não Perturbe",
"enable-wifi": "Ativar Wi-Fi",
"connect-vpn": "Conectar-se a {name}",
"disconnect-vpn": "Desconectar {name}",
"next": "Próximo(a)",
"open-calendar": "Abrir calendário",
"open-display-settings": "Configurações de exibição",
@@ -2053,6 +2055,10 @@
"disabled": "Desativado",
"disconnected": "Desconectado de '{ssid}'",
"enabled": "Ativado"
},
"vpn": {
"connected": "Conectado a '{name}'",
"disconnected": "Desconectado de '{name}'"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "Manter acordado",
"keyboard-layout": "Layout de teclado {layout}",
"manage-wifi": "Gerenciar Wi-Fi",
"manage-vpn": "Gerenciar conexões VPN",
"microphone-volume-at": "Volume do microfone em {volume}%.\nClique esquerdo para configurações. Clique direito para ativar/desativar o mudo.\nRole para modificar o volume.",
"move-to-center-section": "Mover para a seção central",
"move-to-left-section": "Mover para a seção esquerda",
+7
View File
@@ -435,6 +435,8 @@
"enable-bluetooth": "Включить Bluetooth",
"enable-dnd": "Не беспокоить",
"enable-wifi": "Включить Wi-Fi",
"connect-vpn": "Подключиться к {name}",
"disconnect-vpn": "Отключить {name}",
"next": "Следующий",
"open-calendar": "Открыть календарь",
"open-display-settings": "Настройки экрана",
@@ -2053,6 +2055,10 @@
"disabled": "Отключен",
"disconnected": "Отключено от '{ssid}'",
"enabled": "Включен"
},
"vpn": {
"connected": "Подключено к '{name}'",
"disconnected": "Отключено от '{name}'"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "Не засыпать",
"keyboard-layout": "Раскладка клавиатуры {layout}",
"manage-wifi": "Управление Wi-Fi",
"manage-vpn": "Управлять VPN-подключениями",
"microphone-volume-at": "Громкость микрофона {volume}%\nЛевый клик для настроек. Правый клик для переключения заглушения.\nПрокрутка для изменения громкости.",
"move-to-center-section": "Переместить в центральную секцию",
"move-to-left-section": "Переместить в левую секцию",
+7
View File
@@ -435,6 +435,8 @@
"enable-bluetooth": "Bluetooth'u etkinleştir",
"enable-dnd": "Rahatsız Etmeyin'i Etkinleştir",
"enable-wifi": "Wi-Fi'ı etkinleştir",
"connect-vpn": "{name} bağlantısına bağlan",
"disconnect-vpn": "{name} bağlantısını kes",
"next": "Sonraki",
"open-calendar": "Takvimi aç",
"open-display-settings": "Ekran ayarları",
@@ -2053,6 +2055,10 @@
"disabled": "Devre dışı",
"disconnected": "'{ssid}' bağlantısı kesildi",
"enabled": "Etkin"
},
"vpn": {
"connected": "'{name}' ile bağlantı kuruldu",
"disconnected": "'{name}' bağlantısı kesildi"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "Uyanık kal",
"keyboard-layout": "{layout} klavye düzeni",
"manage-wifi": "Wi-Fi yönet",
"manage-vpn": "VPN bağlantılarını yönet",
"microphone-volume-at": "Mikrofon sesi %{volume}\nAyarlar için sol tık. Sessize almak için sağ tık.\nSesi değiştirmek için kaydırın.",
"move-to-center-section": "Orta bölüme taşı",
"move-to-left-section": "Sol bölüme taşı",
+7
View File
@@ -435,6 +435,8 @@
"enable-bluetooth": "Увімкнути Bluetooth",
"enable-dnd": "Увімкнути режим \"Не турбувати\"",
"enable-wifi": "Увімкнути Wi-Fi",
"connect-vpn": "Підключитися до {name}",
"disconnect-vpn": "Відключити {name}",
"next": "Наступний",
"open-calendar": "Відкрити календар",
"open-display-settings": "Параметри дисплея",
@@ -2053,6 +2055,10 @@
"disabled": "Вимкнено",
"disconnected": "Відключено від '{ssid}'",
"enabled": "Увімкнено"
},
"vpn": {
"connected": "Підключено до '{name}'",
"disconnected": "Відключено від '{name}'"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "Не спати",
"keyboard-layout": "Розкладка клавіатури {layout}",
"manage-wifi": "Керувати Wi-Fi",
"manage-vpn": "Керувати підключеннями VPN",
"microphone-volume-at": "Гучність мікрофона на {volume}%\nЛівий клік для налаштувань. Правий клік для вимкнення звуку.\nПрокрутка для зміни гучності.",
"move-to-center-section": "Перемістити в центральну секцію",
"move-to-left-section": "Перемістити в ліву секцію",
+7
View File
@@ -436,6 +436,8 @@
"enable-dnd": "启用勿扰模式",
"enable-wifi": "启用 Wi-Fi",
"next": "下一首",
"disconnect-vpn": "断开 {name}",
"connect-vpn": "连接 {name}",
"open-calendar": "打开日历",
"open-display-settings": "显示设置",
"open-launcher": "打开启动器",
@@ -2053,6 +2055,10 @@
"disabled": "已禁用",
"disconnected": "已断开与 '{ssid}' 的连接",
"enabled": "已启用"
},
"vpn": {
"connected": "已连接到“{name}”",
"disconnected": "已断开与“{name}”的连接"
}
},
"tooltips": {
@@ -2076,6 +2082,7 @@
"keep-awake": "保持唤醒",
"keyboard-layout": "{layout} 键盘布局",
"manage-wifi": "管理 Wi-Fi",
"manage-vpn": "管理 VPN 连接",
"microphone-volume-at": "麦克风音量 {volume}%\n左键点击进入设置。右键点击切换静音。\n滚动滚轮调节音量。",
"move-to-center-section": "移动到中央部分",
"move-to-left-section": "移动到左侧部分",
+143
View File
@@ -0,0 +1,143 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Commons
import qs.Modules.Bar.Extras
import qs.Services.Networking
import qs.Services.UI
import qs.Widgets
Item {
id: root
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex];
}
}
return {};
}
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode
implicitWidth: pill.width
implicitHeight: pill.height
NPopupContextMenu {
id: contextMenu
model: {
const items = [];
const active = VPNService.activeConnections;
for (let i = 0; i < active.length; ++i) {
const conn = active[i];
items.push({
"label": I18n.tr("context-menu.disconnect-vpn", {
"name": conn.name
}),
"action": "disconnect:" + conn.uuid,
"icon": "shield-off"
});
}
const inactive = VPNService.inactiveConnections;
for (let i = 0; i < inactive.length; ++i) {
const conn = inactive[i];
items.push({
"label": I18n.tr("context-menu.connect-vpn", {
"name": conn.name
}),
"action": "connect:" + conn.uuid,
"icon": "shield-lock"
});
}
items.push({
"label": I18n.tr("context-menu.widget-settings"),
"action": "widget-settings",
"icon": "settings"
});
return items;
}
onTriggered: action => {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.close();
}
if (!action) {
return;
}
if (action === "widget-settings") {
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
return;
}
if (action.startsWith("connect:")) {
const uuid = action.substring("connect:".length);
VPNService.connect(uuid);
return;
}
if (action.startsWith("disconnect:")) {
const uuid = action.substring("disconnect:".length);
VPNService.disconnect(uuid);
}
}
}
BarPill {
id: pill
screen: root.screen
density: Settings.data.bar.density
oppositeDirection: BarService.getPillDirection(root)
icon: VPNService.hasActiveConnection ? "shield-lock" : "shield"
text: {
if (VPNService.activeConnections.length > 0) {
return VPNService.activeConnections[0].name;
}
if (VPNService.connectingUuid) {
const pending = VPNService.connections[VPNService.connectingUuid];
if (pending) {
return pending.name;
}
}
return "";
}
suffix: {
if (VPNService.activeConnections.length > 1) {
return ` + ${VPNService.activeConnections.length - 1}`;
}
return "";
}
autoHide: false
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
forceClose: isBarVertical || root.displayMode === "alwaysHide" || !pill.text
onClicked: PanelService.getPanel("vpnPanel", screen)?.toggle(this)
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(pill, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
tooltipText: {
if (pill.text !== "") {
return pill.text;
}
return I18n.tr("tooltips.manage-vpn");
}
}
}
@@ -0,0 +1,43 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginM
property var widgetData: null
property var widgetMetadata: null
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
function saveSettings() {
var settings = Object.assign({}, widgetData || {});
settings.displayMode = valueDisplayMode;
return settings;
}
NComboBox {
label: I18n.tr("bar.widget-settings.battery.display-mode.label")
description: I18n.tr("bar.widget-settings.battery.display-mode.description")
minimumWidth: 134
model: [
{
"key": "onhover",
"name": I18n.tr("options.display-mode.on-hover")
},
{
"key": "alwaysShow",
"name": I18n.tr("options.display-mode.always-show")
},
{
"key": "alwaysHide",
"name": I18n.tr("options.display-mode.always-hide")
}
]
currentKey: root.valueDisplayMode
onSelected: key => root.valueDisplayMode = key
}
}
+290
View File
@@ -0,0 +1,290 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services.UI
Singleton {
id: root
property var connections: ({})
property bool refreshing: false
property bool connecting: false
property bool disconnecting: false
property string connectingUuid: ""
property string disconnectingUuid: ""
property string lastError: ""
property bool refreshPending: false
readonly property var activeConnections: {
const result = [];
const map = connections;
for (const key in map) {
const conn = map[key];
if (conn && conn.active) {
result.push(conn);
}
}
return result;
}
readonly property var inactiveConnections: {
const result = [];
const map = connections;
for (const key in map) {
const conn = map[key];
if (conn && !conn.active) {
result.push(conn);
}
}
return result;
}
readonly property bool hasActiveConnection: activeConnections.length > 0
Timer {
id: refreshTimer
interval: 5000
running: true
repeat: true
onTriggered: refresh()
}
Timer {
id: delayedRefreshTimer
interval: 1000
repeat: false
onTriggered: refresh()
}
Component.onCompleted: {
Logger.i("VPN", "Service started");
refresh();
}
function refresh() {
if (refreshing) {
refreshPending = true;
return;
}
refreshing = true;
lastError = "";
refreshProcess.running = true;
}
function connect(uuid) {
if (connecting || !uuid) {
return;
}
const conn = connections[uuid];
if (!conn) {
return;
}
connecting = true;
connectingUuid = uuid;
lastError = "";
connectProcess.uuid = uuid;
connectProcess.name = conn.name;
connectProcess.running = true;
}
function disconnect(uuid) {
if (disconnecting || !uuid) {
return;
}
const conn = connections[uuid];
if (!conn) {
return;
}
disconnecting = true;
disconnectingUuid = uuid;
lastError = "";
disconnectProcess.uuid = uuid;
disconnectProcess.name = conn.name;
disconnectProcess.running = true;
}
function toggle(uuid) {
const conn = connections[uuid];
if (!conn) {
return;
}
if (conn.active) {
disconnect(uuid);
} else {
connect(uuid);
}
}
function setConnection(uuid, data) {
if (!uuid) {
return;
}
const map = Object.assign({}, connections);
if (map[uuid]) {
map[uuid] = Object.assign({}, map[uuid], data);
connections = map;
}
}
function scheduleRefresh(interval) {
delayedRefreshTimer.interval = interval;
delayedRefreshTimer.restart();
}
Process {
id: refreshProcess
running: false
command: ["nmcli", "-t", "-f", "NAME,UUID,TYPE,DEVICE", "connection", "show"]
stdout: StdioCollector {
onStreamFinished: {
const lines = text.split("\n");
const map = {};
for (let i = 0; i < lines.length; ++i) {
const line = lines[i].trim();
if (!line) {
continue;
}
const lastColonIdx = line.lastIndexOf(":");
if (lastColonIdx === -1) {
continue;
}
const device = line.substring(lastColonIdx + 1);
const remaining = line.substring(0, lastColonIdx);
const secondLastColonIdx = remaining.lastIndexOf(":");
if (secondLastColonIdx === -1) {
continue;
}
const type = remaining.substring(secondLastColonIdx + 1);
if (type !== "vpn") {
continue;
}
const remaining2 = remaining.substring(0, secondLastColonIdx);
const thirdLastColonIdx = remaining2.lastIndexOf(":");
if (thirdLastColonIdx === -1) {
continue;
}
const uuid = remaining2.substring(thirdLastColonIdx + 1);
const name = remaining2.substring(0, thirdLastColonIdx);
if (!uuid || !name) {
continue;
}
const active = device && device !== "--";
map[uuid] = {
"uuid": uuid,
"name": name,
"device": device,
"active": active
};
}
connections = map;
const pending = refreshPending;
refreshing = false;
refreshPending = false;
if (pending) {
scheduleRefresh(200);
}
}
}
stderr: StdioCollector {
onStreamFinished: {
const pending = refreshPending;
refreshing = false;
refreshPending = false;
if (text.trim()) {
lastError = text.split("\n")[0].trim();
Logger.w("VPN", "Refresh error: " + text);
}
if (pending) {
scheduleRefresh(2000);
}
}
}
}
Process {
id: connectProcess
property string uuid: ""
property string name: ""
running: false
command: ["nmcli", "connection", "up", "uuid", uuid]
stdout: StdioCollector {
onStreamFinished: {
const output = text.trim();
if (!output || (!output.includes("successfully activated") && !output.includes("Connection successfully"))) {
return;
}
setConnection(connectProcess.uuid, {
"active": true
});
connecting = false;
connectingUuid = "";
lastError = "";
Logger.i("VPN", "Connected to " + connectProcess.name);
ToastService.showNotice(connectProcess.name, I18n.tr("toast.vpn.connected", {
"name": connectProcess.name
}), "shield-lock");
scheduleRefresh(1000);
}
}
stderr: StdioCollector {
onStreamFinished: {
const trimmed = text.trim();
if (trimmed) {
lastError = trimmed.split("\n")[0].trim();
Logger.w("VPN", "Connect error: " + trimmed);
ToastService.showWarning(connectProcess.name, lastError);
}
connecting = false;
connectingUuid = "";
}
}
}
Process {
id: disconnectProcess
property string uuid: ""
property string name: ""
running: false
command: ["nmcli", "connection", "down", "uuid", uuid]
stdout: StdioCollector {
onStreamFinished: {
Logger.i("VPN", "Disconnected from " + disconnectProcess.name);
setConnection(disconnectProcess.uuid, {
"active": false,
"device": ""
});
disconnecting = false;
disconnectingUuid = "";
lastError = "";
ToastService.showNotice(disconnectProcess.name, I18n.tr("toast.vpn.disconnected", {
"name": disconnectProcess.name
}), "shield-off");
scheduleRefresh(1000);
}
}
stderr: StdioCollector {
onStreamFinished: {
const trimmed = text.trim();
if (trimmed) {
lastError = trimmed.split("\n")[0].trim();
Logger.w("VPN", "Disconnect error: " + trimmed);
ToastService.showWarning(disconnectProcess.name, lastError);
}
disconnecting = false;
disconnectingUuid = "";
}
}
}
}
+9
View File
@@ -36,6 +36,7 @@ Singleton {
"TaskbarGrouped": taskbarGroupedComponent,
"Tray": trayComponent,
"Volume": volumeComponent,
"VPN": vpnComponent,
"WiFi": wiFiComponent,
"WallpaperSelector": wallpaperSelectorComponent,
"Workspace": workspaceComponent
@@ -62,6 +63,7 @@ Singleton {
"TaskbarGrouped": "WidgetSettings/TaskbarGroupedSettings.qml",
"Tray": "WidgetSettings/TraySettings.qml",
"Volume": "WidgetSettings/VolumeSettings.qml",
"VPN": "WidgetSettings/VPNSettings.qml",
"WiFi": "WidgetSettings/WiFiSettings.qml",
"Workspace": "WidgetSettings/WorkspaceSettings.qml"
})
@@ -200,6 +202,10 @@ Singleton {
"pinned": [],
"drawerEnabled": true
},
"VPN": {
"allowUserSettings": true,
"displayMode": "onhover"
},
"WiFi": {
"allowUserSettings": true,
"displayMode": "onhover"
@@ -289,6 +295,9 @@ Singleton {
property Component volumeComponent: Component {
Volume {}
}
property Component vpnComponent: Component {
VPN {}
}
property Component wiFiComponent: Component {
WiFi {}
}