Updated for normandy

This commit is contained in:
Ezri Brimhall 2024-03-06 18:13:34 -07:00
parent 59695984c5
commit 9801adfbc2
Signed by: ezri
GPG Key ID: 41520EAF66DF5A25
40 changed files with 2587 additions and 31 deletions

View File

@ -0,0 +1 @@
eww-modules

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

@ -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"]}%'))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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']}")))

View File

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

View File

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

View File

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

View File

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

View File

@ -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"]}')
))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@ -1 +1 @@
Subproject commit c3d4e576c9c86eac62884bd47c01f6faed043fc5
Subproject commit a411ef3e0992d4839f0732ebeb9823024afaaaa8

@ -1 +1 @@
Subproject commit 8dd05bfcc12b0cd1ee9ea64be725b3d9f713cf64
Subproject commit 4abed97b6e67eb5590b39bcd59080aa23192f25d

@ -1 +1 @@
Subproject commit e0165eaa730dd0fa321a6a6de74f092fe87630b0
Subproject commit c7caf57ca805abd54f11f756fda6395dd4187f8a