From ac23da55c5d80afc1cab29409579006eb5599c30 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 20 Jun 2024 14:05:43 +0200 Subject: [PATCH] Add eww bar This configuration was simply copied from my old Arch Linux system. There are some issues that still need to be solved, namely with fonts and missing bitcoin price script, but it's mostly minor. --- home/programs/graphical/bars/default.nix | 5 + .../bars/eww/config/css/_colors.scss | 43 ++++ .../bars/eww/config/css/modules/_battery.scss | 55 +++++ .../bars/eww/config/css/modules/_bitcoin.scss | 3 + .../bars/eww/config/css/modules/_clock.scss | 4 + .../bars/eww/config/css/modules/_cpu.scss | 3 + .../eww/config/css/modules/_gammarelay.scss | 7 + .../bars/eww/config/css/modules/_kernel.scss | 3 + .../bars/eww/config/css/modules/_memory.scss | 3 + .../bars/eww/config/css/modules/_uptime.scss | 3 + .../bars/eww/config/css/modules/_volume.scss | 9 + .../eww/config/css/modules/_window_name.scss | 4 + .../eww/config/css/modules/_workspaces.scss | 28 +++ .../eww/config/css/windows/_calendar.scss | 32 +++ .../eww/config/css/windows/_radio_menu.scss | 46 +++++ .../graphical/bars/eww/config/eww.scss | 114 +++++++++++ .../graphical/bars/eww/config/eww.yuck | 94 +++++++++ .../bars/eww/config/modules/battery.yuck | 24 +++ .../bars/eww/config/modules/bitcoin.yuck | 14 ++ .../bars/eww/config/modules/clock.yuck | 18 ++ .../bars/eww/config/modules/cpu.yuck | 15 ++ .../bars/eww/config/modules/gammarelay.yuck | 41 ++++ .../bars/eww/config/modules/kernel.yuck | 15 ++ .../bars/eww/config/modules/memory.yuck | 15 ++ .../bars/eww/config/modules/nightlight.yuck | 3 + .../bars/eww/config/modules/uptime.yuck | 15 ++ .../bars/eww/config/modules/variables.yuck | 60 ++++++ .../bars/eww/config/modules/volume.yuck | 40 ++++ .../bars/eww/config/modules/window_name.yuck | 11 + .../bars/eww/config/modules/workspaces.yuck | 21 ++ .../graphical/bars/eww/config/scripts/.flake8 | 11 + .../graphical/bars/eww/config/scripts/battery | 85 ++++++++ .../bars/eww/config/scripts/bluetooth | 85 ++++++++ .../bars/eww/config/scripts/gammarelay | 82 ++++++++ .../graphical/bars/eww/config/scripts/include | 50 +++++ .../graphical/bars/eww/config/scripts/net | 52 +++++ .../bars/eww/config/scripts/nightlight | 12 ++ .../bars/eww/config/scripts/pyproject.toml | 12 ++ .../graphical/bars/eww/config/scripts/storage | 35 ++++ .../graphical/bars/eww/config/scripts/temp | 7 + .../graphical/bars/eww/config/scripts/volume | 112 +++++++++++ .../bars/eww/config/scripts/window_name | 6 + .../bars/eww/config/scripts/window_name.py | 112 +++++++++++ .../bars/eww/config/scripts/workspaces | 13 ++ .../bars/eww/config/scripts/workspaces.py | 188 ++++++++++++++++++ .../bars/eww/config/windows/calendar.yuck | 14 ++ .../bars/eww/config/windows/radio-menu.yuck | 78 ++++++++ home/programs/graphical/bars/eww/default.nix | 62 ++++++ home/programs/graphical/default.nix | 1 + hosts/voyager/default.nix | 1 + options/home/programs/default.nix | 7 + 51 files changed, 1773 insertions(+) create mode 100644 home/programs/graphical/bars/default.nix create mode 100644 home/programs/graphical/bars/eww/config/css/_colors.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_battery.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_bitcoin.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_clock.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_cpu.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_gammarelay.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_kernel.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_memory.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_uptime.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_volume.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_window_name.scss create mode 100644 home/programs/graphical/bars/eww/config/css/modules/_workspaces.scss create mode 100644 home/programs/graphical/bars/eww/config/css/windows/_calendar.scss create mode 100644 home/programs/graphical/bars/eww/config/css/windows/_radio_menu.scss create mode 100644 home/programs/graphical/bars/eww/config/eww.scss create mode 100644 home/programs/graphical/bars/eww/config/eww.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/battery.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/bitcoin.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/clock.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/cpu.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/gammarelay.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/kernel.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/memory.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/nightlight.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/uptime.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/variables.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/volume.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/window_name.yuck create mode 100644 home/programs/graphical/bars/eww/config/modules/workspaces.yuck create mode 100644 home/programs/graphical/bars/eww/config/scripts/.flake8 create mode 100755 home/programs/graphical/bars/eww/config/scripts/battery create mode 100755 home/programs/graphical/bars/eww/config/scripts/bluetooth create mode 100755 home/programs/graphical/bars/eww/config/scripts/gammarelay create mode 100755 home/programs/graphical/bars/eww/config/scripts/include create mode 100755 home/programs/graphical/bars/eww/config/scripts/net create mode 100644 home/programs/graphical/bars/eww/config/scripts/nightlight create mode 100644 home/programs/graphical/bars/eww/config/scripts/pyproject.toml create mode 100755 home/programs/graphical/bars/eww/config/scripts/storage create mode 100755 home/programs/graphical/bars/eww/config/scripts/temp create mode 100755 home/programs/graphical/bars/eww/config/scripts/volume create mode 100755 home/programs/graphical/bars/eww/config/scripts/window_name create mode 100755 home/programs/graphical/bars/eww/config/scripts/window_name.py create mode 100755 home/programs/graphical/bars/eww/config/scripts/workspaces create mode 100755 home/programs/graphical/bars/eww/config/scripts/workspaces.py create mode 100644 home/programs/graphical/bars/eww/config/windows/calendar.yuck create mode 100644 home/programs/graphical/bars/eww/config/windows/radio-menu.yuck create mode 100644 home/programs/graphical/bars/eww/default.nix diff --git a/home/programs/graphical/bars/default.nix b/home/programs/graphical/bars/default.nix new file mode 100644 index 0000000..160e671 --- /dev/null +++ b/home/programs/graphical/bars/default.nix @@ -0,0 +1,5 @@ +_: { + imports = [ + ./eww + ]; +} diff --git a/home/programs/graphical/bars/eww/config/css/_colors.scss b/home/programs/graphical/bars/eww/config/css/_colors.scss new file mode 100644 index 0000000..1534b69 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/_colors.scss @@ -0,0 +1,43 @@ +$rosewater: #f5e0dc; +$flamingo: #f2cdcd; +$pink: #f5c2e7; +$mauve: #cba6f7; +$red: #f38ba8; +$maroon: #eba0ac; +$peach: #fab387; +$yellow: #f9e2af; +$gold: #efcb10; +$green: #a6e3a1; +$lime: #78db32; +$teal: #94e2d5; +$sky: #89dceb; +$sapphire: #74c7ec; +$blue: #89b4fa; +$lavender: #b4befe; +$orange: #ffa500; + +$text: #cdd6f4; +$subtext1: #bac2de; +$subtext0: #a6adc8; +$overlay2: #9399b2; +$overlay1: #7f849c; +$overlay0: #6c7086; + +$surface2: #585b70; +$surface1: #45475a; +$surface0: #313244; + +$base-a: rgba(30, 30, 40, 0.65); +$base: rgba(30, 30, 40, 1); +$base1-a: rgba(49, 50, 68, 0.85); +$base1: rgba(49, 50, 68, 1); +$mantle: #181825; +$crust: #11111b; + +$fg: $text; +$bg-a: $base-a; +$bg: $base; +$bg1: $base1; +$bg1-a: $base1-a; +$border: #28283d; +$shadow: $crust; diff --git a/home/programs/graphical/bars/eww/config/css/modules/_battery.scss b/home/programs/graphical/bars/eww/config/css/modules/_battery.scss new file mode 100644 index 0000000..d4894a4 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_battery.scss @@ -0,0 +1,55 @@ +@keyframes blink { + 0%{ + opacity: 0; + } + 50%{ + opacity: 0.7; + } + 100%{ + opacity: 0; + } + } + +// .unplugged.low { +// color: #0000ff; +// } + +.battery { + + .icon, .icon label { + font-family: "Material Symbols Outlined"; + + .extra, .extra label { + font-family: "Font Awesome 6 Free"; + } + } + + .critical { + .unplugged { + color: #f00; + .extra { animation: blink 1.2s linear infinite; } + } + + .plugged { + .icon { color: $orange } + .extra { color: $green; } + } + } + + .normal { + .unplugged { + .icon { color: $orange } + } + .plugged { + .icon { color: $green; } + } + } + + .full { + .icon { color: $green; } + .extra { color: $lime; } + } + + .extra { margin-right: 5px; } + .icon { margin-right: 5px; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_bitcoin.scss b/home/programs/graphical/bars/eww/config/css/modules/_bitcoin.scss new file mode 100644 index 0000000..82d5a61 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_bitcoin.scss @@ -0,0 +1,3 @@ +.bitcoin { + .icon { margin-right: 6px; color: $gold; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_clock.scss b/home/programs/graphical/bars/eww/config/css/modules/_clock.scss new file mode 100644 index 0000000..98d9823 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_clock.scss @@ -0,0 +1,4 @@ +.clock { + // color: $sapphire; + .icon { margin-right: 6px; color: $sapphire; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_cpu.scss b/home/programs/graphical/bars/eww/config/css/modules/_cpu.scss new file mode 100644 index 0000000..a183c58 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_cpu.scss @@ -0,0 +1,3 @@ +.cpu { + .icon { color: $lime; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_gammarelay.scss b/home/programs/graphical/bars/eww/config/css/modules/_gammarelay.scss new file mode 100644 index 0000000..f62dcc8 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_gammarelay.scss @@ -0,0 +1,7 @@ +.gammarelay { + .icon { + color: $peach; + margin-left: 2px; + margin-right: 2px; + } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_kernel.scss b/home/programs/graphical/bars/eww/config/css/modules/_kernel.scss new file mode 100644 index 0000000..652a60d --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_kernel.scss @@ -0,0 +1,3 @@ +.kernel { + .icon { color: $lavender; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_memory.scss b/home/programs/graphical/bars/eww/config/css/modules/_memory.scss new file mode 100644 index 0000000..37d8432 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_memory.scss @@ -0,0 +1,3 @@ +.memory { + .icon { color: $maroon; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_uptime.scss b/home/programs/graphical/bars/eww/config/css/modules/_uptime.scss new file mode 100644 index 0000000..63b1353 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_uptime.scss @@ -0,0 +1,3 @@ +.uptime { + .icon { color: $green; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_volume.scss b/home/programs/graphical/bars/eww/config/css/modules/_volume.scss new file mode 100644 index 0000000..7a94630 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_volume.scss @@ -0,0 +1,9 @@ +.volume { + .icon { color: $peach; } + .speaker { + .icon { + margin-left: 8px; + margin-right: 5px; + } + } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_window_name.scss b/home/programs/graphical/bars/eww/config/css/modules/_window_name.scss new file mode 100644 index 0000000..edf0d3e --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_window_name.scss @@ -0,0 +1,4 @@ +.window_name { + font-family: "JetBrains Mono", "Font Awesome 6 Free", sans-serif; + font-size: 1.4rem; +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_workspaces.scss b/home/programs/graphical/bars/eww/config/css/modules/_workspaces.scss new file mode 100644 index 0000000..e97b513 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_workspaces.scss @@ -0,0 +1,28 @@ +.workspaces { + background-color: $bg1-a; + border-radius: 25px; + + .icon, .icon label { + font-family: "Material Symbols Outlined"; + font-size: 1.15rem; + } + + .value { + margin: 0 9px; + } + + .focused { + // text-decoration: underline; + // text-decoration-color: red; + // text-decoration-style: double; + color: $fg; + } + + .active { + color: #bbb; + } + + .inactive { + color: #555; + } +} diff --git a/home/programs/graphical/bars/eww/config/css/windows/_calendar.scss b/home/programs/graphical/bars/eww/config/css/windows/_calendar.scss new file mode 100644 index 0000000..2aadfa3 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/windows/_calendar.scss @@ -0,0 +1,32 @@ +.calendar-win { + @include window; + background-color: $bg; + border: 1px solid $border; + color: $fg; + padding: .2em; +} + +calendar { + padding: 5px; + + :selected { + color: $mauve; + } + + .header { + color: $subtext1; + } + + .highlight { + color: $maroon; + font-weight: bold; + } + + .button { + color: $sapphire; + } + + :indeterminate { + color: $overlay0; + } +} diff --git a/home/programs/graphical/bars/eww/config/css/windows/_radio_menu.scss b/home/programs/graphical/bars/eww/config/css/windows/_radio_menu.scss new file mode 100644 index 0000000..e6d0e48 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/windows/_radio_menu.scss @@ -0,0 +1,46 @@ +.radio-menu-box { + @include window; + background-color: $bg; + border: 1px solid $border; + color: $text; + font-family: "Jost *", "JetBrains Mono", "Font Awesome 6 Free", sans-serif; + + .icon, .icon label { + font-family: "Material Symbols Outlined"; + font-size: 1.15rem; + } + + .text-row { + margin: 1rem 1.5rem 0; + + .title { font-size: 1.2rem; } + } + + .element-row { + margin: .5rem .7rem; + + label { + font-size: 1rem; + margin: 0 .1rem; + } + } + + .element { + @include rounding; + background-color: $surface0; + margin: .3rem; + + button { + @include rounding; + padding: .5rem; + + label { + font-size: 1.5rem; + } + + &:hover { + background-color: $overlay0; + } + } + } +} diff --git a/home/programs/graphical/bars/eww/config/eww.scss b/home/programs/graphical/bars/eww/config/eww.scss new file mode 100644 index 0000000..7854a7b --- /dev/null +++ b/home/programs/graphical/bars/eww/config/eww.scss @@ -0,0 +1,114 @@ +@import "css/colors"; + +@mixin rounding { + border-radius: 16px; +} + +@mixin window { + border: 1px solid $border; + box-shadow: 0 2px 3px $shadow; + margin: 5px 5px 10px; + @include rounding; +} + +* { + all: unset; + transition: 200ms ease; +} + +@import "css/windows/calendar"; +@import "css/windows/radio_menu"; +@import "css/modules/clock"; +@import "css/modules/volume"; +@import "css/modules/bitcoin"; +@import "css/modules/cpu"; +@import "css/modules/memory"; +@import "css/modules/uptime"; +@import "css/modules/kernel"; +@import "css/modules/battery"; +@import "css/modules/workspaces"; +@import "css/modules/gammarelay"; +@import "css/modules/window_name"; + +.bar { + background-color: $bg-a; + color: $fg; + + font-family: "JetBrains Mono", "Jost *", sans-serif; + + label { + font-size: 14px; + } + + // TODO: Use percentages (for some reason it fails now) + min-width: 1900px; +} + +tooltip { + background: $bg; + border: 1px solid $border; + border-radius: 8px; + + label { + font-size: 1rem; + } +} + +.icon, +.icon label { + font-family: "Font Awesome 6 Free", "Material Symbols Outlined"; +} + +.module { + margin: 0 5px; +} + +.separ { + color: $surface0; + font-size: 1.5rem; + padding-bottom: 2px; +} + +scale trough { + background-color: $bg1-a; + border-radius: 24px; + margin: 0 1rem; + min-height: 10px; + min-width: 70px; +} + +.tray { + margin-right: 12px; +} + +menu { + background: $bg1; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + border: 2px solid rgba($crust, 0.5); + padding: 0.5rem 0; +} + +menu menu { + border-top-left-radius: 12px; + border-top-right-radius: 12px; +} + +menu > menuitem { + padding: 0.4em 1rem; + background: transparent; + transition: 0.2s ease background; +} + +menu > menuitem:hover { + background: rgba($overlay1, 0.4); +} + +menu > menuitem check:checked ~ label { + color: $bg1-a; + font-weight: 600; +} + +menubar > menuitem { + margin-left: 0.6rem; +} diff --git a/home/programs/graphical/bars/eww/config/eww.yuck b/home/programs/graphical/bars/eww/config/eww.yuck new file mode 100644 index 0000000..ff4aef7 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/eww.yuck @@ -0,0 +1,94 @@ +(defvar terminal "kitty -e") + +(include "./modules/variables.yuck") + +(include "./modules/clock.yuck") +(include "./modules/volume.yuck") +(include "./modules/bitcoin.yuck") +(include "./modules/cpu.yuck") +(include "./modules/memory.yuck") +(include "./modules/uptime.yuck") +; (include "./modules/kernel.yuck") +(include "./modules/battery.yuck") +(include "./modules/window_name.yuck") +(include "./modules/workspaces.yuck") +(include "./modules/gammarelay.yuck") + +(include "./windows/calendar.yuck") +(include "./windows/radio-menu.yuck") + +(defwidget sep [] + (label :class "separ module" :text "|")) + +(defwidget left [] + (box + :space-evenly false + :halign "start" + (gammarelay_module) + (sep) + (window_name_module) + )) + +(defwidget right [] + (box + :space-evenly false + :halign "end" + ; (kernel_module) + ; (sep) + (volume_module) + (sep) + (battery_module) + (sep) + (bitcoin_module) + (sep) + (cpu_module) + (sep) + (memory_module) + (sep) + ; (uptime_module) + ; (sep) + (clock_module) + (sep) + (systray + :pack-direction "left" + :class "module tray" + ) + )) + +(defwidget center [] + (box + :space-evenly false + :halign "center" + (workspaces_module) + )) + +(defwidget bar [] + (centerbox + :class "bar" + :orientation "horizontal" + (left) + (center) + (right))) + +(defwindow bar0 + :monitor 0 + :geometry (geometry :x "0%" + :y "0%" + :width: "100%" + :height "32px" + :anchor "top center") + :stacking "fg" + :exclusive true + (bar)) + + +(defwindow bar1 + :monitor 1 + :geometry (geometry :x "0%" + :y "0%" + :width: "100%" + :height "32px" + :anchor "top center") + :stacking "fg" + :exclusive true + (bar)) diff --git a/home/programs/graphical/bars/eww/config/modules/battery.yuck b/home/programs/graphical/bars/eww/config/modules/battery.yuck new file mode 100644 index 0000000..b0f033e --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/battery.yuck @@ -0,0 +1,24 @@ +(defwidget battery_module [] + (eventbox + :class "module battery" + + (box + :class {battery.critical ? "critical" : battery.full ? "full" : "normal"} + (box + :space-evenly false + :class {battery.plugged ? "plugged" : "unplugged" } + + (box + :class "icon" + :space-evenly false + + (label + :class "extra" + :text {battery.extra_icon}) + (label + :text {battery.capacity_icon})) + (label + :class "value" + :text "${battery.percent}%" + ) + )))) diff --git a/home/programs/graphical/bars/eww/config/modules/bitcoin.yuck b/home/programs/graphical/bars/eww/config/modules/bitcoin.yuck new file mode 100644 index 0000000..963ef6c --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/bitcoin.yuck @@ -0,0 +1,14 @@ +(defwidget bitcoin_module [] + (eventbox + :onclick "~/.local/bin/scripts/cli/bitcoin | xargs -I_ ${EWW_CMD} update bitcoin=_" + :class "module bitcoin" + + (box + :space-evenly false + + (label + :class "icon" + :text "") + (label :text {bitcoin})) + ) +) diff --git a/home/programs/graphical/bars/eww/config/modules/clock.yuck b/home/programs/graphical/bars/eww/config/modules/clock.yuck new file mode 100644 index 0000000..5217101 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/clock.yuck @@ -0,0 +1,18 @@ +(defwidget clock_module [] + + (eventbox + :tooltip {time.day} + :class "module clock" + ;; :onclick "${EWW_CMD} open --toggle calendar" + + (box + :space-evenly false + + (label + :class "icon" + :text "") + (label + :class "value" + :text "${time.date} ${time.hour}:${time.minute}") + ))) + diff --git a/home/programs/graphical/bars/eww/config/modules/cpu.yuck b/home/programs/graphical/bars/eww/config/modules/cpu.yuck new file mode 100644 index 0000000..940ffb3 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/cpu.yuck @@ -0,0 +1,15 @@ +(defwidget cpu_module [] + (eventbox + :class "module cpu" + + (box + :space-evenly false + + (label + :class "icon" + :text " ") + (label + :class "value" + :text "${round(EWW_CPU.avg,2)}%" + ) + ))) diff --git a/home/programs/graphical/bars/eww/config/modules/gammarelay.yuck b/home/programs/graphical/bars/eww/config/modules/gammarelay.yuck new file mode 100644 index 0000000..513d50a --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/gammarelay.yuck @@ -0,0 +1,41 @@ +(defwidget gammarelay_module [] + (box + :class "module gammarelay" + + (eventbox + :onscroll "scripts/gammarelay temperature scroll {}" + :onclick "scripts/gammarelay temperature set toggle" + :onrightclick "scripts/gammarelay temperature set off" + :tooltip "${temperature} K" + :class "temperature" + (box + (label + :class "icon" + :text "") + )) + + (eventbox + :onscroll "scripts/gammarelay brightness scroll {}" + :onclick "scripts/gammarelay brightness set toggle" + :onrightclick "scripts/gammarelay brightness set off" + :tooltip "${brightness}%" + :class "brightness" + (box + (label + :class "icon" + :text "") + )) + + ; (eventbox + ; :onscroll "scripts/gammarelay gamma scroll {}" + ; :onclick "scripts/gammarelay gamma set toggle" + ; :onrightclick "scripts/gammarelay gamma set off" + ; :tooltip "${gamma}%" + ; :class "gamma" + ; :valign "top" + ; (box + ; (label + ; :class "icon" + ; :text "γ") + ; )) + )) diff --git a/home/programs/graphical/bars/eww/config/modules/kernel.yuck b/home/programs/graphical/bars/eww/config/modules/kernel.yuck new file mode 100644 index 0000000..c784049 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/kernel.yuck @@ -0,0 +1,15 @@ +(defwidget kernel_module [] + (eventbox + :class "module kernel" + + (box + :space-evenly false + + (label + :class "icon" + :text " ") + (label + :class "value" + :text {kernel} + ) + ))) diff --git a/home/programs/graphical/bars/eww/config/modules/memory.yuck b/home/programs/graphical/bars/eww/config/modules/memory.yuck new file mode 100644 index 0000000..43bcca4 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/memory.yuck @@ -0,0 +1,15 @@ +(defwidget memory_module [] + (eventbox + :class "module memory" + + (box + :space-evenly false + + (label + :class "icon" + :text " ") + (label + :class "value" + :text "${round(EWW_RAM.used_mem / 1000000000,1)}G: ${round(EWW_RAM.used_mem_perc,0)}%" + ) + ))) diff --git a/home/programs/graphical/bars/eww/config/modules/nightlight.yuck b/home/programs/graphical/bars/eww/config/modules/nightlight.yuck new file mode 100644 index 0000000..fd2f2fe --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/nightlight.yuck @@ -0,0 +1,3 @@ +(deflisten nightlight + :initial `{"running": false,"temperature": 0}` + `scripts/nightlight --state`) diff --git a/home/programs/graphical/bars/eww/config/modules/uptime.yuck b/home/programs/graphical/bars/eww/config/modules/uptime.yuck new file mode 100644 index 0000000..972b3e1 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/uptime.yuck @@ -0,0 +1,15 @@ +(defwidget uptime_module [] + (eventbox + :class "module uptime" + + (box + :space-evenly false + + (label + :class "icon" + :text " ") + (label + :class "value" + :text {uptime} + ) + ))) diff --git a/home/programs/graphical/bars/eww/config/modules/variables.yuck b/home/programs/graphical/bars/eww/config/modules/variables.yuck new file mode 100644 index 0000000..3dbc09e --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/variables.yuck @@ -0,0 +1,60 @@ +(defpoll time + :interval "5s" + :initial '{"date": "01 Jan", "hour": "00", "minute": "00", "day": "Monday"}' + `date +'{"date": "%d %b", "hour": "%H", "minute": "%M", "day": "%A"}'`) + +(deflisten volume + :initial '{ "speaker_vol": "100", "speaker_mute": false, "speaker_icon": "", "microphone_mute": false, "microphone_vol": "100", "microphone_icon": "" }' + `scripts/volume loop`) + +(deflisten window_name + :initial `{"class":"","name":"","formatted_name":""}` + `scripts/window_name`) + +(deflisten workspaces + :initial `[{"id": 1,"name": "N/A","monitor": "N/A","windows": 1,"hasfullscreen": false,"lastwindow": "N/A","lastwindowtitle": "N/A","format_name": "N/A","active": true}]` + `scripts/workspaces --loop`) + +(defpoll battery + :interval "1s" + :initial '{"percent":"0","plugged":"false","status":"N/A","capacity_icon":"","extra_icon":"","manufacturer":"N/A","model_name":"N/A","technology":"N/A","energy_now":"0","enerfy_full":"0","enerfy_full_design":"0","cycle_count":"0","critical":"false","full":"false"}' + `scripts/battery`) + +(defpoll uptime + :interval "1m" + :initial 'N/A' + `uptime -p | sed \\ + -e 's/^up //' \\ + -e 's/ years\\?,\\?/y/' \\ + -e 's/ months\\?,\\?/m/' \\ + -e 's/ weeks\\?,\\?/w/' \\ + -e 's/ days\\?,\\?/d/' \\ + -e 's/ hours\\?,\\?/h/' \\ + -e 's/ minutes\\?,\\?/m/' \\ + -e 's/ seconds\\?,\\?/s/' \\ + | cut -d' ' -f-2`) + +(defpoll bitcoin + :interval "5m" + :initial "$N/A" + `~/.local/bin/scripts/cli/bitcoin`) + +; TODO: Figure out how to store this one-time +(defpoll kernel + :interval "10000h" + :initial 'N/A' + ; `uname -r | sed -r 's/(.+)-arch(.+)/\\1/'` + `uname -r`) + +(deflisten temperature `scripts/gammarelay temperature watch`) +(deflisten brightness `scripts/gammarelay brightness watch`) +(deflisten gamma `scripts/gammarelay gamma watch`) + +(defpoll net + :interval "3s" + :initial '{"essid":"N/A","icon":"󱛇","state":"unknown","signal":"0"}' + `scripts/net status`) +(defpoll bluetooth + :interval "3s" + :initial '{"icon":"󰂲","status":"unknown","name":"","mac":"","battery":""}' + `scripts/bluetooth status`) diff --git a/home/programs/graphical/bars/eww/config/modules/volume.yuck b/home/programs/graphical/bars/eww/config/modules/volume.yuck new file mode 100644 index 0000000..1f689c9 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/volume.yuck @@ -0,0 +1,40 @@ +(defvar mic_rev false) + +(defwidget volume_module [] + (box + :class "module volume" + :space-evenly false + + (eventbox + :onscroll "scripts/volume setvol SOURCE 0.5 {}" + :onclick "scripts/volume togglemute SOURCE" + :onrightclick "${terminal} pulsemixer &" + :onhover "${EWW_CMD} update mic_rev=true" + :onhoverlost "${EWW_CMD} update mic_rev=false" + :class "microphone" + (box + (label + :class "icon" + :text {volume.microphone_icon}) + (label + :visible {mic_rev && !volume.microphone_mute} + :class "value" + :text "${volume.microphone_vol}%") + )) + + (eventbox + :onscroll "scripts/volume setvol SINK 0.5 {}" + :onclick "scripts/volume togglemute SINK" + :onrightclick "${terminal} pulsemixer &" + :class "speaker" + (box + (label + :class "icon" + :text {volume.speaker_icon}) + (label + :visible {!volume.speaker_mute} + :class "value" + :text "${volume.speaker_vol}%") + )) + + )) diff --git a/home/programs/graphical/bars/eww/config/modules/window_name.yuck b/home/programs/graphical/bars/eww/config/modules/window_name.yuck new file mode 100644 index 0000000..16ace74 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/window_name.yuck @@ -0,0 +1,11 @@ +; Consider making the window name clickable, opening up a full window that's showing +; the selected window details (class, unformatted name, and perhaps even more, like +; xwayland status, ...) +(defwidget window_name_module [] + (box + :class "module window_name" + + (label + :class "value" + :text "${window_name.formatted_name}") + )) diff --git a/home/programs/graphical/bars/eww/config/modules/workspaces.yuck b/home/programs/graphical/bars/eww/config/modules/workspaces.yuck new file mode 100644 index 0000000..3f4feb1 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/workspaces.yuck @@ -0,0 +1,21 @@ +; (defwidget sep [] +; (label :class "separ module" :text "|")) + + +; Consider making the window name clickable, opening up a full window that's showing +; the selected window details (class, unformatted name, and perhaps even more, like +; xwayland status, ...) +(defwidget workspaces_module [] + (box + :class "module workspaces" + + (for workspace in workspaces + (eventbox + :class {workspace.active ? 'focused' : workspace.windows > 0 ? 'active' : 'inactive'} + :onclick `scripts/workspaces --switch ${workspace.id}` + + (label + :class "value icon" + :text {workspace.format_name})) + ) + )) diff --git a/home/programs/graphical/bars/eww/config/scripts/.flake8 b/home/programs/graphical/bars/eww/config/scripts/.flake8 new file mode 100644 index 0000000..ce90fbd --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/.flake8 @@ -0,0 +1,11 @@ +[flake8] +max-line-length=119 +extend-ignore=E203 +extend-select=B902,B904 +exclude=.venv,.git,.cache +ignore= + ANN002, # *args annotation + ANN003, # **kwargs annotation + ANN101, # self param annotation + ANN102, # cls param annotation + ANN204, # return type annotation for special methods diff --git a/home/programs/graphical/bars/eww/config/scripts/battery b/home/programs/graphical/bars/eww/config/scripts/battery new file mode 100755 index 0000000..86d1152 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/battery @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +# $BATTERY and $ADAPTER env vars can be set manually, being the names of the +# devices (in /sys/class/power_supply/) i.e. BATTERY=BAT0 ADAPTER=ADP0 +# or, if left unset, they will be automatically picked. + +CAPACITY_ICONS=("󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹") +CHARGING_ICON="" +DISCHARGING_ICON="" +FULL_ICON="" # Plugged in, but no longer charging (fully charged) +CRITICAL_ICON="" +CRITICAL_PERCENTAGE=15 + +if [ -z "$BATTERY" ]; then + # shellcheck disable=SC2010 + BATTERY="$(\ls -t /sys/class/power_supply | grep "BAT" | head -n 1)" +fi + +if [ -z "$ADAPTER" ]; then + # shellcheck disable=SC2010 + ADAPTER="$(\ls -t /sys/class/power_supply | grep -E "ADP|AC" | head -n 1)" +fi + +get_bat_info() { + cat /sys/class/power_supply/"$BATTERY"/"$1" +} + +get_adp_info() { + cat /sys/class/power_supply/"$ADAPTER"/"$1" +} + +manufacturer="$(get_bat_info manufacturer)" +model_name="$(get_bat_info model_name)" +technology="$(get_bat_info technology)" +energy_now="$(get_bat_info energy_now)" +energy_full="$(get_bat_info energy_full)" +energy_full_design="$(get_bat_info energy_full_design)" +cycle_count="$(get_bat_info cycle_count)" + +capacity="$(get_bat_info capacity)" +status="$(get_bat_info status)" +[ "$(get_adp_info online)" -eq 1 ] && adp_connected="true" || adp_connected="false" + +# Quick overrides to showcase how battery works +# capacity=100 +# adp_connected="true" +# status="Charging" +# status="Not charging" +# status="Discharging" + +full="false" +capacity_icon="$(pick_icon "$capacity" 0 100 "${CAPACITY_ICONS[@]}")" + +if [ "$status" = "Not charging" ] || [ "$status" = "Full" ] && [ "$adp_connected" = "true" ]; then + extra_icon="$FULL_ICON" + full="true" +elif [ "$status" = "Discharging" ] && [ "$capacity" -le "$CRITICAL_PERCENTAGE" ]; then + extra_icon="$CRITICAL_ICON" +elif [ "$status" = "Discharging" ]; then + extra_icon="$DISCHARGING_ICON" +elif [ "$status" = "Charging" ]; then + extra_icon="$CHARGING_ICON" +fi + +[ "$capacity" -le "$CRITICAL_PERCENTAGE" ] && critical="true" || critical="false" + +jq -n -c --monochrome-output \ + --arg percent "$capacity" \ + --arg plugged "$adp_connected" \ + --arg status "$status" \ + --arg capacity_icon "$capacity_icon" \ + --arg extra_icon "$extra_icon" \ + --arg manufacturer "$manufacturer" \ + --arg model_name "$model_name" \ + --arg technology "$technology" \ + --arg energy_now "$energy_now" \ + --arg energy_full "$energy_full" \ + --arg energy_full_design "$energy_full_design" \ + --arg cycle_count "$cycle_count" \ + --arg critical "$critical" \ + --arg full "$full" \ + '$ARGS.named' diff --git a/home/programs/graphical/bars/eww/config/scripts/bluetooth b/home/programs/graphical/bars/eww/config/scripts/bluetooth new file mode 100755 index 0000000..240c4ca --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/bluetooth @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +ICON_IDLE="󰂯" +ICON_CONNECTED="󰂱" +ICON_OFF="󰂲" + +toggle() { + status=$(rfkill -J | jq -r '.rfkilldevices[] | select(.type == "bluetooth") | .soft' | head -1) + + if [ "$status" = "unblocked" ]; then + rfkill block bluetooth + else + rfkill unblock bluetooth + if ! systemctl -q is-active bluetooth.service; then + # This will use polkit for privillege elevation + systemctl start bluetooth + fi + fi +} + +get_report() { + status=$(rfkill -J | jq -r '.rfkilldevices[] | select(.type == "bluetooth") | .soft' | head -1) + if [ "$status" = "blocked" ] || ! systemctl -q is-active bluetooth.service || ! command -v bluetoothctl >/dev/null 2>&1; then + jq -n -c --monochrome-output \ + --arg icon "$ICON_OFF" \ + --arg status "unknown" \ + --arg name "" \ + --arg mac "" \ + --arg battery "" \ + '$ARGS.named' + + return + fi + + status="$(bluetoothctl show)" + + powered="$(echo "$status" | grep Powered | cut -d' ' -f 2-)" + if [ "$powered" != "yes" ]; then + jq -n -c --monochrome-output \ + --arg icon "$ICON_OFF" \ + --arg status "unpowered" \ + --arg name "" \ + --arg mac "" \ + --arg battery "" \ + '$ARGS.named' + + return + fi + + status="$(bluetoothctl info)" + if [ "$status" == "Missing device address argument" ]; then + jq -n -c --monochrome-output \ + --arg icon "$ICON_IDLE" \ + --arg status "idle" \ + --arg name "" \ + --arg mac "" \ + --arg battery "" \ + '$ARGS.named' + + return + fi + + name="$(echo "$status" | grep Name | cut -d' ' -f 2-)" + mac="$(echo "$status" | head -1 | awk '{print $2}' | tr ':' '_')" + + if [ "$(echo "$status" | grep Percentage)" != "" ] && command -v upower >/dev/null 2>&1; then + battery="$(upower -i /org/freedesktop/UPower/devices/headset_dev_"$mac" | grep percentage | awk '{print $2}' | cut -f 1 -d '%')%" + else + battery="" + fi + + jq -n -c --monochrome-output \ + --arg icon "$ICON_CONNECTED" \ + --arg status "connected" \ + --arg name "$name" \ + --arg mac "$mac" \ + --arg battery "$battery" \ + '$ARGS.named' +} + +case "$1" in + "status") get_report ;; + "toggle") toggle ;; + *) >&2 echo "Invalid usage, argument '$1' not recognized."; exit 1 ;; +esac diff --git a/home/programs/graphical/bars/eww/config/scripts/gammarelay b/home/programs/graphical/bars/eww/config/scripts/gammarelay new file mode 100755 index 0000000..039dbea --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/gammarelay @@ -0,0 +1,82 @@ +#!/bin/env bash + +if [ "$1" = "temperature" ]; then + watch_cmd="{t}" + update_cmd="UpdateTemperature" + update_signature="n" + set_cmd="Temperature" + set_signature="q" + default_val=6500 + click_val=4500 + scroll_change=100 + cmp_op="<" + +elif [ "$1" = "brightness" ]; then + watch_cmd="{bp}" + update_cmd="UpdateBrightness" + update_signature="d" + set_cmd="Brightness" + set_signature="d" + default_val=1 + click_val=0.8 + scroll_change=0.02 + cmp_op="<" + +elif [ "$1" = "gamma" ]; then + watch_cmd="{g}" + update_cmd="UpdateGamma" + update_signature="d" + set_cmd="Gamma" + set_signature="d" + default_val=1 + click_val=1.1 + scroll_change=0.02 + cmp_op=">" + +else + >&2 echo "Invalid option, first argument must be one of: temperature, brightness, gamma" + exit 1 +fi + +if [ "$2" = "watch" ]; then + exec wl-gammarelay-rs watch "$watch_cmd" + +elif [ "$2" = "get" ]; then + exec busctl --user get-property rs.wl-gammarelay / rs.wl.gammarelay "$set_cmd" | cut -d' ' -f2 + +elif [ "$2" = "scroll" ]; then + if [ "$3" = "up" ]; then + sign="+" + elif [ "$3" = "down" ]; then + sign="-" + else + >&2 echo "Invalid sign, second argument must be one of: up, down" + exit 1 + fi + + exec busctl --user -- call rs.wl-gammarelay / rs.wl.gammarelay "$update_cmd" "$update_signature" ${sign}${scroll_change} + +elif [ "$2" = "set" ]; then + mode="$3" + if [ "$mode" = "toggle" ]; then + cur_val="$(busctl --user get-property rs.wl-gammarelay / rs.wl.gammarelay "$set_cmd" | cut -d' ' -f2)" + if [ "$(echo "$cur_val $cmp_op $default_val" | bc -l)" = "1" ]; then + mode="off" + else + mode="on" + fi + fi + + if [ "$mode" = "on" ]; then + exec busctl --user -- set-property rs.wl-gammarelay / rs.wl.gammarelay "$set_cmd" "$set_signature" "$click_val" + elif [ "$mode" = "off" ]; then + exec busctl --user -- set-property rs.wl-gammarelay / rs.wl.gammarelay "$set_cmd" "$set_signature" "$default_val" + else + >&2 echo "Invalid mode, third argument, must be one of: toggle, on, off" + exit 1 + fi + +else + >&2 echo "Invalid operation, second argument must be one of: watch, scroll, set" + exit 1 +fi diff --git a/home/programs/graphical/bars/eww/config/scripts/include b/home/programs/graphical/bars/eww/config/scripts/include new file mode 100755 index 0000000..49e58e9 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/include @@ -0,0 +1,50 @@ +#!/bin/env bash + +# $1: Current number +# $2: Range minimum +# $3: Range maximum +# $4-: Icons as individual arguments +pick_icon() { + cur="$1" + min="$2" + max="$3" + shift 3 + icons=("$@") + + index="$(echo "($cur-$min)/(($max-$min)/${#icons[@]})" | bc)" + + # Print the picked icon, handling overflows/underflows, i.e. if our index is <0 or >len(icons) + if [ "$index" -ge "${#icons[@]}" ]; then + index=-1 + elif [ "$index" -lt 0 ]; then + index=0 + fi + + echo "${icons[index]}" +} + +# Will block and listen to the hyprland socket messages and output them +# Generally used like: hyprland_ipc | while read line; do handle $line; done +# Read for output format and available events +# Note: requires openbsd version of netcat. +# $1 - Optional event to listen for (no event filtering will be done if not provided) +hyprland_ipc() { + if [ -z "$HYPRLAND_INSTANCE_SIGNATURE" ]; then + >&2 echo "Hyprland is not running, IPC not available" + exit 1 + fi + + SOCKET_PATH="${XDG_RUNTIME_DIR:-/run/user/$UID}/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" + #SOCKET_PATH="/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" + + if [ -z "$1" ]; then + nc -U "$SOCKET_PATH" | while read -r test; do + echo "$test" + done + else + nc -U "$SOCKET_PATH" | while read -r test; do + # shellcheck disable=SC2016 + echo "$test" | grep --line-buffered -E "^$1>>" | stdbuf -oL awk -F '>>' '{print $2}' + done + fi +} diff --git a/home/programs/graphical/bars/eww/config/scripts/net b/home/programs/graphical/bars/eww/config/scripts/net new file mode 100755 index 0000000..7d84697 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/net @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +STRENGTH_ICONS=("󰤫" "󰤯" "󰤟" "󰤢" "󰤥" "󰤨") +DISCONNECTED_ICON="󰤮" +WIFI_OFF="󰖪" + +toggle() { + status=$(rfkill -J | jq -r '.rfkilldevices[] | select(.type == "wlan") | .soft' | head -1) + + if [ "$status" = "unblocked" ]; then + rfkill block wlan + else + rfkill unblock wlan + fi +} + +get_report() { + connection_details="$(nmcli -t -f NAME,TYPE,DEVICE connection show --active | grep wireless | head -n1)" + essid="$(echo $connection_details | cut -d':' -f1)" + device="$(echo $connection_details | cut -d':' -f3)" + if [ -n "$device" ]; then + state="$(nmcli -t -f DEVICE,STATE device status | grep "$device" | head -n1 | cut -d':' -f2)" + signal="$(nmcli -t -f in-use,signal dev wifi | grep "\*" | cut -d':' -f2)" + else + state="unknown" + signal="0" + fi + + if [ "$state" = "disconnected" ] ; then + icon="$DISCONNECTED_ICON" + elif [ "$state" = "unknown" ]; then + icon="$WIFI_OFF" + else + icon="$(pick_icon "$signal" 0 100 "${STRENGTH_ICONS[@]}")" + fi + + jq -n -c --monochrome-output \ + --arg essid "$essid" \ + --arg icon "$icon" \ + --arg state "$state" \ + --arg signal "$signal" \ + '$ARGS.named' +} + +case "$1" in + "toggle") toggle ;; + "status") get_report ;; + *) >&2 echo "Invalid usage, argument '$1' not recognized."; exit 1 ;; +esac diff --git a/home/programs/graphical/bars/eww/config/scripts/nightlight b/home/programs/graphical/bars/eww/config/scripts/nightlight new file mode 100644 index 0000000..71522f0 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/nightlight @@ -0,0 +1,12 @@ +#!/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +# Consider usning a file as a flag for whether nightlight is on or off +# as we might be in transition state and just killing the program might +# not be enough. + +if [ "$1" = "toggle" ]; then + gammastep -x +fi diff --git a/home/programs/graphical/bars/eww/config/scripts/pyproject.toml b/home/programs/graphical/bars/eww/config/scripts/pyproject.toml new file mode 100644 index 0000000..e3a2d4f --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/pyproject.toml @@ -0,0 +1,12 @@ +[tool.black] +line-length = 119 +extend-exclude = "^/.cache" + +[tool.isort] +profile = "black" +line_length = 119 +atomic = true +order_by_type = false +case_sensitive = true +combine_as_imports = true +skip = [".venv", ".git", ".cache"] diff --git a/home/programs/graphical/bars/eww/config/scripts/storage b/home/programs/graphical/bars/eww/config/scripts/storage new file mode 100755 index 0000000..f1ff4b4 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/storage @@ -0,0 +1,35 @@ +#!/bin/env bash + +MOUNTPOINTS=("/" "/mnt/ext") + +data="$(df -H)" + +as_json() { + mountpoint="$1" + res="$2" + arr_res=($res) + + jq -n -c --monochrome-output \ + --arg mountpoint "$mountpoint" \ + --arg size "${arr_res[0]}" \ + --arg used "${arr_res[1]}" \ + --arg avail "${arr_res[2]}" \ + --arg percent "${arr_res[3]}" \ + '$ARGS.named' +} + +output_json="[]" +for mountpoint in "${MOUNTPOINTS[@]}"; do + res="$(echo "$data" | awk -v m="$mountpoint" '$6 == m {print $2 " " $3 " " $4 " " $5}')" + out="$(as_json "$mountpoint" "$res")" + + # echo "$output_json $out" | jq -c -s + + jq --argjson arr1 "$output_json" --argjson arr2 "[$out]" -n \ + '$arr1 + $arr2' + + # mount_data+=("$mountpoint" $res) + # echo "${mount_data[@]}" +done + +# echo "${mount_data[@]}" diff --git a/home/programs/graphical/bars/eww/config/scripts/temp b/home/programs/graphical/bars/eww/config/scripts/temp new file mode 100755 index 0000000..3857531 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/temp @@ -0,0 +1,7 @@ +#!/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +#hyprland_ipc "workspace|createworkspace|destroyworkspace|activewindow" +hyprland_ipc diff --git a/home/programs/graphical/bars/eww/config/scripts/volume b/home/programs/graphical/bars/eww/config/scripts/volume new file mode 100755 index 0000000..6003f79 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/volume @@ -0,0 +1,112 @@ +#!/bin/env bash + +# Define some icons +SPEAKER_ICONS=("" "" "") +SPEAKER_MUTED_ICON="" +MIC_ICON="" +MIC_MUTED_ICON="" + +# Define some helper functions for getting/setting audio data using wireplumber (wpctl) + +# $1 can either be "SINK" (speaker) or "SOURCE" (microphone) +get_vol() { + wpctl get-volume "@DEFAULT_AUDIO_${1}@" | awk '{print int($2*100)}' +} + +# $1 can either be "SINK" (speaker) or "SOURCE" (microphone) +# #2 is the voulme (as percentage) to set the volume to +# $3 is optional, if set, it can be '+' or '-', which then adds/decreases volume, instead of setting +set_vol() { + wpctl set-volume "@DEFAULT_AUDIO_${1}@" "$(awk -v n="$2" 'BEGIN{print (n / 100)}')$3" +} + +# $1 can either be "SINK" (speaker) or "SOURCE" (microphone) +check_mute() { + wpctl get-volume "@DEFAULT_AUDIO_${1}@" | grep -i muted >/dev/null + echo $? +} + +# $1 can either be "SINK" (speaker) or "SOURCE" (microphone) +toggle_mute() { + wpctl set-mute "@DEFAULT_AUDIO_${1}@" toggle +} + +get_report() { + spkr_vol="$(get_vol "SINK")" + mic_vol="$(get_vol "SOURCE")" + + if [ "$(check_mute "SINK")" == "0" ]; then + spkr_mute="true" + spkr_icon="$SPEAKER_MUTED_ICON" + else + spkr_mute="false" + index="$(awk -v n="$spkr_vol" -v m="${#SPEAKER_ICONS[@]}" 'BEGIN{print int(n/(100/m))}')" + + # We might end up with an higher than the length of icons, if the volume is over 100% + # in this case, set the index to last icon + if [ "$index" -ge "${#SPEAKER_ICONS[@]}" ]; then + spkr_icon="${SPEAKER_ICONS[-1]}" + else + spkr_icon="${SPEAKER_ICONS[$index]}" + fi + fi + + if [ "$(check_mute "SOURCE")" = "0" ]; then + mic_mute="true" + mic_icon="$MIC_MUTED_ICON" + else + mic_mute="false" + mic_icon="$MIC_ICON" + fi + + echo "{ \"speaker_vol\": \"$spkr_vol\", \"speaker_mute\": $spkr_mute, \"speaker_icon\": \"$spkr_icon\", \"microphone_mute\": $mic_mute, \"microphone_vol\": \"$mic_vol\", \"microphone_icon\": \"$mic_icon\" }" +} + +# Continually run and report every volume change (into stdout) +loop() { + get_report + pactl subscribe | grep --line-buffered "change" | while read -r _; do + get_report + done +} + +case "$1" in +"loop") loop ;; + +"once") get_report ;; + +"togglemute") + if [ "$2" != "SOURCE" ] && [ "$2" != "SINK" ]; then + >&2 echo "Invalid usage, expected second argument to be 'SINK' or 'SOURCE', got '$2'" + exit 1 + fi + toggle_mute "$2" + ;; + +"setvol") + if [ "$2" != "SOURCE" ] && [ "$2" != "SINK" ]; then + >&2 echo "Invalid usage, expected second argument to be 'SINK' or 'SOURCE', got '$2'" + exit 1 + fi + + if [[ "$3" =~ ^[+-]?[0-9]*\.?[0-9]+$ ]]; then + case "$4" in + "") set_vol "$2" "$3" ;; + up | +) set_vol "$2" "$3" "+" ;; + down | -) set_vol "$2" "$3" "-" ;; + *) + >&2 echo "Invalid usage, expected fourth argument to be up/down or +/-, got '$4'" + exit 1 + ;; + esac + else + >&2 echo "Invalid usage, exepcted third argument to be a number, got '$3'" + exit 1 + fi + ;; + +*) + >&2 echo "Invalid usage, argument '$1' not recognized." + exit 1 + ;; +esac diff --git a/home/programs/graphical/bars/eww/config/scripts/window_name b/home/programs/graphical/bars/eww/config/scripts/window_name new file mode 100755 index 0000000..d32bff7 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/window_name @@ -0,0 +1,6 @@ +#!/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +hyprland_ipc "activewindow" | ./scripts/window_name.py diff --git a/home/programs/graphical/bars/eww/config/scripts/window_name.py b/home/programs/graphical/bars/eww/config/scripts/window_name.py new file mode 100755 index 0000000..acc5376 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/window_name.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""This is a utility script for regex remaps on window names. + +This is done in python, because of the complex regex logic, which would be pretty hard to +recreate in bash. This python script is expected to be called from the bash script controlling +the window names. Window name and class are obtained from piped stdin, to prevent having to +needlessly keep restarting this program, which takes a while due to the interpreter starting +overhead. +""" +import json +import re +import sys +from typing import Iterator, Optional + + +class RemapRule: + __slots__ = ("name_pattern", "output_pattern", "class_pattern") + + def __init__( + self, + name_pattern: str, + output_pattern: str, + class_pattern: Optional[str] = None, + ): + self.name_pattern = re.compile(name_pattern) + self.output_pattern = output_pattern + self.class_pattern = re.compile(class_pattern) if class_pattern else None + + def apply(self, window_name: str, window_class: str) -> str: + """Returns new name after this remap rule was applied.""" + if self.class_pattern is not None: + if not self.class_pattern.fullmatch(window_class): + # Rule doesn't apply, class mismatch, return original name + return window_name + + res = self.name_pattern.fullmatch(window_name) + if not res: + # Rule doesn't apply, name mismatch, return original name + return window_name + + # NOTE: This is potentially unsafe, since output_pattern might be say {0.__class__}, etc. + # meaning this allows arbitrary attribute access, however it doesn't allow running functions + # here. That said, code could still end up being run if there's some descriptor defined, + # and generally I wouldn't trust this in production. However, this code is for my personal + # use here, and all of the output patterns are hard-coded in this file, so in this case, it's + # fine. But if you see this code and you'd like to use it in your production code, maybe don't + return self.output_pattern.format(*res.groups()) + + +# Rules will be applied in specified order +REMAP_RULES: list[RemapRule] = [ + RemapRule(r"", "", ""), + RemapRule(r"(.*) — Mozilla Firefox", " {}", "firefox"), + RemapRule(r"Mozilla Firefox", " Mozilla Firefox", "firefox"), + RemapRule(r"Alacritty", " Alacritty", "Alacritty"), + RemapRule( + r"zsh;#toggleterm#1 - \(term:\/\/(.+)\/\/(\d+):(.+)\) - N?VIM", + " Terminal: {0}", + ), + RemapRule(r"(.+) \+ \((.+)\) - N?VIM", " {0} ({1}) [MODIFIED]"), + RemapRule(r"(.+) \((.+)\) - N?VIM", " {0} ({1})"), + RemapRule(r"(?:\[\d+\] )?\*?WebCord - (.+)", " {}", "WebCord"), + RemapRule(r"(.+) - Discord", " {}", "discord"), + RemapRule(r"(?:\(\d+\) )?Discord \| (.+)", " {}", "vesktop"), + RemapRule(r"(.+) - mpv", " {}", "mpv"), + RemapRule(r"Stremio - (.+)", " Stremio - {}", r"(Stremio)|(com.stremio.stremio)"), + RemapRule(r"Spotify", " Spotify", "Spotify"), + RemapRule(r"pulsemixer", " Pulsemixer"), + RemapRule(r"(.*)", " {}", "Pcmanfm"), + RemapRule(r"(.*)", " {}", "pcmanfm-qt"), + # Needs to be last + RemapRule(r"(.*)", " {}", "kitty"), +] + +MAX_LENGTH = 65 + + +def iter_window() -> Iterator[tuple[str, str]]: + """Listen for the window parameters from stdin/pipe, yields (window_name, window_class).""" + for line in sys.stdin: + line = line.removesuffix("\n") + els = line.split(",", maxsplit=1) + if len(els) != 2: + raise ValueError(f"Expected 2 arguments from stdin line (name, class), but got {len(els)}") + yield els[1], els[0] + + +def main() -> None: + for window_name, window_class in iter_window(): + formatted_name = window_name + for remap_rule in REMAP_RULES: + new_name = remap_rule.apply(formatted_name, window_class) + if new_name != formatted_name: + formatted_name = new_name + break + + if len(formatted_name) > MAX_LENGTH: + formatted_name = formatted_name[: MAX_LENGTH - 3] + "..." + + ret = json.dumps( + { + "name": window_name, + "class": window_class, + "formatted_name": formatted_name, + } + ) + print(ret) + sys.stdout.flush() + + +if __name__ == "__main__": + main() diff --git a/home/programs/graphical/bars/eww/config/scripts/workspaces b/home/programs/graphical/bars/eww/config/scripts/workspaces new file mode 100755 index 0000000..992b7ab --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/workspaces @@ -0,0 +1,13 @@ +#!/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +if [ "$1" = "--switch" ]; then + $HOME/.local/bin/scripts/gui/hyprland/swap-workspace "$2" >/dev/null + # hyprctl dispatch workspace "$2" >/dev/null +elif [ "$1" = "--loop" ]; then + hyprland_ipc "workspace|createworkspace|destroyworkspace" | ./scripts/workspaces.py "$@" +else + ./scripts/workspaces.py "$@" +fi diff --git a/home/programs/graphical/bars/eww/config/scripts/workspaces.py b/home/programs/graphical/bars/eww/config/scripts/workspaces.py new file mode 100755 index 0000000..4c31f4b --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/workspaces.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +import argparse +import json +import subprocess +import sys +from typing import TypedDict, TYPE_CHECKING + +if TYPE_CHECKING: + from _typeshed import SupportsRichComparison + + +class WorkspaceInfo(TypedDict): + id: int + name: str + monitor: str + windows: int + hasfullscreen: bool + lastwindow: str + lastwindowtitle: str + + +class ActiveWorkspaceInfo(TypedDict): + id: int + name: str + + +class MonitorInfo(TypedDict): + id: int + name: str + description: str + make: str + model: str + width: int + height: int + refreshRate: float + x: int + y: int + activeWorkspace: ActiveWorkspaceInfo + reserved: list[int] + scale: float + transform: int + focused: bool + dpmsStatus: bool + vrr: bool + + +class OutputWorkspaceInfo(WorkspaceInfo): + format_name: str + active: bool + monitor_id: int + + +# workspace id -> remapped name +REMAPS = { + 1: "󰞷", + 2: "󰈹", + 3: "󱕂", + 4: "󰭹", + 5: "󰝚", + 6: "󰋹", + 7: "7", + 8: "8", + 9: "9", +} + +# Skip the special (scratchpad) workspace +SKIP = {-99} + + +def workspace_sort(obj: OutputWorkspaceInfo) -> "SupportsRichComparison": + """Returns a key to sort by, given the current element.""" + return obj["id"] + + +def fill_blank_workspaces(open: list[OutputWorkspaceInfo]) -> list[OutputWorkspaceInfo]: + """Add in the rest of the workspaces which don't have any open windows on them. + + This is needed because hyprland deletes workspaces with nothing in them. + Note that this assumes all available workspaces were listed in REMAPS, and will + only fill those. These blank workspaces will have most string values set to "N/A", + and most int values set to 0. + """ + # Work on a copy, we don't want to alter the original list + lst = open.copy() + + for remap_id, format_name in REMAPS.items(): + # Skip for already present workspaces + if any(ws_info["id"] == remap_id for ws_info in lst): + continue + + blank_ws: OutputWorkspaceInfo = { + "id": remap_id, + "name": str(remap_id), + "monitor": "N/A", + "windows": 0, + "hasfullscreen": False, + "lastwindow": "N/A", + "lastwindowtitle": "N/A", + "active": False, + "format_name": format_name, + "monitor_id": 0, + } + lst.append(blank_ws) + + return lst + + +def get_workspaces() -> list[OutputWorkspaceInfo]: + """Obtain workspaces from hyprctl, sort them and add format_name arg.""" + proc = subprocess.run(["hyprctl", "workspaces", "-j"], stdout=subprocess.PIPE) + proc.check_returncode() + try: + workspaces: list[WorkspaceInfo] = json.loads(proc.stdout) + except json.JSONDecodeError: + sys.stderr.writelines([ + "Error decoding json response from hyprctl, returning empty workspaces", + f"Actual captured output from hyprctl: {proc.stdout!r}" + ]) + sys.stderr.flush() + workspaces = [] + + proc = subprocess.run(["hyprctl", "monitors", "-j"], stdout=subprocess.PIPE) + proc.check_returncode() + monitors: list[MonitorInfo] = json.loads(proc.stdout) + + active_workspaces = {monitor["activeWorkspace"]["id"] for monitor in monitors} + + out: list[OutputWorkspaceInfo] = [] + for workspace in workspaces: + if workspace["id"] in SKIP: + continue + format_name = REMAPS.get(workspace["id"], workspace["name"]) + active = workspace["id"] in active_workspaces + try: + mon_id = [monitor["id"] for monitor in monitors if monitor["name"] == workspace["monitor"]][0] + except IndexError: # Sometimes workspace["monitor"] is "?", which doesn't match any monitor + mon_id = -1 + out.append({**workspace, "format_name": format_name, "active": active, "monitor_id": mon_id}) + + out = fill_blank_workspaces(out) + out.sort(key=workspace_sort) + return out + + +def print_workspaces() -> None: + wks = get_workspaces() + ret = json.dumps(wks) + print(ret) + sys.stdout.flush() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + "--oneshot", + action="store_true", + help="Don't listen to stdout for updates, only run once and quit", + ) + parser.add_argument( + "--loop", + action="store_true", + help="Listen to stdout input, once something is received, re-print workspaces" + ) + args = parser.parse_args() + + if args.loop and args.oneshot: + print("Can't use both --oneshot and --loop", file=sys.stdout) + sys.exit(1) + + if args.loop is None and args.oneshot is None: + print("No option specified!", file=sys.stdout) + sys.exit(1) + + # Print workspaces here immediately, we don't want to have to wait for the first + # update from stdin as we only receive those on actual workspace change. + print_workspaces() + + if args.oneshot: + # We've already printed the workspaces once, we can exit now + return + + # Reprint workspaces on each stdin update (flush) + for _ in sys.stdin: + print_workspaces() + + +if __name__ == "__main__": + main() diff --git a/home/programs/graphical/bars/eww/config/windows/calendar.yuck b/home/programs/graphical/bars/eww/config/windows/calendar.yuck new file mode 100644 index 0000000..610f00a --- /dev/null +++ b/home/programs/graphical/bars/eww/config/windows/calendar.yuck @@ -0,0 +1,14 @@ +(defwidget calendar-win [] + (box + :class "calendar-win" + (calendar))) + +(defwindow calendar + :monitor 0 + :geometry (geometry + :x "0%" + :y "0%" + :anchor "top right" + :width "0px" + :height "0px") + (calendar-win)) diff --git a/home/programs/graphical/bars/eww/config/windows/radio-menu.yuck b/home/programs/graphical/bars/eww/config/windows/radio-menu.yuck new file mode 100644 index 0000000..c9a51b5 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/windows/radio-menu.yuck @@ -0,0 +1,78 @@ +(defwidget radio-menu-win [] + (box + :class "radio-menu-box" + :space-evenly false + :orientation "v" + + (box + :class "text-row" + :space-evenly false + (label + :class "title" + :text "Radio/Connections Panel")) + + (box + :class "element-row" + + (box + :class "wifi-box" + :space-evenly false + :orientation "v" + (box + :class "element icon" + :space-evenly false + :halign "center" + (button + :class "wifi-button" + :tooltip "${net.state} (strength: ${net.signal}%)" + :onclick "scripts/net toggle" + {net.icon}) + (label + :class "separator" + :text "│") + (button + :class "wifi-arrow-btn" + :onclick "eww close radio-menu && nm-connection-editor &" + "󰅂")) + (label + :text {net.essid} + :xalign 0.5 + :limit-width 15)) + + (box + :class "bluetooth-box" + :space-evenly false + :orientation "v" + (box + :class "element icon" + :space-evenly false + :halign "center" + (button + :class "bluetooth-button" + :onclick "scripts/bluetooth toggle" + :tooltip "${bluetooth.name} (${bluetooth.mac}) ${bluetooth.battery}" + {bluetooth.icon}) + (label + :class "separator" + :text "│") + (button + :class "bluetooth-arrow-btn" + :onclick "eww close radio-menu && blueberry" + "󰅂")) + (label + :text {bluetooth.name} + :xalign 0.5 + :tooltip "${bluetooth.name} (${bluetooth.mac}) ${bluetooth.battery}" + :limit-width 15))) + )) + +(defwindow radio-menu + :stacking "fg" + :monitor 0 + :geometry (geometry + :x "0" + :y "0" + :width "0%" + :height "0%" + :anchor "right top") + (radio-menu-win)) diff --git a/home/programs/graphical/bars/eww/default.nix b/home/programs/graphical/bars/eww/default.nix new file mode 100644 index 0000000..cb06535 --- /dev/null +++ b/home/programs/graphical/bars/eww/default.nix @@ -0,0 +1,62 @@ +{ + lib, + pkgs, + osConfig, + ... +}: let + inherit (lib) mkIf; + + cfg = osConfig.myOptions.home-manager.programs.bars.eww; +in { + config = mkIf cfg.enable { + programs.eww = { + enable = true; + configDir = ./config; + }; + + systemd.user.services = { + "eww" = { + Unit = { + Description = "ElKowar's Wacky Widgets (eww) daemon"; + After = [ "graphical-session-pre.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + + Service = { + Type = "simple"; + Restart = "always"; + ExecStart = pkgs.writeShellScript "eww-daemon" '' + ${pkgs.eww}/bin/eww daemon --no-daemonize + ''; + + # Takes a value between -20 and 19. Higher values (e.g. 19) mean lower priority. + # Lower priority means the process will get less CPU time and therefore will be slower. + # Fortunately, I do not need my status bar to be fast. Also, te difference is almost + # unnoticeable and definitely negligible. + Nice = 19; + }; + + Install.WantedBy = [ "graphical-session.target" ]; + }; + + "eww-window@" = { + Unit = { + Description = "Open %I eww (ElKowar's Wacky Widgets) window"; + After = [ "eww.service" ]; + PartOf = [ "graphical-session-pre.target" ]; + }; + + Service = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStartPre = "${pkgs.eww}/bin/eww ping"; + ExecStart = "${pkgs.eww}/bin/eww open %i"; + ExecStop = "${pkgs.eww}/bin/eww close %i"; + Restart = "on-failure"; + }; + + Install.WantedBy = [ "graphical-session.target" ]; + }; + }; + }; +} diff --git a/home/programs/graphical/default.nix b/home/programs/graphical/default.nix index 6b2ded2..233cea0 100644 --- a/home/programs/graphical/default.nix +++ b/home/programs/graphical/default.nix @@ -3,5 +3,6 @@ _: { ./wms ./launchers ./apps + ./bars ]; } diff --git a/hosts/voyager/default.nix b/hosts/voyager/default.nix index 990e772..4b424dd 100644 --- a/hosts/voyager/default.nix +++ b/hosts/voyager/default.nix @@ -125,6 +125,7 @@ }; programs = { + bars.eww.enable = true; spotify.enable = true; }; }; diff --git a/options/home/programs/default.nix b/options/home/programs/default.nix index c6bdb95..f37aae7 100644 --- a/options/home/programs/default.nix +++ b/options/home/programs/default.nix @@ -12,6 +12,13 @@ in }; }; + bars = { + eww = { + enable = mkEnableOption "Eww bar"; + autostart.enable = mkEnableOption "auto-starting eww daemon on graphical-session.target"; + }; + }; + spotify.enable = mkEnableOption "Spotify"; }; }