mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-22 03:04:01 -04:00
454 lines
19 KiB
QML
454 lines
19 KiB
QML
import QtQuick
|
|
import Quickshell.Io
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.DCommon.Widgets
|
|
import qs.Modules.Settings.Widgets
|
|
import qs.Modules.DBar.Popouts
|
|
|
|
Item {
|
|
id: root
|
|
|
|
readonly property color batteryStatusColor: {
|
|
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
|
return Theme.error;
|
|
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
|
return Theme.primary;
|
|
return Theme.surfaceText;
|
|
}
|
|
|
|
// The sysfs names DMS can write, in the order they are tried. Hardware that
|
|
// exposes none of them, a Lenovo IdeaPad on ideapad_laptop for instance,
|
|
// used to run the apply script to completion without writing anything, and
|
|
// sh exiting 0 was indistinguishable from a successful write.
|
|
readonly property string thresholdFileList: "charge_control_limit_max charge_stop_threshold charge_control_end_threshold"
|
|
readonly property int noThresholdExitCode: 2
|
|
|
|
property bool chargeLimitSupported: Qt.platform.os === "linux"
|
|
|
|
Component.onCompleted: BatteryService.addRef()
|
|
Component.onDestruction: BatteryService.removeRef()
|
|
|
|
Process {
|
|
id: thresholdProbe
|
|
running: Qt.platform.os === "linux"
|
|
command: ["sh", "-c", "for bat in /sys/class/power_supply/BAT*; do for file in " + root.thresholdFileList + "; do [ -f \"$bat/$file\" ] && exit 0; done; done; exit 1"]
|
|
onExited: exitCode => root.chargeLimitSupported = exitCode === 0
|
|
}
|
|
|
|
Process {
|
|
id: applyLimitProcess
|
|
command: ["pkexec", "sh", "-c", "
|
|
found=0
|
|
for bat in /sys/class/power_supply/BAT*; do
|
|
for file in " + root.thresholdFileList + "; do
|
|
if [ -f \"$bat/$file\" ]; then
|
|
found=1
|
|
echo " + SettingsData.batteryChargeLimit + " > \"$bat/$file\" || exit 1
|
|
break
|
|
fi
|
|
done
|
|
done
|
|
[ \"$found\" = 1 ] || exit " + root.noThresholdExitCode + "
|
|
"]
|
|
running: false
|
|
onExited: exitCode => {
|
|
if (exitCode === root.noThresholdExitCode) {
|
|
root.chargeLimitSupported = false;
|
|
ToastService.showError(I18n.tr("Charge limit not supported on this hardware", "battery settings: toast title when sysfs has no charge threshold"), I18n.tr("No writable charge threshold file was found under /sys/class/power_supply.", "battery settings: why the charge limit could not be applied"));
|
|
} else if (exitCode !== 0) {
|
|
ToastService.showError(I18n.tr("Failed to apply charge limit to system"), I18n.tr("Process exited with code %1", "charge limit error toast detail, %1 is the exit code").arg(exitCode));
|
|
} else {
|
|
ToastService.showInfo(I18n.tr("Charge limit applied successfully"), I18n.tr("Limit set to %1%", "charge limit toast detail, %1 is a percentage number").arg(SettingsData.batteryChargeLimit));
|
|
}
|
|
}
|
|
}
|
|
|
|
BatteryHistory {
|
|
id: history
|
|
readonly property var device: BatteryService.usePreferred ? BatteryService.preferredDevice : BatteryService.device
|
|
active: root.visible && BatteryService.batteryAvailable
|
|
nativePath: device?.nativePath ?? ""
|
|
}
|
|
|
|
SettingsPage {
|
|
id: mainColumn
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
title: I18n.tr("Status")
|
|
settingKey: "batteryStatusCard"
|
|
tags: ["battery", "status", "charge", "health"]
|
|
|
|
SettingsRow {
|
|
body: Column {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
DIcon {
|
|
name: BatteryService.getBatteryIcon()
|
|
size: Theme.iconSizeLarge
|
|
color: root.batteryStatusColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
Column {
|
|
spacing: Theme.spacingXS
|
|
width: parent.width - Theme.iconSizeLarge - Theme.spacingM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Row {
|
|
spacing: Theme.spacingS
|
|
width: parent.width
|
|
|
|
StyledText {
|
|
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : ""
|
|
font.pixelSize: Theme.fontSizeXLarge
|
|
font.weight: Theme.fontWeightMedium
|
|
color: root.batteryStatusColor
|
|
}
|
|
|
|
StyledText {
|
|
text: BatteryService.batteryStatus
|
|
font.pixelSize: Theme.fontSizeLarge
|
|
font.weight: Theme.fontWeightMedium
|
|
color: Theme.surfaceText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
elide: Text.ElideRight
|
|
width: Math.max(0, parent.width - 100)
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
text: BatteryService.isPluggedIn ? I18n.tr("AC adapter (plugged in)") : I18n.tr("Battery power")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceTextMedium
|
|
width: parent.width
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: parent.width
|
|
height: Theme.spacingXS
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Theme.fullRadius(width, height)
|
|
color: Theme.withAlpha(root.batteryStatusColor, Theme.tonalTintAlpha)
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.left: parent.left
|
|
width: parent.width * Math.max(0, Math.min(1, BatteryService.batteryLevel / 100))
|
|
height: parent.height
|
|
radius: Theme.fullRadius(width, height)
|
|
color: root.batteryStatusColor
|
|
visible: BatteryService.batteryAvailable
|
|
}
|
|
}
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
Item {
|
|
width: (parent.width - Theme.spacingM) / 2
|
|
height: timeColumn.implicitHeight
|
|
|
|
Column {
|
|
id: timeColumn
|
|
width: parent.width
|
|
spacing: Theme.spacingXXS
|
|
|
|
StyledText {
|
|
text: I18n.tr("Estimated time")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceTextMedium
|
|
}
|
|
|
|
StyledText {
|
|
text: {
|
|
const remaining = BatteryService.formatTimeRemaining();
|
|
const estimated = BatteryService.formatEstimatedTime();
|
|
return estimated ? `${remaining} (${estimated})` : remaining;
|
|
}
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Theme.fontWeightMedium
|
|
color: Theme.surfaceText
|
|
width: parent.width
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: (parent.width - Theme.spacingM) / 2
|
|
height: healthColumn.implicitHeight
|
|
|
|
Column {
|
|
id: healthColumn
|
|
width: parent.width
|
|
spacing: Theme.spacingXXS
|
|
|
|
StyledText {
|
|
text: I18n.tr("Health", "noun, battery health percentage label")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceTextMedium
|
|
}
|
|
|
|
StyledText {
|
|
text: BatteryService.batteryHealth
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Theme.fontWeightMedium
|
|
color: Theme.surfaceText
|
|
width: parent.width
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "show_chart"
|
|
title: I18n.tr("History")
|
|
settingKey: "batteryHistory"
|
|
tags: ["battery", "history", "graph", "charge"]
|
|
visible: BatteryService.batteryAvailable && history.samples.filter(sample => sample[2] !== 0).length > 1
|
|
|
|
BatteryHistoryChart {
|
|
height: implicitHeight
|
|
color: "transparent"
|
|
pad: 0
|
|
samples: history.samples
|
|
rangeStart: history.rangeStart
|
|
rangeEnd: history.rangeEnd
|
|
deviceName: !BatteryService.usePreferred && BatteryService.batteries.length > 1 ? history.device?.model || history.device?.nativePath || "" : ""
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "tune"
|
|
title: I18n.tr("Protection", "battery settings card title for charge limit")
|
|
settingKey: "batteryProtection"
|
|
tags: ["battery", "protection", "charge", "limit"]
|
|
|
|
SettingsSliderRow {
|
|
settingKey: "batteryChargeLimit"
|
|
text: I18n.tr("Charge limit")
|
|
value: SettingsData.batteryChargeLimit
|
|
minimum: 50
|
|
maximum: 100
|
|
onSliderValueChanged: newValue => SettingsData.set("batteryChargeLimit", newValue)
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: Qt.platform.os === "linux" && BatteryService.batteryAvailable && !root.chargeLimitSupported
|
|
body: StyledText {
|
|
width: parent.width
|
|
text: I18n.tr("No writable charge threshold file was found under /sys/class/power_supply.", "battery settings: why the charge limit could not be applied")
|
|
wrapMode: Text.WordWrap
|
|
color: Theme.surfaceVariantText
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: Qt.platform.os === "linux" && BatteryService.batteryAvailable && root.chargeLimitSupported
|
|
body: Row {
|
|
// charge_control_* live in Linux sysfs; no BSD equivalent
|
|
width: parent.width
|
|
height: applyButton.height
|
|
layoutDirection: I18n.isRtl ? Qt.LeftToRight : Qt.RightToLeft
|
|
|
|
Item {
|
|
width: Theme.spacingM
|
|
height: 1
|
|
}
|
|
|
|
DButton {
|
|
id: applyButton
|
|
text: I18n.tr("Apply to hardware")
|
|
iconName: "lock"
|
|
backgroundColor: Theme.primary
|
|
textColor: Theme.onPrimary
|
|
onClicked: {
|
|
applyLimitProcess.running = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "batteryNotifyChargeLimit"
|
|
text: I18n.tr("Notify when limit is reached")
|
|
checked: SettingsData.batteryNotifyChargeLimit
|
|
onToggled: checked => SettingsData.set("batteryNotifyChargeLimit", checked)
|
|
}
|
|
|
|
SettingsButtonGroupRow {
|
|
settingKey: "batteryChargeLimitNotificationType"
|
|
text: I18n.tr("Alert style")
|
|
model: [I18n.tr("Toast", "noun, battery alert style option, small popup message"), I18n.tr("Notification")]
|
|
visible: SettingsData.batteryNotifyChargeLimit
|
|
currentIndex: SettingsData.batteryChargeLimitNotificationType
|
|
onSelectionChanged: (index, selected) => {
|
|
if (selected) {
|
|
SettingsData.set("batteryChargeLimitNotificationType", index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "notifications"
|
|
title: I18n.tr("Alerts", "noun, battery settings card title for low battery alerts")
|
|
settingKey: "batteryAlerts"
|
|
tags: ["battery", "alerts", "low", "warning"]
|
|
|
|
SettingsSliderRow {
|
|
settingKey: "batteryLowThreshold"
|
|
text: I18n.tr("Low threshold")
|
|
value: SettingsData.batteryLowThreshold
|
|
minimum: 5
|
|
maximum: 40
|
|
onSliderValueChanged: newValue => SettingsData.set("batteryLowThreshold", newValue)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "batteryNotifyLow"
|
|
text: I18n.tr("Low notification")
|
|
checked: SettingsData.batteryNotifyLow
|
|
onToggled: checked => SettingsData.set("batteryNotifyLow", checked)
|
|
}
|
|
|
|
SettingsButtonGroupRow {
|
|
settingKey: "batteryLowNotificationType"
|
|
text: I18n.tr("Alert style")
|
|
model: [I18n.tr("Toast"), I18n.tr("Notification")]
|
|
enabled: SettingsData.batteryNotifyLow
|
|
currentIndex: SettingsData.batteryLowNotificationType
|
|
onSelectionChanged: (index, selected) => {
|
|
if (selected) {
|
|
SettingsData.set("batteryLowNotificationType", index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "battery_alert"
|
|
title: I18n.tr("Critical", "adjective, card title for critical battery level alerts")
|
|
settingKey: "batteryCriticalAlerts"
|
|
tags: ["battery", "critical", "alerts"]
|
|
|
|
SettingsSliderRow {
|
|
settingKey: "batteryCriticalThreshold"
|
|
text: I18n.tr("Critical threshold")
|
|
value: SettingsData.batteryCriticalThreshold
|
|
minimum: 1
|
|
maximum: 30
|
|
onSliderValueChanged: newValue => SettingsData.set("batteryCriticalThreshold", newValue)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "batteryCriticalAnimation"
|
|
tags: ["battery", "critical", "indicator", "animation", "pulse", "blink"]
|
|
text: I18n.tr("Pulse animation", "critical battery indicator animation toggle")
|
|
checked: SettingsData.batteryCriticalAnimation
|
|
onToggled: checked => SettingsData.set("batteryCriticalAnimation", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "batteryNotifyCritical"
|
|
text: I18n.tr("Critical notification")
|
|
checked: SettingsData.batteryNotifyCritical
|
|
onToggled: checked => SettingsData.set("batteryNotifyCritical", checked)
|
|
}
|
|
|
|
SettingsButtonGroupRow {
|
|
settingKey: "batteryCriticalNotificationType"
|
|
text: I18n.tr("Alert style")
|
|
model: [I18n.tr("Toast"), I18n.tr("Notification")]
|
|
enabled: SettingsData.batteryNotifyCritical
|
|
currentIndex: SettingsData.batteryCriticalNotificationType
|
|
onSelectionChanged: (index, selected) => {
|
|
if (selected) {
|
|
SettingsData.set("batteryCriticalNotificationType", index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "power"
|
|
title: I18n.tr("Power profiles")
|
|
settingKey: "powerProfilesSaving"
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "batteryAutoPowerSaver"
|
|
text: I18n.tr("Auto power saver")
|
|
description: I18n.tr("Switches to power saver at the low battery threshold", "battery auto power saver toggle description")
|
|
checked: SettingsData.batteryAutoPowerSaver
|
|
onToggled: checked => SettingsData.set("batteryAutoPowerSaver", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "lowerDisplayRefreshRateOnBattery"
|
|
tags: ["power", "battery", "display", "refresh", "rate", "60hz", "hz", "vrr"]
|
|
text: I18n.tr("Lower display refresh rate on battery")
|
|
visible: BatteryService.batteryAvailable
|
|
checked: SettingsData.lowerDisplayRefreshRateOnBattery
|
|
onToggled: checked => SettingsData.set("lowerDisplayRefreshRateOnBattery", checked)
|
|
}
|
|
|
|
SettingsDropdownRow {
|
|
settingKey: "acProfileName"
|
|
text: I18n.tr("Profile on AC")
|
|
options: [I18n.tr("Don't change"), Theme.getPowerProfileLabel(0), Theme.getPowerProfileLabel(1), Theme.getPowerProfileLabel(2)]
|
|
currentValue: {
|
|
const val = SettingsData.acProfileName;
|
|
const idx = ["", "0", "1", "2"].indexOf(val);
|
|
return idx >= 0 ? options[idx] : options[0];
|
|
}
|
|
onValueChanged: value => {
|
|
const idx = options.indexOf(value);
|
|
if (idx >= 0) {
|
|
SettingsData.set("acProfileName", ["", "0", "1", "2"][idx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsDropdownRow {
|
|
settingKey: "batteryProfileName"
|
|
text: I18n.tr("Profile on battery")
|
|
options: [I18n.tr("Don't change"), Theme.getPowerProfileLabel(0), Theme.getPowerProfileLabel(1), Theme.getPowerProfileLabel(2)]
|
|
currentValue: {
|
|
const val = SettingsData.batteryProfileName;
|
|
const idx = ["", "0", "1", "2"].indexOf(val);
|
|
return idx >= 0 ? options[idx] : options[0];
|
|
}
|
|
onValueChanged: value => {
|
|
const idx = options.indexOf(value);
|
|
if (idx >= 0) {
|
|
SettingsData.set("batteryProfileName", ["", "0", "1", "2"][idx]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|