mirror of
https://github.com/AquaMorph/dotfiles.git
synced 2026-09-29 20:04:21 +00:00
feat(quickshell): show detailed status popups
This commit is contained in:
@@ -31,6 +31,15 @@ PanelWindow {
|
||||
precision: SystemClock.Seconds
|
||||
}
|
||||
|
||||
DetailPopup {
|
||||
id: detailsPopup
|
||||
hardware: bar.hardware
|
||||
sink: bar.sink
|
||||
source: bar.source
|
||||
battery: bar.battery
|
||||
clock: clock
|
||||
}
|
||||
|
||||
Row {
|
||||
id: leftModules
|
||||
anchors {
|
||||
@@ -137,7 +146,9 @@ PanelWindow {
|
||||
: Math.round(bar.source.audio.volume * 100) + "%")
|
||||
: ""
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.MiddleButton && bar.sink && bar.sink.audio)
|
||||
if (mouse.button === Qt.LeftButton)
|
||||
detailsPopup.toggle("audio", audioBlock);
|
||||
else if (mouse.button === Qt.MiddleButton && bar.sink && bar.sink.audio)
|
||||
bar.sink.audio.muted = !bar.sink.audio.muted;
|
||||
else
|
||||
Quickshell.execDetached(["qjackctl"]);
|
||||
@@ -151,13 +162,18 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Network { service: bar.hardware }
|
||||
Network {
|
||||
service: bar.hardware
|
||||
onDetailsRequested: anchorItem => detailsPopup.toggle("network", anchorItem)
|
||||
}
|
||||
Backlight { service: bar.hardware }
|
||||
|
||||
Block {
|
||||
id: batteryBlock
|
||||
property int percentage: Math.round(bar.battery.percentage * 100)
|
||||
|
||||
visible: bar.battery.ready && bar.battery.isLaptopBattery
|
||||
interactive: true
|
||||
blockColor: percentage <= 15 && UPower.onBattery
|
||||
? "#e91e63" : UPower.onBattery ? "#ffffff" : "#2980b9"
|
||||
textColor: UPower.onBattery && percentage > 15
|
||||
@@ -171,6 +187,7 @@ PanelWindow {
|
||||
if (percentage <= 85) return "\uf241 " + percentage + "%";
|
||||
return "\uf240 " + percentage + "%";
|
||||
}
|
||||
onClicked: detailsPopup.toggle("battery", batteryBlock)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -233,12 +250,11 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Block {
|
||||
property bool detailed: false
|
||||
id: clockBlock
|
||||
|
||||
interactive: true
|
||||
text: Qt.formatDateTime(clock.date,
|
||||
detailed ? "yyyy-MM-dd hh:mm:ss" : "hh:mm")
|
||||
onClicked: detailed = !detailed
|
||||
text: Qt.formatDateTime(clock.date, "hh:mm")
|
||||
onClicked: detailsPopup.toggle("clock", clockBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
|
||||
PopupWindow {
|
||||
id: root
|
||||
|
||||
property var hardware
|
||||
property var sink
|
||||
property var source
|
||||
property var battery
|
||||
property var clock
|
||||
property var anchorItem: null
|
||||
property string page: ""
|
||||
|
||||
function toggle(nextPage, item) {
|
||||
if (visible && page === nextPage) {
|
||||
visible = false;
|
||||
return;
|
||||
}
|
||||
page = nextPage;
|
||||
anchorItem = item;
|
||||
if (page === "network")
|
||||
hardware.refreshWifi();
|
||||
anchor.updateAnchor();
|
||||
visible = true;
|
||||
}
|
||||
|
||||
function duration(seconds) {
|
||||
if (seconds <= 0)
|
||||
return "Calculating";
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
return hours + "h " + minutes + "m";
|
||||
}
|
||||
|
||||
function batteryState() {
|
||||
switch (battery.state) {
|
||||
case UPowerDeviceState.Charging: return "Charging";
|
||||
case UPowerDeviceState.Discharging: return "Discharging";
|
||||
case UPowerDeviceState.FullyCharged: return "Fully charged";
|
||||
case UPowerDeviceState.Empty: return "Empty";
|
||||
case UPowerDeviceState.PendingCharge: return "Waiting to charge";
|
||||
case UPowerDeviceState.PendingDischarge: return "Waiting to discharge";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
visible: false
|
||||
implicitWidth: 320
|
||||
implicitHeight: contentLoader.item ? contentLoader.item.implicitHeight + 32 : 100
|
||||
color: "transparent"
|
||||
|
||||
anchor {
|
||||
item: root.anchorItem
|
||||
edges: Edges.Bottom | Edges.Right
|
||||
gravity: Edges.Bottom | Edges.Left
|
||||
margins.top: 4
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#e0000000"
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
margins: 12
|
||||
}
|
||||
sourceComponent: root.page === "clock" ? clockPage
|
||||
: root.page === "audio" ? audioPage
|
||||
: root.page === "network" ? networkPage
|
||||
: batteryPage
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: 3
|
||||
color: "#b3e5fc"
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: clockPage
|
||||
|
||||
Item {
|
||||
id: clockContent
|
||||
property date now: root.clock.date
|
||||
property int year: now.getFullYear()
|
||||
property int month: now.getMonth()
|
||||
property int firstDay: new Date(year, month, 1).getDay()
|
||||
property int days: new Date(year, month + 1, 0).getDate()
|
||||
|
||||
implicitWidth: 288
|
||||
implicitHeight: 310
|
||||
|
||||
PopupHeader {
|
||||
width: parent.width
|
||||
text: Qt.formatDateTime(parent.now, "dddd, MMMM d")
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.top
|
||||
topMargin: 40
|
||||
}
|
||||
text: Qt.formatDateTime(parent.now, "hh:mm:ss")
|
||||
color: "#ffffff"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 30
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
topMargin: 86
|
||||
}
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: ["S", "M", "T", "W", "T", "F", "S"]
|
||||
Text {
|
||||
required property string modelData
|
||||
width: 37
|
||||
text: modelData
|
||||
color: "#7aa6da"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
topMargin: 112
|
||||
}
|
||||
columns: 7
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: 42
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
property int day: index - clockContent.firstDay + 1
|
||||
property bool today: day === clockContent.now.getDate()
|
||||
|
||||
width: 37
|
||||
height: 29
|
||||
radius: 0
|
||||
color: today ? "#0288d1" : "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: 3
|
||||
visible: parent.today
|
||||
color: "#b3e5fc"
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: parent.day > 0 && parent.day <= clockContent.days
|
||||
? parent.day : ""
|
||||
color: parent.today ? "#ffffff" : "#cfd8dc"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 14
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: audioPage
|
||||
|
||||
Column {
|
||||
width: 288
|
||||
spacing: 12
|
||||
|
||||
PopupHeader {
|
||||
width: parent.width
|
||||
text: "Audio"
|
||||
}
|
||||
|
||||
PopupSlider {
|
||||
width: parent.width
|
||||
label: "Output"
|
||||
value: root.sink && root.sink.audio ? root.sink.audio.volume : 0
|
||||
muted: root.sink && root.sink.audio ? root.sink.audio.muted : false
|
||||
onValueRequested: value => {
|
||||
if (root.sink && root.sink.audio)
|
||||
root.sink.audio.volume = value;
|
||||
}
|
||||
onMuteRequested: {
|
||||
if (root.sink && root.sink.audio)
|
||||
root.sink.audio.muted = !root.sink.audio.muted;
|
||||
}
|
||||
}
|
||||
|
||||
PopupSlider {
|
||||
width: parent.width
|
||||
label: "Microphone"
|
||||
value: root.source && root.source.audio ? root.source.audio.volume : 0
|
||||
muted: root.source && root.source.audio ? root.source.audio.muted : false
|
||||
onValueRequested: value => {
|
||||
if (root.source && root.source.audio)
|
||||
root.source.audio.volume = value;
|
||||
}
|
||||
onMuteRequested: {
|
||||
if (root.source && root.source.audio)
|
||||
root.source.audio.muted = !root.source.audio.muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: networkPage
|
||||
|
||||
Column {
|
||||
width: 288
|
||||
spacing: 10
|
||||
|
||||
PopupHeader {
|
||||
width: parent.width
|
||||
text: "Network"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Connected to " + root.hardware.connection
|
||||
color: "#ffffff"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
Column {
|
||||
id: networks
|
||||
width: parent.width
|
||||
spacing: 5
|
||||
|
||||
Repeater {
|
||||
model: root.hardware.wifiNetworks
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
width: networks.width
|
||||
height: 29
|
||||
radius: 0
|
||||
color: modelData.active ? "#0288d1" : "#80000000"
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: 3
|
||||
visible: parent.modelData.active
|
||||
color: "#b3e5fc"
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: 9
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
width: parent.width - 70
|
||||
elide: Text.ElideRight
|
||||
text: modelData.ssid
|
||||
color: "#ffffff"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors {
|
||||
right: parent.right
|
||||
rightMargin: 9
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
text: modelData.signal + "%"
|
||||
color: "#ffffff"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 30
|
||||
radius: 0
|
||||
color: "#0288d1"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Open network settings"
|
||||
color: "#ffffff"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Quickshell.execDetached(["nm-connection-editor"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: batteryPage
|
||||
|
||||
Column {
|
||||
width: 288
|
||||
spacing: 12
|
||||
|
||||
PopupHeader {
|
||||
width: parent.width
|
||||
text: "Battery"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Math.round(root.battery.percentage * 100) + "% "
|
||||
+ root.batteryState()
|
||||
color: "#ffffff"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 25
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 8
|
||||
radius: 0
|
||||
color: "#40000000"
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * root.battery.percentage
|
||||
height: parent.height
|
||||
radius: 0
|
||||
color: root.battery.percentage <= 0.15 ? "#e91e63" : "#0288d1"
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: UPower.onBattery
|
||||
? "Remaining: " + root.duration(root.battery.timeToEmpty)
|
||||
: "Until full: " + root.duration(root.battery.timeToFull)
|
||||
color: "#b3e5fc"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Rate: " + Math.abs(root.battery.changeRate).toFixed(1) + " W"
|
||||
+ (root.battery.healthSupported
|
||||
? " Health: " + Math.round(root.battery.healthPercentage * 100) + "%"
|
||||
: "")
|
||||
color: "#ffffff"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 14
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,18 @@ Scope {
|
||||
|
||||
property string connection: "Disconnected"
|
||||
property int brightness: -1
|
||||
property var wifiNetworks: []
|
||||
|
||||
function setBrightness(change) {
|
||||
Quickshell.execDetached(["brightnessctl", "set", change]);
|
||||
brightnessRefresh.restart();
|
||||
}
|
||||
|
||||
function refreshWifi() {
|
||||
if (!wifiQuery.running)
|
||||
wifiQuery.running = true;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: networkQuery
|
||||
command: ["nmcli", "-t", "-f", "TYPE,NAME", "connection", "show", "--active"]
|
||||
@@ -76,4 +82,35 @@ Scope {
|
||||
interval: 250
|
||||
onTriggered: brightnessQuery.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: wifiQuery
|
||||
command: ["nmcli", "-t", "-f", "IN-USE,SSID,SIGNAL", "device", "wifi", "list", "--rescan", "auto"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const networks = [];
|
||||
const seen = {};
|
||||
for (let line of text.trim().split("\n")) {
|
||||
const fields = line.split(":");
|
||||
if (fields.length < 3)
|
||||
continue;
|
||||
const active = fields.shift() === "*";
|
||||
const signal = parseInt(fields.pop());
|
||||
const ssid = fields.join(":").replace(/\\:/g, ":");
|
||||
if (!ssid || seen[ssid])
|
||||
continue;
|
||||
seen[ssid] = true;
|
||||
networks.push({
|
||||
"ssid": ssid,
|
||||
"signal": signal,
|
||||
"active": active
|
||||
});
|
||||
if (networks.length >= 8)
|
||||
break;
|
||||
}
|
||||
root.wifiNetworks = networks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,15 @@ Block {
|
||||
|
||||
required property var service
|
||||
property bool connected: service.connection !== "Disconnected"
|
||||
signal detailsRequested(var anchorItem)
|
||||
|
||||
blockColor: connected ? "#2980b9" : "#e91e63"
|
||||
interactive: true
|
||||
text: connected ? "\uf1eb " + service.connection : "Disconnected !"
|
||||
onClicked: Quickshell.execDetached(["nm-connection-editor"])
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton)
|
||||
detailsRequested(root);
|
||||
else
|
||||
Quickshell.execDetached(["nm-connection-editor"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string text: ""
|
||||
|
||||
implicitHeight: 32
|
||||
color: "#0288d1"
|
||||
|
||||
Text {
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: 10
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
text: root.text
|
||||
color: "#ffffff"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string label: ""
|
||||
property real value: 0
|
||||
property bool muted: false
|
||||
signal valueRequested(real value)
|
||||
signal muteRequested()
|
||||
|
||||
implicitWidth: 280
|
||||
implicitHeight: 58
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
text: root.label
|
||||
color: "#ffffff"
|
||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
width: 58
|
||||
height: 24
|
||||
radius: 0
|
||||
color: root.muted ? "#e91e63" : "#0288d1"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.muted ? "MUTED" : Math.round(root.value * 100) + "%"
|
||||
color: "#ffffff"
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.muteRequested()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: track
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: 8
|
||||
radius: 0
|
||||
color: "#40000000"
|
||||
|
||||
Rectangle {
|
||||
width: Math.min(parent.width, parent.width * root.value / 1.5)
|
||||
height: parent.height
|
||||
radius: 0
|
||||
color: root.muted ? "#e91e63" : "#0288d1"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
function updateValue(mouse) {
|
||||
root.valueRequested(Math.max(0,
|
||||
Math.min(1.5, mouse.x / width * 1.5)));
|
||||
}
|
||||
|
||||
onPressed: mouse => updateValue(mouse)
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed)
|
||||
updateValue(mouse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user