feat(eww): add config
This commit is contained in:
239
eww/eww.scss
Normal file
239
eww/eww.scss
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
// gruvbox-material-soft-dark palette
|
||||||
|
$bg: #32302f;
|
||||||
|
$bg_dim: #252423;
|
||||||
|
$bg_sel: #45403d;
|
||||||
|
$fg: #d4be98;
|
||||||
|
$red: #ea6962;
|
||||||
|
$green: #a9b665;
|
||||||
|
$yellow: #d8a657;
|
||||||
|
$blue: #7daea3;
|
||||||
|
$magenta: #d3869b;
|
||||||
|
$cyan: #89b482;
|
||||||
|
$gray: #928374;
|
||||||
|
|
||||||
|
// base
|
||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
font-family: "JetBrainsMono Nerd Font";
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// transparent backdrop for click-away dismiss
|
||||||
|
.backdrop {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// popup container
|
||||||
|
.popup {
|
||||||
|
background-color: $bg_dim;
|
||||||
|
border: 1px solid $bg_sel;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// section titles
|
||||||
|
.title {
|
||||||
|
color: $fg;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: $gray;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// labels and values
|
||||||
|
.label {
|
||||||
|
color: $gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: $fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value.dim {
|
||||||
|
color: $gray;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// detail lines (smaller, dimmer)
|
||||||
|
.details {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid $bg_sel;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail {
|
||||||
|
color: $gray;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rows
|
||||||
|
.row {
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// progress bars
|
||||||
|
progressbar {
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
trough {
|
||||||
|
min-height: 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: $bg_sel;
|
||||||
|
|
||||||
|
progress {
|
||||||
|
min-height: 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: $green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progressbar.warning trough progress {
|
||||||
|
background-color: $yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressbar.critical trough progress {
|
||||||
|
background-color: $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sliders
|
||||||
|
scale {
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
trough {
|
||||||
|
min-height: 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: $bg_sel;
|
||||||
|
|
||||||
|
highlight {
|
||||||
|
min-height: 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
slider {
|
||||||
|
min-width: 14px;
|
||||||
|
min-height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: $fg;
|
||||||
|
margin: -5px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buttons
|
||||||
|
.toggle-btn {
|
||||||
|
color: $gray;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: $bg_sel;
|
||||||
|
min-width: 30px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: $bg_dim;
|
||||||
|
background-color: $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten($bg_sel, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// device list buttons
|
||||||
|
.device-btn {
|
||||||
|
color: $fg;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $bg_sel;
|
||||||
|
color: $fg;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// device rows (non-clickable list items)
|
||||||
|
.device-row {
|
||||||
|
padding: 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close/disconnect button
|
||||||
|
.close-btn {
|
||||||
|
color: $gray;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $red;
|
||||||
|
background-color: $bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// profile selector buttons
|
||||||
|
.profile-btn {
|
||||||
|
color: $gray;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: $bg;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: $bg_dim;
|
||||||
|
background-color: $cyan;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $bg_sel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// separator line
|
||||||
|
.separator {
|
||||||
|
min-height: 1px;
|
||||||
|
background-color: $bg_sel;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// status indicators
|
||||||
|
.indicator {
|
||||||
|
min-width: 14px;
|
||||||
|
|
||||||
|
&.online {
|
||||||
|
color: $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offline {
|
||||||
|
color: $gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty state
|
||||||
|
.empty {
|
||||||
|
color: $gray;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// active player label
|
||||||
|
.playing {
|
||||||
|
color: $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
// slider row layout
|
||||||
|
.slider-row {
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
421
eww/eww.yuck
Normal file
421
eww/eww.yuck
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
; sway shell popups
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; variables
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defpoll sys :interval "5s"
|
||||||
|
:initial '{"cpu":0,"ram_percent":0,"ram_used":"0Gi","ram_total":"0Gi","temp":0,"disk_percent":0,"disk_used":"0","disk_total":"0","swap":"0/0","load":"0 0 0","uptime":""}'
|
||||||
|
"~/.config/eww/scripts/system.sh")
|
||||||
|
|
||||||
|
(defpoll bat :interval "30s"
|
||||||
|
:initial '{"capacity":0,"status":"Unknown","power":"0","time":"","cycles":0,"profile":"unknown"}'
|
||||||
|
"~/.config/eww/scripts/battery.sh")
|
||||||
|
|
||||||
|
(defpoll vol :interval "5s"
|
||||||
|
:initial '{"volume":0,"muted":false,"mic_volume":0,"mic_muted":false,"brightness":0,"sinks":[],"sources":[],"sink_count":0,"source_count":0}'
|
||||||
|
"~/.config/eww/scripts/volume.sh")
|
||||||
|
|
||||||
|
(defpoll bt :interval "15s"
|
||||||
|
:initial '{"powered":false,"count":0,"devices":[]}'
|
||||||
|
"~/.config/eww/scripts/bluetooth.sh")
|
||||||
|
|
||||||
|
(defpoll net :interval "10s"
|
||||||
|
:initial '{"type":"none","iface":"none","ip":"none","gateway":"none","ssid":"","signal":0,"conn_name":"","count":0,"networks":[],"unknown_count":0,"unknown":[]}'
|
||||||
|
"~/.config/eww/scripts/network.sh")
|
||||||
|
|
||||||
|
(defpoll vpn_data :interval "15s"
|
||||||
|
:initial '{"tailscale":{"running":false,"ip":"","hostname":"","login":"","exit_nodes":[],"exit_count":0,"peers":[],"peer_count":0},"wireguard":{"active":false,"iface":""}}'
|
||||||
|
"~/.config/eww/scripts/vpn.sh")
|
||||||
|
|
||||||
|
(defpoll kbd :interval "10s"
|
||||||
|
:initial '{"current":"unknown","layouts":[],"layout_count":0,"keyboards":[],"kb_count":0}'
|
||||||
|
"~/.config/eww/scripts/keyboard.sh")
|
||||||
|
|
||||||
|
(defpoll media :interval "5s"
|
||||||
|
:initial '{"count":0,"players":[]}'
|
||||||
|
"~/.config/eww/scripts/media.sh")
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; media popup
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwidget media-widget []
|
||||||
|
(box :class "popup" :orientation "v" :space-evenly false :spacing 8
|
||||||
|
(label :class "title" :text "media" :halign "start")
|
||||||
|
(box :visible {media.count == 0}
|
||||||
|
(label :class "empty" :text "no active players"))
|
||||||
|
(box :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(for player in {media.players}
|
||||||
|
(box :class "device-row" :orientation "v" :space-evenly false :spacing 2
|
||||||
|
(box :orientation "h" :space-evenly false :spacing 8
|
||||||
|
(label :class "label ${player.status == 'Playing' ? 'playing' : ''}"
|
||||||
|
:text {player.display} :halign "start" :hexpand true)
|
||||||
|
(button :class "toggle-btn ${player.status == 'Playing' ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/media.sh play-pause '${player.name}'"
|
||||||
|
{player.status == "Playing" ? "pause" : "play"})
|
||||||
|
(button :class "close-btn"
|
||||||
|
:onclick "~/.config/eww/scripts/media.sh prev '${player.name}'"
|
||||||
|
"prev")
|
||||||
|
(button :class "close-btn"
|
||||||
|
:onclick "~/.config/eww/scripts/media.sh next '${player.name}'"
|
||||||
|
"next"))
|
||||||
|
(label :class "value" :text "${player.artist}${player.artist != '' ? ' - ' : ''}${player.title}"
|
||||||
|
:halign "start" :limit-width 45))))))
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; system popup
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwidget system-widget []
|
||||||
|
(box :class "popup" :orientation "v" :space-evenly false :spacing 8
|
||||||
|
(label :class "title" :text "system" :halign "start")
|
||||||
|
|
||||||
|
; cpu
|
||||||
|
(box :class "row" :orientation "v" :space-evenly false :spacing 2
|
||||||
|
(box :orientation "h"
|
||||||
|
(label :class "label" :text "cpu" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text "${sys.cpu}%"))
|
||||||
|
(progress :class "bar" :value {sys.cpu}))
|
||||||
|
|
||||||
|
; ram
|
||||||
|
(box :class "row" :orientation "v" :space-evenly false :spacing 2
|
||||||
|
(box :orientation "h"
|
||||||
|
(label :class "label" :text "ram" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text "${sys.ram_used}/${sys.ram_total}"))
|
||||||
|
(progress :class "bar" :value {sys.ram_percent}))
|
||||||
|
|
||||||
|
; temp
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "label" :text "temp" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text "${sys.temp}°"))
|
||||||
|
|
||||||
|
; disk
|
||||||
|
(box :class "row" :orientation "v" :space-evenly false :spacing 2
|
||||||
|
(box :orientation "h"
|
||||||
|
(label :class "label" :text "disk" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text "${sys.disk_used}/${sys.disk_total}"))
|
||||||
|
(progress :class "bar" :value {sys.disk_percent}))
|
||||||
|
|
||||||
|
; details
|
||||||
|
(box :class "details" :orientation "v" :space-evenly false :spacing 2
|
||||||
|
(label :class "detail" :text "swap ${sys.swap}" :halign "start")
|
||||||
|
(label :class "detail" :text "load ${sys.load}" :halign "start")
|
||||||
|
(label :class "detail" :text "up ${sys.uptime}" :halign "start"))))
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; battery popup
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwidget battery-widget []
|
||||||
|
(box :class "popup" :orientation "v" :space-evenly false :spacing 8
|
||||||
|
(label :class "title" :text "battery" :halign "start")
|
||||||
|
|
||||||
|
; capacity bar
|
||||||
|
(box :class "row" :orientation "v" :space-evenly false :spacing 2
|
||||||
|
(box :orientation "h"
|
||||||
|
(label :class "label" :text {bat.status} :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text "${bat.capacity}%"))
|
||||||
|
(progress :class "bar ${bat.capacity < 15 ? 'critical' : bat.capacity < 30 ? 'warning' : ''}"
|
||||||
|
:value {bat.capacity}))
|
||||||
|
|
||||||
|
; power + time
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "label" :text "power" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text "${bat.power}W"))
|
||||||
|
(box :class "row" :orientation "h" :visible {bat.time != ""}
|
||||||
|
(label :class "label" :text "remaining" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text {bat.time}))
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "label" :text "cycles" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text {bat.cycles}))
|
||||||
|
|
||||||
|
; power profile selector
|
||||||
|
(box :class "separator")
|
||||||
|
(label :class "title" :text "profile" :halign "start")
|
||||||
|
(box :class "profiles" :orientation "h" :space-evenly true :spacing 4
|
||||||
|
(button :class "profile-btn ${bat.profile == 'power-saver' ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/battery.sh set-profile power-saver"
|
||||||
|
"saver")
|
||||||
|
(button :class "profile-btn ${bat.profile == 'balanced' ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/battery.sh set-profile balanced"
|
||||||
|
"balanced")
|
||||||
|
(button :class "profile-btn ${bat.profile == 'performance' ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/battery.sh set-profile performance"
|
||||||
|
"perform"))))
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; volume popup
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwidget volume-widget []
|
||||||
|
(box :class "popup" :orientation "v" :space-evenly false :spacing 8
|
||||||
|
; output section
|
||||||
|
(box :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(label :class "title" :text "output" :halign "start")
|
||||||
|
(box :class "row slider-row" :orientation "h" :space-evenly false :spacing 8
|
||||||
|
(button :class "toggle-btn ${vol.muted ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/volume.sh toggle-mute"
|
||||||
|
{vol.muted ? "muted" : "vol"})
|
||||||
|
(scale :class "slider" :min 0 :max 100 :value {vol.volume}
|
||||||
|
:onchange "pamixer --set-volume {}" :hexpand true)
|
||||||
|
(label :class "value" :text "${vol.volume}%"))
|
||||||
|
(for sink in {vol.sinks}
|
||||||
|
(button :class "device-btn ${sink.active ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/volume.sh set-sink '${sink.sink_name}'"
|
||||||
|
(label :text {sink.name} :halign "start"))))
|
||||||
|
|
||||||
|
; input section
|
||||||
|
(box :class "separator")
|
||||||
|
(box :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(label :class "title" :text "input" :halign "start")
|
||||||
|
(box :class "row slider-row" :orientation "h" :space-evenly false :spacing 8
|
||||||
|
(button :class "toggle-btn ${vol.mic_muted ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/volume.sh toggle-mic"
|
||||||
|
{vol.mic_muted ? "muted" : "mic"})
|
||||||
|
(scale :class "slider" :min 0 :max 100 :value {vol.mic_volume}
|
||||||
|
:onchange "pamixer --default-source --set-volume {}" :hexpand true)
|
||||||
|
(label :class "value" :text "${vol.mic_volume}%"))
|
||||||
|
(for source in {vol.sources}
|
||||||
|
(button :class "device-btn ${source.active ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/volume.sh set-source '${source.source_name}'"
|
||||||
|
(label :text {source.name} :halign "start"))))
|
||||||
|
|
||||||
|
; brightness section
|
||||||
|
(box :class "separator")
|
||||||
|
(label :class "title" :text "brightness" :halign "start")
|
||||||
|
(box :class "row slider-row" :orientation "h" :space-evenly false :spacing 8
|
||||||
|
(label :class "label" :text "bl")
|
||||||
|
(scale :class "slider" :min 0 :max 100 :value {vol.brightness}
|
||||||
|
:onchange "brightnessctl set {}%" :hexpand true)
|
||||||
|
(label :class "value" :text "${vol.brightness}%"))))
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; bluetooth popup
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwidget bluetooth-widget []
|
||||||
|
(box :class "popup" :orientation "v" :space-evenly false :spacing 8
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "title" :text "bluetooth" :halign "start" :hexpand true)
|
||||||
|
(button :class "toggle-btn ${bt.powered ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/bluetooth.sh toggle-power"
|
||||||
|
{bt.powered ? "on" : "off"}))
|
||||||
|
|
||||||
|
(box :visible {bt.powered} :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(for device in {bt.devices}
|
||||||
|
(box :class "device-row" :orientation "h" :space-evenly false :spacing 8
|
||||||
|
(label :class "label" :text {device.name} :halign "start" :hexpand true)
|
||||||
|
(label :class "value dim" :visible {device.battery >= 0}
|
||||||
|
:text "${device.battery}%")
|
||||||
|
(button :class "close-btn"
|
||||||
|
:onclick "~/.config/eww/scripts/bluetooth.sh disconnect '${device.mac}'"
|
||||||
|
"x")))
|
||||||
|
(label :visible {bt.count == 0} :class "empty" :text "no devices connected"))))
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; network popup
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwidget network-widget []
|
||||||
|
(box :class "popup" :orientation "v" :space-evenly false :spacing 8
|
||||||
|
(label :class "title" :text "network" :halign "start")
|
||||||
|
|
||||||
|
; connection info
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "label" :text "interface" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text {net.iface}))
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "label" :text "ip" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text {net.ip}))
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "label" :text "gateway" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text {net.gateway}))
|
||||||
|
(box :class "row" :orientation "h" :visible {net.type == "wifi"}
|
||||||
|
(label :class "label" :text "ssid" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text "${net.ssid} ${net.signal}%"))
|
||||||
|
|
||||||
|
; saved wifi networks nearby
|
||||||
|
(box :visible {net.count > 0} :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(box :class "separator")
|
||||||
|
(label :class "title" :text "saved" :halign "start")
|
||||||
|
(box :orientation "v" :space-evenly false :spacing 0
|
||||||
|
(for network in {net.networks}
|
||||||
|
(button :class "device-btn ${network.active ? 'active' : ''}"
|
||||||
|
:onclick {network.active ? "~/.config/eww/scripts/network.sh disconnect '${network.ssid}'" : "~/.config/eww/scripts/network.sh connect '${network.ssid}'"}
|
||||||
|
(box :orientation "h" :space-evenly false
|
||||||
|
(label :class "label" :text {network.ssid} :halign "start" :hexpand true)
|
||||||
|
(label :class "value dim" :text "${network.signal}%"))))))
|
||||||
|
|
||||||
|
; unknown wifi networks nearby
|
||||||
|
(box :visible {net.unknown_count > 0} :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(box :class "separator")
|
||||||
|
(label :class "title" :text "nearby" :halign "start")
|
||||||
|
(box :orientation "v" :space-evenly false :spacing 0
|
||||||
|
(for network in {net.unknown}
|
||||||
|
(button :class "device-btn"
|
||||||
|
:onclick "~/.config/eww/scripts/network.sh connect-new '${network.ssid}'"
|
||||||
|
(box :orientation "h" :space-evenly false
|
||||||
|
(label :class "label" :text {network.ssid} :halign "start" :hexpand true)
|
||||||
|
(label :class "value dim" :text "${network.signal}%"))))))))
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; vpn popup
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwidget vpn-widget []
|
||||||
|
(box :class "popup" :orientation "v" :space-evenly false :spacing 8
|
||||||
|
; tailscale
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "title" :text "tailscale" :halign "start" :hexpand true)
|
||||||
|
(button :class "toggle-btn ${vpn_data.tailscale.running ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/vpn.sh ${vpn_data.tailscale.running ? 'ts-down' : 'ts-up'}"
|
||||||
|
{vpn_data.tailscale.running ? "on" : "off"}))
|
||||||
|
|
||||||
|
(box :visible {vpn_data.tailscale.running} :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "label" :text "ip" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text {vpn_data.tailscale.ip}))
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "label" :text "host" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text {vpn_data.tailscale.hostname}))
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "label" :text "network" :halign "start" :hexpand true)
|
||||||
|
(label :class "value" :text {vpn_data.tailscale.login}))
|
||||||
|
|
||||||
|
; exit nodes
|
||||||
|
(box :visible {vpn_data.tailscale.exit_count > 0} :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(box :class "separator")
|
||||||
|
(label :class "subtitle" :text "exit node" :halign "start")
|
||||||
|
(box :orientation "v" :space-evenly false :spacing 0
|
||||||
|
(for node in {vpn_data.tailscale.exit_nodes}
|
||||||
|
(button :class "device-btn ${node.active ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/vpn.sh ts-exit '${node.active ? '' : node.ip}'"
|
||||||
|
(label :text {node.name} :halign "start")))))
|
||||||
|
|
||||||
|
; peers
|
||||||
|
(box :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(box :class "separator")
|
||||||
|
(label :class "subtitle" :text "devices" :halign "start")
|
||||||
|
(for peer in {vpn_data.tailscale.peers}
|
||||||
|
(box :class "device-row" :orientation "h" :space-evenly false :spacing 8
|
||||||
|
(label :class "indicator ${peer.online ? 'online' : 'offline'}"
|
||||||
|
:text {peer.online ? "●" : "○"})
|
||||||
|
(label :class "label" :text {peer.name} :halign "start" :hexpand true)
|
||||||
|
(label :class "value dim" :text {peer.ip})))))
|
||||||
|
|
||||||
|
; wireguard
|
||||||
|
(box :class "separator")
|
||||||
|
(box :class "row" :orientation "h"
|
||||||
|
(label :class "title" :text "wireguard" :halign "start" :hexpand true)
|
||||||
|
(button :class "toggle-btn ${vpn_data.wireguard.active ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/vpn.sh ${vpn_data.wireguard.active ? 'wg-down' : 'wg-up'}"
|
||||||
|
{vpn_data.wireguard.active ? "on" : "off"}))))
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; keyboard popup
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwidget keyboard-widget []
|
||||||
|
(box :class "popup" :orientation "v" :space-evenly false :spacing 8
|
||||||
|
(label :class "title" :text "keyboard" :halign "start")
|
||||||
|
|
||||||
|
; layout selector
|
||||||
|
(label :class "subtitle" :text "layout" :halign "start")
|
||||||
|
(for layout in {kbd.layouts}
|
||||||
|
(button :class "device-btn ${layout == kbd.current ? 'active' : ''}"
|
||||||
|
:onclick "~/.config/eww/scripts/keyboard.sh switch"
|
||||||
|
(label :text {layout} :halign "start")))
|
||||||
|
|
||||||
|
; keyboard devices
|
||||||
|
(box :visible {kbd.kb_count > 1} :orientation "v" :space-evenly false :spacing 4
|
||||||
|
(box :class "separator")
|
||||||
|
(label :class "subtitle" :text "devices" :halign "start")
|
||||||
|
(for kb in {kbd.keyboards}
|
||||||
|
(box :class "device-row" :orientation "h" :space-evenly false :spacing 8
|
||||||
|
(label :class "label" :text {kb.name} :halign "start" :hexpand true)
|
||||||
|
(label :class "value dim" :text {kb.layout}))))))
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; backdrop (click-away to close)
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwidget backdrop-widget []
|
||||||
|
(eventbox :onclick "~/.config/eww/scripts/popup.sh close-all"
|
||||||
|
(box :class "backdrop" :hexpand true :vexpand true)))
|
||||||
|
|
||||||
|
(defwindow backdrop
|
||||||
|
:monitor 0
|
||||||
|
:geometry (geometry :x "0px" :y "0px" :width "100%" :height "100%" :anchor "top left")
|
||||||
|
:stacking "overlay"
|
||||||
|
:exclusive false
|
||||||
|
:focusable true
|
||||||
|
(backdrop-widget))
|
||||||
|
|
||||||
|
; =============================================================================
|
||||||
|
; windows
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
|
(defwindow system-popup
|
||||||
|
:monitor 0
|
||||||
|
:geometry (geometry :x "380px" :y "3px" :width "300px" :anchor "top right")
|
||||||
|
:stacking "overlay"
|
||||||
|
:exclusive false
|
||||||
|
:focusable false
|
||||||
|
(system-widget))
|
||||||
|
|
||||||
|
(defwindow battery-popup
|
||||||
|
:monitor 0
|
||||||
|
:geometry (geometry :x "260px" :y "3px" :width "300px" :anchor "top right")
|
||||||
|
:stacking "overlay"
|
||||||
|
:exclusive false
|
||||||
|
:focusable false
|
||||||
|
(battery-widget))
|
||||||
|
|
||||||
|
(defwindow volume-popup
|
||||||
|
:monitor 0
|
||||||
|
:geometry (geometry :x "140px" :y "3px" :width "320px" :anchor "top right")
|
||||||
|
:stacking "overlay"
|
||||||
|
:exclusive false
|
||||||
|
:focusable false
|
||||||
|
(volume-widget))
|
||||||
|
|
||||||
|
(defwindow bluetooth-popup
|
||||||
|
:monitor 0
|
||||||
|
:geometry (geometry :x "500px" :y "3px" :width "280px" :anchor "top right")
|
||||||
|
:stacking "overlay"
|
||||||
|
:exclusive false
|
||||||
|
:focusable false
|
||||||
|
(bluetooth-widget))
|
||||||
|
|
||||||
|
(defwindow network-popup
|
||||||
|
:monitor 0
|
||||||
|
:geometry (geometry :x "560px" :y "3px" :width "320px" :anchor "top right")
|
||||||
|
:stacking "overlay"
|
||||||
|
:exclusive false
|
||||||
|
:focusable false
|
||||||
|
(network-widget))
|
||||||
|
|
||||||
|
(defwindow vpn-popup
|
||||||
|
:monitor 0
|
||||||
|
:geometry (geometry :x "480px" :y "3px" :width "320px" :anchor "top right")
|
||||||
|
:stacking "overlay"
|
||||||
|
:exclusive false
|
||||||
|
:focusable false
|
||||||
|
(vpn-widget))
|
||||||
|
|
||||||
|
(defwindow keyboard-popup
|
||||||
|
:monitor 0
|
||||||
|
:geometry (geometry :x "50px" :y "3px" :width "280px" :anchor "top right")
|
||||||
|
:stacking "overlay"
|
||||||
|
:exclusive false
|
||||||
|
:focusable false
|
||||||
|
(keyboard-widget))
|
||||||
|
|
||||||
|
(defwindow media-popup
|
||||||
|
:monitor 0
|
||||||
|
:geometry (geometry :x "700px" :y "3px" :width "350px" :anchor "top right")
|
||||||
|
:stacking "overlay"
|
||||||
|
:exclusive false
|
||||||
|
:focusable false
|
||||||
|
(media-widget))
|
||||||
44
eww/scripts/battery.sh
Executable file
44
eww/scripts/battery.sh
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# battery info as JSON for eww battery-popup
|
||||||
|
|
||||||
|
action="${1:-status}"
|
||||||
|
|
||||||
|
if [[ "$action" == "set-profile" ]]; then
|
||||||
|
powerprofilesctl set "$2"
|
||||||
|
( data=$(~/.config/eww/scripts/battery.sh); eww update bat="$data" ) &
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
bat_path=$(echo /sys/class/power_supply/BAT* 2>/dev/null | awk '{print $1}')
|
||||||
|
|
||||||
|
if [[ ! -d "$bat_path" ]]; then
|
||||||
|
jq -nc '{capacity:0,status:"No battery",power:"0",time:"",cycles:0,profile:"unknown"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
capacity=$(cat "$bat_path/capacity" 2>/dev/null || echo 0)
|
||||||
|
status=$(cat "$bat_path/status" 2>/dev/null || echo "Unknown")
|
||||||
|
|
||||||
|
power_uw=$(cat "$bat_path/power_now" 2>/dev/null || echo 0)
|
||||||
|
power=$(awk -v p="$power_uw" 'BEGIN{printf "%.1f", p/1000000}')
|
||||||
|
|
||||||
|
cycles=$(cat "$bat_path/cycle_count" 2>/dev/null || echo 0)
|
||||||
|
[[ "$cycles" =~ ^[0-9]+$ ]] || cycles=0
|
||||||
|
|
||||||
|
bat_upower=$(upower -e 2>/dev/null | grep BAT | head -1)
|
||||||
|
time_str=""
|
||||||
|
if [[ -n "$bat_upower" ]]; then
|
||||||
|
time_str=$(upower -i "$bat_upower" 2>/dev/null | awk '/time to/{print $4, $5}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
profile=$(powerprofilesctl get 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
jq -nc \
|
||||||
|
--argjson capacity "$capacity" \
|
||||||
|
--arg status "$status" \
|
||||||
|
--arg power "$power" \
|
||||||
|
--arg time "$time_str" \
|
||||||
|
--argjson cycles "${cycles:-0}" \
|
||||||
|
--arg profile "$profile" \
|
||||||
|
'{$capacity,$status,$power,$time,$cycles,$profile}'
|
||||||
43
eww/scripts/bluetooth.sh
Executable file
43
eww/scripts/bluetooth.sh
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# bluetooth device info as JSON for eww bluetooth-popup
|
||||||
|
|
||||||
|
action="${1:-status}"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
status)
|
||||||
|
powered=$(bluetoothctl show 2>/dev/null | grep -q "Powered: yes" && echo true || echo false)
|
||||||
|
|
||||||
|
devices="[]"
|
||||||
|
count=0
|
||||||
|
if [[ "$powered" == "true" ]]; then
|
||||||
|
# get connected devices in one pass
|
||||||
|
devices=$(bluetoothctl devices Connected 2>/dev/null | while read -r _ mac name; do
|
||||||
|
info=$(bluetoothctl info "$mac" 2>/dev/null)
|
||||||
|
battery=$(awk '/Battery Percentage:/{gsub(/[()]/,""); print $4}' <<< "$info")
|
||||||
|
jq -nc --arg name "$name" --arg mac "$mac" --argjson battery "${battery:--1}" \
|
||||||
|
'{$name,$mac,$battery}'
|
||||||
|
done | jq -sc '.')
|
||||||
|
[[ -z "$devices" || "$devices" == "null" ]] && devices="[]"
|
||||||
|
count=$(jq 'length' <<< "$devices" 2>/dev/null || echo 0)
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq -nc \
|
||||||
|
--argjson powered "$powered" \
|
||||||
|
--argjson count "$count" \
|
||||||
|
--argjson devices "$devices" \
|
||||||
|
'{$powered,$count,$devices}'
|
||||||
|
;;
|
||||||
|
toggle-power)
|
||||||
|
if bluetoothctl show 2>/dev/null | grep -q "Powered: yes"; then
|
||||||
|
bluetoothctl power off
|
||||||
|
else
|
||||||
|
bluetoothctl power on
|
||||||
|
fi
|
||||||
|
( sleep 0.5; data=$(~/.config/eww/scripts/bluetooth.sh); eww update bt="$data" ) &
|
||||||
|
;;
|
||||||
|
disconnect)
|
||||||
|
bluetoothctl disconnect "$2"
|
||||||
|
( sleep 0.5; data=$(~/.config/eww/scripts/bluetooth.sh); eww update bt="$data" ) &
|
||||||
|
;;
|
||||||
|
esac
|
||||||
35
eww/scripts/keyboard.sh
Executable file
35
eww/scripts/keyboard.sh
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# keyboard layout info as JSON for eww keyboard-popup
|
||||||
|
|
||||||
|
action="${1:-status}"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
status)
|
||||||
|
inputs=$(swaymsg -t get_inputs 2>/dev/null)
|
||||||
|
|
||||||
|
current=$(jq -r '[.[] | select(.type == "keyboard")] | .[0].xkb_active_layout_name // "unknown"' <<< "$inputs")
|
||||||
|
layouts=$(jq -c '[.[] | select(.type == "keyboard")] | .[0].xkb_layout_names // []' <<< "$inputs")
|
||||||
|
layout_count=$(jq 'length' <<< "$layouts" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
keyboards=$(jq -c '[.[] | select(.type == "keyboard") |
|
||||||
|
{id: .identifier, name: .name, layout: .xkb_active_layout_name}] | unique_by(.name)' <<< "$inputs")
|
||||||
|
kb_count=$(jq 'length' <<< "$keyboards" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
jq -nc \
|
||||||
|
--arg current "${current:-unknown}" \
|
||||||
|
--argjson layouts "${layouts:-[]}" \
|
||||||
|
--argjson layout_count "${layout_count:-0}" \
|
||||||
|
--argjson keyboards "${keyboards:-[]}" \
|
||||||
|
--argjson kb_count "${kb_count:-0}" \
|
||||||
|
'{$current,$layouts,$layout_count,$keyboards,$kb_count}'
|
||||||
|
;;
|
||||||
|
switch)
|
||||||
|
swaymsg input type:keyboard xkb_switch_layout next 2>/dev/null
|
||||||
|
( sleep 0.3; data=$(~/.config/eww/scripts/keyboard.sh); eww update kbd="$data" ) &
|
||||||
|
;;
|
||||||
|
set-layout)
|
||||||
|
swaymsg input type:keyboard xkb_switch_layout "$2" 2>/dev/null
|
||||||
|
( sleep 0.3; data=$(~/.config/eww/scripts/keyboard.sh); eww update kbd="$data" ) &
|
||||||
|
;;
|
||||||
|
esac
|
||||||
46
eww/scripts/media.sh
Executable file
46
eww/scripts/media.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# media player info as JSON for eww media-popup
|
||||||
|
|
||||||
|
action="${1:-status}"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
status)
|
||||||
|
players=$(playerctl -l 2>/dev/null | head -10)
|
||||||
|
if [[ -z "$players" ]]; then
|
||||||
|
jq -nc '{count:0,players:[]}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
result="[]"
|
||||||
|
while IFS= read -r name; do
|
||||||
|
status=$(playerctl -p "$name" status 2>/dev/null || echo "Stopped")
|
||||||
|
artist=$(playerctl -p "$name" metadata artist 2>/dev/null || echo "")
|
||||||
|
title=$(playerctl -p "$name" metadata title 2>/dev/null || echo "")
|
||||||
|
album=$(playerctl -p "$name" metadata album 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# clean up player name for display
|
||||||
|
display=${name%%.*}
|
||||||
|
|
||||||
|
result=$(jq -c --arg name "$name" --arg display "$display" \
|
||||||
|
--arg status "$status" --arg artist "$artist" \
|
||||||
|
--arg title "$title" --arg album "$album" \
|
||||||
|
'. + [{name:$name, display:$display, status:$status, artist:$artist, title:$title, album:$album}]' <<< "$result")
|
||||||
|
done <<< "$players"
|
||||||
|
|
||||||
|
count=$(jq 'length' <<< "$result")
|
||||||
|
jq -nc --argjson count "$count" --argjson players "$result" '{$count,$players}'
|
||||||
|
;;
|
||||||
|
play-pause)
|
||||||
|
playerctl -p "$2" play-pause 2>/dev/null
|
||||||
|
( sleep 0.3; data=$(~/.config/eww/scripts/media.sh); eww update media="$data" ) &
|
||||||
|
;;
|
||||||
|
next)
|
||||||
|
playerctl -p "$2" next 2>/dev/null
|
||||||
|
( sleep 0.5; data=$(~/.config/eww/scripts/media.sh); eww update media="$data" ) &
|
||||||
|
;;
|
||||||
|
prev)
|
||||||
|
playerctl -p "$2" previous 2>/dev/null
|
||||||
|
( sleep 0.5; data=$(~/.config/eww/scripts/media.sh); eww update media="$data" ) &
|
||||||
|
;;
|
||||||
|
esac
|
||||||
80
eww/scripts/network.sh
Executable file
80
eww/scripts/network.sh
Executable file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# network info as JSON for eww network-popup
|
||||||
|
|
||||||
|
action="${1:-status}"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
status)
|
||||||
|
active=$(nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status 2>/dev/null \
|
||||||
|
| grep ':connected:' | head -1)
|
||||||
|
iface=$(echo "$active" | cut -d: -f1)
|
||||||
|
conn_type=$(echo "$active" | cut -d: -f2)
|
||||||
|
conn_name=$(echo "$active" | cut -d: -f4-)
|
||||||
|
|
||||||
|
ip=$(ip -4 -o addr show "$iface" 2>/dev/null | awk '{print $4}' | cut -d/ -f1)
|
||||||
|
gateway=$(ip route show default dev "$iface" 2>/dev/null | awk '{print $3}')
|
||||||
|
|
||||||
|
networks="[]"
|
||||||
|
net_count=0
|
||||||
|
|
||||||
|
# always check active wifi regardless of primary connection type
|
||||||
|
ssid=$(nmcli -t -f active,ssid dev wifi 2>/dev/null | grep '^yes' | cut -d: -f2-)
|
||||||
|
signal=$(nmcli -t -f active,signal dev wifi 2>/dev/null | grep '^yes' | cut -d: -f2-)
|
||||||
|
signal=${signal:-0}
|
||||||
|
|
||||||
|
# scan nearby wifi
|
||||||
|
saved=$(nmcli -t -f NAME connection show 2>/dev/null | sort -u)
|
||||||
|
# replace last 3 colons with tabs to handle SSIDs containing colons
|
||||||
|
all_wifi=$(nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE dev wifi list --rescan no 2>/dev/null \
|
||||||
|
| sed 's/:\([^:]*\):\([^:]*\):\([^:]*\)$/\t\1\t\2\t\3/' \
|
||||||
|
| awk -F'\t' 'NF>=3 && $1!=""' \
|
||||||
|
| sort -t$'\t' -k4,4r -k2,2rn \
|
||||||
|
| awk -F'\t' '!seen[$1]++')
|
||||||
|
|
||||||
|
# known networks nearby
|
||||||
|
networks=$(echo "$all_wifi" \
|
||||||
|
| while IFS=$'\t' read -r s sig sec use; do
|
||||||
|
echo "$saved" | grep -qxF "$s" && printf '%s\t%s\n' "$s" "$sig"
|
||||||
|
done \
|
||||||
|
| head -10 \
|
||||||
|
| jq -Rnc --arg active "$ssid" '[inputs | split("\t") |
|
||||||
|
{ssid:.[0], signal:(.[1]|tonumber), active:(.[0] == $active)}]')
|
||||||
|
net_count=$(jq 'length' <<< "$networks" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
# unknown networks nearby
|
||||||
|
unknown=$(echo "$all_wifi" \
|
||||||
|
| while IFS=$'\t' read -r s sig sec use; do
|
||||||
|
echo "$saved" | grep -qxF "$s" || printf '%s\t%s\t%s\n' "$s" "$sig" "$sec"
|
||||||
|
done \
|
||||||
|
| head -5 \
|
||||||
|
| jq -Rnc '[inputs | split("\t") |
|
||||||
|
{ssid:.[0], signal:(.[1]|tonumber), security:.[2]}]')
|
||||||
|
unknown_count=$(jq 'length' <<< "$unknown" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
jq -nc \
|
||||||
|
--arg type "${conn_type:-none}" \
|
||||||
|
--arg iface "${iface:-none}" \
|
||||||
|
--arg ip "${ip:-none}" \
|
||||||
|
--arg gateway "${gateway:-none}" \
|
||||||
|
--arg ssid "$ssid" \
|
||||||
|
--argjson signal "${signal:-0}" \
|
||||||
|
--arg conn_name "$conn_name" \
|
||||||
|
--argjson count "${net_count:-0}" \
|
||||||
|
--argjson networks "${networks:-[]}" \
|
||||||
|
--argjson unknown_count "${unknown_count:-0}" \
|
||||||
|
--argjson unknown "${unknown:-[]}" \
|
||||||
|
'{$type,$iface,$ip,$gateway,$ssid,$signal,$conn_name,$count,$networks,$unknown_count,$unknown}'
|
||||||
|
;;
|
||||||
|
connect)
|
||||||
|
( nmcli dev wifi connect "$2" 2>/dev/null; sleep 1; data=$(~/.config/eww/scripts/network.sh); eww update net="$data" ) &
|
||||||
|
;;
|
||||||
|
disconnect)
|
||||||
|
( nmcli connection down "$2" 2>/dev/null; sleep 1; data=$(~/.config/eww/scripts/network.sh); eww update net="$data" ) &
|
||||||
|
;;
|
||||||
|
connect-new)
|
||||||
|
ssid="$2"
|
||||||
|
pass=$(zenity --entry --hide-text --title="WiFi" --text="Password for $ssid" 2>/dev/null)
|
||||||
|
( [[ -n "$pass" ]] && nmcli dev wifi connect "$ssid" password "$pass" 2>/dev/null; sleep 1; data=$(~/.config/eww/scripts/network.sh); eww update net="$data" ) &
|
||||||
|
;;
|
||||||
|
esac
|
||||||
29
eww/scripts/popup.sh
Executable file
29
eww/scripts/popup.sh
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# toggle an eww popup
|
||||||
|
# opens on the currently focused monitor
|
||||||
|
|
||||||
|
POPUPS=(system-popup battery-popup network-popup vpn-popup volume-popup bluetooth-popup keyboard-popup media-popup)
|
||||||
|
|
||||||
|
target="$1"
|
||||||
|
|
||||||
|
if [[ -z "$target" ]]; then
|
||||||
|
echo "usage: popup.sh <popup-name|close-all>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$target" == "close-all" ]]; then
|
||||||
|
eww close "${POPUPS[@]}" 2>/dev/null
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if target is already open
|
||||||
|
if eww active-windows 2>/dev/null | grep -q "$target"; then
|
||||||
|
eww close "$target" 2>/dev/null
|
||||||
|
else
|
||||||
|
# close others, open popup
|
||||||
|
screen=$(swaymsg -t get_outputs 2>/dev/null \
|
||||||
|
| jq '[.[] | .focused] | index(true) // 0')
|
||||||
|
eww close "${POPUPS[@]}" 2>/dev/null
|
||||||
|
eww open --screen "${screen:-0}" "$target"
|
||||||
|
fi
|
||||||
40
eww/scripts/system.sh
Executable file
40
eww/scripts/system.sh
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# system metrics as JSON for eww system-popup
|
||||||
|
|
||||||
|
# delta-based cpu: sample /proc/stat twice 0.5s apart
|
||||||
|
read -r _ u1 n1 s1 i1 _ < /proc/stat
|
||||||
|
sleep 0.5
|
||||||
|
read -r _ u2 n2 s2 i2 _ < /proc/stat
|
||||||
|
cpu=$(( (u2+n2+s2 - u1-n1-s1) * 100 / (u2+n2+s2+i2 - u1-n1-s1-i1) ))
|
||||||
|
|
||||||
|
ram_info=$(free -b | awk '/^Mem:/{printf "%.0f %.1f %.1f", $3/$2*100, $3/1073741824, $2/1073741824}')
|
||||||
|
ram_percent=$(awk '{print $1}' <<< "$ram_info")
|
||||||
|
ram_used=$(awk '{printf "%.1f", $2}' <<< "$ram_info")
|
||||||
|
ram_total=$(awk '{printf "%.1f", $3}' <<< "$ram_info")
|
||||||
|
|
||||||
|
temp=$(cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | sort -rn | head -1)
|
||||||
|
temp=$(( ${temp:-0} / 1000 ))
|
||||||
|
|
||||||
|
disk_info=$(df -h / | awk 'NR==2{gsub(/%/,""); printf "%s %s %s", $5, $3, $2}')
|
||||||
|
disk_percent=$(awk '{print $1}' <<< "$disk_info")
|
||||||
|
disk_used=$(awk '{print $2}' <<< "$disk_info")
|
||||||
|
disk_total=$(awk '{print $3}' <<< "$disk_info")
|
||||||
|
|
||||||
|
swap=$(free -h | awk '/^Swap:/{printf "%s/%s", $3, $2}')
|
||||||
|
load=$(awk '{print $1, $2, $3}' /proc/loadavg)
|
||||||
|
uptime_str=$(uptime -p 2>/dev/null | sed 's/up //' || echo "n/a")
|
||||||
|
|
||||||
|
jq -nc \
|
||||||
|
--argjson cpu "${cpu:-0}" \
|
||||||
|
--argjson ram_percent "${ram_percent:-0}" \
|
||||||
|
--arg ram_used "${ram_used:-0}Gi" \
|
||||||
|
--arg ram_total "${ram_total:-0}Gi" \
|
||||||
|
--argjson temp "${temp:-0}" \
|
||||||
|
--argjson disk_percent "${disk_percent:-0}" \
|
||||||
|
--arg disk_used "$disk_used" \
|
||||||
|
--arg disk_total "$disk_total" \
|
||||||
|
--arg swap "$swap" \
|
||||||
|
--arg load "$load" \
|
||||||
|
--arg uptime "$uptime_str" \
|
||||||
|
'{$cpu,$ram_percent,$ram_used,$ram_total,$temp,$disk_percent,$disk_used,$disk_total,$swap,$load,$uptime}'
|
||||||
62
eww/scripts/volume.sh
Executable file
62
eww/scripts/volume.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# audio + brightness info for eww volume-popup
|
||||||
|
|
||||||
|
action="${1:-status}"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
status)
|
||||||
|
default_sink=$(pactl get-default-sink 2>/dev/null)
|
||||||
|
default_source=$(pactl get-default-source 2>/dev/null)
|
||||||
|
|
||||||
|
# get all sink+source info in one pactl call, extract volume/mute + device lists
|
||||||
|
all_data=$(pactl --format=json list sinks 2>/dev/null)
|
||||||
|
sinks=$(jq -c --arg d "$default_sink" \
|
||||||
|
'[.[] | {name: .description, sink_name: .name, active: (.name == $d)}]' <<< "$all_data" 2>/dev/null || echo '[]')
|
||||||
|
# extract default sink volume+mute
|
||||||
|
volume=$(jq --arg d "$default_sink" \
|
||||||
|
'[.[] | select(.name == $d)][0] | .volume | to_entries[0].value.value_percent | rtrimstr("%") | tonumber' <<< "$all_data" 2>/dev/null || echo 0)
|
||||||
|
muted=$(jq --arg d "$default_sink" \
|
||||||
|
'[.[] | select(.name == $d)][0].mute' <<< "$all_data" 2>/dev/null || echo false)
|
||||||
|
|
||||||
|
all_sources=$(pactl --format=json list sources 2>/dev/null)
|
||||||
|
sources=$(jq -c --arg d "$default_source" \
|
||||||
|
'[.[] | select(.name | test("monitor$") | not) | {name: .description, source_name: .name, active: (.name == $d)}]' <<< "$all_sources" 2>/dev/null || echo '[]')
|
||||||
|
mic_volume=$(jq --arg d "$default_source" \
|
||||||
|
'[.[] | select(.name == $d)][0] | .volume | to_entries[0].value.value_percent | rtrimstr("%") | tonumber' <<< "$all_sources" 2>/dev/null || echo 0)
|
||||||
|
mic_muted=$(jq --arg d "$default_source" \
|
||||||
|
'[.[] | select(.name == $d)][0].mute' <<< "$all_sources" 2>/dev/null || echo false)
|
||||||
|
|
||||||
|
brightness=$(brightnessctl -m 2>/dev/null | cut -d, -f5 | tr -d '%')
|
||||||
|
sink_count=$(jq 'length' <<< "$sinks" 2>/dev/null || echo 0)
|
||||||
|
source_count=$(jq 'length' <<< "$sources" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
jq -nc \
|
||||||
|
--argjson volume "${volume:-0}" \
|
||||||
|
--argjson muted "${muted:-false}" \
|
||||||
|
--argjson mic_volume "${mic_volume:-0}" \
|
||||||
|
--argjson mic_muted "${mic_muted:-false}" \
|
||||||
|
--argjson brightness "${brightness:-0}" \
|
||||||
|
--argjson sinks "${sinks:-[]}" \
|
||||||
|
--argjson sources "${sources:-[]}" \
|
||||||
|
--argjson sink_count "${sink_count:-0}" \
|
||||||
|
--argjson source_count "${source_count:-0}" \
|
||||||
|
'{$volume,$muted,$mic_volume,$mic_muted,$brightness,$sinks,$sources,$sink_count,$source_count}'
|
||||||
|
;;
|
||||||
|
set-sink)
|
||||||
|
pactl set-default-sink "$2"
|
||||||
|
( data=$(~/.config/eww/scripts/volume.sh); eww update vol="$data" ) &
|
||||||
|
;;
|
||||||
|
set-source)
|
||||||
|
pactl set-default-source "$2"
|
||||||
|
( data=$(~/.config/eww/scripts/volume.sh); eww update vol="$data" ) &
|
||||||
|
;;
|
||||||
|
toggle-mute)
|
||||||
|
pamixer -t
|
||||||
|
( data=$(~/.config/eww/scripts/volume.sh); eww update vol="$data" ) &
|
||||||
|
;;
|
||||||
|
toggle-mic)
|
||||||
|
pamixer --default-source -t
|
||||||
|
( data=$(~/.config/eww/scripts/volume.sh); eww update vol="$data" ) &
|
||||||
|
;;
|
||||||
|
esac
|
||||||
89
eww/scripts/vpn.sh
Executable file
89
eww/scripts/vpn.sh
Executable file
@@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# vpn status as JSON for eww vpn-popup
|
||||||
|
|
||||||
|
action="${1:-status}"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
status)
|
||||||
|
ts_running=false
|
||||||
|
ts_ip=""
|
||||||
|
ts_hostname=""
|
||||||
|
ts_login=""
|
||||||
|
ts_exit_nodes="[]"
|
||||||
|
ts_exit_count=0
|
||||||
|
ts_peers="[]"
|
||||||
|
ts_peer_count=0
|
||||||
|
|
||||||
|
if command -v tailscale &>/dev/null; then
|
||||||
|
ts_json=$(tailscale status --json 2>/dev/null)
|
||||||
|
state=$(jq -r '.BackendState // empty' <<< "$ts_json")
|
||||||
|
|
||||||
|
if [[ "$state" == "Running" ]]; then
|
||||||
|
ts_running=true
|
||||||
|
ts_ip=$(jq -r '.TailscaleIPs[0] // empty' <<< "$ts_json")
|
||||||
|
ts_hostname=$(jq -r '.Self.HostName // empty' <<< "$ts_json")
|
||||||
|
ts_login=$(jq -r '.CurrentTailnet.Name // empty' <<< "$ts_json")
|
||||||
|
|
||||||
|
ts_exit_nodes=$(jq -c '[.Peer | to_entries[]? |
|
||||||
|
select(.value.ExitNodeOption) |
|
||||||
|
{id: .key, name: .value.HostName, ip: (.value.TailscaleIPs[0] // ""), active: (.value.ExitNode // false)}
|
||||||
|
] // []' <<< "$ts_json" 2>/dev/null || echo '[]')
|
||||||
|
ts_exit_count=$(jq 'length' <<< "$ts_exit_nodes" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
ts_peers=$(jq -c '[.Peer | to_entries[]? |
|
||||||
|
{name: .value.HostName, ip: (.value.TailscaleIPs[0] // ""), online: .value.Online}
|
||||||
|
] // []' <<< "$ts_json" 2>/dev/null || echo '[]')
|
||||||
|
ts_peer_count=$(jq 'length' <<< "$ts_peers" 2>/dev/null || echo 0)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
wg_active=false
|
||||||
|
wg_iface=""
|
||||||
|
if command -v wg &>/dev/null; then
|
||||||
|
wg_iface=$(wg show interfaces 2>/dev/null | head -1)
|
||||||
|
[[ -n "$wg_iface" ]] && wg_active=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq -nc \
|
||||||
|
--argjson ts_running "$ts_running" \
|
||||||
|
--arg ts_ip "$ts_ip" \
|
||||||
|
--arg ts_hostname "$ts_hostname" \
|
||||||
|
--arg ts_login "$ts_login" \
|
||||||
|
--argjson ts_exit_nodes "$ts_exit_nodes" \
|
||||||
|
--argjson ts_exit_count "$ts_exit_count" \
|
||||||
|
--argjson ts_peers "$ts_peers" \
|
||||||
|
--argjson ts_peer_count "$ts_peer_count" \
|
||||||
|
--argjson wg_active "$wg_active" \
|
||||||
|
--arg wg_iface "$wg_iface" \
|
||||||
|
'{tailscale:{running:$ts_running,ip:$ts_ip,hostname:$ts_hostname,login:$ts_login,
|
||||||
|
exit_nodes:$ts_exit_nodes,exit_count:$ts_exit_count,
|
||||||
|
peers:$ts_peers,peer_count:$ts_peer_count},
|
||||||
|
wireguard:{active:$wg_active,iface:$wg_iface}}'
|
||||||
|
;;
|
||||||
|
ts-up)
|
||||||
|
tailscale up 2>/dev/null
|
||||||
|
( sleep 1; data=$(~/.config/eww/scripts/vpn.sh); eww update vpn_data="$data" ) &
|
||||||
|
;;
|
||||||
|
ts-down)
|
||||||
|
tailscale down 2>/dev/null
|
||||||
|
( sleep 1; data=$(~/.config/eww/scripts/vpn.sh); eww update vpn_data="$data" ) &
|
||||||
|
;;
|
||||||
|
ts-exit)
|
||||||
|
if [[ -n "$2" ]]; then
|
||||||
|
tailscale set --exit-node="$2" 2>/dev/null
|
||||||
|
else
|
||||||
|
tailscale set --exit-node="" 2>/dev/null
|
||||||
|
fi
|
||||||
|
( sleep 0.5; data=$(~/.config/eww/scripts/vpn.sh); eww update vpn_data="$data" ) &
|
||||||
|
;;
|
||||||
|
wg-up)
|
||||||
|
sudo wg-quick up "${2:-wg0}" 2>/dev/null
|
||||||
|
( sleep 1; data=$(~/.config/eww/scripts/vpn.sh); eww update vpn_data="$data" ) &
|
||||||
|
;;
|
||||||
|
wg-down)
|
||||||
|
iface="${2:-$(wg show interfaces 2>/dev/null | head -1)}"
|
||||||
|
sudo wg-quick down "${iface:-wg0}" 2>/dev/null
|
||||||
|
( sleep 1; data=$(~/.config/eww/scripts/vpn.sh); eww update vpn_data="$data" ) &
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user