From 5fb19f10389b70ee2389755106092a9ef6c64598 Mon Sep 17 00:00:00 2001 From: David Moc Date: Sun, 24 May 2026 11:48:07 +0200 Subject: Go --- quickshell/gruvbar/README.md | 34 ++ quickshell/gruvbar/launch.sh | 10 + quickshell/gruvbar/scripts/action.sh | 83 +++++ quickshell/gruvbar/scripts/status.sh | 229 +++++++++++++ quickshell/gruvbar/shell.qml | 631 +++++++++++++++++++++++++++++++++++ 5 files changed, 987 insertions(+) create mode 100644 quickshell/gruvbar/README.md create mode 100755 quickshell/gruvbar/launch.sh create mode 100755 quickshell/gruvbar/scripts/action.sh create mode 100755 quickshell/gruvbar/scripts/status.sh create mode 100644 quickshell/gruvbar/shell.qml (limited to 'quickshell') diff --git a/quickshell/gruvbar/README.md b/quickshell/gruvbar/README.md new file mode 100644 index 0000000..3c759ec --- /dev/null +++ b/quickshell/gruvbar/README.md @@ -0,0 +1,34 @@ +# Gruvbar Quickshell + +This is a Quickshell replacement bar for the current i3/Polybar setup. It keeps the Gruvbox palette and FiraCode Nerd Font look, but uses QML widgets instead of Polybar text modules. + +## Run + +```sh +sudo pacman -S quickshell +~/.config/quickshell/gruvbar/launch.sh +``` + +The config is a named Quickshell config, so this also works: + +```sh +quickshell -c gruvbar +``` + +## Switch i3 From Polybar + +After testing the bar manually, change the i3 startup line from Polybar to Gruvbar: + +```i3 +# exec_always --no-startup-id ~/.config/polybar/launch.sh +exec_always --no-startup-id /home/aag/.config/quickshell/gruvbar/launch.sh +``` + +## Widgets + +- Workspaces with click and scroll switching. +- Expand/minimize button for compact and full modes. +- Clickable notification, force-awake, volume, keyboard, WireGuard PC, display, mic, music, scratchpad, screenshot, clipboard, update, and power controls. +- Live CPU, memory, disk, network, network speed, temperature, and clock status. + +Most actions reuse the existing Polybar scripts so the behavior stays aligned while the UI moves to Quickshell. diff --git a/quickshell/gruvbar/launch.sh b/quickshell/gruvbar/launch.sh new file mode 100755 index 0000000..a591a40 --- /dev/null +++ b/quickshell/gruvbar/launch.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v quickshell >/dev/null 2>&1; then + echo "quickshell is not installed. Install it with: sudo pacman -S quickshell" >&2 + exit 1 +fi + +pkill -f "quickshell.*-c gruvbar" 2>/dev/null || true +exec quickshell -c gruvbar diff --git a/quickshell/gruvbar/scripts/action.sh b/quickshell/gruvbar/scripts/action.sh new file mode 100755 index 0000000..80d26d3 --- /dev/null +++ b/quickshell/gruvbar/scripts/action.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -euo pipefail + +action="${1:-}" +arg="${2:-}" +polybar_scripts="/home/aag/.config/polybar/scripts" + +run_existing() { + local script="$1" + shift + + if [ -x "$polybar_scripts/$script" ]; then + exec "$polybar_scripts/$script" "$@" + fi +} + +case "$action" in + awake) + run_existing awake-toggle.sh "$arg" + ;; + clipboard) + run_existing clipboard-menu.sh + ;; + display) + run_existing display-toggle.sh + ;; + display-brightness) + run_existing display-brightness.sh "${arg:-up}" + ;; + dunst) + command -v dunstctl >/dev/null 2>&1 || exit 0 + case "$arg" in + history) dunstctl history-pop ;; + close) dunstctl close-all ;; + *) dunstctl set-paused toggle ;; + esac + ;; + keyboard) + run_existing kb-switch.sh + ;; + mic) + run_existing mic-toggle.sh + ;; + music) + command -v playerctl >/dev/null 2>&1 || exit 0 + case "$arg" in + prev) playerctl previous ;; + next) playerctl next ;; + *) playerctl play-pause ;; + esac + ;; + power) + run_existing power-menu.sh + ;; + screenshot) + run_existing screenshot-menu.sh + ;; + scratch) + command -v i3-msg >/dev/null 2>&1 || exit 0 + case "$arg" in + move) i3-msg move scratchpad ;; + *) i3-msg scratchpad show ;; + esac + ;; + updates) + run_existing updates-action.sh + ;; + volume) + command -v pactl >/dev/null 2>&1 || { + [ "$arg" = "" ] && command -v pavucontrol >/dev/null 2>&1 && exec pavucontrol + exit 0 + } + case "$arg" in + up) pactl set-sink-volume @DEFAULT_SINK@ +5% ;; + down) pactl set-sink-volume @DEFAULT_SINK@ -5% ;; + mute) pactl set-sink-mute @DEFAULT_SINK@ toggle ;; + *) command -v pavucontrol >/dev/null 2>&1 && pavucontrol ;; + esac + ;; + wg) + run_existing wg-pc-toggle.sh "$arg" + ;; +esac diff --git a/quickshell/gruvbar/scripts/status.sh b/quickshell/gruvbar/scripts/status.sh new file mode 100755 index 0000000..f6d4402 --- /dev/null +++ b/quickshell/gruvbar/scripts/status.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +set -euo pipefail + +kind="${1:-}" +iface="${POLYBAR_NETWORK:-eth0}" +profile="${WG_QUICK_PROFILE:-PC}" + +state_dir="${XDG_RUNTIME_DIR:-/tmp}" +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" + mkdir -p "$state_dir" 2>/dev/null || true +fi +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi + +human_bytes() { + local bytes="$1" + + if [ "$bytes" -ge 1048576 ]; then + awk -v v="$bytes" 'BEGIN { printf "%.1fM", v / 1048576 }' + elif [ "$bytes" -ge 1024 ]; then + awk -v v="$bytes" 'BEGIN { printf "%.0fK", v / 1024 }' + else + printf '%sB' "$bytes" + fi +} + +case "$kind" in + awake) + [ -f "$state_dir/polybar-awake.state" ] && echo on || echo off + ;; + cpu) + state_file="$state_dir/quickshell-cpu.state" + read -r cpu user nice system idle iowait irq softirq steal _ "$state_file" + diff_total=$((total - old_total)) + diff_idle=$((idle_all - old_idle)) + + if [ "$diff_total" -le 0 ]; then + echo 0% + else + echo "$(((100 * (diff_total - diff_idle)) / diff_total))%" + fi + ;; + date) + date '+%a %d %b %H:%M' + ;; + disk) + df -P / | awk 'NR == 2 { print $5 }' + ;; + display) + [ -f "$state_dir/polybar-display-night.state" ] && echo night || echo day + ;; + dunst) + if ! command -v dunstctl >/dev/null 2>&1; then + echo off + elif [ "$(dunstctl is-paused 2>/dev/null || echo false)" = true ]; then + waiting="$(dunstctl count waiting 2>/dev/null || echo 0)" + [ "$waiting" -gt 0 ] 2>/dev/null && echo "dnd $waiting" || echo dnd + else + echo on + fi + ;; + keyboard) + export DISPLAY="${DISPLAY:-:0}" + if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" + fi + query="$(setxkbmap -query 2>/dev/null || true)" + layout="$(printf '%s\n' "$query" | awk '/^layout:/ { print $2; exit }')" + variant="$(printf '%s\n' "$query" | awk '/^variant:/ { print $2; exit }')" + case "${layout:-unknown}:${variant:-}" in + us:dvorak) echo "us dv" ;; + cz:*|cz:) echo cz ;; + *:) echo "${layout:-unknown}" ;; + *) echo "${layout:-unknown} ${variant}" ;; + esac + ;; + mem) + awk ' + /MemTotal:/ { total = $2 } + /MemAvailable:/ { avail = $2 } + END { if (total > 0) printf "%d%%\n", ((total - avail) * 100) / total; else print "n/a" } + ' /proc/meminfo + ;; + mic) + if ! command -v pactl >/dev/null 2>&1; then + echo n/a + else + mute="$(pactl get-source-mute @DEFAULT_SOURCE@ 2>/dev/null | awk '{ print $2 }' || true)" + [ "$mute" = yes ] && echo muted || echo on + fi + ;; + music) + if ! command -v playerctl >/dev/null 2>&1; then + echo n/a + exit 0 + fi + status="$(playerctl status 2>/dev/null || true)" + [ -n "$status" ] || { echo off; exit 0; } + artist="$(playerctl metadata artist 2>/dev/null || true)" + title="$(playerctl metadata title 2>/dev/null || true)" + if [ -n "$artist" ] && [ -n "$title" ]; then + printf '%s - %s\n' "$artist" "$title" | cut -c 1-34 + elif [ -n "$title" ]; then + printf '%s\n' "$title" | cut -c 1-34 + else + echo "$status" + fi + ;; + net) + ip -o -4 addr show "$iface" 2>/dev/null | awk '{ split($4, ip, "/"); print ip[1]; found = 1; exit } END { if (!found) print "offline" }' + ;; + netspeed) + rx_file="/sys/class/net/$iface/statistics/rx_bytes" + tx_file="/sys/class/net/$iface/statistics/tx_bytes" + state_file="$state_dir/quickshell-net-${iface}.state" + + if [ ! -r "$rx_file" ] || [ ! -r "$tx_file" ]; then + echo n/a + exit 0 + fi + + now="$(date +%s)" + rx="$(cat "$rx_file")" + tx="$(cat "$tx_file")" + + if [ -r "$state_file" ]; then + read -r old_now old_rx old_tx <"$state_file" || true + else + old_now="$now" + old_rx="$rx" + old_tx="$tx" + fi + + printf '%s %s %s\n' "$now" "$rx" "$tx" >"$state_file" + delta=$((now - old_now)) + [ "$delta" -gt 0 ] || delta=1 + down=$(((rx - old_rx) / delta)) + up=$(((tx - old_tx) / delta)) + [ "$down" -ge 0 ] || down=0 + [ "$up" -ge 0 ] || up=0 + printf '%s/%s\n' "$(human_bytes "$down")" "$(human_bytes "$up")" + ;; + scratch) + if command -v i3-msg >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then + i3-msg -t get_tree 2>/dev/null | + jq '[.. | objects | select(.name == "__i3_scratch") | .floating_nodes[]?] | length' 2>/dev/null || echo 0 + else + echo n/a + fi + ;; + temp) + if command -v sensors >/dev/null 2>&1; then + temp="$( + sensors 2>/dev/null | + awk ' + /Package id 0:/ { gsub(/[^0-9.-]/, "", $4); print int($4); exit } + /Tctl:/ { gsub(/[^0-9.-]/, "", $2); print int($2); exit } + /temp1:/ && $2 ~ /^\+/ { gsub(/[^0-9.-]/, "", $2); print int($2); exit } + ' + )" + else + temp="" + fi + if [ -z "$temp" ]; then + for zone in /sys/class/thermal/thermal_zone*/temp; do + [ -r "$zone" ] || continue + value="$(cat "$zone")" + if [ "$value" -gt 0 ] 2>/dev/null; then + temp=$((value / 1000)) + break + fi + done + fi + [ -n "${temp:-}" ] && echo "${temp}C" || echo n/a + ;; + updates) + if command -v checkupdates >/dev/null 2>&1; then + (checkupdates 2>/dev/null || true) | wc -l + elif command -v pacman >/dev/null 2>&1; then + (pacman -Qu 2>/dev/null || true) | wc -l + else + echo n/a + fi + ;; + volume) + if ! command -v pactl >/dev/null 2>&1; then + echo n/a + exit 0 + fi + mute="$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | awk '{ print $2 }' || true)" + vol="$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | awk 'NR == 1 { print $5; exit }' || true)" + [ "$mute" = yes ] && echo muted || echo "${vol:-n/a}" + ;; + wg) + if [ ! -d "/sys/class/net/$profile" ]; then + echo down + exit 0 + fi + if ! command -v wg >/dev/null 2>&1; then + echo up + exit 0 + fi + latest="$(wg show "$profile" latest-handshakes 2>/dev/null | awk 'NF { if ($2 > newest) newest = $2 } END { print newest + 0 }' || true)" + if [ -z "$latest" ] || [ "$latest" -le 0 ] 2>/dev/null; then + echo up + exit 0 + fi + age=$(($(date +%s) - latest)) + if [ "$age" -lt 60 ]; then suffix="${age}s"; elif [ "$age" -lt 3600 ]; then suffix="$((age / 60))m"; elif [ "$age" -lt 86400 ]; then suffix="$((age / 3600))h"; else suffix="$((age / 86400))d"; fi + echo "up $suffix" + ;; + *) + echo n/a + exit 1 + ;; +esac diff --git a/quickshell/gruvbar/shell.qml b/quickshell/gruvbar/shell.qml new file mode 100644 index 0000000..0d03f70 --- /dev/null +++ b/quickshell/gruvbar/shell.qml @@ -0,0 +1,631 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.I3 +import Quickshell.Io + +ShellRoot { + id: root + + property bool expanded: false + property int pendingWorkspace: 0 + property int barHeight: 38 + property int drawerHeight: 118 + property string scriptDir: Quickshell.shellPath("scripts") + property string fontFamily: "FiraCode Nerd Font" + property string bg: "#282828" + property string bg1: "#3c3836" + property string bg2: "#504945" + property string bg3: "#665c54" + property string fg: "#ebdbb2" + property string fg1: "#d5c4a1" + property string red: "#cc241d" + property string green: "#98971a" + property string yellow: "#d79921" + property string orange: "#d65d0e" + property string purple: "#b16286" + property string aqua: "#689d6a" + property string gray: "#928374" + property string clockText: Qt.formatDateTime(new Date(), "hh:mm dd MMM") + + signal refreshRequested(string actionName) + + function statusCommand(name) { + return [root.scriptDir + "/status.sh", name]; + } + + function requestStatusRefresh(actionName) { + root.refreshRequested(actionName); + quickStatusRefresh.actionName = actionName; + settleStatusRefresh.actionName = actionName; + lateStatusRefresh.actionName = actionName; + quickStatusRefresh.restart(); + settleStatusRefresh.restart(); + lateStatusRefresh.restart(); + } + + function runAction(name, arg) { + var cmd = [root.scriptDir + "/action.sh", name]; + if (arg !== undefined && arg !== "") + cmd.push(arg); + + Quickshell.execDetached(cmd); + root.requestStatusRefresh(name); + } + + function switchWorkspace(workspaceNumber) { + root.pendingWorkspace = workspaceNumber; + pendingWorkspaceReset.restart(); + I3.dispatch("workspace number " + workspaceNumber); + } + + function textColor(value, fallback) { + if (value === "n/a") + return root.gray; + + if (value.indexOf("down") === 0 || value.indexOf("muted") === 0) + return root.red; + + if (value.indexOf("dnd") === 0 || value.indexOf("night") === 0) + return root.orange; + + if (value === "on" || value.indexOf("up") === 0 || value === "day") + return root.green; + + return fallback; + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: root.clockText = Qt.formatDateTime(new Date(), "hh:mm dd MMM") + } + + Timer { + id: quickStatusRefresh + + property string actionName: "" + + interval: 120 + onTriggered: root.refreshRequested(actionName) + } + + Timer { + id: settleStatusRefresh + + property string actionName: "" + + interval: 500 + onTriggered: root.refreshRequested(actionName) + } + + Timer { + id: lateStatusRefresh + + property string actionName: "" + + interval: 1600 + onTriggered: root.refreshRequested(actionName) + } + + Timer { + id: pendingWorkspaceReset + + interval: 700 + onTriggered: root.pendingWorkspace = 0 + } + + ListModel { + id: workspaceModel + + ListElement { + ws: 1 + } + + ListElement { + ws: 2 + } + + ListElement { + ws: 3 + } + + ListElement { + ws: 4 + } + + ListElement { + ws: 5 + } + + ListElement { + ws: 6 + } + + ListElement { + ws: 7 + } + + ListElement { + ws: 8 + } + + ListElement { + ws: 9 + } + + ListElement { + ws: 10 + } + + } + + Variants { + model: Quickshell.screens + + PanelWindow { + id: bar + + required property var modelData + readonly property bool canExpand: modelData.name === Quickshell.screens[0].name + + screen: modelData + implicitHeight: root.barHeight + exclusiveZone: root.barHeight + aboveWindows: true + + anchors { + top: true + left: true + right: true + } + + Rectangle { + anchors.fill: parent + color: root.bg + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 1 + color: root.bg2 + } + + Row { + id: taskStrip + + anchors.centerIn: parent + height: 30 + spacing: 6 + + ToggleButton { + visible: bar.canExpand + } + + Repeater { + model: workspaceModel + + delegate: WorkspaceButton { + required property int ws + + workspaceNumber: ws + } + + } + + } + + Row { + id: tray + + anchors.right: parent.right + anchors.rightMargin: 8 + anchors.verticalCenter: parent.verticalCenter + height: 28 + spacing: 5 + + StatusPill { + title: "ntf" + command: root.statusCommand("dunst") + actionName: "dunst" + accent: root.yellow + } + + StatusPill { + title: "wake" + command: root.statusCommand("awake") + actionName: "awake" + accent: root.orange + } + + StatusPill { + title: "wg" + command: root.statusCommand("wg") + actionName: "wg" + accent: root.aqua + } + + StatusPill { + title: "vol" + command: root.statusCommand("volume") + actionName: "volume" + actionArg: "mute" + wheelAction: "volume" + wheelUpArg: "up" + wheelDownArg: "down" + accent: root.green + } + + StatusPill { + title: "kbd" + command: root.statusCommand("keyboard") + actionName: "keyboard" + accent: root.purple + } + + ClockPill { + } + + } + + } + + PopupWindow { + id: drawer + + visible: root.expanded && bar.canExpand + grabFocus: false + anchor.window: bar + anchor.rect.x: Math.max(8, Math.round((bar.width - implicitWidth) / 2)) + anchor.rect.y: root.barHeight + 6 + implicitWidth: Math.max(320, Math.min(760, bar.width - 24)) + implicitHeight: root.drawerHeight + color: "transparent" + + Rectangle { + anchors.fill: parent + radius: 7 + color: root.bg1 + border.width: 1 + border.color: root.bg3 + + RowLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + Flow { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 7 + + StatusPill { + title: "net" + command: root.statusCommand("net") + accent: root.green + } + + StatusPill { + title: "spd" + command: root.statusCommand("netspeed") + accent: root.green + } + + StatusPill { + title: "tmp" + command: root.statusCommand("temp") + accent: root.orange + } + + StatusPill { + title: "cpu" + command: root.statusCommand("cpu") + accent: root.orange + } + + StatusPill { + title: "ram" + command: root.statusCommand("mem") + accent: root.purple + } + + StatusPill { + title: "disk" + command: root.statusCommand("disk") + accent: root.aqua + } + + StatusPill { + title: "upd" + command: root.statusCommand("updates") + actionName: "updates" + interval: 900000 + accent: root.yellow + } + + StatusPill { + title: "disp" + command: root.statusCommand("display") + actionName: "display" + wheelAction: "display-brightness" + wheelUpArg: "up" + wheelDownArg: "down" + accent: root.orange + } + + StatusPill { + title: "mic" + command: root.statusCommand("mic") + actionName: "mic" + accent: root.purple + } + + StatusPill { + title: "mus" + command: root.statusCommand("music") + actionName: "music" + interval: 2000 + accent: root.green + } + + StatusPill { + title: "scratch" + command: root.statusCommand("scratch") + actionName: "scratch" + accent: root.yellow + } + + } + + ColumnLayout { + Layout.preferredWidth: 84 + Layout.fillHeight: true + spacing: 7 + + ActionButton { + label: "shot" + actionName: "screenshot" + accent: root.aqua + } + + ActionButton { + label: "clip" + actionName: "clipboard" + accent: root.purple + } + + ActionButton { + label: "power" + actionName: "power" + accent: root.red + } + + } + + } + + } + + } + + } + + } + + component WorkspaceButton: Rectangle { + id: wsButton + + required property int workspaceNumber + readonly property bool focused: I3.focusedWorkspace !== null && I3.focusedWorkspace.number === workspaceNumber + readonly property bool active: focused || root.pendingWorkspace === workspaceNumber + + Layout.preferredWidth: 30 + Layout.preferredHeight: 28 + width: 30 + height: 28 + radius: 4 + color: active ? root.yellow : (mouse.containsMouse ? root.bg2 : root.bg1) + border.width: 1 + border.color: active ? root.yellow : root.bg2 + + Text { + anchors.centerIn: parent + text: String(wsButton.workspaceNumber) + color: wsButton.active ? root.bg : root.fg1 + font.family: root.fontFamily + font.pixelSize: 13 + font.bold: wsButton.active + } + + MouseArea { + id: mouse + + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onClicked: root.switchWorkspace(wsButton.workspaceNumber) + onWheel: wheel.angleDelta.y > 0 ? I3.dispatch("workspace next") : I3.dispatch("workspace prev") + } + + } + + component ToggleButton: Rectangle { + Layout.preferredHeight: 28 + Layout.preferredWidth: 68 + width: 68 + height: 28 + radius: 5 + color: root.expanded ? root.bg2 : root.bg1 + border.width: 1 + border.color: root.yellow + + Text { + anchors.centerIn: parent + text: root.expanded ? "close" : "start" + color: root.yellow + font.family: root.fontFamily + font.pixelSize: 12 + font.bold: true + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.expanded = !root.expanded + } + + } + + component ClockPill: Rectangle { + width: 122 + height: 28 + radius: 5 + color: root.bg1 + border.width: 1 + border.color: root.bg2 + + Text { + anchors.centerIn: parent + text: root.clockText + color: root.fg + font.family: root.fontFamily + font.pixelSize: 12 + font.bold: true + } + + } + + component ActionButton: Rectangle { + id: button + + property string label: "" + property string actionName: "" + property string actionArg: "" + property string accent: root.fg1 + + Layout.preferredWidth: 76 + Layout.preferredHeight: 28 + width: 76 + height: 28 + radius: 5 + color: mouse.pressed ? root.bg3 : (mouse.containsMouse ? root.bg2 : root.bg) + border.width: 1 + border.color: accent + + Text { + id: labelText + + anchors.centerIn: parent + text: button.label + color: button.accent + font.family: root.fontFamily + font.pixelSize: 12 + font.bold: true + } + + MouseArea { + id: mouse + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.runAction(button.actionName, button.actionArg) + } + + } + + component StatusPill: Rectangle { + id: pill + + property string title: "" + property var command: [] + property int interval: 2000 + property string value: "..." + property string actionName: "" + property string actionArg: "" + property string wheelAction: "" + property string wheelUpArg: "" + property string wheelDownArg: "" + property string accent: root.fg1 + + function refresh() { + if (pill.command.length > 0) + proc.exec(pill.command); + + } + + function matchesRefresh(actionName) { + return actionName === "" || actionName === pill.actionName || actionName === pill.wheelAction; + } + + Layout.preferredHeight: 28 + Layout.preferredWidth: Math.max(56, label.implicitWidth + 18) + width: Math.max(56, label.implicitWidth + 18) + height: 28 + radius: 5 + color: mouse.pressed ? root.bg3 : (mouse.containsMouse ? root.bg2 : root.bg) + border.width: 1 + border.color: root.bg2 + + Text { + id: label + + anchors.centerIn: parent + text: pill.title + " " + pill.value + color: root.textColor(pill.value, pill.accent) + font.family: root.fontFamily + font.pixelSize: 12 + } + + Process { + id: proc + + command: pill.command + running: true + + stdout: StdioCollector { + onStreamFinished: pill.value = this.text.trim() + } + + } + + Connections { + function onRefreshRequested(actionName) { + if (pill.matchesRefresh(actionName)) + pill.refresh(); + + } + + target: root + } + + Timer { + interval: pill.interval + running: true + repeat: true + onTriggered: pill.refresh() + } + + MouseArea { + id: mouse + + anchors.fill: parent + hoverEnabled: true + cursorShape: pill.actionName !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + if (pill.actionName !== "") + root.runAction(pill.actionName, pill.actionArg); + + } + onWheel: { + if (pill.wheelAction !== "") + root.runAction(pill.wheelAction, wheel.angleDelta.y > 0 ? pill.wheelUpArg : pill.wheelDownArg); + + } + } + + } + +} -- cgit v1.2.3