a BUNCH of qml which I havent reviewed yet

This commit is contained in:
2026-10-23 16:19:22 -06:00
parent 7a57f3a0d2
commit 0d297296ca
16 changed files with 921 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.UPower
// port of the polybar 'i3' bar ($DOTWRYN/config/user/polybar/config.ini)
PanelWindow {
id: bar
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(screen)
readonly property int activeWorkspaceId: monitor?.activeWorkspace?.id ?? 1
readonly property bool activeWorkspaceFullscreen: monitor?.activeWorkspace?.hasFullscreen ?? false
// the bar shows on every workspace except these
readonly property var hiddenWorkspaceIds: [2]
// fullscreen is checked here too: re-mapping the bar (on workspace switch) would
// otherwise draw it over a window that was already fullscreen
visible: !hiddenWorkspaceIds.includes(activeWorkspaceId)
&& !activeWorkspaceFullscreen
anchors {
bottom: true
left: true
right: true
}
implicitHeight: 32
color: Colors.background
TextMetrics {
id: padding
font.family: Colors.font
font.pointSize: Colors.fontSize
text: " "
}
readonly property real separatorWidth: padding.advanceWidth * 2
Row { // modules-left
anchors.left: parent.left
anchors.leftMargin: padding.advanceWidth
anchors.verticalCenter: parent.verticalCenter
height: parent.height
spacing: bar.separatorWidth
Workspaces { height: parent.height; monitor: bar.monitor }
}
Row { // modules-center
anchors.centerIn: parent
height: parent.height
spacing: bar.separatorWidth
Tray { height: parent.height; window: bar }
Repeater {
model: UPower.devices.values.filter(device => device.isLaptopBattery)
Battery { required property var modelData; device: modelData; anchors.verticalCenter: parent.verticalCenter }
}
}
Row { // modules-right
anchors.right: parent.right
anchors.rightMargin: padding.advanceWidth
anchors.verticalCenter: parent.verticalCenter
height: parent.height
spacing: bar.separatorWidth
Disk { anchors.verticalCenter: parent.verticalCenter }
Memory { anchors.verticalCenter: parent.verticalCenter }
Cpu { anchors.verticalCenter: parent.verticalCenter }
Network { anchors.verticalCenter: parent.verticalCenter; interfaceType: "wireless" }
Network { anchors.verticalCenter: parent.verticalCenter; interfaceType: "wired" }
Clock { anchors.verticalCenter: parent.verticalCenter }
}
}
+10
View File
@@ -0,0 +1,10 @@
import QtQuick
// polybar-style text; hidden whenever it has nothing to say (like an empty polybar label)
Text {
color: Colors.foreground
font.family: Colors.font
font.pointSize: Colors.fontSize
font.bold: true
visible: text !== ""
}
+43
View File
@@ -0,0 +1,43 @@
import QtQuick
import Quickshell.Services.UPower
// polybar 'internal/battery' (battery0 / battery1)
Row {
id: battery
required property UPowerDevice device
readonly property int fullAt: 74
readonly property int lowAt: 39
readonly property real percentage: device.percentage <= 1 ? device.percentage * 100 : device.percentage
readonly property bool discharging: device.state === UPowerDeviceState.Discharging
readonly property var ramp: ["", "", "", "", ""]
readonly property int rampIndex: Math.min(ramp.length - 1, Math.floor(percentage / (100 / ramp.length)))
readonly property var rampColors: [
Colors.alert,
Colors.foreground,
Colors.secondaryGradient[2],
Colors.secondaryGradient[3],
Colors.secondaryGradient[4],
]
spacing: 4
visible: device.isPresent
BarLabel {
text: battery.ramp[battery.rampIndex]
color: battery.rampColors[battery.rampIndex]
}
BarLabel { // label-full
text: battery.percentage >= battery.fullAt ? "c:" : ""
color: Colors.secondaryGradient[4]
}
BarLabel { // label-low
text: battery.discharging && battery.percentage <= battery.lowAt ? "⚠" : ""
color: Colors.alert
}
}
+14
View File
@@ -0,0 +1,14 @@
import QtQuick
import Quickshell
// polybar 'datetime' : Monday October 19, 2026 : 09:29:00-am {15:29-UTC}
BarLabel {
SystemClock {
id: clock
precision: SystemClock.Seconds
}
readonly property date utc: new Date(clock.date.getTime() + clock.date.getTimezoneOffset() * 60000)
text: `${Qt.formatDateTime(clock.date, "dddd MMMM dd, yyyy : hh:mm:ss-ap")} {${Qt.formatDateTime(utc, "HH:mm")}-UTC}`
}
+44
View File
@@ -0,0 +1,44 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
// colors.json is generated by 'scwrypts set theme'; watching it means a theme
// change recolors the shell without a restart
Singleton {
readonly property color background: json.background
readonly property color foreground: json.foreground
readonly property color backgroundAlt: json.backgroundAlt
readonly property color foregroundAlt: json.foregroundAlt
readonly property color primary: json.primary
readonly property color secondary: json.secondary
readonly property color alert: json.alert
readonly property color disabled: json.disabled
readonly property var secondaryGradient: json.secondaryGradient
readonly property string font: "Monaspace Krypton"
readonly property real fontSize: 11
FileView {
path: Quickshell.shellPath("colors.json")
watchChanges: true
onFileChanged: reload()
JsonAdapter {
id: json
property string background: "#000505"
property string foreground: "#d0f0f0"
property string backgroundAlt: "#6911aa"
property string foregroundAlt: "#d0f0f0"
property string primary: "#6911aa"
property string secondary: "#11bb98"
property string alert: "#c80064"
property string disabled: "#424253"
property var secondaryGradient: ["#006942", "#009949", "#00aa79", "#11bb98", "#22ccba"]
}
}
}
+36
View File
@@ -0,0 +1,36 @@
import QtQuick
// polybar 'internal/cpu'; silent until cpu usage is high
BarLabel {
id: cpu
property int warnPercentage: 79
property real lastIdle: 0
property real lastTotal: 0
color: Colors.alert
text: ""
PollingCommand {
interval: 3000
command: ["head", "-n1", "/proc/stat"]
onOutput: output => {
const fields = output.trim().split(/\s+/).slice(1).map(Number)
const idle = fields[3] + fields[4] // idle + iowait
const total = fields.reduce((sum, value) => sum + value, 0)
const deltaTotal = total - cpu.lastTotal
const used = cpu.lastTotal > 0 && deltaTotal > 0
? Math.round((1 - (idle - cpu.lastIdle) / deltaTotal) * 100)
: 0
cpu.lastIdle = idle
cpu.lastTotal = total
cpu.text = used >= cpu.warnPercentage
? `CPU${String(used).padStart(2)}%`
: ""
}
}
}
+23
View File
@@ -0,0 +1,23 @@
import QtQuick
// polybar 'internal/fs'; silent until the disk is nearly full
BarLabel {
id: disk
property int warnPercentage: 83
color: Colors.alert
text: ""
PollingCommand {
interval: 17000
command: ["df", "--human-readable", "--output=pcent,avail", "/"]
onOutput: output => {
const [percentage, free] = output.trim().split("\n").pop().trim().split(/\s+/)
const used = parseInt(percentage)
disk.text = used >= disk.warnPercentage
? `${free} remaining on disk (${used}% full)`
: ""
}
}
}
+81
View File
@@ -0,0 +1,81 @@
import QtQuick
import Quickshell
import Quickshell.Io
// i3-nagbar replacement; shown with 'qs ipc call logout show'
Scope {
id: prompt
property bool shown: false
IpcHandler {
target: "logout"
function show(): void { prompt.shown = true }
function hide(): void { prompt.shown = false }
}
PanelWindow {
visible: prompt.shown
anchors {
top: true
left: true
right: true
}
implicitHeight: 32
color: Colors.alert
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Do you really want to exit Hyprland?"
color: Colors.foreground
font.family: Colors.font
font.pointSize: Colors.fontSize
font.bold: true
}
Row {
anchors.right: parent.right
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
spacing: 8
Repeater {
model: [
{ label: "Yes", action: () => Quickshell.execDetached(["sh", "-c", "notify-send 'system' 'exiting hyprland...' -i face-tired; hyprctl dispatch 'hl.dsp.exit()'"]) },
{ label: "X", action: () => prompt.shown = false },
]
Rectangle {
required property var modelData
implicitWidth: buttonLabel.implicitWidth + 20
implicitHeight: 24
color: Colors.background
border.width: 1
border.color: Colors.foreground
Text {
id: buttonLabel
anchors.centerIn: parent
text: parent.modelData.label
color: Colors.foreground
font.family: Colors.font
font.pointSize: Colors.fontSize
font.bold: true
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: parent.modelData.action()
}
}
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
import QtQuick
// polybar 'internal/memory'; silent until memory usage is high
BarLabel {
id: memory
property int warnPercentage: 73
color: Colors.alert
text: ""
PollingCommand {
interval: 2000
command: ["cat", "/proc/meminfo"]
onOutput: output => {
const total = parseInt(output.match(/^MemTotal:\s+(\d+)/m)[1])
const available = parseInt(output.match(/^MemAvailable:\s+(\d+)/m)[1])
const used = Math.round((total - available) * 100 / total)
memory.text = used >= memory.warnPercentage
? `RAM${String(used).padStart(2)}%`
: ""
}
}
}
+39
View File
@@ -0,0 +1,39 @@
import QtQuick
// polybar 'internal/network' (wlan / eth); shows the first physical interface of the given type
BarLabel {
id: network
required property string interfaceType // "wireless" | "wired"
text: ""
PollingCommand {
interval: 31000
command: ["sh", "-c", `
for path in /sys/class/net/*
do
[ -e "$path/device" ] || continue
[ -d "$path/wireless" ] && type=wireless || type=wired
[ "$type" = "${network.interfaceType}" ] || continue
ifname="$(basename "$path")"
ip="$(ip -4 -o addr show dev "$ifname" | awk '{print $4}' | cut -d/ -f1 | head -n1)"
echo "$ifname $ip"
break
done
`]
onOutput: output => {
const [ifname, ip] = output.trim().split(/\s+/)
if (!ifname) {
network.text = ""
} else if (ip) {
network.text = `${ifname}/${ip}`
network.color = Colors.secondary
} else {
network.text = `(${ifname}:⊘)`
network.color = Colors.disabled
}
}
}
}
+145
View File
@@ -0,0 +1,145 @@
import QtQuick
import Quickshell
import Quickshell.Services.Notifications
// notification daemon (replaces xfce4-notifyd for the hyprland session)
Scope {
id: notifications
readonly property int defaultTimeout: 5000
NotificationServer {
id: server
keepOnReload: true
bodySupported: true
bodyMarkupSupported: true
actionsSupported: true
imageSupported: true
onNotification: notification => notification.tracked = true
}
PanelWindow {
visible: server.trackedNotifications.values.length > 0
anchors {
top: true
right: true
}
margins {
top: 8
right: 8
}
exclusiveZone: 0
implicitWidth: 380
implicitHeight: stack.implicitHeight
color: "transparent"
Column {
id: stack
width: parent.width
spacing: 8
Repeater {
model: server.trackedNotifications
Rectangle {
id: card
required property Notification modelData
readonly property bool critical: modelData.urgency === NotificationUrgency.Critical
width: stack.width
implicitHeight: content.implicitHeight + 20
color: Colors.background
border.width: 2
border.color: critical ? Colors.alert : Colors.secondary
Timer {
running: !card.critical && card.modelData.expireTimeout !== 0
interval: card.modelData.expireTimeout > 0
? card.modelData.expireTimeout * 1000
: notifications.defaultTimeout
onTriggered: card.modelData.expire()
}
Column {
id: content
anchors {
left: parent.left
right: parent.right
top: parent.top
margins: 10
}
spacing: 4
Text {
width: parent.width
text: card.modelData.summary
color: card.critical ? Colors.alert : Colors.foreground
font.family: Colors.font
font.pointSize: Colors.fontSize
font.bold: true
elide: Text.ElideRight
}
Text {
width: parent.width
visible: text !== ""
text: card.modelData.body
textFormat: Text.StyledText
color: Colors.foreground
font.family: Colors.font
font.pointSize: Colors.fontSize - 1
wrapMode: Text.Wrap
maximumLineCount: 6
elide: Text.ElideRight
}
Row {
visible: card.modelData.actions.length > 0
spacing: 8
Repeater {
model: card.modelData.actions
Rectangle {
required property NotificationAction modelData
implicitWidth: actionLabel.implicitWidth + 16
implicitHeight: actionLabel.implicitHeight + 8
color: Colors.backgroundAlt
Text {
id: actionLabel
anchors.centerIn: parent
text: parent.modelData.text
color: Colors.foregroundAlt
font.family: Colors.font
font.pointSize: Colors.fontSize - 1
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: parent.modelData.invoke()
}
}
}
}
}
MouseArea { // click anywhere else on the card to dismiss
anchors.fill: parent
z: -1
onClicked: card.modelData.dismiss()
}
}
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
import QtQuick
import Quickshell.Io
// runs a command every 'interval' ms and emits its stdout (polybar module 'interval')
Item {
id: polling
required property var command
property int interval: 1000
signal output(string text)
Process {
id: process
command: polling.command
stdout: StdioCollector {
onStreamFinished: polling.output(this.text)
}
}
Timer {
interval: polling.interval
running: true
repeat: true
triggeredOnStart: true
onTriggered: process.running = true
}
}
+51
View File
@@ -0,0 +1,51 @@
import QtQuick
import Quickshell
import Quickshell.Services.SystemTray
import Quickshell.Widgets
// polybar 'internal/tray'
Row {
id: tray
required property var window
spacing: 8
leftPadding: 8
rightPadding: 8
visible: SystemTray.items.values.length > 0
Repeater {
model: SystemTray.items
IconImage {
id: icon
required property SystemTrayItem modelData
anchors.verticalCenter: parent.verticalCenter
implicitSize: 18
source: modelData.icon
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
cursorShape: Qt.PointingHandCursor
onClicked: mouse => {
const item = icon.modelData
const showMenu = mouse.button === Qt.RightButton || item.onlyMenu
if (showMenu && item.hasMenu) {
const position = icon.mapToItem(null, 0, 0)
item.display(tray.window, position.x, position.y - 4)
} else if (mouse.button === Qt.MiddleButton) {
item.secondaryActivate()
} else {
item.activate()
}
}
onWheel: wheel => icon.modelData.scroll(wheel.angleDelta.y, false)
}
}
}
}
+234
View File
@@ -0,0 +1,234 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pipewire
Scope {
id: osd
readonly property PwNode sink: Pipewire.defaultAudioSink
readonly property PwNode source: Pipewire.defaultAudioSource
property string label: ""
property real level: 0
property bool muted: false
property bool shown: false
property string warning: ""
// going past 100% : at 100%, it takes 'unlockPresses' separate presses of
// volume-up (the first shows a warning); above 100% there is no limit
readonly property real step: 0.10
readonly property int unlockPresses: 3
readonly property int repeatGuardMs: 150 // key-repeat events don't count toward unlocking
property int pressesAtMax: 0
property real lastUpPress: 0
IpcHandler {
target: "volume"
function up(): void { osd.volumeUp() }
}
Timer {
id: unlockReset
interval: 3000
onTriggered: osd.pressesAtMax = 0
}
function volumeUp(): void {
const audio = sink?.audio
if (!audio) return
const now = Date.now()
const isRepeat = now - lastUpPress < repeatGuardMs
lastUpPress = now
const percent = Math.round(audio.volume * 100)
if (percent < 100) { // normal range; stop exactly at 100%
pressesAtMax = 0
audio.volume = Math.min(1.0, audio.volume + step)
return
}
if (percent > 100) { // already past 100% : no limit
audio.volume = audio.volume + step
return
}
// exactly 100%
if (!isRepeat) {
pressesAtMax += 1
unlockReset.restart()
}
if (pressesAtMax >= unlockPresses) {
pressesAtMax = 0
audio.volume = audio.volume + step
return
}
if (isRepeat && shown) { // holding the key at 100% : just keep the warning up
hideTimer.restart()
return
}
const remaining = unlockPresses - pressesAtMax
show("volume", sink, "volume-max", `press ${remaining} more to go past 100%`)
}
// sound effects : names from $DOTWRYN/config/sfx.map, files in DESKTOP__SFX_PATH
property string sfxPath: ""
property var sfxFiles: ({})
// default devices must be tracked for their volume / mute to update
PwObjectTracker { objects: [osd.sink, osd.source] }
Process { // DESKTOP__SFX_PATH lives in the scwrypts environment; read it once
running: true
command: ["scwrypts", "--group", "scwrypts", "--type", "zsh", "--name", "scwrypts/environment/getenv", "--", "DESKTOP__SFX_PATH"]
environment: ({ SCWRYPTS_LOG_LEVEL: "0" })
stdout: StdioCollector {
onStreamFinished: osd.sfxPath = this.text.trim().replace(/^\$HOME|^~/, Quickshell.env("HOME"))
}
}
FileView {
path: Quickshell.env("DOTWRYN") + "/config/sfx.map"
watchChanges: true
onFileChanged: reload()
onLoaded: {
const files = {}
for (const line of text().split("\n")) {
const [name, file] = line.trim().split(/\s+/)
if (name && file && !name.startsWith("#")) files[name] = file
}
osd.sfxFiles = files
}
}
function playSfx(name: string): void {
if (sfxPath === "" || !sfxFiles[name]) return
Quickshell.execDetached(["canberra-gtk-play", "-f", `${sfxPath}/${sfxFiles[name]}`])
}
function show(label: string, node: PwNode, sfx: string, warning: string): void {
if (!node?.audio) return
osd.warning = warning ?? ""
osd.label = label
osd.level = node.audio.volume
osd.muted = node.audio.muted
osd.shown = true
hideTimer.restart()
playSfx(sfx)
}
// ignore the initial values reported while the devices bind at startup
property bool armed: false
Timer { running: true; interval: 1500; onTriggered: osd.armed = true }
Connections {
target: osd.sink?.audio ?? null
ignoreUnknownSignals: true
function onVolumeChanged() { if (osd.armed) osd.show("volume", osd.sink, "volume", "") }
function onMutedChanged() { if (osd.armed) osd.show("volume", osd.sink, "mute", "") }
}
Connections {
target: osd.source?.audio ?? null
ignoreUnknownSignals: true
function onVolumeChanged() { if (osd.armed) osd.show("mic", osd.source, "volume", "") }
function onMutedChanged() { if (osd.armed) osd.show("mic", osd.source, "mute", "") }
}
Timer {
id: hideTimer
interval: 1500
onTriggered: osd.shown = false
}
PanelWindow {
visible: osd.shown
anchors.bottom: true
margins.bottom: 120
exclusiveZone: 0
readonly property bool boosted: osd.level > 1.005
implicitWidth: 400
implicitHeight: osd.warning !== "" ? 84 : 56
color: "transparent"
Rectangle {
anchors.fill: parent
color: Colors.background
border.width: 2
border.color: osd.muted || parent.boosted || osd.warning !== "" ? Colors.alert : Colors.secondary
TextMetrics {
id: percentSlot
font.family: Colors.font
font.pointSize: Colors.fontSize
font.bold: true
text: "1000%"
}
Text { // past-100% warning; sits above the row so the row never moves
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: row.top
anchors.bottomMargin: 8
visible: osd.warning !== ""
text: osd.warning
color: Colors.alert
font.family: Colors.font
font.pointSize: Colors.fontSize - 1
font.bold: true
}
Row {
id: row
// pinned to the bottom edge : the popup is anchored to the screen
// bottom, so it grows upward for the warning and the bar stays put
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 18
spacing: 14
Text {
anchors.verticalCenter: parent.verticalCenter
width: 64
text: osd.muted ? `${osd.label} \u2298` : osd.label
color: osd.muted ? Colors.alert : Colors.foreground
font.family: Colors.font
font.pointSize: Colors.fontSize
font.bold: true
}
Rectangle { // level bar
anchors.verticalCenter: parent.verticalCenter
width: 200
height: 10
color: Colors.disabled
Rectangle {
width: parent.width * Math.min(osd.level, 1)
height: parent.height
color: osd.muted || osd.level > 1.005 ? Colors.alert : Colors.secondary
}
}
Text {
anchors.verticalCenter: parent.verticalCenter
width: percentSlot.advanceWidth
horizontalAlignment: Text.AlignRight
text: `${Math.round(osd.level * 100)}%`
color: osd.level > 1.005 ? Colors.alert : Colors.foreground
font.family: Colors.font
font.pointSize: Colors.fontSize
font.bold: true
}
}
}
}
}
+51
View File
@@ -0,0 +1,51 @@
import QtQuick
import Quickshell
import Quickshell.Hyprland
// polybar 'internal/xworkspaces'
Row {
id: workspaces
required property HyprlandMonitor monitor
// numbered workspaces first (🎮 is 100), then named in creation order; specials are hidden
readonly property var visibleWorkspaces: Hyprland.workspaces.values
.filter(workspace => !workspace.name.startsWith("special"))
.sort((a, b) => {
if ((a.id > 0) !== (b.id > 0)) return a.id > 0 ? -1 : 1
return a.id > 0 ? a.id - b.id : b.id - a.id
})
Repeater {
model: workspaces.visibleWorkspaces
Rectangle {
id: workspace
required property HyprlandWorkspace modelData
readonly property bool isActive: modelData.id === workspaces.monitor?.activeWorkspace?.id
height: workspaces.height
width: label.implicitWidth + label.font.pixelSize * 1.2
color: modelData.urgent ? Colors.alert
: isActive ? Colors.backgroundAlt
: "transparent"
BarLabel {
id: label
anchors.centerIn: parent
text: workspace.modelData.name
color: workspace.isActive ? Colors.foregroundAlt : Colors.foreground
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
// through hyprland.lua so the slide direction matches the keybinds
onClicked: Quickshell.execDetached(["hyprctl", "eval", `dotwryn_focus_workspace("${
workspace.modelData.id > 0 ? workspace.modelData.id : "name:" + workspace.modelData.name
}")`])
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
//@ pragma UseQApplication
import QtQuick
import Quickshell
// hyprland shell; a port of the polybar / xfce4-notifyd / i3-nagbar setup (+ volume popup)
ShellRoot {
Variants {
model: Quickshell.screens
Bar {
required property var modelData
screen: modelData
}
}
Notifications {}
VolumeOsd {}
LogoutPrompt {}
}