From 9d239329c08606a30e63d75a49418fce2c715eef Mon Sep 17 00:00:00 2001 From: David Aizenberg Date: Fri, 13 Feb 2026 13:27:05 +0100 Subject: [PATCH] sync --- README.md | 2 + home/private_dot_config/hypr/colors.conf | 36 +- .../hypr/hyprland.conf.tmpl | 39 +- home/private_dot_config/niri/config.kdl.tmpl | 172 ----- .../quickshell/shell.qml.tmpl | 664 ++++++++++++++++-- home/private_dot_config/waybar/style.css | 60 +- 6 files changed, 691 insertions(+), 282 deletions(-) delete mode 100644 home/private_dot_config/niri/config.kdl.tmpl diff --git a/README.md b/README.md index 2fc7d54..1bd27ed 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # dotfiles +### caveat: these are mostly for own reference while setting up devices, most of these are llm slop + Dotfiles managed with [chezmoi](https://chezmoi.io/). Supports CachyOS (desktop) and Bluefin (laptop) with device-specific configs for Hyprland, Waybar, and related tools. Templates handle monitor setup, touchpad/gestures, battery modules, and idle behavior automatically based on hostname detection. ```bash diff --git a/home/private_dot_config/hypr/colors.conf b/home/private_dot_config/hypr/colors.conf index 0932f1b..fea003a 100644 --- a/home/private_dot_config/hypr/colors.conf +++ b/home/private_dot_config/hypr/colors.conf @@ -1,18 +1,18 @@ -$background = rgb(150E11) -$foreground = rgb(FEEFF0) -$color0 = rgb(3D373A) -$color1 = rgb(220615) -$color2 = rgb(08432C) -$color3 = rgb(CE2E9E) -$color4 = rgb(0881BC) -$color5 = rgb(C1E177) -$color6 = rgb(FCD0D3) -$color7 = rgb(F5DFE1) -$color8 = rgb(AC9C9D) -$color9 = rgb(220615) -$color10 = rgb(08432C) -$color11 = rgb(CE2E9E) -$color12 = rgb(0881BC) -$color13 = rgb(C1E177) -$color14 = rgb(FCD0D3) -$color15 = rgb(F5DFE1) +$background = rgb(0f0f0f) +$foreground = rgb(e0e0e0) +$color0 = rgb(1a1a1a) +$color1 = rgb(e74c3c) +$color2 = rgb(2ecc71) +$color3 = rgb(f1c40f) +$color4 = rgb(3498db) +$color5 = rgb(e67e22) +$color6 = rgb(b0b0b0) +$color7 = rgb(e0e0e0) +$color8 = rgb(333333) +$color9 = rgb(e74c3c) +$color10 = rgb(2ecc71) +$color11 = rgb(e67e22) +$color12 = rgb(3498db) +$color13 = rgb(f1c40f) +$color14 = rgb(b0b0b0) +$color15 = rgb(e0e0e0) diff --git a/home/private_dot_config/hypr/hyprland.conf.tmpl b/home/private_dot_config/hypr/hyprland.conf.tmpl index eabd99f..04cb93b 100644 --- a/home/private_dot_config/hypr/hyprland.conf.tmpl +++ b/home/private_dot_config/hypr/hyprland.conf.tmpl @@ -16,7 +16,7 @@ source = ~/.config/hypr/monitors.conf ### MY PROGRAMS ### ################### -$terminal = foot +$terminal = alacritty $fileManager = thunar $menu = wofi --show drun $run_menu = wofi --show run @@ -27,40 +27,43 @@ $run_menu = wofi --show run env = XCURSOR_SIZE,24 env = HYPRCURSOR_SIZE,24 +env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1 +env = GTK_CSD,0 + +# Cursor behavior (avoids hw cursor glitches on some setups) +cursor { + no_hardware_cursors = true +} ##################### ### LOOK AND FEEL ### ##################### general { - gaps_in = 2 - gaps_out = 5 - border_size = 2 - col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg - col.inactive_border = rgba(595959aa) - resize_on_border = false + gaps_in = 0 + gaps_out = 0 + border_size = 1 + col.active_border = rgb(e67e22) + col.inactive_border = rgb(333333) + resize_on_border = true allow_tearing = false layout = dwindle } decoration { - rounding = 10 - rounding_power = 2 + rounding = 0 active_opacity = 1.0 inactive_opacity = 1.0 shadow { enabled = false - range = 4 - render_power = 3 - color = rgba(1a1a1aee) + range = 2 + render_power = 5 + color = rgba(000000d9) } blur { enabled = false - size = 3 - passes = 1 - vibrancy = 0.1696 } } @@ -125,8 +128,8 @@ master { } misc { - force_default_wallpaper = -1 - disable_hyprland_logo = false + force_default_wallpaper = 0 + disable_hyprland_logo = true enable_anr_dialog = false } @@ -303,7 +306,7 @@ plugin { hyprexpo { columns = 3 gap_size = 5 - bg_col = rgb(111111) + bg_col = rgb(0f0f0f) workspace_method = first 1 {{- if .hasTouchpad }} enable_gesture = true diff --git a/home/private_dot_config/niri/config.kdl.tmpl b/home/private_dot_config/niri/config.kdl.tmpl deleted file mode 100644 index fb2d44a..0000000 --- a/home/private_dot_config/niri/config.kdl.tmpl +++ /dev/null @@ -1,172 +0,0 @@ -// Niri configuration -// Device: {{ .deviceProfile }} ({{ .hostname }}) -// Managed by chezmoi - edit source at ~/dotfiles - -input { - keyboard { - xkb { - layout "us,ru" - options "caps:escape" - } - numlock - } - -{{- if .hasTouchpad }} - touchpad { - tap - dwt - natural-scroll - } -{{- end }} - - focus-follows-mouse -} - -{{- if eq .deviceProfile "desktop" }} -output "{{ .primaryMonitor }}" { - mode "{{ .primaryResolution }}" - position x=0 y=0 -} - -{{- if .secondaryMonitor }} -output "{{ .secondaryMonitor }}" { - mode "{{ .secondaryResolution }}" -} -{{- end }} -{{- else }} -output "{{ .primaryMonitor }}" { - mode "{{ .primaryResolution }}" -} -{{- end }} - -layout { - gaps 5 - center-focused-column "never" - - focus-ring { - off - } - - border { - on - width 2 - // Subtle orange gradient for a muted "Hackers (1995)" look. - active-gradient from="#ff9a33" to="#ffad55" angle=90 - inactive-color "#3a2a12cc" - urgent-color "#ff0000" - } - - shadow { - off - } -} - -animations { - off -} - -// Avoid border/background fill showing through transparent terminals. -prefer-no-csd - -// Startup apps (portable subset from Hyprland autostart) -spawn-at-startup "nm-applet" -spawn-at-startup "qs" -spawn-at-startup "swaync" -spawn-at-startup "hyprpaper" -spawn-at-startup "hypridle" -spawn-at-startup "copyq" "--start-server" -spawn-at-startup "zen-browser" - -// Keep quick memo popup floating. -window-rule { - match app-id=r#"^(yad)$"# - open-floating true -} - -binds { - Mod+Shift+Slash { show-hotkey-overlay; } - - // Terminal / launcher - Mod+Q { spawn "alacritty"; } - Mod+R { spawn-sh "wofi --show drun"; } - - // Core controls - Mod+K repeat=false { close-window; } - Mod+M { quit; } - Mod+V { toggle-window-floating; } - Mod+E { toggle-overview; } - Mod+F { maximize-column; } - Mod+Shift+F { fullscreen-window; } - - // Layout toggle (us/ru) - Mod+Space { switch-layout "next"; } - - // Lock - Super+Alt+L { spawn-sh "pactl set-sink-mute @DEFAULT_SINK@ 1 && hyprlock"; } - - // VPN and screenshot helpers - F6 { spawn-sh "~/.local/bin/vpn-switcher"; } - Print { spawn-sh "~/.local/bin/screenshot"; } - - // Snap-like half-screen placement for focused window. - // Keep navigation on Mod+H / Mod+L. - Mod+Left { - spawn-sh "niri msg action move-window-to-floating && niri msg action set-window-width '50%' && niri msg action move-floating-window -x -100000"; - } - Mod+Right { - spawn-sh "niri msg action move-window-to-floating && niri msg action set-window-width '50%' && niri msg action move-floating-window -x +100000"; - } - Mod+Up { focus-window-up; } - Mod+Down { focus-window-down; } - Mod+H { focus-column-left; } - Mod+L { focus-column-right; } - Mod+J { focus-window-down; } - Mod+I { focus-window-up; } - - // Move windows/columns - Mod+Ctrl+Left { move-column-left; } - Mod+Ctrl+Right { move-column-right; } - Mod+Ctrl+Up { move-window-up; } - Mod+Ctrl+Down { move-window-down; } - - // Workspace navigation - Mod+1 { focus-workspace 1; } - Mod+2 { focus-workspace 2; } - Mod+3 { focus-workspace 3; } - Mod+4 { focus-workspace 4; } - Mod+5 { focus-workspace 5; } - Mod+6 { focus-workspace 6; } - Mod+7 { focus-workspace 7; } - Mod+8 { focus-workspace 8; } - Mod+9 { focus-workspace 9; } - - Mod+Ctrl+1 { move-column-to-workspace 1; } - Mod+Ctrl+2 { move-column-to-workspace 2; } - Mod+Ctrl+3 { move-column-to-workspace 3; } - Mod+Ctrl+4 { move-column-to-workspace 4; } - Mod+Ctrl+5 { move-column-to-workspace 5; } - Mod+Ctrl+6 { move-column-to-workspace 6; } - Mod+Ctrl+7 { move-column-to-workspace 7; } - Mod+Ctrl+8 { move-column-to-workspace 8; } - Mod+Ctrl+9 { move-column-to-workspace 9; } - - // Scroll workspaces - Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; } - Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; } - - // Media keys - XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.05+ -l 1.0"; } - XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.05-"; } - XF86AudioMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; } - XF86AudioMicMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; } - -{{- if .hasBattery }} - XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "set" "+10%"; } - XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "set" "10%-"; } -{{- end }} - - XF86AudioNext allow-when-locked=true { spawn-sh "playerctl next"; } - XF86AudioPause allow-when-locked=true { spawn-sh "playerctl play-pause"; } - XF86AudioPlay allow-when-locked=true { spawn-sh "playerctl play-pause"; } - XF86AudioPrev allow-when-locked=true { spawn-sh "playerctl previous"; } -} diff --git a/home/private_dot_config/quickshell/shell.qml.tmpl b/home/private_dot_config/quickshell/shell.qml.tmpl index 685e224..9a9a9f8 100644 --- a/home/private_dot_config/quickshell/shell.qml.tmpl +++ b/home/private_dot_config/quickshell/shell.qml.tmpl @@ -8,12 +8,14 @@ import Quickshell.Widgets ShellRoot { id: root - property color bgColor: "#141414" - property color fgColor: "#cccccc" - property color dimColor: "#666666" - property color okColor: "#00ff00" - property color warnColor: "#ffaa00" - property color critColor: "#ff0000" + 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 @@ -29,21 +31,33 @@ ShellRoot { return (s || "").toString().trim() } - function wsLabel(name) { - if (name === "1") return "WEB" - if (name === "2") return "CODE" - if (name === "3") return "TERM" - if (name === "4") return "IDE" - if (name === "5") return "VM" - if (name === "6") return "GFX" - if (name === "7") return "DOC" - if (name === "8") return "GAME" - if (name === "9") return "MISC" - if (name === "10") return "TMP" - if (name === "special:termius") return "s:term" - if (name === "special:org") return "s:org" - if (name === "special:llm") return "s:llm" - return "WS" + property var wsNames: ({"1": "WEB", "2": "CODE", "3": "TERM", "4": "IDE", "5": "VM", "6": "GFX", "7": "DOC", "8": "GAME", "9": "MISC", "10": "TMP", "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) { @@ -51,9 +65,9 @@ ShellRoot { } component ModuleBox: Rectangle { - color: "transparent" + color: "#1a1a1a" border.width: 1 - border.color: root.fgColor + border.color: root.borderColor radius: 0 implicitHeight: 22 } @@ -67,7 +81,9 @@ ShellRoot { screen: modelData color: "transparent" - exclusionMode: ExclusionMode.Normal + exclusionMode: ExclusionMode.Auto + exclusiveZone: 34 + aboveWindows: true anchors { bottom: true @@ -84,6 +100,8 @@ ShellRoot { anchors.fill: parent anchors.margins: 4 color: root.bgColor + border.width: 1 + border.color: root.borderColor Row { id: mainRow @@ -116,9 +134,9 @@ ShellRoot { visible: !ignored color: modelData.urgent ? root.critColor - : (modelData.active ? root.fgColor : "transparent") + : (modelData.active ? root.accentColor : "transparent") border.width: 1 - border.color: modelData.urgent ? root.critColor : root.fgColor + border.color: modelData.urgent ? root.critColor : root.borderColor radius: 0 implicitHeight: 18 implicitWidth: wsText.implicitWidth + 10 @@ -126,16 +144,27 @@ ShellRoot { Text { id: wsText anchors.centerIn: parent - text: root.wsLabel(modelData.name) - color: modelData.active || modelData.urgent ? "#000000" : root.fgColor + 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 - onClicked: modelData.activate() + 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() + } + } } } } @@ -169,33 +198,121 @@ ShellRoot { 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.okColor + ? root.accentColor : (vpnBox.vpnClass === "disconnected" ? root.dimColor : root.fgColor) font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" font.pixelSize: 12 } Process { - id: vpnProc - command: ["/usr/bin/env", "bash", "-lc", "~/.local/bin/vpn-status"] + id: wgProc + command: ["/usr/bin/env", "bash", "-c", "ip link show type wireguard 2>/dev/null || true"] running: true stdout: StdioCollector { onStreamFinished: { - const raw = root.trim(this.text) - try { - const parsed = JSON.parse(raw) - vpnText.text = parsed.text || "VPN off" - vpnBox.vpnClass = parsed.class || "disconnected" - } catch (_) { - vpnText.text = raw || "VPN off" - vpnBox.vpnClass = "disconnected" - } + 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() } } } @@ -204,13 +321,21 @@ ShellRoot { interval: 5000 running: true repeat: true - onTriggered: vpnProc.running = true + onTriggered: { + vpnBox.vpnPending = 3 + wgProc.running = true + nbProc.running = true + tsProc.running = true + } } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton - onClicked: root.runShell("~/.local/bin/vpn-switcher") + onClicked: { + vpnPopup.visible = !vpnPopup.visible + if (vpnPopup.visible) vpnPopup.refresh() + } } } @@ -563,6 +688,457 @@ ShellRoot { } } } + + 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") + } + } + } + } + } + } } } } diff --git a/home/private_dot_config/waybar/style.css b/home/private_dot_config/waybar/style.css index 689f8b6..88f4341 100644 --- a/home/private_dot_config/waybar/style.css +++ b/home/private_dot_config/waybar/style.css @@ -7,15 +7,15 @@ } window#waybar { - background: #141414; - color: #cccccc; - border: 0px solid #cccccc; + background: #0f0f0f; + color: #e0e0e0; + border: 1px solid #333333; border-radius: 0; - margin: 4px 6px; - padding: 2px 6px; + margin: 4px 8px; + padding: 2px 8px; } -/* Boxy ASCII-ish modules */ +/* Retroism-inspired module chrome */ #workspaces button, #custom-vpn, #pulseaudio, @@ -26,63 +26,63 @@ window#waybar { #memory, #clock, #tray { - background: transparent; - color: #cccccc; - border: 1px dashed #cccccc; + background: #1a1a1a; + color: #e0e0e0; + border: 1px solid #333333; border-radius: 0; padding: 1px 6px; margin: 0 3px; } -/* Workspaces: focused/urgent with inverse/red */ +/* Workspaces */ #workspaces button:hover { - background: #001900; + background: #232323; } #workspaces button.focused, #workspaces button.active { - background: #cccccc; - color: #000; - border-color: #cccccc; + background: #7a4210; + color: #ffffff; + border-color: #e67e22; } #workspaces button.urgent { - background: #ff0000; - color: #000; - border-color: #ff0000; + background: #6b1e13; + color: #ffffff; + border-color: #e74c3c; } -/* Specials: dotted border to mark them */ +/* Specials keep a distinct dashed edge */ #workspaces button.special { border-style: dotted; } /* VPN status colors */ #custom-vpn.connected { - color: #00ff00; - border-color: #00ff00; + color: #e67e22; + border-color: #e67e22; } #custom-vpn.connected-no-tunnel { - color: #cccccc; + color: #e0e0e0; } #custom-vpn.disconnected { - color: #666666; + color: #888888; } /* Battery states */ #battery.warning { - color: #ffaa00; - border-color: #ffaa00; + color: #f1c40f; + border-color: #f1c40f; } #battery.critical { - color: #ff0000; - border-color: #ff0000; + color: #e74c3c; + border-color: #e74c3c; } #battery.charging { - color: #00ff00; + color: #2ecc71; } /* Retro tooltip */ tooltip { - background: #000; - color: #cccccc; - border: 1px solid #cccccc; + background: #0f0f0f; + color: #e0e0e0; + border: 1px solid #333333; }