mirror of
https://github.com/neoromantique/dotfiles.git
synced 2026-03-13 21:53:20 +03:00
sync
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
# dotfiles
|
# 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.
|
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
|
```bash
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
$background = rgb(150E11)
|
$background = rgb(0f0f0f)
|
||||||
$foreground = rgb(FEEFF0)
|
$foreground = rgb(e0e0e0)
|
||||||
$color0 = rgb(3D373A)
|
$color0 = rgb(1a1a1a)
|
||||||
$color1 = rgb(220615)
|
$color1 = rgb(e74c3c)
|
||||||
$color2 = rgb(08432C)
|
$color2 = rgb(2ecc71)
|
||||||
$color3 = rgb(CE2E9E)
|
$color3 = rgb(f1c40f)
|
||||||
$color4 = rgb(0881BC)
|
$color4 = rgb(3498db)
|
||||||
$color5 = rgb(C1E177)
|
$color5 = rgb(e67e22)
|
||||||
$color6 = rgb(FCD0D3)
|
$color6 = rgb(b0b0b0)
|
||||||
$color7 = rgb(F5DFE1)
|
$color7 = rgb(e0e0e0)
|
||||||
$color8 = rgb(AC9C9D)
|
$color8 = rgb(333333)
|
||||||
$color9 = rgb(220615)
|
$color9 = rgb(e74c3c)
|
||||||
$color10 = rgb(08432C)
|
$color10 = rgb(2ecc71)
|
||||||
$color11 = rgb(CE2E9E)
|
$color11 = rgb(e67e22)
|
||||||
$color12 = rgb(0881BC)
|
$color12 = rgb(3498db)
|
||||||
$color13 = rgb(C1E177)
|
$color13 = rgb(f1c40f)
|
||||||
$color14 = rgb(FCD0D3)
|
$color14 = rgb(b0b0b0)
|
||||||
$color15 = rgb(F5DFE1)
|
$color15 = rgb(e0e0e0)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ source = ~/.config/hypr/monitors.conf
|
|||||||
### MY PROGRAMS ###
|
### MY PROGRAMS ###
|
||||||
###################
|
###################
|
||||||
|
|
||||||
$terminal = foot
|
$terminal = alacritty
|
||||||
$fileManager = thunar
|
$fileManager = thunar
|
||||||
$menu = wofi --show drun
|
$menu = wofi --show drun
|
||||||
$run_menu = wofi --show run
|
$run_menu = wofi --show run
|
||||||
@@ -27,40 +27,43 @@ $run_menu = wofi --show run
|
|||||||
|
|
||||||
env = XCURSOR_SIZE,24
|
env = XCURSOR_SIZE,24
|
||||||
env = HYPRCURSOR_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 ###
|
### LOOK AND FEEL ###
|
||||||
#####################
|
#####################
|
||||||
|
|
||||||
general {
|
general {
|
||||||
gaps_in = 2
|
gaps_in = 0
|
||||||
gaps_out = 5
|
gaps_out = 0
|
||||||
border_size = 2
|
border_size = 1
|
||||||
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
|
col.active_border = rgb(e67e22)
|
||||||
col.inactive_border = rgba(595959aa)
|
col.inactive_border = rgb(333333)
|
||||||
resize_on_border = false
|
resize_on_border = true
|
||||||
allow_tearing = false
|
allow_tearing = false
|
||||||
layout = dwindle
|
layout = dwindle
|
||||||
}
|
}
|
||||||
|
|
||||||
decoration {
|
decoration {
|
||||||
rounding = 10
|
rounding = 0
|
||||||
rounding_power = 2
|
|
||||||
active_opacity = 1.0
|
active_opacity = 1.0
|
||||||
inactive_opacity = 1.0
|
inactive_opacity = 1.0
|
||||||
|
|
||||||
shadow {
|
shadow {
|
||||||
enabled = false
|
enabled = false
|
||||||
range = 4
|
range = 2
|
||||||
render_power = 3
|
render_power = 5
|
||||||
color = rgba(1a1a1aee)
|
color = rgba(000000d9)
|
||||||
}
|
}
|
||||||
|
|
||||||
blur {
|
blur {
|
||||||
enabled = false
|
enabled = false
|
||||||
size = 3
|
|
||||||
passes = 1
|
|
||||||
vibrancy = 0.1696
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,8 +128,8 @@ master {
|
|||||||
}
|
}
|
||||||
|
|
||||||
misc {
|
misc {
|
||||||
force_default_wallpaper = -1
|
force_default_wallpaper = 0
|
||||||
disable_hyprland_logo = false
|
disable_hyprland_logo = true
|
||||||
enable_anr_dialog = false
|
enable_anr_dialog = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +306,7 @@ plugin {
|
|||||||
hyprexpo {
|
hyprexpo {
|
||||||
columns = 3
|
columns = 3
|
||||||
gap_size = 5
|
gap_size = 5
|
||||||
bg_col = rgb(111111)
|
bg_col = rgb(0f0f0f)
|
||||||
workspace_method = first 1
|
workspace_method = first 1
|
||||||
{{- if .hasTouchpad }}
|
{{- if .hasTouchpad }}
|
||||||
enable_gesture = true
|
enable_gesture = true
|
||||||
|
|||||||
@@ -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"; }
|
|
||||||
}
|
|
||||||
@@ -8,12 +8,14 @@ import Quickshell.Widgets
|
|||||||
ShellRoot {
|
ShellRoot {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property color bgColor: "#141414"
|
property color bgColor: "#0f0f0f"
|
||||||
property color fgColor: "#cccccc"
|
property color fgColor: "#e0e0e0"
|
||||||
property color dimColor: "#666666"
|
property color dimColor: "#888888"
|
||||||
property color okColor: "#00ff00"
|
property color accentColor: "#e67e22"
|
||||||
property color warnColor: "#ffaa00"
|
property color okColor: "#2ecc71"
|
||||||
property color critColor: "#ff0000"
|
property color warnColor: "#f1c40f"
|
||||||
|
property color critColor: "#e74c3c"
|
||||||
|
property color borderColor: "#333333"
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: shellExec
|
id: shellExec
|
||||||
@@ -29,21 +31,33 @@ ShellRoot {
|
|||||||
return (s || "").toString().trim()
|
return (s || "").toString().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function wsLabel(name) {
|
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"})
|
||||||
if (name === "1") return "WEB"
|
|
||||||
if (name === "2") return "CODE"
|
function setWsLabel(wsId, label) {
|
||||||
if (name === "3") return "TERM"
|
var n = Object.assign({}, wsNames)
|
||||||
if (name === "4") return "IDE"
|
n[wsId] = label
|
||||||
if (name === "5") return "VM"
|
wsNames = n
|
||||||
if (name === "6") return "GFX"
|
wsNamesSaver.command = ["/usr/bin/env", "bash", "-c", "echo '" + JSON.stringify(n) + "' > ~/.config/quickshell/ws-names.json"]
|
||||||
if (name === "7") return "DOC"
|
wsNamesSaver.running = true
|
||||||
if (name === "8") return "GAME"
|
}
|
||||||
if (name === "9") return "MISC"
|
|
||||||
if (name === "10") return "TMP"
|
Process {
|
||||||
if (name === "special:termius") return "s:term"
|
id: wsNamesLoader
|
||||||
if (name === "special:org") return "s:org"
|
command: ["/usr/bin/env", "bash", "-c", "cat ~/.config/quickshell/ws-names.json 2>/dev/null || echo '{}'"]
|
||||||
if (name === "special:llm") return "s:llm"
|
running: true
|
||||||
return "WS"
|
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) {
|
function wsIgnored(name) {
|
||||||
@@ -51,9 +65,9 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
component ModuleBox: Rectangle {
|
component ModuleBox: Rectangle {
|
||||||
color: "transparent"
|
color: "#1a1a1a"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: root.fgColor
|
border.color: root.borderColor
|
||||||
radius: 0
|
radius: 0
|
||||||
implicitHeight: 22
|
implicitHeight: 22
|
||||||
}
|
}
|
||||||
@@ -67,7 +81,9 @@ ShellRoot {
|
|||||||
|
|
||||||
screen: modelData
|
screen: modelData
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
exclusionMode: ExclusionMode.Normal
|
exclusionMode: ExclusionMode.Auto
|
||||||
|
exclusiveZone: 34
|
||||||
|
aboveWindows: true
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
bottom: true
|
bottom: true
|
||||||
@@ -84,6 +100,8 @@ ShellRoot {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 4
|
anchors.margins: 4
|
||||||
color: root.bgColor
|
color: root.bgColor
|
||||||
|
border.width: 1
|
||||||
|
border.color: root.borderColor
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: mainRow
|
id: mainRow
|
||||||
@@ -116,9 +134,9 @@ ShellRoot {
|
|||||||
visible: !ignored
|
visible: !ignored
|
||||||
color: modelData.urgent
|
color: modelData.urgent
|
||||||
? root.critColor
|
? root.critColor
|
||||||
: (modelData.active ? root.fgColor : "transparent")
|
: (modelData.active ? root.accentColor : "transparent")
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: modelData.urgent ? root.critColor : root.fgColor
|
border.color: modelData.urgent ? root.critColor : root.borderColor
|
||||||
radius: 0
|
radius: 0
|
||||||
implicitHeight: 18
|
implicitHeight: 18
|
||||||
implicitWidth: wsText.implicitWidth + 10
|
implicitWidth: wsText.implicitWidth + 10
|
||||||
@@ -126,16 +144,27 @@ ShellRoot {
|
|||||||
Text {
|
Text {
|
||||||
id: wsText
|
id: wsText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: root.wsLabel(modelData.name)
|
text: root.wsNames[modelData.name] || "WS"
|
||||||
color: modelData.active || modelData.urgent ? "#000000" : root.fgColor
|
color: modelData.active || modelData.urgent ? "#111111" : root.fgColor
|
||||||
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
|
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
onClicked: modelData.activate()
|
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
|
id: vpnBox
|
||||||
implicitWidth: vpnText.implicitWidth + 12
|
implicitWidth: vpnText.implicitWidth + 12
|
||||||
property string vpnClass: "disconnected"
|
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 {
|
Text {
|
||||||
id: vpnText
|
id: vpnText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "VPN off"
|
text: "VPN off"
|
||||||
color: vpnBox.vpnClass === "connected"
|
color: vpnBox.vpnClass === "connected"
|
||||||
? root.okColor
|
? root.accentColor
|
||||||
: (vpnBox.vpnClass === "disconnected" ? root.dimColor : root.fgColor)
|
: (vpnBox.vpnClass === "disconnected" ? root.dimColor : root.fgColor)
|
||||||
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
|
font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace"
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: vpnProc
|
id: wgProc
|
||||||
command: ["/usr/bin/env", "bash", "-lc", "~/.local/bin/vpn-status"]
|
command: ["/usr/bin/env", "bash", "-c", "ip link show type wireguard 2>/dev/null || true"]
|
||||||
running: true
|
running: true
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
const raw = root.trim(this.text)
|
vpnBox.wgRaw = (this.text || "").trim()
|
||||||
try {
|
vpnBox.vpnPending--
|
||||||
const parsed = JSON.parse(raw)
|
if (vpnBox.vpnPending <= 0) vpnBox.updateVpnStatus()
|
||||||
vpnText.text = parsed.text || "VPN off"
|
}
|
||||||
vpnBox.vpnClass = parsed.class || "disconnected"
|
}
|
||||||
} catch (_) {
|
}
|
||||||
vpnText.text = raw || "VPN off"
|
|
||||||
vpnBox.vpnClass = "disconnected"
|
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
|
interval: 5000
|
||||||
running: true
|
running: true
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: vpnProc.running = true
|
onTriggered: {
|
||||||
|
vpnBox.vpnPending = 3
|
||||||
|
wgProc.running = true
|
||||||
|
nbProc.running = true
|
||||||
|
tsProc.running = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.LeftButton
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
window#waybar {
|
window#waybar {
|
||||||
background: #141414;
|
background: #0f0f0f;
|
||||||
color: #cccccc;
|
color: #e0e0e0;
|
||||||
border: 0px solid #cccccc;
|
border: 1px solid #333333;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
margin: 4px 6px;
|
margin: 4px 8px;
|
||||||
padding: 2px 6px;
|
padding: 2px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Boxy ASCII-ish modules */
|
/* Retroism-inspired module chrome */
|
||||||
#workspaces button,
|
#workspaces button,
|
||||||
#custom-vpn,
|
#custom-vpn,
|
||||||
#pulseaudio,
|
#pulseaudio,
|
||||||
@@ -26,63 +26,63 @@ window#waybar {
|
|||||||
#memory,
|
#memory,
|
||||||
#clock,
|
#clock,
|
||||||
#tray {
|
#tray {
|
||||||
background: transparent;
|
background: #1a1a1a;
|
||||||
color: #cccccc;
|
color: #e0e0e0;
|
||||||
border: 1px dashed #cccccc;
|
border: 1px solid #333333;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 1px 6px;
|
padding: 1px 6px;
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Workspaces: focused/urgent with inverse/red */
|
/* Workspaces */
|
||||||
#workspaces button:hover {
|
#workspaces button:hover {
|
||||||
background: #001900;
|
background: #232323;
|
||||||
}
|
}
|
||||||
#workspaces button.focused,
|
#workspaces button.focused,
|
||||||
#workspaces button.active {
|
#workspaces button.active {
|
||||||
background: #cccccc;
|
background: #7a4210;
|
||||||
color: #000;
|
color: #ffffff;
|
||||||
border-color: #cccccc;
|
border-color: #e67e22;
|
||||||
}
|
}
|
||||||
#workspaces button.urgent {
|
#workspaces button.urgent {
|
||||||
background: #ff0000;
|
background: #6b1e13;
|
||||||
color: #000;
|
color: #ffffff;
|
||||||
border-color: #ff0000;
|
border-color: #e74c3c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Specials: dotted border to mark them */
|
/* Specials keep a distinct dashed edge */
|
||||||
#workspaces button.special {
|
#workspaces button.special {
|
||||||
border-style: dotted;
|
border-style: dotted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* VPN status colors */
|
/* VPN status colors */
|
||||||
#custom-vpn.connected {
|
#custom-vpn.connected {
|
||||||
color: #00ff00;
|
color: #e67e22;
|
||||||
border-color: #00ff00;
|
border-color: #e67e22;
|
||||||
}
|
}
|
||||||
#custom-vpn.connected-no-tunnel {
|
#custom-vpn.connected-no-tunnel {
|
||||||
color: #cccccc;
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
#custom-vpn.disconnected {
|
#custom-vpn.disconnected {
|
||||||
color: #666666;
|
color: #888888;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Battery states */
|
/* Battery states */
|
||||||
#battery.warning {
|
#battery.warning {
|
||||||
color: #ffaa00;
|
color: #f1c40f;
|
||||||
border-color: #ffaa00;
|
border-color: #f1c40f;
|
||||||
}
|
}
|
||||||
#battery.critical {
|
#battery.critical {
|
||||||
color: #ff0000;
|
color: #e74c3c;
|
||||||
border-color: #ff0000;
|
border-color: #e74c3c;
|
||||||
}
|
}
|
||||||
#battery.charging {
|
#battery.charging {
|
||||||
color: #00ff00;
|
color: #2ecc71;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Retro tooltip */
|
/* Retro tooltip */
|
||||||
tooltip {
|
tooltip {
|
||||||
background: #000;
|
background: #0f0f0f;
|
||||||
color: #cccccc;
|
color: #e0e0e0;
|
||||||
border: 1px solid #cccccc;
|
border: 1px solid #333333;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user