Files
neoromantique-dotfiles/home/private_dot_config/quickshell/shell.qml.tmpl
2026-02-15 00:54:03 +01:00

2672 lines
89 KiB
Cheetah
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Services.SystemTray
import Quickshell.Widgets
ShellRoot {
id: root
property color bgColor: "#0f0f0f"
property color fgColor: "#e0e0e0"
property color dimColor: "#888888"
property color accentColor: "#e67e22"
property color okColor: "#2ecc71"
property color warnColor: "#f1c40f"
property color critColor: "#e74c3c"
property color borderColor: "#333333"
Process {
id: shellExec
running: false
}
function runShell(cmd) {
shellExec.command = ["/usr/bin/env", "bash", "-lc", cmd]
shellExec.running = true
}
function trim(s) {
return (s || "").toString().trim()
}
property var wsNames: ({"1": "WEB", "2": "CODE", "3": "TERM", "4": "IDE", "5": "VM", "6": "GFX", "7": "DOC", "8": "GAME", "9": "MISC", "10": "TMP", "11": "AUX1", "12": "AUX2", "13": "AUX3", "14": "AUX4", "15": "AUX5", "special:termius": "s:term", "special:org": "s:org", "special:llm": "s:llm"})
function setWsLabel(wsId, label) {
var n = Object.assign({}, wsNames)
n[wsId] = label
wsNames = n
wsNamesSaver.command = ["/usr/bin/env", "bash", "-c", "echo '" + JSON.stringify(n) + "' > ~/.config/quickshell/ws-names.json"]
wsNamesSaver.running = true
}
Process {
id: wsNamesLoader
command: ["/usr/bin/env", "bash", "-c", "cat ~/.config/quickshell/ws-names.json 2>/dev/null || echo '{}'"]
running: true
stdout: StdioCollector {
onStreamFinished: {
try {
var loaded = JSON.parse((this.text || "{}").trim())
root.wsNames = Object.assign({}, root.wsNames, loaded)
} catch(e) {}
}
}
}
Process {
id: wsNamesSaver
running: false
}
function wsIgnored(name) {
return name === "chrome-sharing-indicator" || name === "special:chrome-sharing-indicator"
}
component ModuleBox: Rectangle {
color: "#1a1a1a"
border.width: 1
border.color: root.borderColor
radius: 0
implicitHeight: 22
}
Variants {
model: Quickshell.screens
delegate: PanelWindow {
id: panel
required property var modelData
screen: modelData
color: "transparent"
exclusionMode: ExclusionMode.Auto
exclusiveZone: 34
aboveWindows: true
anchors {
bottom: true
left: true
right: true
}
implicitHeight: 34
property var hyprMonitor: Hyprland.monitorFor(modelData)
visible: {{- if eq .deviceProfile "desktop" }}hyprMonitor && hyprMonitor.name === "{{ .primaryMonitor }}"{{- else }}true{{- end }}
Rectangle {
anchors.fill: parent
anchors.margins: 4
color: root.bgColor
border.width: 1
border.color: root.borderColor
Row {
id: mainRow
anchors.fill: parent
anchors.leftMargin: 6
anchors.rightMargin: 6
spacing: 0
Row {
id: leftGroup
anchors.verticalCenter: parent.verticalCenter
spacing: 6
ModuleBox {
id: workspacesBox
implicitWidth: workspacesRow.implicitWidth + 12
Row {
id: workspacesRow
anchors.centerIn: parent
spacing: 6
Repeater {
model: Hyprland.workspaces
delegate: Rectangle {
required property var modelData
property bool ignored: root.wsIgnored(modelData.name)
visible: !ignored
color: modelData.urgent
? root.critColor
: (modelData.active ? root.accentColor : "transparent")
border.width: 1
border.color: modelData.urgent ? root.critColor : root.borderColor
radius: 0
implicitHeight: 18
implicitWidth: wsText.implicitWidth + 10
Text {
id: wsText
anchors.centerIn: parent
text: root.wsNames[modelData.name] || "WS"
color: modelData.active || modelData.urgent ? "#111111" : root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton) {
wsRenamePopup.wsId = modelData.name
wsRenamePopup.renameX = mapToItem(null, 0, 0).x
wsRenamePopup.visible = true
wsRenameInput.text = root.wsNames[modelData.name] || ""
wsRenameInput.selectAll()
wsRenameInput.forceActiveFocus()
} else {
modelData.activate()
}
}
}
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) {
Hyprland.dispatch("workspace e+1")
} else if (wheel.angleDelta.y < 0) {
Hyprland.dispatch("workspace e-1")
}
}
}
}
}
Item {
width: Math.max(0, panel.width - leftGroup.implicitWidth - rightGroup.implicitWidth - 12)
height: 1
}
Row {
id: rightGroup
anchors.verticalCenter: parent.verticalCenter
spacing: 6
ModuleBox {
id: vpnBox
implicitWidth: vpnText.implicitWidth + 12
property string vpnClass: "disconnected"
property string wgRaw: ""
property string nbRaw: ""
property string tsRaw: ""
property int vpnPending: 3
function updateVpnStatus() {
var parts = []
var hasTunnel = false
// Parse WireGuard interfaces (exclude NetBird wt*)
var wgLines = wgRaw.split("\n")
for (var i = 0; i < wgLines.length; i++) {
var m = wgLines[i].match(/^\d+:\s+([^:@\s]+)/)
if (m && !m[1].startsWith("wt")) {
parts.push("WG:" + m[1])
hasTunnel = true
}
}
// Parse NetBird
try {
var nb = JSON.parse(nbRaw)
if (nb.management && nb.management.connected) {
var fqdn = nb.fqdn || ""
var segs = fqdn.split(".")
var net = segs.length >= 2 ? segs[segs.length - 2] : "nb"
var conn = (nb.peers && nb.peers.connected) || 0
var tot = (nb.peers && nb.peers.total) || 0
parts.push("NB:" + net + "(" + conn + "/" + tot + ")")
}
} catch(e) {}
// Parse Tailscale
try {
var ts = JSON.parse(tsRaw)
if (ts.BackendState === "Running") {
var tailnet = (ts.CurrentTailnet && ts.CurrentTailnet.Name) || ""
var shortNet = tailnet.replace(/\.ts\.net$/, "").replace(/\.tail.*$/, "")
var exitId = (ts.ExitNodeStatus && ts.ExitNodeStatus.ID) || ""
var exitOnline = (ts.ExitNodeStatus && ts.ExitNodeStatus.Online) || false
if (exitId && exitOnline) {
var exitHost = "exit"
if (ts.Peer) {
var keys = Object.keys(ts.Peer)
for (var j = 0; j < keys.length; j++) {
if (ts.Peer[keys[j]].ExitNode) {
exitHost = ts.Peer[keys[j]].HostName || "exit"
break
}
}
}
parts.push("TS:" + shortNet + "→" + exitHost)
hasTunnel = true
} else {
parts.push("TS:" + shortNet)
}
}
} catch(e) {}
if (parts.length > 0) {
vpnText.text = parts.join(" | ")
vpnClass = hasTunnel ? "connected" : "connected-no-tunnel"
} else {
vpnText.text = "VPN off"
vpnClass = "disconnected"
}
}
Text {
id: vpnText
anchors.centerIn: parent
text: "VPN off"
color: vpnBox.vpnClass === "connected"
? root.accentColor
: (vpnBox.vpnClass === "disconnected" ? root.dimColor : root.fgColor)
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Process {
id: wgProc
command: ["/usr/bin/env", "bash", "-c", "ip link show type wireguard 2>/dev/null || true"]
running: true
stdout: StdioCollector {
onStreamFinished: {
vpnBox.wgRaw = (this.text || "").trim()
vpnBox.vpnPending--
if (vpnBox.vpnPending <= 0) vpnBox.updateVpnStatus()
}
}
}
Process {
id: nbProc
command: ["/usr/bin/env", "bash", "-c", "command -v netbird &>/dev/null && netbird status --json 2>/dev/null || echo '{}'"]
running: true
stdout: StdioCollector {
onStreamFinished: {
vpnBox.nbRaw = (this.text || "").trim()
vpnBox.vpnPending--
if (vpnBox.vpnPending <= 0) vpnBox.updateVpnStatus()
}
}
}
Process {
id: tsProc
command: ["/usr/bin/env", "bash", "-c", "command -v tailscale &>/dev/null && tailscale status --json 2>/dev/null || echo '{}'"]
running: true
stdout: StdioCollector {
onStreamFinished: {
vpnBox.tsRaw = (this.text || "").trim()
vpnBox.vpnPending--
if (vpnBox.vpnPending <= 0) vpnBox.updateVpnStatus()
}
}
}
Timer {
interval: 5000
running: true
repeat: true
onTriggered: {
vpnBox.vpnPending = 3
wgProc.running = true
nbProc.running = true
tsProc.running = true
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
vpnPopup.visible = !vpnPopup.visible
if (vpnPopup.visible) vpnPopup.refresh()
}
}
}
ModuleBox {
id: spkBox
implicitWidth: spkText.implicitWidth + 12
Text {
id: spkText
anchors.centerIn: parent
text: "VOL --"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Process {
id: spkProc
command: ["/usr/bin/env", "bash", "-lc", "mute=$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | awk '{print $2}'); vol=$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | awk 'NR==1{print $5}'); if [ \"${mute}\" = \"yes\" ]; then echo 'VOL muted'; elif [ -n \"${vol}\" ]; then echo \"VOL ${vol}\"; else echo 'VOL --'; fi"]
running: true
stdout: StdioCollector { onStreamFinished: spkText.text = root.trim(this.text) || "VOL --" }
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton) {
root.runShell("pactl set-sink-mute @DEFAULT_SINK@ toggle")
} else {
sinkPopup.visible = !sinkPopup.visible
if (sinkPopup.visible) sinkPopup.refresh()
}
}
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) {
root.runShell("pactl set-sink-volume @DEFAULT_SINK@ +2%")
} else if (wheel.angleDelta.y < 0) {
root.runShell("pactl set-sink-volume @DEFAULT_SINK@ -2%")
}
}
}
}
ModuleBox {
id: micBox
implicitWidth: micText.implicitWidth + 12
Text {
id: micText
anchors.centerIn: parent
text: "MIC --"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Process {
id: micProc
command: ["/usr/bin/env", "bash", "-lc", "mute=$(pactl get-source-mute @DEFAULT_SOURCE@ 2>/dev/null | awk '{print $2}'); vol=$(pactl get-source-volume @DEFAULT_SOURCE@ 2>/dev/null | awk 'NR==1{print $5}'); if [ \"${mute}\" = \"yes\" ]; then echo 'MIC muted'; elif [ -n \"${vol}\" ]; then echo \"MIC ${vol}\"; else echo 'MIC --'; fi"]
running: true
stdout: StdioCollector { onStreamFinished: micText.text = root.trim(this.text) || "MIC --" }
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton) {
root.runShell("pactl set-source-mute @DEFAULT_SOURCE@ toggle")
} else {
sourcePopup.visible = !sourcePopup.visible
if (sourcePopup.visible) sourcePopup.refresh()
}
}
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) {
root.runShell("pactl set-source-volume @DEFAULT_SOURCE@ +2%")
} else if (wheel.angleDelta.y < 0) {
root.runShell("pactl set-source-volume @DEFAULT_SOURCE@ -2%")
}
}
}
}
ModuleBox {
id: netBox
implicitWidth: netText.implicitWidth + 12
Text {
id: netText
anchors.centerIn: parent
text: "NET down"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Process {
id: netProc
command: ["/usr/bin/env", "bash", "-lc", "wifi=$(iwgetid -r 2>/dev/null || true); if [ -n \"$wifi\" ]; then sig=$(awk 'NR==3{gsub(/\./,\"\",$3); q=$3+0; printf \"%d\", int((q/70)*100)}' /proc/net/wireless 2>/dev/null); [ -z \"$sig\" ] && sig=0; echo \"NET wifi $wifi ${sig}%\"; exit; fi; iface=$(ip route | awk '/^default/{print $5; exit}'); if [ -n \"$iface\" ]; then ip4=$(ip -4 addr show dev \"$iface\" | awk '/inet /{print $2; exit}' | cut -d/ -f1); if [ -n \"$ip4\" ]; then echo \"NET eth $ip4\"; else echo 'NET link'; fi; else echo 'NET down'; fi"]
running: true
stdout: StdioCollector { onStreamFinished: netText.text = root.trim(this.text) || "NET down" }
}
Timer {
interval: 5000
running: true
repeat: true
onTriggered: netProc.running = true
}
}
{{- if .hasBattery }}
ModuleBox {
id: batteryBox
implicitWidth: batteryText.implicitWidth + 12
Text {
id: batteryText
anchors.centerIn: parent
text: "BAT --"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Process {
id: batteryProc
command: ["/usr/bin/env", "bash", "-lc", "cap=$(cat /sys/class/power_supply/BAT*/capacity 2>/dev/null | head -n1); stat=$(cat /sys/class/power_supply/BAT*/status 2>/dev/null | head -n1); if [ -z \"$cap\" ]; then echo 'BAT --'; exit; fi; if [ \"$stat\" = 'Charging' ]; then echo \"BAT+ ${cap}%\"; elif [ \"$stat\" = 'Full' ]; then echo 'BAT full'; elif [ \"$stat\" = 'Not charging' ] || [ \"$stat\" = 'Unknown' ]; then echo \"BAT= ${cap}%\"; else echo \"BAT ${cap}%\"; fi"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const v = root.trim(this.text)
batteryText.text = v || "BAT --"
const m = v.match(/(\d+)%/)
if (v.startsWith("BAT+")) {
batteryText.color = root.okColor
} else if (m && Number(m[1]) <= 15) {
batteryText.color = root.critColor
} else if (m && Number(m[1]) <= 30) {
batteryText.color = root.warnColor
} else {
batteryText.color = root.fgColor
}
}
}
}
Timer {
interval: 10000
running: true
repeat: true
onTriggered: batteryProc.running = true
}
}
ModuleBox {
id: brtBox
implicitWidth: brtText.implicitWidth + 12
Text {
id: brtText
anchors.centerIn: parent
text: "BRT --"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Process {
id: brtProc
command: ["/usr/bin/env", "bash", "-lc", "pct=$(brightnessctl -m 2>/dev/null | awk -F, '{print $4}' | tr -d '%'); if [ -n \"$pct\" ]; then echo \"BRT ${pct}%\"; else echo 'BRT --'; fi"]
running: true
stdout: StdioCollector { onStreamFinished: brtText.text = root.trim(this.text) || "BRT --" }
}
Timer {
interval: 5000
running: true
repeat: true
onTriggered: brtProc.running = true
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) {
root.runShell("brightnessctl s 5%+")
} else if (wheel.angleDelta.y < 0) {
root.runShell("brightnessctl s 5%-")
}
brtProc.running = true
}
}
}
{{- end }}
ModuleBox {
id: pomBox
implicitWidth: pomText.implicitWidth + 12
property string pomState: "idle"
property int pomSeconds: 0
property int pomCount: 0
property int totalPoms: 0
property int totalFocusMins: 0
property bool pomBlink: false
property int workMins: 25
property int breakMins: 5
property int longBreakMins: 15
function fmt(s) {
var m = Math.floor(s / 60)
var ss = s % 60
return (m < 10 ? "0" : "") + m + ":" + (ss < 10 ? "0" : "") + ss
}
function update() {
if (pomState === "idle") {
pomText.text = "POM off"
pomText.color = root.dimColor
return
}
var c = pomState === "work" ? " " + (pomCount + 1) + "/4" : pomCount > 0 ? " " + pomCount + "/4" : ""
if (pomState === "work") {
pomText.text = "POM " + fmt(pomSeconds) + c
pomText.color = pomSeconds <= 60 ? root.critColor : root.accentColor
} else if (pomState === "break" || pomState === "longbreak") {
pomText.text = "BRK " + fmt(pomSeconds) + c
pomText.color = pomSeconds <= 60 ? root.warnColor : root.okColor
} else if (pomState === "done") {
pomText.text = pomBlink ? ">>> GO <<<" : " GO "
pomText.color = root.critColor
}
}
function startWork() {
pomState = "work"
pomSeconds = workMins * 60
pomBlinkTimer.running = false
pomBlink = false
pomTimer.running = true
pomFlash.dismiss()
update()
}
function startBreak() {
pomCount++
totalPoms++
totalFocusMins += workMins
if (pomCount >= 4) {
pomState = "longbreak"
pomSeconds = longBreakMins * 60
pomCount = 0
pomFlash.show("LONG BREAK", root.okColor)
} else {
pomState = "break"
pomSeconds = breakMins * 60
pomFlash.show("BREAK", root.okColor)
}
pomTimer.running = true
update()
root.runShell("notify-send -u normal 'Pomodoro' 'Take a break!'")
}
function goDone() {
pomState = "done"
pomTimer.running = false
pomBlinkTimer.running = true
pomFlash.show("GET BACK TO WORK", root.critColor)
update()
root.runShell("notify-send -u critical 'Pomodoro' 'Break over. Get back to work.'")
}
function reset() {
pomState = "idle"
pomSeconds = 0
pomCount = 0
pomBlink = false
pomTimer.running = false
pomBlinkTimer.running = false
pomFlash.dismiss()
update()
}
function saveSettings() {
pomSettingsSaver.command = ["/usr/bin/env", "bash", "-c",
"echo '" + JSON.stringify({work: workMins, brk: breakMins, lng: longBreakMins}) + "' > ~/.config/quickshell/pom-settings.json"]
pomSettingsSaver.running = true
}
Process {
id: pomSettingsLoader
command: ["/usr/bin/env", "bash", "-c", "cat ~/.config/quickshell/pom-settings.json 2>/dev/null || echo '{}'"]
running: true
stdout: StdioCollector {
onStreamFinished: {
try {
var s = JSON.parse((this.text || "{}").trim())
if (s.work) pomBox.workMins = s.work
if (s.brk) pomBox.breakMins = s.brk
if (s.lng) pomBox.longBreakMins = s.lng
} catch(e) {}
}
}
}
Process {
id: pomSettingsSaver
running: false
}
Timer {
id: pomTimer
interval: 1000
repeat: true
onTriggered: {
pomBox.pomSeconds--
if (pomBox.pomSeconds <= 0) {
if (pomBox.pomState === "work") pomBox.startBreak()
else pomBox.goDone()
} else {
pomBox.update()
}
}
}
Timer {
id: pomBlinkTimer
interval: 500
repeat: true
onTriggered: { pomBox.pomBlink = !pomBox.pomBlink; pomBox.update() }
}
Text {
id: pomText
anchors.centerIn: parent
text: "POM off"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton) {
pomPopup.visible = !pomPopup.visible
} else {
if (pomBox.pomState === "idle" || pomBox.pomState === "done")
pomBox.startWork()
else
pomTimer.running = !pomTimer.running
}
}
}
}
ModuleBox {
id: memoBox
implicitWidth: memoBoxText.implicitWidth + 12
property int todayCount: 0
Text {
id: memoBoxText
anchors.centerIn: parent
text: memoBox.todayCount > 0 ? "MEMO " + memoBox.todayCount : "MEMO"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Process {
id: memoCountProc
command: ["/usr/bin/env", "bash", "-c", "today=$(date +%Y-%m-%d); grep -c \"$today\" ~/.local/share/quickmemo/notes.jsonl 2>/dev/null || echo 0"]
running: true
stdout: StdioCollector {
onStreamFinished: memoBox.todayCount = parseInt(root.trim(this.text)) || 0
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton) {
memoReview.refresh()
memoReview.visible = !memoReview.visible
} else {
memoInput.visible = !memoInput.visible
if (memoInput.visible) {
memoInputField.text = ""
memoInputField.forceActiveFocus()
}
}
}
}
}
ModuleBox {
id: cpuBox
implicitWidth: cpuText.implicitWidth + 12
Text {
id: cpuText
anchors.centerIn: parent
text: "CPU --"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Process {
id: cpuProc
command: ["/usr/bin/env", "bash", "-lc", "read -r _ u n s i _ < /proc/stat; used=$((u+n+s)); total=$((u+n+s+i)); if [ \"$total\" -gt 0 ]; then printf 'CPU %d%%\\n' $((used*100/total)); else echo 'CPU --'; fi"]
running: true
stdout: StdioCollector { onStreamFinished: cpuText.text = root.trim(this.text) || "CPU --" }
}
Timer {
interval: 5000
running: true
repeat: true
onTriggered: cpuProc.running = true
}
}
ModuleBox {
id: memBox
implicitWidth: memText.implicitWidth + 12
Text {
id: memText
anchors.centerIn: parent
text: "MEM --"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Process {
id: memProc
command: ["/usr/bin/env", "bash", "-lc", "free | awk '/Mem:/ { if ($2 > 0) printf \"MEM %d%%\\n\", int($3*100/$2); else print \"MEM --\"; }'"]
running: true
stdout: StdioCollector { onStreamFinished: memText.text = root.trim(this.text) || "MEM --" }
}
Timer {
interval: 5000
running: true
repeat: true
onTriggered: memProc.running = true
}
}
ModuleBox {
id: clockBox
implicitWidth: clockText.implicitWidth + 12
Text {
id: clockText
anchors.centerIn: parent
text: "TIME --"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
Timer {
interval: 1000
running: true
repeat: true
onTriggered: {
const now = new Date()
clockText.text = Qt.formatDateTime(now, "'TIME' yyyy-MM-dd HH:mm")
}
}
Component.onCompleted: {
const now = new Date()
clockText.text = Qt.formatDateTime(now, "'TIME' yyyy-MM-dd HH:mm")
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
calPopup.visible = !calPopup.visible
if (calPopup.visible) calPopup.resetToToday()
}
}
}
ModuleBox {
id: trayBox
implicitWidth: trayRow.implicitWidth + 12
Row {
id: trayRow
anchors.centerIn: parent
spacing: 4
Repeater {
model: SystemTray.items
delegate: Rectangle {
required property var modelData
color: "transparent"
border.width: 0
implicitWidth: 18
implicitHeight: 18
IconImage {
anchors.fill: parent
source: modelData.icon
asynchronous: true
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton && modelData.hasMenu) {
modelData.display(panel, mouse.x, mouse.y)
} else if (mouse.button === Qt.MiddleButton) {
modelData.secondaryActivate()
} else {
modelData.activate()
}
}
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) {
modelData.scroll(1, false)
} else if (wheel.angleDelta.y < 0) {
modelData.scroll(-1, false)
}
}
}
}
}
}
}
}
}
}
Process {
id: paSubscribe
command: ["/usr/bin/env", "bash", "-c", "pactl subscribe 2>/dev/null"]
running: true
stdout: SplitParser {
onRead: function(line) {
if (line.indexOf("sink") >= 0 || line.indexOf("server") >= 0)
spkDebounce.restart()
if (line.indexOf("source") >= 0 || line.indexOf("server") >= 0)
micDebounce.restart()
}
}
onRunningChanged: {
if (!running) paResubscribe.running = true
}
}
Timer {
id: paResubscribe
interval: 2000
repeat: false
onTriggered: paSubscribe.running = true
}
Timer {
id: spkDebounce
interval: 50
repeat: false
onTriggered: spkProc.running = true
}
Timer {
id: micDebounce
interval: 50
repeat: false
onTriggered: micProc.running = true
}
Timer {
interval: 10000
running: true
repeat: true
onTriggered: { spkProc.running = true; micProc.running = true }
}
Timer {
interval: 150
running: true
repeat: true
onTriggered: memoTriggerCheck.running = true
}
Process {
id: memoTriggerCheck
command: ["/usr/bin/env", "bash", "-c", "r=''; [ -f /tmp/qs-memo-input ] && rm /tmp/qs-memo-input && r=\"input\n\"; [ -f /tmp/qs-memo-clip ] && rm /tmp/qs-memo-clip && r=\"${r}clipboard\"; printf '%s' \"$r\""]
running: false
stdout: StdioCollector {
onStreamFinished: {
var t = (this.text || "").trim()
if (t.indexOf("input") >= 0) {
memoInput.visible = true
memoInputField.text = ""
memoInputField.forceActiveFocus()
}
if (t.indexOf("clipboard") >= 0)
memoClipCapture.running = true
}
}
}
Process {
id: memoClipCapture
command: ["/usr/bin/env", "bash", "-c", "wl-paste 2>/dev/null || true"]
running: false
stdout: StdioCollector {
onStreamFinished: {
var text = (this.text || "").trim()
if (text) {
panel.memoSave(text, "clipboard")
root.runShell("notify-send -r 91192 -t 2000 'Quick Memo' 'Clipboard saved'")
}
}
}
}
function memoSave(text, src) {
var entry = JSON.stringify({ts: new Date().toISOString(), text: text, src: src || "input"})
var b64 = Qt.btoa(entry)
memoSaveProc.command = ["/usr/bin/env", "bash", "-c",
"mkdir -p ~/.local/share/quickmemo && printf '%s' " + b64 + " | base64 -d >> ~/.local/share/quickmemo/notes.jsonl && printf '\\n' >> ~/.local/share/quickmemo/notes.jsonl"]
memoSaveProc.running = true
memoBox.todayCount++
}
Process {
id: memoSaveProc
running: false
}
PanelWindow {
id: sinkPopup
screen: panel.screen
visible: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
aboveWindows: true
anchors {
top: true
bottom: true
left: true
right: true
}
property var sinks: []
property string defaultSink: ""
function refresh() {
sinkListProc.running = true
}
function parseSinks(raw) {
var sections = raw.split("---DEF---")
var listRaw = (sections[0] || "").replace("---LIST---", "").trim()
var defRaw = (sections[1] || "").trim()
defaultSink = defRaw
var items = []
var lines = listRaw.split("\n")
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim()
if (!line) continue
var tabIdx = line.indexOf("\t")
if (tabIdx < 0) continue
var name = line.substring(0, tabIdx)
var desc = line.substring(tabIdx + 1)
items.push({ name: name, desc: desc, active: name === defRaw })
}
sinks = items
}
function setDefault(name) {
sinkActionProc.command = ["/usr/bin/env", "bash", "-c", "pactl set-default-sink '" + name + "'"]
sinkActionProc.running = true
visible = false
sinkRefreshTimer.running = true
}
Process {
id: sinkListProc
command: ["/usr/bin/env", "bash", "-c", "echo '---LIST---'; pactl list sinks 2>/dev/null | awk '/^\\tName:/{name=$2} /^\\tDescription:/{sub(/^\\tDescription: /,\"\"); print name\"\\t\"$0}'; echo '---DEF---'; pactl get-default-sink 2>/dev/null"]
running: false
stdout: StdioCollector {
onStreamFinished: sinkPopup.parseSinks((this.text || "").trim())
}
}
Process {
id: sinkActionProc
running: false
}
Timer {
id: sinkRefreshTimer
interval: 500
running: false
repeat: false
onTriggered: spkProc.running = true
}
MouseArea {
anchors.fill: parent
onClicked: sinkPopup.visible = false
}
Rectangle {
x: parent.width - 10 - rightGroup.implicitWidth + vpnBox.implicitWidth + 6
y: parent.height - 38 - height
width: Math.max(250, sinkCol.implicitWidth + 16)
height: sinkCol.implicitHeight + 16
color: root.bgColor
border.width: 1
border.color: root.borderColor
MouseArea {
anchors.fill: parent
}
Column {
id: sinkCol
anchors.fill: parent
anchors.margins: 8
spacing: 2
z: 1
Text {
text: "━━ Output Devices ━━"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 11
topPadding: 2
bottomPadding: 2
}
Repeater {
model: sinkPopup.sinks
delegate: Rectangle {
required property var modelData
width: sinkCol.width
height: 24
color: sinkItemMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: (modelData.active ? "●" : "○") + " " + modelData.desc
color: modelData.active ? root.accentColor : root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
elide: Text.ElideRight
width: sinkCol.width - 8
}
MouseArea {
id: sinkItemMouse
anchors.fill: parent
hoverEnabled: true
onClicked: sinkPopup.setDefault(modelData.name)
}
}
}
}
}
}
PanelWindow {
id: sourcePopup
screen: panel.screen
visible: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
aboveWindows: true
anchors {
top: true
bottom: true
left: true
right: true
}
property var sources: []
property string defaultSource: ""
function refresh() {
sourceListProc.running = true
}
function parseSources(raw) {
var sections = raw.split("---DEF---")
var listRaw = (sections[0] || "").replace("---LIST---", "").trim()
var defRaw = (sections[1] || "").trim()
defaultSource = defRaw
var items = []
var lines = listRaw.split("\n")
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim()
if (!line) continue
var tabIdx = line.indexOf("\t")
if (tabIdx < 0) continue
var name = line.substring(0, tabIdx)
var desc = line.substring(tabIdx + 1)
items.push({ name: name, desc: desc, active: name === defRaw })
}
sources = items
}
function setDefault(name) {
sourceActionProc.command = ["/usr/bin/env", "bash", "-c", "pactl set-default-source '" + name + "'"]
sourceActionProc.running = true
visible = false
sourceRefreshTimer.running = true
}
Process {
id: sourceListProc
command: ["/usr/bin/env", "bash", "-c", "echo '---LIST---'; pactl list sources 2>/dev/null | awk '/^\\tName:/{name=$2} /^\\tDescription:/{sub(/^\\tDescription: /,\"\"); print name\"\\t\"$0}' | grep -v '\\.monitor'; echo '---DEF---'; pactl get-default-source 2>/dev/null"]
running: false
stdout: StdioCollector {
onStreamFinished: sourcePopup.parseSources((this.text || "").trim())
}
}
Process {
id: sourceActionProc
running: false
}
Timer {
id: sourceRefreshTimer
interval: 500
running: false
repeat: false
onTriggered: micProc.running = true
}
MouseArea {
anchors.fill: parent
onClicked: sourcePopup.visible = false
}
Rectangle {
x: parent.width - 10 - rightGroup.implicitWidth + vpnBox.implicitWidth + spkBox.implicitWidth + 12
y: parent.height - 38 - height
width: Math.max(250, sourceCol.implicitWidth + 16)
height: sourceCol.implicitHeight + 16
color: root.bgColor
border.width: 1
border.color: root.borderColor
MouseArea {
anchors.fill: parent
}
Column {
id: sourceCol
anchors.fill: parent
anchors.margins: 8
spacing: 2
z: 1
Text {
text: "━━ Input Devices ━━"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 11
topPadding: 2
bottomPadding: 2
}
Repeater {
model: sourcePopup.sources
delegate: Rectangle {
required property var modelData
width: sourceCol.width
height: 24
color: srcItemMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: (modelData.active ? "●" : "○") + " " + modelData.desc
color: modelData.active ? root.accentColor : root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
elide: Text.ElideRight
width: sourceCol.width - 8
}
MouseArea {
id: srcItemMouse
anchors.fill: parent
hoverEnabled: true
onClicked: sourcePopup.setDefault(modelData.name)
}
}
}
}
}
}
PanelWindow {
id: calPopup
screen: panel.screen
visible: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
aboveWindows: true
anchors {
top: true
bottom: true
left: true
right: true
}
property int calYear: new Date().getFullYear()
property int calMonth: new Date().getMonth()
property var calDays: []
function resetToToday() {
var now = new Date()
calYear = now.getFullYear()
calMonth = now.getMonth()
buildDays()
}
function changeMonth(delta) {
var m = calMonth + delta
var y = calYear
while (m < 0) { m += 12; y-- }
while (m > 11) { m -= 12; y++ }
calMonth = m
calYear = y
buildDays()
}
function buildDays() {
var first = new Date(calYear, calMonth, 1)
var startDow = (first.getDay() + 6) % 7
var daysInMonth = new Date(calYear, calMonth + 1, 0).getDate()
var prevMonthDays = new Date(calYear, calMonth, 0).getDate()
var today = new Date()
var isCurrentMonth = (today.getFullYear() === calYear && today.getMonth() === calMonth)
var todayDate = today.getDate()
var cells = []
for (var i = 0; i < startDow; i++)
cells.push({ day: prevMonthDays - startDow + 1 + i, current: false, today: false })
for (var d = 1; d <= daysInMonth; d++)
cells.push({ day: d, current: true, today: isCurrentMonth && d === todayDate })
var remainder = 42 - cells.length
for (var r = 1; r <= remainder; r++)
cells.push({ day: r, current: false, today: false })
calDays = cells
}
MouseArea {
anchors.fill: parent
onClicked: calPopup.visible = false
}
Rectangle {
id: calRect
x: parent.width - 16 - clockBox.implicitWidth - trayBox.implicitWidth
y: parent.height - 38 - height
width: 7 * 30 + 16
height: calCol.implicitHeight + 16
color: root.bgColor
border.width: 1
border.color: root.borderColor
MouseArea {
anchors.fill: parent
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) calPopup.changeMonth(-1)
else if (wheel.angleDelta.y < 0) calPopup.changeMonth(1)
}
}
Column {
id: calCol
anchors.fill: parent
anchors.margins: 8
spacing: 4
z: 1
Row {
width: parent.width
height: 24
Rectangle {
width: 30
height: 24
color: calPrevMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.centerIn: parent
text: "◄"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: calPrevMouse
anchors.fill: parent
hoverEnabled: true
onClicked: calPopup.changeMonth(-1)
}
}
Item {
width: parent.width - 60
height: 24
Text {
anchors.centerIn: parent
text: Qt.formatDate(new Date(calPopup.calYear, calPopup.calMonth, 1), "MMMM yyyy")
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
}
Rectangle {
width: 30
height: 24
color: calNextMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.centerIn: parent
text: "►"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: calNextMouse
anchors.fill: parent
hoverEnabled: true
onClicked: calPopup.changeMonth(1)
}
}
}
Grid {
columns: 7
spacing: 0
width: parent.width
Repeater {
model: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
delegate: Item {
required property var modelData
width: 30
height: 20
Text {
anchors.centerIn: parent
text: modelData
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 11
}
}
}
}
Grid {
columns: 7
spacing: 0
width: parent.width
Repeater {
model: calPopup.calDays
delegate: Rectangle {
required property var modelData
width: 30
height: 24
radius: 2
color: modelData.today ? root.accentColor : "transparent"
Text {
anchors.centerIn: parent
text: modelData.day
color: modelData.today
? "#111111"
: (modelData.current ? root.fgColor : root.dimColor)
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
}
}
}
}
}
}
PanelWindow {
id: wsRenamePopup
screen: panel.screen
visible: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
aboveWindows: true
focusable: true
anchors {
top: true
bottom: true
left: true
right: true
}
property string wsId: ""
property real renameX: 0
MouseArea {
anchors.fill: parent
onClicked: wsRenamePopup.visible = false
}
Rectangle {
x: wsRenamePopup.renameX
y: parent.height - 72
width: 180
height: 26
color: root.bgColor
border.width: 1
border.color: root.accentColor
radius: 0
MouseArea {
anchors.fill: parent
}
TextInput {
id: wsRenameInput
anchors.fill: parent
anchors.leftMargin: 6
anchors.rightMargin: 6
verticalAlignment: TextInput.AlignVCenter
color: root.fgColor
selectionColor: root.accentColor
selectedTextColor: "#111111"
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
maximumLength: 12
z: 1
onAccepted: {
if (text.trim())
root.setWsLabel(wsRenamePopup.wsId, text.trim())
wsRenamePopup.visible = false
}
Keys.onEscapePressed: wsRenamePopup.visible = false
}
}
}
PanelWindow {
id: vpnPopup
screen: panel.screen
visible: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
aboveWindows: true
anchors {
top: true
bottom: true
left: true
right: true
}
property var wgConfigs: []
property var wgActive: []
property var tsAccounts: []
property var tsExits: []
property bool tsRunning: false
property string tsExitId: ""
property int popupPending: 0
function refresh() {
popupPending = 2
popupWgProc.running = true
popupTsProc.running = true
}
function parseWgData(raw) {
var sections = raw.split("---A---")
var configsRaw = (sections[0] || "").replace("---C---", "").trim()
var activeRaw = (sections[1] || "").trim()
wgConfigs = configsRaw ? configsRaw.split("\n").filter(function(s) { return s.trim() }) : []
wgActive = activeRaw ? activeRaw.split(/\s+/).filter(function(s) { return s.trim() }) : []
}
function parseTsData(raw) {
var statusSplit = raw.split("---L---")
var statusRaw = (statusSplit[0] || "").replace("---S---", "").trim()
var rest = statusSplit[1] || ""
var exitSplit = rest.split("---E---")
var accountsRaw = (exitSplit[0] || "").trim()
var exitsRaw = (exitSplit[1] || "").trim()
try {
var status = JSON.parse(statusRaw)
tsRunning = status.BackendState === "Running"
tsExitId = (status.ExitNodeStatus && status.ExitNodeStatus.ID) || ""
} catch(e) {
tsRunning = false
tsExitId = ""
}
var accs = []
var accLines = accountsRaw.split("\n")
for (var i = 1; i < accLines.length; i++) {
var parts = accLines[i].trim().split(/\s+/)
if (parts.length >= 3) {
var acct = parts[2]
var isActive = acct.endsWith("*")
if (isActive) acct = acct.slice(0, -1)
accs.push({ id: parts[0], tailnet: parts[1], account: acct, active: isActive })
}
}
tsAccounts = accs
var exits = []
var exitLines = exitsRaw.split("\n")
for (var j = 0; j < exitLines.length; j++) {
var line = exitLines[j].trim()
if (!line || line.startsWith("IP") || line.startsWith("#") || line.startsWith("-")) continue
var ep = line.split(/\s+/)
if (ep.length >= 4) {
var st = ep.slice(4).join(" ")
if (st.toLowerCase().indexOf("offline") >= 0) continue
exits.push({ ip: ep[0], hostname: ep[1], short: ep[1].split(".")[0], country: ep[2], city: ep[3] })
}
}
tsExits = exits
}
function vpnAction(cmd) {
vpnActionProc.command = ["/usr/bin/env", "bash", "-c", cmd]
vpnActionProc.running = true
visible = false
vpnRefreshTimer.running = true
}
Process {
id: vpnActionProc
running: false
}
Process {
id: popupWgProc
command: ["/usr/bin/env", "bash", "-c", "echo '---C---'; vpndir='{{ .secretsPath }}/vpn'; [ -d \"$vpndir\" ] || vpndir=\"$HOME/cfg/vpn\"; for f in \"$vpndir\"/*.conf; do [ -f \"$f\" ] && basename \"$f\" .conf; done; echo '---A---'; wg show interfaces 2>/dev/null || true"]
running: false
stdout: StdioCollector {
onStreamFinished: {
vpnPopup.parseWgData((this.text || "").trim())
vpnPopup.popupPending--
}
}
}
Process {
id: popupTsProc
command: ["/usr/bin/env", "bash", "-c", "echo '---S---'; command -v tailscale &>/dev/null && tailscale status --json 2>/dev/null || echo '{}'; echo '---L---'; command -v tailscale &>/dev/null && tailscale switch --list 2>/dev/null || true; echo '---E---'; command -v tailscale &>/dev/null && tailscale exit-node list 2>/dev/null || true"]
running: false
stdout: StdioCollector {
onStreamFinished: {
vpnPopup.parseTsData((this.text || "").trim())
vpnPopup.popupPending--
}
}
}
Timer {
id: vpnRefreshTimer
interval: 1500
running: false
repeat: false
onTriggered: {
vpnBox.vpnPending = 3
wgProc.running = true
nbProc.running = true
tsProc.running = true
}
}
MouseArea {
anchors.fill: parent
onClicked: vpnPopup.visible = false
}
Rectangle {
id: vpnPopupRect
x: parent.width - 10 - rightGroup.implicitWidth
y: parent.height - 38 - height
width: 300
height: vpnPopupCol.implicitHeight + 16
color: root.bgColor
border.width: 1
border.color: root.borderColor
MouseArea {
anchors.fill: parent
}
Column {
id: vpnPopupCol
anchors.fill: parent
anchors.margins: 8
spacing: 2
z: 1
Text {
text: "━━ WireGuard ━━"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 11
topPadding: 2
bottomPadding: 2
}
Repeater {
model: vpnPopup.wgConfigs
delegate: Rectangle {
required property var modelData
width: vpnPopupCol.width
height: 24
color: wgItemMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
property bool active: vpnPopup.wgActive.indexOf(modelData) >= 0
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: (parent.active ? "●" : "○") + " " + modelData
color: parent.active ? root.accentColor : root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: wgItemMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
var dir = "{{ .secretsPath }}/vpn"
if (parent.active) {
vpnPopup.vpnAction("sudo ~/.local/bin/vpn-helper wg-down \"" + dir + "/" + modelData + ".conf\"")
} else {
vpnPopup.vpnAction("for i in $(wg show interfaces 2>/dev/null); do sudo ~/.local/bin/vpn-helper wg-down \"$i\" 2>/dev/null; done; tailscale set --exit-node= 2>/dev/null; sudo ~/.local/bin/vpn-helper wg-up \"" + dir + "/" + modelData + ".conf\"")
}
}
}
}
}
Text {
text: "━━ Tailscale ━━"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 11
topPadding: 6
bottomPadding: 2
}
Repeater {
model: vpnPopup.tsAccounts
delegate: Rectangle {
required property var modelData
width: vpnPopupCol.width
height: 24
color: tsAcctMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: (modelData.active ? "●" : "○") + " " + modelData.tailnet + " (" + modelData.account + ")"
color: modelData.active ? root.accentColor : root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: tsAcctMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (!modelData.active)
vpnPopup.vpnAction("tailscale switch '" + modelData.id + "'")
}
}
}
}
Rectangle {
visible: !vpnPopup.tsRunning
width: vpnPopupCol.width
height: 24
color: tsStartMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: "○ Start Tailscale"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: tsStartMouse
anchors.fill: parent
hoverEnabled: true
onClicked: vpnPopup.vpnAction("tailscale up")
}
}
Text {
visible: vpnPopup.tsRunning
text: "━━ Exit Nodes ━━"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 11
topPadding: 6
bottomPadding: 2
}
Rectangle {
visible: vpnPopup.tsRunning && vpnPopup.tsExitId !== ""
width: vpnPopupCol.width
height: 24
color: exitOffMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: "● Disconnect Exit Node"
color: root.accentColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: exitOffMouse
anchors.fill: parent
hoverEnabled: true
onClicked: vpnPopup.vpnAction("tailscale set --exit-node=")
}
}
Repeater {
model: vpnPopup.tsRunning ? vpnPopup.tsExits : []
delegate: Rectangle {
required property var modelData
width: vpnPopupCol.width
height: 24
color: exitNodeMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: "○ " + modelData.short + " (" + modelData.country + "/" + modelData.city + ")"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: exitNodeMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
vpnPopup.vpnAction("for i in $(wg show interfaces 2>/dev/null); do sudo ~/.local/bin/vpn-helper wg-down \"$i\" 2>/dev/null; done; tailscale set --exit-node='" + modelData.ip + "'")
}
}
}
}
Rectangle {
visible: vpnPopup.tsRunning
width: vpnPopupCol.width
height: 24
color: tsStopMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: "⊘ Stop Tailscale"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: tsStopMouse
anchors.fill: parent
hoverEnabled: true
onClicked: vpnPopup.vpnAction("tailscale down")
}
}
Rectangle {
width: vpnPopupCol.width
height: 1
color: root.borderColor
}
Rectangle {
width: vpnPopupCol.width
height: 24
color: disconnAllMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: "⊘ Disconnect All"
color: root.critColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: disconnAllMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
vpnPopup.vpnAction("for i in $(wg show interfaces 2>/dev/null); do sudo ~/.local/bin/vpn-helper wg-down \"$i\" 2>/dev/null; done; tailscale set --exit-node= 2>/dev/null || true")
}
}
}
}
}
}
PanelWindow {
id: pomFlash
screen: panel.screen
visible: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
aboveWindows: true
anchors {
top: true
bottom: true
left: true
right: true
}
property string flashText: ""
property color flashColor: root.critColor
property bool flashOn: true
function show(text, col) {
flashText = text
flashColor = col
flashOn = true
visible = true
flashAnim.running = true
flashAutoClose.running = true
}
function dismiss() {
visible = false
flashAnim.running = false
flashAutoClose.running = false
}
Timer {
id: flashAnim
interval: 400
repeat: true
onTriggered: pomFlash.flashOn = !pomFlash.flashOn
}
Timer {
id: flashAutoClose
interval: 10000
repeat: false
onTriggered: pomFlash.dismiss()
}
Rectangle {
anchors.fill: parent
color: pomFlash.flashOn ? "#000000E0" : "#000000A0"
MouseArea {
anchors.fill: parent
onClicked: {
pomFlash.dismiss()
if (pomBox.pomState === "done") pomBox.startWork()
}
}
Column {
anchors.centerIn: parent
spacing: 16
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: pomFlash.flashText
color: pomFlash.flashOn ? pomFlash.flashColor : "#333333"
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 72
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: pomBox.pomState === "done" ? "click anywhere to start" : "click to dismiss"
color: "#666666"
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 14
}
}
}
}
PanelWindow {
id: pomPopup
screen: panel.screen
visible: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
aboveWindows: true
anchors {
top: true
bottom: true
left: true
right: true
}
MouseArea {
anchors.fill: parent
onClicked: pomPopup.visible = false
}
Rectangle {
x: parent.width - 10 - rightGroup.implicitWidth + vpnBox.implicitWidth + spkBox.implicitWidth + micBox.implicitWidth + netBox.implicitWidth + 24
{{- if .hasBattery }}
+ batteryBox.implicitWidth + brtBox.implicitWidth + 12
{{- end }}
y: parent.height - 38 - height
width: 220
height: pomPopupCol.implicitHeight + 16
color: root.bgColor
border.width: 1
border.color: root.borderColor
MouseArea {
anchors.fill: parent
}
Column {
id: pomPopupCol
anchors.fill: parent
anchors.margins: 8
spacing: 2
z: 1
Text {
text: "━━ Settings ━━"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 11
topPadding: 2
bottomPadding: 2
}
Rectangle {
width: pomPopupCol.width
height: 24
color: "transparent"
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: "Work " + pomBox.workMins + " min"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) pomBox.workMins = Math.min(90, pomBox.workMins + 5)
else pomBox.workMins = Math.max(5, pomBox.workMins - 5)
pomBox.saveSettings()
}
}
}
Rectangle {
width: pomPopupCol.width
height: 24
color: "transparent"
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: "Break " + pomBox.breakMins + " min"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) pomBox.breakMins = Math.min(30, pomBox.breakMins + 1)
else pomBox.breakMins = Math.max(1, pomBox.breakMins - 1)
pomBox.saveSettings()
}
}
}
Rectangle {
width: pomPopupCol.width
height: 24
color: "transparent"
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: "Long Break " + pomBox.longBreakMins + " min"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) pomBox.longBreakMins = Math.min(45, pomBox.longBreakMins + 5)
else pomBox.longBreakMins = Math.max(5, pomBox.longBreakMins - 5)
pomBox.saveSettings()
}
}
}
Text {
text: "━━ Session ━━"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 11
topPadding: 6
bottomPadding: 2
}
Text {
leftPadding: 8
text: "Completed " + pomBox.totalPoms + " poms"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
height: 24
verticalAlignment: Text.AlignVCenter
}
Text {
leftPadding: 8
text: "Focus time " + pomBox.totalFocusMins + " min"
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
height: 24
verticalAlignment: Text.AlignVCenter
}
Text {
leftPadding: 8
text: "Current " + (pomBox.pomState === "idle" ? "---" : pomBox.pomState)
color: pomBox.pomState === "work" ? root.accentColor : pomBox.pomState === "idle" ? root.dimColor : root.okColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
height: 24
verticalAlignment: Text.AlignVCenter
}
Item {
width: pomPopupCol.width
height: 9
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: root.borderColor
}
}
Rectangle {
width: pomPopupCol.width
height: 24
color: pomResetMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Text {
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
text: "⊘ Reset"
color: root.critColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
}
MouseArea {
id: pomResetMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
pomBox.reset()
pomBox.totalPoms = 0
pomBox.totalFocusMins = 0
pomPopup.visible = false
}
}
}
}
}
}
PanelWindow {
id: memoInput
screen: panel.screen
visible: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
aboveWindows: true
focusable: true
anchors {
top: true
bottom: true
left: true
right: true
}
property var notes: []
property bool inDepthMode: false
function refresh() {
memoInlineLoadProc.running = true
}
function parseNotes(raw) {
var lines = raw.split("\n").filter(function(l) { return l.trim() })
var items = []
for (var i = lines.length - 1; i >= 0 && items.length < 8; i--) {
try { items.push(JSON.parse(lines[i])) } catch(e) {}
}
if (items.length === 0 && raw.trim().length > 0) {
var stitched = raw.replace(/}\s*{/g, "}\n{")
var fallbackLines = stitched.split("\n").filter(function(l) { return l.trim() })
for (var j = fallbackLines.length - 1; j >= 0 && items.length < 8; j--) {
try { items.push(JSON.parse(fallbackLines[j])) } catch(e2) {}
}
}
notes = items
}
function fmtRelative(iso) {
var d = new Date(iso)
var now = new Date()
var secs = Math.floor((now - d) / 1000)
if (isNaN(secs) || secs < 0) return ""
if (secs < 60) return "now"
var mins = Math.floor(secs / 60)
if (mins < 60) return mins + "m ago"
var hrs = Math.floor(mins / 60)
if (hrs < 48) return hrs + "h ago"
var days = Math.floor(hrs / 24)
return days + "d ago"
}
onVisibleChanged: {
if (visible) {
memoInputField.text = ""
memoInputArea.text = ""
memoInput.inDepthMode = false
memoInput.refresh()
memoInputField.forceActiveFocus()
}
}
Process {
id: memoInlineLoadProc
command: ["/usr/bin/env", "bash", "-c", "cat ~/.local/share/quickmemo/notes.jsonl 2>/dev/null || true"]
running: false
stdout: StdioCollector {
onStreamFinished: memoInput.parseNotes((this.text || "").trim())
}
}
MouseArea {
anchors.fill: parent
onClicked: memoInput.visible = false
}
Rectangle {
anchors.centerIn: parent
width: Math.min(parent.width - 128, 784)
height: Math.min(parent.height - 60, 432)
color: "#111214"
border.width: 1
border.color: root.borderColor
clip: true
MouseArea { anchors.fill: parent }
Item {
anchors.fill: parent
anchors.margins: 22
z: 1
Rectangle {
id: memoEntryBox
anchors.horizontalCenter: parent.horizontalCenter
y: {
if (!memoInput.inDepthMode && memoNotesPanel.visible) return 10
var base = Math.floor((parent.height - memoEntryBox.height - (memoNotesPanel.visible ? (memoNotesPanel.height + 16) : 0)) / 2)
return Math.max(0, base)
}
width: Math.floor(parent.width * 0.9)
height: memoInput.inDepthMode ? Math.min(parent.height - 8, 340) : 56
color: "#111214"
border.width: 1
border.color: "#303236"
Row {
anchors.fill: parent
anchors.leftMargin: 14
anchors.rightMargin: 14
spacing: 10
Text {
id: memoPrompt
anchors.verticalCenter: parent.verticalCenter
text: ""
color: root.accentColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: memoInput.inDepthMode ? 18 : 22
}
Item {
width: Math.max(0, parent.width - memoPrompt.width - 10)
height: parent.height
TextInput {
id: memoInputField
anchors.fill: parent
anchors.topMargin: 12
anchors.bottomMargin: 12
anchors.leftMargin: 6
anchors.rightMargin: 6
color: root.fgColor
selectionColor: root.accentColor
selectedTextColor: "#111111"
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 22
clip: true
visible: !memoInput.inDepthMode
Keys.onEscapePressed: memoInput.visible = false
Keys.onTabPressed: function(event) {
memoInput.inDepthMode = true
memoInputArea.forceActiveFocus()
event.accepted = true
}
Keys.onReturnPressed: {
var text = memoInputField.text.trim()
if (text) {
panel.memoSave(text, "input")
memoInputField.text = ""
memoInput.refresh()
}
}
}
Flickable {
id: memoInputAreaFlick
anchors.fill: parent
anchors.topMargin: 8
anchors.bottomMargin: 8
anchors.leftMargin: 6
anchors.rightMargin: 6
contentWidth: width
contentHeight: memoInputArea.implicitHeight
clip: true
flickableDirection: Flickable.VerticalFlick
visible: memoInput.inDepthMode
TextEdit {
id: memoInputArea
width: memoInputAreaFlick.width
color: root.fgColor
selectionColor: root.accentColor
selectedTextColor: "#111111"
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 16
wrapMode: TextEdit.Wrap
Keys.onEscapePressed: memoInput.visible = false
Keys.onTabPressed: function(event) {
memoInput.inDepthMode = false
memoInputField.forceActiveFocus()
event.accepted = true
}
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Return && (event.modifiers & Qt.ControlModifier)) {
var text = memoInputArea.text.trim()
if (text) {
panel.memoSave(text, "input")
memoInputArea.text = ""
memoInput.refresh()
}
event.accepted = true
}
}
}
}
Text {
anchors.fill: parent
anchors.topMargin: 12
anchors.bottomMargin: 12
anchors.leftMargin: 6
anchors.rightMargin: 6
text: memoInput.inDepthMode ? "In-depth note (Ctrl+Enter to save, Tab for quick mode)" : "Add memo"
color: root.dimColor
visible: memoInput.inDepthMode ? memoInputArea.text.length === 0 : memoInputField.text.length === 0
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: memoInput.inDepthMode ? 14 : 22
horizontalAlignment: Text.AlignLeft
verticalAlignment: memoInput.inDepthMode ? Text.AlignTop : Text.AlignVCenter
}
}
}
}
Rectangle {
id: memoNotesPanel
anchors.left: memoEntryBox.left
anchors.right: memoEntryBox.right
anchors.top: memoEntryBox.bottom
anchors.topMargin: 16
anchors.bottom: parent.bottom
color: "#15171a"
border.width: 1
border.color: "#2b2d31"
radius: 0
visible: !memoInput.inDepthMode && memoInput.notes.length > 0
Flickable {
id: memoNotesFlick
anchors.fill: parent
anchors.margins: 9
contentWidth: width
contentHeight: memoNotesCol.implicitHeight
clip: true
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.VerticalFlick
Column {
id: memoNotesCol
width: memoNotesFlick.width
spacing: 6
Repeater {
model: memoInput.notes
delegate: Rectangle {
required property var modelData
width: memoNotesCol.width
height: 32
color: memoItemHover.containsMouse ? "#1c1f23" : "transparent"
radius: 0
Text {
anchors.left: parent.left
anchors.right: noteAge.left
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
leftPadding: 4
text: "→ " + ((modelData.text || "").replace(/\n+/g, " ").trim())
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 16
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
Text {
id: noteAge
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
rightPadding: 4
text: memoInput.fmtRelative(modelData.ts)
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 14
horizontalAlignment: Text.AlignRight
}
MouseArea {
id: memoItemHover
anchors.fill: parent
hoverEnabled: true
}
}
}
}
}
}
}
}
}
PanelWindow {
id: memoReview
screen: panel.screen
visible: false
color: "transparent"
exclusionMode: ExclusionMode.Ignore
aboveWindows: true
anchors {
top: true
bottom: true
left: true
right: true
}
property var notes: []
function refresh() {
memoLoadProc.running = true
}
function parseNotes(raw) {
var lines = raw.split("\n").filter(function(l) { return l.trim() })
var items = []
for (var i = lines.length - 1; i >= 0 && items.length < 50; i--) {
try { items.push(JSON.parse(lines[i])) } catch(e) {}
}
notes = items
}
function fmtTs(iso) {
var d = new Date(iso)
return Qt.formatDateTime(d, "yyyy-MM-dd HH:mm")
}
Process {
id: memoLoadProc
command: ["/usr/bin/env", "bash", "-c", "cat ~/.local/share/quickmemo/notes.jsonl 2>/dev/null || true"]
running: false
stdout: StdioCollector {
onStreamFinished: memoReview.parseNotes((this.text || "").trim())
}
}
Process {
id: memoClipCopy
running: false
}
MouseArea {
anchors.fill: parent
onClicked: memoReview.visible = false
}
Rectangle {
anchors.centerIn: parent
width: 600
height: Math.min(memoReviewFlick.contentHeight + 50, parent.height - 80)
color: root.bgColor
border.width: 1
border.color: root.borderColor
clip: true
MouseArea {
anchors.fill: parent
onWheel: function(wheel) {}
}
Column {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 8
z: 1
Text {
text: "━━ Recent Memos ━━"
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 11
topPadding: 2
bottomPadding: 4
}
Flickable {
id: memoReviewFlick
width: parent.width
height: Math.min(contentHeight, panel.height - 130)
contentHeight: memoReviewCol.implicitHeight
clip: true
flickableDirection: Flickable.VerticalFlick
boundsBehavior: Flickable.StopAtBounds
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheel) {
memoReviewFlick.contentY = Math.max(0,
Math.min(memoReviewFlick.contentHeight - memoReviewFlick.height,
memoReviewFlick.contentY - wheel.angleDelta.y))
}
}
Column {
id: memoReviewCol
width: parent.width
spacing: 0
Repeater {
model: memoReview.notes
delegate: Rectangle {
required property var modelData
width: memoReviewCol.width
height: memoNoteCol.implicitHeight + 12
color: memoItemMouse.containsMouse ? "#2a2a2a" : "transparent"
radius: 2
Column {
id: memoNoteCol
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
leftPadding: 8
rightPadding: 8
spacing: 2
Text {
text: memoReview.fmtTs(modelData.ts) + (modelData.src === "clipboard" ? " [clip]" : "")
color: root.dimColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 10
}
Text {
width: parent.width - 16
text: (modelData.text || "").substring(0, 200)
color: root.fgColor
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
font.pixelSize: 12
wrapMode: Text.Wrap
maximumLineCount: 3
elide: Text.ElideRight
}
}
MouseArea {
id: memoItemMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
memoClipCopy.command = ["/usr/bin/env", "bash", "-c",
"echo " + Qt.btoa(modelData.text) + " | base64 -d | wl-copy"]
memoClipCopy.running = true
root.runShell("notify-send -r 91192 -t 1500 'Quick Memo' 'Copied to clipboard'")
}
}
}
}
}
}
}
}
}
}
}
}