1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 23:42:51 -05:00

Add VPN widget for control center

This commit is contained in:
bbedward
2025-10-05 21:28:52 -04:00
parent c092cd2921
commit 2dc310dcbc
4 changed files with 447 additions and 70 deletions

View File

@@ -0,0 +1,241 @@
import QtQuick
import QtQuick.Layouts
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
ccWidgetIcon: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN"
ccWidgetSecondaryText: {
if (!VpnService.connected)
return "Disconnected"
const names = VpnService.activeNames || []
if (names.length <= 1)
return names[0] || "Connected"
return names[0] + " +" + (names.length - 1)
}
ccWidgetIsActive: VpnService.connected
onCcWidgetToggled: {
if (VpnService.connected) {
VpnService.disconnectAllActive()
} else if (VpnService.profiles.length > 0) {
VpnService.connect(VpnService.profiles[0].uuid)
}
}
ccDetailContent: Component {
Rectangle {
id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: detailColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
RowLayout {
spacing: Theme.spacingS
width: parent.width
StyledText {
text: {
if (!VpnService.connected)
return "Active: None"
const names = VpnService.activeNames || []
if (names.length <= 1)
return "Active: " + (names[0] || "VPN")
return "Active: " + names[0] + " +" + (names.length - 1)
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Item {
Layout.fillWidth: true
}
Rectangle {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: VpnService.connected
width: 110
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "link_off"
size: Theme.fontSizeSmall
color: Theme.surfaceText
}
StyledText {
text: "Disconnect"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: discAllArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
}
}
}
Rectangle {
height: 1
width: parent.width
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
DankFlickable {
width: parent.width
height: 160
contentHeight: listCol.height
clip: true
Column {
id: listCol
width: parent.width
spacing: Theme.spacingXS
Item {
width: parent.width
height: VpnService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "playlist_remove"
size: 36
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: "No VPN profiles found"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: "Add a VPN in NetworkManager"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Repeater {
model: VpnService.profiles
delegate: Rectangle {
required property var modelData
width: parent ? parent.width : 300
height: 50
radius: Theme.cornerRadius
color: rowArea.containsMouse ? Theme.primaryHoverLight : (VpnService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: VpnService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
Column {
spacing: 2
Layout.alignment: Qt.AlignVCenter
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
text: {
if (modelData.type === "wireguard")
return "WireGuard"
const svc = modelData.serviceType || ""
if (svc.indexOf("openvpn") !== -1)
return "OpenVPN"
if (svc.indexOf("wireguard") !== -1)
return "WireGuard (plugin)"
if (svc.indexOf("openconnect") !== -1)
return "OpenConnect"
if (svc.indexOf("fortissl") !== -1 || svc.indexOf("forti") !== -1)
return "Fortinet"
if (svc.indexOf("strongswan") !== -1)
return "IPsec (strongSwan)"
if (svc.indexOf("libreswan") !== -1)
return "IPsec (Libreswan)"
if (svc.indexOf("l2tp") !== -1)
return "L2TP/IPsec"
if (svc.indexOf("pptp") !== -1)
return "PPTP"
if (svc.indexOf("vpnc") !== -1)
return "Cisco (vpnc)"
if (svc.indexOf("sstp") !== -1)
return "SSTP"
if (svc)
return svc.split('.').pop()
return "VPN"
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
Item {
Layout.fillWidth: true
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
}
}
}
}
}
}
}
}
}

View File

@@ -2,6 +2,7 @@ import QtQuick
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Modules.ControlCenter.Details import qs.Modules.ControlCenter.Details
import qs.Modules.ControlCenter.Models
Item { Item {
id: root id: root
@@ -11,6 +12,7 @@ Item {
property var bluetoothCodecSelector: null property var bluetoothCodecSelector: null
property var pluginDetailInstance: null property var pluginDetailInstance: null
property var widgetModel: null
Loader { Loader {
id: pluginDetailLoader id: pluginDetailLoader
@@ -42,6 +44,23 @@ Item {
return return
} }
if (root.expandedSection.startsWith("builtin_")) {
const builtinId = root.expandedSection
let builtinInstance = null
if (builtinId === "builtin_vpn" && widgetModel?.vpnBuiltinInstance) {
builtinInstance = widgetModel.vpnBuiltinInstance
}
if (!builtinInstance || !builtinInstance.ccDetailContent) {
return
}
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent
pluginDetailLoader.active = parent.height > 0
return
}
if (root.expandedSection.startsWith("plugin_")) { if (root.expandedSection.startsWith("plugin_")) {
const pluginId = root.expandedSection.replace("plugin_", "") const pluginId = root.expandedSection.replace("plugin_", "")
const pluginComponent = PluginService.pluginWidgetComponents[pluginId] const pluginComponent = PluginService.pluginWidgetComponents[pluginId]

View File

@@ -62,7 +62,8 @@ Column {
property var rowWidgets: modelData property var rowWidgets: modelData
property bool isSliderOnlyRow: { property bool isSliderOnlyRow: {
const widgets = rowWidgets || [] const widgets = rowWidgets || []
if (widgets.length === 0) return false if (widgets.length === 0)
return false
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider") return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
} }
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0 topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
@@ -121,7 +122,9 @@ Column {
widgetComponent: { widgetComponent: {
const id = modelData.id || "" const id = modelData.id || ""
if (id.startsWith("plugin_")) { if (id.startsWith("builtin_")) {
return builtinPluginWidgetComponent
} else if (id.startsWith("plugin_")) {
return pluginWidgetComponent return pluginWidgetComponent
} else if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") { } else if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
return compoundPillComponent return compoundPillComponent
@@ -153,7 +156,8 @@ Column {
width: parent.width width: parent.width
height: active ? (250 + Theme.spacingS) : 0 height: active ? (250 + Theme.spacingS) : 0
property bool active: { property bool active: {
if (root.expandedSection === "") return false if (root.expandedSection === "")
return false
if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) { if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) {
const expandedInstanceId = root.expandedWidgetData.instanceId const expandedInstanceId = root.expandedWidgetData.instanceId
@@ -166,6 +170,7 @@ Column {
expandedSection: root.expandedSection expandedSection: root.expandedSection
expandedWidgetData: root.expandedWidgetData expandedWidgetData: root.expandedWidgetData
bluetoothCodecSelector: root.bluetoothCodecSelector bluetoothCodecSelector: root.bluetoothCodecSelector
widgetModel: root.model
} }
} }
} }
@@ -347,7 +352,8 @@ Column {
} }
enabled: widgetDef?.enabled ?? true enabled: widgetDef?.enabled ?? true
onToggled: { onToggled: {
if (root.editMode) return if (root.editMode)
return
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "wifi": case "wifi":
{ {
@@ -380,11 +386,13 @@ Column {
} }
} }
onExpandClicked: { onExpandClicked: {
if (root.editMode) return if (root.editMode)
return
root.expandClicked(widgetData, widgetIndex) root.expandClicked(widgetData, widgetIndex)
} }
onWheelEvent: function (wheelEvent) { onWheelEvent: function (wheelEvent) {
if (root.editMode) return if (root.editMode)
return
const id = widgetData.id || "" const id = widgetData.id || ""
if (id === "audioOutput") { if (id === "audioOutput") {
if (!AudioService.sink || !AudioService.sink.audio) if (!AudioService.sink || !AudioService.sink.audio)
@@ -539,7 +547,8 @@ Column {
} }
iconRotation: { iconRotation: {
if (widgetData.id !== "darkMode") return 0 if (widgetData.id !== "darkMode")
return 0
if (darkModeTransitionPending) { if (darkModeTransitionPending) {
return SessionData.isLightMode ? 180 : 0 return SessionData.isLightMode ? 180 : 0
} }
@@ -561,7 +570,7 @@ Column {
} }
} }
enabled: !root.editMode enabled: !root.editMode
onClicked: { onClicked: {
if (root.editMode) if (root.editMode)
@@ -617,7 +626,8 @@ enabled: !root.editMode
} }
iconRotation: { iconRotation: {
if (widgetData.id !== "darkMode") return 0 if (widgetData.id !== "darkMode")
return 0
if (darkModeTransitionPending) { if (darkModeTransitionPending) {
return SessionData.isLightMode ? 180 : 0 return SessionData.isLightMode ? 180 : 0
} }
@@ -639,7 +649,7 @@ enabled: !root.editMode
} }
} }
enabled: !root.editMode enabled: !root.editMode
onClicked: { onClicked: {
if (root.editMode) if (root.editMode)
@@ -702,6 +712,111 @@ enabled: !root.editMode
} }
} }
Component {
id: builtinPluginWidgetComponent
Loader {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property int widgetWidth: widgetData.width || 50
width: parent.width
height: 60
property var builtinInstance: {
const id = widgetData.id || ""
if (id === "builtin_vpn") {
return root.model?.vpnBuiltinInstance
}
return null
}
sourceComponent: {
if (!builtinInstance)
return null
const hasDetail = builtinInstance.ccDetailContent !== null
if (widgetWidth <= 25) {
return builtinSmallToggleComponent
} else if (hasDetail) {
return builtinCompoundPillComponent
} else {
return builtinToggleComponent
}
}
}
}
Component {
id: builtinCompoundPillComponent
CompoundPill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var builtinInstance: parent.builtinInstance
iconName: builtinInstance?.ccWidgetIcon || "extension"
primaryText: builtinInstance?.ccWidgetPrimaryText || "Built-in"
secondaryText: builtinInstance?.ccWidgetSecondaryText || ""
isActive: builtinInstance?.ccWidgetIsActive || false
onToggled: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetToggled()
}
}
onExpandClicked: {
if (root.editMode)
return
root.expandClicked(widgetData, widgetIndex)
}
}
}
Component {
id: builtinToggleComponent
ToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var builtinInstance: parent.builtinInstance
iconName: builtinInstance?.ccWidgetIcon || "extension"
text: builtinInstance?.ccWidgetPrimaryText || "Built-in"
isActive: builtinInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetToggled()
}
}
}
}
Component {
id: builtinSmallToggleComponent
SmallToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var builtinInstance: parent.builtinInstance
iconName: builtinInstance?.ccWidgetIcon || "extension"
isActive: builtinInstance?.ccWidgetIsActive || false
enabled: !root.editMode
onClicked: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetToggled()
}
}
}
}
Component { Component {
id: pluginWidgetComponent id: pluginWidgetComponent
Loader { Loader {
@@ -715,7 +830,8 @@ enabled: !root.editMode
property string pluginId: widgetData.id?.replace("plugin_", "") || "" property string pluginId: widgetData.id?.replace("plugin_", "") || ""
sourceComponent: { sourceComponent: {
if (!pluginInstance) return null if (!pluginInstance)
return null
const hasDetail = pluginInstance.ccDetailContent !== null const hasDetail = pluginInstance.ccDetailContent !== null
@@ -730,20 +846,20 @@ enabled: !root.editMode
Component.onCompleted: { Component.onCompleted: {
Qt.callLater(() => { Qt.callLater(() => {
const pluginComponent = PluginService.pluginWidgetComponents[pluginId] const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
if (pluginComponent) { if (pluginComponent) {
const instance = pluginComponent.createObject(null, { const instance = pluginComponent.createObject(null, {
pluginId: pluginId, "pluginId": pluginId,
pluginService: PluginService, "pluginService": PluginService,
visible: false, "visible": false,
width: 0, "width": 0,
height: 0 "height": 0
}) })
if (instance) { if (instance) {
pluginInstance = instance pluginInstance = instance
} }
} }
}) })
} }
Connections { Connections {
@@ -776,14 +892,16 @@ enabled: !root.editMode
isActive: pluginInstance?.ccWidgetIsActive || false isActive: pluginInstance?.ccWidgetIsActive || false
onToggled: { onToggled: {
if (root.editMode) return if (root.editMode)
return
if (pluginInstance) { if (pluginInstance) {
pluginInstance.ccWidgetToggled() pluginInstance.ccWidgetToggled()
} }
} }
onExpandClicked: { onExpandClicked: {
if (root.editMode) return if (root.editMode)
return
root.expandClicked(widgetData, widgetIndex) root.expandClicked(widgetData, widgetIndex)
} }
} }
@@ -804,7 +922,8 @@ enabled: !root.editMode
enabled: !root.editMode enabled: !root.editMode
onClicked: { onClicked: {
if (root.editMode) return if (root.editMode)
return
if (pluginInstance) { if (pluginInstance) {
pluginInstance.ccWidgetToggled() pluginInstance.ccWidgetToggled()
} }
@@ -825,7 +944,8 @@ enabled: !root.editMode
enabled: !root.editMode enabled: !root.editMode
onClicked: { onClicked: {
if (root.editMode) return if (root.editMode)
return
if (pluginInstance && pluginInstance.ccDetailContent) { if (pluginInstance && pluginInstance.ccDetailContent) {
root.expandClicked(widgetData, widgetIndex) root.expandClicked(widgetData, widgetIndex)
} else if (pluginInstance) { } else if (pluginInstance) {

View File

@@ -1,13 +1,15 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Modules.ControlCenter.BuiltinPlugins
import "../utils/widgets.js" as WidgetUtils import "../utils/widgets.js" as WidgetUtils
QtObject { QtObject {
id: root id: root
readonly property var coreWidgetDefinitions: [ property var vpnBuiltinInstance: VpnWidget {}
{
readonly property var coreWidgetDefinitions: [{
"id": "nightMode", "id": "nightMode",
"text": "Night Mode", "text": "Night Mode",
"description": "Blue light filter", "description": "Blue light filter",
@@ -15,32 +17,28 @@ QtObject {
"type": "toggle", "type": "toggle",
"enabled": DisplayService.automationAvailable, "enabled": DisplayService.automationAvailable,
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined "warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
}, }, {
{
"id": "darkMode", "id": "darkMode",
"text": "Dark Mode", "text": "Dark Mode",
"description": "System theme toggle", "description": "System theme toggle",
"icon": "contrast", "icon": "contrast",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, }, {
{
"id": "doNotDisturb", "id": "doNotDisturb",
"text": "Do Not Disturb", "text": "Do Not Disturb",
"description": "Block notifications", "description": "Block notifications",
"icon": "do_not_disturb_on", "icon": "do_not_disturb_on",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, }, {
{
"id": "idleInhibitor", "id": "idleInhibitor",
"text": "Keep Awake", "text": "Keep Awake",
"description": "Prevent screen timeout", "description": "Prevent screen timeout",
"icon": "motion_sensor_active", "icon": "motion_sensor_active",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, }, {
{
"id": "wifi", "id": "wifi",
"text": "Network", "text": "Network",
"description": "Wi-Fi and Ethernet connection", "description": "Wi-Fi and Ethernet connection",
@@ -48,8 +46,7 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": NetworkService.wifiAvailable, "enabled": NetworkService.wifiAvailable,
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined "warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
}, }, {
{
"id": "bluetooth", "id": "bluetooth",
"text": "Bluetooth", "text": "Bluetooth",
"description": "Device connections", "description": "Device connections",
@@ -57,32 +54,28 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": BluetoothService.available, "enabled": BluetoothService.available,
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined "warning": !BluetoothService.available ? "Bluetooth not available" : undefined
}, }, {
{
"id": "audioOutput", "id": "audioOutput",
"text": "Audio Output", "text": "Audio Output",
"description": "Speaker settings", "description": "Speaker settings",
"icon": "volume_up", "icon": "volume_up",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, }, {
{
"id": "audioInput", "id": "audioInput",
"text": "Audio Input", "text": "Audio Input",
"description": "Microphone settings", "description": "Microphone settings",
"icon": "mic", "icon": "mic",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, }, {
{
"id": "volumeSlider", "id": "volumeSlider",
"text": "Volume Slider", "text": "Volume Slider",
"description": "Audio volume control", "description": "Audio volume control",
"icon": "volume_up", "icon": "volume_up",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, }, {
{
"id": "brightnessSlider", "id": "brightnessSlider",
"text": "Brightness Slider", "text": "Brightness Slider",
"description": "Display brightness control", "description": "Display brightness control",
@@ -90,24 +83,21 @@ QtObject {
"type": "slider", "type": "slider",
"enabled": DisplayService.brightnessAvailable, "enabled": DisplayService.brightnessAvailable,
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined "warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined
}, }, {
{
"id": "inputVolumeSlider", "id": "inputVolumeSlider",
"text": "Input Volume Slider", "text": "Input Volume Slider",
"description": "Microphone volume control", "description": "Microphone volume control",
"icon": "mic", "icon": "mic",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, }, {
{
"id": "battery", "id": "battery",
"text": "Battery", "text": "Battery",
"description": "Battery and power management", "description": "Battery and power management",
"icon": "battery_std", "icon": "battery_std",
"type": "action", "type": "action",
"enabled": true "enabled": true
}, }, {
{
"id": "diskUsage", "id": "diskUsage",
"text": "Disk Usage", "text": "Disk Usage",
"description": "Filesystem usage monitoring", "description": "Filesystem usage monitoring",
@@ -116,22 +106,29 @@ QtObject {
"enabled": DgopService.dgopAvailable, "enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined, "warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
"allowMultiple": true "allowMultiple": true
}, }, {
{
"id": "colorPicker", "id": "colorPicker",
"text": "Color Picker", "text": "Color Picker",
"description": "Choose colors from palette", "description": "Choose colors from palette",
"icon": "palette", "icon": "palette",
"type": "action", "type": "action",
"enabled": true "enabled": true
} }, {
] "id": "builtin_vpn",
"text": "VPN",
"description": "VPN connections",
"icon": "vpn_key",
"type": "builtin_plugin",
"enabled": VpnService.available,
"warning": !VpnService.available ? "VPN not available" : undefined,
"isBuiltinPlugin": true
}]
function getPluginWidgets() { function getPluginWidgets() {
const plugins = [] const plugins = []
const loadedPlugins = PluginService.getLoadedPlugins() const loadedPlugins = PluginService.getLoadedPlugins()
for (let i = 0; i < loadedPlugins.length; i++) { for (var i = 0; i < loadedPlugins.length; i++) {
const plugin = loadedPlugins[i] const plugin = loadedPlugins[i]
if (plugin.type === "daemon") { if (plugin.type === "daemon") {
@@ -156,15 +153,15 @@ QtObject {
} }
plugins.push({ plugins.push({
"id": "plugin_" + plugin.id, "id": "plugin_" + plugin.id,
"pluginId": plugin.id, "pluginId": plugin.id,
"text": plugin.name || "Plugin", "text": plugin.name || "Plugin",
"description": plugin.description || "", "description": plugin.description || "",
"icon": plugin.icon || "extension", "icon": plugin.icon || "extension",
"type": "plugin", "type": "plugin",
"enabled": true, "enabled": true,
"isPlugin": true "isPlugin": true
}) })
} }
return plugins return plugins
@@ -199,4 +196,4 @@ QtObject {
function clearAll() { function clearAll() {
WidgetUtils.clearAll() WidgetUtils.clearAll()
} }
} }