added three new modules (Crypto.qml, Battery.qml, Network.qml), improved Stats.qml, improved Tray.qml, optimized code in general, added a polkit agent, configurations change (check configs)
This commit is contained in:
589
bar/modules/Battery.qml
Normal file
589
bar/modules/Battery.qml
Normal file
@@ -0,0 +1,589 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.UPower
|
||||
|
||||
import "../../config" as Cfg
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property real panelRadius: Cfg.Config.radius / 2
|
||||
property int borderWidth: Cfg.Config.panelBorderWidth
|
||||
|
||||
property bool isTop: true
|
||||
property var targetScreen: null
|
||||
property int barHeight: Cfg.Config.barHeight
|
||||
property int barMargin: Cfg.Config.margin
|
||||
|
||||
readonly property var t: Cfg.Config.theme
|
||||
|
||||
readonly property var bat: UPower.displayDevice
|
||||
readonly property bool batReady: bat && bat.ready && bat.isLaptopBattery
|
||||
|
||||
readonly property bool moduleEnabled: batready
|
||||
|
||||
readonly property real pct: batReady ? bat.percentage * 100 : 72
|
||||
readonly property int state: batReady ? bat.state : UPowerDeviceState.Unknown
|
||||
readonly property bool charging: state === UPowerDeviceState.Charging
|
||||
readonly property bool fullCharge: state === UPowerDeviceState.FullyCharged
|
||||
|
||||
readonly property string batIcon: {
|
||||
if (!batReady) return ""
|
||||
if (fullCharge) return ""
|
||||
if (charging) {
|
||||
if (pct >= 90) return ""
|
||||
if (pct >= 80) return ""
|
||||
if (pct >= 70) return ""
|
||||
if (pct >= 60) return ""
|
||||
if (pct >= 50) return ""
|
||||
if (pct >= 40) return ""
|
||||
if (pct >= 30) return ""
|
||||
if (pct >= 20) return ""
|
||||
if (pct >= 10) return ""
|
||||
return ""
|
||||
}
|
||||
if (pct >= 90) return ""
|
||||
if (pct >= 80) return ""
|
||||
if (pct >= 70) return ""
|
||||
if (pct >= 60) return ""
|
||||
if (pct >= 50) return ""
|
||||
if (pct >= 40) return ""
|
||||
if (pct >= 30) return ""
|
||||
if (pct >= 20) return ""
|
||||
if (pct >= 10) return ""
|
||||
return ""
|
||||
}
|
||||
|
||||
readonly property bool isCritical: batReady && !charging && pct <= 15
|
||||
readonly property bool isLow: batReady && !charging && pct <= 30
|
||||
|
||||
readonly property color iconColor: {
|
||||
if (fullCharge) return t.batteryFull ?? t.accent
|
||||
if (charging) return t.batteryCharging ?? t.accent
|
||||
if (isCritical) return t.batteryCritical ?? "#ff5555"
|
||||
if (isLow) return t.batteryLow ?? "#ffb86c"
|
||||
return t.batteryIcon ?? t.clockIcon
|
||||
}
|
||||
|
||||
readonly property var up: UPower
|
||||
readonly property var pp: PowerProfiles
|
||||
|
||||
readonly property string typeText: {
|
||||
if (!batReady) return "—"
|
||||
switch (bat.type) {
|
||||
case UPowerDeviceType.Battery: return "Battery"
|
||||
case UPowerDeviceType.Ups: return "UPS"
|
||||
case UPowerDeviceType.Mouse: return "Mouse"
|
||||
case UPowerDeviceType.Keyboard: return "Keyboard"
|
||||
case UPowerDeviceType.Phone: return "Phone"
|
||||
case UPowerDeviceType.Tablet: return "Tablet"
|
||||
default: return "Other"
|
||||
}
|
||||
}
|
||||
|
||||
readonly property string degradationText: {
|
||||
if (pp.degradationReason === PerformanceDegradationReason.None) return ""
|
||||
if (pp.degradationReason === PerformanceDegradationReason.LapDetected) return "Lap detected"
|
||||
if (pp.degradationReason === PerformanceDegradationReason.HighTemperature) return "High temp"
|
||||
return ""
|
||||
}
|
||||
|
||||
readonly property var profileHolds: {
|
||||
let names = []
|
||||
for (let i = 0; i < pp.holds.length; i++) {
|
||||
let h = pp.holds[i]
|
||||
let label = PowerProfile.toString(h.profile)
|
||||
if (h.applicationId) label += " (" + h.applicationId + ")"
|
||||
names.push(label)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
width: batLayout.implicitWidth + 24
|
||||
height: Cfg.Config.moduleHeight
|
||||
radius: panelRadius
|
||||
|
||||
color: hoverHandler.hovered ? t.batBgHover : t.batBg
|
||||
border.color: hoverHandler.hovered ? t.batBorderHover : t.batBorder
|
||||
border.width: borderWidth
|
||||
|
||||
Behavior on width { NumberAnimation { duration: 250; easing.type: Easing.OutCubic } }
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
Behavior on border.color { ColorAnimation { duration: 150 } }
|
||||
|
||||
HoverHandler { id: hoverHandler; cursorShape: Qt.PointingHandCursor }
|
||||
TapHandler { onTapped: batteryPopup.toggle() }
|
||||
|
||||
RowLayout {
|
||||
id: batLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: root.batIcon
|
||||
color: root.iconColor
|
||||
font.pixelSize: 16
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: 7
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: root.isCritical
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { to: 0.3; duration: 700; easing.type: Easing.InOutSine }
|
||||
NumberAnimation { to: 1.0; duration: 700; easing.type: Easing.InOutSine }
|
||||
}
|
||||
opacity: 1.0
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.batReady ? Math.round(root.pct) + "%" : "—"
|
||||
color: root.t.batText
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: chargingLabel
|
||||
text: root.charging ? " +" : (root.fullCharge ? " ✓" : "")
|
||||
color: root.iconColor
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
opacity: (hoverHandler.hovered || batteryPopup.popupOpen) ? 1.0 : 0.0
|
||||
Layout.preferredWidth: (hoverHandler.hovered || batteryPopup.popupOpen)
|
||||
? implicitWidth : 0
|
||||
clip: true
|
||||
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: batteryPopup
|
||||
screen: root.targetScreen
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
property bool popupOpen: false
|
||||
|
||||
function open() { hideTimer.stop(); visible = true; popupOpen = true }
|
||||
function close() { popupOpen = false; hideTimer.restart() }
|
||||
function toggle() { popupOpen ? close() : open() }
|
||||
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: Cfg.Config.popupAnimDuration + 70
|
||||
onTriggered: batteryPopup.visible = false
|
||||
}
|
||||
|
||||
anchors { top: true; bottom: true; left: true; right: true }
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
WlrLayershell.namespace: "main-shell-battery"
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
mask: popupOpen ? null : _noInput
|
||||
Region { id: _noInput }
|
||||
|
||||
TapHandler { onTapped: batteryPopup.close() }
|
||||
|
||||
Item {
|
||||
id: popupClipContainer
|
||||
readonly property int cardW: 260
|
||||
readonly property int screenPad: root.barMargin + 10
|
||||
|
||||
x: {
|
||||
let centerX = root.mapToItem(null, 0, 0).x + root.width / 2
|
||||
let desiredX = centerX - cardW / 2
|
||||
return Math.max(screenPad,
|
||||
Math.min(desiredX, parent.width - cardW - screenPad))
|
||||
}
|
||||
width: cardW
|
||||
|
||||
anchors.top: root.isTop ? parent.top : undefined
|
||||
anchors.bottom: root.isTop ? undefined : parent.bottom
|
||||
anchors.topMargin: root.isTop ? (root.barHeight + 10) : 0
|
||||
anchors.bottomMargin: root.isTop ? 0 : (root.barHeight + 10)
|
||||
height: parent.height - root.barHeight - 10
|
||||
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
id: cardShadow
|
||||
anchors.fill: card
|
||||
anchors.margins: -3
|
||||
radius: card.radius + 3
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(0, 0, 0, 0.35)
|
||||
border.width: 6
|
||||
visible: batteryPopup.popupOpen
|
||||
opacity: batteryPopup.popupOpen ? 0.6 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
width: parent.width
|
||||
height: cardCol.implicitHeight + 28
|
||||
|
||||
anchors.top: root.isTop ? parent.top : undefined
|
||||
anchors.bottom: root.isTop ? undefined : parent.bottom
|
||||
|
||||
transform: Translate {
|
||||
y: batteryPopup.popupOpen ? 0
|
||||
: (root.isTop ? -card.height : card.height)
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Cfg.Config.popupAnimDuration
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
enabled: batteryPopup.visible
|
||||
}
|
||||
}
|
||||
|
||||
opacity: batteryPopup.popupOpen ? 1.0 : 0.0
|
||||
Behavior on opacity { NumberAnimation { duration: 220 } }
|
||||
|
||||
color: root.t.batPopupBg
|
||||
radius: Cfg.Config.popupRadius
|
||||
border.color: root.t.batPopupBorder
|
||||
border.width: Cfg.Config.popupBorderWidth
|
||||
layer.enabled: true
|
||||
|
||||
MouseArea { anchors.fill: parent; propagateComposedEvents: false }
|
||||
|
||||
ColumnLayout {
|
||||
id: cardCol
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
margins: 18
|
||||
topMargin: 20
|
||||
}
|
||||
spacing: 12
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 14
|
||||
|
||||
Item {
|
||||
width: 52
|
||||
height: 52
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 46
|
||||
height: 46
|
||||
radius: 23
|
||||
color: Qt.rgba(
|
||||
Qt.darker(root.iconColor, 1.0).r,
|
||||
Qt.darker(root.iconColor, 1.0).g,
|
||||
Qt.darker(root.iconColor, 1.0).b,
|
||||
0.15
|
||||
)
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.batIcon
|
||||
color: root.iconColor
|
||||
font.pixelSize: 26
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: root.isCritical
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { to: 0.3; duration: 700; easing.type: Easing.InOutSine }
|
||||
NumberAnimation { to: 1.0; duration: 700; easing.type: Easing.InOutSine }
|
||||
}
|
||||
opacity: 1.0
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: root.batReady ? Math.round(root.pct) + "%" : "—"
|
||||
color: root.t.batPopupHeader
|
||||
font.pixelSize: 30
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (!root.batReady) return "No battery"
|
||||
if (root.fullCharge) return "Fully charged"
|
||||
if (root.charging) return "Charging"
|
||||
if (root.isCritical) return "Critical"
|
||||
if (root.isLow) return "Low"
|
||||
return "Discharging"
|
||||
}
|
||||
color: root.iconColor
|
||||
font.pixelSize: 11
|
||||
font.capitalization: Font.AllUppercase
|
||||
font.letterSpacing: 0.8
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
height: 10
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 5
|
||||
color: root.t.batProgressTrack
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(height, parent.width * (root.pct / 100))
|
||||
height: parent.height
|
||||
radius: 5
|
||||
clip: false
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: Qt.darker(root.iconColor, 1.25)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: root.iconColor
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width { NumberAnimation { duration: 450; easing.type: Easing.OutCubic } }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 5
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(root.iconColor.r,
|
||||
root.iconColor.g,
|
||||
root.iconColor.b, 0.2)
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.t.batPopupSeparator
|
||||
opacity: 0.6
|
||||
}
|
||||
|
||||
component DetailRow: RowLayout {
|
||||
required property string label
|
||||
required property string value
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: label
|
||||
color: root.t.batPopupDim
|
||||
font.pixelSize: 12
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Text {
|
||||
text: value
|
||||
color: root.t.batPopupText
|
||||
font.pixelSize: 12
|
||||
font.bold: true
|
||||
font.family: "monospace"
|
||||
}
|
||||
}
|
||||
|
||||
DetailRow {
|
||||
label: root.charging ? "Time to full" : "Time left"
|
||||
value: {
|
||||
if (!root.batReady || root.fullCharge) return "—"
|
||||
let secs = root.charging ? bat.timeToFull : bat.timeToEmpty
|
||||
if (secs <= 0) return "—"
|
||||
let h = Math.floor(secs / 3600)
|
||||
let m = Math.floor((secs % 3600) / 60)
|
||||
return h > 0 ? h + "h " + m + "m" : m + "m"
|
||||
}
|
||||
}
|
||||
|
||||
DetailRow {
|
||||
label: "Rate"
|
||||
value: {
|
||||
if (!root.batReady) return "—"
|
||||
let r = bat.changeRate
|
||||
return (r >= 0 ? "+" : "") + r.toFixed(1) + " W"
|
||||
}
|
||||
}
|
||||
|
||||
DetailRow {
|
||||
label: "Health"
|
||||
value: {
|
||||
if (!root.batReady) return "—"
|
||||
if (!bat.healthSupported) return "Undetected"
|
||||
return Math.round(bat.healthPercentage) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
DetailRow {
|
||||
label: "Energy"
|
||||
value: {
|
||||
if (!root.batReady) return "—"
|
||||
return bat.energy.toFixed(1) + " / "
|
||||
+ bat.energyCapacity.toFixed(1) + " Wh"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
height: 16
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: root.t.batPopupSeparator
|
||||
opacity: 0.6
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: sectionLabel.implicitWidth + 14
|
||||
height: sectionLabel.implicitHeight + 4
|
||||
radius: 4
|
||||
color: root.t.batPopupBg
|
||||
|
||||
Text {
|
||||
id: sectionLabel
|
||||
anchors.centerIn: parent
|
||||
text: "Power Profile"
|
||||
color: root.t.batPopupDim
|
||||
font.pixelSize: 10
|
||||
font.capitalization: Font.AllUppercase
|
||||
font.letterSpacing: 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ProfileButton: Rectangle {
|
||||
required property string text
|
||||
required property var profileValue
|
||||
property bool interactive: true
|
||||
|
||||
property bool isActive: interactive && (root.pp.profile === profileValue)
|
||||
|
||||
height: 38
|
||||
radius: 10
|
||||
color: isActive ? root.iconColor
|
||||
: Qt.rgba(root.iconColor.r, root.iconColor.g, root.iconColor.b,
|
||||
(profileHover.containsMouse && interactive) ? 0.14 : 0.06)
|
||||
border.color: isActive
|
||||
? Qt.lighter(root.iconColor, 1.2)
|
||||
: Qt.rgba(root.iconColor.r, root.iconColor.g, root.iconColor.b, 0.12)
|
||||
border.width: 1
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
Behavior on border.color { ColorAnimation { duration: 150 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: parent.text
|
||||
font.pixelSize: 10
|
||||
font.bold: parent.isActive
|
||||
font.letterSpacing: 0.4
|
||||
color: parent.isActive ? root.t.batProfileActiveText : root.t.batPopupText
|
||||
}
|
||||
|
||||
HoverHandler { id: profileHover; enabled: parent.interactive }
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: parent.interactive ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (parent.interactive) root.pp.profile = parent.profileValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 6
|
||||
|
||||
ProfileButton {
|
||||
text: "Power Saver"
|
||||
profileValue: PowerProfile.PowerSaver
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ProfileButton {
|
||||
text: "Balanced"
|
||||
profileValue: PowerProfile.Balanced
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ProfileButton {
|
||||
text: "Performance"
|
||||
profileValue: PowerProfile.Performance
|
||||
interactive: root.pp.hasPerformanceProfile
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
DetailRow {
|
||||
label: "Degradation"
|
||||
value: root.degradationText || "None"
|
||||
}
|
||||
|
||||
DetailRow {
|
||||
label: "Holds"
|
||||
value: root.profileHolds.length > 0 ? root.profileHolds[0] : "—"
|
||||
visible: root.profileHolds.length > 0
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
height: 16
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: root.t.batPopupSeparator
|
||||
opacity: 0.6
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: devLabel.implicitWidth + 14
|
||||
height: devLabel.implicitHeight + 4
|
||||
radius: 4
|
||||
color: root.t.batPopupBg
|
||||
|
||||
Text {
|
||||
id: devLabel
|
||||
anchors.centerIn: parent
|
||||
text: "Device"
|
||||
color: root.t.batPopupDim
|
||||
font.pixelSize: 10
|
||||
font.capitalization: Font.AllUppercase
|
||||
font.letterSpacing: 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DetailRow { label: "Type"; value: root.typeText }
|
||||
DetailRow { label: "Model"; value: root.batReady ? (bat.model || "Undetected") : "—" }
|
||||
DetailRow { label: "Power supply"; value: root.batReady ? (bat.powerSupply ? "Yes" : "No") : "—" }
|
||||
DetailRow { label: "On battery"; value: root.up.onBattery ? "Yes" : "No" }
|
||||
DetailRow { label: "Devices"; value: root.up.devices.values.length + " total" }
|
||||
|
||||
Item { height: 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user