mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
@@ -53,9 +53,13 @@ Item {
|
||||
readonly property bool testPluggedIn: false
|
||||
readonly property string deviceNativePath: widgetSettings.deviceNativePath || ""
|
||||
|
||||
readonly property var device: BatteryService.resolveDevice(deviceNativePath)
|
||||
readonly property var battery: device && !BatteryService.isBluetoothDevice(device) ? device : null
|
||||
readonly property var bluetoothDevice: device && BatteryService.isBluetoothDevice(device) ? device : null
|
||||
readonly property var battery: BatteryService.findUPowerDevice(deviceNativePath)
|
||||
readonly property var bluetoothDevice: deviceNativePath ? BatteryService.findBluetoothDevice(deviceNativePath) : null
|
||||
readonly property var device: {
|
||||
if (deviceNativePath)
|
||||
return bluetoothDevice || battery;
|
||||
return BatteryService.primaryDevice;
|
||||
}
|
||||
readonly property bool hasBluetoothBattery: BatteryService.isBluetoothDevice(device)
|
||||
|
||||
readonly property bool isReady: testMode ? true : (BatteryService.ready && BatteryService.isDeviceReady(device))
|
||||
@@ -82,7 +86,7 @@ Item {
|
||||
hasNotifiedLowBattery = true;
|
||||
ToastService.showWarning(I18n.tr("toast.battery.low"), I18n.tr("toast.battery.low-desc", {
|
||||
"percent": Math.round(currentPercent)
|
||||
}));
|
||||
}), "battery-exclamation", "warning", 4000, "", null);
|
||||
} else if (hasNotifiedLowBattery && (charging || pluggedIn || currentPercent > warningThreshold + 5)) {
|
||||
hasNotifiedLowBattery = false;
|
||||
}
|
||||
@@ -93,14 +97,15 @@ Item {
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: battery
|
||||
target: device
|
||||
function onPercentageChanged() {
|
||||
if (battery) {
|
||||
if (device) {
|
||||
maybeNotify(getCurrentPercent(), isCharging, isPluggedIn, isReady);
|
||||
}
|
||||
}
|
||||
|
||||
function onStateChanged() {
|
||||
if (battery) {
|
||||
if (device) {
|
||||
if (isCharging || isPluggedIn) {
|
||||
hasNotifiedLowBattery = false;
|
||||
}
|
||||
@@ -110,12 +115,7 @@ Item {
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: bluetoothDevice
|
||||
function onBatteryChanged() {
|
||||
if (BatteryService.isDeviceReady(bluetoothDevice)) {
|
||||
maybeNotify(BatteryService.getPercentage(bluetoothDevice), BatteryService.isCharging(bluetoothDevice), BatteryService.isPluggedIn(bluetoothDevice), true);
|
||||
}
|
||||
}
|
||||
target: (device && BatteryService.isBluetoothDevice(device)) ? device : null
|
||||
}
|
||||
|
||||
NPopupContextMenu {
|
||||
@@ -141,7 +141,6 @@ Item {
|
||||
|
||||
BarPill {
|
||||
id: pill
|
||||
|
||||
screen: root.screen
|
||||
oppositeDirection: BarService.getPillDirection(root)
|
||||
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, testPluggedIn, true) : BatteryService.getIcon(percent, isCharging, isPluggedIn, isReady)
|
||||
@@ -156,40 +155,48 @@ Item {
|
||||
tooltipText: {
|
||||
let lines = [];
|
||||
if (testMode) {
|
||||
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`);
|
||||
lines.push("Time left: " + Time.formatVagueHumanReadableDuration(12345) + ".");
|
||||
return lines.join("\n");
|
||||
}
|
||||
if (!isReady || !isDevicePresent) {
|
||||
return I18n.tr("battery.no-battery-detected");
|
||||
}
|
||||
if (battery) {
|
||||
if (!isPluggedIn && battery.timeToEmpty > 0) {
|
||||
lines.push(I18n.tr("battery.time-left", {
|
||||
"time": Time.formatVagueHumanReadableDuration(battery.timeToEmpty)
|
||||
}));
|
||||
const isInternal = device === BatteryService.primaryDevice && BatteryService.isLaptopBattery;
|
||||
|
||||
if (isInternal) {
|
||||
let timeText = BatteryService.getTimeRemainingText(device);
|
||||
if (timeText && timeText !== I18n.tr("common.idle") && timeText !== I18n.tr("battery.no-battery-detected") && timeText !== I18n.tr("battery.plugged-in")) {
|
||||
lines.push(timeText);
|
||||
}
|
||||
if (!isPluggedIn && battery.timeToFull > 0) {
|
||||
lines.push(I18n.tr("battery.time-until-full", {
|
||||
"time": Time.formatVagueHumanReadableDuration(battery.timeToFull)
|
||||
}));
|
||||
|
||||
let rateText = BatteryService.getRateText(device);
|
||||
if (rateText) {
|
||||
lines.push(rateText);
|
||||
}
|
||||
if (battery.changeRate !== undefined) {
|
||||
const rate = Math.abs(battery.changeRate);
|
||||
if (isPluggedIn) {
|
||||
lines.push(I18n.tr("battery.plugged-in"));
|
||||
} else if (isCharging) {
|
||||
lines.push(I18n.tr("battery.charging-rate", {
|
||||
"rate": rate.toFixed(2)
|
||||
}));
|
||||
} else {
|
||||
lines.push(I18n.tr("battery.discharging-rate", {
|
||||
"rate": rate.toFixed(2)
|
||||
}));
|
||||
} else if (device) {
|
||||
// External / Peripheral Device (Phone, Keyboard, Mouse, Gamepad, Headphone etc.)
|
||||
let name = BatteryService.getDeviceName(device);
|
||||
let pct = Math.round(BatteryService.getPercentage(device));
|
||||
lines.push(name + ": " + pct + suffix);
|
||||
}
|
||||
|
||||
// If we are showing the main laptop battery, append external devices
|
||||
if (isInternal) {
|
||||
var external = BatteryService.externalBatteries;
|
||||
if (external.length > 0) {
|
||||
if (lines.length > 0)
|
||||
lines.push(""); // Separator
|
||||
for (var j = 0; j < external.length; j++) {
|
||||
var dev = external[j];
|
||||
var dName = BatteryService.getDeviceName(dev);
|
||||
var dPct = Math.round(BatteryService.getPercentage(dev));
|
||||
lines.push(dName + ": " + dPct + suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
onClicked: PanelService.getPanel("batteryPanel", screen)?.toggle(this)
|
||||
onRightClicked: {
|
||||
PanelService.showContextMenu(contextMenu, pill, screen);
|
||||
|
||||
@@ -4,6 +4,7 @@ import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Hardware
|
||||
import qs.Widgets
|
||||
import qs.Services.Hardware
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
@@ -24,14 +25,7 @@ ColumnLayout {
|
||||
property bool valueHideIfNotDetected: widgetData.hideIfNotDetected !== undefined ? widgetData.hideIfNotDetected : widgetMetadata.hideIfNotDetected
|
||||
property bool valueHideIfIdle: widgetData.hideIfIdle !== undefined ? widgetData.hideIfIdle : widgetMetadata.hideIfIdle
|
||||
|
||||
property var deviceModel: BatteryService.getDeviceOptionsModel()
|
||||
|
||||
Connections {
|
||||
target: BatteryService
|
||||
function onDevicesChanged() {
|
||||
deviceModel = BatteryService.getDeviceOptionsModel();
|
||||
}
|
||||
}
|
||||
property var deviceModel: BatteryService.devicesModel
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
@@ -83,7 +77,7 @@ ColumnLayout {
|
||||
icon: "refresh"
|
||||
// TODO i18n
|
||||
tooltipText: "Refresh device list"
|
||||
onClicked: deviceModel = BatteryService.getDeviceOptionsModel()
|
||||
onClicked: BatteryService.devicesModel = BatteryService.buildDeviceModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,48 +11,23 @@ import qs.Services.UI
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// 1. Centralized list of all batteries
|
||||
readonly property var devices: {
|
||||
var list = [];
|
||||
var seenPaths = new Set();
|
||||
// MARK: BatteryService
|
||||
// Primary battery device (prioritizes laptop over Bluetooth)
|
||||
readonly property var primaryDevice: _laptopBattery || _bluetoothBattery || null
|
||||
// Whether the primary device is a laptop battery
|
||||
readonly property bool isLaptopBattery: _laptopBattery !== null && primaryDevice === _laptopBattery
|
||||
readonly property real batteryPercentage: getPercentage(primaryDevice)
|
||||
readonly property bool batteryCharging: isCharging(primaryDevice)
|
||||
readonly property bool batteryPluggedIn: isPluggedIn(primaryDevice)
|
||||
readonly property bool batteryReady: isDeviceReady(primaryDevice)
|
||||
readonly property bool batteryPresent: isDevicePresent(primaryDevice)
|
||||
|
||||
// Add UPower batteries
|
||||
var upowerBatteryCount = 0;
|
||||
if (UPower.devices) {
|
||||
var upowerArray = UPower.devices.values || [];
|
||||
for (var i = 0; i < upowerArray.length; i++) {
|
||||
var d = upowerArray[i];
|
||||
if (isDevicePresent(d) && d.type === UPowerDeviceType.Battery) {
|
||||
if (d.nativePath && !seenPaths.has(d.nativePath)) {
|
||||
list.push(d);
|
||||
seenPaths.add(d.nativePath);
|
||||
upowerBatteryCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add DisplayDevice (Aggregate)
|
||||
if (UPower.displayDevice && UPower.displayDevice.type === UPowerDeviceType.Battery && isDevicePresent(UPower.displayDevice)) {
|
||||
if (upowerBatteryCount !== 1) {
|
||||
list.push(UPower.displayDevice);
|
||||
}
|
||||
}
|
||||
// Add Bluetooth batteries
|
||||
if (BluetoothService.devices) {
|
||||
var btArray = BluetoothService.devices.values || [];
|
||||
for (var j = 0; j < btArray.length; j++) {
|
||||
var btd = btArray[j];
|
||||
if (isDevicePresent(btd) && btd.batteryAvailable) {
|
||||
// Bluetooth devices use address as unique ID
|
||||
if (btd.address && !seenPaths.has(btd.address)) {
|
||||
list.push(btd);
|
||||
seenPaths.add(btd.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
property bool healthAvailable: false
|
||||
property int healthPercent: -1
|
||||
|
||||
readonly property var _laptopBattery: {
|
||||
if (!UPower.devices)
|
||||
return UPower.displayDevice;
|
||||
|
||||
// 2. Determine the primary device (System Battery)
|
||||
readonly property var primaryDevice: {
|
||||
@@ -80,30 +55,45 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to the first available device
|
||||
return devices[0];
|
||||
if (UPower.displayDevice.isPresent) {
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Whether the primary device is a laptop battery
|
||||
readonly property bool isLaptopBattery: primaryDevice !== null && !isBluetoothDevice(primaryDevice) && primaryDevice.isLaptopBattery
|
||||
readonly property var _bluetoothBattery: {
|
||||
if (externalBatteries.length > 0)
|
||||
return externalBatteries[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
// Global properties for the Primary Device (used by LockScreen etc)
|
||||
readonly property real batteryPercentage: getPercentage(primaryDevice)
|
||||
readonly property bool batteryCharging: isCharging(primaryDevice)
|
||||
readonly property bool batteryPluggedIn: isPluggedIn(primaryDevice)
|
||||
readonly property bool batteryReady: isDeviceReady(primaryDevice)
|
||||
readonly property bool batteryPresent: isDevicePresent(primaryDevice)
|
||||
// MARK: resolveDevice
|
||||
function resolveDevice(nativePath) {
|
||||
if (!nativePath || nativePath === "") {
|
||||
return primaryDevice;
|
||||
}
|
||||
|
||||
// Exposed subsets of devices
|
||||
readonly property var laptopBatteries: devices.filter(d => !isBluetoothDevice(d))
|
||||
readonly property var externalBatteries: devices.filter(d => isBluetoothDevice(d))
|
||||
// Check for DisplayDevice explicitly (Literal key OR actual native path)
|
||||
if ((nativePath === "DisplayDevice" || (UPower.displayDevice && nativePath === UPower.displayDevice.nativePath)) && UPower.displayDevice) {
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
|
||||
property bool healthAvailable: false
|
||||
property int healthPercent: -1
|
||||
var upowerDev = findUPowerDevice(nativePath);
|
||||
if (upowerDev)
|
||||
return upowerDev;
|
||||
|
||||
// Initialization state
|
||||
property bool initializationComplete: false
|
||||
readonly property bool ready: initializationComplete
|
||||
var btDev = findBluetoothDevice(nativePath);
|
||||
if (btDev)
|
||||
return btDev;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// MARK: findUPowerDevice
|
||||
function findUPowerDevice(nativePath) {
|
||||
if (!nativePath || nativePath === "" || nativePath === "DisplayDevice") {
|
||||
return _laptopBattery;
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 500
|
||||
@@ -112,10 +102,10 @@ Singleton {
|
||||
onTriggered: root.initializationComplete = true
|
||||
}
|
||||
|
||||
// 3. Helper to resolve a device by path, or return primary if path is empty/invalid
|
||||
function resolveDevice(nativePath) {
|
||||
if (!nativePath || nativePath === "") {
|
||||
return primaryDevice;
|
||||
// MARK: findBluetoothDevice
|
||||
function findBluetoothDevice(nativePath) {
|
||||
if (!nativePath || !BluetoothService.devices) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for DisplayDevice explicitly if requested via "DisplayDevice" or empty string
|
||||
@@ -140,6 +130,7 @@ Singleton {
|
||||
return null;
|
||||
}
|
||||
|
||||
// MARK: isDevicePresent
|
||||
function isDevicePresent(device) {
|
||||
if (!device)
|
||||
return false;
|
||||
@@ -159,6 +150,7 @@ Singleton {
|
||||
return false;
|
||||
}
|
||||
|
||||
// MARK: isDeviceReady
|
||||
function isDeviceReady(device) {
|
||||
if (!isDevicePresent(device))
|
||||
return false;
|
||||
@@ -169,6 +161,7 @@ Singleton {
|
||||
return device.ready && device.percentage !== undefined;
|
||||
}
|
||||
|
||||
// MARK: getPercentage
|
||||
function getPercentage(device) {
|
||||
if (!device)
|
||||
return 0;
|
||||
@@ -178,22 +171,34 @@ Singleton {
|
||||
return (device.percentage || 0) * 100;
|
||||
}
|
||||
|
||||
// MARK: isCharging
|
||||
function isCharging(device) {
|
||||
if (!device || device.batteryAvailable !== undefined)
|
||||
return false;
|
||||
return device.state === UPowerDeviceState.Charging;
|
||||
if (!device || isBluetoothDevice(device))
|
||||
// Tracking bluetooth devices can charge or not is a loop hole, none of my devices has it, even if it possible?!
|
||||
return false; // Assuming not charging until someone/quickshell brings a way to do pretty unlikely.
|
||||
if (device.state !== undefined) {
|
||||
return device.state === UPowerDeviceState.Charging;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// MARK: isPluggedIn
|
||||
function isPluggedIn(device) {
|
||||
if (!device || device.batteryAvailable !== undefined)
|
||||
return false;
|
||||
return device.state === UPowerDeviceState.FullyCharged || device.state === UPowerDeviceState.PendingCharge;
|
||||
if (!device || isBluetoothDevice(device))
|
||||
// Tracking bluetooth devices can charge or not is a loop hole, none of my devices has it, even if it possible?!
|
||||
return false; // Assuming not charging until someone/quickshell brings a way to do pretty unlikely.
|
||||
if (device.state !== undefined) {
|
||||
return device.state === UPowerDeviceState.FullyCharged || device.state === UPowerDeviceState.PendingCharge;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// MARK: isBluetoothDevice
|
||||
function isBluetoothDevice(device) {
|
||||
return device && device.batteryAvailable !== undefined;
|
||||
}
|
||||
|
||||
// MARK: getDeviceName
|
||||
function getDeviceName(device) {
|
||||
if (!isDeviceReady(device))
|
||||
return "";
|
||||
@@ -214,70 +219,9 @@ Singleton {
|
||||
return "";
|
||||
}
|
||||
|
||||
function getTimeRemainingText(device) {
|
||||
if (!ready || !isDevicePresent(device)) {
|
||||
return I18n.tr("battery.no-battery-detected");
|
||||
}
|
||||
if (isPluggedIn(device)) {
|
||||
return I18n.tr("battery.plugged-in");
|
||||
}
|
||||
if (device) {
|
||||
if (device.timeToFull > 0) {
|
||||
return I18n.tr("battery.time-until-full", {
|
||||
"time": Time.formatVagueHumanReadableDuration(device.timeToFull)
|
||||
});
|
||||
}
|
||||
if (device.timeToEmpty > 0) {
|
||||
return I18n.tr("battery.time-left", {
|
||||
"time": Time.formatVagueHumanReadableDuration(device.timeToEmpty)
|
||||
});
|
||||
}
|
||||
}
|
||||
return I18n.tr("common.idle");
|
||||
}
|
||||
|
||||
function getDeviceOptionsModel() {
|
||||
var model = [
|
||||
{
|
||||
"key": "",
|
||||
"name": I18n.tr("bar.battery.device-default")
|
||||
}
|
||||
];
|
||||
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
var d = devices[i];
|
||||
var name = "";
|
||||
|
||||
// Determine friendly name
|
||||
if (isBluetoothDevice(d)) {
|
||||
name = d.name || "Bluetooth Device";
|
||||
} else if (d === UPower.displayDevice) {
|
||||
name = I18n.tr("common.battery-aggregate") || "Display Device";
|
||||
} else {
|
||||
name = d.model || I18n.tr("common.battery");
|
||||
}
|
||||
|
||||
// Determine ID/Path
|
||||
var key = isBluetoothDevice(d) ? d.address : d.nativePath;
|
||||
if (!key && d === UPower.displayDevice)
|
||||
key = "DisplayDevice";
|
||||
|
||||
// Format: "Model (ID)"
|
||||
var displayName = name;
|
||||
if (key && key !== "DisplayDevice") {
|
||||
displayName = `${name} (${key})`;
|
||||
}
|
||||
|
||||
model.push({
|
||||
"key": key || "",
|
||||
"name": displayName
|
||||
});
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
// MARK: refreshHealth
|
||||
function refreshHealth() {
|
||||
if (!isLaptopBattery || !primaryDevice) {
|
||||
if (!isLaptopBattery) {
|
||||
healthAvailable = false;
|
||||
healthPercent = -1;
|
||||
return;
|
||||
@@ -287,7 +231,6 @@ Singleton {
|
||||
|
||||
Process {
|
||||
id: healthProcess
|
||||
// Dynamically target the primary device if possible, otherwise fall back to first battery
|
||||
command: ["sh", "-c", `upower -i ${primaryDevice && primaryDevice.nativePath ? "/org/freedesktop/UPower/devices/battery_" + primaryDevice.nativePath : "$(upower -e | grep battery | head -n 1)"} 2>/dev/null | grep -iE 'capacity'`]
|
||||
environment: ({
|
||||
"LC_ALL": "C"
|
||||
@@ -314,7 +257,7 @@ Singleton {
|
||||
Qt.callLater(refreshHealth);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: getIcon
|
||||
function getIcon(percent, charging, pluggedIn, isReady) {
|
||||
if (!isReady) {
|
||||
return "battery-exclamation";
|
||||
@@ -343,7 +286,116 @@ Singleton {
|
||||
return "battery-off"; // New fallback icon clearly represent if nothing is true here.
|
||||
}
|
||||
|
||||
// MARK: hasAnyBattery
|
||||
function hasAnyBattery() {
|
||||
return primaryDevice !== null;
|
||||
}
|
||||
|
||||
|
||||
// MARK: Battery
|
||||
// MARK: getRateText
|
||||
function getRateText(device) {
|
||||
if (!device || device.changeRate === undefined)
|
||||
return "";
|
||||
|
||||
const rate = Math.abs(device.changeRate);
|
||||
if (isPluggedIn(device)) {
|
||||
return I18n.tr("battery.plugged-in");
|
||||
} else if (isCharging(device)) {
|
||||
return I18n.tr("battery.charging-rate", {
|
||||
"rate": rate.toFixed(2)
|
||||
});
|
||||
} else {
|
||||
return I18n.tr("battery.discharging-rate", {
|
||||
"rate": rate.toFixed(2)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: BatteryPanel
|
||||
readonly property var externalBatteries: {
|
||||
var list = [];
|
||||
var devices = BluetoothService.devices ? (BluetoothService.devices.values || []) : [];
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
var device = devices[i];
|
||||
if (device && device.connected && device.batteryAvailable) {
|
||||
list.push(device);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// MARK: getTimeRemainingText
|
||||
function getTimeRemainingText(device) {
|
||||
if (!isDeviceReady(device)) {
|
||||
return I18n.tr("battery.no-battery-detected");
|
||||
}
|
||||
if (isPluggedIn(device)) {
|
||||
return I18n.tr("battery.plugged-in");
|
||||
}
|
||||
if (device) {
|
||||
if (device.timeToFull > 0) {
|
||||
return I18n.tr("battery.time-until-full", {
|
||||
"time": Time.formatVagueHumanReadableDuration(device.timeToFull)
|
||||
});
|
||||
}
|
||||
if (device.timeToEmpty > 0) {
|
||||
return I18n.tr("battery.time-left", {
|
||||
"time": Time.formatVagueHumanReadableDuration(device.timeToEmpty)
|
||||
});
|
||||
}
|
||||
}
|
||||
return I18n.tr("common.idle");
|
||||
}
|
||||
|
||||
|
||||
// MARK: BatterySettings
|
||||
property var devicesModel: buildDeviceModel()
|
||||
|
||||
function buildDeviceModel() {
|
||||
var model = [
|
||||
{
|
||||
"key": UPower.devices.DisplayDevice || "", // It was capital D and i spend an hour to figure out [why tf this do absolutely nothing] XD (I hate my left shift it sticks)
|
||||
"name": I18n.tr("bar.battery.device-default")
|
||||
}
|
||||
];
|
||||
|
||||
// UPower Devices
|
||||
if (UPower.devices && UPower.devices.values) {
|
||||
var deviceArray = UPower.devices.values;
|
||||
for (var i = 0; i < deviceArray.length; i++) {
|
||||
var device = deviceArray[i];
|
||||
if (!device || device.type === UPowerDeviceType.LinePower) {
|
||||
continue;
|
||||
}
|
||||
var displayName = device.model || device.nativePath || "Unknown";
|
||||
model.push({
|
||||
"key": device.nativePath || "",
|
||||
"name": displayName
|
||||
});
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
// MARK: modelUpdateTimer
|
||||
Timer {
|
||||
id: modelUpdateTimer
|
||||
interval: 2000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
var newModel = buildDeviceModel();
|
||||
// Simple change detection to avoid unnecessary bindings updates
|
||||
if (JSON.stringify(newModel) !== JSON.stringify(devicesModel)) {
|
||||
devicesModel = newModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: UPower.devices
|
||||
function onValuesChanged() { modelUpdateTimer.restart(); devicesModel = buildDeviceModel(); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user