mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-06-01 01:59:47 +00:00
BatterySettings: add option to pick which battery is being shown
BatteryPanel: remove redundant things
This commit is contained in:
+109
-37
@@ -6,6 +6,7 @@ import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Networking
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
@@ -34,55 +35,128 @@ Item {
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
readonly property real warningThreshold: widgetSettings.warningThreshold !== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
|
||||
readonly property bool isLowBattery: !charging && percent <= warningThreshold
|
||||
// Only show low battery warning if device is ready (prevents false positive during initialization)
|
||||
readonly property bool isLowBattery: isReady && !charging && percent <= warningThreshold
|
||||
|
||||
// Test mode
|
||||
readonly property bool testMode: false
|
||||
readonly property int testPercent: 35
|
||||
readonly property bool testCharging: false
|
||||
|
||||
// Main properties
|
||||
readonly property var battery: UPower.displayDevice
|
||||
readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
|
||||
readonly property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
|
||||
readonly property string deviceNativePath: widgetSettings.deviceNativePath || ""
|
||||
|
||||
function findBatteryDevice(nativePath) {
|
||||
if (!nativePath || !UPower.devices) {
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
var devices = UPower.devices.values || [];
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
var device = devices[i];
|
||||
if (device && device.nativePath === nativePath && device.type !== UPowerDeviceType.LinePower && device.percentage !== undefined) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
|
||||
function findBluetoothDevice(nativePath) {
|
||||
if (!nativePath || !BluetoothService.devices) {
|
||||
return null;
|
||||
}
|
||||
var macMatch = nativePath.match(/([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})/);
|
||||
if (!macMatch) {
|
||||
return null;
|
||||
}
|
||||
var macAddress = macMatch[1].toUpperCase();
|
||||
var devices = BluetoothService.devices.values || [];
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
var device = devices[i];
|
||||
if (device && device.address && device.address.toUpperCase() === macAddress) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
readonly property var battery: findBatteryDevice(deviceNativePath)
|
||||
readonly property var bluetoothDevice: deviceNativePath ? findBluetoothDevice(deviceNativePath) : null
|
||||
readonly property bool hasBluetoothBattery: bluetoothDevice && bluetoothDevice.batteryAvailable && bluetoothDevice.battery !== undefined
|
||||
readonly property bool isBluetoothConnected: bluetoothDevice && bluetoothDevice.connected === true
|
||||
|
||||
property bool initializationComplete: false
|
||||
Timer {
|
||||
interval: 500
|
||||
running: true
|
||||
onTriggered: root.initializationComplete = true
|
||||
}
|
||||
|
||||
readonly property bool isDevicePresent: {
|
||||
if (testMode)
|
||||
return true;
|
||||
if (deviceNativePath) {
|
||||
if (bluetoothDevice) {
|
||||
return isBluetoothConnected;
|
||||
}
|
||||
if (battery && battery.nativePath === deviceNativePath) {
|
||||
if (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) {
|
||||
return battery.isPresent;
|
||||
}
|
||||
return battery.ready && battery.percentage !== undefined && (battery.percentage > 0 || battery.state === UPowerDeviceState.Charging);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (battery) {
|
||||
return (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) ? battery.isPresent : (battery.ready && battery.percentage !== undefined);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property bool isReady: testMode ? true : (initializationComplete && battery && battery.ready && isDevicePresent && (battery.percentage !== undefined || hasBluetoothBattery))
|
||||
readonly property real percent: testMode ? testPercent : (isReady ? (hasBluetoothBattery ? (bluetoothDevice.battery * 100) : (battery.percentage * 100)) : 0)
|
||||
readonly property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
|
||||
property bool hasNotifiedLowBattery: false
|
||||
|
||||
implicitWidth: pill.width
|
||||
implicitHeight: pill.height
|
||||
|
||||
// Helper to evaluate and possibly notify
|
||||
function maybeNotify(percent, charging) {
|
||||
// Only notify once we are a below threshold
|
||||
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
|
||||
root.hasNotifiedLowBattery = true;
|
||||
function maybeNotify(currentPercent, isCharging) {
|
||||
if (!isCharging && !hasNotifiedLowBattery && currentPercent <= warningThreshold) {
|
||||
hasNotifiedLowBattery = true;
|
||||
ToastService.showWarning(I18n.tr("toast.battery.low"), I18n.tr("toast.battery.low-desc", {
|
||||
"percent": Math.round(percent)
|
||||
"percent": Math.round(currentPercent)
|
||||
}));
|
||||
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
|
||||
// Reset when charging starts or when battery recovers 5% above threshold
|
||||
root.hasNotifiedLowBattery = false;
|
||||
} else if (hasNotifiedLowBattery && (isCharging || currentPercent > warningThreshold + 5)) {
|
||||
hasNotifiedLowBattery = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for battery changes
|
||||
Connections {
|
||||
target: UPower.displayDevice
|
||||
function onPercentageChanged() {
|
||||
var currentPercent = UPower.displayDevice.percentage * 100;
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
|
||||
root.maybeNotify(currentPercent, isCharging);
|
||||
}
|
||||
function getCurrentPercent() {
|
||||
return hasBluetoothBattery ? (bluetoothDevice.battery * 100) : (battery ? battery.percentage * 100 : 0);
|
||||
}
|
||||
|
||||
function onStateChanged() {
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
|
||||
// Reset notification flag when charging starts
|
||||
if (isCharging) {
|
||||
root.hasNotifiedLowBattery = false;
|
||||
Connections {
|
||||
target: battery
|
||||
function onPercentageChanged() {
|
||||
if (battery) {
|
||||
maybeNotify(getCurrentPercent(), battery.state === UPowerDeviceState.Charging);
|
||||
}
|
||||
}
|
||||
function onStateChanged() {
|
||||
if (battery) {
|
||||
if (battery.state === UPowerDeviceState.Charging) {
|
||||
hasNotifiedLowBattery = false;
|
||||
}
|
||||
maybeNotify(getCurrentPercent(), battery.state === UPowerDeviceState.Charging);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: bluetoothDevice
|
||||
function onBatteryChanged() {
|
||||
if (bluetoothDevice && hasBluetoothBattery) {
|
||||
maybeNotify(bluetoothDevice.battery * 100, battery ? battery.state === UPowerDeviceState.Charging : false);
|
||||
}
|
||||
// Also re-evaluate maybeNotify, as state might have changed
|
||||
var currentPercent = UPower.displayDevice.percentage * 100;
|
||||
root.maybeNotify(currentPercent, isCharging);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,12 +193,10 @@ Item {
|
||||
text: (isReady || testMode) ? Math.round(percent) : "-"
|
||||
suffix: "%"
|
||||
autoHide: false
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery)
|
||||
|
||||
// Charging is the most important, then low battery
|
||||
customBackgroundColor: charging ? Color.mPrimary : (isLowBattery ? Color.mError : Color.transparent)
|
||||
customTextIconColor: charging ? Color.mOnPrimary : (isLowBattery ? Color.mOnError : Color.transparent)
|
||||
forceOpen: isReady && displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide" || (initializationComplete && !isReady)
|
||||
customBackgroundColor: !initializationComplete ? Color.transparent : (charging ? Color.mPrimary : (isLowBattery ? Color.mError : Color.transparent))
|
||||
customTextIconColor: !initializationComplete ? Color.transparent : (charging ? Color.mOnPrimary : (isLowBattery ? Color.mOnError : Color.transparent))
|
||||
|
||||
tooltipText: {
|
||||
let lines = [];
|
||||
@@ -132,7 +204,7 @@ Item {
|
||||
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
if (!isReady || !battery.isLaptopBattery) {
|
||||
if (!isReady || !isDevicePresent) {
|
||||
return I18n.tr("battery.no-battery-detected");
|
||||
}
|
||||
if (battery.timeToEmpty > 0) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Power
|
||||
import qs.Services.Networking
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
@@ -15,16 +15,133 @@ SmartPanel {
|
||||
preferredWidth: Math.round(360 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(460 * Style.uiScaleRatio)
|
||||
|
||||
readonly property var battery: UPower.displayDevice
|
||||
readonly property bool isReady: battery && battery.ready && battery.isLaptopBattery && battery.isPresent
|
||||
readonly property int percent: isReady ? Math.round(battery.percentage * 100) : -1
|
||||
// Get device selection from Battery widget settings (check right section first, then any Battery widget)
|
||||
function getBatteryDevicePath() {
|
||||
// Check right section first (most common location for Battery widget)
|
||||
var rightWidgets = Settings.data.bar.widgets.right || [];
|
||||
for (var i = 0; i < rightWidgets.length; i++) {
|
||||
if (rightWidgets[i].id === "Battery" && rightWidgets[i].deviceNativePath) {
|
||||
return rightWidgets[i].deviceNativePath;
|
||||
}
|
||||
}
|
||||
// Check other sections
|
||||
var sections = ["left", "center"];
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
var widgets = Settings.data.bar.widgets[sections[s]] || [];
|
||||
for (var j = 0; j < widgets.length; j++) {
|
||||
if (widgets[j].id === "Battery" && widgets[j].deviceNativePath) {
|
||||
return widgets[j].deviceNativePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Helper function to find battery device by nativePath
|
||||
function findBatteryDevice(nativePath) {
|
||||
if (!nativePath || nativePath === "") {
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
|
||||
if (!UPower.devices) {
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
|
||||
var deviceArray = UPower.devices.values || [];
|
||||
for (var i = 0; i < deviceArray.length; i++) {
|
||||
var device = deviceArray[i];
|
||||
if (device && device.nativePath === nativePath) {
|
||||
if (device.type === UPowerDeviceType.LinePower) {
|
||||
continue;
|
||||
}
|
||||
if (device.percentage !== undefined) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
|
||||
// Helper function to find Bluetooth device by MAC address from nativePath
|
||||
function findBluetoothDevice(nativePath) {
|
||||
if (!nativePath || !BluetoothService.devices) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var macMatch = nativePath.match(/([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})/);
|
||||
if (!macMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var macAddress = macMatch[1].toUpperCase();
|
||||
var deviceArray = BluetoothService.devices.values || [];
|
||||
|
||||
for (var i = 0; i < deviceArray.length; i++) {
|
||||
var device = deviceArray[i];
|
||||
if (device && device.address && device.address.toUpperCase() === macAddress) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
readonly property string deviceNativePath: getBatteryDevicePath()
|
||||
readonly property var battery: findBatteryDevice(deviceNativePath)
|
||||
readonly property var bluetoothDevice: deviceNativePath ? findBluetoothDevice(deviceNativePath) : null
|
||||
readonly property bool hasBluetoothBattery: bluetoothDevice && bluetoothDevice.batteryAvailable && bluetoothDevice.battery !== undefined
|
||||
readonly property bool isBluetoothConnected: bluetoothDevice && bluetoothDevice.connected !== undefined ? bluetoothDevice.connected : false
|
||||
|
||||
// Check if device is actually present/connected
|
||||
readonly property bool isDevicePresent: {
|
||||
if (deviceNativePath && deviceNativePath !== "") {
|
||||
if (bluetoothDevice) {
|
||||
return isBluetoothConnected;
|
||||
}
|
||||
if (battery && battery.nativePath === deviceNativePath) {
|
||||
if (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) {
|
||||
return battery.isPresent;
|
||||
}
|
||||
return battery.ready && battery.percentage !== undefined && (battery.percentage > 0 || battery.state === UPowerDeviceState.Charging);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (battery) {
|
||||
if (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) {
|
||||
return battery.isPresent;
|
||||
}
|
||||
return battery.ready && battery.percentage !== undefined;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property bool isReady: battery && battery.ready && isDevicePresent && (battery.percentage !== undefined || hasBluetoothBattery)
|
||||
readonly property int percent: isReady ? Math.round(hasBluetoothBattery ? (bluetoothDevice.battery * 100) : (battery.percentage * 100)) : -1
|
||||
readonly property bool charging: isReady ? battery.state === UPowerDeviceState.Charging : false
|
||||
readonly property bool healthAvailable: isReady && battery.healthSupported
|
||||
readonly property int healthPercent: healthAvailable ? Math.round(battery.healthPercentage) : -1
|
||||
readonly property bool powerProfileAvailable: PowerProfileService.available
|
||||
readonly property var powerProfiles: [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||
|
||||
function getDeviceName() {
|
||||
if (!isReady) {
|
||||
return "";
|
||||
}
|
||||
// Don't show name for laptop batteries
|
||||
if (battery && battery.isLaptopBattery) {
|
||||
return "";
|
||||
}
|
||||
if (bluetoothDevice && bluetoothDevice.name) {
|
||||
return bluetoothDevice.name;
|
||||
}
|
||||
if (battery && battery.model) {
|
||||
return battery.model;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
readonly property string deviceName: getDeviceName()
|
||||
readonly property string panelTitle: deviceName ? `${I18n.tr("battery.panel-title")} - ${deviceName}` : I18n.tr("battery.panel-title")
|
||||
|
||||
readonly property string timeText: {
|
||||
if (!isReady)
|
||||
if (!isReady || !isDevicePresent)
|
||||
return I18n.tr("battery.no-battery-detected");
|
||||
if (charging && battery.timeToFull > 0) {
|
||||
return I18n.tr("battery.time-until-full", {
|
||||
@@ -39,9 +156,6 @@ SmartPanel {
|
||||
return I18n.tr("battery.idle");
|
||||
}
|
||||
readonly property string iconName: BatteryService.getIcon(percent, charging, isReady)
|
||||
readonly property bool profilesAvailable: PowerProfileService.available
|
||||
property int profileIndex: profileToIndex(PowerProfileService.profile)
|
||||
property bool manualInhibitActive: manualInhibitorEnabled()
|
||||
|
||||
panelContent: Item {
|
||||
property real contentPreferredHeight: mainLayout.implicitHeight + Style.marginL * 2
|
||||
@@ -74,11 +188,12 @@ SmartPanel {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: I18n.tr("battery.panel-title")
|
||||
text: root.panelTitle
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
NText {
|
||||
@@ -103,6 +218,7 @@ SmartPanel {
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
height: chargeLayout.implicitHeight + Style.marginL * 2
|
||||
visible: isReady
|
||||
|
||||
ColumnLayout {
|
||||
id: chargeLayout
|
||||
@@ -165,131 +281,6 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Power profile and idle inhibit controls
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
height: controlsLayout.implicitHeight + Style.marginM * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: controlsLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
ColumnLayout {
|
||||
id: ppd
|
||||
visible: root.powerProfileAvailable
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
NIcon {
|
||||
icon: PowerProfileService.getIcon()
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
text: I18n.tr("battery.power-profile")
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NText {
|
||||
text: PowerProfileService.getName(profileIndex)
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 2
|
||||
stepSize: 1
|
||||
snapAlways: true
|
||||
value: profileIndex
|
||||
enabled: profilesAvailable
|
||||
onPressedChanged: (pressed, v) => {
|
||||
if (!pressed) {
|
||||
setProfileByIndex(v);
|
||||
}
|
||||
}
|
||||
onMoved: v => {
|
||||
profileIndex = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NIcon {
|
||||
icon: manualInhibitActive ? "keep-awake-on" : "keep-awake-off"
|
||||
pointSize: Style.fontSizeL
|
||||
color: manualInhibitActive ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
checked: manualInhibitActive
|
||||
label: I18n.tr("battery.inhibit-idle-label")
|
||||
description: I18n.tr("battery.inhibit-idle-description")
|
||||
onToggled: function (checked) {
|
||||
if (checked) {
|
||||
IdleInhibitorService.addManualInhibitor(null);
|
||||
} else {
|
||||
IdleInhibitorService.removeManualInhibitor();
|
||||
}
|
||||
manualInhibitActive = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function profileToIndex(p) {
|
||||
return powerProfiles.indexOf(p) ?? 1;
|
||||
}
|
||||
|
||||
function indexToProfile(idx) {
|
||||
return powerProfiles[idx] ?? PowerProfile.Balanced;
|
||||
}
|
||||
|
||||
function setProfileByIndex(idx) {
|
||||
var prof = indexToProfile(idx);
|
||||
profileIndex = idx;
|
||||
PowerProfileService.setProfile(prof);
|
||||
}
|
||||
|
||||
function manualInhibitorEnabled() {
|
||||
return IdleInhibitorService.activeInhibitors && IdleInhibitorService.activeInhibitors.indexOf("manual") >= 0;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: IdleInhibitorService
|
||||
|
||||
function onIsInhibitedChanged() {
|
||||
manualInhibitActive = manualInhibitorEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: inhibitorPoll
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: manualInhibitActive = manualInhibitorEnabled()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PowerProfileService
|
||||
|
||||
function onProfileChanged() {
|
||||
profileIndex = profileToIndex(PowerProfileService.profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,9 +153,15 @@ Popup {
|
||||
function loadWidgetSettings() {
|
||||
const source = BarWidgetRegistry.widgetSettingsMap[widgetId];
|
||||
if (source) {
|
||||
// Use setSource to pass properties at creation time
|
||||
var currentWidgetData = widgetData;
|
||||
if (sectionId && widgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[sectionId];
|
||||
if (widgets && widgetIndex < widgets.length) {
|
||||
currentWidgetData = widgets[widgetIndex];
|
||||
}
|
||||
}
|
||||
settingsLoader.setSource(source, {
|
||||
"widgetData": widgetData,
|
||||
"widgetData": currentWidgetData,
|
||||
"widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
@@ -15,14 +16,109 @@ ColumnLayout {
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property int valueWarningThreshold: widgetData.warningThreshold !== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
|
||||
property string valueDeviceNativePath: widgetData.deviceNativePath !== undefined ? widgetData.deviceNativePath : ""
|
||||
|
||||
// Build model of available battery devices
|
||||
function buildDeviceModel() {
|
||||
var model = [
|
||||
{
|
||||
"key": "",
|
||||
"name": I18n.tr("bar.widget-settings.battery.device.default")
|
||||
}
|
||||
];
|
||||
|
||||
if (!UPower.devices) {
|
||||
return model;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
readonly property int _deviceCount: (UPower.devices && UPower.devices.values) ? UPower.devices.values.length : 0
|
||||
property var deviceModel: buildDeviceModel()
|
||||
|
||||
on_DeviceCountChanged: {
|
||||
deviceModel = buildDeviceModel();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: UPower.devices
|
||||
function onValuesChanged() {
|
||||
deviceModel = buildDeviceModel();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: 2000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
var currentCount = (UPower.devices && UPower.devices.values) ? UPower.devices.values.length : 0;
|
||||
if (currentCount !== root._deviceCount) {
|
||||
deviceModel = buildDeviceModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
if (widgetData && widgetData.id) {
|
||||
settings.id = widgetData.id;
|
||||
}
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.warningThreshold = valueWarningThreshold;
|
||||
if (valueDeviceNativePath && valueDeviceNativePath !== "") {
|
||||
settings.deviceNativePath = valueDeviceNativePath;
|
||||
} else {
|
||||
delete settings.deviceNativePath;
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NComboBox {
|
||||
id: deviceComboBox
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.battery.device.label")
|
||||
description: I18n.tr("bar.widget-settings.battery.device.description")
|
||||
minimumWidth: 134
|
||||
model: root.deviceModel
|
||||
currentKey: root.valueDeviceNativePath
|
||||
onSelected: key => root.valueDeviceNativePath = key
|
||||
}
|
||||
|
||||
// Update currentKey when model changes to ensure selection is preserved
|
||||
Connections {
|
||||
target: root
|
||||
function onDeviceModelChanged() {
|
||||
// Force update of currentKey to trigger selection update
|
||||
deviceComboBox.currentKey = root.valueDeviceNativePath;
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: "Refresh device list"
|
||||
onClicked: deviceModel = buildDeviceModel()
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.widget-settings.battery.display-mode.label")
|
||||
description: I18n.tr("bar.widget-settings.battery.display-mode.description")
|
||||
|
||||
Reference in New Issue
Block a user