diff --git a/.config/quickshell/ii/modules/bar/BatteryIndicator.qml b/.config/quickshell/ii/modules/bar/BatteryIndicator.qml index fa5d5daf4..ae907e42e 100644 --- a/.config/quickshell/ii/modules/bar/BatteryIndicator.qml +++ b/.config/quickshell/ii/modules/bar/BatteryIndicator.qml @@ -3,8 +3,9 @@ import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts +import Quickshell -Item { +MouseArea { id: root property bool borderless: Config.options.bar.borderless readonly property var chargeState: Battery.chargeState @@ -18,6 +19,8 @@ Item { implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 32 + hoverEnabled: true + RowLayout { id: rowLayout @@ -92,4 +95,25 @@ Item { } } + LazyLoader { + id: popupLoader + active: root.containsMouse + + component: PopupWindow { + id: popupWindow + visible: true + implicitWidth: batteryPopup.implicitWidth + implicitHeight: batteryPopup.implicitHeight + anchor.item: root + anchor.rect.x: (root.implicitWidth - popupWindow.implicitWidth) / 2 + anchor.rect.y: Config.options.bar.bottom + ? (-batteryPopup.implicitHeight - 15) + : (root.implicitHeight + 15) + color: "transparent" + BatteryPopup { + id: batteryPopup + } + } + } + } diff --git a/.config/quickshell/ii/modules/bar/BatteryPopup.qml b/.config/quickshell/ii/modules/bar/BatteryPopup.qml new file mode 100644 index 000000000..ae0264c20 --- /dev/null +++ b/.config/quickshell/ii/modules/bar/BatteryPopup.qml @@ -0,0 +1,125 @@ +import qs.modules.common +import qs.modules.common.widgets +import qs.services +import qs +import QtQuick +import QtQuick.Layouts + +Rectangle { + id: root + readonly property real margin: 10 + implicitWidth: columnLayout.implicitWidth + margin * 2 + implicitHeight: columnLayout.implicitHeight + margin * 2 + color: Appearance.colors.colLayer0 + radius: Appearance.rounding.small + border.width: 1 + border.color: Appearance.colors.colLayer0Border + clip: true + + ColumnLayout { + id: columnLayout + anchors.centerIn: parent + spacing: 8 + + RowLayout { + spacing: 5 + Layout.fillWidth: true + MaterialSymbol { text: "thermostat"; color: Appearance.m3colors.m3onSecondaryContainer } + StyledText { text: Translation.tr("Temperature:"); color: Appearance.colors.colOnLayer1 } + StyledText { Layout.fillWidth: true; horizontalAlignment: Text.AlignRight; color: Appearance.colors.colOnLayer1; text: `${Battery.temperature}°C` } + } + + // This row is hidden when the battery is full. + RowLayout { + spacing: 5 + Layout.fillWidth: true + property bool rowVisible: { + let timeValue = Battery.isCharging ? Battery.timeToFull : Battery.timeToEmpty; + let power = Battery.energyRate; + return !(Battery.chargeState == 4 || timeValue <= 0 || power <= 0.01); + } + visible: rowVisible + opacity: rowVisible ? 1 : 0 + Behavior on opacity { NumberAnimation { duration: 500 } } + + MaterialSymbol { text: "schedule"; color: Appearance.m3colors.m3onSecondaryContainer } + StyledText { text: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:"); color: Appearance.colors.colOnLayer1 } + StyledText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + color: Appearance.colors.colOnLayer1 + text: { + function formatTime(seconds) { + var h = Math.floor(seconds / 3600); + var m = Math.floor((seconds % 3600) / 60); + if (h > 0) + return `${h}h ${m}m`; + else + return `${m}m`; + } + if (Battery.isCharging) + return formatTime(Battery.timeToFull); + else + return formatTime(Battery.timeToEmpty); + } + } + } + + RowLayout { + spacing: 5 + Layout.fillWidth: true + + property bool rowVisible: !(Battery.chargeState != 4 && Battery.energyRate == 0) + visible: rowVisible + opacity: rowVisible ? 1 : 0 + Behavior on opacity { NumberAnimation { duration: 500 } } + + MaterialSymbol { + text: { + if (Battery.isCharging) { + return "power"; + } else if (Battery.percentage >= 0.8) { + return "battery_full"; + } else if (Battery.percentage >= 0.6) { + return "battery_5_bar"; + } else if (Battery.percentage >= 0.4) { + return "battery_4_bar"; + } else if (Battery.percentage >= 0.2) { + return "battery_2_bar"; + } else { + return "battery_0_bar"; + } + } + color: Appearance.m3colors.m3onSecondaryContainer + } + + + StyledText { + text: { + if (Battery.chargeState == 4) { + return Translation.tr("Fully charged"); + } else if (Battery.chargeState == 1) { + return Translation.tr("Charging:"); + } else { + return Translation.tr("Discharging:"); + } + } + color: Appearance.colors.colOnLayer1 + } + + StyledText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + color: Appearance.colors.colOnLayer1 + text: { + if (Battery.chargeState == 4) { + return ""; + } else { + return `${Battery.energyRate.toFixed(2)}W`; + } + } + } + } + + } +} diff --git a/.config/quickshell/ii/services/Battery.qml b/.config/quickshell/ii/services/Battery.qml index 8a3cf3160..5193c19f5 100644 --- a/.config/quickshell/ii/services/Battery.qml +++ b/.config/quickshell/ii/services/Battery.qml @@ -4,6 +4,8 @@ import qs import qs.modules.common import Quickshell import Quickshell.Services.UPower +import QtQuick +import Quickshell.Io Singleton { property bool available: UPower.displayDevice.isLaptopBattery @@ -21,6 +23,43 @@ Singleton { property bool isCriticalAndNotCharging: isCritical && !isCharging property bool isSuspendingAndNotCharging: allowAutomaticSuspend && isSuspending && !isCharging + property real energyRate: UPower.displayDevice.changeRate + property real timeToEmpty: UPower.displayDevice.timeToEmpty + property real timeToFull: UPower.displayDevice.timeToFull + property real temperature: 0 + + Process { + id: tempProcess + command: ["bash", "-c", "cat /sys/class/thermal/thermal_zone0/temp"] + stdout: StdioCollector { + onStreamFinished: { + if (text && text.trim() !== "") { + Battery.temperature = parseInt(text.trim()) / 1000 + } else { + Battery.temperature = 0 + } + } + } + } + + Timer { + interval: 3000 + repeat: true + running: true + onTriggered: { + if (!tempProcess.running) { + tempProcess.running = true + } + } + } + + Component.onCompleted: { + if (!tempProcess.running) { + tempProcess.running = true; + } + } + + onIsLowAndNotChargingChanged: { if (available && isLowAndNotCharging) Quickshell.execDetached([ "notify-send", diff --git a/.config/quickshell/translations/en_US.json b/.config/quickshell/translations/en_US.json index fe8c64533..474d4a4f1 100644 --- a/.config/quickshell/translations/en_US.json +++ b/.config/quickshell/translations/en_US.json @@ -310,5 +310,11 @@ "Sunrise": "Sunrise", "Pressure": "Pressure", "Visibility": "Visibility", - "Precipitation": "Precipitation" + "Precipitation": "Precipitation", + "Temperature:": "Temperature:", + "Time to full:": "Time to full:", + "Time to empty:": "Time to empty:", + "Fully charged": "Fully charged", + "Charging:": "Charging:", + "Discharging:": "Discharging:" } \ No newline at end of file diff --git a/.config/quickshell/translations/zh_CN.json b/.config/quickshell/translations/zh_CN.json index 34dc54157..216e3f94b 100644 --- a/.config/quickshell/translations/zh_CN.json +++ b/.config/quickshell/translations/zh_CN.json @@ -310,5 +310,11 @@ "Sunset": "日落", "Humidity": "湿度", "Wind": "风", - "Precipitation": "降水量" + "Precipitation": "降水量", + "Temperature:": "温度:", + "Time to full:": "距离充满:", + "Time to empty:": "距离耗尽:", + "Fully charged": "已充满电", + "Charging:": "充电功率:", + "Discharging:": "放电功率:" }