mirror of
https://github.com/AquaMorph/dotfiles.git
synced 2026-10-22 04:45:34 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f6473ce17 | ||
|
|
04cf13ae99 | ||
|
|
4fdb6d90d8 | ||
|
|
73f74fe05f | ||
|
|
1a9d6a6a78 | ||
|
|
c31b2486e3 | ||
|
|
56581c54a7 |
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Start the Quickshell bar on the current Wayland session.
|
# Start the systemd-managed Quickshell bar on the current Wayland session.
|
||||||
|
|
||||||
qs -c bar kill 2>/dev/null || true
|
systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE
|
||||||
qs -c bar --daemonize
|
systemctl --user restart quickshell-bar.service
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Block {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var sink
|
||||||
|
required property var source
|
||||||
|
signal detailsRequested(var anchorItem)
|
||||||
|
|
||||||
|
blockColor: sink && sink.audio && sink.audio.muted
|
||||||
|
? Theme.foreground : Theme.primary
|
||||||
|
textColor: sink && sink.audio && sink.audio.muted
|
||||||
|
? Theme.foregroundDark : Theme.foreground
|
||||||
|
interactive: true
|
||||||
|
text: {
|
||||||
|
if (!sink || !sink.audio)
|
||||||
|
return "\uf026";
|
||||||
|
return sink.audio.muted
|
||||||
|
? "\uf026" : "\uf028 " + Math.round(sink.audio.volume * 100) + "%";
|
||||||
|
}
|
||||||
|
secondaryIcon: source && source.audio
|
||||||
|
? (source.audio.muted
|
||||||
|
? "file://" + Quickshell.shellPath("icons/microphone-muted.svg")
|
||||||
|
: "file://" + Quickshell.shellPath("icons/microphone.svg"))
|
||||||
|
: ""
|
||||||
|
secondaryText: source && source.audio
|
||||||
|
? (source.audio.muted ? "OFF"
|
||||||
|
: Math.round(source.audio.volume * 100) + "%")
|
||||||
|
: ""
|
||||||
|
|
||||||
|
onClicked: mouse => {
|
||||||
|
if (mouse.button === Qt.LeftButton)
|
||||||
|
detailsRequested(root);
|
||||||
|
else if (mouse.button === Qt.MiddleButton && sink && sink.audio)
|
||||||
|
sink.audio.muted = !sink.audio.muted;
|
||||||
|
else
|
||||||
|
Quickshell.execDetached(["qjackctl"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onWheel: wheel => {
|
||||||
|
if (!sink || !sink.audio)
|
||||||
|
return;
|
||||||
|
const change = wheel.angleDelta.y > 0 ? 0.05 : -0.05;
|
||||||
|
sink.audio.volume = Math.max(0, Math.min(1.5,
|
||||||
|
sink.audio.volume + change));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +1,16 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
|
|
||||||
Block {
|
Block {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int percentage: -1
|
required property var service
|
||||||
|
|
||||||
visible: percentage >= 0
|
visible: service.brightness >= 0
|
||||||
blockColor: "#2980b9"
|
blockColor: Theme.secondary
|
||||||
interactive: true
|
interactive: true
|
||||||
text: "\uf185 " + percentage + "%"
|
text: "\uf185 " + service.brightness + "%"
|
||||||
onWheel: wheel => {
|
onWheel: wheel => {
|
||||||
const change = wheel.angleDelta.y > 0 ? "+5%" : "5%-";
|
const change = wheel.angleDelta.y > 0 ? "+5%" : "5%-";
|
||||||
Quickshell.execDetached(["brightnessctl", "set", change]);
|
service.setBrightness(change);
|
||||||
refreshTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: brightnessQuery
|
|
||||||
command: ["brightnessctl", "-m"]
|
|
||||||
running: true
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
const fields = text.trim().split(",");
|
|
||||||
root.percentage = fields.length >= 4
|
|
||||||
? parseInt(fields[3].replace("%", ""))
|
|
||||||
: -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: refreshTimer
|
|
||||||
interval: 500
|
|
||||||
repeat: true
|
|
||||||
running: true
|
|
||||||
onTriggered: brightnessQuery.running = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import Quickshell.Services.Pipewire
|
import Quickshell.Services.Pipewire
|
||||||
import Quickshell.Services.SystemTray
|
|
||||||
import Quickshell.Services.UPower
|
import Quickshell.Services.UPower
|
||||||
import Quickshell.Widgets
|
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: bar
|
id: bar
|
||||||
@@ -12,14 +10,15 @@ PanelWindow {
|
|||||||
property var sink: Pipewire.defaultAudioSink
|
property var sink: Pipewire.defaultAudioSink
|
||||||
property var source: Pipewire.defaultAudioSource
|
property var source: Pipewire.defaultAudioSource
|
||||||
property var battery: UPower.displayDevice
|
property var battery: UPower.displayDevice
|
||||||
|
property var hardware
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
left: true
|
left: true
|
||||||
right: true
|
right: true
|
||||||
}
|
}
|
||||||
implicitHeight: 38
|
implicitHeight: Theme.barHeight
|
||||||
color: "#80000000"
|
color: Theme.background
|
||||||
|
|
||||||
PwObjectTracker {
|
PwObjectTracker {
|
||||||
objects: [bar.sink, bar.source]
|
objects: [bar.sink, bar.source]
|
||||||
@@ -30,6 +29,15 @@ PanelWindow {
|
|||||||
precision: SystemClock.Seconds
|
precision: SystemClock.Seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DetailPopup {
|
||||||
|
id: detailsPopup
|
||||||
|
hardware: bar.hardware
|
||||||
|
sink: bar.sink
|
||||||
|
source: bar.source
|
||||||
|
battery: bar.battery
|
||||||
|
clock: clock
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: leftModules
|
id: leftModules
|
||||||
anchors {
|
anchors {
|
||||||
@@ -37,70 +45,23 @@ PanelWindow {
|
|||||||
top: parent.top
|
top: parent.top
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
}
|
}
|
||||||
spacing: 8
|
spacing: Theme.moduleSpacing
|
||||||
|
|
||||||
Row {
|
Workspaces {
|
||||||
height: parent.height
|
height: parent.height
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Hyprland.workspaces
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: workspaceButton
|
|
||||||
|
|
||||||
required property var modelData
|
|
||||||
property bool active: modelData.active
|
|
||||||
|
|
||||||
visible: modelData.id > 0
|
|
||||||
width: 34
|
|
||||||
height: parent.height
|
|
||||||
color: modelData.urgent ? "#e91e63"
|
|
||||||
: active ? "#0288d1" : "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.bottom
|
|
||||||
}
|
|
||||||
height: 3
|
|
||||||
color: workspaceButton.active ? "#b3e5fc" : "transparent"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: modelData.id === 2 ? "\uf120"
|
|
||||||
: modelData.id === 13 ? "\uf001" : modelData.name
|
|
||||||
color: "#ffffff"
|
|
||||||
font.family: "SF Pro Display, Font Awesome 6 Free"
|
|
||||||
font.pixelSize: 18
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: Quickshell.execDetached([
|
|
||||||
"hyprctl",
|
|
||||||
"dispatch",
|
|
||||||
"hl.dsp.focus({ workspace = " + workspaceButton.modelData.id + " })"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: Math.max(0, Math.min(implicitWidth,
|
width: Math.max(0, Math.min(implicitWidth,
|
||||||
parent.width - leftModules.width - rightModules.width - 32))
|
parent.width - leftModules.width - rightModules.width - Theme.blockHeight))
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: Hyprland.activeToplevel ? Hyprland.activeToplevel.title : ""
|
text: Hyprland.activeToplevel ? Hyprland.activeToplevel.title : ""
|
||||||
color: "#ffffff"
|
color: Theme.foreground
|
||||||
font.family: "SF Pro Display, Helvetica, Arial, sans-serif"
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: 18
|
font.pixelSize: Theme.fontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -109,135 +70,32 @@ PanelWindow {
|
|||||||
right: parent.right
|
right: parent.right
|
||||||
top: parent.top
|
top: parent.top
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
rightMargin: 4
|
rightMargin: Theme.moduleSpacing / 2
|
||||||
}
|
}
|
||||||
spacing: 8
|
spacing: Theme.moduleSpacing
|
||||||
|
|
||||||
Block {
|
AudioBlock {
|
||||||
id: audioBlock
|
sink: bar.sink
|
||||||
blockColor: bar.sink && bar.sink.audio && bar.sink.audio.muted
|
source: bar.source
|
||||||
? "#ffffff" : "#0288d1"
|
onDetailsRequested: anchorItem => detailsPopup.toggle("audio", anchorItem)
|
||||||
textColor: bar.sink && bar.sink.audio && bar.sink.audio.muted
|
|
||||||
? "#000000" : "#ffffff"
|
|
||||||
interactive: true
|
|
||||||
text: {
|
|
||||||
if (!bar.sink || !bar.sink.audio)
|
|
||||||
return "\uf026";
|
|
||||||
return bar.sink.audio.muted
|
|
||||||
? "\uf026" : "\uf028 " + Math.round(bar.sink.audio.volume * 100) + "%";
|
|
||||||
}
|
|
||||||
secondaryIcon: bar.source && bar.source.audio
|
|
||||||
? (bar.source.audio.muted
|
|
||||||
? "file://" + Quickshell.shellPath("icons/microphone-muted.svg")
|
|
||||||
: "file://" + Quickshell.shellPath("icons/microphone.svg"))
|
|
||||||
: ""
|
|
||||||
secondaryText: bar.source && bar.source.audio
|
|
||||||
? (bar.source.audio.muted ? "OFF"
|
|
||||||
: Math.round(bar.source.audio.volume * 100) + "%")
|
|
||||||
: ""
|
|
||||||
onClicked: mouse => {
|
|
||||||
if (mouse.button === Qt.MiddleButton && bar.sink && bar.sink.audio)
|
|
||||||
bar.sink.audio.muted = !bar.sink.audio.muted;
|
|
||||||
else
|
|
||||||
Quickshell.execDetached(["qjackctl"]);
|
|
||||||
}
|
|
||||||
onWheel: wheel => {
|
|
||||||
if (!bar.sink || !bar.sink.audio)
|
|
||||||
return;
|
|
||||||
const change = wheel.angleDelta.y > 0 ? 0.05 : -0.05;
|
|
||||||
bar.sink.audio.volume = Math.max(0, Math.min(1.5,
|
|
||||||
bar.sink.audio.volume + change));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Network {}
|
Network {
|
||||||
Backlight {}
|
service: bar.hardware
|
||||||
|
onDetailsRequested: anchorItem => detailsPopup.toggle("network", anchorItem)
|
||||||
Block {
|
|
||||||
property int percentage: Math.round(bar.battery.percentage * 100)
|
|
||||||
|
|
||||||
visible: bar.battery.ready && bar.battery.isLaptopBattery
|
|
||||||
blockColor: percentage <= 15 && UPower.onBattery
|
|
||||||
? "#e91e63" : UPower.onBattery ? "#ffffff" : "#2980b9"
|
|
||||||
textColor: UPower.onBattery && percentage > 15
|
|
||||||
? "#000000" : "#ffffff"
|
|
||||||
text: {
|
|
||||||
if (!UPower.onBattery)
|
|
||||||
return "\uf1e6 " + percentage + "%";
|
|
||||||
if (percentage <= 10) return "\uf244 " + percentage + "%";
|
|
||||||
if (percentage <= 35) return "\uf243 " + percentage + "%";
|
|
||||||
if (percentage <= 60) return "\uf242 " + percentage + "%";
|
|
||||||
if (percentage <= 85) return "\uf241 " + percentage + "%";
|
|
||||||
return "\uf240 " + percentage + "%";
|
|
||||||
}
|
}
|
||||||
|
Backlight { service: bar.hardware }
|
||||||
|
|
||||||
|
BatteryBlock {
|
||||||
|
battery: bar.battery
|
||||||
|
onDetailsRequested: anchorItem => detailsPopup.toggle("battery", anchorItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Tray { barWindow: bar }
|
||||||
visible: trayRepeater.count > 0
|
|
||||||
implicitWidth: trayItems.implicitWidth + 20
|
|
||||||
implicitHeight: 32
|
|
||||||
color: "#0288d1"
|
|
||||||
|
|
||||||
Row {
|
ClockBlock {
|
||||||
id: trayItems
|
clock: clock
|
||||||
anchors.centerIn: parent
|
onDetailsRequested: anchorItem => detailsPopup.toggle("clock", anchorItem)
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: trayRepeater
|
|
||||||
model: SystemTray.items
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: trayItem
|
|
||||||
|
|
||||||
required property var modelData
|
|
||||||
width: 26
|
|
||||||
height: 30
|
|
||||||
|
|
||||||
IconImage {
|
|
||||||
anchors {
|
|
||||||
fill: parent
|
|
||||||
margins: 3
|
|
||||||
}
|
|
||||||
source: trayItem.modelData.icon
|
|
||||||
opacity: trayMouse.containsMouse ? 1 : 0.82
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation { duration: 120 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: trayMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
|
||||||
onClicked: mouse => {
|
|
||||||
if (mouse.button === Qt.MiddleButton)
|
|
||||||
trayItem.modelData.secondaryActivate();
|
|
||||||
else if (mouse.button === Qt.RightButton || trayItem.modelData.onlyMenu) {
|
|
||||||
const point = mapToItem(bar.contentItem, 0, height);
|
|
||||||
trayItem.modelData.display(bar, point.x, point.y);
|
|
||||||
} else {
|
|
||||||
trayItem.modelData.activate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onWheel: wheel => trayItem.modelData.scroll(
|
|
||||||
wheel.angleDelta.y, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Block {
|
|
||||||
property bool detailed: false
|
|
||||||
|
|
||||||
interactive: true
|
|
||||||
text: Qt.formatDateTime(clock.date,
|
|
||||||
detailed ? "yyyy-MM-dd hh:mm:ss" : "hh:mm")
|
|
||||||
onClicked: detailed = !detailed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import Quickshell.Services.UPower
|
||||||
|
|
||||||
|
Block {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var battery
|
||||||
|
property int percentage: Math.round(battery.percentage * 100)
|
||||||
|
signal detailsRequested(var anchorItem)
|
||||||
|
|
||||||
|
visible: battery.ready && battery.isLaptopBattery
|
||||||
|
interactive: true
|
||||||
|
blockColor: percentage <= 15 && UPower.onBattery
|
||||||
|
? Theme.urgent : UPower.onBattery ? Theme.foreground : Theme.secondary
|
||||||
|
textColor: UPower.onBattery && percentage > 15
|
||||||
|
? Theme.foregroundDark : Theme.foreground
|
||||||
|
text: {
|
||||||
|
if (!UPower.onBattery)
|
||||||
|
return "\uf1e6 " + percentage + "%";
|
||||||
|
if (percentage <= 10) return "\uf244 " + percentage + "%";
|
||||||
|
if (percentage <= 35) return "\uf243 " + percentage + "%";
|
||||||
|
if (percentage <= 60) return "\uf242 " + percentage + "%";
|
||||||
|
if (percentage <= 85) return "\uf241 " + percentage + "%";
|
||||||
|
return "\uf240 " + percentage + "%";
|
||||||
|
}
|
||||||
|
onClicked: detailsRequested(root)
|
||||||
|
}
|
||||||
@@ -4,18 +4,19 @@ import Quickshell.Widgets
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property color blockColor: "#0288d1"
|
property color blockColor: Theme.primary
|
||||||
property color textColor: "#ffffff"
|
property color textColor: Theme.foreground
|
||||||
property bool interactive: false
|
property bool interactive: false
|
||||||
property string text: ""
|
property string text: ""
|
||||||
property string fontFamily: "SF Pro Display, Font Awesome 6 Free"
|
property string fontFamily: Theme.iconFontFamily
|
||||||
property string secondaryIcon: ""
|
property string secondaryIcon: ""
|
||||||
property string secondaryText: ""
|
property string secondaryText: ""
|
||||||
signal clicked(var mouse)
|
signal clicked(var mouse)
|
||||||
|
signal doubleClicked(var mouse)
|
||||||
signal wheel(var wheel)
|
signal wheel(var wheel)
|
||||||
|
|
||||||
implicitWidth: content.implicitWidth + 20
|
implicitWidth: content.implicitWidth + Theme.contentPadding * 2
|
||||||
implicitHeight: 32
|
implicitHeight: Theme.blockHeight
|
||||||
color: blockColor
|
color: blockColor
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -27,7 +28,7 @@ Rectangle {
|
|||||||
text: root.text
|
text: root.text
|
||||||
color: root.textColor
|
color: root.textColor
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: 18
|
font.pixelSize: Theme.fontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
@@ -44,7 +45,7 @@ Rectangle {
|
|||||||
text: root.secondaryText
|
text: root.secondaryText
|
||||||
color: root.textColor
|
color: root.textColor
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: 18
|
font.pixelSize: Theme.fontSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ Rectangle {
|
|||||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: mouse => root.clicked(mouse)
|
onClicked: mouse => root.clicked(mouse)
|
||||||
|
onDoubleClicked: mouse => root.doubleClicked(mouse)
|
||||||
onWheel: wheel => root.wheel(wheel)
|
onWheel: wheel => root.wheel(wheel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
Block {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var clock
|
||||||
|
property bool showSeconds: false
|
||||||
|
signal detailsRequested(var anchorItem)
|
||||||
|
|
||||||
|
function formattedTime() {
|
||||||
|
const date = clock.date;
|
||||||
|
const hour = date.getHours() % 12 || 12;
|
||||||
|
const minute = date.getMinutes().toString().padStart(2, "0");
|
||||||
|
const second = date.getSeconds().toString().padStart(2, "0");
|
||||||
|
return hour + ":" + minute + (showSeconds ? ":" + second : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
interactive: true
|
||||||
|
text: formattedTime()
|
||||||
|
onClicked: detailsRequested(root)
|
||||||
|
onDoubleClicked: showSeconds = !showSeconds
|
||||||
|
}
|
||||||
@@ -0,0 +1,386 @@
|
|||||||
|
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: Theme.popupWidth
|
||||||
|
implicitHeight: contentLoader.item
|
||||||
|
? contentLoader.item.implicitHeight + Theme.popupPadding * 2 : 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: Theme.popupBackground
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: contentLoader
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
margins: Theme.popupPadding
|
||||||
|
}
|
||||||
|
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: Theme.activeLineWidth
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: Theme.popupContentWidth
|
||||||
|
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, "h:mm:ss AP")
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontClock
|
||||||
|
}
|
||||||
|
|
||||||
|
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: Theme.muted
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
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 ? Theme.primary : "transparent"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
height: Theme.activeLineWidth
|
||||||
|
visible: parent.today
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: parent.day > 0 && parent.day <= clockContent.days
|
||||||
|
? parent.day : ""
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: audioPage
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: Theme.popupContentWidth
|
||||||
|
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: Theme.popupContentWidth
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
PopupHeader {
|
||||||
|
width: parent.width
|
||||||
|
text: "Network"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Connected to " + root.hardware.connection
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontBody
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ? Theme.primary : Theme.background
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
height: Theme.activeLineWidth
|
||||||
|
visible: parent.modelData.active
|
||||||
|
color: Theme.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: 9
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
width: parent.width - 70
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: modelData.ssid
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSmall
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: 9
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
text: modelData.signal + "%"
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontCaption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 30
|
||||||
|
radius: 0
|
||||||
|
color: Theme.primary
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Open network settings"
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontCaption
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Quickshell.execDetached(["nm-connection-editor"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: batteryPage
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: Theme.popupContentWidth
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
PopupHeader {
|
||||||
|
width: parent.width
|
||||||
|
text: "Battery"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: Math.round(root.battery.percentage * 100) + "% "
|
||||||
|
+ root.batteryState()
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontDisplay
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 8
|
||||||
|
radius: 0
|
||||||
|
color: Theme.track
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width * root.battery.percentage
|
||||||
|
height: parent.height
|
||||||
|
radius: 0
|
||||||
|
color: root.battery.percentage <= 0.15 ? Theme.urgent : Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: UPower.onBattery
|
||||||
|
? "Remaining: " + root.duration(root.battery.timeToEmpty)
|
||||||
|
: "Until full: " + root.duration(root.battery.timeToFull)
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontBody
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Rate: " + Math.abs(root.battery.changeRate).toFixed(1) + " W"
|
||||||
|
+ (root.battery.healthSupported
|
||||||
|
? " Health: " + Math.round(root.battery.healthPercentage * 100) + "%"
|
||||||
|
: "")
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string connection: "Disconnected"
|
||||||
|
property int brightness: -1
|
||||||
|
property var wifiNetworks: []
|
||||||
|
|
||||||
|
function setBrightness(change) {
|
||||||
|
Quickshell.execDetached(["brightnessctl", "set", change]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshNetwork() {
|
||||||
|
if (!networkQuery.running)
|
||||||
|
networkQuery.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshWifi() {
|
||||||
|
if (!wifiQuery.running)
|
||||||
|
wifiQuery.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: networkQuery
|
||||||
|
command: ["nmcli", "-t", "-f", "TYPE,NAME", "connection", "show", "--active"]
|
||||||
|
running: true
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
const lines = text.trim().split("\n");
|
||||||
|
for (let line of lines) {
|
||||||
|
const separator = line.indexOf(":");
|
||||||
|
const type = line.slice(0, separator);
|
||||||
|
if (type === "802-11-wireless" || type === "wifi") {
|
||||||
|
root.connection = line.slice(separator + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let line of lines) {
|
||||||
|
const separator = line.indexOf(":");
|
||||||
|
const type = line.slice(0, separator);
|
||||||
|
if (type === "802-3-ethernet" || type === "ethernet") {
|
||||||
|
root.connection = line.slice(separator + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.connection = "Disconnected";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: networkMonitor
|
||||||
|
command: ["nmcli", "monitor"]
|
||||||
|
running: true
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: {
|
||||||
|
root.refreshNetwork();
|
||||||
|
if (root.wifiNetworks.length > 0)
|
||||||
|
root.refreshWifi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: networkRestart.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: networkRestart
|
||||||
|
interval: 1000
|
||||||
|
onTriggered: networkMonitor.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBrightness() {
|
||||||
|
if (!brightnessFile.loaded || !maximumBrightnessFile.loaded)
|
||||||
|
return;
|
||||||
|
const current = parseInt(brightnessFile.text().trim());
|
||||||
|
const maximum = parseInt(maximumBrightnessFile.text().trim());
|
||||||
|
root.brightness = maximum > 0 && !isNaN(current)
|
||||||
|
? Math.round(current / maximum * 100) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: brightnessFile
|
||||||
|
path: "file:///sys/class/backlight/intel_backlight/brightness"
|
||||||
|
watchChanges: true
|
||||||
|
onFileChanged: reload()
|
||||||
|
onLoaded: root.updateBrightness()
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: maximumBrightnessFile
|
||||||
|
path: "file:///sys/class/backlight/intel_backlight/max_brightness"
|
||||||
|
onLoaded: root.updateBrightness()
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +1,20 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
|
||||||
|
|
||||||
Block {
|
Block {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string connection: "Disconnected"
|
required property var service
|
||||||
property bool connected: connection !== "Disconnected"
|
property bool connected: service.connection !== "Disconnected"
|
||||||
|
signal detailsRequested(var anchorItem)
|
||||||
|
|
||||||
blockColor: connected ? "#2980b9" : "#e91e63"
|
blockColor: connected ? Theme.secondary : Theme.urgent
|
||||||
interactive: true
|
interactive: true
|
||||||
text: connected ? "\uf1eb " + connection : "Disconnected !"
|
text: connected ? "\uf1eb " + service.connection : "Disconnected !"
|
||||||
onClicked: Quickshell.execDetached(["nm-connection-editor"])
|
onClicked: mouse => {
|
||||||
|
if (mouse.button === Qt.LeftButton)
|
||||||
Process {
|
detailsRequested(root);
|
||||||
id: networkQuery
|
else
|
||||||
command: ["nmcli", "-t", "-f", "TYPE,NAME", "connection", "show", "--active"]
|
Quickshell.execDetached(["nm-connection-editor"]);
|
||||||
running: true
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
const lines = text.trim().split("\n");
|
|
||||||
for (let line of lines) {
|
|
||||||
const separator = line.indexOf(":");
|
|
||||||
const type = line.slice(0, separator);
|
|
||||||
if (type === "802-11-wireless" || type === "wifi") {
|
|
||||||
root.connection = line.slice(separator + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let line of lines) {
|
|
||||||
const separator = line.indexOf(":");
|
|
||||||
const type = line.slice(0, separator);
|
|
||||||
if (type === "802-3-ethernet" || type === "ethernet") {
|
|
||||||
root.connection = line.slice(separator + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
root.connection = "Disconnected";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
interval: 5000
|
|
||||||
repeat: true
|
|
||||||
running: true
|
|
||||||
onTriggered: networkQuery.running = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string text: ""
|
||||||
|
|
||||||
|
implicitHeight: Theme.blockHeight
|
||||||
|
color: Theme.primary
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: Theme.contentPadding
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
text: root.text
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
|
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: Theme.foreground
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontBody
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
}
|
||||||
|
width: 58
|
||||||
|
height: 24
|
||||||
|
radius: 0
|
||||||
|
color: root.muted ? Theme.urgent : Theme.primary
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: root.muted ? "MUTED" : Math.round(root.value * 100) + "%"
|
||||||
|
color: Theme.foreground
|
||||||
|
font.pixelSize: Theme.fontTiny
|
||||||
|
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: Theme.track
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.min(parent.width, parent.width * root.value / 1.5)
|
||||||
|
height: parent.height
|
||||||
|
radius: 0
|
||||||
|
color: root.muted ? Theme.urgent : Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
readonly property color background: "#80000000"
|
||||||
|
readonly property color popupBackground: "#e0000000"
|
||||||
|
readonly property color primary: "#0288d1"
|
||||||
|
readonly property color secondary: "#2980b9"
|
||||||
|
readonly property color accent: "#b3e5fc"
|
||||||
|
readonly property color urgent: "#e91e63"
|
||||||
|
readonly property color foreground: "#ffffff"
|
||||||
|
readonly property color foregroundDark: "#000000"
|
||||||
|
readonly property color muted: "#7aa6da"
|
||||||
|
readonly property color track: "#40000000"
|
||||||
|
|
||||||
|
readonly property string fontFamily: "SF Pro Display, Helvetica, Arial, sans-serif"
|
||||||
|
readonly property string iconFontFamily: "SF Pro Display, Font Awesome 6 Free"
|
||||||
|
|
||||||
|
readonly property int barHeight: 38
|
||||||
|
readonly property int blockHeight: 32
|
||||||
|
readonly property int fontSize: 18
|
||||||
|
readonly property int fontTiny: 11
|
||||||
|
readonly property int fontCaption: 13
|
||||||
|
readonly property int fontSmall: 14
|
||||||
|
readonly property int fontBody: 15
|
||||||
|
readonly property int fontDisplay: 25
|
||||||
|
readonly property int fontClock: 30
|
||||||
|
readonly property int moduleSpacing: 8
|
||||||
|
readonly property int contentPadding: 10
|
||||||
|
readonly property int activeLineWidth: 3
|
||||||
|
readonly property int popupWidth: 320
|
||||||
|
readonly property int popupPadding: 12
|
||||||
|
readonly property int popupContentWidth: popupWidth - popupPadding * 2
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell.Services.SystemTray
|
||||||
|
import Quickshell.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var barWindow
|
||||||
|
|
||||||
|
visible: trayRepeater.count > 0
|
||||||
|
implicitWidth: trayItems.implicitWidth + Theme.contentPadding * 2
|
||||||
|
implicitHeight: Theme.blockHeight
|
||||||
|
color: Theme.primary
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: trayItems
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.contentPadding
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: trayRepeater
|
||||||
|
model: SystemTray.items
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: trayItem
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
width: 26
|
||||||
|
height: 30
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
margins: 3
|
||||||
|
}
|
||||||
|
source: trayItem.modelData.icon
|
||||||
|
opacity: trayMouse.containsMouse ? 1 : 0.82
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: 120 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: trayMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||||
|
onClicked: mouse => {
|
||||||
|
if (mouse.button === Qt.MiddleButton) {
|
||||||
|
trayItem.modelData.secondaryActivate();
|
||||||
|
} else if (mouse.button === Qt.RightButton || trayItem.modelData.onlyMenu) {
|
||||||
|
const point = mapToItem(root.barWindow.contentItem, 0, height);
|
||||||
|
trayItem.modelData.display(root.barWindow, point.x, point.y);
|
||||||
|
} else {
|
||||||
|
trayItem.modelData.activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onWheel: wheel => trayItem.modelData.scroll(
|
||||||
|
wheel.angleDelta.y, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Hyprland.workspaces
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: workspaceButton
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
property bool active: modelData.active
|
||||||
|
|
||||||
|
visible: modelData.id > 0
|
||||||
|
width: Theme.blockHeight + 2
|
||||||
|
height: parent.height
|
||||||
|
color: modelData.urgent ? Theme.urgent
|
||||||
|
: active ? Theme.primary : "transparent"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
height: Theme.activeLineWidth
|
||||||
|
color: workspaceButton.active ? Theme.accent : "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: modelData.id === 2 ? "\uf120"
|
||||||
|
: modelData.id === 13 ? "\uf001" : modelData.name
|
||||||
|
color: Theme.foreground
|
||||||
|
font.family: Theme.iconFontFamily
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Quickshell.execDetached([
|
||||||
|
"hyprctl",
|
||||||
|
"dispatch",
|
||||||
|
"hl.dsp.focus({ workspace = " + workspaceButton.modelData.id + " })"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
||||||
ShellRoot {
|
ShellRoot {
|
||||||
|
HardwareService {
|
||||||
|
id: hardwareService
|
||||||
|
}
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
model: Quickshell.screens
|
model: Quickshell.screens
|
||||||
|
|
||||||
Bar {
|
Bar {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
screen: modelData
|
screen: modelData
|
||||||
|
hardware: hardwareService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Quickshell desktop bar
|
||||||
|
Documentation=https://quickshell.org/
|
||||||
|
After=graphical-session.target
|
||||||
|
PartOf=graphical-session.target
|
||||||
|
ConditionEnvironment=WAYLAND_DISPLAY
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/qs -c bar --no-duplicate
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=2
|
||||||
|
TimeoutStopSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical-session.target
|
||||||
Reference in New Issue
Block a user