This commit is contained in:
David Aizenberg
2026-02-13 13:27:05 +01:00
parent 85a18a0091
commit 9d239329c0
6 changed files with 691 additions and 282 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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"; }
}

View File

@@ -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,48 +198,144 @@ 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()
}
}
}
Timer {
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")
}
}
}
}
}
}
}
}
}

View File

@@ -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;
}