summaryrefslogtreecommitdiff
path: root/quickshell
diff options
context:
space:
mode:
Diffstat (limited to 'quickshell')
-rw-r--r--quickshell/gruvbar/README.md34
-rwxr-xr-xquickshell/gruvbar/launch.sh10
-rwxr-xr-xquickshell/gruvbar/scripts/action.sh83
-rwxr-xr-xquickshell/gruvbar/scripts/status.sh229
-rw-r--r--quickshell/gruvbar/shell.qml631
5 files changed, 987 insertions, 0 deletions
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 _ </proc/stat
+ idle_all=$((idle + iowait))
+ total=$((user + nice + system + idle + iowait + irq + softirq + steal))
+
+ if [ -r "$state_file" ]; then
+ read -r old_total old_idle <"$state_file" || true
+ else
+ old_total="$total"
+ old_idle="$idle_all"
+ fi
+
+ printf '%s %s\n' "$total" "$idle_all" >"$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);
+
+ }
+ }
+
+ }
+
+}