aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-16 09:58:33 +0200
committerDanilo M. <danix@danix.xyz>2026-10-16 09:58:33 +0200
commit23f1fde76647ecadac0f17bf2f87195cc2ea3c14 (patch)
treef544795be3e611ff35f9dcf6cec0f495b6c10626
parent5fabbeb09aea782b7df05e8827b552e5ec44cf83 (diff)
downloadquickshell-23f1fde76647ecadac0f17bf2f87195cc2ea3c14.tar.gz
quickshell-23f1fde76647ecadac0f17bf2f87195cc2ea3c14.zip
feat(desktop): a greeting row above the notifications
Avatar, Hello <login> and the real name, re-read whenever the grid view shows again so an edit is visible on the way back from the page. Module gains tiled, so a module can own a page without a tile. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
-rw-r--r--desktop/Drawer.qml11
-rw-r--r--desktop/Module.qml4
-rw-r--r--desktop/modules/user/UserRow.qml102
3 files changed, 115 insertions, 2 deletions
diff --git a/desktop/Drawer.qml b/desktop/Drawer.qml
index 92c4fca..72aa4d8 100644
--- a/desktop/Drawer.qml
+++ b/desktop/Drawer.qml
@@ -13,6 +13,7 @@ import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import QtQuick
+import "modules/user"
Scope {
id: root
@@ -165,9 +166,15 @@ Scope {
// The notification engine's space. The live queue lists
// here while the drawer is open; the balloons stand down
// because the drawer flag above is set.
+ UserRow {
+ id: user
+ anchors { top: parent.top; left: parent.left; right: parent.right }
+ onClicked: root.show("user")
+ }
+
NotificationList {
id: notifications
- anchors { top: parent.top; left: parent.left; right: parent.right }
+ anchors { top: user.bottom; topMargin: 12; left: parent.left; right: parent.right }
anchors.bottom: tiles.top
anchors.bottomMargin: 16
onHistory: root.history = true
@@ -191,7 +198,7 @@ Scope {
spacing: 10
Repeater {
- model: root.modules
+ model: root.modules.filter(m => m.tiled)
Tile {
required property QtObject modelData
diff --git a/desktop/Module.qml b/desktop/Module.qml
index 7c13b9b..2ce2939 100644
--- a/desktop/Module.qml
+++ b/desktop/Module.qml
@@ -38,6 +38,10 @@ QtObject {
// Whether the tile should render in its active accent. Default false.
property bool active: false
+ // Whether the drawer's grid shows a tile. False for a module reached
+ // some other way: the user page opens from the drawer's header row.
+ property bool tiled: true
+
// Rendered inside the tile, below the icon: a short state line. Null for
// a tile that says nothing beyond its label.
property Component tileContent: null
diff --git a/desktop/modules/user/UserRow.qml b/desktop/modules/user/UserRow.qml
new file mode 100644
index 0000000..106a490
--- /dev/null
+++ b/desktop/modules/user/UserRow.qml
@@ -0,0 +1,102 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import Quickshell
+import Quickshell.Io
+import Quickshell.Widgets
+import QtQuick
+import "../.."
+import "User.js" as U
+
+// The drawer's greeting: avatar, login name, real name. It re-reads each time
+// it becomes visible, which is every drawer open and every return from a
+// page, so a save on the user page shows here on the way back.
+Rectangle {
+ id: row
+
+ signal clicked
+ property var info: ({})
+
+ implicitHeight: 64
+ radius: 12
+ color: area.containsMouse ? Qt.alpha(Theme.accent, 0.22) : Qt.alpha(Theme.surface, 0.35)
+ border.width: 1
+ border.color: Qt.alpha(Theme.text, 0.08)
+
+ function read() {
+ reader.running = false;
+ reader.running = true;
+ }
+ onVisibleChanged: if (visible) read()
+ Component.onCompleted: read()
+
+ Process {
+ id: reader
+ command: [Qt.resolvedUrl("userctl").toString().replace(/^file:\/\//, ""), "get"]
+ // Cleared first: the icon path never changes, so only a source going
+ // through "" makes an uncached Image load the new picture.
+ stdout: StdioCollector {
+ onStreamFinished: { row.info = ({}); row.info = U.parse(text); }
+ }
+ }
+
+ ClippingRectangle {
+ id: face
+ anchors { left: parent.left; leftMargin: 12; verticalCenter: parent.verticalCenter }
+ width: 44
+ height: 44
+ radius: 22
+ color: Theme.surfaceAlt
+
+ Image {
+ id: pic
+ anchors.fill: parent
+ source: row.info.avatar ? "file://" + row.info.avatar : ""
+ fillMode: Image.PreserveAspectCrop
+ sourceSize: Qt.size(88, 88)
+ cache: false
+ }
+ Text {
+ anchors.centerIn: parent
+ visible: pic.status !== Image.Ready
+ text: ""
+ font { family: Theme.iconFamily; pixelSize: 20 }
+ color: Theme.subtext
+ }
+ }
+
+ Column {
+ anchors { left: face.right; leftMargin: 12; right: parent.right; rightMargin: 12
+ verticalCenter: parent.verticalCenter }
+ spacing: 2
+ Text {
+ text: "Hello " + Quickshell.env("USER")
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize + 2; bold: true }
+ color: Theme.text
+ }
+ Text {
+ width: parent.width
+ visible: text !== ""
+ text: row.info.name ?? ""
+ elide: Text.ElideRight
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ color: Theme.subtext
+ }
+ }
+
+ MouseArea {
+ id: area
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: row.clicked()
+ }
+}