From 9801adfbc29da89447a6319f5a0c677af4c8984b Mon Sep 17 00:00:00 2001 From: Ezri Brimhall Date: Wed, 6 Mar 2024 18:13:34 -0700 Subject: [PATCH] Updated for normandy --- .../eww##hostname.normandy/.python-version | 1 + .config/eww##hostname.normandy/colors.scss | 13 + .config/eww##hostname.normandy/eww.scss | 115 ++++++++ .config/eww##hostname.normandy/eww.yuck | 130 +++++++++ .../modules/angles/angles.yuck | 11 + .../modules/angles/left.png | Bin 0 -> 1061 bytes .../modules/angles/right.png | Bin 0 -> 1177 bytes .../eww##hostname.normandy/modules/audio.py | 34 +++ .../eww##hostname.normandy/modules/audio.yuck | 17 ++ .../modules/clock/clock.yuck | 67 +++++ .../eww##hostname.normandy/modules/i3.json | 102 +++++++ .config/eww##hostname.normandy/modules/i3.py | 114 ++++++++ .../eww##hostname.normandy/modules/i3.yuck | 47 +++ .../eww##hostname.normandy/modules/mpris.py | 111 +++++++ .../eww##hostname.normandy/modules/mpris.yuck | 17 ++ .../modules/network/network.py | 64 ++++ .../modules/network/network.yuck | 194 ++++++++++++ .../eww##hostname.normandy/modules/reboot.sh | 10 + .../eww##hostname.normandy/modules/system.py | 66 +++++ .../modules/system.yuck | 79 +++++ .../modules/system/system.py | 111 +++++++ .../modules/system/system.yuck | 276 ++++++++++++++++++ .../modules/timer/timer.py | 44 +++ .../modules/timer/timer.yuck | 29 ++ .../modules/volume/volume.py | 50 ++++ .../modules/volume/volume.yuck | 28 ++ .../modules/workspaces/hyprland.py | 213 ++++++++++++++ .../modules/workspaces/sway.py | 184 ++++++++++++ .../modules/workspaces/workspaces.yuck | 75 +++++ .config/sway/config | 254 +++++++++++++++- ...isplay-arrangement.conf##hostname.normandy | 9 + ...0-display-arrangement.conf##hostname.tycho | 7 + ...settings.conf => 11-display-settings.conf} | 10 +- .config/sway/swayidle | 8 +- .../sway/workspaces.json##hostname.normandy | 109 +++++++ ...s.json => workspaces.json##hostname.tycho} | 0 .local/bin/i3-sensible-workspaces | 13 +- .local/lib/zsh/zsh-autosuggestions | 2 +- .local/lib/zsh/zsh-history-substring-search | 2 +- .local/lib/zsh/zsh-syntax-highlighting | 2 +- 40 files changed, 2587 insertions(+), 31 deletions(-) create mode 100644 .config/eww##hostname.normandy/.python-version create mode 100644 .config/eww##hostname.normandy/colors.scss create mode 100644 .config/eww##hostname.normandy/eww.scss create mode 100644 .config/eww##hostname.normandy/eww.yuck create mode 100644 .config/eww##hostname.normandy/modules/angles/angles.yuck create mode 100644 .config/eww##hostname.normandy/modules/angles/left.png create mode 100644 .config/eww##hostname.normandy/modules/angles/right.png create mode 100755 .config/eww##hostname.normandy/modules/audio.py create mode 100644 .config/eww##hostname.normandy/modules/audio.yuck create mode 100644 .config/eww##hostname.normandy/modules/clock/clock.yuck create mode 100644 .config/eww##hostname.normandy/modules/i3.json create mode 100755 .config/eww##hostname.normandy/modules/i3.py create mode 100644 .config/eww##hostname.normandy/modules/i3.yuck create mode 100755 .config/eww##hostname.normandy/modules/mpris.py create mode 100644 .config/eww##hostname.normandy/modules/mpris.yuck create mode 100755 .config/eww##hostname.normandy/modules/network/network.py create mode 100644 .config/eww##hostname.normandy/modules/network/network.yuck create mode 100755 .config/eww##hostname.normandy/modules/reboot.sh create mode 100755 .config/eww##hostname.normandy/modules/system.py create mode 100644 .config/eww##hostname.normandy/modules/system.yuck create mode 100755 .config/eww##hostname.normandy/modules/system/system.py create mode 100644 .config/eww##hostname.normandy/modules/system/system.yuck create mode 100755 .config/eww##hostname.normandy/modules/timer/timer.py create mode 100644 .config/eww##hostname.normandy/modules/timer/timer.yuck create mode 100755 .config/eww##hostname.normandy/modules/volume/volume.py create mode 100644 .config/eww##hostname.normandy/modules/volume/volume.yuck create mode 100755 .config/eww##hostname.normandy/modules/workspaces/hyprland.py create mode 100755 .config/eww##hostname.normandy/modules/workspaces/sway.py create mode 100644 .config/eww##hostname.normandy/modules/workspaces/workspaces.yuck create mode 100644 .config/sway/config.d/10-display-arrangement.conf##hostname.normandy create mode 100644 .config/sway/config.d/10-display-arrangement.conf##hostname.tycho rename .config/sway/config.d/{10-display-settings.conf => 11-display-settings.conf} (92%) create mode 100644 .config/sway/workspaces.json##hostname.normandy rename .config/sway/{workspaces.json => workspaces.json##hostname.tycho} (100%) diff --git a/.config/eww##hostname.normandy/.python-version b/.config/eww##hostname.normandy/.python-version new file mode 100644 index 0000000..bba70ea --- /dev/null +++ b/.config/eww##hostname.normandy/.python-version @@ -0,0 +1 @@ +eww-modules diff --git a/.config/eww##hostname.normandy/colors.scss b/.config/eww##hostname.normandy/colors.scss new file mode 100644 index 0000000..a62e408 --- /dev/null +++ b/.config/eww##hostname.normandy/colors.scss @@ -0,0 +1,13 @@ +$wallpaper: #1e1e1e; +$foreground: #9a7c9d; + +$bg0: #2d272f; +$bg1: #3f3242; + +$red: #cf6a4c; +$green: #8f9d6a; +$yellow: #f9ee98; +$blue: #7587a6; +$magenta: #9b859d; +$cyan: #afc4db; +$white: #a7a7a7; diff --git a/.config/eww##hostname.normandy/eww.scss b/.config/eww##hostname.normandy/eww.scss new file mode 100644 index 0000000..d6aeebc --- /dev/null +++ b/.config/eww##hostname.normandy/eww.scss @@ -0,0 +1,115 @@ +@import 'colors'; + +* { + all: unset; +} + +.root { + color: $foreground; + font-family: "Source Code Pro"; + font-size: 7.5pt; + background-color: rgba(0,0,0,0); + // Probably needs to change + &.bar { + margin: 10px; + margin-bottom: 0px; + } + + &.side { + margin: 10px; + margin-right: 0px; + } +} + +.gauge { + color: $foreground; +} + +.gauge-hole { + color: $wallpaper; +} + +.gauge-gutter { + color: $bg0; +} + +.icon { + font-family: "Font Awesome 5 Free Solid"; +} + +.invisible { + color: $wallpaper; +} + +.highlight { + color: $red; +} + +.offline { + color: $bg1; +} + +.special { + color: $blue; +} + +.green { + color: $green; +} + +.message-overlay { + background-color: $wallpaper; +} + +.nebula { + font-family: "Nebula"; + font-size: 1.2em; + &.big, .big { + font-size: 2.4em; + } + &.bigger, .bigger { + font-size: 2.9em; + } + &.medium, .medium { + font-size: 1.6em; + } + &.medium-big, .medium-big { + font-size: 2em; + } + +} + +.big .normal, .bigger .normal { + font-size: 1rem; +} + +// sway module + +.sway--ws { + + .fill { + background-color: $foreground; + } + color: $bg1; + + &.sway--active { + color: $foreground; + } + + &.sway--visible { + color: $blue; + } + + &.sway--focused { + } +} + +// clock module + +.clock--time { + font-size: 20pt; +} + +.clock--date { + font-size: 9pt; +} diff --git a/.config/eww##hostname.normandy/eww.yuck b/.config/eww##hostname.normandy/eww.yuck new file mode 100644 index 0000000..add6034 --- /dev/null +++ b/.config/eww##hostname.normandy/eww.yuck @@ -0,0 +1,130 @@ +;; Include modules +(include "./modules/workspaces/workspaces.yuck") +(include "./modules/clock/clock.yuck") +(include "./modules/system/system.yuck") +(include "./modules/network/network.yuck") +(include "./modules/volume/volume.yuck") +(include "./modules/timer/timer.yuck") + +;; Windows +(defwidget leftbar--left [] + (box :orientation "h" + :halign "start" + :space-evenly false + :spacing 20 + :class "leftbox" + (system-name))) + + +(defwidget leftbar--center [] + (box :orientation "h" + :halign "center" + :space-evenly false + :spacing 0 + :class "centerbox" + (met) + )) + +(defwidget leftbar--right [] + (box :orientation "h" + :halign "end" + :space-evenly false + :spacing 20 + :class "rightbox" + (sway-workspace :group "left") + (sway-workspaces :group "left"))) + +(defwidget rightbar--left [] + (box :orientation "h" + :halign "start" + :space-evenly false + :spacing 20 + :class "leftbox" + (sway-workspaces :group "right") + (sway-workspace :group "right"))) + +(defwidget rightbar--hypr-left [] + (box :orientation "h" + :halign "start" + :space-evenly false + :spacing 20 + :class "leftbox" + (hypr-workspaces :group "main") + (hypr-workspace :group "main"))) + +(defwidget rightbar--center [] + (box :orientation "h" + :halign "center" + :space-evenly false + :spacing 0 + :class "centerbox" + (met) + )) + +(defwidget rightbar--right [] + (box :orientation "h" + :halign "end" + :space-evenly false + :spacing 20 + :class "leftbox" + (system-name))) + +(defwidget sidebar [] + (box :orientation "v" + :valign "start" + :space-evenly false + :spacing 20 + :class "bar root" + (sideclock) + (network-details) + (system-gauges) + )) + +(defwindow leftbar + :monitor 1 + :geometry (geometry :width "100%" + :height "36px" + :anchor "top center") + :exclusive true + :focusable false + :stacking "fg" + (centerbox :orientation "h" :class "bar root" + (leftbar--left) + (leftbar--center) + (leftbar--right))) + +(defwindow rightbar + :monitor 0 + :geometry (geometry :width "100%" + :height "26px" + :anchor "top center") + :exclusive true + :focusable false + :stacking "fg" + (centerbox :orientation "h" :class "bar root" + (rightbar--left) + (rightbar--center) + (rightbar--right))) + +(defwindow hypr-mainbar + :monitor 0 + :geometry (geometry :width "100%" + :height "36px" + :anchor "top center") + :exclusive true + :focusable false + :stacking "overlay" + (centerbox :orientation "h" :class "bar root" + (rightbar--hypr-left) + (rightbar--center) + (rightbar--right))) + +(defwindow sidebar + :monitor 1 + :geometry (geometry :width "210px" + :height "1044px" + :anchor "left bottom") + :exclusive true + :focusable false + :stacking "fg" + (sidebar)) diff --git a/.config/eww##hostname.normandy/modules/angles/angles.yuck b/.config/eww##hostname.normandy/modules/angles/angles.yuck new file mode 100644 index 0000000..c424577 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/angles/angles.yuck @@ -0,0 +1,11 @@ +(defwidget left-angle [] + (image :path "/home/ezri/.config/eww.new/modules/angles/left.png" + :width 150 + :height 36)) + +(defwidget right-angle [] + (image :path "/home/ezri/.config/eww.new/modules/angles/right.png" + :width 150 + :height 36)) + + diff --git a/.config/eww##hostname.normandy/modules/angles/left.png b/.config/eww##hostname.normandy/modules/angles/left.png new file mode 100644 index 0000000000000000000000000000000000000000..f79354dfd326326ae78d3a452ea6dc0a4a7deddb GIT binary patch literal 1061 zcmV+=1ls$FP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H11F}g( zK~#90?b}Ol8+8-~@N>s@8s||Ui$D+(q7p9&!4xTdXeQxNv*^y{v56E=#0TILbi>DB zMF4pLNQhMzn6`>%B$AXeAW^!as9jXF1Uup3Cbs9WNQ#E3O+v8!_e}0@YstQgBTGj! zN8Wn()6Ynp0`Mb{Tykz0&xhu1h63x4}vm4 zfAQ>VSBhuO(DcD78KhV&w*6qjo5y2}0j!!0|K%5ZZdPsPdMV}!rZUm+4 zUHLIJ@VXYPxO)o$Una0FfG;AR7J_u^S+LsKE(9o$bOFo)I0V5Zke&$3kA+vh34$Q( z+d-;l`WmdbHih7E2v)QZB%je>#pS0L9?Uiy#{}X;3ju&KK|jMxr4}sKOy8+?H&DTf z%h_{dd1OvNLKh-Vv=Bg8&gOWrx>CAr`c8GPfeuz&n?i7kKrLDb)-Qkbz3Dqu!h#i- zo14pwT(zD8#MvnXF9661#3Sg7gq{`xGZG!Fb|QtKUeG~c0jq~qqn!Ypb zQWvba`|Y`hT4GIa7XlOjJ_+Dk?EI_c4~0v&zACMozB6r7AFQ}Gh2SJ0SMNTGYO-;m z9(??Z={u9|zy&LA^7QH4=Fbm2OG*JEE+`Jc>fJ&RO6!Z?zAjxeeP_}f_+Z7gDFiP9 z$VLmn;@LMY>!YZDr@`8T=(}mLLQn?j*MXHrP}(wm=OEEw?Zepg{FF39_aE=30p9`Q zfhgAxxBSLZsc!mClQdXeFmZBWyjgD^?^FmRmpQ+&^1+(vJNrq4)dQ3E+aq^Se}3oP zG#A7Sfi)0i%c3t~cBvLD{$%>jUeaLoL9`HnC`899X)OdAEJNaB6#%0l&;BY`68gW#ukZFTHpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H11SUyD zK~#90?VCYp9Ay}W-|wH@*iDmEgH|g6v4>Eh;8aN3Fk`8@-cw0EEVLRu^r+y;Lm?h> z%`qpF%3kzR5b9wOO=rQZrCEx$JyqKrgq9@RlqQ?Z?EiX5wjFl65@L2|XY)Vzf95;9 z3=i|apNx)Ajb>C6P0C7O137!eSK&U(s<{^4|_R_fU+N-e5gA3DGV@oH92?!lErn>(j@D*`=uGY=j9` zoA9EYd95O9NFWY?bWrvOA-D-BkK|P91wHoQx9p>+=iDhKSP@Yx+8P)TK>^e`kaeQ{ zILcvnfeBU%2#YpJ7r>bSOoT=IsJ^vOTqx?OXWc7Iu$sWLs}V^<#KmoKG`J#QUPL@g zV`8~@`EK}3gPW1qVuG~?B<)O33f8HrX!mhx1XTA(cmQV;N&VSMA^R}uIa`Sd)-Fg5 zjlY@*{NXLprVHRWz(1;@ZP7YcD$bOmo-;~Juy#PGgOuL-qwNo1OSJQ#nmE*-y0-Z9 z^hVTkwi6~;HQ2VT{!+)^3W@gV0DhN(^4LJMS0kLaUD@}+s_h`%1wSBBs){zi$wxQ7 z`!jr|(?q)&?7Lv~Iv*XBCkIY!UyY>uxO56s_epr7^pBQGezkJ_+r_Boj5{44tX})V zVTeBYRJ7M{0wT8oNz`&jtf?8*&on@5X@IaTVd5J6|a<|p0m@5 zeXzE!Mv&eX?RP8KOjXPNBKqQ+9ri~%F2pKW^<{s)AAp-e@kAsCaI&`SZ$vv9+PaTb zBieOke`@}+TFl;$dQL+fE&F2)Hbk0I_BS+_NP|@`+FM(-AoHMVYT4h=Tv{5e za0dy3bO66cLS_F(TeXJx(X?RIm;Ik@)rv4LqNitS%KnYEY7OyY&*VLBWq(6++4UDI z?qz>NbJ=mQYPV{`Wq)W)8qqd{X&9{MY}F2IZ`I~ny;W<79|>S*W&bF*Y7Oy2E`0OD rPXI;$EDCWALQh%AIa3`m{2%@W0Qq2h)60p900000NkvXXu0mjfb9pXb literal 0 HcmV?d00001 diff --git a/.config/eww##hostname.normandy/modules/audio.py b/.config/eww##hostname.normandy/modules/audio.py new file mode 100755 index 0000000..e68f3b8 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/audio.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import asyncio +import signal +from contextlib import suppress +import pulsectl_asyncio +import json + +async def get_values(pulse): + sink = await pulse.get_sink_by_name("@DEFAULT_SINK@") + result = { + "mute": sink.mute == 1, + "volume": f"{int(sink.volume.value_flat * 100):2}" + } + print(json.dumps(result), flush=True) + return result + +async def listen(): + async with pulsectl_asyncio.PulseAsync("volume-monitor") as pulse: + await get_values(pulse) + async for _ in pulse.subscribe_events('sink'): + await get_values(pulse) + +async def main(): + listen_task = asyncio.create_task(listen()) + + for sig in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT): + loop.add_signal_handler(sig, listen_task.cancel) + + with suppress(asyncio.CancelledError): + await listen_task + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/.config/eww##hostname.normandy/modules/audio.yuck b/.config/eww##hostname.normandy/modules/audio.yuck new file mode 100644 index 0000000..435c693 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/audio.yuck @@ -0,0 +1,17 @@ +(deflisten audio-data + `~/.config/eww/modules/audio.py`) + +(defwidget volume [align] + (box :orientation "h" + :halign align + :class "module volume ${audio-data['mute'] ? 'muted' : ''}" + :space-evenly false + :spacing 0 + (revealer :transition "none" + :reveal {!audio-data["mute"]} + "") + (revealer :transition "none" + :reveal {audio-data["mute"]} + "") + ' ${audio-data["volume"]}%')) + diff --git a/.config/eww##hostname.normandy/modules/clock/clock.yuck b/.config/eww##hostname.normandy/modules/clock/clock.yuck new file mode 100644 index 0000000..3b4ed67 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/clock/clock.yuck @@ -0,0 +1,67 @@ +(defpoll clock--data :interval "500ms" + `date +'{"hour": "%H", "minute": "%M", "second": "%S", "year": "%Y", "day": "%d", "month": "%m", "dow": "%A", "month_name": "%B", "unix": "%s"}'`) + +(defvar clock--show "clock") + +(defwidget clock [] + (box :class "module text" + :spacing 0 + :orientation "v" + (label :class "special" + :text "${clock--data['hour']}:${clock--data['minute']}") + (label :text "${clock--data['year']}-${clock--data['month']}-${clock--data['day']}"))) + +(defwidget sideclock [] + (button :onclick "echo -n $(date +%Y-%d-%m) | wl-copy" + :onrightclick "echo -n $(date +%s) | wl-copy && eww update clock--show=unixtime && sleep 2 && eww update clock--show=clock" + (overlay + (box :class "module text nebula" + :spacing 0 + :space-evenly false + :orientation "v" + (centerbox :class "bigger nebula" + :space-evenly false + :width 150 + :halign "center" + :orientation "h" + (box :halign "start" "${clock--data.hour}") + (box :halign "center" ":") + (box :halign "end" "${clock--data.minute}")) + (label :text "${clock--data.dow}" + :class "special") + (label :text "${clock--data.month_name} ${clock--data.day}")) + (box + :height 100 + (revealer :transition "crossfade" + :reveal {clock--show == "unixtime"} + :duration {clock--show == "unixtime" ? "2s" : "500ms"} + (box :width 200 + :height 50 + :class "message-overlay nebula special" + (box + :space-evenly false + :spacing 0 + :halign "center" + :valign "center" + :orientation "v" + "Copied UNIX" + "timestamp" + )))) + (box + :height 100 + (revealer :transition "crossfade" + :reveal {clock--show == "date"} + :duration {clock--show == "date" ? "2s" : "500ms"} + (box :width 200 + :height 50 + :class "message-overlay nebula special" + (box + :space-evenly false + :spacing 0 + :halign "center" + :valign "center" + :orientation "v" + "Copied" + "current date" + + ))))))) diff --git a/.config/eww##hostname.normandy/modules/i3.json b/.config/eww##hostname.normandy/modules/i3.json new file mode 100644 index 0000000..311ccb0 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/i3.json @@ -0,0 +1,102 @@ +{ + "1": { + "name": "1", + "use": "terminal", + "groups": ["left"] + }, + "2": { + "name": "2", + "use": "code", + "groups": ["left"] + }, + "3": { + "name": "3", + "use": "web", + "groups": ["left"] + }, + "4": { + "groups": ["left"], + "name": "4", + "use": "project" + }, + "5": { + "groups": ["left"], + "name": "5", + "use": "vms" + }, + "6": { + "groups": ["left"], + "name": "6", + "use": "documents" + }, + "7": { + "groups": ["left"], + "name": "7", + "use": "media" + }, + "8": { + "groups": ["left"], + "name": "8", + "use": "discord" + }, + "9": { + "groups": ["left"], + "name": "9", + "use": "zoom" + }, + "10": { + "groups": ["left"], + "name": "10", + "use": "config" + }, + "11": { + "groups": ["right"], + "name": "1", + "use": "terminal" + }, + "12": { + "groups": ["right"], + "name": "2", + "use": "code" + }, + "13": { + "groups": ["right"], + "name": "3", + "use": "web" + }, + "14": { + "groups": ["right"], + "name": "4", + "use": "project" + }, + "15": { + "groups": ["right"], + "name": "5", + "use": "misc" + }, + "16": { + "groups": ["right"], + "name": "6", + "use": "documents" + }, + "17": { + "groups": ["right"], + "name": "7", + "use": "media" + }, + "18": { + "groups": ["right"], + "name": "8", + "use": "slack" + }, + "19": { + "groups": ["right"], + "name": "9", + "use": "email" + }, + "20": { + "groups": ["right"], + "name": "10", + "use": "config" + } +} diff --git a/.config/eww##hostname.normandy/modules/i3.py b/.config/eww##hostname.normandy/modules/i3.py new file mode 100755 index 0000000..794ef93 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/i3.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +import asyncio +import json +import os +from i3ipc.aio import Connection +from i3ipc import Event +from typing import List + +class WorkspaceConfig: + + i3_index: str + sorting_index: int + display_index: str + display_name: str + display_groups: List[str] + + def __init__(self, i3_index: str, dictionary: dict): + self.i3_index = i3_index + self.sorting_index = dictionary.get("sort", int(i3_index)) + self.display_index = dictionary["name"] + self.display_name = dictionary.get("use", "???") + self.display_groups = dictionary.get("groups", ['default']) + + @classmethod + def parse_file(cls: type, filename: str): + result = [] + dictionary: dict = None + + with open(filename, 'r') as file: + dictionary = json.load(file) + + for index, data in dictionary.items(): + result.append(cls(index, data)) + + result.sort(key=lambda config: config.sorting_index) + + return result + +class WorkspaceStatus: + + config: WorkspaceConfig + expanded: bool + state: str + active: bool + + def __init__(self, config, state = None): + self.config = config + self.state = state or "" + self.active = state != None + self.expanded = state == "focused" or state == "visible" + + def dict_dump(self): + return { + "i3_index": self.config.i3_index, + "name": self.config.display_index, + "purpose": self.config.display_name, + "expand": self.expanded, + "state": self.state, + "active": self.active + } + +config = WorkspaceConfig.parse_file(f"{os.environ['HOME']}/.config/eww/modules/i3.json") + +groups = [] + +for ws in config: + for group in ws.display_groups: + if not group in groups: + groups.append(group) + +data = { + "ws": {}, + "mode": "default" +} + +def write_data(): + global data + print(json.dumps(data), flush=True) + +async def workspace_event(self: Connection, _): + global config, data + + ws_i3_dict = { ws.name: ws for ws in await self.get_workspaces()} + status = [] + for ws_def in config: + ws_i3 = ws_i3_dict.get(ws_def.i3_index, None) + if ws_i3: + state = ws_i3.focused and "focused" or ws_i3.visible and "visible" or ws_i3.urgent and "urgent" or "unfocused" + status.append(WorkspaceStatus(ws_def, state)) + else: + status.append(WorkspaceStatus(ws_def)) + + for group in groups: + data['ws'][group] = [ ws.dict_dump() for ws in filter(lambda ws: group in ws.config.display_groups, status) ] + # data['ws']['left'] = [ ws.dict_dump() for ws in filter(lambda ws: ws.config.sorting_index in range(1, 11), status) ] + # data['ws']['right'] = [ ws.dict_dump() for ws in filter(lambda ws: ws.config.sorting_index in range(11, 21), status) ] + write_data() + +async def mode_event(self: Connection, event): + global data + data['mode'] = event.change; + write_data() + +async def main(): + i3 = await Connection(auto_reconnect = True).connect() + + i3.on(Event.WORKSPACE_FOCUS, workspace_event) + i3.on(Event.WORKSPACE_URGENT, workspace_event) + i3.on(Event.MODE, mode_event) + await workspace_event(i3, None) + await i3.main() + +asyncio.run(main()) diff --git a/.config/eww##hostname.normandy/modules/i3.yuck b/.config/eww##hostname.normandy/modules/i3.yuck new file mode 100644 index 0000000..210a05a --- /dev/null +++ b/.config/eww##hostname.normandy/modules/i3.yuck @@ -0,0 +1,47 @@ +(defwidget workspace [description] + (revealer :transition "slideright" + :reveal {description["active"]} + :duration "500ms" + (button :onclick "i3-msg workspace ${description['i3_index']}" + (box :orientation "h" + :halign "start" + :class '${description["state"]} workspace' + :space-evenly false + :spacing 1 + (revealer :transition "slideleft" :reveal {description["state"] == "urgent"} :duration "500ms" + " ") + {description["name"]} + (revealer :transition "slideright" :reveal {description["expand"]} :duration "500ms" + ': ${description["purpose"]}'))))) + +(deflisten i3-data :initial "[]" + `~/.config/eww/modules/i3.py`) + +(defwidget i3-mode [align] + (box :orientation "h" + :halign align + :space-evenly false + :spacing 0 + :class "module i3-mode" + :visible {i3-data["mode"] != "default"} + "mode: ${i3-data['mode']}")) + + +(defwidget i3 [align group] + (box :orientation "h" + :halign align + :space-evenly false + :spacing 0 + :class "module i3" + (workspace :description {i3-data["ws"][group][0]}) + (workspace :description {i3-data["ws"][group][1]}) + (workspace :description {i3-data["ws"][group][2]}) + (workspace :description {i3-data["ws"][group][3]}) + (workspace :description {i3-data["ws"][group][4]}) + (workspace :description {i3-data["ws"][group][5]}) + (workspace :description {i3-data["ws"][group][6]}) + (workspace :description {i3-data["ws"][group][7]}) + (workspace :description {i3-data["ws"][group][8]}) + (workspace :description {i3-data["ws"][group][9]}) +)) + diff --git a/.config/eww##hostname.normandy/modules/mpris.py b/.config/eww##hostname.normandy/modules/mpris.py new file mode 100755 index 0000000..7b4b0c4 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/mpris.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +import gi +import os +import sys +import json +import time + +try: + gi.require_version('Playerctl', '2.0') +except: + sys.exit(1) + +from gi.repository import Playerctl, GLib + +title_maxlen = 30; +artist_maxlen = 30; + +class Status: + + def __init__(self): + self.running = False + self.playing = False + self.title = None + self.artist = None + self.album = None + self.album_artist = None + + def _dict_dump(self): + return { + "running": self.running, + "playing": self.playing, + "title": self.title, + "artist": self.artist, + "album": self.album, + "album_artist": self.album_artist + } + + def __str__(self): + return json.dumps(self._dict_dump()) + +class StatusDisplay: + + def __init__(self): + self._player = None + + def show(self): + self._init_player() + + main = GLib.MainLoop() + main.run() + + def _get_status(self, playing = None): + if self._player: + status = Status() + status.running = self._player.props.playback_status != 2 + if playing == None: + status.playing = self._player.props.playback_status == 0 + else: + status.playing = playing + status.title = self._player.get_title() or "" + status.artist = self._player.get_artist() or "" + status.album = self._player.get_album() or "" + + if len(status.title) > title_maxlen: + status.title = status.title[:title_maxlen-1] + '…' + if len(status.artist) > artist_maxlen: + status.artist = status.artist[:artist_maxlen-1] + '…' + + return status + else: + return Status() + + def _print_status(self, status): + print(status, flush=True) + + def _init_player(self): + + success = False + + while not success: + try: + self._player = Playerctl.Player() + self._player.connect('metadata', self._on_update) + self._player.connect('play', self._on_play) + self._player.connect('pause', self._on_pause) + self._player.connect('exit', self._on_exit) + + self._print_status(self._get_status()) + + success = True + except: + + self._print_status(Status()) + time.sleep(2) + + def _on_update(self, *args): + self._print_status(self._get_status()) + + def _on_play(self, *args): + self._print_status(self._get_status(playing=True)) + + def _on_pause(self, *args): + self._print_status(self._get_status(playing=False)) + + def _on_exit(self, player): + del self._player + self._player = None + self._init_player() + +StatusDisplay().show() diff --git a/.config/eww##hostname.normandy/modules/mpris.yuck b/.config/eww##hostname.normandy/modules/mpris.yuck new file mode 100644 index 0000000..51bc293 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/mpris.yuck @@ -0,0 +1,17 @@ +(deflisten mpris-data + `~/.config/eww/modules/mpris.py`) + +(defwidget mpris [align] + (box :orientation "h" + :halign align + :class "module mpris ${mpris-data['running'] ? '' : 'offline'} ${mpris-data['playing'] ? 'playing' : 'paused'}" + :space-evenly false + :spacing 0 + (label :class "fontawesome" + :text "") + (revealer :transition "none" + :reveal {!mpris-data['running']} + " players offline") + (revealer :transition "none" + :reveal {mpris-data['running']} + " ${mpris-data['title']} by ${mpris-data['artist']}"))) diff --git a/.config/eww##hostname.normandy/modules/network/network.py b/.config/eww##hostname.normandy/modules/network/network.py new file mode 100755 index 0000000..4b71007 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/network/network.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +import subprocess +import json +import sys +from time import sleep + +TRUSTED_NETWORKS = ['honnouji', 'honnouji_2.4'] + +def wifi(): + ssid_cmd = subprocess.run(['iwgetid', '-r'], capture_output = True) + if ssid_cmd.returncode != 0: + return {"connected": False, "ssid": None} + return {"connected": True, "ssid": ssid_cmd.stdout.decode('utf-8').strip()} + +def netdev(device: str): + ip_cmd = subprocess.run(['ip', '-j', 'addr', 'show', device], capture_output = True) + if ip_cmd.returncode != 0: + sys.stderr.write(ip_cmd.stdout.decode('utf-8')) + return {"exists": False, "online": False} + ip_data = json.loads(ip_cmd.stdout.decode('utf-8'))[0] + ip4_addr = "" + ip4_prefix_length = 24 + addr4_info = list(filter(lambda addr: addr.get("family") == "inet", ip_data["addr_info"])) + if len(addr4_info) >= 1: + ip4_addr = addr4_info[0]["local"] + ip4_prefix_length = addr4_info[0]["prefixlen"] + online = ip_data["operstate"] == "UP" or ip4_addr != "" + connecting = ip_data["operstate"] != "DOWN" and (ip4_addr == "" or not online) + return { + "exists": True, + "online": online, + "connecting": connecting, + "offline": not online and not connecting, + "ip4_addr": ip4_addr, + "ip4_prefix": ip4_prefix_length + } + +def trusted(ssid: str | None): + # Don't throw up an "INSECURE" alert when offline + if not ssid: + return True + return ssid in TRUSTED_NETWORKS + +while True: + insight = netdev('insight') + wlan0 = netdev('wlan0') + wifi_data = wifi() + connected = insight['exists'] and insight['online'] or wifi_data['connected'] + networks = { + "insight": insight, + "wlan0": netdev("wlan0"), + "ezrinet": netdev("ezrinet"), + "wg-mullvad": netdev("wg-mullvad"), + } + result = { + 'connected': connected, + "network": networks, + 'wifi': wifi_data, + "trusted": True, + "ip4_addrs": [ network.get('ip4_addr') for network in networks.values() if network.get('ip4_addr', "") != "" ] + } + print(json.dumps(result), flush=True) + sleep(1) diff --git a/.config/eww##hostname.normandy/modules/network/network.yuck b/.config/eww##hostname.normandy/modules/network/network.yuck new file mode 100644 index 0000000..cf95775 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/network/network.yuck @@ -0,0 +1,194 @@ +(deflisten network--data + `~/.config/eww/modules/network/network.py`) + +(defwidget network--wlan [device] + (box :orientation "h" + :halign "start" + :space-evenly false + :spacing 0 + (label :class "offline" + :visible {network--data["network"][device]["offline"]} + :text "offline") + (label :visible {network--data["network"][device]["connecting"]} + :class "highlight" + :text "connecting...") + (label :visible {network--data["network"][device]["online"]} + :class "special" + :text "${network--data['wifi']['ssid']}"))) + +(defwidget network--lan [device] + (box :orientation "h" + :halign "start" + :space-evenly false + :spacing 0 + (label :class "offline" + :visible {network--data["network"][device]["offline"]} + :text "offline") + (label :visible {network--data["network"][device]["connecting"]} + :class "highlight" + :text "connecting...") + (label :visible {network--data["network"][device]["online"] && !network--data.network[device].connecting} + :class "special" + :text "${network--data['network'][device]['ip4_addr']}/${network--data['network'][device]['ip4_prefix']}"))) + +(defwidget network--proxy-vpn [device ?required] + (box :orientation "h" + :halign "start" + :space-evenly false + :spacing 0 + :visible {network--data["network"][device]["exists"] || required} + (label :class "highlight" + :text "- insecure" + :visible {! network--data["network"][device]["exists"]}) + (label :class "green" + :text "- secured" + :visible {network--data["network"][device]["exists"]}))) + +(defwidget proxy-network [device] + (box :orientation "v" + :halign "start" + :space-evenly false + :spacing 0 + (label :class "green" + :text "connected" + :visible {network--data.network[device].exists}) + (label :class "offline" + :text "offline" + :visible {!network--data.network[device].exists}) + "proxy vpn")) + +(defwidget vpn-network [] + (box :orientation "v" + :halign "start" + :space-evenly false + :spacing 0 + (label :class "highlight" + :text "offline" + :visible {! network--data["network"]["ezrinet"]["exists"] || ! network--data["connected"]}) + (label :class "green" + :text "connected" + :visible {network--data["network"]["ezrinet"]["exists"] && network--data["connected"]}) + "personal network")) + +(defwidget network [] + (box :orientation "v" + :halign "start" + :space-evenly false + :spacing 0 + (box :orientation "h" + :halign "center" + :space-evenly false + :spacing 10 + (network--lan :device "insight") + (network--proxy-vpn :device "wg-mullvad" :required {!network--data.trusted})) + "communications")) + +(defwidget network-details [] + (box :orientation "v" + :halign "start" + :class "module" + :space-evenly false + :spacing 5 + :width 200 + (box :orientation "h" + :halign "center" + :class "nebula" + :spacing 10 + :space-evenly false + (label :text "Comms" + :class "medium special")) + (centerbox :orientation "h" + :halign "start" + :class "nebula" + :spacing 10 + :width 200 + :space-evenly false + (box :halign "start" + "Status:") + "" + (box :halign "end" + (label :text "Online" + :class "green" + :visible {network--data.connected}) + (label :text "Offline" + :class "highlight" + :visible {!network--data.connected}))) + (centerbox :orientation "h" + :halign "start" + :class "nebula" + :spacing 10 + :width 200 + :space-evenly false + (box :halign "start" + "VPN:") + "" + (box :halign "end" + (label :text "Connected" + :class "green" + :visible {network--data.network.ezrinet.online && network--data.connected}) + (label :text "Offline" + :class "highlight" + :visible {!network--data.network.ezrinet.exists}))) + (centerbox :orientation "h" + :halign "start" + :class "nebula" + :spacing 10 + :width 200 + :space-evenly false + (box :halign "start" + "Proxy:") + "" + (box :halign "end" + (label :text "Connected" + :class "green" + :visible {network--data.network.wg-mullvad.online}) + (label :text "Offline" + :class "highlight" + :visible {!network--data.network.wg-mullvad.exists}))) + (centerbox :orientation "h" + :halign "start" + :class "nebula" + :spacing 10 + :width 200 + :space-evenly false + (box :halign "start" + "Wi-Fi:") + "" + (box :halign "end" + (label :text {network--data.wifi.ssid} + :limit-width 12 + :class "green" + :visible {network--data.wifi.connected}) + (label :text "Offline" + :class "offline" + :visible {!network--data.wifi.connected}))) + (centerbox :orientation "h" + :halign "start" + :spacing 10 + :width 200 + :class "nebula" + :space-evenly false + (box :valign "start" + :halign "start" + "Addrs:") + "" + (box :valign "start" + :halign "end" + :visible {network--data.connected} + :orientation "v" + (for addr in {network--data.ip4_addrs} + (box :class "special" + :halign "end" + {addr})) + (box :visible {!network--data.connected} + :class "offline" + "none"))) + ;; (box :orientation "h" + ;; :halign "start" + ;; :class "nebula" + ;; :spacing 10 + ;; :space-evenly false + ;; (label :text "Addr:") + ;; (label :text {network--data.network[device].ip4_addr} + ;; :class "medium special")))) + )) diff --git a/.config/eww##hostname.normandy/modules/reboot.sh b/.config/eww##hostname.normandy/modules/reboot.sh new file mode 100755 index 0000000..bd2cb1c --- /dev/null +++ b/.config/eww##hostname.normandy/modules/reboot.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env zsh + +installed="$(file /boot/vmlinuz-linux | cut -d',' -f2 | cut -d' ' -f3)" +running="$(uname -r)" + +if [[ $installed == $running ]]; then + echo "false" +else + echo "true" +fi diff --git a/.config/eww##hostname.normandy/modules/system.py b/.config/eww##hostname.normandy/modules/system.py new file mode 100755 index 0000000..9de78ec --- /dev/null +++ b/.config/eww##hostname.normandy/modules/system.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +from time import sleep +import subprocess +import json + +def static(**kwargs): + def decorate(func): + for k in kwargs: + setattr(func, k, kwargs[k]) + return func + return decorate + +@static(idle = 0, total = 0) +def cpu_util(): + with open('/proc/stat') as file: + fields = [float(column) for column in file.readline().strip().split()[1:]] + idle = fields[3] + total = sum(fields) + idle_delta = idle - cpu_util.idle + total_delta = total - cpu_util.total + utilization = 100 * (1 - idle_delta / total_delta) + cpu_util.idle = idle + cpu_util.total = total + return f"{int(utilization):2}" + +def wifi(): + ssid_cmd = subprocess.run(['iwgetid', '-r'], capture_output = True) + if ssid_cmd.returncode != 0: + return {"connected": False, "ssid": ""} + return {"connected": True, "ssid": ssid_cmd.stdout.decode('utf-8').strip()} + +def netdev(device: str): + ip_cmd = subprocess.run(['ip', '-j', 'addr', 'show', device], capture_output = True) + if ip_cmd.returncode != 0: + return {"exists": False} + ip_data = json.loads(ip_cmd.stdout.decode('utf-8'))[0] + online = ip_data["operstate"] == "UP" + ip4_addr = "" + ip4_prefix_length = 24 + addr4_info = list(filter(lambda addr: addr.get("family") == "inet", ip_data["addr_info"])) + if len(addr4_info) >= 1: + ip4_addr = addr4_info[0]["local"] + ip4_prefix_length = addr4_info[0]["prefixlen"] + connecting = ip_data["operstate"] != "DOWN" and (ip4_addr == "" or not online) + return { + "exists": True, + "online": online, + "connecting": connecting, + "ip4_addr": ip4_addr, + "ip4_prefix": ip4_prefix_length + } + +# Prime cpu_util() with starting values +cpu_util() +sleep(0.1) + +while True: + result = { + "cpu": cpu_util(), + "network": { + "br0": netdev("br0") + } + } + print(json.dumps(result), flush=True) + sleep(1) diff --git a/.config/eww##hostname.normandy/modules/system.yuck b/.config/eww##hostname.normandy/modules/system.yuck new file mode 100644 index 0000000..3d55486 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/system.yuck @@ -0,0 +1,79 @@ +(deflisten system-data + `~/.config/eww/modules/system.py`) +(defpoll reboot-needed :interval "10s" + :initial false + `~/.config/eww/modules/reboot.sh`) + +(defwidget reboot [align] + (box :orientation "h" + :halign align + :class "module reboot" + :space-evenly false + :spacing 0 + :visible {reboot-needed} + (button :onclick "~/.local/bin/i3-reboot" + :timeout "60s" + " reboot required"))) + +(defwidget memory [align] + (box :orientation "h" + :halign align + :class "module memory" + :space-evenly false + :spacing 0 + ' ${round(EWW_RAM["used_mem"]/1024/1024, 2)} GiB')) + +(defwidget cpu [align] + (box :orientation "h" + :halign align + :class "module cpu" + :space-evenly false + :spacing 0 + ' ${system-data["cpu"]}%')) + +(defwidget wlan-dev [align device] + (box :orientation "h" + :halign align + :class "module wlan-dev network ${system-data['network'][device]['online'] ? 'online' : 'offline'} ${system-data['network'][device]['connecting'] ? 'connecting' : ''}" + :space-evenly false + :spacing 0 + "" + (revealer :transition "slideright" + :reveal {!system-data["network"][device]["online"] && !system-data["network"][device]["connecting"]} + :duration "500ms" + " offline") + (revealer :transition "slideright" + :reveal {system-data["network"][device]["connecting"]} + :duration "500ms" + " connecting...") + (revealer :transition "slideright" + :reveal {system-data["network"][device]["online"] && system-data["network"][device]["ip4_addr"] != ""} + :duration "500ms" + ' ${system-data["wifi"]["ssid"]}') +)) + +(defwidget lan-dev [align device] + (box :orientation "h" + :halign align + :class "module lan-dev network ${system-data['network'][device]['online'] ? 'online' : 'offline'} ${system-data['network'][device]['connecting'] ? 'connecting' : ''}" + :space-evenly false + :spacing 0 + (revealer :transition "none" + :reveal {!system-data["network"][device]["online"] && !system-data["network"][device]["connecting"]} + "") + (revealer :transition "none" + :reveal {system-data["network"][device]["online"] || system-data["network"][device]["connecting"]} + "") + (revealer :transition "slideright" + :reveal {!system-data["network"][device]["online"] && !system-data["network"][device]["connecting"]} + :duration "500ms" + " offline") + (revealer :transition "slideright" + :reveal {system-data["network"][device]["connecting"]} + :duration "500ms" + " connecting...") + (revealer :transition "slideright" + :reveal {system-data["network"][device]["online"] && system-data["network"][device]["ip4_addr"] != ""} + :duration "500ms" + ' ${system-data["network"][device]["ip4_addr"]}') +)) diff --git a/.config/eww##hostname.normandy/modules/system/system.py b/.config/eww##hostname.normandy/modules/system/system.py new file mode 100755 index 0000000..372ae10 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/system/system.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +from time import sleep +import json +import psutil +import subprocess +import os +import sys + +amdgpuinfo_installed = False +# Try to import GPU info +try: + import pyamdgpuinfo as gpuinfo + amdgpuinfo_installed = True +except: + pass + +def cpu(): + util = psutil.cpu_percent(percpu=False) + per_core = psutil.cpu_percent(percpu=True) + return { + "avg_display": f"{int(util):2}", + "avg": util, + "cores": [ { "value": core, "display": f"{int(core):2}" } for core in per_core ], + "cores_display": "".join([ f"{int(core):3}" for core in per_core ]), + "core_count": len(per_core) + } + +def gpu(): + if not amdgpuinfo_installed or gpuinfo.detect_gpus() <= 0: + return { + "available": False, + "load": 0, + "power_state": 0, + "memory": 0 + } + gpu = gpuinfo.get_gpu(0) + return { + "available": True, + "load": gpu.query_load(), + "power_state": gpu.query_power(), + "memory": gpu.query_vram_usage() / gpu.memory_info['vram_size'] + } + +def sensor(chip: str): + return { temp.label: temp.current for temp in psutil.sensors_temperatures().get(chip) } + +def memory(): + mem = psutil.virtual_memory() + return { + "total": mem.total, + "available": mem.available, + "percent": mem.percent, + "used": mem.used, + "free": mem.free, + "active": mem.active, + "inactive": mem.inactive, + "buffers": mem.buffers, + "cached": mem.cached, + "shared": mem.shared, + "slab": mem.slab + } + +def swap(): + swap = psutil.swap_memory() + return { + "total": swap.total, + "used": swap.used, + "free": swap.free, + "percent": swap.percent + } + +def reboot(): + running = os.uname().release + version_proc = subprocess.run(["pacman", "-Q", "linux"], capture_output=True) + installed = version_proc.stdout.strip().decode('utf-8').split(' ')[1] + return running != installed + +def get_processes(): + return [ process for process in psutil.process_iter() ] + +def cpu_top(processes): + # Get the top 5 CPU usage processes + try: + result = [ { 'name': ps.name(), 'cpu': ps.cpu_percent() } for ps in processes ] + result.sort(key=lambda ps : -ps.get('cpu')) + return result[0:5] + except: + print("[ ERR ] missing process", file=sys.stderr) + +def mem_top(processes): + try: + result = [ { 'name': ps.name(), 'memory': ps.memory_info().rss } for ps in processes ] + result.sort(key=lambda ps : -ps.get('memory')) + return result[0:5] + except: + print("[ ERR ] missing process", file=sys.stderr) + +sensor_list = ['nvme', 'k10temp', 'amdgpu'] + +while True: + result = { + "cpu": cpu(), + "gpu": gpu(), + "sensors": { chip: sensor(chip) for chip in sensor_list }, + "memory": memory(), + "swap": swap(), + "reboot": reboot(), + } + print(json.dumps(result), flush=True) + sleep(1) diff --git a/.config/eww##hostname.normandy/modules/system/system.yuck b/.config/eww##hostname.normandy/modules/system/system.yuck new file mode 100644 index 0000000..85e3e01 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/system/system.yuck @@ -0,0 +1,276 @@ +(defpoll system--hostname :interval "30s" + `hostnamectl hostname --pretty`) + +(deflisten system--data :initial "{}" + `~/.config/eww/modules/system/system.py`) + +(defwidget system-battery [battery] + (box :orientation "v" + :halign "start" + :class "module text" + :space-evenly false + :spacing 0 + (label :class {EWW_BATTERY[battery].status == 'Charging' || EWW_BATTERY[battery].status == 'Full' ? 'green' : EWW_BATTERY[battery].capacity <= 25 ? 'highlight' : 'special'} + :text "${EWW_BATTERY[battery].capacity}%") + (label :text "${EWW_BATTERY[battery].status == 'Charging' || EWW_BATTERY[battery].status == 'Full' ? 'external' : 'internal'} power"))) + +(defwidget system-name [] + (label :halign "center" + :valign "center" + :class "module text big nebula" + :text {system--hostname})) + +(defwidget system-cpu-avg [] + (box :orientation "v" + :halign "start" + :class "module text" + :space-evenly false + :spacing 0 + (label :class {system--data.cpu.avg > 90 ? 'highlight' : 'special'} + :text "${system--data.cpu.avg_display}%") + (label :text "cpu utilization"))) + +(defwidget system--cpu-core [core] + (label :class {core.value > 90 ? "highlight" : "special"} + :text "${core.display}%")) + +(defwidget system-cpu-percore [] + (box :orientation "v" + :halign "start" + :class "module text" + :space-evenly false + ;; (box :orientation "h" + ;; :halign "center" + ;; :space-evenly false + ;; (for core in {system--data.cpu.cores} + ;; (system--cpu-core :core core))) + (label :class "special" + :text {system--data.cpu.cores_display}) + (label :text "per-core cpu utilization") + )) + +(defwidget system-memory [] + (box :orientation "v" + :halign "start" + :class "module text" + :space-evenly false + (label :class {system--data.memory.percent > 85 ? "highlight" : "special"} + :text "${round(system--data.memory.used / 1024 / 1024 / 1024, 2)} / ${round(system--data.memory.total / 1024 / 1024 / 1024, 2)} GiB") + (label :text "system memory"))) + +(defwidget system--gauge [value ?threshold] + (overlay :width 80 + :height 80 + (circular-progress :value 75 + :class "gauge-gutter" + :start-at 37.5 + :thickness 2) + (circular-progress :value {value * 0.75} + :class 'gauge ${value > (threshold ?: 80) ? "highlight" : ""}' + :start-at 37.5 + :thickness 2))) + +(defwidget system-gauges [] + (box :orientation "h" + :space-evenly false + :width 200 + :halign "start" + :spacing 10 + (box :orientation "v" + :space-evenly false + :spacing 20 + :width 95 + :halign "start" + (system--cpu-gauge) + (system--gpu-gauge) + ) + (box :orientation "v" + :space-evenly false + :spacing 20 + :width 95 + :halign "end" + (system--memory-gauge) + (system--vram-gauge)))) + +(defwidget system--memory-gauge [] + (box :orientation "v" + :halign "end" + :width 95 + :space-evenly false + :spacing 10 + :class "nebula" + (label :text "RAM" + :class "medium special") + (box :halign "center" + (overlay + (system--gauge :value {system--data.memory.percent}) + (transform + :translate-y "-2px" + (box :halign "center" + :valign "center" + :orientation "h" + :space-evenly false + (label :text "%" + :class "invisible") + (label :text {round(system--data.memory.percent, 0)} + :class "medium special") + (transform + :translate-y "3px" + (label :text "%" + :class "offline")))))))) + +(defwidget system--cpu-gauge [] + (box :orientation "v" + :halign "start" + :width 95 + :space-evenly false + :spacing 10 + :class "nebula" + (label :text "CPU" + :class "medium special") + (box :halign "center" + (overlay + (system--gauge :value {system--data.cpu.avg}) + (transform + :translate-y "-2px" + (box :halign "center" + :valign "center" + :orientation "h" + :space-evenly false + (label :text "%" + :class "invisible") + (label :text {system--data.cpu.avg_display} + :class "medium special") + (transform + :translate-y "3px" + (label :text "%" + :class "offline")))))) + ;; (box :orientation "v" + ;; :halign "end" + ;; :valign "start" + ;; :width 100 + ;; :space-evenly false + ;; (for ps in {system--data.cpu_top} + ;; (centerbox :width 100 + ;; :orientation "h" + ;; (box :halign "start" + ;; (label :text {ps.name} + ;; :limit-width 7)) + ;; "" + ;; (box :halign "end" + ;; (label :text '${round(ps.cpu, 0)}%'))) + ;; ) + )) + +(defwidget system--gpu-gauge [] + (box :orientation "v" + :halign "start" + :width 95 + :space-evenly false + :spacing 10 + :class "nebula" + (label :text "GPU" + :class "medium special") + (box :halign "center" + (overlay + (system--gauge :value {system--data.gpu.load * 100}) + (transform + :translate-y "-2px" + (box :halign "center" + :valign "center" + :orientation "h" + :space-evenly false + (label :text "%" + :class "invisible") + (label :text {round(system--data.gpu.load * 100, 0)} + :class "medium special") + (transform + :translate-y "3px" + (label :text "%" + :class "offline")))))))) + +(defwidget system--vram-gauge [] + (box :orientation "v" + :halign "start" + :width 95 + :space-evenly false + :spacing 10 + :class "nebula" + (label :text "VRAM" + :class "medium special") + (box :halign "center" + (overlay + (system--gauge :value {system--data.gpu.memory * 100}) + (transform + :translate-y "-2px" + (box :halign "center" + :valign "center" + :orientation "h" + :space-evenly false + (label :text "%" + :class "invisible") + (label :text {round(system--data.gpu.memory * 100, 0)} + :class "medium special") + (transform + :translate-y "3px" + (label :text "%" + :class "offline")))))))) + + + +(defwidget system--gauge-generic [value value-fmt big-text little-text ?subscript ?threshold] + (box :orientation "h" + :halign "start" + :class "module gauge-widget" + :space-evenly true + :spacing 0 + (overlay :width 100 + (circular-progress :value 100 + :class "gauge-hole" + :start-at 0 + :thickness 50 + ) + (circular-progress :value 80 + :class "gauge-gutter" + :start-at 35 + :thickness 2) + (circular-progress :value {value * 0.8} + :class 'gauge ${value > (threshold ?: 80) ? "highlight" : ""}' + :start-at 35 + :thickness 2) + (transform :translate-y "-5px" + :translate-x "3px" + (box :halign "center" + :valign "center" + :class "big nebula" + :space-evenly false + "${value-fmt}" + (box :class "normal offline" + :halign "start" + :valign "end" + (transform :translate-y "-2px" + :translate-x "1px" + {subscript}))))) + (transform :translate-y "-5px" + (box :orientation "v" + :halign "center" + :valign "center" + :space-evenly false + (label :text {big-text} + :class "big nebula") + (label :text {little-text} + :class "nebula special"))))) + +;; (defwidget system-cpu-gauge [] +;; (system--gauge-generic :value {system--data.cpu.avg} +;; :value-fmt {system--data.cpu.avg_display} +;; :big-text "CPU" +;; :little-text "usage" +;; :subscript "%")) + +;; (defwidget system-memory-gauge [] +;; (system--gauge-generic :value {system--data.memory.percent} +;; :value-fmt {round(system--data.memory.percent, 0)} +;; :big-text "RAM" +;; :little-text "${round(system--data.memory.used / 1024 / 1024 / 1024, 1)} GiB" +;; :subscript "%")) diff --git a/.config/eww##hostname.normandy/modules/timer/timer.py b/.config/eww##hostname.normandy/modules/timer/timer.py new file mode 100755 index 0000000..931a503 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/timer/timer.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +import datetime +import os +import json +from time import sleep +import sys + +def get_time(): + try: + with open(f'{os.environ["HOME"]}/.timer', 'rb') as timestamp_file: + return datetime.datetime.fromtimestamp(int.from_bytes(timestamp_file.read(8), 'little')) + except Exception as exc: + return None + +while True: + now = datetime.datetime.now() + end = get_time() + if end is None: + print(json.dumps({ + 'hours': '00', + 'minutes': '00', + 'seconds': '00', + 'future': False, + 'timer': False + }), flush=True) + sleep(0.5) + continue + delta = int((now - get_time()).total_seconds()) + future = False + if delta < 0: + future = True + delta = -delta + hours = int(delta / 3600) + minutes = int((delta % 3600) / 60) + seconds = delta % 60 + print(json.dumps({ + 'hours': hours, + 'minutes': f'{minutes:02}', + 'seconds': f'{seconds:02}', + 'future': future, + 'timer': True + }), flush=True) + sleep(0.5) diff --git a/.config/eww##hostname.normandy/modules/timer/timer.yuck b/.config/eww##hostname.normandy/modules/timer/timer.yuck new file mode 100644 index 0000000..76afba1 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/timer/timer.yuck @@ -0,0 +1,29 @@ +(deflisten timer--data :initial '{"timer": false}' + `~/.config/eww/modules/timer/timer.py`) + +(defwidget timer--gauge [value green] + (overlay :width 180 + :height 180 + (circular-progress :value 75 + :class "gauge-gutter" + :start-at 37.5 + :thickness 2) + (circular-progress :value {value * 0.75} + :class 'gauge ${green ? "green" : ""}' + :start-at 37.5 + :thickness 2))) + + +(defwidget met [] + (box :orientation "h" + :halign "center" + :space-evenly false + :spacing 5 + :class "nebula medium" + :visible {timer--data.timer} + "Baldur's Gate 3: " + (label :text 'T${timer--data.future ? "-" : "+"}' + :class {timer--data.future ? "highlight" : "green"}) + (label :text '${timer--data.hours}:${timer--data.minutes}:${timer--data.seconds}' + :class "special"))) + diff --git a/.config/eww##hostname.normandy/modules/volume/volume.py b/.config/eww##hostname.normandy/modules/volume/volume.py new file mode 100755 index 0000000..8bc8bd9 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/volume/volume.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import asyncio +import signal +from contextlib import suppress +import pulsectl_asyncio +import json +import sys +import os + +async def get_values(pulse): + sink = None + try: + sink = await pulse.get_sink_by_name("@DEFAULT_SINK@") + source = await pulse.get_source_by_name("@DEFAULT_SOURCE@") + except: + # reload the script in the event of an exception + os.execv(sys.argv[0], sys.argv) + sink_result = { + "mute": sink.mute == 1, + "volume": f"{int(sink.volume.value_flat * 100):2}" + } + source_result = { + "mute": source.mute == 1, + "volume": f"{int(source.volume.value_flat * 100):2}" + } + result = { + "output": sink_result, + "input": source_result + } + print(json.dumps(result), flush=True) + return result + +async def listen(): + async with pulsectl_asyncio.PulseAsync("volume-monitor") as pulse: + await get_values(pulse) + async for _ in pulse.subscribe_events('all'): + await get_values(pulse) + +async def main(): + listen_task = asyncio.create_task(listen()) + + for sig in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT): + loop.add_signal_handler(sig, listen_task.cancel) + + with suppress(asyncio.CancelledError): + await listen_task + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/.config/eww##hostname.normandy/modules/volume/volume.yuck b/.config/eww##hostname.normandy/modules/volume/volume.yuck new file mode 100644 index 0000000..31ef72c --- /dev/null +++ b/.config/eww##hostname.normandy/modules/volume/volume.yuck @@ -0,0 +1,28 @@ +(deflisten volume--data + `~/.config/eww/modules/volume/volume.py`) + +(defwidget volume-h [] + (box :orientation "h" + :halign "start" + :class "module ${volume--data['mute'] ? 'offline' : ''}" + :space-evenly false + :spacing 0 + (label :visible {!volume--data["mute"]} :text "") + (label :visible { volume--data["mute"]} :text "") + (label :text " ${volume--data['volume']}"))) + +(defwidget audio [] + (box :orientation "v" + :halign "center" + :class "module text" + :space-evenly false + :spacing 0 + (box :orientation "h" + :halign "center" + :space-evenly false + :spacing 15 + (label :class {volume--data.output.mute ? "offline" : "special"} + :text "${volume--data.output.volume}%") + (label :class {volume--data.input.mute ? "offline" : "special"} + :text "${volume--data.input.volume}%")) + "audio system")) diff --git a/.config/eww##hostname.normandy/modules/workspaces/hyprland.py b/.config/eww##hostname.normandy/modules/workspaces/hyprland.py new file mode 100755 index 0000000..a332f07 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/workspaces/hyprland.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 + +import asyncio +import json +import os +import sys + +WorkspaceTree = dict[str, dict[str, list['Workspace']]] +WorkspaceList = dict[str, 'Workspace'] + +socketdir = f"/tmp/hypr/{os.environ['HYPRLAND_INSTANCE_SIGNATURE']}" +socket1 = f"{socketdir}/.socket.sock" +socket2 = f"{socketdir}/.socket2.sock" + +listened_events = ['workspace', 'movewindow', 'createworkspace', 'destroyworkspace'] + +class Workspace: + + index: str = None + name: str = None + exec_cmd: str = None + group: str = None + + active: bool = False + visible: bool = False + focused: bool = False + alerted: bool = False + + def __init__(self, dictionary: dict[str, str | int], group: str): + self.index = str(dictionary.get('index')) + self.name = dictionary.get('name') + self.exec_cmd = dictionary.get('exec') + self.group = group + + self.active = dictionary.get('active', False) + self.visible = dictionary.get('visible', False) + self.focused = dictionary.get('focused', False) + self.alerted = dictionary.get('alerted', False) + + def update_state(self, state_update, monitor_dict): + self.active = True + self.visible = monitor_dict[state_update['monitor']]['activeWorkspace']['name'] == state_update['name'] + self.focused = self.visible and monitor_dict[state_update['monitor']]['focused'] + self.alerted = False + + def deactivate(self): + self.active = False + self.visible = False + self.focused = False + self.alerted = False + + @classmethod + def parse_file(cls: type, filename: str) -> tuple[WorkspaceTree, WorkspaceList]: + + result = {} + + initial: dict = None + with open(filename, 'r') as file: + initial = json.load(file) + + workspaces: WorkspaceList = {} + + def find_workspace(workspace: dict[str, str|int], group: str) -> 'Workspace': + index = str(workspace.get('index')) + if index in workspaces: + return workspaces[index] + else: + workspaces[index] = cls(workspace, group) + return workspaces[index] + + result = { + context: { + group: [ + find_workspace(workspace, group) for workspace in workspaces + ] for group, workspaces in groups.items() + } for context, groups in initial.items() + } + + return result, workspaces + + @staticmethod + def full_dictify(tree: WorkspaceTree): + return { + context: { + group: [ + workspace.dictify() for workspace in workspaces + ] for group, workspaces in groups.items() + } for context, groups in tree.items() + } + + def dictify(self): + + return { + 'index': self.index, + 'name': self.name, + 'exec': self.exec_cmd, + 'active': self.active, + 'visible': self.visible, + 'focused': self.focused, + 'alerted': self.alerted + } + +workspace_tree, workspace_list = Workspace.parse_file(f"{os.environ['HOME']}/.config/hypr/workspaces.json") + +data = { + "ws": {}, + "current": {}, + "context": "personal", + "visible": {}, + } + +def write_data(): + global data + print(json.dumps(data), flush=True) + +async def get_workspace_data(): + request = b"[[BATCH]] j/workspaces; j/monitors" + reader = None + writer = None + data = None + try: + reader, writer = await asyncio.open_unix_connection(socket1) + writer.write(request) + await writer.drain() + data = await reader.read() + finally: + if writer is not None: + writer.close() + if data is None: + return; + + workspace_data = None + monitor_data = None + try: + workspace_data, monitor_data = data.decode().split("}][{") + workspace_data += "}]" + monitor_data = "[{" + monitor_data + except ValueError: + print("Error unpacking response", file=sys.stderr) + return + + return [json.loads(workspace_data), json.loads(monitor_data)] + +async def update_workspaces(): + global workspace_tree, workspace_list, data + + workspaces, monitors = await get_workspace_data() + + ws_hypr_dict = { ws["name"]: ws for ws in workspaces } + monitor_dict = { monitor["name"]: monitor for monitor in monitors } + + touched = [] + + for index, ws_data in ws_hypr_dict.items(): + + # Update data for all active workspaces + ws_state = workspace_list.get(index) + if ws_state is None: + # If the workspace isn't configured, note it and handle gracefully + print(f"Warning: found unconfigured workspace {index}", file=sys.stderr, flush=True) + if monitor_dict[ws_data['monitor']]['activeWorkspace']['name'] == ws_data['name']: + data['current'] = { + 'index': index, + 'name': 'undefined', + 'exec': 'alacritty', + 'active': True, + 'visible': True, + 'focused': True, + 'alerted': False + } + else: + # Otherwise, 'touch' it and update status data + touched.append(index) + ws_state.update_state(ws_data, monitor_dict) + if ws_state.focused: + data['current'] = ws_state.dictify() + if ws_state.visible: + data['visible'][ws_state.group] = ws_state.dictify() + + inactives = [ + v for k, v in workspace_list.items() if v.active and k not in touched + ] + + for workspace in inactives: + workspace.deactivate() + + # Create an 'unknown' workspace object for each group that doesn't yet have a listed visible workspace + for group in workspace_tree[data['context'] or 'personal'].keys(): + if group not in data['visible']: + data['visible'][group] = { + 'index': 'U', + 'name': 'undefined', + 'exec': 'alacritty', + 'active': True, + 'visible': True, + 'focused': False, + 'alerted': False + } + data['ws'] = Workspace.full_dictify(workspace_tree) + write_data() + +async def main(): + reader, writer = await asyncio.open_unix_connection(socket2) + + await update_workspaces() + + while True: + event = (await reader.readline()).decode() + eventType, eventData = event.split(">>") + if eventType in listened_events: + await update_workspaces() + +asyncio.run(main()) diff --git a/.config/eww##hostname.normandy/modules/workspaces/sway.py b/.config/eww##hostname.normandy/modules/workspaces/sway.py new file mode 100755 index 0000000..e1c426e --- /dev/null +++ b/.config/eww##hostname.normandy/modules/workspaces/sway.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 + +import asyncio +import json +import os +import sys +from i3ipc.aio import Connection +from i3ipc import Event +from typing import List, Dict, Any, Union, Tuple + +WorkspaceTree = Dict[str, Dict[str, List['Workspace']]] +WorkspaceList = Dict[str, 'Workspace'] + +class Workspace: + + index: str = None + name: str = None + exec_cmd: str = None + group: str = None + + active: bool = False + visible: bool = False + focused: bool = False + alerted: bool = False + + def __init__(self, dictionary: Dict[str, Union[str, int]], group: str): + self.index = str(dictionary.get('index')) + self.name = dictionary.get('name') + self.exec_cmd = dictionary.get('exec') + self.group = group + + self.active = dictionary.get('active', False) + self.visible = dictionary.get('visible', False) + self.focused = dictionary.get('focused', False) + self.alerted = dictionary.get('alerted', False) + + def update_state(self, state_update: Any): + self.active = True + self.visible = state_update.visible + self.focused = state_update.focused + self.alerted = state_update.urgent + + def deactivate(self): + self.active = False + self.visible = False + self.focused = False + self.alerted = False + + @classmethod + def parse_file(cls: type, filename: str) -> Tuple[WorkspaceTree, WorkspaceList]: + + result = {} + + initial: dict = None + with open(filename, 'r') as file: + initial = json.load(file) + + workspaces: WorkspaceList = {} + + def find_workspace(workspace: Dict[str, Union[str, int]], group: str) -> 'Workspace': + index = str(workspace.get('index')) + if index in workspaces: + return workspaces[index] + else: + workspaces[index] = cls(workspace, group) + return workspaces[index] + + result = { + context: { + group: [ + find_workspace(workspace, group) for workspace in workspaces + ] for group, workspaces in groups.items() + } for context, groups in initial.items() + } + + return result, workspaces + + @staticmethod + def full_dictify(tree: WorkspaceTree): + return { + context: { + group: [ + workspace.dictify() for workspace in workspaces + ] for group, workspaces in groups.items() + } for context, groups in tree.items() + } + + def dictify(self): + + return { + 'index': self.index, + 'name': self.name, + 'exec': self.exec_cmd, + 'active': self.active, + 'visible': self.visible, + 'focused': self.focused, + 'alerted': self.alerted + } + +workspace_tree, workspace_list = Workspace.parse_file(f"{os.environ['HOME']}/.config/sway/workspaces.json") + +data = { + "ws": {}, + "mode": "default", + "current": {}, + "context": "personal", + "visible": {}, +} + +def write_data(): + global data + print(json.dumps(data), flush=True) + +async def workspace_event(self: Connection, _): + global workspace_tree, workspace_list, data + + ws_sway_dict = { ws.name: ws for ws in await self.get_workspaces()} + + touched = [] + + for index, ws_data in ws_sway_dict.items(): + + # Update data for all active workspaces + ws_state = workspace_list.get(index) + if ws_state is None: + # If the workspace isn't configured, note it and handle gracefully + print(f"Warning: found unconfigured workspace {index}", file=sys.stderr, flush=True) + if ws_data.focused: + data['current'] = { + 'index': index, + 'name': 'undefined', + 'exec': 'alacritty', + 'active': True, + 'visible': True, + 'focused': True, + 'alerted': False + } + else: + # Otherwise, 'touch' it and update the status data + touched.append(index) + ws_state.update_state(ws_data) + if ws_state.focused: + data['current'] = ws_state.dictify() + if ws_state.visible: + data['visible'][ws_state.group] = ws_state.dictify() + + # 'Deactivate' any untouched workspaces + inactives = [ + v for k, v in workspace_list.items() if v.active and k not in touched + ] + + for workspace in inactives: + workspace.deactivate() + + # Create an 'unknown' workspace object for each group that doesn't yet have a listed visible workspace + for group in workspace_tree[data['context'] or 'personal'].keys(): + if group not in data['visible']: + data['visible'][group] = { + 'index': 'U', + 'name': 'undefined', + 'exec': 'alacritty', + 'active': True, + 'visible': True, + 'focused': False, + 'alerted': False + } + + data['ws'] = Workspace.full_dictify(workspace_tree) + write_data() + +async def mode_event(self: Connection, event): + global data + data['mode'] = event.change + write_data() + +async def main(): + sway = await Connection(auto_reconnect = True).connect() + + sway.on(Event.WORKSPACE, workspace_event) + sway.on(Event.MODE, mode_event) + await workspace_event(sway, None) + await sway.main() + +asyncio.run(main()) diff --git a/.config/eww##hostname.normandy/modules/workspaces/workspaces.yuck b/.config/eww##hostname.normandy/modules/workspaces/workspaces.yuck new file mode 100644 index 0000000..780cc93 --- /dev/null +++ b/.config/eww##hostname.normandy/modules/workspaces/workspaces.yuck @@ -0,0 +1,75 @@ +(deflisten sway--data :initial '{mode: "default"}' + `~/.config/eww/modules/workspaces/sway.py`) + +(defvar hypr--data '{}') + +(defwidget sway--workspace [ws] + (button :onclick "swaymsg workspace ${ws['index']}" + (circular-progress + :class 'indicator-circle sway--ws ${ws.active ? "sway--active" : ""} ${ws.visible ? "sway--visible" : ""} ${ws.focused ? "sway--focused" : ""} ${ws.alerted ? "sway--alerted" : ""}' + :value 100 + :start-at 0 + :clockwise true + :width 16 + :thickness 1 + (box :class 'fill' :visible {ws.visible})))) + +(defwidget hypr--workspace [ws] + (button :onclick "hyprmsg dispatch workspace ${ws['index']}" + (circular-progress + :class 'indicator-circle sway--ws ${ws.active ? "sway--active" : ""} ${ws.visible ? "sway--visible" : ""} ${ws.focused ? "sway--focused": ""}' + :value 100 + :start-at 0 + :clockwise true + :width 15 + :thickness 1 + (box :class 'fill' :visible {ws.focused})))) + +(defwidget sway-mode [] + (box :orientation "v" + :halign "center" + :space-evenly false + :spacing 0 + :class "module text" + :visible {hypr--data.mode != "default"} + (label :class "special" + :text "${hypr--data.mode}") + "sway mode")) + +(defwidget sway-workspace [group] + (box :orientation "v" + :halign "center" + :valign "center" + :space-evenly false + :spacing 0 + :class 'module text nebula medium' + (label :class '${sway--data.visible[group].focused ? "special" : "offline"}' + :text '${sway--data.visible[group].name}'))) + +(defwidget hypr-workspace [group] + (box :orientation "v" + :halign "center" + :space-evenly false + :spacing 0 + :class 'module text' + (label :class '${hypr--data.visible[group].focused ? "special" : "offline"}' + :text '${hypr--data.visible[group].name}') + (label :text "current workspace"))) + +(defwidget sway-workspaces [group] + (box :orientation "h" + :halign "start" + :space-evenly false + :spacing 5 + :class "sway--root" + (for workspace in {sway--data.ws[sway--data.context ?: "personal"][group]} + (sway--workspace :ws workspace)))) + +(defwidget hypr-workspaces [group] + (box :orientation "h" + :halign "start" + :space-evenly false + :spacing 5 + :class "sway--root" + (for workspace in {hypr--data.ws[hypr--data.context ?: "personal"][group]} + (hypr--workspace :ws workspace)))) diff --git a/.config/sway/config b/.config/sway/config index 02c7b87..c96b15d 100644 --- a/.config/sway/config +++ b/.config/sway/config @@ -1,19 +1,249 @@ -### ### -# Tycho Station Sway Config # -### ### +#### #### +## Triskelion sway Config File ## +#### #### -# This config file is modular. Set variables here, then use them in -# drop-in config files located in ~/.config/sway/config.d/ +### ### +# Display settings # +### ### + +# This section controls how connected displays are treated + +set $leftdisplay HDMI-A-1 +set $rightdisplay DP-2 + +output { + $leftdisplay pos 0 0 mode --custom 1920x1080@75Hz + $rightdisplay pos 1920 0 mode --custom 1920x1080@75Hz + * bg "#1e1e1e" solid_color +} + +input '*' xkb_file ~/.config/sway/keymap.xkb + +### ### +# Window Manager settings # +### ### + +# This section controls how the window manager functions. It does +# not include keybinds that are not directly related to the window +# manager's window management functions. set $mod Mod4 set $alt Mod1 -set $colors.primary '#815986' -set $colors.background '#2d272f' +# Set font +font pango:Source Code Pro 8 -### ### -# HERE BE DRAGONS # -### ### +# Reload configuration +bindsym $mod+Shift+r reload -# Do not edit anything below these comments. You have been warned. -include ~/.config/sway/config.d/*.conf +# Exit +bindsym $mod+Shift+e exec i3-logout + +### ### +# Window Management Settings # +### ### + +# This section controls window behavior and management keybinds. +# It also includes the resize mode. + +# Set monitor vars +set $monitor_1 $leftdisplay +set $monitor_2 $rightdisplay + +# Set keybinds to switch workspaces +bindsym $mod+1 exec swaymsg workspace $(i3-sensible-workspaces 1) +bindsym $mod+2 exec swaymsg workspace $(i3-sensible-workspaces 2) +bindsym $mod+3 exec swaymsg workspace $(i3-sensible-workspaces 3) +bindsym $mod+4 exec swaymsg workspace $(i3-sensible-workspaces 4) +bindsym $mod+5 exec swaymsg workspace $(i3-sensible-workspaces 5) +bindsym $mod+6 exec swaymsg workspace $(i3-sensible-workspaces 6) +bindsym $mod+7 exec swaymsg workspace $(i3-sensible-workspaces 7) +bindsym $mod+8 exec swaymsg workspace $(i3-sensible-workspaces 8) +bindsym $mod+9 exec swaymsg workspace $(i3-sensible-workspaces 9) +bindsym $mod+0 exec swaymsg workspace $(i3-sensible-workspaces 10) + +# Set keybinds to move windows between workspaces +bindsym $mod+Shift+1 exec i3-move-container 1 +bindsym $mod+Shift+2 exec i3-move-container 2 +bindsym $mod+Shift+3 exec i3-move-container 3 +bindsym $mod+Shift+4 exec i3-move-container 4 +bindsym $mod+Shift+5 exec i3-move-container 5 +bindsym $mod+Shift+6 exec i3-move-container 6 +bindsym $mod+Shift+7 exec i3-move-container 7 +bindsym $mod+Shift+8 exec i3-move-container 8 +bindsym $mod+Shift+9 exec i3-move-container 9 +bindsym $mod+Shift+0 exec i3-move-container 10 + +# Bind workspaces to monitors +workspace 1 output $monitor_1 +workspace 2 output $monitor_1 +workspace 3 output $monitor_1 +workspace 4 output $monitor_1 +workspace 5 output $monitor_1 +workspace 6 output $monitor_1 +workspace 7 output $monitor_1 +workspace 8 output $monitor_1 +workspace 9 output $monitor_1 +workspace 10 output $monitor_1 + +workspace 11 output $monitor_2 +workspace 12 output $monitor_2 +workspace 13 output $monitor_2 +workspace 14 output $monitor_2 +workspace 15 output $monitor_2 +workspace 16 output $monitor_2 +workspace 17 output $monitor_2 +workspace 18 output $monitor_2 +workspace 19 output $monitor_2 +workspace 20 output $monitor_2 + +# Allow dragging of floating windows whilst holding $mod +floating_modifier $mod + +# Resize mode +mode "resize" { + bindsym Left resize shrink width 10 px or 1 ppt + bindsym Down resize grow height 10 px or 1 ppt + bindsym Up resize shrink height 10 px or 1 ppt + bindsym f resize grow width 10 px or 1 ppt + + # Exit resize mode + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym $mod+r mode "default" +} + +bindsym $mod+r mode "resize" + +# Move windows +bindsym $mod+Shift+b move left +bindsym $mod+Shift+f move right +bindsym $mod+Shift+p move up +bindsym $mod+Shift+n move down + +bindsym $mod+Shift+Left move left +bindsym $mod+Shift+Right move right +bindsym $mod+Shift+Up move up +bindsym $mod+Shift+Down move down + +# Shift focus +bindsym $mod+b focus left +bindsym $mod+f focus right +bindsym $mod+p focus up +bindsym $mod+n focus down +bindsym $mod+a focus parent + +# Splits +bindsym $mod+h split h +bindsym $mod+v split v + +# Fullscreen +bindsym $mod+$alt+f fullscreen toggle + +# Floating +bindsym $mod+Shift+space floating toggle +bindsym $mod+space focus mode_toggle + +# Close window +bindsym $mod+Shift+q kill + +# Change window arrangement +bindsym $mod+e layout toggle split +bindsym $mod+t layout tabbed +bindsym $mod+s layout stacking + +### ### +# Appearance # +### ### + +default_border pixel 1 + +# Colors +set $primary #815986 +set $background #2d272f +set $wallpaper #242933 + +client.focused $primary $background $primary $primary $primary +client.focused_inactive $primary $background $primary $background $background +client.unfocused $background $background $primary $background $background + +# Gaps +gaps inner 10 + +workspace 1 gaps left 210 +workspace 2 gaps left 210 +workspace 3 gaps left 210 +workspace 4 gaps left 210 +workspace 5 gaps left 210 +workspace 6 gaps left 210 +workspace 7 gaps left 210 +workspace 8 gaps left 210 +workspace 9 gaps left 210 +workspace 10 gaps left 210 + +# Themes + +set $gnome-schema org.gnome.desktop.interface + +exec_always gsettings set $gnome-schema { + gtk-theme 'Nordic-darker' + icon-theme 'ePapirus-Dark' + font-name 'Source Sans Pro:12' +} + +### ### +# Desktop Features # +### ### + +# This section includes basic desktop features, such as +# the ability to launch programs and functional media keys + +# Program launchers +bindsym { + # "Sensible" launcher -- auto-launch default program for workspace + $mod+Return exec i3-sensible-launcher + # Terminal launcher + $mod+Shift+Return exec alacritty + # Program menu launcher + $mod+d exec wofi --show drun +} + +# Media keys (function while locked, be careful with these) +bindsym --locked { + # Playback Control + XF86AudioPlay exec playerctl play-pause + XF86AudioNext exec playerctl next + XF86AudioPrev exec playerctl previous +} + +# Utility keybinds +bindsym { + # Screen locker + $mod+l exec loginctl lock-session + # Dismiss notification + $mod+Ctrl+Space exec dunstctl close +} + +# Start daemons +exec_always /usr/bin/systemctl --user import-environment DISPLAY WAYLAND_DISPLAY SWAYSOCK +exec hash dbus-update-activation-environment 2>/dev/null && \ + dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK SSH_AUTH_SOCK GPG_TTY +exec_always /usr/bin/systemctl --user start sway-session.target + +exec noisetorch -i -t 95 + +# Start status bars +exec eww open-many leftbar rightbar sidebar + +# Keyboard settings +input * { + xkb_numlock enabled + xkb_options compose:menu +} + +# Key overrides for G-keys +bindsym XF86Tools exec wtype -k F13 +bindsym XF86Launch5 exec wtype -k F14 +bindsym XF86Launch6 exec wtype -k F15 +bindsym XF86Launch7 exec wtype -k F16 +bindsym XF86Launch8 exec wtype -k F17 diff --git a/.config/sway/config.d/10-display-arrangement.conf##hostname.normandy b/.config/sway/config.d/10-display-arrangement.conf##hostname.normandy new file mode 100644 index 0000000..3d6ec91 --- /dev/null +++ b/.config/sway/config.d/10-display-arrangement.conf##hostname.normandy @@ -0,0 +1,9 @@ +# Display Arrangement # + +set $leftdisplay 'HDMI-A-1' +set $centerdisplay 'DP-2' + +output { + $leftdisplay pos 0 0 mode 1920x1200 + $centerdisplay pos 1920 0 mode 1920x1200 +} diff --git a/.config/sway/config.d/10-display-arrangement.conf##hostname.tycho b/.config/sway/config.d/10-display-arrangement.conf##hostname.tycho new file mode 100644 index 0000000..d520e2d --- /dev/null +++ b/.config/sway/config.d/10-display-arrangement.conf##hostname.tycho @@ -0,0 +1,7 @@ +set $leftdisplay 'Hewlett Packard HP Z24n CN47090537' +set $centerdisplay 'Hewlett Packard HP Z24n CN4709053L' + +output { + $leftdisplay pos 0 0 mode 1920x1200 + $centerdisplay pos 1920 0 mode 1920x1200 +} \ No newline at end of file diff --git a/.config/sway/config.d/10-display-settings.conf b/.config/sway/config.d/11-display-settings.conf similarity index 92% rename from .config/sway/config.d/10-display-settings.conf rename to .config/sway/config.d/11-display-settings.conf index 3c0f74a..15cfe90 100644 --- a/.config/sway/config.d/10-display-settings.conf +++ b/.config/sway/config.d/11-display-settings.conf @@ -2,15 +2,7 @@ # Display Settings # ### ### -set $leftdisplay 'Hewlett Packard HP Z24n CN47090537' -set $centerdisplay 'Hewlett Packard HP Z24n CN4709053L' - -output { - $leftdisplay pos 0 0 mode 1920x1200 - $centerdisplay pos 1920 0 mode 1920x1200 - # * bg '/home/ezri/2023-09-28T14:06:22,599070355-06:00.png' center '#1e1e1e' - * bg '#1e1e1e' solid_color -} +output * bg '#1e1e1e' solid_color mode "output-switching" { bindsym 1 mode output-1 diff --git a/.config/sway/swayidle b/.config/sway/swayidle index 715a68c..0c17b86 100644 --- a/.config/sway/swayidle +++ b/.config/sway/swayidle @@ -1,9 +1,9 @@ # -*-conf-*- -timeout 30 'if pgrep swaylock; then swaymsg "output * power off"; pkill -SIGSTOP electron; pkill -SIGSTOP slack; fi' resume 'if pgrep swaylock; then swaymsg "output * power on"; pkill -SIGCONT electron; pkill -SIGCONT slack; fi' -timeout 300 'if pgrep swaylock; then lock-keyring; fi' +timeout 10 'pgrep swaylock &> /dev/null && swaymsg "output * power off"' resume 'pgrep swaylock &> /dev/null && swaymsg "output * power on"' +timeout 15 'pgrep swaylock &> /dev/null && pkill -u 1000 -SIGSTOP electron' resume 'pgrep swaylock &> /dev/null && pkill -u 1000 -SIGCONT electron' lock ~/.local/bin/screenlock before-sleep 'loginctl lock-session' -unlock 'pkill -USR1 swaylock' -idlehint 300 +unlock 'pkill -9 swaylock' +idlehint 600 diff --git a/.config/sway/workspaces.json##hostname.normandy b/.config/sway/workspaces.json##hostname.normandy new file mode 100644 index 0000000..83f78f6 --- /dev/null +++ b/.config/sway/workspaces.json##hostname.normandy @@ -0,0 +1,109 @@ +{ + "personal": { + "left": [ + { + "index": 1, + "name": "terminal", + "exec": "alacritty" + }, + { + "index": 2, + "name": "code", + "exec": "alacritty" + }, + { + "index": 3, + "name": "web", + "exec": "firefox --new-window" + }, + { + "index": 4, + "name": "project", + "exec": "firefox --new-window" + }, + { + "index": 5, + "name": "images", + "exec": "dolphin" + }, + { + "index": 6, + "name": "documents", + "exec": "alacritty" + }, + { + "index": 7, + "name": "music", + "exec": "spotify" + }, + { + "index": 8, + "name": "discord", + "exec": "discord", + "systemd": false + }, + { + "index": 9, + "name": "email", + "exec": "alacritty" + }, + { + "index": 10, + "name": "config", + "exec": "pavucontrol" + } + ], + "right": [ + { + "index": 11, + "name": "terminal", + "exec": "alacritty" + }, + { + "index": 12, + "name": "code", + "exec": "alacritty" + }, + { + "index": 13, + "name": "web", + "exec": "firefox --new-window" + }, + { + "index": 14, + "name": "steam", + "exec": "steam" + }, + { + "index": 15, + "name": "minecraft", + "exec": "polymc" + }, + { + "index": 16, + "name": "virtual machines", + "exec": "virt-manager" + }, + { + "index": 17, + "name": "video", + "exec": "vlc" + }, + { + "index": 18, + "name": "slack", + "exec": "slack" + }, + { + "index": 19, + "name": "zoom", + "exec": "zoom" + }, + { + "index": 20, + "name": "config", + "exec": "pavucontrol" + } + ] + } +} diff --git a/.config/sway/workspaces.json b/.config/sway/workspaces.json##hostname.tycho similarity index 100% rename from .config/sway/workspaces.json rename to .config/sway/workspaces.json##hostname.tycho diff --git a/.local/bin/i3-sensible-workspaces b/.local/bin/i3-sensible-workspaces index 147191f..8355e1e 100755 --- a/.local/bin/i3-sensible-workspaces +++ b/.local/bin/i3-sensible-workspaces @@ -1,9 +1,16 @@ #!/usr/bin/env zsh -output_sets=("DP-1" "DP-2") +output_sets=() -current_output=$(swaymsg -t get_workspaces | jq '.[] | select(.focused==true).output' | cut -d'"' -f2) -current_workspace=$(swaymsg -t get_workspaces | jq '.[] | select(.focused==true).name' | cut -d'"' -f2) +# Order outputs by x position (left to right) +for output in $(swaymsg -t get_outputs | jq '[ (.[] | select(.active) | pick(.name, .rect)) ] | sort_by(.rect.x) | .[] | .name' -r); do + output_sets+=$output +done + +workspaces=$(swaymsg -t get_workspaces) + +current_output=$(echo $workspaces | jq '.[] | select(.focused).output' -r) +current_workspace=$(echo $workspaces | jq '.[] | select(.focused).name' -r) function get_ws { ws_set=$1 diff --git a/.local/lib/zsh/zsh-autosuggestions b/.local/lib/zsh/zsh-autosuggestions index c3d4e57..a411ef3 160000 --- a/.local/lib/zsh/zsh-autosuggestions +++ b/.local/lib/zsh/zsh-autosuggestions @@ -1 +1 @@ -Subproject commit c3d4e576c9c86eac62884bd47c01f6faed043fc5 +Subproject commit a411ef3e0992d4839f0732ebeb9823024afaaaa8 diff --git a/.local/lib/zsh/zsh-history-substring-search b/.local/lib/zsh/zsh-history-substring-search index 8dd05bf..4abed97 160000 --- a/.local/lib/zsh/zsh-history-substring-search +++ b/.local/lib/zsh/zsh-history-substring-search @@ -1 +1 @@ -Subproject commit 8dd05bfcc12b0cd1ee9ea64be725b3d9f713cf64 +Subproject commit 4abed97b6e67eb5590b39bcd59080aa23192f25d diff --git a/.local/lib/zsh/zsh-syntax-highlighting b/.local/lib/zsh/zsh-syntax-highlighting index e0165ea..c7caf57 160000 --- a/.local/lib/zsh/zsh-syntax-highlighting +++ b/.local/lib/zsh/zsh-syntax-highlighting @@ -1 +1 @@ -Subproject commit e0165eaa730dd0fa321a6a6de74f092fe87630b0 +Subproject commit c7caf57ca805abd54f11f756fda6395dd4187f8a