From 6dc56f4f12b7defbd1b9603d3176d9d4925a1f7a Mon Sep 17 00:00:00 2001 From: chodak166 Date: Thu, 18 Jun 2026 20:18:46 +0200 Subject: [PATCH] Added waybar config --- .config/nvim/lazy-lock.json | 1 - .config/nvim/lua/plugins/colorscheme.lua | 68 +++++----- .config/waybar/config.jsonc | 135 +++++++++++++++++++ .config/waybar/disk.py | 157 +++++++++++++++++++++++ .config/waybar/network.py | 140 ++++++++++++++++++++ .config/waybar/network.sh | 97 ++++++++++++++ .config/waybar/style.css | 77 +++++++++++ .config/waybar/top.sh | 30 +++++ .config/waybar/watch.sh | 5 + 9 files changed, 675 insertions(+), 35 deletions(-) create mode 100644 .config/waybar/config.jsonc create mode 100755 .config/waybar/disk.py create mode 100755 .config/waybar/network.py create mode 100755 .config/waybar/network.sh create mode 100644 .config/waybar/style.css create mode 100755 .config/waybar/top.sh create mode 100755 .config/waybar/watch.sh diff --git a/.config/nvim/lazy-lock.json b/.config/nvim/lazy-lock.json index 4686a7c..ec559f8 100644 --- a/.config/nvim/lazy-lock.json +++ b/.config/nvim/lazy-lock.json @@ -12,7 +12,6 @@ "friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" }, "gitsigns.nvim": { "branch": "main", "commit": "25050e4ed39e628282831d4cbecb1850454ce915" }, "grug-far.nvim": { "branch": "main", "commit": "c995bbacf8229dc096ec1c3d60f8531059c86c1b" }, - "hybrid-theme": { "branch": "main", "commit": "b131110fbe63481d7d263c71982206a70a04d236" }, "indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" }, "just-runner.nvim": { "branch": "main", "commit": "f29d405aa828900df242600720a2b0e57261489f" }, "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" }, diff --git a/.config/nvim/lua/plugins/colorscheme.lua b/.config/nvim/lua/plugins/colorscheme.lua index 8394a72..f81ac04 100644 --- a/.config/nvim/lua/plugins/colorscheme.lua +++ b/.config/nvim/lua/plugins/colorscheme.lua @@ -13,39 +13,39 @@ -- } -- Local development: --- return { --- dir = "/home/chodak/src/git/nvim-hybrid-theme", --- name = "hybrid-theme", --- lazy = false, --- priority = 1000, --- config = function() --- require("hybrid-theme").setup({ --- theme = "dark", --- transparent = false, --- background_variant = "flat", --- italics = { --- comments = true, --- keywords = false, --- functions = false, --- strings = false, --- variables = false, --- }, --- }) --- require("hybrid-theme").colorscheme() --- end, --- } - return { - { - "chodak166/nvim-hybrid-theme", - name = "hybrid-theme", - lazy = false, - priority = 1000, - config = function() - require("hybrid-theme").setup({ - background_variant = "semi_flat", - }) - require("hybrid-theme").colorscheme() - end, - }, + dir = "/home/chodak/src/git/nvim-hybrid-theme", + name = "hybrid-theme", + lazy = false, + priority = 1000, + config = function() + require("hybrid-theme").setup({ + theme = "dark", + transparent = false, + background_variant = "flat", + italics = { + comments = true, + keywords = false, + functions = false, + strings = false, + variables = false, + }, + }) + require("hybrid-theme").colorscheme() + end, } + +-- return { +-- { +-- "chodak166/nvim-hybrid-theme", +-- name = "hybrid-theme", +-- lazy = false, +-- priority = 1000, +-- config = function() +-- require("hybrid-theme").setup({ +-- background_variant = "semi_flat", +-- }) +-- require("hybrid-theme").colorscheme() +-- end, +-- }, +-- } diff --git a/.config/waybar/config.jsonc b/.config/waybar/config.jsonc new file mode 100644 index 0000000..5d95ab3 --- /dev/null +++ b/.config/waybar/config.jsonc @@ -0,0 +1,135 @@ +// -*- mode: json -*- + +{ + "layer": "top", + "position": "top", + + "modules-left": [ + "sway/workspaces", + "custom/right-arrow-dark" + ], + "modules-center": [ + "custom/left-arrow-dark", + "sway/window", + "custom/right-arrow-dark" + ], + "modules-right": [ + "custom/left-arrow-dark", + "pulseaudio", + "custom/left-arrow-light", + "custom/left-arrow-dark", + "custom/network", + "custom/left-arrow-light", + "custom/left-arrow-dark", + "custom/disk", + "custom/left-arrow-light", + "custom/left-arrow-dark", + "memory", + "custom/left-arrow-light", + "custom/left-arrow-dark", + "custom/top", + "custom/left-arrow-light", + "custom/left-arrow-dark", + "battery", + "custom/left-arrow-light", + "custom/left-arrow-dark", + "clock#0", + "custom/left-arrow-light", + "custom/left-arrow-dark", + "tray" + ], + + "custom/left-arrow-dark": { + "format": "", + "tooltip": false + }, + "custom/left-arrow-light": { + "format": "", + "tooltip": false + }, + "custom/right-arrow-dark": { + "format": "", + "tooltip": false + }, + + "sway/workspaces": { + "disable-scroll": true, + "format": "{name}" + }, + + "sway/window": { + "max-length": 70, + "format": "{title}" + }, + + "custom/network": { + "exec": "/home/chodak/.config/waybar/network.py", + "interval": 10, + "return-type": "json", + "tooltip": true + }, + + "clock#2": { + "format": "{:%H:%M}", + "tooltip": false + }, + "clock#3": { + "format": "{:%d/%m}", + "tooltip": false + }, + "clock#0": { + "format": "{:%H:%M %d/%m/%y}", + "tooltip": false + }, + + "pulseaudio": { + "format": "{icon} {volume:2}%", + "format-bluetooth": "{icon} {volume}%", + "format-muted": "MUTE", + "format-icons": { + "headphones": "", + "default": [ + "", + "" + ] + }, + "scroll-step": 5, + "on-click": "pamixer -t", + "on-click-right": "pavucontrol" + }, + "memory": { + "interval": 5, + "format": " {}%" + }, + "custom/top": { + "exec": "~/.config/waybar/top.sh", + "interval": 5, + "return-type": "json", + "tooltip": true + }, + "battery": { + "states": { + "good": 95, + "warning": 30, + "critical": 15 + }, + "format": "{icon} {capacity}%", + "format-icons": [ + "", + "", + "", + "", + "" + ] + }, + "custom/disk": { + "exec": "/home/chodak/.config/waybar/disk.py", + "interval": 30, + "return-type": "json", + "tooltip": true, + "escape": false + }, + "tray": { + "icon-size": 24 + } +} diff --git a/.config/waybar/disk.py b/.config/waybar/disk.py new file mode 100755 index 0000000..dab4761 --- /dev/null +++ b/.config/waybar/disk.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Waybar disk module: shows disk usage across all mounted partitions.""" + +import json +import os + +# Filesystem types to ignore (virtual / non-disk) +IGNORE_FSTYPES = { + "tmpfs", "devtmpfs", "proc", "sysfs", "cgroup", "cgroup2", + "pstore", "bpf", "debugfs", "tracefs", "securityfs", "configfs", + "hugetlbfs", "devpts", "ramfs", "fuse.gvfsd-fuse", "fusectl", + "mqueue", "rpc_pipefs", "overlay", "squashfs", +} + +# Pango color thresholds +COLOR_LOW = "#a6e3a1" # green — < 70% +COLOR_MID = "#f9e2af" # yellow — 70–89% +COLOR_HIGH = "#f38ba8" # red — ≥ 90% + + +def _pct_color(pct: float) -> str: + """Return a Pango color string for the given usage percentage.""" + if pct >= 90: + return COLOR_HIGH + if pct >= 70: + return COLOR_MID + return COLOR_LOW + + +def _span(color: str, text: str) -> str: + """Wrap text in a Pango with the given foreground color.""" + return f"{text}" + + +def get_partitions(): + """Return list of (device, mountpoint, fstype) for real partitions.""" + partitions = [] + seen_devices = set() + + try: + with open("/proc/mounts") as f: + for line in f: + parts = line.split() + if len(parts) < 3: + continue + device, mountpoint, fstype = parts[:3] + + if fstype in IGNORE_FSTYPES: + continue + if not device.startswith("/dev/"): + continue + if device in seen_devices: + continue + seen_devices.add(device) + + partitions.append((device, mountpoint, fstype)) + except OSError: + pass + + return partitions + + +def get_usage(mountpoint): + """Return (total_bytes, used_bytes, pct) for a mountpoint, or None.""" + try: + st = os.statvfs(mountpoint) + total = st.f_frsize * st.f_blocks + free = st.f_frsize * st.f_bfree + used = total - free + pct = (used / total) * 100 if total > 0 else 0.0 + return total, used, pct + except OSError: + return None + + +def fmt_size(bytes_val): + """Format bytes as human-readable.""" + if bytes_val >= 1 << 40: + return f"{bytes_val / (1 << 40):.1f}T" + if bytes_val >= 1 << 30: + return f"{bytes_val / (1 << 30):.1f}G" + if bytes_val >= 1 << 20: + return f"{bytes_val / (1 << 20):.1f}M" + if bytes_val >= 1 << 10: + return f"{bytes_val / (1 << 10):.1f}K" + return f"{bytes_val}B" + + +def main(): + partitions = get_partitions() + + if not partitions: + print(json.dumps( + {"text": " —", "tooltip": "No disk partitions found"}, + ensure_ascii=False)) + return + + tooltip_lines = [] + root_pct = None + bar_parts = [] + + for device, mountpoint, fstype in partitions: + usage = get_usage(mountpoint) + if usage is None: + continue + total, used, pct = usage + + total_str = fmt_size(total) + used_str = fmt_size(used) + color = _pct_color(pct) + + # Icons per mountpoint + if mountpoint == "/": + icon = "" + elif mountpoint == "/home": + icon = "" + elif mountpoint == "/boot": + icon = "" + elif "/media/" in mountpoint or "/run/media/" in mountpoint: + icon = "" + else: + icon = "" + + # Tooltip line with colored percentage + pct_span = _span(color, f"{pct:5.1f}%") + warn = " ⚠" if pct >= 90 else "" + tooltip_lines.append( + f"{icon} {mountpoint:20s} {used_str:>6s} / {total_str:>6s} ({pct_span}){warn}" + ) + + # Track root for bar display + if mountpoint == "/": + root_pct = pct + + # Build bar text using Pango markup + if root_pct is not None: + bar_icon = "⚠" if root_pct >= 90 else "" + bar_color = _pct_color(root_pct) + bar_text = ( + f"{bar_icon}" + f" {_span(bar_color, f'{root_pct:.0f}%')}" + ) + elif tooltip_lines: + bar_text = f" —" + else: + bar_text = " —" + + tooltip = "\n".join(tooltip_lines) if tooltip_lines else "No partitions" + + print(json.dumps( + {"text": bar_text, "tooltip": tooltip}, + ensure_ascii=False)) + + +if __name__ == "__main__": + main() + diff --git a/.config/waybar/network.py b/.config/waybar/network.py new file mode 100755 index 0000000..dc67b1a --- /dev/null +++ b/.config/waybar/network.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Waybar network module: shows interface, IP, and up/down transfer rate.""" + +import json +import os +import re +import subprocess +import time + +STATE_FILE_TMPL = "/tmp/waybar-network-stats-{}" +ICON = "󰲝" # nerd font network icon +NO_NET_ICON = "󰖪" + + +def run(cmd: list[str]) -> str: + """Run a command and return stdout, or empty string on failure.""" + try: + return subprocess.check_output( + cmd, stderr=subprocess.DEVNULL, text=True + ).strip() + except subprocess.CalledProcessError: + return "" + + +def get_default_interface() -> str | None: + """Return the default route interface name.""" + output = run(["ip", "route"]) + for line in output.splitlines(): + if line.startswith("default"): + parts = line.split() + # "default via X.X.X.X dev INTERFACE ..." + if len(parts) >= 5: + return parts[4] + return None + + +def get_ip(iface: str) -> str | None: + """Return the IPv4 address of the given interface.""" + output = run(["ip", "-4", "addr", "show", iface]) + m = re.search(r"inet\s+(\d+\.\d+\.\d+\.\d+)", output) + return m.group(1) if m else None + + +def iface_type(iface: str) -> str: + """Classify interface as WiFi, Ethernet, or generic.""" + if re.match(r"^(wlan|wlp)", iface): + return "WiFi" + if re.match(r"^(eth|enp|eno|ens)", iface): + return "Ethernet" + return iface + + +def format_rate(bps: float) -> str: + """Format bytes-per-second as human-readable with one decimal.""" + bps = max(bps, 0) + if bps >= 1_073_741_824: + return f"{bps / 1_073_741_824:.1f}G" + if bps >= 1_048_576: + return f"{bps / 1_048_576:.1f}M" + if bps >= 1024: + return f"{bps / 1024:.1f}K" + return f"{bps:.1f}B" + + +def main() -> None: + iface = get_default_interface() + if iface is None: + print(json.dumps( + {"text": f"{NO_NET_ICON} No net", + "tooltip": "No network connection"}, + ensure_ascii=False)) + return + + ip = get_ip(iface) + if ip is None: + print(json.dumps( + {"text": f"{NO_NET_ICON} No IP", + "tooltip": f"Interface {iface} has no IP"}, + ensure_ascii=False)) + return + + itype = iface_type(iface) + + # Read current byte counters + now_ts = time.time_ns() + rx_path = f"/sys/class/net/{iface}/statistics/rx_bytes" + tx_path = f"/sys/class/net/{iface}/statistics/tx_bytes" + + try: + with open(rx_path) as f: + now_rx = int(f.read().strip()) + with open(tx_path) as f: + now_tx = int(f.read().strip()) + except (OSError, ValueError): + now_rx = now_tx = 0 + + # Transfer rate calculation + rate_down = "" + rate_up = "" + state_file = STATE_FILE_TMPL.format(iface) + + if os.path.exists(state_file): + try: + with open(state_file) as f: + prev_rx, prev_tx, prev_ts = map(int, f.read().split()) + delta_ns = now_ts - prev_ts + if delta_ns > 0: + delta_rx = now_rx - prev_rx + delta_tx = now_tx - prev_tx + rx_bps = delta_rx * 1e9 / delta_ns + tx_bps = delta_tx * 1e9 / delta_ns + rate_down = format_rate(rx_bps) + rate_up = format_rate(tx_bps) + except (OSError, ValueError): + pass + + # Save current state for next run + with open(state_file, "w") as f: + f.write(f"{now_rx} {now_tx} {now_ts}") + + # Build output + if rate_down and rate_up: + rate_str = f" ↓{rate_down} ↑{rate_up}" + tooltip_rate = f"↓ {rate_down}/s ↑ {rate_up}/s" + else: + rate_str = "" + tooltip_rate = "↓ ?/s ↑ ?/s" + + text = ( + f"{ICON}" + f" {iface} {ip}{rate_str}" + ) + tooltip = f"{itype}: {iface} — {ip} | {tooltip_rate}" + + print(json.dumps({"text": text, "tooltip": tooltip}, ensure_ascii=False)) + + +if __name__ == "__main__": + main() + diff --git a/.config/waybar/network.sh b/.config/waybar/network.sh new file mode 100755 index 0000000..4f6dd70 --- /dev/null +++ b/.config/waybar/network.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +# Get the default interface used for routing +INTERFACE=$(ip route 2>/dev/null | grep '^default' | awk '{print $5}' | head -1) + +if [ -z "$INTERFACE" ]; then + echo '{"text": "󰖪 No net", "tooltip": "No network connection"}' + exit 0 +fi + +# Get IPv4 address of the interface +IP=$(ip -4 addr show "$INTERFACE" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1) + +if [ -z "$IP" ]; then + echo '{"text": "󰖪 No IP", "tooltip": "Interface '"$INTERFACE"' has no IP"}' + exit 0 +fi + +# Determine interface type and set appropriate icon +if [[ "$INTERFACE" =~ ^(wlan|wlp) ]]; then + ICON="󰲝" # generic network icon (nerd font) + TYPE="WiFi" +elif [[ "$INTERFACE" =~ ^(eth|enp|eno|ens) ]]; then + ICON="󰲝" # generic network icon (nerd font) + TYPE="Ethernet" +else + ICON="󰲝" # generic network icon (nerd font) + TYPE="$INTERFACE" +fi + +# --- Transfer rate calculation --- + +# Path to sysfs statistics +RX_FILE="/sys/class/net/$INTERFACE/statistics/rx_bytes" +TX_FILE="/sys/class/net/$INTERFACE/statistics/tx_bytes" +STATE_FILE="/tmp/waybar-network-stats-$INTERFACE" + +# Read current byte counters +NOW_RX=$(cat "$RX_FILE" 2>/dev/null) +NOW_TX=$(cat "$TX_FILE" 2>/dev/null) +NOW_TS=$(date +%s%N) # nanoseconds for precision + +# Helper: format bytes/sec to human-readable +format_rate() { + local bytes_per_sec="$1" + if [ "$bytes_per_sec" -lt 0 ]; then + bytes_per_sec=0 + fi + if [ "$bytes_per_sec" -ge 1073741824 ]; then + awk -v n="$bytes_per_sec" 'BEGIN { printf "%.1fG", n / 1073741824 }' + elif [ "$bytes_per_sec" -ge 1048576 ]; then + awk -v n="$bytes_per_sec" 'BEGIN { printf "%.1fM", n / 1048576 }' + elif [ "$bytes_per_sec" -ge 1024 ]; then + awk -v n="$bytes_per_sec" 'BEGIN { printf "%.1fK", n / 1024 }' + else + awk -v n="$bytes_per_sec" 'BEGIN { printf "%.1fB", n }' + fi +} + +RATE_DOWN="" +RATE_UP="" + +if [ -f "$STATE_FILE" ]; then + # Read previous values: rx tx timestamp_ns + read -r PREV_RX PREV_TX PREV_TS < "$STATE_FILE" + + if [ -n "$PREV_RX" ] && [ -n "$PREV_TX" ] && [ -n "$PREV_TS" ]; then + # Calculate time delta in seconds (nanosecond difference) + DELTA_NS=$((NOW_TS - PREV_TS)) + if [ "$DELTA_NS" -gt 0 ]; then + DELTA_RX=$((NOW_RX - PREV_RX)) + DELTA_TX=$((NOW_TX - PREV_TX)) + + # bytes per second = delta_bytes / (delta_ns / 1e9) + RX_BPS=$(awk -v rx="$DELTA_RX" -v ns="$DELTA_NS" 'BEGIN { printf "%.0f", rx * 1000000000 / ns }') + TX_BPS=$(awk -v tx="$DELTA_TX" -v ns="$DELTA_NS" 'BEGIN { printf "%.0f", tx * 1000000000 / ns }') + + if [ -n "$RX_BPS" ] && [ -n "$TX_BPS" ]; then + RATE_DOWN=$(format_rate "$RX_BPS") + RATE_UP=$(format_rate "$TX_BPS") + fi + fi + fi +fi + +# Save current state for next run +echo "$NOW_RX $NOW_TX $NOW_TS" > "$STATE_FILE" + +# --- Build output --- + +if [ -n "$RATE_DOWN" ] && [ -n "$RATE_UP" ]; then + RATE_STR=" ↓${RATE_DOWN} ↑${RATE_UP}" +else + RATE_STR="" +fi + +echo "{\"text\": \"$ICON $INTERFACE $IP${RATE_STR}\", \"tooltip\": \"$TYPE: $INTERFACE — $IP | ↓ ${RATE_DOWN:-?}/s ↑ ${RATE_UP:-?}/s\"}" diff --git a/.config/waybar/style.css b/.config/waybar/style.css new file mode 100644 index 0000000..a9d2135 --- /dev/null +++ b/.config/waybar/style.css @@ -0,0 +1,77 @@ +* { + font-size: 13px; + font-family: monospace; +} + +window#waybar { + background: #282828; + color: #fbf1c7; +} + +#custom-right-arrow-dark, +#custom-left-arrow-dark { + color: #1d2021; +} +#custom-right-arrow-light, +#custom-left-arrow-light { + color: #282828; + background: #1d2021; +} + +#workspaces, +#window, +#clock.0, +#clock.2, +#clock.3, +#pulseaudio, +#memory, +#custom-top, +#battery, +#custom-disk, +#custom-network, +#tray { + background: #1d2021; +} + +#workspaces button { + padding: 0 2px; + color: #a89984; +} +#workspaces button.focused { + color: #fabd2f; + font-weight: bold; + /* border-bottom: 2px solid #fabd2f; */ +} +#workspaces button:hover { + box-shadow: inherit; + text-shadow: inherit; +} +#workspaces button:hover { + background: #1d2021; + border: #1d2021; + padding: 0 3px; +} + +/* Right-side widgets: lighter sandy yellow */ +#window, +#clock, +#pulseaudio, +#memory, +#custom-top, +#battery, +#custom-disk, +#custom-network, +#tray { + color: #f5d67b; +} + +#window, +#clock, +#pulseaudio, +#memory, +#custom-top, +#battery, +#custom-disk, +#custom-network { + padding: 0 10px; +} diff --git a/.config/waybar/top.sh b/.config/waybar/top.sh new file mode 100755 index 0000000..ae05501 --- /dev/null +++ b/.config/waybar/top.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Get the top CPU-consuming process, excluding kernel threads and this script +read -r NAME CPU < <( + ps -eo comm,pcpu --sort=-pcpu --no-headers 2>/dev/null | + awk '{ + # Skip kernel threads (names in brackets) + if ($1 ~ /^\[/) next + # Skip this script and ps itself + if ($1 == "top.sh" || $1 == "ps") next + # Output the first matching line and exit + print $1, $2 + exit + }' +) + +if [ -z "$NAME" ]; then + echo '{"text": " --", "tooltip": "No process data"}' + exit 0 +fi + +# Round CPU to integer +CPU=$(printf "%.0f" "$CPU") + +# Truncate long process names (keep bar readable) +if [ ${#NAME} -gt 10 ]; then + NAME="${NAME:0:9}…" +fi + +echo "{\"text\": \"󰍛 $NAME ${CPU}%\", \"tooltip\": \"Top CPU: $NAME — ${CPU}%\"}" diff --git a/.config/waybar/watch.sh b/.config/waybar/watch.sh new file mode 100755 index 0000000..4474036 --- /dev/null +++ b/.config/waybar/watch.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +while inotifywait -e close_write ~/.config/waybar; do + killall -SIGUSR2 waybar +done