BatterySettings: add option to pick which battery is being shown

BatteryPanel: remove redundant things
This commit is contained in:
Ly-sec
2025-11-28 20:55:50 +01:00
parent 46f881026e
commit aeee91d08a
16 changed files with 423 additions and 202 deletions
+8 -3
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Standard (Anzeigegerät)",
"description": "Wählen Sie das anzuzeigende Batteriegerät aus.",
"label": "Batteriebetriebenes Gerät"
},
"display-mode": {
"description": "Wählen Sie, wie dieser Wert angezeigt werden soll.",
"label": "Anzeigemodus"
@@ -1832,6 +1837,9 @@
"memory-section": {
"label": "Speicherverbrauch"
},
"network-section": {
"label": "Netzwerk"
},
"polling-interval": {
"label": "Abfrageintervall"
},
@@ -1848,9 +1856,6 @@
"description": "Passe die Warn-/Kritisch-Schwellen und die Abfrageintervalle für jede Systemmetrik an.",
"label": "Schwellenwerte"
},
"network-section": {
"label": "Netzwerk"
},
"title": "Systemmonitor",
"use-custom-highlight-colors": {
"description": "Wenn deaktiviert, werden die Standard-Hervorhebungsfarben des Themes verwendet.",
+5
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Default (Display Device)",
"description": "Select which battery device to display.",
"label": "Battery device"
},
"display-mode": {
"description": "Choose how you'd like this value to appear.",
"label": "Display mode"
+8 -3
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Predeterminado (Dispositivo de visualización)",
"description": "Seleccione qué dispositivo de batería mostrar.",
"label": "Dispositivo de batería"
},
"display-mode": {
"description": "Elige cómo te gustaría que apareciera este valor.",
"label": "Modo de visualización"
@@ -1832,6 +1837,9 @@
"memory-section": {
"label": "Uso de memoria"
},
"network-section": {
"label": "Red"
},
"polling-interval": {
"label": "Intervalo de sondeo"
},
@@ -1848,9 +1856,6 @@
"description": "Ajusta los umbrales de advertencia/crítico y los intervalos de sondeo para cada métrica del sistema.",
"label": "Umbrales"
},
"network-section": {
"label": "Red"
},
"title": "Monitor del sistema",
"use-custom-highlight-colors": {
"description": "Cuando está desactivado, se usan los colores de resaltado predeterminados del tema.",
+8 -3
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Par défaut (périphérique d'affichage)",
"description": "Sélectionnez le périphérique de batterie à afficher.",
"label": "Dispositif à pile"
},
"display-mode": {
"description": "Choisissez comment vous souhaitez que cette valeur apparaisse.",
"label": "Mode d'affichage"
@@ -1832,6 +1837,9 @@
"memory-section": {
"label": "Utilisation mémoire"
},
"network-section": {
"label": "Réseau"
},
"polling-interval": {
"label": "Intervalle d'interrogation"
},
@@ -1848,9 +1856,6 @@
"description": "Ajustez les seuils davertissement/critiques et les intervalles dinterrogation pour chaque métrique système.",
"label": "Seuils"
},
"network-section": {
"label": "Réseau"
},
"title": "Moniteur système",
"use-custom-highlight-colors": {
"description": "Lorsque cette option est désactivée, les couleurs de surbrillance par défaut du thème sont utilisées.",
+5
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "標準 (表示デバイス)",
"description": "表示するバッテリーデバイスを選択してください。",
"label": "電池デバイス"
},
"display-mode": {
"description": "値の表示方法を選択します。",
"label": "表示モード"
+8 -3
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Standaard (weergaveapparaat)",
"description": "Selecteer welk batterijapparaat u wilt weergeven.",
"label": "Batterijapparaat"
},
"display-mode": {
"description": "Kies hoe je wilt dat deze waarde wordt weergegeven.",
"label": "Weergavemodus"
@@ -1832,6 +1837,9 @@
"memory-section": {
"label": "Geheugengebruik"
},
"network-section": {
"label": "Netwerk"
},
"polling-interval": {
"label": "Peilingsinterval"
},
@@ -1848,9 +1856,6 @@
"description": "Pas de waarschuwing/kritieke drempels en de pollingsintervallen voor elke systeemmetriek aan.",
"label": "Drempels"
},
"network-section": {
"label": "Netwerk"
},
"title": "Systeemmonitor",
"use-custom-highlight-colors": {
"description": "Indien uitgeschakeld, worden de standaard markeerkleuren van het thema gebruikt.",
+8 -3
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Padrão (Dispositivo de Exibição)",
"description": "Selecione qual dispositivo de bateria exibir.",
"label": "Dispositivo de bateria"
},
"display-mode": {
"description": "Escolha como você gostaria que este valor aparecesse.",
"label": "Modo de exibição"
@@ -1832,6 +1837,9 @@
"memory-section": {
"label": "Uso de memória"
},
"network-section": {
"label": "Rede"
},
"polling-interval": {
"label": "Intervalo de pesquisa"
},
@@ -1848,9 +1856,6 @@
"description": "Ajuste os limites de aviso/crítico e os intervalos de consulta para cada métrica do sistema.",
"label": "Limiares"
},
"network-section": {
"label": "Rede"
},
"title": "Monitor do Sistema",
"use-custom-highlight-colors": {
"description": "Quando desativado, são usadas as cores de destaque padrão do tema.",
+8 -3
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Устройство отображения по умолчанию",
"description": "Выберите, какое устройство с батареей отображать.",
"label": "Батарейное устройство"
},
"display-mode": {
"description": "Выберите, как это значение должно отображаться.",
"label": "Режим отображения"
@@ -1832,6 +1837,9 @@
"memory-section": {
"label": "Использование памяти"
},
"network-section": {
"label": "Сеть"
},
"polling-interval": {
"label": "Интервал опроса"
},
@@ -1848,9 +1856,6 @@
"description": "Настройте пороги предупреждения/критические пороги и интервалы опроса для каждой системной метрики.",
"label": "Пороги"
},
"network-section": {
"label": "Сеть"
},
"title": "Системный монитор",
"use-custom-highlight-colors": {
"description": "Если отключено, используются цвета выделения по умолчанию темы.",
+8 -3
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Varsayılan (Görüntü Aygıtı)",
"description": "Görüntülenecek pil cihazını seçin.",
"label": "Pil cihazı"
},
"display-mode": {
"description": "Bu değerin nasıl görünmesini istediğinizi seçin.",
"label": "Görüntüleme modu"
@@ -1832,6 +1837,9 @@
"memory-section": {
"label": "Bellek Kullanımı"
},
"network-section": {
"label": "Ağ"
},
"polling-interval": {
"label": "Yoklama aralığı"
},
@@ -1848,9 +1856,6 @@
"description": "Her sistem metriği için uyarı/kritik eşiklerini ve yoklama aralıklarını ayarlayın.",
"label": "Eşikler"
},
"network-section": {
"label": "Ağ"
},
"title": "Sistem İzleme",
"use-custom-highlight-colors": {
"description": "Devre dışı bırakıldığında, tema varsayılan vurgulama renkleri kullanılır.",
+8 -3
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Пристрій відображення за замовчуванням",
"description": "Виберіть пристрій з акумулятором для відображення.",
"label": "Пристрій живлення від батареї"
},
"display-mode": {
"description": "Виберіть, як ви хочете, щоб це значення відображалося.",
"label": "Режим відображення"
@@ -1832,6 +1837,9 @@
"memory-section": {
"label": "Використання пам'яті"
},
"network-section": {
"label": "Мережа"
},
"polling-interval": {
"label": "Інтервал опитування"
},
@@ -1848,9 +1856,6 @@
"description": "Налаштуйте пороги попередження/критичні пороги та інтервали опитування для кожного системного показника.",
"label": "Пороги"
},
"network-section": {
"label": "Мережа"
},
"title": "Системний монітор",
"use-custom-highlight-colors": {
"description": "Якщо вимкнено, використовуються кольори підсвічування за замовчуванням теми.",
+8 -3
View File
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "默认(显示设备)",
"description": "选择要显示的电池设备。",
"label": "电池设备"
},
"display-mode": {
"description": "选择您希望此值显示的方式。",
"label": "显示模式"
@@ -1832,6 +1837,9 @@
"memory-section": {
"label": "内存使用率"
},
"network-section": {
"label": "网络"
},
"polling-interval": {
"label": "轮询间隔"
},
@@ -1848,9 +1856,6 @@
"description": "为每个系统指标调整警告/严重阈值和轮询间隔。",
"label": "阈值"
},
"network-section": {
"label": "网络"
},
"title": "系统监视器",
"use-custom-highlight-colors": {
"description": "关闭时将使用主题默认高亮颜色。",
+108 -36
View File
@@ -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);
}
Connections {
target: battery
function onPercentageChanged() {
if (battery) {
maybeNotify(getCurrentPercent(), battery.state === UPowerDeviceState.Charging);
}
}
function onStateChanged() {
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
// Reset notification flag when charging starts
if (isCharging) {
root.hasNotifiedLowBattery = false;
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) {
+127 -136
View File
@@ -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")
+2 -1
View File
@@ -87,7 +87,8 @@ Singleton {
"Battery": {
"allowUserSettings": true,
"displayMode": "onhover",
"warningThreshold": 30
"warningThreshold": 30,
"deviceNativePath": ""
},
"Bluetooth": {
"allowUserSettings": true,