noctalia-shell/Modules/SidePanel/Cards/ProfileCard.qml
2025-08-13 09:03:09 -04:00

483 lines
14 KiB
QML

import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.Services
import qs.Widgets
// Header card with avatar, user and quick actions
NBox {
id: root
readonly property real scaling: Scaling.scale(screen)
property string uptimeText: "--"
Layout.fillWidth: true
// Height driven by content
implicitHeight: content.implicitHeight + Style.marginMedium * 2 * scaling
RowLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Style.marginMedium * scaling
spacing: Style.marginMedium * scaling
NImageRounded {
width: Style.baseWidgetSize * 1.25 * scaling
height: Style.baseWidgetSize * 1.25 * scaling
imagePath: Settings.data.general.avatarImage
fallbackIcon: "person"
borderColor: Colors.accentPrimary
borderWidth: Math.max(1, Style.borderMedium * scaling)
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2 * scaling
NText {
text: Quickshell.env("USER") || "user"
font.weight: Style.fontWeightBold
}
NText {
text: `System Uptime: ${uptimeText}`
color: Colors.textSecondary
}
}
RowLayout {
spacing: Style.marginSmall * scaling
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Item {
Layout.fillWidth: true
}
NIconButton {
icon: "settings"
tooltipText: "Open settings"
onClicked: {
settingsPanel.isLoaded = !settingsPanel.isLoaded
}
}
NIconButton {
id: powerButton
icon: "power_settings_new"
onClicked: {
//settingsPanel.isLoaded = !settingsPanel.isLoaded
powerMenu.show()
}
}
}
}
// ----------------------------------
// Uptime
Timer {
interval: 60000
repeat: true
running: true
onTriggered: uptimeProcess.running = true
}
Process {
id: uptimeProcess
command: ["cat", "/proc/uptime"]
running: true
stdout: StdioCollector {
onStreamFinished: {
var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0])
var minutes = Math.floor(uptimeSeconds / 60) % 60
var hours = Math.floor(uptimeSeconds / 3600) % 24
var days = Math.floor(uptimeSeconds / 86400)
// Format the output
if (days > 0) {
uptimeText = days + "d " + hours + "h"
} else if (hours > 0) {
uptimeText = hours + "h" + minutes + "m"
} else {
uptimeText = minutes + "m"
}
uptimeProcess.running = false
}
}
}
// ----------------------------------
// Logout menu
function logout() {
if (WorkspaceManager.isNiri) {
logoutProcessNiri.running = true
} else if (WorkspaceManager.isHyprland) {
logoutProcessHyprland.running = true
} else {
console.warn("No supported compositor detected for logout")
}
}
function suspend() {
suspendProcess.running = true
}
function shutdown() {
shutdownProcess.running = true
}
function reboot() {
rebootProcess.running = true
}
function updateSystemInfo() {
uptimeProcess.running = true
}
Process {
id: shutdownProcess
command: ["shutdown", "-h", "now"]
running: false
}
Process {
id: rebootProcess
command: ["reboot"]
running: false
}
Process {
id: suspendProcess
command: ["systemctl", "suspend"]
running: false
}
Process {
id: logoutProcessNiri
command: ["niri", "msg", "action", "quit", "--skip-confirmation"]
running: false
}
Process {
id: logoutProcessHyprland
command: ["hyprctl", "dispatch", "exit"]
running: false
}
Process {
id: logoutProcess
command: ["loginctl", "terminate-user", Quickshell.env("USER")]
running: false
}
NPanel {
id: powerMenu
anchors.top: powerButton.bottom
anchors.right: powerButton.right
Rectangle {
width: 160 * scaling
height: 220 * scaling
color: Colors.surface
radius: 8 * scaling
border.color: Colors.outline
border.width: 1 * scaling
visible: true
z: 9999
anchors.top: parent.top
anchors.right: parent.right
anchors.rightMargin: 32 * scaling
anchors.topMargin: powerButton.y + powerButton.height + 48 * scaling
// Prevent closing when clicking in the panel bg
MouseArea {
anchors.fill: parent
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 8 * scaling
spacing: 4 * scaling
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 36 * scaling
radius: 6 * scaling
color: lockButtonArea.containsMouse ? Colors.accentPrimary : "transparent"
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 12 * scaling
anchors.rightMargin: 12 * scaling
Row {
id: lockRow
spacing: 8 * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Text {
text: "lock_outline"
font.family: "Material Symbols Outlined"
font.pixelSize: 16 * scaling
color: lockButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
Text {
text: "Lock Screen"
font.pixelSize: 14 * scaling
color: lockButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: lockButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
lockScreen.locked = true
systemMenu.visible = false
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 36 * scaling
radius: 6 * scaling
color: suspendButtonArea.containsMouse ? Colors.accentPrimary : "transparent"
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 12 * scaling
anchors.rightMargin: 12 * scaling
Row {
id: suspendRow
spacing: 8 * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Text {
text: "bedtime"
font.family: "Material Symbols Outlined"
font.pixelSize: 16 * scaling
color: suspendButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
Text {
text: "Suspend"
font.pixelSize: 14 * scaling
color: suspendButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: suspendButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
suspend()
systemMenu.visible = false
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 36 * scaling
radius: 6 * scaling
color: rebootButtonArea.containsMouse ? Colors.accentPrimary : "transparent"
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 12 * scaling
anchors.rightMargin: 12 * scaling
Row {
id: rebootRow
spacing: 8 * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Text {
text: "refresh"
font.family: "Material Symbols Outlined"
font.pixelSize: 16 * scaling
color: rebootButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
Text {
text: "Reboot"
font.pixelSize: 14 * scaling
color: rebootButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: rebootButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
reboot()
systemMenu.visible = false
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 36 * scaling
radius: 6 * scaling
color: logoutButtonArea.containsMouse ? Colors.accentPrimary : "transparent"
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 12 * scaling
anchors.rightMargin: 12 * scaling
Row {
id: logoutRow
spacing: 8 * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Text {
text: "exit_to_app"
font.family: "Material Symbols Outlined"
font.pixelSize: 16 * scaling
color: logoutButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
Text {
text: "Logout"
font.pixelSize: 14 * scaling
color: logoutButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: logoutButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
logout()
systemMenu.visible = false
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 36 * scaling
radius: 6 * scaling
color: shutdownButtonArea.containsMouse ? Colors.accentPrimary : "transparent"
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 12 * scaling
anchors.rightMargin: 12 * scaling
Row {
id: shutdownRow
spacing: 8 * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Text {
text: "power_settings_new"
font.family: "Material Symbols Outlined"
font.pixelSize: 16 * scaling
color: shutdownButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
Text {
text: "Shutdown"
font.pixelSize: 14 * scaling
color: shutdownButtonArea.containsMouse ? Colors.onAccent : Colors.textPrimary
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: shutdownButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
shutdown()
systemMenu.visible = false
}
}
}
}
}
}
}