You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

353 lines
16 KiB

/*
SPDX-FileCopyrightText: 2011 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2011 Viranch Mehta <viranch.mehta@gmail.com>
SPDX-FileCopyrightText: 2013-2015 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-FileCopyrightText: 2021-2022 ivan tkachenko <me@ratijas.tk>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import org.kde.kcoreaddons 1.0 as KCoreAddons
import org.kde.kquickcontrolsaddons 2.1 // For KCMShell
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.plasmoid 2.0
import "logic.js" as Logic
Item {
id: batterymonitor
property QtObject pmSource: PlasmaCore.DataSource {
id: pmSource
engine: "powermanagement"
connectedSources: sources
onSourceAdded: {
disconnectSource(source);
connectSource(source);
}
onSourceRemoved: {
disconnectSource(source);
}
onDataChanged: {
Logic.updateBrightness(batterymonitor, pmSource);
Logic.updateInhibitions(batterymonitor, pmSource);
}
}
property QtObject batteries: PlasmaCore.SortFilterModel {
id: batteries
filterRole: "Is Power Supply"
sortOrder: Qt.DescendingOrder
sourceModel: PlasmaCore.SortFilterModel {
sortRole: "Pretty Name"
sortOrder: Qt.AscendingOrder
sortCaseSensitivity: Qt.CaseInsensitive
sourceModel: PlasmaCore.DataModel {
dataSource: pmSource
sourceFilter: "Battery[0-9]+"
}
}
}
property QtObject updateScreenBrightnessJob
property QtObject updateKeyboardBrightnessJob
readonly property bool isBrightnessAvailable: pmSource.data["PowerDevil"] && pmSource.data["PowerDevil"]["Screen Brightness Available"] ? true : false
readonly property bool isKeyboardBrightnessAvailable: pmSource.data["PowerDevil"] && pmSource.data["PowerDevil"]["Keyboard Brightness Available"] ? true : false
readonly property bool hasBatteries: batteries.count > 0 && pmSource.data["Battery"]["Has Cumulative"]
readonly property bool hasBrightness: isBrightnessAvailable || isKeyboardBrightnessAvailable
readonly property bool kcmAuthorized: KCMShell.authorize("powerdevilprofilesconfig.desktop").length > 0
readonly property bool kcmEnergyInformationAuthorized: KCMShell.authorize("kcm_energyinfo.desktop").length > 0
readonly property int maximumScreenBrightness: pmSource.data["PowerDevil"] ? pmSource.data["PowerDevil"]["Maximum Screen Brightness"] || 0 : 0
readonly property int maximumKeyboardBrightness: pmSource.data["PowerDevil"] ? pmSource.data["PowerDevil"]["Maximum Keyboard Brightness"] || 0 : 0
readonly property int remainingTime: Number(pmSource.data["Battery"]["Remaining msec"])
property bool powermanagementDisabled: false
property bool disableBrightnessUpdate: true
property int screenBrightness
property int keyboardBrightness
// List of active power management inhibitions (applications that are
// blocking sleep and screen locking).
//
// type: [{
// Icon: string,
// Name: string,
// Reason: string,
// }]
property var inhibitions: []
function action_configure() {
KCMShell.openSystemSettings("kcm_powerdevilprofilesconfig");
}
function action_energyinformationkcm() {
KCMShell.openInfoCenter("kcm_energyinfo");
}
function action_showPercentage() {
if (!Plasmoid.configuration.showPercentage) {
Plasmoid.configuration.showPercentage = true;
} else {
Plasmoid.configuration.showPercentage = false;
}
}
Plasmoid.switchWidth: PlasmaCore.Units.gridUnit * 10
Plasmoid.switchHeight: PlasmaCore.Units.gridUnit * 10
Plasmoid.title: (hasBatteries && hasBrightness ? i18n("Battery and Brightness") :
hasBrightness ? i18n("Brightness") :
hasBatteries ? i18n("Battery") : i18n("Power Management"))
LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
LayoutMirroring.childrenInherit: true
Plasmoid.status: {
if (powermanagementDisabled) {
return PlasmaCore.Types.ActiveStatus;
}
if (pmSource.data.Battery["Has Cumulative"]
&& !((pmSource.data["AC Adapter"]["Plugged in"] && pmSource.data["Battery"]["State"] === "FullyCharged") ||
// When we are using a charge threshold, the kernel
// may stop charging within a percentage point of the actual threshold
// and this is considered correct behavior, so we have to handle
// that. See https://bugzilla.kernel.org/show_bug.cgi?id=215531.
(pmSource.data["AC Adapter"]["Plugged in"]
&& typeof pmSource.data["Battery"]["Charge Stop Threshold"] === "number"
&& (pmSource.data.Battery.Percent >= pmSource.data["Battery"]["Charge Stop Threshold"] - 1
&& pmSource.data.Battery.Percent <= pmSource.data["Battery"]["Charge Stop Threshold"] + 1)
// Also, Upower may give us a status of "Not charging" rather than
// "Fully charged", so we need to account for that as well. See
// https://gitlab.freedesktop.org/upower/upower/-/issues/142.
&& (pmSource.data["Battery"]["State"] === "NoCharge" || pmSource.data["Battery"]["State"] === "FullyCharged"))
)){
return PlasmaCore.Types.ActiveStatus;
}
return PlasmaCore.Types.PassiveStatus;
}
Plasmoid.toolTipMainText: {
if (!hasBatteries) {
return Plasmoid.title
} else if (pmSource.data["Battery"]["State"] === "FullyCharged") {
return i18n("Fully Charged");
}
const percent = pmSource.data.Battery.Percent;
if (pmSource.data["AC Adapter"] && pmSource.data["AC Adapter"]["Plugged in"]) {
const state = pmSource.data.Battery.State;
if (state === "NoCharge") {
return i18n("Battery at %1%, not Charging", percent);
} else if (state === "Discharging") {
return i18n("Battery at %1%, plugged in but still discharging", percent);
} else if (state === "Charging") {
return i18n("Battery at %1%, Charging", percent);
}
}
return i18n("Battery at %1%", percent);
}
Plasmoid.toolTipSubText: {
const parts = [];
// Add special text for the "plugged in but still discharging" case
if (pmSource.data["AC Adapter"] && pmSource.data["AC Adapter"]["Plugged in"] && pmSource.data.Battery.State === "Discharging") {
parts.push(i18n("The power supply is not powerful enough to charge the battery"));
}
if (batteries.count === 0) {
parts.push(i18n("No Batteries Available"));
} else if (remainingTime > 0) {
const remainingTimeString = KCoreAddons.Format.formatDuration(remainingTime, KCoreAddons.FormatTypes.HideSeconds);
if (pmSource.data["Battery"]["State"] === "FullyCharged") {
// Don't add anything
} else if (pmSource.data["AC Adapter"] && pmSource.data["AC Adapter"]["Plugged in"]) {
parts.push(i18nc("time until fully charged - HH:MM","%1 until fully charged", remainingTimeString));
} else {
parts.push(i18nc("remaining time left of battery usage - HH:MM","%1 remaining", remainingTimeString));
}
} else if (pmSource.data.Battery.State === "NoCharge") {
parts.push(i18n("Not charging"));
} // otherwise, don't add anything
if (powermanagementDisabled) {
parts.push(i18n("Automatic sleep and screen locking are disabled"));
}
return parts.join("\n");
}
Plasmoid.icon: !hasBatteries ? "video-display-brightness" : "battery"
onScreenBrightnessChanged: {
if (disableBrightnessUpdate) {
return;
}
const service = pmSource.serviceForSource("PowerDevil");
const operation = service.operationDescription("setBrightness");
operation.brightness = screenBrightness;
// show OSD only when the plasmoid isn't expanded since the moving slider is feedback enough
operation.silent = Plasmoid.expanded;
updateScreenBrightnessJob = service.startOperationCall(operation);
updateScreenBrightnessJob.finished.connect(job => {
Logic.updateBrightness(batterymonitor, pmSource);
});
}
onKeyboardBrightnessChanged: {
if (disableBrightnessUpdate) {
return;
}
var service = pmSource.serviceForSource("PowerDevil");
var operation = service.operationDescription("setKeyboardBrightness");
operation.brightness = keyboardBrightness;
// show OSD only when the plasmoid isn't expanded since the moving slider is feedback enough
operation.silent = Plasmoid.expanded;
updateKeyboardBrightnessJob = service.startOperationCall(operation);
updateKeyboardBrightnessJob.finished.connect(job => {
Logic.updateBrightness(batterymonitor, pmSource);
});
}
Plasmoid.compactRepresentation: CompactRepresentation {
hasBatteries: batterymonitor.hasBatteries
batteries: batterymonitor.batteries
onWheel: {
const delta = wheel.angleDelta.y || wheel.angleDelta.x
const maximumBrightness = batterymonitor.maximumScreenBrightness
// Don't allow the UI to turn off the screen
// Please see https://git.reviewboard.kde.org/r/122505/ for more information
const minimumBrightness = (maximumBrightness > 100 ? 1 : 0)
const stepSize = Math.max(1, maximumBrightness / 20)
let newBrightness;
if (Math.abs(delta) < 120) {
// Touchpad scrolling
brightnessError += delta * stepSize / 120;
const change = Math.round(brightnessError);
brightnessError -= change;
newBrightness = batterymonitor.screenBrightness + change;
} else {
// Discrete/wheel scrolling
newBrightness = Math.round(batterymonitor.screenBrightness/stepSize + delta/120) * stepSize;
}
batterymonitor.screenBrightness = Math.max(minimumBrightness, Math.min(maximumBrightness, newBrightness));
}
}
Plasmoid.fullRepresentation: PopupDialog {
readonly property var appletInterface: Plasmoid.self
Layout.minimumWidth: PlasmaCore.Units.gridUnit * 10
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 80
Layout.preferredWidth: PlasmaCore.Units.gridUnit * 20
Layout.minimumHeight: PlasmaCore.Units.gridUnit * 10
Layout.maximumHeight: PlasmaCore.Units.gridUnit * 40
Layout.preferredHeight: implicitHeight
id: dialogItem
model: Plasmoid.expanded ? batteries : null
isBrightnessAvailable: batterymonitor.isBrightnessAvailable
isKeyboardBrightnessAvailable: batterymonitor.isKeyboardBrightnessAvailable
pluggedIn: pmSource.data["AC Adapter"] !== undefined && pmSource.data["AC Adapter"]["Plugged in"]
remainingTime: batterymonitor.remainingTime
readonly property string actuallyActiveProfile: pmSource.data["Power Profiles"] ? (pmSource.data["Power Profiles"]["Current Profile"] || "") : ""
activeProfile: actuallyActiveProfile
inhibitions: batterymonitor.inhibitions
inhibitsLidAction: pmSource.data["PowerDevil"] && pmSource.data["PowerDevil"]["Is Lid Present"] && !pmSource.data["PowerDevil"]["Triggers Lid Action"] ? true : false
profiles: pmSource.data["Power Profiles"] ? (pmSource.data["Power Profiles"]["Profiles"] || []) : []
inhibitionReason: pmSource.data["Power Profiles"] ? (pmSource.data["Power Profiles"]["Performance Inhibited Reason"] || "") : ""
degradationReason: pmSource.data["Power Profiles"] ? (pmSource.data["Power Profiles"]["Performance Degraded Reason"] || "") : ""
profileHolds: pmSource.data["Power Profiles"] ? (pmSource.data["Power Profiles"]["Profile Holds"] || []) : []
property int cookie1: -1
property int cookie2: -1
onPowerManagementChanged: disabled => {
const service = pmSource.serviceForSource("PowerDevil");
if (disabled) {
const reason = i18n("The battery applet has enabled system-wide inhibition");
const op1 = service.operationDescription("beginSuppressingSleep");
op1.reason = reason;
const op2 = service.operationDescription("beginSuppressingScreenPowerManagement");
op2.reason = reason;
const job1 = service.startOperationCall(op1);
job1.finished.connect(job => {
cookie1 = job.result;
});
const job2 = service.startOperationCall(op2);
job2.finished.connect(job => {
cookie2 = job.result;
});
} else {
const op1 = service.operationDescription("stopSuppressingSleep");
op1.cookie = cookie1;
const op2 = service.operationDescription("stopSuppressingScreenPowerManagement");
op2.cookie = cookie2;
const job1 = service.startOperationCall(op1);
job1.finished.connect(job => {
cookie1 = -1;
});
const job2 = service.startOperationCall(op2);
job2.finished.connect(job => {
cookie2 = -1;
});
}
batterymonitor.powermanagementDisabled = disabled
}
PlasmaCore.DataSource {
id: notificationSource
engine: "notifications"
}
onActivateProfileRequested: {
dialogItem.activeProfile = profile;
const service = pmSource.serviceForSource("PowerDevil");
const op = service.operationDescription("setPowerProfile");
op.profile = profile;
const job = service.startOperationCall(op);
job.finished.connect(job => {
dialogItem.activeProfile = Qt.binding(() => actuallyActiveProfile);
if (!job.result) {
const notifications = notificationSource.serviceForSource("notification")
const operation = notifications.operationDescription("createNotification");
operation.appName = i18n("Battery and Brightness");
operation.appIcon = "dialog-error";
operation.icon = "dialog-error";
operation.body = i18n("Failed to activate %1 mode", profile);
notifications.startOperationCall(operation);
}
});
}
}
Component.onCompleted: {
Logic.updateBrightness(batterymonitor, pmSource);
Logic.updateInhibitions(batterymonitor, pmSource)
if (batterymonitor.kcmEnergyInformationAuthorized) {
Plasmoid.setAction("energyinformationkcm", i18n("&Show Energy Information…"), "documentinfo");
}
Plasmoid.setAction("showPercentage", i18n("Show Battery Percentage on Icon"), "format-number-percent");
Plasmoid.action("showPercentage").checkable = true;
Plasmoid.action("showPercentage").checked = Qt.binding(() => Plasmoid.configuration.showPercentage);
if (batterymonitor.kcmAuthorized) {
Plasmoid.removeAction("configure");
Plasmoid.setAction("configure", i18n("&Configure Energy Saving…"), "configure", "alt+d, s");
}
}
}