mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-29 07:52:50 -05:00
plugins/desktop-widgets: create a new "desktop" widget plugin type
- Draggable per-monitor background layer widgets - Add basic dms version checks on plugins - Clock: built-in clock desktop plugin - dgop: built-in system monitor desktop plugin
This commit is contained in:
@@ -406,6 +406,128 @@ Singleton {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
property bool desktopClockEnabled: false
|
||||||
|
property string desktopClockStyle: "analog"
|
||||||
|
property real desktopClockTransparency: 0.8
|
||||||
|
property string desktopClockColorMode: "primary"
|
||||||
|
property color desktopClockCustomColor: "#ffffff"
|
||||||
|
property bool desktopClockShowDate: true
|
||||||
|
property bool desktopClockShowAnalogNumbers: false
|
||||||
|
property real desktopClockX: -1
|
||||||
|
property real desktopClockY: -1
|
||||||
|
property real desktopClockWidth: 280
|
||||||
|
property real desktopClockHeight: 180
|
||||||
|
property var desktopClockDisplayPreferences: ["all"]
|
||||||
|
|
||||||
|
property bool systemMonitorEnabled: false
|
||||||
|
property bool systemMonitorShowHeader: true
|
||||||
|
property real systemMonitorTransparency: 0.8
|
||||||
|
property string systemMonitorColorMode: "primary"
|
||||||
|
property color systemMonitorCustomColor: "#ffffff"
|
||||||
|
property bool systemMonitorShowCpu: true
|
||||||
|
property bool systemMonitorShowCpuGraph: true
|
||||||
|
property bool systemMonitorShowCpuTemp: true
|
||||||
|
property bool systemMonitorShowGpuTemp: false
|
||||||
|
property string systemMonitorGpuPciId: ""
|
||||||
|
property bool systemMonitorShowMemory: true
|
||||||
|
property bool systemMonitorShowMemoryGraph: true
|
||||||
|
property bool systemMonitorShowNetwork: true
|
||||||
|
property bool systemMonitorShowNetworkGraph: true
|
||||||
|
property bool systemMonitorShowDisk: true
|
||||||
|
property bool systemMonitorShowTopProcesses: false
|
||||||
|
property int systemMonitorTopProcessCount: 3
|
||||||
|
property string systemMonitorTopProcessSortBy: "cpu"
|
||||||
|
property string systemMonitorLayoutMode: "auto"
|
||||||
|
property int systemMonitorGraphInterval: 60
|
||||||
|
property real systemMonitorX: -1
|
||||||
|
property real systemMonitorY: -1
|
||||||
|
property real systemMonitorWidth: 320
|
||||||
|
property real systemMonitorHeight: 480
|
||||||
|
property var systemMonitorDisplayPreferences: ["all"]
|
||||||
|
property var systemMonitorVariants: []
|
||||||
|
property var desktopWidgetPositions: ({})
|
||||||
|
|
||||||
|
function getDesktopWidgetPosition(pluginId, screenKey, property, defaultValue) {
|
||||||
|
const pos = desktopWidgetPositions?.[pluginId]?.[screenKey]?.[property];
|
||||||
|
return pos !== undefined ? pos : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDesktopWidgetPosition(pluginId, screenKey, updates) {
|
||||||
|
const allPositions = JSON.parse(JSON.stringify(desktopWidgetPositions || {}));
|
||||||
|
if (!allPositions[pluginId])
|
||||||
|
allPositions[pluginId] = {};
|
||||||
|
allPositions[pluginId][screenKey] = Object.assign({}, allPositions[pluginId][screenKey] || {}, updates);
|
||||||
|
desktopWidgetPositions = allPositions;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSystemMonitorVariants() {
|
||||||
|
return systemMonitorVariants || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSystemMonitorVariant(name, config) {
|
||||||
|
const id = "sysmon_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
|
||||||
|
const variant = {
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
config: config || getDefaultSystemMonitorConfig()
|
||||||
|
};
|
||||||
|
const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
|
||||||
|
variants.push(variant);
|
||||||
|
systemMonitorVariants = variants;
|
||||||
|
saveSettings();
|
||||||
|
return variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSystemMonitorVariant(variantId, updates) {
|
||||||
|
const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
|
||||||
|
const idx = variants.findIndex(v => v.id === variantId);
|
||||||
|
if (idx === -1)
|
||||||
|
return;
|
||||||
|
Object.assign(variants[idx], updates);
|
||||||
|
systemMonitorVariants = variants;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSystemMonitorVariant(variantId) {
|
||||||
|
const variants = (systemMonitorVariants || []).filter(v => v.id !== variantId);
|
||||||
|
systemMonitorVariants = variants;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSystemMonitorVariant(variantId) {
|
||||||
|
return (systemMonitorVariants || []).find(v => v.id === variantId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultSystemMonitorConfig() {
|
||||||
|
return {
|
||||||
|
showHeader: true,
|
||||||
|
transparency: 0.8,
|
||||||
|
colorMode: "primary",
|
||||||
|
customColor: "#ffffff",
|
||||||
|
showCpu: true,
|
||||||
|
showCpuGraph: true,
|
||||||
|
showCpuTemp: true,
|
||||||
|
showGpuTemp: false,
|
||||||
|
gpuPciId: "",
|
||||||
|
showMemory: true,
|
||||||
|
showMemoryGraph: true,
|
||||||
|
showNetwork: true,
|
||||||
|
showNetworkGraph: true,
|
||||||
|
showDisk: true,
|
||||||
|
showTopProcesses: false,
|
||||||
|
topProcessCount: 3,
|
||||||
|
topProcessSortBy: "cpu",
|
||||||
|
layoutMode: "auto",
|
||||||
|
graphInterval: 60,
|
||||||
|
x: -1,
|
||||||
|
y: -1,
|
||||||
|
width: 320,
|
||||||
|
height: 480,
|
||||||
|
displayPreferences: ["all"]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
signal forceDankBarLayoutRefresh
|
signal forceDankBarLayoutRefresh
|
||||||
signal forceDockLayoutRefresh
|
signal forceDockLayoutRefresh
|
||||||
signal widgetDataChanged
|
signal widgetDataChanged
|
||||||
|
|||||||
@@ -300,7 +300,47 @@ var SPEC = {
|
|||||||
scrollEnabled: true,
|
scrollEnabled: true,
|
||||||
scrollXBehavior: "column",
|
scrollXBehavior: "column",
|
||||||
scrollYBehavior: "workspace"
|
scrollYBehavior: "workspace"
|
||||||
}], onChange: "updateBarConfigs" }
|
}], onChange: "updateBarConfigs" },
|
||||||
|
|
||||||
|
desktopClockEnabled: { def: false },
|
||||||
|
desktopClockStyle: { def: "analog" },
|
||||||
|
desktopClockTransparency: { def: 0.8, coerce: percentToUnit },
|
||||||
|
desktopClockColorMode: { def: "primary" },
|
||||||
|
desktopClockCustomColor: { def: "#ffffff" },
|
||||||
|
desktopClockShowDate: { def: true },
|
||||||
|
desktopClockShowAnalogNumbers: { def: false },
|
||||||
|
desktopClockX: { def: -1 },
|
||||||
|
desktopClockY: { def: -1 },
|
||||||
|
desktopClockWidth: { def: 280 },
|
||||||
|
desktopClockHeight: { def: 180 },
|
||||||
|
desktopClockDisplayPreferences: { def: ["all"] },
|
||||||
|
|
||||||
|
systemMonitorEnabled: { def: false },
|
||||||
|
systemMonitorShowHeader: { def: true },
|
||||||
|
systemMonitorTransparency: { def: 0.8, coerce: percentToUnit },
|
||||||
|
systemMonitorColorMode: { def: "primary" },
|
||||||
|
systemMonitorCustomColor: { def: "#ffffff" },
|
||||||
|
systemMonitorShowCpu: { def: true },
|
||||||
|
systemMonitorShowCpuGraph: { def: true },
|
||||||
|
systemMonitorShowCpuTemp: { def: true },
|
||||||
|
systemMonitorShowGpuTemp: { def: false },
|
||||||
|
systemMonitorGpuPciId: { def: "" },
|
||||||
|
systemMonitorShowMemory: { def: true },
|
||||||
|
systemMonitorShowMemoryGraph: { def: true },
|
||||||
|
systemMonitorShowNetwork: { def: true },
|
||||||
|
systemMonitorShowNetworkGraph: { def: true },
|
||||||
|
systemMonitorShowDisk: { def: true },
|
||||||
|
systemMonitorShowTopProcesses: { def: false },
|
||||||
|
systemMonitorTopProcessCount: { def: 3 },
|
||||||
|
systemMonitorTopProcessSortBy: { def: "cpu" },
|
||||||
|
systemMonitorGraphInterval: { def: 60 },
|
||||||
|
systemMonitorX: { def: -1 },
|
||||||
|
systemMonitorY: { def: -1 },
|
||||||
|
systemMonitorWidth: { def: 320 },
|
||||||
|
systemMonitorHeight: { def: 480 },
|
||||||
|
systemMonitorDisplayPreferences: { def: ["all"] },
|
||||||
|
systemMonitorVariants: { def: [] },
|
||||||
|
desktopWidgetPositions: { def: {} }
|
||||||
};
|
};
|
||||||
|
|
||||||
function getValidKeys() {
|
function getValidKeys() {
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ Item {
|
|||||||
|
|
||||||
WallpaperBackground {}
|
WallpaperBackground {}
|
||||||
|
|
||||||
|
DesktopWidgetLayer {}
|
||||||
|
|
||||||
Lock {
|
Lock {
|
||||||
id: lock
|
id: lock
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -421,5 +421,20 @@ FocusScope {
|
|||||||
Qt.callLater(() => item.forceActiveFocus());
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: desktopWidgetsLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 27
|
||||||
|
visible: active
|
||||||
|
focus: active
|
||||||
|
|
||||||
|
sourceComponent: DesktopWidgetsTab {}
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active && item)
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,12 @@ Rectangle {
|
|||||||
"text": I18n.tr("System Updater"),
|
"text": I18n.tr("System Updater"),
|
||||||
"icon": "refresh",
|
"icon": "refresh",
|
||||||
"tabIndex": 20
|
"tabIndex": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "desktop_widgets",
|
||||||
|
"text": I18n.tr("Desktop Widgets"),
|
||||||
|
"icon": "widgets",
|
||||||
|
"tabIndex": 27
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
439
quickshell/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml
Normal file
439
quickshell/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real widgetWidth: 280
|
||||||
|
property real widgetHeight: 200
|
||||||
|
|
||||||
|
property string clockStyle: SettingsData.desktopClockStyle
|
||||||
|
property bool forceSquare: clockStyle === "analog"
|
||||||
|
|
||||||
|
property real defaultWidth: {
|
||||||
|
switch (clockStyle) {
|
||||||
|
case "analog":
|
||||||
|
return 200;
|
||||||
|
case "stacked":
|
||||||
|
return 160;
|
||||||
|
default:
|
||||||
|
return 280;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property real defaultHeight: {
|
||||||
|
switch (clockStyle) {
|
||||||
|
case "analog":
|
||||||
|
return 200;
|
||||||
|
case "stacked":
|
||||||
|
return 220;
|
||||||
|
default:
|
||||||
|
return 160;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property real minWidth: {
|
||||||
|
switch (clockStyle) {
|
||||||
|
case "analog":
|
||||||
|
return 120;
|
||||||
|
case "stacked":
|
||||||
|
return 100;
|
||||||
|
default:
|
||||||
|
return 140;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property real minHeight: {
|
||||||
|
switch (clockStyle) {
|
||||||
|
case "analog":
|
||||||
|
return 120;
|
||||||
|
case "stacked":
|
||||||
|
return 140;
|
||||||
|
default:
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool enabled: SettingsData.desktopClockEnabled
|
||||||
|
property real transparency: SettingsData.desktopClockTransparency
|
||||||
|
property string colorMode: SettingsData.desktopClockColorMode
|
||||||
|
property color customColor: SettingsData.desktopClockCustomColor
|
||||||
|
property bool showDate: SettingsData.desktopClockShowDate
|
||||||
|
property bool showAnalogNumbers: SettingsData.desktopClockShowAnalogNumbers
|
||||||
|
|
||||||
|
readonly property real scaleFactor: Math.min(width, height) / 200
|
||||||
|
|
||||||
|
readonly property color accentColor: {
|
||||||
|
if (colorMode === "primary")
|
||||||
|
return Theme.primary;
|
||||||
|
if (colorMode === "secondary")
|
||||||
|
return Theme.secondary;
|
||||||
|
if (colorMode === "custom")
|
||||||
|
return customColor;
|
||||||
|
return Theme.primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property color handColor: accentColor
|
||||||
|
readonly property color handColorDim: Theme.withAlpha(accentColor, 0.65)
|
||||||
|
readonly property color textColor: Theme.onSurface
|
||||||
|
readonly property color subtleTextColor: Theme.onSurfaceVariant
|
||||||
|
readonly property color backgroundColor: Theme.withAlpha(Theme.surface, root.transparency)
|
||||||
|
|
||||||
|
SystemClock {
|
||||||
|
id: systemClock
|
||||||
|
precision: SystemClock.Seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: root.backgroundColor
|
||||||
|
visible: root.clockStyle !== "analog"
|
||||||
|
}
|
||||||
|
|
||||||
|
OrganicBlobHourBulges {
|
||||||
|
anchors.fill: parent
|
||||||
|
fillColor: root.backgroundColor
|
||||||
|
visible: root.clockStyle === "analog"
|
||||||
|
lobes: 12
|
||||||
|
rotationDeg: -90
|
||||||
|
lobeAmount: 0.075
|
||||||
|
hillPower: 0.92
|
||||||
|
roundness: 0.22
|
||||||
|
paddingFrac: 0.02
|
||||||
|
segments: 144
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
sourceComponent: {
|
||||||
|
if (root.clockStyle === "analog")
|
||||||
|
return analogClock;
|
||||||
|
if (root.clockStyle === "stacked")
|
||||||
|
return stackedClock;
|
||||||
|
return digitalClock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: analogClock
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: analogRoot
|
||||||
|
|
||||||
|
property real clockSize: Math.min(width, height)
|
||||||
|
property real centerX: width / 2
|
||||||
|
property real centerY: height / 2
|
||||||
|
property real faceRadius: clockSize / 2 - 12
|
||||||
|
|
||||||
|
property int hours: systemClock.date?.getHours() % 12 ?? 0
|
||||||
|
property int minutes: systemClock.date?.getMinutes() ?? 0
|
||||||
|
property int seconds: systemClock.date?.getSeconds() ?? 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.showAnalogNumbers ? 12 : 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
required property int index
|
||||||
|
property real angle: (index + 1) * 30 * Math.PI / 180
|
||||||
|
property real numRadius: analogRoot.faceRadius + 10
|
||||||
|
|
||||||
|
x: analogRoot.centerX + numRadius * Math.sin(angle) - width / 2
|
||||||
|
y: analogRoot.centerY - numRadius * Math.cos(angle) - height / 2
|
||||||
|
text: index + 1
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: hourHand
|
||||||
|
property real angle: (analogRoot.hours + analogRoot.minutes / 60) * 30
|
||||||
|
property real handWidth: Math.max(8, 12 * root.scaleFactor)
|
||||||
|
property real mainLength: analogRoot.faceRadius * 0.55
|
||||||
|
property real tailLength: handWidth * 0.5
|
||||||
|
|
||||||
|
x: analogRoot.centerX - width / 2
|
||||||
|
y: analogRoot.centerY - mainLength
|
||||||
|
width: handWidth
|
||||||
|
height: mainLength + tailLength
|
||||||
|
radius: width / 2
|
||||||
|
color: root.handColor
|
||||||
|
antialiasing: true
|
||||||
|
|
||||||
|
transform: Rotation {
|
||||||
|
origin.x: hourHand.width / 2
|
||||||
|
origin.y: hourHand.mainLength
|
||||||
|
angle: hourHand.angle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: minuteHand
|
||||||
|
property real angle: (analogRoot.minutes + analogRoot.seconds / 60) * 6
|
||||||
|
property real mainLength: analogRoot.faceRadius * 0.75
|
||||||
|
property real tailLength: hourHand.handWidth * 0.5
|
||||||
|
|
||||||
|
x: analogRoot.centerX - width / 2
|
||||||
|
y: analogRoot.centerY - mainLength
|
||||||
|
width: hourHand.handWidth
|
||||||
|
height: mainLength + tailLength
|
||||||
|
radius: width / 2
|
||||||
|
color: root.handColorDim
|
||||||
|
antialiasing: true
|
||||||
|
|
||||||
|
transform: Rotation {
|
||||||
|
origin.x: minuteHand.width / 2
|
||||||
|
origin.y: minuteHand.mainLength
|
||||||
|
angle: minuteHand.angle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: secondDot
|
||||||
|
property real angle: analogRoot.seconds * 6 * Math.PI / 180
|
||||||
|
property real orbitRadius: analogRoot.faceRadius * 0.92
|
||||||
|
|
||||||
|
x: analogRoot.centerX + orbitRadius * Math.sin(angle) - width / 2
|
||||||
|
y: analogRoot.centerY - orbitRadius * Math.cos(angle) - height / 2
|
||||||
|
width: Math.max(10, analogRoot.clockSize * 0.07)
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: root.accentColor
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: dateText
|
||||||
|
visible: root.showDate
|
||||||
|
|
||||||
|
property real hourAngle: (analogRoot.hours + analogRoot.minutes / 60) * 30
|
||||||
|
property real minuteAngle: analogRoot.minutes * 6
|
||||||
|
|
||||||
|
property string bestPosition: {
|
||||||
|
const hRad = hourAngle * Math.PI / 180;
|
||||||
|
const mRad = minuteAngle * Math.PI / 180;
|
||||||
|
|
||||||
|
const topWeight = Math.max(0, Math.cos(hRad)) + Math.max(0, Math.cos(mRad));
|
||||||
|
const bottomWeight = Math.max(0, -Math.cos(hRad)) + Math.max(0, -Math.cos(mRad));
|
||||||
|
const rightWeight = Math.max(0, Math.sin(hRad)) + Math.max(0, Math.sin(mRad));
|
||||||
|
const leftWeight = Math.max(0, -Math.sin(hRad)) + Math.max(0, -Math.sin(mRad));
|
||||||
|
|
||||||
|
const minWeight = Math.min(topWeight, bottomWeight, leftWeight, rightWeight);
|
||||||
|
|
||||||
|
if (minWeight === bottomWeight)
|
||||||
|
return "bottom";
|
||||||
|
if (minWeight === topWeight)
|
||||||
|
return "top";
|
||||||
|
if (minWeight === rightWeight)
|
||||||
|
return "right";
|
||||||
|
return "left";
|
||||||
|
}
|
||||||
|
|
||||||
|
x: {
|
||||||
|
if (bestPosition === "left")
|
||||||
|
return analogRoot.centerX - analogRoot.faceRadius * 0.5 - width / 2;
|
||||||
|
if (bestPosition === "right")
|
||||||
|
return analogRoot.centerX + analogRoot.faceRadius * 0.5 - width / 2;
|
||||||
|
return analogRoot.centerX - width / 2;
|
||||||
|
}
|
||||||
|
y: {
|
||||||
|
if (bestPosition === "top")
|
||||||
|
return analogRoot.centerY - analogRoot.faceRadius * 0.5 - height / 2;
|
||||||
|
if (bestPosition === "bottom")
|
||||||
|
return analogRoot.centerY + analogRoot.faceRadius * 0.5 - height / 2;
|
||||||
|
return analogRoot.centerY - height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
text: {
|
||||||
|
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
|
||||||
|
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
|
||||||
|
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.accentColor
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: digitalClock
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: digitalRoot
|
||||||
|
|
||||||
|
property real baseSize: Math.max(28, height * 0.38)
|
||||||
|
property real smallSize: Math.max(12, baseSize * 0.32)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: root.showDate
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: {
|
||||||
|
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
|
||||||
|
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
|
||||||
|
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
|
||||||
|
}
|
||||||
|
font.pixelSize: digitalRoot.smallSize
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: {
|
||||||
|
const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12);
|
||||||
|
const minutes = String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0');
|
||||||
|
return hours + ":" + minutes;
|
||||||
|
}
|
||||||
|
font.pixelSize: digitalRoot.baseSize
|
||||||
|
font.weight: Font.Normal
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
visible: !SettingsData.use24HourClock || SettingsData.showSeconds
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
visible: SettingsData.showSeconds
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "timer"
|
||||||
|
size: Math.max(10, digitalRoot.baseSize * 0.25)
|
||||||
|
color: root.subtleTextColor
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0')
|
||||||
|
font.pixelSize: digitalRoot.smallSize
|
||||||
|
color: root.subtleTextColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: !SettingsData.use24HourClock
|
||||||
|
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
|
||||||
|
font.pixelSize: digitalRoot.smallSize
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: stackedClock
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: stackedRoot
|
||||||
|
|
||||||
|
property real baseSize: Math.max(32, height * 0.32)
|
||||||
|
property real smallSize: Math.max(12, baseSize * 0.28)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: -baseSize * 0.1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: root.showDate
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
bottomPadding: Theme.spacingS
|
||||||
|
text: {
|
||||||
|
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
|
||||||
|
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
|
||||||
|
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
|
||||||
|
}
|
||||||
|
font.pixelSize: stackedRoot.smallSize
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: {
|
||||||
|
const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12);
|
||||||
|
return String(hours).padStart(2, '0');
|
||||||
|
}
|
||||||
|
font.pixelSize: stackedRoot.baseSize
|
||||||
|
font.weight: Font.Normal
|
||||||
|
color: root.accentColor
|
||||||
|
lineHeight: 0.85
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0')
|
||||||
|
font.pixelSize: stackedRoot.baseSize
|
||||||
|
font.weight: Font.Normal
|
||||||
|
color: root.accentColor
|
||||||
|
lineHeight: 0.85
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
visible: SettingsData.showSeconds || !SettingsData.use24HourClock
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
topPadding: Theme.spacingXS
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
visible: SettingsData.showSeconds
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "timer"
|
||||||
|
size: Math.max(10, stackedRoot.baseSize * 0.28)
|
||||||
|
color: root.subtleTextColor
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0')
|
||||||
|
font.pixelSize: stackedRoot.smallSize
|
||||||
|
color: root.subtleTextColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: !SettingsData.use24HourClock
|
||||||
|
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
|
||||||
|
font.pixelSize: stackedRoot.smallSize
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property color fillColor: "transparent"
|
||||||
|
|
||||||
|
property int lobes: 12
|
||||||
|
property real lobeAmount: 0.070
|
||||||
|
property real roundness: 0.22
|
||||||
|
property real hillPower: 0.78
|
||||||
|
property real paddingFrac: 0.020
|
||||||
|
property real inset: 0
|
||||||
|
property int segments: 120
|
||||||
|
property real rotationDeg: -90
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.samples: 4
|
||||||
|
|
||||||
|
function sgn(v) {
|
||||||
|
return v < 0 ? -1 : 1;
|
||||||
|
}
|
||||||
|
function clamp(v, a, b) {
|
||||||
|
return Math.max(a, Math.min(b, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
function squircleMap(u, v, p) {
|
||||||
|
const ax = sgn(u) * Math.pow(Math.abs(u), p);
|
||||||
|
const ay = sgn(v) * Math.pow(Math.abs(v), p);
|
||||||
|
return {
|
||||||
|
x: ax,
|
||||||
|
y: ay
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPathD(w, h) {
|
||||||
|
const x0 = inset, y0 = inset;
|
||||||
|
const x1 = w - inset, y1 = h - inset;
|
||||||
|
const iw = Math.max(2, x1 - x0);
|
||||||
|
const ih = Math.max(2, y1 - y0);
|
||||||
|
|
||||||
|
const cx = x0 + iw * 0.5;
|
||||||
|
const cy = y0 + ih * 0.5;
|
||||||
|
|
||||||
|
const rx = iw * 0.5;
|
||||||
|
const ry = ih * 0.5;
|
||||||
|
const rMin = Math.min(rx, ry);
|
||||||
|
|
||||||
|
const amp = clamp(lobeAmount, 0.0, 0.14) * rMin;
|
||||||
|
const extraPad = paddingFrac * rMin + 1.5;
|
||||||
|
const pad = amp + extraPad;
|
||||||
|
|
||||||
|
const rxBase = Math.max(2, rx - pad);
|
||||||
|
const ryBase = Math.max(2, ry - pad);
|
||||||
|
|
||||||
|
const blend = clamp(roundness, 0.0, 0.45);
|
||||||
|
const squirclePow = 1.0 + blend * 2.8;
|
||||||
|
|
||||||
|
const N = Math.max(48, segments);
|
||||||
|
const rot = rotationDeg * Math.PI / 180.0;
|
||||||
|
const dt = (Math.PI * 2.0) / N;
|
||||||
|
|
||||||
|
function hillWave(a) {
|
||||||
|
const t = (Math.cos(lobes * a) + 1.0) * 0.5;
|
||||||
|
return Math.pow(t, hillPower);
|
||||||
|
}
|
||||||
|
|
||||||
|
function P(t) {
|
||||||
|
const a = t + rot;
|
||||||
|
const u = Math.cos(a);
|
||||||
|
const v = Math.sin(a);
|
||||||
|
|
||||||
|
const m = 1.0 + (amp / rMin) * hillWave(a);
|
||||||
|
|
||||||
|
const ex = u * rxBase * m;
|
||||||
|
const ey = v * ryBase * m;
|
||||||
|
|
||||||
|
const sm = squircleMap(u, v, 1.0 / squirclePow);
|
||||||
|
const sx = sm.x * rxBase * m;
|
||||||
|
const sy = sm.y * ryBase * m;
|
||||||
|
|
||||||
|
const x = ex * (1.0 - blend) + sx * blend;
|
||||||
|
const y = ey * (1.0 - blend) + sy * blend;
|
||||||
|
return {
|
||||||
|
x: cx + x,
|
||||||
|
y: cy + y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function dP(t) {
|
||||||
|
const eps = dt * 0.25;
|
||||||
|
const p1 = P(t - eps);
|
||||||
|
const p2 = P(t + eps);
|
||||||
|
return {
|
||||||
|
x: (p2.x - p1.x) / (2 * eps),
|
||||||
|
y: (p2.y - p1.y) / (2 * eps)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const p0 = P(0.0);
|
||||||
|
let d = `M ${p0.x.toFixed(2)} ${p0.y.toFixed(2)} `;
|
||||||
|
|
||||||
|
for (let i = 0; i < N; i++) {
|
||||||
|
const tA = i * dt;
|
||||||
|
const tB = (i + 1) * dt;
|
||||||
|
|
||||||
|
const A = P(tA);
|
||||||
|
const B = P(tB);
|
||||||
|
const dA = dP(tA);
|
||||||
|
const dB = dP(tB);
|
||||||
|
|
||||||
|
const c1x = A.x + (dt / 3.0) * dA.x;
|
||||||
|
const c1y = A.y + (dt / 3.0) * dA.y;
|
||||||
|
const c2x = B.x - (dt / 3.0) * dB.x;
|
||||||
|
const c2y = B.y - (dt / 3.0) * dB.y;
|
||||||
|
|
||||||
|
d += `C ${c1x.toFixed(2)} ${c1y.toFixed(2)}, ${c2x.toFixed(2)} ${c2y.toFixed(2)}, ${B.x.toFixed(2)} ${B.y.toFixed(2)} `;
|
||||||
|
}
|
||||||
|
|
||||||
|
d += "Z";
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: root.fillColor
|
||||||
|
strokeColor: "transparent"
|
||||||
|
|
||||||
|
PathSvg {
|
||||||
|
path: root.buildPathD(root.width, root.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
736
quickshell/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml
Normal file
736
quickshell/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml
Normal file
@@ -0,0 +1,736 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real widgetWidth: 320
|
||||||
|
property real widgetHeight: 480
|
||||||
|
property real defaultWidth: 320
|
||||||
|
property real defaultHeight: 480
|
||||||
|
property real minWidth: {
|
||||||
|
const tileCount = enabledTiles.length;
|
||||||
|
if (tileCount === 0)
|
||||||
|
return 80;
|
||||||
|
if (tileCount === 1)
|
||||||
|
return 100;
|
||||||
|
return 160;
|
||||||
|
}
|
||||||
|
property real minHeight: {
|
||||||
|
const tileCount = enabledTiles.length;
|
||||||
|
if (tileCount === 0)
|
||||||
|
return 60;
|
||||||
|
if (tileCount === 1)
|
||||||
|
return 80;
|
||||||
|
if (tileCount <= 2)
|
||||||
|
return 120;
|
||||||
|
return 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
property string variantId: ""
|
||||||
|
property var variantData: null
|
||||||
|
|
||||||
|
readonly property var cfg: variantData?.config ?? null
|
||||||
|
readonly property bool isVariant: variantId !== "" && cfg !== null
|
||||||
|
|
||||||
|
property bool enabled: SettingsData.systemMonitorEnabled
|
||||||
|
property bool showHeader: isVariant ? (cfg.showHeader ?? true) : SettingsData.systemMonitorShowHeader
|
||||||
|
property real transparency: isVariant ? (cfg.transparency ?? 0.8) : SettingsData.systemMonitorTransparency
|
||||||
|
property string colorMode: isVariant ? (cfg.colorMode ?? "primary") : SettingsData.systemMonitorColorMode
|
||||||
|
property color customColor: isVariant ? (cfg.customColor ?? "#ffffff") : SettingsData.systemMonitorCustomColor
|
||||||
|
property bool showCpu: isVariant ? (cfg.showCpu ?? true) : SettingsData.systemMonitorShowCpu
|
||||||
|
property bool showCpuGraph: isVariant ? (cfg.showCpuGraph ?? true) : SettingsData.systemMonitorShowCpuGraph
|
||||||
|
property bool showCpuTemp: isVariant ? (cfg.showCpuTemp ?? true) : SettingsData.systemMonitorShowCpuTemp
|
||||||
|
property bool showGpuTemp: isVariant ? (cfg.showGpuTemp ?? false) : SettingsData.systemMonitorShowGpuTemp
|
||||||
|
property string selectedGpuPciId: isVariant ? (cfg.gpuPciId ?? "") : SettingsData.systemMonitorGpuPciId
|
||||||
|
property bool showMemory: isVariant ? (cfg.showMemory ?? true) : SettingsData.systemMonitorShowMemory
|
||||||
|
property bool showMemoryGraph: isVariant ? (cfg.showMemoryGraph ?? true) : SettingsData.systemMonitorShowMemoryGraph
|
||||||
|
property bool showNetwork: isVariant ? (cfg.showNetwork ?? true) : SettingsData.systemMonitorShowNetwork
|
||||||
|
property bool showNetworkGraph: isVariant ? (cfg.showNetworkGraph ?? true) : SettingsData.systemMonitorShowNetworkGraph
|
||||||
|
property bool showDisk: isVariant ? (cfg.showDisk ?? true) : SettingsData.systemMonitorShowDisk
|
||||||
|
property bool showTopProcesses: isVariant ? (cfg.showTopProcesses ?? false) : SettingsData.systemMonitorShowTopProcesses
|
||||||
|
property int topProcessCount: isVariant ? (cfg.topProcessCount ?? 3) : SettingsData.systemMonitorTopProcessCount
|
||||||
|
property string topProcessSortBy: isVariant ? (cfg.topProcessSortBy ?? "cpu") : SettingsData.systemMonitorTopProcessSortBy
|
||||||
|
property string layoutMode: isVariant ? (cfg.layoutMode ?? "auto") : SettingsData.systemMonitorLayoutMode
|
||||||
|
property int graphInterval: isVariant ? (cfg.graphInterval ?? 60) : SettingsData.systemMonitorGraphInterval
|
||||||
|
|
||||||
|
readonly property color accentColor: {
|
||||||
|
switch (colorMode) {
|
||||||
|
case "secondary":
|
||||||
|
return Theme.secondary;
|
||||||
|
case "custom":
|
||||||
|
return customColor;
|
||||||
|
default:
|
||||||
|
return Theme.primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property color bgColor: Theme.withAlpha(Theme.surface, root.transparency)
|
||||||
|
readonly property color tileBg: Theme.withAlpha(Theme.surfaceContainerHigh, root.transparency)
|
||||||
|
readonly property color textColor: Theme.surfaceText
|
||||||
|
readonly property color dimColor: Theme.surfaceVariantText
|
||||||
|
|
||||||
|
property string currentGpuPciIdRef: ""
|
||||||
|
|
||||||
|
property var cpuHistory: []
|
||||||
|
property var memHistory: []
|
||||||
|
property var netRxHistory: []
|
||||||
|
property var netTxHistory: []
|
||||||
|
property var diskReadHistory: []
|
||||||
|
property var diskWriteHistory: []
|
||||||
|
readonly property int historySize: 60
|
||||||
|
|
||||||
|
readonly property int sampleInterval: {
|
||||||
|
switch (graphInterval) {
|
||||||
|
case 60:
|
||||||
|
return 1000;
|
||||||
|
case 300:
|
||||||
|
return 5000;
|
||||||
|
case 600:
|
||||||
|
return 10000;
|
||||||
|
case 900:
|
||||||
|
return 15000;
|
||||||
|
case 1800:
|
||||||
|
return 30000;
|
||||||
|
default:
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var enabledTiles: {
|
||||||
|
var tiles = [];
|
||||||
|
if (showCpu)
|
||||||
|
tiles.push("cpu");
|
||||||
|
if (showMemory)
|
||||||
|
tiles.push("mem");
|
||||||
|
if (showNetwork)
|
||||||
|
tiles.push("net");
|
||||||
|
if (showDisk)
|
||||||
|
tiles.push("disk");
|
||||||
|
if (showGpuTemp && selectedGpuPciId)
|
||||||
|
tiles.push("gpu");
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var sortedProcesses: {
|
||||||
|
if (!showTopProcesses || !DgopService.processes)
|
||||||
|
return [];
|
||||||
|
var procs = DgopService.processes.slice();
|
||||||
|
if (topProcessSortBy === "memory") {
|
||||||
|
procs.sort((a, b) => (b.memoryKB || 0) - (a.memoryKB || 0));
|
||||||
|
} else {
|
||||||
|
procs.sort((a, b) => (b.cpu || 0) - (a.cpu || 0));
|
||||||
|
}
|
||||||
|
return procs.slice(0, topProcessCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
var modules = ["system"];
|
||||||
|
if (showCpu || showCpuTemp || showCpuGraph)
|
||||||
|
modules.push("cpu");
|
||||||
|
if (showMemory || showMemoryGraph)
|
||||||
|
modules.push("memory");
|
||||||
|
if (showNetwork || showNetworkGraph)
|
||||||
|
modules.push("network");
|
||||||
|
if (showDisk)
|
||||||
|
modules.push("disk", "diskmounts");
|
||||||
|
if (showTopProcesses)
|
||||||
|
modules.push("processes");
|
||||||
|
DgopService.addRef(modules);
|
||||||
|
updateGpuRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: {
|
||||||
|
DgopService.removeRef();
|
||||||
|
if (currentGpuPciIdRef)
|
||||||
|
DgopService.removeGpuPciId(currentGpuPciIdRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
onShowGpuTempChanged: updateGpuRef()
|
||||||
|
onSelectedGpuPciIdChanged: updateGpuRef()
|
||||||
|
onShowTopProcessesChanged: {
|
||||||
|
if (showTopProcesses)
|
||||||
|
DgopService.addRef(["processes"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGpuRef() {
|
||||||
|
if (currentGpuPciIdRef && currentGpuPciIdRef !== selectedGpuPciId) {
|
||||||
|
DgopService.removeGpuPciId(currentGpuPciIdRef);
|
||||||
|
currentGpuPciIdRef = "";
|
||||||
|
}
|
||||||
|
if (!showGpuTemp || !selectedGpuPciId) {
|
||||||
|
if (currentGpuPciIdRef) {
|
||||||
|
DgopService.removeGpuPciId(currentGpuPciIdRef);
|
||||||
|
currentGpuPciIdRef = "";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedGpuPciId && !currentGpuPciIdRef) {
|
||||||
|
DgopService.addGpuPciId(selectedGpuPciId);
|
||||||
|
currentGpuPciIdRef = selectedGpuPciId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGpuInfo() {
|
||||||
|
if (!selectedGpuPciId || !DgopService.availableGpus)
|
||||||
|
return null;
|
||||||
|
return DgopService.availableGpus.find(g => g.pciId === selectedGpuPciId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
if (bytes < 1024)
|
||||||
|
return bytes.toFixed(0) + "B";
|
||||||
|
if (bytes < 1024 * 1024)
|
||||||
|
return (bytes / 1024).toFixed(0) + "K";
|
||||||
|
if (bytes < 1024 * 1024 * 1024)
|
||||||
|
return (bytes / (1024 * 1024)).toFixed(1) + "M";
|
||||||
|
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + "G";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMemKB(kb) {
|
||||||
|
if (kb < 1024)
|
||||||
|
return kb.toFixed(0) + "K";
|
||||||
|
if (kb < 1024 * 1024)
|
||||||
|
return (kb / 1024).toFixed(0) + "M";
|
||||||
|
return (kb / (1024 * 1024)).toFixed(1) + "G";
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToHistory(arr, val) {
|
||||||
|
var newArr = arr.slice();
|
||||||
|
newArr.push(val);
|
||||||
|
if (newArr.length > historySize)
|
||||||
|
newArr.shift();
|
||||||
|
return newArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sampleData() {
|
||||||
|
if (showCpuGraph)
|
||||||
|
cpuHistory = addToHistory(cpuHistory, DgopService.cpuUsage);
|
||||||
|
if (showMemoryGraph)
|
||||||
|
memHistory = addToHistory(memHistory, DgopService.memoryUsage);
|
||||||
|
if (showNetworkGraph) {
|
||||||
|
netRxHistory = addToHistory(netRxHistory, DgopService.networkRxRate);
|
||||||
|
netTxHistory = addToHistory(netTxHistory, DgopService.networkTxRate);
|
||||||
|
}
|
||||||
|
if (showDisk) {
|
||||||
|
diskReadHistory = addToHistory(diskReadHistory, DgopService.diskReadRate);
|
||||||
|
diskWriteHistory = addToHistory(diskWriteHistory, DgopService.diskWriteRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property int sampleSeconds: sampleInterval / 1000
|
||||||
|
|
||||||
|
SystemClock {
|
||||||
|
id: sampleClock
|
||||||
|
precision: SystemClock.Seconds
|
||||||
|
onDateChanged: {
|
||||||
|
var sec = date.getSeconds();
|
||||||
|
if (sec % root.sampleSeconds === 0)
|
||||||
|
root.sampleData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: root.bgColor
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: (root.enabledTiles.length === 1 && !root.showHeader) ? 0 : Theme.spacingS
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: root.showHeader
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: DgopService.cpuModel || DgopService.hostname || "System"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.textColor
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.maximumWidth: root.width - Theme.spacingM * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: DgopService.shortUptime && DgopService.shortUptime.length > 0
|
||||||
|
text: DgopService.shortUptime
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.dimColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: tileGrid
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
columns: {
|
||||||
|
if (root.layoutMode === "list")
|
||||||
|
return 1;
|
||||||
|
if (root.layoutMode === "grid")
|
||||||
|
return 2;
|
||||||
|
// auto
|
||||||
|
if (root.width < 280)
|
||||||
|
return 1;
|
||||||
|
if (root.width < 500)
|
||||||
|
return 2;
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
rowSpacing: Theme.spacingXS
|
||||||
|
columnSpacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.enabledTiles
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: tile
|
||||||
|
readonly property int span: Layout.columnSpan
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.columnSpan: {
|
||||||
|
if (root.layoutMode === "list")
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
var cols = tileGrid.columns;
|
||||||
|
if (cols <= 1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
var count = root.enabledTiles.length;
|
||||||
|
var idx = index;
|
||||||
|
|
||||||
|
if (idx !== count - 1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
var remainder = count % cols;
|
||||||
|
if (remainder === 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (!tile.hasGraph)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return cols - remainder + 1;
|
||||||
|
}
|
||||||
|
Layout.minimumHeight: 60
|
||||||
|
radius: Theme.cornerRadius - 2
|
||||||
|
color: root.tileBg
|
||||||
|
border.width: 0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
readonly property string tileType: modelData
|
||||||
|
readonly property bool hasGraph: {
|
||||||
|
switch (tileType) {
|
||||||
|
case "cpu":
|
||||||
|
return root.showCpuGraph;
|
||||||
|
case "mem":
|
||||||
|
return root.showMemoryGraph;
|
||||||
|
case "net":
|
||||||
|
return root.showNetworkGraph;
|
||||||
|
case "disk":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: tileGraph
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: tile.hasGraph
|
||||||
|
renderStrategy: Canvas.Cooperative
|
||||||
|
|
||||||
|
property var hist: {
|
||||||
|
switch (tile.tileType) {
|
||||||
|
case "cpu":
|
||||||
|
return root.cpuHistory;
|
||||||
|
case "mem":
|
||||||
|
return root.memHistory;
|
||||||
|
case "net":
|
||||||
|
return root.netRxHistory;
|
||||||
|
case "disk":
|
||||||
|
return root.diskReadHistory;
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property var hist2: {
|
||||||
|
switch (tile.tileType) {
|
||||||
|
case "net":
|
||||||
|
return root.netTxHistory;
|
||||||
|
case "disk":
|
||||||
|
return root.diskWriteHistory;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onHistChanged: requestPaint()
|
||||||
|
onHist2Changed: requestPaint()
|
||||||
|
onWidthChanged: requestPaint()
|
||||||
|
onHeightChanged: requestPaint()
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
var ctx = getContext("2d");
|
||||||
|
ctx.reset();
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
if (!hist || hist.length < 2)
|
||||||
|
return;
|
||||||
|
var maxVal = 100;
|
||||||
|
if (tile.tileType === "net" || tile.tileType === "disk") {
|
||||||
|
maxVal = 1;
|
||||||
|
for (var k = 0; k < hist.length; k++)
|
||||||
|
maxVal = Math.max(maxVal, hist[k]);
|
||||||
|
if (hist2)
|
||||||
|
for (var l = 0; l < hist2.length; l++)
|
||||||
|
maxVal = Math.max(maxVal, hist2[l]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = root.accentColor;
|
||||||
|
var grad = ctx.createLinearGradient(0, 0, 0, height);
|
||||||
|
grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.3));
|
||||||
|
grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.05));
|
||||||
|
|
||||||
|
ctx.fillStyle = grad;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, height);
|
||||||
|
for (var i = 0; i < hist.length; i++) {
|
||||||
|
var x = (width / (root.historySize - 1)) * i;
|
||||||
|
var y = height - (hist[i] / maxVal) * height * 0.85;
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
ctx.lineTo((width / (root.historySize - 1)) * (hist.length - 1), height);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.6);
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.beginPath();
|
||||||
|
for (var j = 0; j < hist.length; j++) {
|
||||||
|
var px = (width / (root.historySize - 1)) * j;
|
||||||
|
var py = height - (hist[j] / maxVal) * height * 0.85;
|
||||||
|
j === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
if (hist2 && hist2.length >= 2) {
|
||||||
|
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.3);
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
for (var m = 0; m < hist2.length; m++) {
|
||||||
|
var sx = (width / (root.historySize - 1)) * m;
|
||||||
|
var sy = height - (hist2[m] / maxVal) * height * 0.85;
|
||||||
|
m === 0 ? ctx.moveTo(sx, sy) : ctx.lineTo(sx, sy);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: tile.tileType.toUpperCase()
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: tile.tileType === "cpu" && root.showCpuTemp && DgopService.cpuTemperature > 0
|
||||||
|
text: DgopService.cpuTemperature.toFixed(0) + "°"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: DgopService.cpuTemperature > 80 ? Theme.error : (DgopService.cpuTemperature > 60 ? Theme.warning : root.dimColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: tile.tileType === "mem"
|
||||||
|
text: DgopService.formatSystemMemory(DgopService.usedMemoryKB)
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.dimColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: tile.tileType === "cpu"
|
||||||
|
text: DgopService.cpuUsage.toFixed(0) + "%"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: tile.tileType === "mem"
|
||||||
|
text: DgopService.memoryUsage.toFixed(0) + "%"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
visible: tile.tileType === "net"
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
StyledText {
|
||||||
|
text: "↓"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: root.formatBytes(DgopService.networkRxRate) + "/s"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
StyledText {
|
||||||
|
text: "↑"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.dimColor
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: root.formatBytes(DgopService.networkTxRate) + "/s"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
visible: tile.tileType === "disk"
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
StyledText {
|
||||||
|
text: "R"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: root.formatBytes(DgopService.diskReadRate) + "/s"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
StyledText {
|
||||||
|
text: "W"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.dimColor
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: root.formatBytes(DgopService.diskWriteRate) + "/s"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
visible: tile.tileType === "gpu"
|
||||||
|
spacing: 0
|
||||||
|
property var gpu: root.getGpuInfo()
|
||||||
|
Layout.alignment: tile.span > 1 ? Qt.AlignHCenter : Qt.AlignLeft
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
property real temp: parent.gpu?.temperature ?? 0
|
||||||
|
text: temp > 0 ? temp.toFixed(0) + "°C" : "--"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: root.textColor
|
||||||
|
Layout.alignment: tile.span > 1 ? Qt.AlignHCenter : Qt.AlignLeft
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: parent.gpu?.displayName ?? ""
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.dimColor
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: tile.span > 1 ? Text.AlignHCenter : Text.AlignLeft
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: tile.tileType === "cpu" || tile.tileType === "mem"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 4
|
||||||
|
radius: 2
|
||||||
|
color: Theme.withAlpha(Theme.outline, 0.2)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
property real pct: tile.tileType === "cpu" ? DgopService.cpuUsage / 100 : DgopService.memoryUsage / 100
|
||||||
|
width: parent.width * Math.min(1, pct)
|
||||||
|
height: parent.height
|
||||||
|
radius: 2
|
||||||
|
color: pct > 0.8 ? Theme.error : (pct > 0.6 ? Theme.warning : root.accentColor)
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: root.showTopProcesses && root.sortedProcesses.length > 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
color: Theme.withAlpha(Theme.outline, 0.15)
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "TOP BY " + root.topProcessSortBy.toUpperCase()
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: root.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.topProcessSortBy === "cpu" ? "CPU" : "MEM"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.dimColor
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.sortedProcesses
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.command || "unknown"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.textColor
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.topProcessSortBy === "cpu" ? (modelData.cpu || 0).toFixed(1) + "%" : root.formatMemKB(modelData.memoryKB || 0)
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.dimColor
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: root.showDisk && DgopService.diskMounts.length > 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
color: Theme.withAlpha(Theme.outline, 0.15)
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: DgopService.diskMounts.filter(m => m.mountpoint === "/" || m.mountpoint === "/home")
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.mountpoint
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.dimColor
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 4
|
||||||
|
radius: 2
|
||||||
|
color: Theme.withAlpha(Theme.outline, 0.2)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
property real pct: (modelData.used || 0) / Math.max(1, modelData.total || 1)
|
||||||
|
width: parent.width * pct
|
||||||
|
height: parent.height
|
||||||
|
radius: 2
|
||||||
|
color: pct > 0.9 ? Theme.error : (pct > 0.75 ? Theme.warning : root.accentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: ((modelData.used || 0) / (modelData.total || 1) * 100).toFixed(0) + "%"
|
||||||
|
isMonospace: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.textColor
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
quickshell/Modules/DesktopWidgetLayer.qml
Normal file
119
quickshell/Modules/DesktopWidgetLayer.qml
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Modules.Plugins
|
||||||
|
import qs.Modules.BuiltinDesktopPlugins
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
id: root
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: screenDelegate
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
readonly property var screen: modelData
|
||||||
|
|
||||||
|
function shouldShowOnScreen(prefs) {
|
||||||
|
if (!Array.isArray(prefs) || prefs.length === 0 || prefs.includes("all"))
|
||||||
|
return true;
|
||||||
|
return prefs.some(p => p.name === modelData.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool showBuiltinClock: SettingsData.desktopClockEnabled && shouldShowOnScreen(SettingsData.desktopClockDisplayPreferences)
|
||||||
|
|
||||||
|
readonly property bool showSystemMonitor: SettingsData.systemMonitorEnabled && shouldShowOnScreen(SettingsData.systemMonitorDisplayPreferences)
|
||||||
|
|
||||||
|
readonly property var visibleSystemMonitorVariants: {
|
||||||
|
if (!SettingsData.systemMonitorEnabled)
|
||||||
|
return [];
|
||||||
|
const variants = SettingsData.systemMonitorVariants || [];
|
||||||
|
return variants.filter(v => shouldShowOnScreen(v.config?.displayPreferences));
|
||||||
|
}
|
||||||
|
|
||||||
|
property var _pluginComponents: PluginService.pluginDesktopComponents
|
||||||
|
property var _pluginTrigger: 0
|
||||||
|
|
||||||
|
readonly property var visiblePlugins: {
|
||||||
|
void _pluginTrigger;
|
||||||
|
return Object.keys(_pluginComponents).filter(id => {
|
||||||
|
const prefs = PluginService.loadPluginData(id, "displayPreferences", ["all"]);
|
||||||
|
return shouldShowOnScreen(prefs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
property var pluginServiceConnections: Connections {
|
||||||
|
target: PluginService
|
||||||
|
function onPluginDataChanged(pluginId) {
|
||||||
|
screenDelegate._pluginTrigger++;
|
||||||
|
}
|
||||||
|
function onPluginLoaded(pluginId) {
|
||||||
|
const plugin = PluginService.availablePlugins[pluginId];
|
||||||
|
if (plugin?.type === "desktop")
|
||||||
|
screenDelegate._pluginTrigger++;
|
||||||
|
}
|
||||||
|
function onPluginUnloaded(pluginId) {
|
||||||
|
screenDelegate._pluginTrigger++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Loader clockLoader: Loader {
|
||||||
|
active: screenDelegate.showBuiltinClock
|
||||||
|
sourceComponent: Component {
|
||||||
|
DesktopPluginWrapper {
|
||||||
|
pluginId: "desktopClock"
|
||||||
|
pluginComponent: clockComponent
|
||||||
|
screen: screenDelegate.screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Component clockComponent: Component {
|
||||||
|
DesktopClockWidget {}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Loader systemMonitorLoader: Loader {
|
||||||
|
active: screenDelegate.showSystemMonitor
|
||||||
|
sourceComponent: Component {
|
||||||
|
DesktopPluginWrapper {
|
||||||
|
pluginId: "systemMonitor"
|
||||||
|
pluginComponent: systemMonitorComponent
|
||||||
|
screen: screenDelegate.screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Component systemMonitorComponent: Component {
|
||||||
|
SystemMonitorWidget {}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Instantiator sysMonVariantInstantiator: Instantiator {
|
||||||
|
model: screenDelegate.visibleSystemMonitorVariants
|
||||||
|
|
||||||
|
DesktopPluginWrapper {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
pluginId: "systemMonitor"
|
||||||
|
variantId: modelData.id
|
||||||
|
variantData: modelData
|
||||||
|
pluginComponent: screenDelegate.systemMonitorComponent
|
||||||
|
screen: screenDelegate.screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Instantiator pluginInstantiator: Instantiator {
|
||||||
|
model: screenDelegate.visiblePlugins
|
||||||
|
|
||||||
|
DesktopPluginWrapper {
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
pluginId: modelData
|
||||||
|
pluginComponent: PluginService.pluginDesktopComponents[modelData]
|
||||||
|
pluginService: PluginService
|
||||||
|
screen: screenDelegate.screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
quickshell/Modules/Plugins/DesktopPluginComponent.qml
Normal file
49
quickshell/Modules/Plugins/DesktopPluginComponent.qml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var pluginService: null
|
||||||
|
property string pluginId: ""
|
||||||
|
|
||||||
|
property real widgetWidth: 200
|
||||||
|
property real widgetHeight: 200
|
||||||
|
property real minWidth: 100
|
||||||
|
property real minHeight: 100
|
||||||
|
|
||||||
|
property var pluginData: ({})
|
||||||
|
|
||||||
|
Component.onCompleted: loadPluginData()
|
||||||
|
onPluginServiceChanged: loadPluginData()
|
||||||
|
onPluginIdChanged: loadPluginData()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: pluginService
|
||||||
|
function onPluginDataChanged(changedPluginId) {
|
||||||
|
if (changedPluginId !== pluginId)
|
||||||
|
return;
|
||||||
|
loadPluginData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPluginData() {
|
||||||
|
if (!pluginService || !pluginId) {
|
||||||
|
pluginData = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pluginData = SettingsData.getPluginSettingsForPlugin(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(key, defaultValue) {
|
||||||
|
if (!pluginService || !pluginId)
|
||||||
|
return defaultValue;
|
||||||
|
return pluginService.loadPluginData(pluginId, key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setData(key, value) {
|
||||||
|
if (!pluginService || !pluginId)
|
||||||
|
return;
|
||||||
|
pluginService.savePluginData(pluginId, key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
270
quickshell/Modules/Plugins/DesktopPluginWrapper.qml
Normal file
270
quickshell/Modules/Plugins/DesktopPluginWrapper.qml
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string pluginId
|
||||||
|
required property var pluginComponent
|
||||||
|
required property var screen
|
||||||
|
|
||||||
|
property var pluginService: null
|
||||||
|
property string variantId: ""
|
||||||
|
property var variantData: null
|
||||||
|
|
||||||
|
readonly property string settingsKey: variantId ? variantId : pluginId
|
||||||
|
readonly property bool isVariant: variantId !== "" && variantData !== null
|
||||||
|
readonly property bool usePluginService: pluginService !== null && !isVariant
|
||||||
|
readonly property string screenKey: SettingsData.getScreenDisplayName(screen)
|
||||||
|
|
||||||
|
readonly property int screenWidth: screen?.width ?? 1920
|
||||||
|
readonly property int screenHeight: screen?.height ?? 1080
|
||||||
|
|
||||||
|
readonly property bool hasSavedPosition: {
|
||||||
|
if (isVariant)
|
||||||
|
return variantData?.positions?.[screenKey]?.x !== undefined;
|
||||||
|
if (usePluginService)
|
||||||
|
return pluginService.loadPluginData(pluginId, "desktopX_" + screenKey, null) !== null;
|
||||||
|
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "x", null) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool hasSavedSize: {
|
||||||
|
if (isVariant)
|
||||||
|
return variantData?.positions?.[screenKey]?.width !== undefined;
|
||||||
|
if (usePluginService)
|
||||||
|
return pluginService.loadPluginData(pluginId, "desktopWidth_" + screenKey, null) !== null;
|
||||||
|
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "width", null) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
property real savedX: {
|
||||||
|
if (isVariant)
|
||||||
|
return variantData?.positions?.[screenKey]?.x ?? (screenWidth / 2 - savedWidth / 2);
|
||||||
|
if (usePluginService)
|
||||||
|
return pluginService.loadPluginData(pluginId, "desktopX_" + screenKey, screenWidth / 2 - savedWidth / 2);
|
||||||
|
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "x", screenWidth / 2 - savedWidth / 2);
|
||||||
|
}
|
||||||
|
property real savedY: {
|
||||||
|
if (isVariant)
|
||||||
|
return variantData?.positions?.[screenKey]?.y ?? (screenHeight / 2 - savedHeight / 2);
|
||||||
|
if (usePluginService)
|
||||||
|
return pluginService.loadPluginData(pluginId, "desktopY_" + screenKey, screenHeight / 2 - savedHeight / 2);
|
||||||
|
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "y", screenHeight / 2 - savedHeight / 2);
|
||||||
|
}
|
||||||
|
property real savedWidth: {
|
||||||
|
if (isVariant)
|
||||||
|
return variantData?.positions?.[screenKey]?.width ?? 280;
|
||||||
|
if (usePluginService)
|
||||||
|
return pluginService.loadPluginData(pluginId, "desktopWidth_" + screenKey, 200);
|
||||||
|
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "width", 280);
|
||||||
|
}
|
||||||
|
property real savedHeight: {
|
||||||
|
if (isVariant)
|
||||||
|
return variantData?.positions?.[screenKey]?.height ?? 180;
|
||||||
|
if (usePluginService)
|
||||||
|
return pluginService.loadPluginData(pluginId, "desktopHeight_" + screenKey, 200);
|
||||||
|
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "height", 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
property real widgetX: Math.max(0, Math.min(savedX, screenWidth - widgetWidth))
|
||||||
|
property real widgetY: Math.max(0, Math.min(savedY, screenHeight - widgetHeight))
|
||||||
|
property real widgetWidth: Math.max(minWidth, Math.min(savedWidth, screenWidth))
|
||||||
|
property real widgetHeight: Math.max(minHeight, Math.min(savedHeight, screenHeight))
|
||||||
|
|
||||||
|
property real minWidth: contentLoader.item?.minWidth ?? 100
|
||||||
|
property real minHeight: contentLoader.item?.minHeight ?? 100
|
||||||
|
property bool forceSquare: contentLoader.item?.forceSquare ?? false
|
||||||
|
property bool isInteracting: dragArea.drag.active || resizeArea.pressed
|
||||||
|
|
||||||
|
function updateVariantPositions(updates) {
|
||||||
|
const positions = JSON.parse(JSON.stringify(variantData?.positions || {}));
|
||||||
|
positions[screenKey] = Object.assign({}, positions[screenKey] || {}, updates);
|
||||||
|
SettingsData.updateSystemMonitorVariant(variantId, {
|
||||||
|
positions: positions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePosition() {
|
||||||
|
if (isVariant && variantData) {
|
||||||
|
updateVariantPositions({
|
||||||
|
x: root.widgetX,
|
||||||
|
y: root.widgetY
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (usePluginService) {
|
||||||
|
pluginService.savePluginData(pluginId, "desktopX_" + screenKey, root.widgetX);
|
||||||
|
pluginService.savePluginData(pluginId, "desktopY_" + screenKey, root.widgetY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SettingsData.updateDesktopWidgetPosition(pluginId, screenKey, {
|
||||||
|
x: root.widgetX,
|
||||||
|
y: root.widgetY
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSize() {
|
||||||
|
if (isVariant && variantData) {
|
||||||
|
updateVariantPositions({
|
||||||
|
width: root.widgetWidth,
|
||||||
|
height: root.widgetHeight
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (usePluginService) {
|
||||||
|
pluginService.savePluginData(pluginId, "desktopWidth_" + screenKey, root.widgetWidth);
|
||||||
|
pluginService.savePluginData(pluginId, "desktopHeight_" + screenKey, root.widgetHeight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SettingsData.updateDesktopWidgetPosition(pluginId, screenKey, {
|
||||||
|
width: root.widgetWidth,
|
||||||
|
height: root.widgetHeight
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: widgetWindow
|
||||||
|
screen: root.screen
|
||||||
|
visible: root.visible
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
WlrLayershell.namespace: "quickshell:desktop-widget:" + root.pluginId + (root.variantId ? ":" + root.variantId : "")
|
||||||
|
WlrLayershell.layer: WlrLayer.Bottom
|
||||||
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: true
|
||||||
|
top: true
|
||||||
|
}
|
||||||
|
|
||||||
|
WlrLayershell.margins {
|
||||||
|
left: root.widgetX
|
||||||
|
top: root.widgetY
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: root.widgetWidth
|
||||||
|
implicitHeight: root.widgetHeight
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: contentLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
sourceComponent: root.pluginComponent
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
if (root.usePluginService) {
|
||||||
|
item.pluginService = root.pluginService;
|
||||||
|
item.pluginId = root.pluginId;
|
||||||
|
}
|
||||||
|
if (item.variantId !== undefined)
|
||||||
|
item.variantId = root.variantId;
|
||||||
|
if (item.variantData !== undefined)
|
||||||
|
item.variantData = Qt.binding(() => root.variantData);
|
||||||
|
if (!root.hasSavedSize) {
|
||||||
|
const defW = item.defaultWidth ?? item.widgetWidth ?? 280;
|
||||||
|
const defH = item.defaultHeight ?? item.widgetHeight ?? 180;
|
||||||
|
root.widgetWidth = Math.max(root.minWidth, Math.min(defW, root.screenWidth));
|
||||||
|
root.widgetHeight = Math.max(root.minHeight, Math.min(defH, root.screenHeight));
|
||||||
|
}
|
||||||
|
if (!root.hasSavedPosition) {
|
||||||
|
root.widgetX = Math.max(0, Math.min(root.screenWidth / 2 - root.widgetWidth / 2, root.screenWidth - root.widgetWidth));
|
||||||
|
root.widgetY = Math.max(0, Math.min(root.screenHeight / 2 - root.widgetHeight / 2, root.screenHeight - root.widgetHeight));
|
||||||
|
}
|
||||||
|
if (item.widgetWidth !== undefined)
|
||||||
|
item.widgetWidth = Qt.binding(() => contentLoader.width);
|
||||||
|
if (item.widgetHeight !== undefined)
|
||||||
|
item.widgetHeight = Qt.binding(() => contentLoader.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: interactionBorder
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "transparent"
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
visible: root.isInteracting
|
||||||
|
opacity: 0.8
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: 48
|
||||||
|
height: 48
|
||||||
|
topLeftRadius: Theme.cornerRadius
|
||||||
|
bottomRightRadius: Theme.cornerRadius
|
||||||
|
color: Theme.primary
|
||||||
|
opacity: resizeArea.pressed ? 1 : 0.6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dragArea
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
cursorShape: drag.active ? Qt.ClosedHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
drag.target: dragProxy
|
||||||
|
drag.minimumX: 0
|
||||||
|
drag.minimumY: 0
|
||||||
|
drag.maximumX: root.screenWidth - root.widgetWidth
|
||||||
|
drag.maximumY: root.screenHeight - root.widgetHeight
|
||||||
|
|
||||||
|
onReleased: root.savePosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: dragProxy
|
||||||
|
x: root.widgetX
|
||||||
|
y: root.widgetY
|
||||||
|
|
||||||
|
onXChanged: if (dragArea.drag.active)
|
||||||
|
root.widgetX = x
|
||||||
|
onYChanged: if (dragArea.drag.active)
|
||||||
|
root.widgetY = y
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: resizeArea
|
||||||
|
width: 48
|
||||||
|
height: 48
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
cursorShape: pressed ? Qt.SizeFDiagCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
property point startPos
|
||||||
|
property real startWidth
|
||||||
|
property real startHeight
|
||||||
|
|
||||||
|
onPressed: mouse => {
|
||||||
|
startPos = mapToGlobal(mouse.x, mouse.y);
|
||||||
|
startWidth = root.widgetWidth;
|
||||||
|
startHeight = root.widgetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPositionChanged: mouse => {
|
||||||
|
if (!pressed)
|
||||||
|
return;
|
||||||
|
const currentPos = mapToGlobal(mouse.x, mouse.y);
|
||||||
|
const deltaX = currentPos.x - startPos.x;
|
||||||
|
const deltaY = currentPos.y - startPos.y;
|
||||||
|
let newW = Math.max(root.minWidth, Math.min(startWidth + deltaX, root.screenWidth - root.widgetX));
|
||||||
|
let newH = Math.max(root.minHeight, Math.min(startHeight + deltaY, root.screenHeight - root.widgetY));
|
||||||
|
if (root.forceSquare) {
|
||||||
|
const size = Math.max(newW, newH);
|
||||||
|
newW = Math.min(size, root.screenWidth - root.widgetX);
|
||||||
|
newH = Math.min(size, root.screenHeight - root.widgetY);
|
||||||
|
}
|
||||||
|
root.widgetWidth = newW;
|
||||||
|
root.widgetHeight = newH;
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: root.saveSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -10,7 +10,7 @@ Item {
|
|||||||
property var pluginService: null
|
property var pluginService: null
|
||||||
default property list<QtObject> content
|
default property list<QtObject> content
|
||||||
|
|
||||||
signal settingChanged()
|
signal settingChanged
|
||||||
|
|
||||||
property var variants: []
|
property var variants: []
|
||||||
property alias variantsModel: variantsListModel
|
property alias variantsModel: variantsListModel
|
||||||
@@ -18,26 +18,35 @@ Item {
|
|||||||
implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight
|
implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
|
|
||||||
|
readonly property bool isDesktopPlugin: {
|
||||||
|
if (!pluginService || !pluginId)
|
||||||
|
return false;
|
||||||
|
const plugin = pluginService.availablePlugins[pluginId];
|
||||||
|
return plugin?.type === "desktop";
|
||||||
|
}
|
||||||
|
|
||||||
readonly property bool hasPermission: {
|
readonly property bool hasPermission: {
|
||||||
if (!pluginService || !pluginId) return true
|
if (!pluginService || !pluginId)
|
||||||
const allPlugins = pluginService.availablePlugins
|
return true;
|
||||||
const plugin = allPlugins[pluginId]
|
const allPlugins = pluginService.availablePlugins;
|
||||||
if (!plugin) return true
|
const plugin = allPlugins[pluginId];
|
||||||
const permissions = Array.isArray(plugin.permissions) ? plugin.permissions : []
|
if (!plugin)
|
||||||
return permissions.indexOf("settings_write") !== -1
|
return true;
|
||||||
|
const permissions = Array.isArray(plugin.permissions) ? plugin.permissions : [];
|
||||||
|
return permissions.indexOf("settings_write") !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
loadVariants()
|
loadVariants();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPluginServiceChanged: {
|
onPluginServiceChanged: {
|
||||||
if (pluginService) {
|
if (pluginService) {
|
||||||
loadVariants()
|
loadVariants();
|
||||||
for (let i = 0; i < content.length; i++) {
|
for (let i = 0; i < content.length; i++) {
|
||||||
const child = content[i]
|
const child = content[i];
|
||||||
if (child.loadValue) {
|
if (child.loadValue) {
|
||||||
child.loadValue()
|
child.loadValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,9 +54,9 @@ Item {
|
|||||||
|
|
||||||
onContentChanged: {
|
onContentChanged: {
|
||||||
for (let i = 0; i < content.length; i++) {
|
for (let i = 0; i < content.length; i++) {
|
||||||
const item = content[i]
|
const item = content[i];
|
||||||
if (item instanceof Item) {
|
if (item instanceof Item) {
|
||||||
item.parent = settingsColumn
|
item.parent = settingsColumn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,29 +65,29 @@ Item {
|
|||||||
target: pluginService
|
target: pluginService
|
||||||
function onPluginDataChanged(changedPluginId) {
|
function onPluginDataChanged(changedPluginId) {
|
||||||
if (changedPluginId === pluginId) {
|
if (changedPluginId === pluginId) {
|
||||||
loadVariants()
|
loadVariants();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadVariants() {
|
function loadVariants() {
|
||||||
if (!pluginService || !pluginId) {
|
if (!pluginService || !pluginId) {
|
||||||
variants = []
|
variants = [];
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
variants = pluginService.getPluginVariants(pluginId)
|
variants = pluginService.getPluginVariants(pluginId);
|
||||||
syncVariantsToModel()
|
syncVariantsToModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncVariantsToModel() {
|
function syncVariantsToModel() {
|
||||||
variantsListModel.clear()
|
variantsListModel.clear();
|
||||||
for (let i = 0; i < variants.length; i++) {
|
for (let i = 0; i < variants.length; i++) {
|
||||||
variantsListModel.append(variants[i])
|
variantsListModel.append(variants[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onVariantsChanged: {
|
onVariantsChanged: {
|
||||||
syncVariantsToModel()
|
syncVariantsToModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
@@ -87,79 +96,76 @@ Item {
|
|||||||
|
|
||||||
function createVariant(variantName, variantConfig) {
|
function createVariant(variantName, variantConfig) {
|
||||||
if (!pluginService || !pluginId) {
|
if (!pluginService || !pluginId) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
return pluginService.createPluginVariant(pluginId, variantName, variantConfig)
|
return pluginService.createPluginVariant(pluginId, variantName, variantConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeVariant(variantId) {
|
function removeVariant(variantId) {
|
||||||
if (!pluginService || !pluginId) {
|
if (!pluginService || !pluginId) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
pluginService.removePluginVariant(pluginId, variantId)
|
pluginService.removePluginVariant(pluginId, variantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateVariant(variantId, variantConfig) {
|
function updateVariant(variantId, variantConfig) {
|
||||||
if (!pluginService || !pluginId) {
|
if (!pluginService || !pluginId) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
pluginService.updatePluginVariant(pluginId, variantId, variantConfig)
|
pluginService.updatePluginVariant(pluginId, variantId, variantConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveValue(key, value) {
|
function saveValue(key, value) {
|
||||||
if (!pluginService) {
|
if (!pluginService) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
console.warn("PluginSettings: Plugin", pluginId, "does not have settings_write permission")
|
console.warn("PluginSettings: Plugin", pluginId, "does not have settings_write permission");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (pluginService.savePluginData) {
|
if (pluginService.savePluginData) {
|
||||||
pluginService.savePluginData(pluginId, key, value)
|
pluginService.savePluginData(pluginId, key, value);
|
||||||
settingChanged()
|
settingChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadValue(key, defaultValue) {
|
function loadValue(key, defaultValue) {
|
||||||
if (pluginService && pluginService.loadPluginData) {
|
if (pluginService && pluginService.loadPluginData) {
|
||||||
return pluginService.loadPluginData(pluginId, key, defaultValue)
|
return pluginService.loadPluginData(pluginId, key, defaultValue);
|
||||||
}
|
}
|
||||||
return defaultValue
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFlickable(item) {
|
function findFlickable(item) {
|
||||||
var current = item?.parent
|
var current = item?.parent;
|
||||||
while (current) {
|
while (current) {
|
||||||
if (current.contentY !== undefined && current.contentHeight !== undefined) {
|
if (current.contentY !== undefined && current.contentHeight !== undefined) {
|
||||||
return current
|
return current;
|
||||||
}
|
}
|
||||||
current = current.parent
|
current = current.parent;
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureItemVisible(item) {
|
function ensureItemVisible(item) {
|
||||||
if (!item) return
|
if (!item)
|
||||||
|
return;
|
||||||
|
var flickable = findFlickable(root);
|
||||||
|
if (!flickable)
|
||||||
|
return;
|
||||||
|
var itemGlobalY = item.mapToItem(null, 0, 0).y;
|
||||||
|
var itemHeight = item.height;
|
||||||
|
var flickableGlobalY = flickable.mapToItem(null, 0, 0).y;
|
||||||
|
var viewportHeight = flickable.height;
|
||||||
|
|
||||||
var flickable = findFlickable(root)
|
var itemRelativeY = itemGlobalY - flickableGlobalY;
|
||||||
if (!flickable) return
|
var viewportTop = 0;
|
||||||
|
var viewportBottom = viewportHeight;
|
||||||
var itemGlobalY = item.mapToItem(null, 0, 0).y
|
|
||||||
var itemHeight = item.height
|
|
||||||
var flickableGlobalY = flickable.mapToItem(null, 0, 0).y
|
|
||||||
var viewportHeight = flickable.height
|
|
||||||
|
|
||||||
var itemRelativeY = itemGlobalY - flickableGlobalY
|
|
||||||
var viewportTop = 0
|
|
||||||
var viewportBottom = viewportHeight
|
|
||||||
|
|
||||||
if (itemRelativeY < viewportTop) {
|
if (itemRelativeY < viewportTop) {
|
||||||
flickable.contentY = Math.max(0, flickable.contentY - (viewportTop - itemRelativeY) - Theme.spacingL)
|
flickable.contentY = Math.max(0, flickable.contentY - (viewportTop - itemRelativeY) - Theme.spacingL);
|
||||||
} else if (itemRelativeY + itemHeight > viewportBottom) {
|
} else if (itemRelativeY + itemHeight > viewportBottom) {
|
||||||
flickable.contentY = Math.min(
|
flickable.contentY = Math.min(flickable.contentHeight - viewportHeight, flickable.contentY + (itemRelativeY + itemHeight - viewportBottom) + Theme.spacingL);
|
||||||
flickable.contentHeight - viewportHeight,
|
|
||||||
flickable.contentY + (itemRelativeY + itemHeight - viewportBottom) + Theme.spacingL
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,5 +186,97 @@ Item {
|
|||||||
visible: root.hasPermission
|
visible: root.hasPermission
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: desktopDisplaySettings
|
||||||
|
visible: root.isDesktopPlugin
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? displaySettingsColumn.implicitHeight : 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: displaySettingsColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outline
|
||||||
|
opacity: 0.3
|
||||||
|
visible: root.content.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Display Settings")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Choose which displays show this widget")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("All displays")
|
||||||
|
checked: {
|
||||||
|
const prefs = root.loadValue("displayPreferences", ["all"]);
|
||||||
|
return Array.isArray(prefs) && (prefs.includes("all") || prefs.length === 0);
|
||||||
|
}
|
||||||
|
onToggled: isChecked => {
|
||||||
|
if (isChecked) {
|
||||||
|
root.saveValue("displayPreferences", ["all"]);
|
||||||
|
} else {
|
||||||
|
root.saveValue("displayPreferences", []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: {
|
||||||
|
const prefs = root.loadValue("displayPreferences", ["all"]);
|
||||||
|
return !Array.isArray(prefs) || (!prefs.includes("all") && prefs.length >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
required property var modelData
|
||||||
|
width: parent.width
|
||||||
|
text: SettingsData.getScreenDisplayName(modelData)
|
||||||
|
description: modelData.width + "×" + modelData.height
|
||||||
|
checked: {
|
||||||
|
const prefs = root.loadValue("displayPreferences", ["all"]);
|
||||||
|
if (!Array.isArray(prefs) || prefs.includes("all"))
|
||||||
|
return false;
|
||||||
|
return prefs.some(p => p.name === modelData.name);
|
||||||
|
}
|
||||||
|
onToggled: isChecked => {
|
||||||
|
var prefs = root.loadValue("displayPreferences", ["all"]);
|
||||||
|
if (!Array.isArray(prefs) || prefs.includes("all")) {
|
||||||
|
prefs = [];
|
||||||
|
}
|
||||||
|
prefs = prefs.filter(p => p.name !== modelData.name);
|
||||||
|
if (isChecked) {
|
||||||
|
prefs.push({
|
||||||
|
name: modelData.name,
|
||||||
|
model: modelData.model || ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
root.saveValue("displayPreferences", prefs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
780
quickshell/Modules/Settings/DesktopWidgetsTab.qml
Normal file
780
quickshell/Modules/Settings/DesktopWidgetsTab.qml
Normal file
@@ -0,0 +1,780 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
import qs.Modules.Settings.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
DankFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
contentHeight: mainColumn.height + Theme.spacingXL
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
width: Math.min(550, parent.width - Theme.spacingL * 2)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
width: parent.width
|
||||||
|
iconName: "schedule"
|
||||||
|
title: I18n.tr("Desktop Clock")
|
||||||
|
collapsible: true
|
||||||
|
expanded: false
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Enable Desktop Clock")
|
||||||
|
checked: SettingsData.desktopClockEnabled
|
||||||
|
onToggled: checked => SettingsData.set("desktopClockEnabled", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
visible: SettingsData.desktopClockEnabled
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsDropdownRow {
|
||||||
|
text: I18n.tr("Clock Style")
|
||||||
|
options: [I18n.tr("Digital"), I18n.tr("Analog"), I18n.tr("Stacked")]
|
||||||
|
currentValue: {
|
||||||
|
switch (SettingsData.desktopClockStyle) {
|
||||||
|
case "analog":
|
||||||
|
return I18n.tr("Analog");
|
||||||
|
case "stacked":
|
||||||
|
return I18n.tr("Stacked");
|
||||||
|
default:
|
||||||
|
return I18n.tr("Digital");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onValueChanged: value => {
|
||||||
|
switch (value) {
|
||||||
|
case I18n.tr("Analog"):
|
||||||
|
SettingsData.set("desktopClockStyle", "analog");
|
||||||
|
return;
|
||||||
|
case I18n.tr("Stacked"):
|
||||||
|
SettingsData.set("desktopClockStyle", "stacked");
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
SettingsData.set("desktopClockStyle", "digital");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {
|
||||||
|
visible: SettingsData.desktopClockStyle === "analog"
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
visible: SettingsData.desktopClockStyle === "analog"
|
||||||
|
text: I18n.tr("Show Hour Numbers")
|
||||||
|
checked: SettingsData.desktopClockShowAnalogNumbers
|
||||||
|
onToggled: checked => SettingsData.set("desktopClockShowAnalogNumbers", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Show Date")
|
||||||
|
checked: SettingsData.desktopClockShowDate
|
||||||
|
onToggled: checked => SettingsData.set("desktopClockShowDate", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
text: I18n.tr("Transparency")
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
value: Math.round(SettingsData.desktopClockTransparency * 100)
|
||||||
|
unit: "%"
|
||||||
|
onSliderValueChanged: newValue => SettingsData.set("desktopClockTransparency", newValue / 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsColorPicker {
|
||||||
|
colorMode: SettingsData.desktopClockColorMode
|
||||||
|
customColor: SettingsData.desktopClockCustomColor
|
||||||
|
onColorModeSelected: mode => SettingsData.set("desktopClockColorMode", mode)
|
||||||
|
onCustomColorSelected: selectedColor => SettingsData.set("desktopClockCustomColor", selectedColor.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsDisplayPicker {
|
||||||
|
displayPreferences: SettingsData.desktopClockDisplayPreferences
|
||||||
|
onPreferencesChanged: prefs => SettingsData.set("desktopClockDisplayPreferences", prefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: clockResetRow.height + Theme.spacingM * 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: clockResetRow
|
||||||
|
x: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Reset Position")
|
||||||
|
backgroundColor: Theme.surfaceHover
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
buttonHeight: 36
|
||||||
|
onClicked: {
|
||||||
|
SettingsData.set("desktopClockX", -1);
|
||||||
|
SettingsData.set("desktopClockY", -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Reset Size")
|
||||||
|
backgroundColor: Theme.surfaceHover
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
buttonHeight: 36
|
||||||
|
onClicked: {
|
||||||
|
SettingsData.set("desktopClockWidth", 280);
|
||||||
|
SettingsData.set("desktopClockHeight", 180);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
width: parent.width
|
||||||
|
iconName: "monitoring"
|
||||||
|
title: I18n.tr("System Monitor")
|
||||||
|
collapsible: true
|
||||||
|
expanded: false
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Enable System Monitor")
|
||||||
|
checked: SettingsData.systemMonitorEnabled
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorEnabled", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
visible: SettingsData.systemMonitorEnabled
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Show Header")
|
||||||
|
checked: SettingsData.systemMonitorShowHeader
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowHeader", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: graphIntervalColumn.height + Theme.spacingM * 2
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: graphIntervalColumn
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Graph Time Range")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
model: ["1m", "5m", "10m", "30m"]
|
||||||
|
currentIndex: {
|
||||||
|
switch (SettingsData.systemMonitorGraphInterval) {
|
||||||
|
case 60:
|
||||||
|
return 0;
|
||||||
|
case 300:
|
||||||
|
return 1;
|
||||||
|
case 600:
|
||||||
|
return 2;
|
||||||
|
case 1800:
|
||||||
|
return 3;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buttonHeight: 32
|
||||||
|
minButtonWidth: 48
|
||||||
|
textSize: Theme.fontSizeSmall
|
||||||
|
checkEnabled: false
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected)
|
||||||
|
return;
|
||||||
|
const values = [60, 300, 600, 1800];
|
||||||
|
SettingsData.set("systemMonitorGraphInterval", values[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("CPU")
|
||||||
|
checked: SettingsData.systemMonitorShowCpu
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowCpu", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {
|
||||||
|
visible: SettingsData.systemMonitorShowCpu
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
visible: SettingsData.systemMonitorShowCpu
|
||||||
|
text: I18n.tr("CPU Graph")
|
||||||
|
checked: SettingsData.systemMonitorShowCpuGraph
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowCpuGraph", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {
|
||||||
|
visible: SettingsData.systemMonitorShowCpu
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
visible: SettingsData.systemMonitorShowCpu
|
||||||
|
text: I18n.tr("CPU Temperature")
|
||||||
|
checked: SettingsData.systemMonitorShowCpuTemp
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowCpuTemp", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("GPU Temperature")
|
||||||
|
checked: SettingsData.systemMonitorShowGpuTemp
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowGpuTemp", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {
|
||||||
|
visible: SettingsData.systemMonitorShowGpuTemp && DgopService.availableGpus.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: gpuSelectColumn.height + Theme.spacingM * 2
|
||||||
|
visible: SettingsData.systemMonitorShowGpuTemp && DgopService.availableGpus.length > 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: gpuSelectColumn
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("GPU")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: DgopService.availableGpus
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: SettingsData.systemMonitorGpuPciId === modelData.pciId ? Theme.primarySelected : Theme.surfaceHover
|
||||||
|
border.color: SettingsData.systemMonitorGpuPciId === modelData.pciId ? Theme.primary : "transparent"
|
||||||
|
border.width: 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "videocam"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
|
color: SettingsData.systemMonitorGpuPciId === modelData.pciId ? Theme.primary : Theme.surfaceVariantText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSizeSmall - Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.displayName || "Unknown GPU"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.driver || ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 2
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: SettingsData.set("systemMonitorGpuPciId", modelData.pciId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Memory")
|
||||||
|
checked: SettingsData.systemMonitorShowMemory
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowMemory", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {
|
||||||
|
visible: SettingsData.systemMonitorShowMemory
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
visible: SettingsData.systemMonitorShowMemory
|
||||||
|
text: I18n.tr("Memory Graph")
|
||||||
|
checked: SettingsData.systemMonitorShowMemoryGraph
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowMemoryGraph", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Network")
|
||||||
|
checked: SettingsData.systemMonitorShowNetwork
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowNetwork", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {
|
||||||
|
visible: SettingsData.systemMonitorShowNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
visible: SettingsData.systemMonitorShowNetwork
|
||||||
|
text: I18n.tr("Network Graph")
|
||||||
|
checked: SettingsData.systemMonitorShowNetworkGraph
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowNetworkGraph", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Disk")
|
||||||
|
checked: SettingsData.systemMonitorShowDisk
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowDisk", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
text: I18n.tr("Top Processes")
|
||||||
|
checked: SettingsData.systemMonitorShowTopProcesses
|
||||||
|
onToggled: checked => SettingsData.set("systemMonitorShowTopProcesses", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {
|
||||||
|
visible: SettingsData.systemMonitorShowTopProcesses
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: topProcessesColumn.height + Theme.spacingM * 2
|
||||||
|
visible: SettingsData.systemMonitorShowTopProcesses
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: topProcessesColumn
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width - processCountButtons.width - Theme.spacingM
|
||||||
|
text: I18n.tr("Process Count")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
id: processCountButtons
|
||||||
|
model: ["3", "5", "10"]
|
||||||
|
currentIndex: {
|
||||||
|
switch (SettingsData.systemMonitorTopProcessCount) {
|
||||||
|
case 3:
|
||||||
|
return 0;
|
||||||
|
case 5:
|
||||||
|
return 1;
|
||||||
|
case 10:
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buttonHeight: 32
|
||||||
|
minButtonWidth: 36
|
||||||
|
textSize: Theme.fontSizeSmall
|
||||||
|
checkEnabled: false
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected)
|
||||||
|
return;
|
||||||
|
const values = [3, 5, 10];
|
||||||
|
SettingsData.set("systemMonitorTopProcessCount", values[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width - sortByButtons.width - Theme.spacingM
|
||||||
|
text: I18n.tr("Sort By")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
id: sortByButtons
|
||||||
|
model: ["CPU", "MEM"]
|
||||||
|
currentIndex: SettingsData.systemMonitorTopProcessSortBy === "cpu" ? 0 : 1
|
||||||
|
buttonHeight: 32
|
||||||
|
minButtonWidth: 48
|
||||||
|
textSize: Theme.fontSizeSmall
|
||||||
|
checkEnabled: false
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected)
|
||||||
|
return;
|
||||||
|
SettingsData.set("systemMonitorTopProcessSortBy", index === 0 ? "cpu" : "memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsDropdownRow {
|
||||||
|
text: I18n.tr("Layout")
|
||||||
|
options: [I18n.tr("Auto"), I18n.tr("Grid"), I18n.tr("List")]
|
||||||
|
currentValue: {
|
||||||
|
switch (SettingsData.systemMonitorLayoutMode) {
|
||||||
|
case "grid":
|
||||||
|
return I18n.tr("Grid");
|
||||||
|
case "list":
|
||||||
|
return I18n.tr("List");
|
||||||
|
default:
|
||||||
|
return I18n.tr("Auto");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onValueChanged: value => {
|
||||||
|
switch (value) {
|
||||||
|
case I18n.tr("Grid"):
|
||||||
|
SettingsData.set("systemMonitorLayoutMode", "grid");
|
||||||
|
return;
|
||||||
|
case I18n.tr("List"):
|
||||||
|
SettingsData.set("systemMonitorLayoutMode", "list");
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
SettingsData.set("systemMonitorLayoutMode", "auto");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
text: I18n.tr("Transparency")
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
value: Math.round(SettingsData.systemMonitorTransparency * 100)
|
||||||
|
unit: "%"
|
||||||
|
onSliderValueChanged: newValue => SettingsData.set("systemMonitorTransparency", newValue / 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsColorPicker {
|
||||||
|
colorMode: SettingsData.systemMonitorColorMode
|
||||||
|
customColor: SettingsData.systemMonitorCustomColor
|
||||||
|
onColorModeSelected: mode => SettingsData.set("systemMonitorColorMode", mode)
|
||||||
|
onCustomColorSelected: selectedColor => SettingsData.set("systemMonitorCustomColor", selectedColor.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
SettingsDisplayPicker {
|
||||||
|
displayPreferences: SettingsData.systemMonitorDisplayPreferences
|
||||||
|
onPreferencesChanged: prefs => SettingsData.set("systemMonitorDisplayPreferences", prefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: sysMonResetRow.height + Theme.spacingM * 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: sysMonResetRow
|
||||||
|
x: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Reset Position")
|
||||||
|
backgroundColor: Theme.surfaceHover
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
buttonHeight: 36
|
||||||
|
onClicked: {
|
||||||
|
SettingsData.set("systemMonitorX", -1);
|
||||||
|
SettingsData.set("systemMonitorY", -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Reset Size")
|
||||||
|
backgroundColor: Theme.surfaceHover
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
buttonHeight: 36
|
||||||
|
onClicked: {
|
||||||
|
SettingsData.set("systemMonitorWidth", 320);
|
||||||
|
SettingsData.set("systemMonitorHeight", 480);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: variantsColumn.height + Theme.spacingM * 2
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: variantsColumn
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width - addVariantBtn.width - Theme.spacingM
|
||||||
|
text: I18n.tr("Widget Variants")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
id: addVariantBtn
|
||||||
|
text: I18n.tr("Add")
|
||||||
|
iconName: "add"
|
||||||
|
onClicked: {
|
||||||
|
const variant = SettingsData.createSystemMonitorVariant("Monitor " + (SettingsData.systemMonitorVariants.length + 1), SettingsData.getDefaultSystemMonitorConfig());
|
||||||
|
if (variant)
|
||||||
|
ToastService.showInfo(I18n.tr("Variant created - expand to configure"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: variantsListColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: SettingsData.systemMonitorVariants.length > 0
|
||||||
|
|
||||||
|
property var expandedStates: ({})
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: SettingsData.systemMonitorVariants
|
||||||
|
|
||||||
|
SystemMonitorVariantCard {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
variant: modelData
|
||||||
|
expanded: variantsListColumn.expandedStates[modelData.id] || false
|
||||||
|
|
||||||
|
onExpandToggled: isExpanded => {
|
||||||
|
var states = JSON.parse(JSON.stringify(variantsListColumn.expandedStates));
|
||||||
|
states[modelData.id] = isExpanded;
|
||||||
|
variantsListColumn.expandedStates = states;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteRequested: {
|
||||||
|
SettingsData.removeSystemMonitorVariant(modelData.id);
|
||||||
|
ToastService.showInfo(I18n.tr("Variant removed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
onNameChanged: newName => {
|
||||||
|
SettingsData.updateSystemMonitorVariant(modelData.id, {
|
||||||
|
name: newName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfigChanged: (key, value) => {
|
||||||
|
var update = {};
|
||||||
|
update[key] = value;
|
||||||
|
SettingsData.updateSystemMonitorVariant(modelData.id, update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: SettingsData.systemMonitorVariants.length === 0
|
||||||
|
text: I18n.tr("No variants created. Click Add to create a new monitor widget.")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
width: parent.width
|
||||||
|
iconName: "info"
|
||||||
|
title: I18n.tr("Help")
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
radius: 20
|
||||||
|
color: Theme.primarySelected
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "drag_pan"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Move Widget")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Right-click and drag anywhere on the widget")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
radius: 20
|
||||||
|
color: Theme.primarySelected
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "open_in_full"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Resize Widget")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Right-click and drag the bottom-right corner")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -983,9 +983,10 @@ Singleton {
|
|||||||
if (output.logical.x !== undefined && output.logical.y !== undefined)
|
if (output.logical.x !== undefined && output.logical.y !== undefined)
|
||||||
kdlContent += ` position x=${output.logical.x} y=${output.logical.y}\n`;
|
kdlContent += ` position x=${output.logical.x} y=${output.logical.y}\n`;
|
||||||
}
|
}
|
||||||
if (output.vrr_enabled) {
|
if (settings.vrrOnDemand) {
|
||||||
const vrrOnDemand = settings.vrrOnDemand ?? false;
|
kdlContent += ` variable-refresh-rate on-demand=true\n`;
|
||||||
kdlContent += vrrOnDemand ? ` variable-refresh-rate on-demand=true\n` : ` variable-refresh-rate\n`;
|
} else if (output.vrr_enabled) {
|
||||||
|
kdlContent += ` variable-refresh-rate\n`;
|
||||||
}
|
}
|
||||||
if (settings.focusAtStartup)
|
if (settings.focusAtStartup)
|
||||||
kdlContent += ` focus-at-startup\n`;
|
kdlContent += ` focus-at-startup\n`;
|
||||||
|
|||||||
@@ -29,6 +29,15 @@ StyledRect {
|
|||||||
property var pluginPermissions: pluginData ? (pluginData.permissions || []) : []
|
property var pluginPermissions: pluginData ? (pluginData.permissions || []) : []
|
||||||
property bool hasSettings: pluginData && pluginData.settings !== undefined && pluginData.settings !== ""
|
property bool hasSettings: pluginData && pluginData.settings !== undefined && pluginData.settings !== ""
|
||||||
property bool isSystemPlugin: pluginData ? (pluginData.source === "system") : false
|
property bool isSystemPlugin: pluginData ? (pluginData.source === "system") : false
|
||||||
|
property string requiresDms: pluginData ? (pluginData.requires_dms || "") : ""
|
||||||
|
property bool meetsRequirements: requiresDms ? PluginService.checkPluginCompatibility(requiresDms) : true
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SystemUpdateService
|
||||||
|
function onSemverVersionChanged() {
|
||||||
|
root.meetsRequirementsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
property bool isExpanded: expandedPluginId === pluginId
|
property bool isExpanded: expandedPluginId === pluginId
|
||||||
property bool isLoaded: {
|
property bool isLoaded: {
|
||||||
PluginService.loadedPlugins;
|
PluginService.loadedPlugins;
|
||||||
@@ -90,6 +99,41 @@ StyledRect {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: incompatIcon.width + Theme.spacingXS * 2
|
||||||
|
height: 18
|
||||||
|
radius: 9
|
||||||
|
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15)
|
||||||
|
visible: !root.meetsRequirements
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: incompatIcon
|
||||||
|
name: "warning"
|
||||||
|
size: 12
|
||||||
|
color: Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onEntered: {
|
||||||
|
if (root.sharedTooltip)
|
||||||
|
root.sharedTooltip.show(I18n.tr("Requires DMS") + " " + root.requiresDms, parent, 0, 0, "top");
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
if (root.sharedTooltip)
|
||||||
|
root.sharedTooltip.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: root.hasSettings ? (root.isExpanded ? "expand_less" : "expand_more") : ""
|
name: root.hasSettings ? (root.isExpanded ? "expand_less" : "expand_more") : ""
|
||||||
size: 16
|
size: 16
|
||||||
|
|||||||
@@ -120,6 +120,75 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: incompatWarning
|
||||||
|
property var incompatPlugins: []
|
||||||
|
width: parent.width
|
||||||
|
height: incompatWarningColumn.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.1)
|
||||||
|
border.color: Theme.error
|
||||||
|
border.width: 1
|
||||||
|
visible: incompatPlugins.length > 0
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
incompatPlugins = PluginService.getIncompatiblePlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: Qt.callLater(refresh)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: incompatWarningColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "error"
|
||||||
|
size: 16
|
||||||
|
color: Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Incompatible Plugins Loaded")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.error
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Some plugins require a newer version of DMS:") + " " + incompatWarning.incompatPlugins.map(p => p.name + " (" + p.requires_dms + ")").join(", ")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 1
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: PluginService
|
||||||
|
function onPluginLoaded() {
|
||||||
|
incompatWarning.refresh();
|
||||||
|
}
|
||||||
|
function onPluginUnloaded() {
|
||||||
|
incompatWarning.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SystemUpdateService
|
||||||
|
function onSemverVersionChanged() {
|
||||||
|
incompatWarning.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|||||||
119
quickshell/Modules/Settings/Widgets/SettingsColorPicker.qml
Normal file
119
quickshell/Modules/Settings/Widgets/SettingsColorPicker.qml
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string colorMode: "primary"
|
||||||
|
property color customColor: "#ffffff"
|
||||||
|
property string pickerTitle: I18n.tr("Choose Color")
|
||||||
|
|
||||||
|
signal colorModeSelected(string mode)
|
||||||
|
signal customColorSelected(color selectedColor)
|
||||||
|
|
||||||
|
width: parent?.width ?? 0
|
||||||
|
height: colorColumn.height + Theme.spacingM * 2
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: colorColumn
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Color")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: [
|
||||||
|
{
|
||||||
|
id: "primary",
|
||||||
|
label: I18n.tr("Primary"),
|
||||||
|
color: Theme.primary
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "secondary",
|
||||||
|
label: I18n.tr("Secondary"),
|
||||||
|
color: Theme.secondary
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "custom",
|
||||||
|
label: I18n.tr("Custom"),
|
||||||
|
color: root.customColor
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
width: (parent.width - Theme.spacingS * 2) / 3
|
||||||
|
height: 60
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: root.colorMode === modelData.id ? Theme.primarySelected : Theme.surfaceHover
|
||||||
|
border.color: root.colorMode === modelData.id ? Theme.primary : "transparent"
|
||||||
|
border.width: 2
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
radius: 12
|
||||||
|
color: modelData.color
|
||||||
|
border.color: Theme.outline
|
||||||
|
border.width: 1
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
visible: modelData.id === "custom"
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "colorize"
|
||||||
|
size: 14
|
||||||
|
color: Theme.background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.label
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (modelData.id !== "custom") {
|
||||||
|
root.colorModeSelected(modelData.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PopoutService.colorPickerModal.selectedColor = root.customColor;
|
||||||
|
PopoutService.colorPickerModal.pickerTitle = root.pickerTitle;
|
||||||
|
PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
|
||||||
|
root.customColorSelected(selectedColor);
|
||||||
|
root.colorModeSelected("custom");
|
||||||
|
};
|
||||||
|
PopoutService.colorPickerModal.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var displayPreferences: []
|
||||||
|
|
||||||
|
signal preferencesChanged(var preferences)
|
||||||
|
|
||||||
|
readonly property bool allDisplaysEnabled: {
|
||||||
|
if (!Array.isArray(displayPreferences))
|
||||||
|
return true;
|
||||||
|
return displayPreferences.includes("all") || displayPreferences.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
width: parent?.width ?? 0
|
||||||
|
height: displayColumn.height + Theme.spacingM * 2
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: displayColumn
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Displays")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("All displays")
|
||||||
|
checked: root.allDisplaysEnabled
|
||||||
|
onToggled: isChecked => root.preferencesChanged(isChecked ? ["all"] : [])
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: !root.allDisplaysEnabled
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
text: SettingsData.getScreenDisplayName(modelData)
|
||||||
|
description: modelData.width + "×" + modelData.height
|
||||||
|
checked: {
|
||||||
|
const prefs = root.displayPreferences;
|
||||||
|
if (!Array.isArray(prefs) || prefs.includes("all"))
|
||||||
|
return false;
|
||||||
|
return prefs.some(p => p.name === modelData.name);
|
||||||
|
}
|
||||||
|
onToggled: isChecked => {
|
||||||
|
var prefs = root.displayPreferences;
|
||||||
|
if (!Array.isArray(prefs) || prefs.includes("all"))
|
||||||
|
prefs = [];
|
||||||
|
prefs = prefs.filter(p => p.name !== modelData.name);
|
||||||
|
if (isChecked) {
|
||||||
|
prefs.push({
|
||||||
|
name: modelData.name,
|
||||||
|
model: modelData.model || ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
root.preferencesChanged(prefs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
quickshell/Modules/Settings/Widgets/SettingsDivider.qml
Normal file
11
quickshell/Modules/Settings/Widgets/SettingsDivider.qml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent?.width ?? 0
|
||||||
|
height: 1
|
||||||
|
color: Theme.outline
|
||||||
|
opacity: 0.15
|
||||||
|
}
|
||||||
381
quickshell/Modules/Settings/Widgets/SystemMonitorVariantCard.qml
Normal file
381
quickshell/Modules/Settings/Widgets/SystemMonitorVariantCard.qml
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var variant: ({})
|
||||||
|
property bool expanded: false
|
||||||
|
|
||||||
|
signal expandToggled(bool isExpanded)
|
||||||
|
signal deleteRequested
|
||||||
|
signal nameChanged(string newName)
|
||||||
|
signal configChanged(string key, var value)
|
||||||
|
|
||||||
|
readonly property var cfg: variant.config || {}
|
||||||
|
|
||||||
|
function updateConfig(key, value) {
|
||||||
|
var newConfig = JSON.parse(JSON.stringify(cfg));
|
||||||
|
newConfig[key] = value;
|
||||||
|
root.configChanged("config", newConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
width: parent?.width ?? 0
|
||||||
|
height: variantColumn.height
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: variantColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: headerContent.height + Theme.spacingM * 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: headerContent
|
||||||
|
x: Theme.spacingM
|
||||||
|
y: Theme.spacingM
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: root.expanded ? "expand_less" : "expand_more"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - deleteBtn.width - Theme.spacingM * 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.variant.name || "Unnamed"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
property var features: {
|
||||||
|
var f = [];
|
||||||
|
if (root.cfg.showCpu)
|
||||||
|
f.push("CPU");
|
||||||
|
if (root.cfg.showMemory)
|
||||||
|
f.push("RAM");
|
||||||
|
if (root.cfg.showNetwork)
|
||||||
|
f.push("Net");
|
||||||
|
if (root.cfg.showDisk)
|
||||||
|
f.push("Disk");
|
||||||
|
if (root.cfg.showGpuTemp)
|
||||||
|
f.push("GPU");
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
text: features.length > 0 ? features.join(", ") : I18n.tr("No features enabled")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: deleteBtn
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: 16
|
||||||
|
color: deleteMouse.containsMouse ? Theme.error : "transparent"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "delete"
|
||||||
|
size: 16
|
||||||
|
color: deleteMouse.containsMouse ? Theme.background : Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: deleteMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.deleteRequested()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: deleteBtn.width + Theme.spacingM
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.expandToggled(!root.expanded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
visible: root.expanded
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: nameRow.height + Theme.spacingM * 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: nameRow
|
||||||
|
x: Theme.spacingM
|
||||||
|
y: Theme.spacingM
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Name")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: 80
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width - 80 - Theme.spacingM
|
||||||
|
text: root.variant.name || ""
|
||||||
|
onEditingFinished: {
|
||||||
|
if (text !== root.variant.name)
|
||||||
|
root.nameChanged(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show Header")
|
||||||
|
checked: root.cfg.showHeader ?? true
|
||||||
|
onToggled: checked => root.updateConfig("showHeader", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show CPU")
|
||||||
|
checked: root.cfg.showCpu ?? true
|
||||||
|
onToggled: checked => root.updateConfig("showCpu", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show CPU Graph")
|
||||||
|
visible: root.cfg.showCpu
|
||||||
|
checked: root.cfg.showCpuGraph ?? true
|
||||||
|
onToggled: checked => root.updateConfig("showCpuGraph", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show CPU Temp")
|
||||||
|
visible: root.cfg.showCpu
|
||||||
|
checked: root.cfg.showCpuTemp ?? true
|
||||||
|
onToggled: checked => root.updateConfig("showCpuTemp", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show Memory")
|
||||||
|
checked: root.cfg.showMemory ?? true
|
||||||
|
onToggled: checked => root.updateConfig("showMemory", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show Memory Graph")
|
||||||
|
visible: root.cfg.showMemory
|
||||||
|
checked: root.cfg.showMemoryGraph ?? true
|
||||||
|
onToggled: checked => root.updateConfig("showMemoryGraph", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show Network")
|
||||||
|
checked: root.cfg.showNetwork ?? true
|
||||||
|
onToggled: checked => root.updateConfig("showNetwork", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show Network Graph")
|
||||||
|
visible: root.cfg.showNetwork
|
||||||
|
checked: root.cfg.showNetworkGraph ?? true
|
||||||
|
onToggled: checked => root.updateConfig("showNetworkGraph", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show Disk")
|
||||||
|
checked: root.cfg.showDisk ?? true
|
||||||
|
onToggled: checked => root.updateConfig("showDisk", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show GPU Temperature")
|
||||||
|
checked: root.cfg.showGpuTemp ?? false
|
||||||
|
onToggled: checked => root.updateConfig("showGpuTemp", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: root.cfg.showGpuTemp && DgopService.availableGpus.length > 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: Theme.spacingS
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: DgopService.availableGpus
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: root.cfg.gpuPciId === modelData.pciId ? Theme.primarySelected : Theme.surfaceContainer
|
||||||
|
border.color: root.cfg.gpuPciId === modelData.pciId ? Theme.primary : "transparent"
|
||||||
|
border.width: 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "videocam"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
|
color: root.cfg.gpuPciId === modelData.pciId ? Theme.primary : Theme.surfaceVariantText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.displayName || "Unknown GPU"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width - Theme.iconSizeSmall - Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.updateConfig("gpuPciId", modelData.pciId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: Theme.spacingS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
text: I18n.tr("Show Top Processes")
|
||||||
|
checked: root.cfg.showTopProcesses ?? false
|
||||||
|
onToggled: checked => root.updateConfig("showTopProcesses", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDivider {}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
topPadding: Theme.spacingM
|
||||||
|
bottomPadding: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: transparencyLabel
|
||||||
|
text: I18n.tr("Transparency")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width - transparencyLabel.width - transparencyValue.width
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: transparencyValue
|
||||||
|
text: transparencySlider.value + "%"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
|
id: transparencySlider
|
||||||
|
width: parent.width
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
value: Math.round((root.cfg.transparency ?? 0.8) * 100)
|
||||||
|
showValue: false
|
||||||
|
wheelEnabled: false
|
||||||
|
onSliderDragFinished: finalValue => root.updateConfig("transparency", finalValue / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: Theme.spacingM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
172
quickshell/PLUGINS/ExampleDesktopClock/DesktopClock.qml
Normal file
172
quickshell/PLUGINS/ExampleDesktopClock/DesktopClock.qml
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Modules.Plugins
|
||||||
|
|
||||||
|
DesktopPluginComponent {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
minWidth: 120
|
||||||
|
minHeight: 120
|
||||||
|
|
||||||
|
property bool showSeconds: pluginData.showSeconds ?? true
|
||||||
|
property bool showDate: pluginData.showDate ?? true
|
||||||
|
property string clockStyle: pluginData.clockStyle ?? "analog"
|
||||||
|
property real backgroundOpacity: (pluginData.backgroundOpacity ?? 50) / 100
|
||||||
|
|
||||||
|
SystemClock {
|
||||||
|
id: systemClock
|
||||||
|
precision: root.showSeconds ? SystemClock.Seconds : SystemClock.Minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: background
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
opacity: root.backgroundOpacity
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
sourceComponent: root.clockStyle === "digital" ? digitalClock : analogClock
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: analogClock
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: analogClockRoot
|
||||||
|
|
||||||
|
property real clockSize: Math.min(width, height) - (root.showDate ? 30 : 0)
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: clockFace
|
||||||
|
width: analogClockRoot.clockSize
|
||||||
|
height: analogClockRoot.clockSize
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 12
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
property real markAngle: index * 30
|
||||||
|
property real markRadius: clockFace.width / 2 - 8
|
||||||
|
|
||||||
|
x: clockFace.width / 2 + markRadius * Math.sin(markAngle * Math.PI / 180) - width / 2
|
||||||
|
y: clockFace.height / 2 - markRadius * Math.cos(markAngle * Math.PI / 180) - height / 2
|
||||||
|
width: index % 3 === 0 ? 8 : 4
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: index % 3 === 0 ? Theme.primary : Theme.outlineVariant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: hourHand
|
||||||
|
property int hours: systemClock.date?.getHours() % 12 ?? 0
|
||||||
|
property int minutes: systemClock.date?.getMinutes() ?? 0
|
||||||
|
|
||||||
|
x: clockFace.width / 2 - width / 2
|
||||||
|
y: clockFace.height / 2 - height + 4
|
||||||
|
width: 6
|
||||||
|
height: clockFace.height * 0.25
|
||||||
|
radius: 3
|
||||||
|
color: Theme.primary
|
||||||
|
antialiasing: true
|
||||||
|
transformOrigin: Item.Bottom
|
||||||
|
rotation: (hours + minutes / 60) * 30
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: minuteHand
|
||||||
|
property int minutes: systemClock.date?.getMinutes() ?? 0
|
||||||
|
property int seconds: systemClock.date?.getSeconds() ?? 0
|
||||||
|
|
||||||
|
x: clockFace.width / 2 - width / 2
|
||||||
|
y: clockFace.height / 2 - height + 4
|
||||||
|
width: 4
|
||||||
|
height: clockFace.height * 0.35
|
||||||
|
radius: 2
|
||||||
|
color: Theme.onSurface
|
||||||
|
antialiasing: true
|
||||||
|
transformOrigin: Item.Bottom
|
||||||
|
rotation: (minutes + seconds / 60) * 6
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: secondHand
|
||||||
|
visible: root.showSeconds
|
||||||
|
property int seconds: systemClock.date?.getSeconds() ?? 0
|
||||||
|
|
||||||
|
x: clockFace.width / 2 - width / 2
|
||||||
|
y: clockFace.height / 2 - height + 4
|
||||||
|
width: 2
|
||||||
|
height: clockFace.height * 0.4
|
||||||
|
radius: 1
|
||||||
|
color: Theme.error
|
||||||
|
antialiasing: true
|
||||||
|
transformOrigin: Item.Bottom
|
||||||
|
rotation: seconds * 6
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 10
|
||||||
|
height: 10
|
||||||
|
radius: 5
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: root.showDate
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: Theme.spacingXS
|
||||||
|
text: systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: digitalClock
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: digitalRoot
|
||||||
|
|
||||||
|
property real timeFontSize: Math.min(width * 0.16, height * (root.showDate ? 0.4 : 0.5))
|
||||||
|
property real dateFontSize: Math.max(Theme.fontSizeSmall, timeFontSize * 0.35)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: timeText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.verticalCenterOffset: root.showDate ? -digitalRoot.dateFontSize * 0.8 : 0
|
||||||
|
text: systemClock.date?.toLocaleTimeString(Qt.locale(), root.showSeconds ? "hh:mm:ss" : "hh:mm") ?? ""
|
||||||
|
font.pixelSize: digitalRoot.timeFontSize
|
||||||
|
font.weight: Font.Bold
|
||||||
|
font.family: "monospace"
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: dateText
|
||||||
|
visible: root.showDate
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: timeText.bottom
|
||||||
|
anchors.topMargin: Theme.spacingXS
|
||||||
|
text: systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? ""
|
||||||
|
font.pixelSize: digitalRoot.dateFontSize
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Modules.Plugins
|
||||||
|
|
||||||
|
PluginSettings {
|
||||||
|
id: root
|
||||||
|
pluginId: "exampleDesktopClock"
|
||||||
|
|
||||||
|
SelectionSetting {
|
||||||
|
settingKey: "clockStyle"
|
||||||
|
label: I18n.tr("Clock Style")
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: I18n.tr("Analog"),
|
||||||
|
value: "analog"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: I18n.tr("Digital"),
|
||||||
|
value: "digital"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
defaultValue: "analog"
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleSetting {
|
||||||
|
settingKey: "showSeconds"
|
||||||
|
label: I18n.tr("Show Seconds")
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleSetting {
|
||||||
|
settingKey: "showDate"
|
||||||
|
label: I18n.tr("Show Date")
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
|
|
||||||
|
SliderSetting {
|
||||||
|
settingKey: "backgroundOpacity"
|
||||||
|
label: I18n.tr("Background Opacity")
|
||||||
|
defaultValue: 50
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
unit: "%"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
quickshell/PLUGINS/ExampleDesktopClock/plugin.json
Normal file
17
quickshell/PLUGINS/ExampleDesktopClock/plugin.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"id": "exampleDesktopClock",
|
||||||
|
"name": "Desktop Clock",
|
||||||
|
"description": "An example desktop widget displaying an analog clock",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "DankMaterialShell",
|
||||||
|
"type": "desktop",
|
||||||
|
"capabilities": ["desktop-widget", "clock"],
|
||||||
|
"component": "./DesktopClock.qml",
|
||||||
|
"icon": "schedule",
|
||||||
|
"settings": "./DesktopClockSettings.qml",
|
||||||
|
"requires_dms": ">=1.2.0",
|
||||||
|
"permissions": [
|
||||||
|
"settings_read",
|
||||||
|
"settings_write"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -80,7 +80,7 @@ The manifest file defines plugin metadata and configuration.
|
|||||||
- `description`: Short description of plugin functionality (displayed in UI)
|
- `description`: Short description of plugin functionality (displayed in UI)
|
||||||
- `version`: Semantic version string (e.g., "1.0.0")
|
- `version`: Semantic version string (e.g., "1.0.0")
|
||||||
- `author`: Plugin creator name or email
|
- `author`: Plugin creator name or email
|
||||||
- `type`: Plugin type - "widget", "daemon", or "launcher"
|
- `type`: Plugin type - "widget", "daemon", "launcher", or "desktop"
|
||||||
- `capabilities`: Array of plugin capabilities (e.g., ["dankbar-widget"], ["control-center"], ["monitoring"])
|
- `capabilities`: Array of plugin capabilities (e.g., ["dankbar-widget"], ["control-center"], ["monitoring"])
|
||||||
- `component`: Relative path to main QML component file
|
- `component`: Relative path to main QML component file
|
||||||
|
|
||||||
@@ -1450,6 +1450,191 @@ See `PLUGINS/LauncherExample/` for a complete working example demonstrating:
|
|||||||
- Settings integration
|
- Settings integration
|
||||||
- Proper error handling
|
- Proper error handling
|
||||||
|
|
||||||
|
## Desktop Plugins
|
||||||
|
|
||||||
|
Desktop plugins are widgets that appear directly on the desktop background layer. They can be dragged, resized, and positioned freely by the user.
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
Desktop plugins enable you to:
|
||||||
|
- Display widgets on the desktop background
|
||||||
|
- Support drag-and-drop positioning
|
||||||
|
- Support resize via corner handles
|
||||||
|
- Persist position and size across sessions
|
||||||
|
- Provide settings for customization
|
||||||
|
|
||||||
|
### Plugin Type Configuration
|
||||||
|
|
||||||
|
To create a desktop plugin, set the plugin type in `plugin.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "myDesktopWidget",
|
||||||
|
"name": "My Desktop Widget",
|
||||||
|
"description": "A custom desktop widget",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Your Name",
|
||||||
|
"type": "desktop",
|
||||||
|
"capabilities": ["desktop-widget"],
|
||||||
|
"component": "./MyWidget.qml",
|
||||||
|
"icon": "widgets",
|
||||||
|
"settings": "./MySettings.qml",
|
||||||
|
"permissions": ["settings_read", "settings_write"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Desktop Widget Component Contract
|
||||||
|
|
||||||
|
Create your widget component (`MyWidget.qml`) with the following interface:
|
||||||
|
|
||||||
|
```qml
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Injected properties (provided by DesktopPluginWrapper)
|
||||||
|
property var pluginService: null
|
||||||
|
property string pluginId: ""
|
||||||
|
property bool editMode: false
|
||||||
|
property real widgetWidth: 200
|
||||||
|
property real widgetHeight: 200
|
||||||
|
|
||||||
|
// Optional: Define minimum size constraints
|
||||||
|
property real minWidth: 100
|
||||||
|
property real minHeight: 100
|
||||||
|
|
||||||
|
// Your widget content
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
opacity: 0.85
|
||||||
|
|
||||||
|
// Widget content here
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Hello Desktop!"
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Injected Properties
|
||||||
|
|
||||||
|
Desktop widgets receive these properties automatically:
|
||||||
|
|
||||||
|
- `pluginService`: Reference to PluginService for data persistence
|
||||||
|
- `pluginId`: The plugin's unique identifier
|
||||||
|
- `editMode`: Boolean indicating if the user is in edit mode (dragging/resizing)
|
||||||
|
- `widgetWidth`: Current width of the widget container
|
||||||
|
- `widgetHeight`: Current height of the widget container
|
||||||
|
|
||||||
|
### Optional Properties
|
||||||
|
|
||||||
|
Define these properties on your widget to customize behavior:
|
||||||
|
|
||||||
|
- `minWidth`: Minimum allowed width (default: 100)
|
||||||
|
- `minHeight`: Minimum allowed height (default: 100)
|
||||||
|
|
||||||
|
### Loading and Saving Data
|
||||||
|
|
||||||
|
Use the injected `pluginService` to persist widget-specific data:
|
||||||
|
|
||||||
|
```qml
|
||||||
|
property string myValue: pluginService ? pluginService.loadPluginData(pluginId, "myValue", "default") : "default"
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: pluginService
|
||||||
|
function onPluginDataChanged(changedPluginId) {
|
||||||
|
if (changedPluginId !== pluginId) return;
|
||||||
|
root.myValue = pluginService.loadPluginData(pluginId, "myValue", "default");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveMyValue(value) {
|
||||||
|
if (pluginService) {
|
||||||
|
pluginService.savePluginData(pluginId, "myValue", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Position and Size Persistence
|
||||||
|
|
||||||
|
Position (`desktopX`, `desktopY`) and size (`desktopWidth`, `desktopHeight`) are automatically persisted by the `DesktopPluginWrapper`. You don't need to handle this in your widget.
|
||||||
|
|
||||||
|
### Edit Mode
|
||||||
|
|
||||||
|
When `editMode` is true, the user is repositioning or resizing the widget. You can use this to:
|
||||||
|
- Show visual indicators
|
||||||
|
- Disable interactive elements
|
||||||
|
- Display additional controls
|
||||||
|
|
||||||
|
```qml
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
border.color: root.editMode ? Theme.primary : "transparent"
|
||||||
|
border.width: root.editMode ? 2 : 0
|
||||||
|
|
||||||
|
// Content that should be disabled during edit mode
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: !root.editMode
|
||||||
|
onClicked: doSomething()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Settings Component
|
||||||
|
|
||||||
|
Create a settings component using `PluginSettings`:
|
||||||
|
|
||||||
|
```qml
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
import qs.Modules.Plugins
|
||||||
|
|
||||||
|
PluginSettings {
|
||||||
|
pluginId: "myDesktopWidget"
|
||||||
|
|
||||||
|
ToggleSetting {
|
||||||
|
settingKey: "showBorder"
|
||||||
|
label: "Show Border"
|
||||||
|
description: "Display a border around the widget"
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionSetting {
|
||||||
|
settingKey: "theme"
|
||||||
|
label: "Theme"
|
||||||
|
options: [
|
||||||
|
{label: "Light", value: "light"},
|
||||||
|
{label: "Dark", value: "dark"}
|
||||||
|
]
|
||||||
|
defaultValue: "dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Interaction
|
||||||
|
|
||||||
|
Desktop widgets support:
|
||||||
|
|
||||||
|
1. **Drag**: Click and drag anywhere on the widget (when in edit mode)
|
||||||
|
2. **Resize**: Drag the bottom-right corner handle (when in edit mode)
|
||||||
|
3. **Edit Mode Toggle**: Click the edit button in the bottom-right corner of the screen
|
||||||
|
|
||||||
|
### Example Plugin
|
||||||
|
|
||||||
|
See `PLUGINS/ExampleDesktopClock/` for a complete working example demonstrating:
|
||||||
|
- Analog and digital clock styles
|
||||||
|
- Settings integration
|
||||||
|
- Responsive sizing
|
||||||
|
- Edit mode handling
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- **Plugin Schema**: `plugin-schema.json` - JSON Schema for validation
|
- **Plugin Schema**: `plugin-schema.json` - JSON Schema for validation
|
||||||
@@ -1458,10 +1643,12 @@ See `PLUGINS/LauncherExample/` for a complete working example demonstrating:
|
|||||||
- [WorldClock](https://github.com/rochacbruno/WorldClock)
|
- [WorldClock](https://github.com/rochacbruno/WorldClock)
|
||||||
- [LauncherExample](./LauncherExample/)
|
- [LauncherExample](./LauncherExample/)
|
||||||
- [Calculator](https://github.com/rochacbruno/DankCalculator)
|
- [Calculator](https://github.com/rochacbruno/DankCalculator)
|
||||||
|
- [Desktop Clock](./ExampleDesktopClock/)
|
||||||
- **PluginService**: `Services/PluginService.qml`
|
- **PluginService**: `Services/PluginService.qml`
|
||||||
- **Settings UI**: `Modules/Settings/PluginsTab.qml`
|
- **Settings UI**: `Modules/Settings/PluginsTab.qml`
|
||||||
- **DankBar Integration**: `Modules/DankBar/DankBar.qml`
|
- **DankBar Integration**: `Modules/DankBar/DankBar.qml`
|
||||||
- **Launcher Integration**: `Modules/AppDrawer/AppLauncher.qml`
|
- **Launcher Integration**: `Modules/AppDrawer/AppLauncher.qml`
|
||||||
|
- **Desktop Widget Integration**: `Modules/DesktopWidgetLayer.qml`
|
||||||
- **Theme Reference**: `Common/Theme.qml`
|
- **Theme Reference**: `Common/Theme.qml`
|
||||||
- **Widget Library**: `Widgets/`
|
- **Widget Library**: `Widgets/`
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Plugin type",
|
"description": "Plugin type",
|
||||||
"enum": ["widget", "daemon", "launcher"]
|
"enum": ["widget", "daemon", "launcher", "desktop"]
|
||||||
},
|
},
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import QtQuick
|
|||||||
import Qt.labs.folderlistmodel
|
import Qt.labs.folderlistmodel
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -15,6 +16,7 @@ Singleton {
|
|||||||
property var pluginWidgetComponents: ({})
|
property var pluginWidgetComponents: ({})
|
||||||
property var pluginDaemonComponents: ({})
|
property var pluginDaemonComponents: ({})
|
||||||
property var pluginLauncherComponents: ({})
|
property var pluginLauncherComponents: ({})
|
||||||
|
property var pluginDesktopComponents: ({})
|
||||||
property var availablePluginsList: []
|
property var availablePluginsList: []
|
||||||
property string pluginDirectory: {
|
property string pluginDirectory: {
|
||||||
var configDir = StandardPaths.writableLocation(StandardPaths.ConfigLocation);
|
var configDir = StandardPaths.writableLocation(StandardPaths.ConfigLocation);
|
||||||
@@ -206,6 +208,7 @@ Singleton {
|
|||||||
info.loaded = isPluginLoaded(manifest.id);
|
info.loaded = isPluginLoaded(manifest.id);
|
||||||
info.type = manifest.type || "widget";
|
info.type = manifest.type || "widget";
|
||||||
info.source = sourceTag;
|
info.source = sourceTag;
|
||||||
|
info.requires_dms = manifest.requires_dms || null;
|
||||||
|
|
||||||
const existing = availablePlugins[manifest.id];
|
const existing = availablePlugins[manifest.id];
|
||||||
const shouldReplace = (!existing) || (existing && existing.source === "system" && sourceTag === "user");
|
const shouldReplace = (!existing) || (existing && existing.source === "system" && sourceTag === "user");
|
||||||
@@ -262,6 +265,7 @@ Singleton {
|
|||||||
|
|
||||||
const isDaemon = plugin.type === "daemon";
|
const isDaemon = plugin.type === "daemon";
|
||||||
const isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"));
|
const isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"));
|
||||||
|
const isDesktop = plugin.type === "desktop";
|
||||||
|
|
||||||
const prevInstance = pluginInstances[pluginId];
|
const prevInstance = pluginInstances[pluginId];
|
||||||
if (prevInstance) {
|
if (prevInstance) {
|
||||||
@@ -302,6 +306,10 @@ Singleton {
|
|||||||
const newLaunchers = Object.assign({}, pluginLauncherComponents);
|
const newLaunchers = Object.assign({}, pluginLauncherComponents);
|
||||||
newLaunchers[pluginId] = comp;
|
newLaunchers[pluginId] = comp;
|
||||||
pluginLauncherComponents = newLaunchers;
|
pluginLauncherComponents = newLaunchers;
|
||||||
|
} else if (isDesktop) {
|
||||||
|
const newDesktop = Object.assign({}, pluginDesktopComponents);
|
||||||
|
newDesktop[pluginId] = comp;
|
||||||
|
pluginDesktopComponents = newDesktop;
|
||||||
} else {
|
} else {
|
||||||
const newComponents = Object.assign({}, pluginWidgetComponents);
|
const newComponents = Object.assign({}, pluginWidgetComponents);
|
||||||
newComponents[pluginId] = comp;
|
newComponents[pluginId] = comp;
|
||||||
@@ -332,6 +340,7 @@ Singleton {
|
|||||||
try {
|
try {
|
||||||
const isDaemon = plugin.type === "daemon";
|
const isDaemon = plugin.type === "daemon";
|
||||||
const isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"));
|
const isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"));
|
||||||
|
const isDesktop = plugin.type === "desktop";
|
||||||
|
|
||||||
const instance = pluginInstances[pluginId];
|
const instance = pluginInstances[pluginId];
|
||||||
if (instance) {
|
if (instance) {
|
||||||
@@ -349,6 +358,10 @@ Singleton {
|
|||||||
const newLaunchers = Object.assign({}, pluginLauncherComponents);
|
const newLaunchers = Object.assign({}, pluginLauncherComponents);
|
||||||
delete newLaunchers[pluginId];
|
delete newLaunchers[pluginId];
|
||||||
pluginLauncherComponents = newLaunchers;
|
pluginLauncherComponents = newLaunchers;
|
||||||
|
} else if (isDesktop && pluginDesktopComponents[pluginId]) {
|
||||||
|
const newDesktop = Object.assign({}, pluginDesktopComponents);
|
||||||
|
delete newDesktop[pluginId];
|
||||||
|
pluginDesktopComponents = newDesktop;
|
||||||
} else if (pluginWidgetComponents[pluginId]) {
|
} else if (pluginWidgetComponents[pluginId]) {
|
||||||
const newComponents = Object.assign({}, pluginWidgetComponents);
|
const newComponents = Object.assign({}, pluginWidgetComponents);
|
||||||
delete newComponents[pluginId];
|
delete newComponents[pluginId];
|
||||||
@@ -376,6 +389,10 @@ Singleton {
|
|||||||
return pluginDaemonComponents;
|
return pluginDaemonComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDesktopComponents() {
|
||||||
|
return pluginDesktopComponents;
|
||||||
|
}
|
||||||
|
|
||||||
function getAvailablePlugins() {
|
function getAvailablePlugins() {
|
||||||
return availablePluginsList;
|
return availablePluginsList;
|
||||||
}
|
}
|
||||||
@@ -667,4 +684,21 @@ Singleton {
|
|||||||
globalVars = newGlobals;
|
globalVars = newGlobals;
|
||||||
globalVarChanged(pluginId, varName);
|
globalVarChanged(pluginId, varName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkPluginCompatibility(requiresDms) {
|
||||||
|
if (!requiresDms)
|
||||||
|
return true;
|
||||||
|
return SystemUpdateService.checkVersionRequirement(requiresDms, SystemUpdateService.getParsedShellVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIncompatiblePlugins() {
|
||||||
|
const result = [];
|
||||||
|
for (const pluginId in availablePlugins) {
|
||||||
|
const plugin = availablePlugins[pluginId];
|
||||||
|
if (plugin.loaded && plugin.requires_dms && !checkPluginCompatibility(plugin.requires_dms)) {
|
||||||
|
result.push(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ Singleton {
|
|||||||
property bool distributionSupported: false
|
property bool distributionSupported: false
|
||||||
property string shellVersion: ""
|
property string shellVersion: ""
|
||||||
property string shellCodename: ""
|
property string shellCodename: ""
|
||||||
|
property string semverVersion: ""
|
||||||
|
|
||||||
|
function getParsedShellVersion() {
|
||||||
|
return parseVersion(semverVersion);
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var archBasedUCSettings: {
|
readonly property var archBasedUCSettings: {
|
||||||
"listUpdatesSettings": {
|
"listUpdatesSettings": {
|
||||||
@@ -135,6 +140,18 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: semverDetection
|
||||||
|
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f VERSION ]; then cat VERSION; fi`]
|
||||||
|
running: true
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
semverVersion = text.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: codenameDetection
|
id: codenameDetection
|
||||||
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f CODENAME ]; then cat CODENAME; fi`]
|
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f CODENAME ]; then cat CODENAME; fi`]
|
||||||
@@ -288,4 +305,86 @@ Singleton {
|
|||||||
return "SUCCESS: Now checking...";
|
return "SUCCESS: Now checking...";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseVersion(versionStr) {
|
||||||
|
if (!versionStr || typeof versionStr !== "string")
|
||||||
|
return {
|
||||||
|
major: 0,
|
||||||
|
minor: 0,
|
||||||
|
patch: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
let v = versionStr.trim();
|
||||||
|
if (v.startsWith("v"))
|
||||||
|
v = v.substring(1);
|
||||||
|
|
||||||
|
const dashIdx = v.indexOf("-");
|
||||||
|
if (dashIdx !== -1)
|
||||||
|
v = v.substring(0, dashIdx);
|
||||||
|
|
||||||
|
const plusIdx = v.indexOf("+");
|
||||||
|
if (plusIdx !== -1)
|
||||||
|
v = v.substring(0, plusIdx);
|
||||||
|
|
||||||
|
const parts = v.split(".");
|
||||||
|
return {
|
||||||
|
major: parseInt(parts[0], 10) || 0,
|
||||||
|
minor: parseInt(parts[1], 10) || 0,
|
||||||
|
patch: parseInt(parts[2], 10) || 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareVersions(v1, v2) {
|
||||||
|
if (v1.major !== v2.major)
|
||||||
|
return v1.major - v2.major;
|
||||||
|
if (v1.minor !== v2.minor)
|
||||||
|
return v1.minor - v2.minor;
|
||||||
|
return v1.patch - v2.patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkVersionRequirement(requirementStr, currentVersion) {
|
||||||
|
if (!requirementStr || typeof requirementStr !== "string")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const req = requirementStr.trim();
|
||||||
|
let operator = "";
|
||||||
|
let versionPart = req;
|
||||||
|
|
||||||
|
if (req.startsWith(">=")) {
|
||||||
|
operator = ">=";
|
||||||
|
versionPart = req.substring(2);
|
||||||
|
} else if (req.startsWith("<=")) {
|
||||||
|
operator = "<=";
|
||||||
|
versionPart = req.substring(2);
|
||||||
|
} else if (req.startsWith(">")) {
|
||||||
|
operator = ">";
|
||||||
|
versionPart = req.substring(1);
|
||||||
|
} else if (req.startsWith("<")) {
|
||||||
|
operator = "<";
|
||||||
|
versionPart = req.substring(1);
|
||||||
|
} else if (req.startsWith("=")) {
|
||||||
|
operator = "=";
|
||||||
|
versionPart = req.substring(1);
|
||||||
|
} else {
|
||||||
|
operator = ">=";
|
||||||
|
}
|
||||||
|
|
||||||
|
const reqVersion = parseVersion(versionPart);
|
||||||
|
const cmp = compareVersions(currentVersion, reqVersion);
|
||||||
|
|
||||||
|
switch (operator) {
|
||||||
|
case ">=":
|
||||||
|
return cmp >= 0;
|
||||||
|
case ">":
|
||||||
|
return cmp > 0;
|
||||||
|
case "<=":
|
||||||
|
return cmp <= 0;
|
||||||
|
case "<":
|
||||||
|
return cmp < 0;
|
||||||
|
case "=":
|
||||||
|
return cmp === 0;
|
||||||
|
default:
|
||||||
|
return cmp >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user