Added rocinante eww config
This commit is contained in:
parent
bbb65f61c3
commit
e5cc36a647
1
.config/eww##hostname.rocinante/.python-version
Normal file
1
.config/eww##hostname.rocinante/.python-version
Normal file
@ -0,0 +1 @@
|
||||
eww-modules
|
||||
13
.config/eww##hostname.rocinante/colors.scss
Normal file
13
.config/eww##hostname.rocinante/colors.scss
Normal 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;
|
||||
90
.config/eww##hostname.rocinante/eww.scss
Normal file
90
.config/eww##hostname.rocinante/eww.scss
Normal file
@ -0,0 +1,90 @@
|
||||
@import 'colors';
|
||||
|
||||
* {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
.reservepower.reservepower {
|
||||
color: $red;
|
||||
}
|
||||
|
||||
.root {
|
||||
color: $foreground;
|
||||
font-family: "Source Code Pro";
|
||||
font-size: 9pt;
|
||||
background-color: rgba(0,0,0,0);
|
||||
// Probably needs to change
|
||||
&.bar {
|
||||
margin: 10px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
&.side {
|
||||
margin: 10px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-family: "Font Awesome 5 Free Solid";
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: $red;
|
||||
}
|
||||
|
||||
.offline {
|
||||
color: $bg1;
|
||||
}
|
||||
|
||||
.special {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: $green;
|
||||
}
|
||||
|
||||
.big {
|
||||
font-size: 1.8rem;
|
||||
font-family: "Nebula";
|
||||
}
|
||||
|
||||
// sway module
|
||||
|
||||
.sway--ws {
|
||||
|
||||
.fill {
|
||||
background-color: $foreground;
|
||||
}
|
||||
color: $bg1;
|
||||
|
||||
&.sway--active {
|
||||
color: $foreground;
|
||||
}
|
||||
|
||||
&.sway--visible {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
&.sway--focused {
|
||||
}
|
||||
}
|
||||
|
||||
.reservepower .sway--ws.sway--active {
|
||||
color: $red;
|
||||
}
|
||||
|
||||
.reservepower .sway--ws .fill {
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
// clock module
|
||||
|
||||
.clock--time {
|
||||
font-size: 20pt;
|
||||
}
|
||||
|
||||
.clock--date {
|
||||
font-size: 9pt;
|
||||
}
|
||||
183
.config/eww##hostname.rocinante/eww.yuck
Normal file
183
.config/eww##hostname.rocinante/eww.yuck
Normal file
@ -0,0 +1,183 @@
|
||||
;; 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")
|
||||
|
||||
(defvar power-state "normal")
|
||||
|
||||
;; Windows
|
||||
(defwidget leftbar--left []
|
||||
(box :orientation "h"
|
||||
:halign "start"
|
||||
:space-evenly false
|
||||
:spacing 10
|
||||
:class "leftbox"
|
||||
(vpn-network)
|
||||
(network)))
|
||||
|
||||
(defwidget leftbar--center []
|
||||
(box :orientation "h"
|
||||
:halign "center"
|
||||
:space-evenly false
|
||||
:spacing 0
|
||||
:class "centerbox"
|
||||
(system-name)))
|
||||
|
||||
(defwidget leftbar--right []
|
||||
(box :orientation "h"
|
||||
:halign "end"
|
||||
:space-evenly false
|
||||
:spacing 10
|
||||
: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"
|
||||
(system-name)))
|
||||
|
||||
(defwidget rightbar--right []
|
||||
(box :orientation "h"
|
||||
:halign "end"
|
||||
:space-evenly false
|
||||
:spacing 20
|
||||
:class "leftbox"
|
||||
(system-battery :battery "BAT1")
|
||||
(audio)
|
||||
(clock)))
|
||||
|
||||
(defwidget builtinbar--left []
|
||||
(box :orientation "h"
|
||||
:halign "start"
|
||||
:space-evenly false
|
||||
:spacing 20
|
||||
:class "leftbox"
|
||||
(sway-workspaces :group "builtin")
|
||||
(sway-workspace :group "builtin")))
|
||||
|
||||
(defwidget builtinbar--center []
|
||||
(box :orientation "h"
|
||||
:halign "center"
|
||||
:space-evenly false
|
||||
:spacing 0
|
||||
:class "centerbox"
|
||||
(system-name)))
|
||||
|
||||
(defwidget builtinbar--right []
|
||||
(box :orientation "h"
|
||||
:halign "end"
|
||||
:space-evenly false
|
||||
:spacing 20
|
||||
:class "rightbox"
|
||||
(vpn-network)
|
||||
(network)
|
||||
(system-battery :battery "BAT1")
|
||||
(audio)
|
||||
(clock)))
|
||||
|
||||
(defwindow builtinbar
|
||||
:monitor 0
|
||||
:geometry (geometry :width "100%"
|
||||
:height "36px"
|
||||
:anchor "top center")
|
||||
:exclusive true
|
||||
:focusable false
|
||||
:stacking "fg"
|
||||
(centerbox :orientation "h" :class "bar root ${power-state == "critical" ? 'reservepower' : ''}"
|
||||
(builtinbar--left)
|
||||
(builtinbar--center)
|
||||
(builtinbar--right)))
|
||||
|
||||
(defwindow side
|
||||
:monitor 0
|
||||
:geometry (geometry :width "36px"
|
||||
:height "100%"
|
||||
:anchor "center left")
|
||||
:exclusive true
|
||||
:focusable false
|
||||
:stacking "fg"
|
||||
(centerbox :orientation "v" :class "bar root" :width 36
|
||||
(sway-workspaces-v :group "main")
|
||||
""
|
||||
""))
|
||||
|
||||
(defwidget sidebar []
|
||||
(box :orientation "v"
|
||||
:valign "start"
|
||||
:space-evenly false
|
||||
:spacing 0
|
||||
:class "bar root"
|
||||
(clock)))
|
||||
|
||||
(defwindow leftbar
|
||||
:monitor 0
|
||||
: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 1
|
||||
:geometry (geometry :width "100%"
|
||||
:height "55px"
|
||||
: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))
|
||||
11
.config/eww##hostname.rocinante/modules/angles/angles.yuck
Normal file
11
.config/eww##hostname.rocinante/modules/angles/angles.yuck
Normal 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))
|
||||
|
||||
|
||||
BIN
.config/eww##hostname.rocinante/modules/angles/left.png
Normal file
BIN
.config/eww##hostname.rocinante/modules/angles/left.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
.config/eww##hostname.rocinante/modules/angles/right.png
Normal file
BIN
.config/eww##hostname.rocinante/modules/angles/right.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
10
.config/eww##hostname.rocinante/modules/clock/clock.yuck
Normal file
10
.config/eww##hostname.rocinante/modules/clock/clock.yuck
Normal file
@ -0,0 +1,10 @@
|
||||
(defpoll clock--data :interval "500ms"
|
||||
`date +'{"hour": "%H", "minute": "%M", "second": "%S", "year": "%Y", "day": "%d", "month": "%m", "dow": "%A", "month_name": "%B"}'`)
|
||||
|
||||
(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']}")))
|
||||
81
.config/eww##hostname.rocinante/modules/network/network.py
Executable file
81
.config/eww##hostname.rocinante/modules/network/network.py
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
from time import sleep
|
||||
|
||||
TRUSTED_NETWORKS = ['gayer people']
|
||||
|
||||
def wifi():
|
||||
ssid_cmd = subprocess.run(['iwgetid', '-r'], capture_output = True)
|
||||
if ssid_cmd.returncode != 0:
|
||||
return {"connected": False, "ssid": None}
|
||||
ssid = ssid_cmd.stdout.decode('utf-8').strip()
|
||||
ssid = ssid if len(ssid) <= 15 else f"{ssid[:14]}…"
|
||||
return {"connected": True, "ssid": ssid}
|
||||
|
||||
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,
|
||||
"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
|
||||
|
||||
def ping(host: str):
|
||||
ping_cmd = subprocess.Popen(['/usr/bin/ping', '-c1', '-W2', host], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
return ping_cmd
|
||||
|
||||
counter = 0
|
||||
ezrinet_last = False
|
||||
internet_last = False
|
||||
ezrinet_ping = None
|
||||
internet_ping = None
|
||||
|
||||
while True:
|
||||
if counter == 0:
|
||||
ezrinet_ping = ping('10.242.3.1')
|
||||
internet_ping = ping('1.1.1.1')
|
||||
if ezrinet_ping.poll() is not None:
|
||||
ezrinet_last = ezrinet_ping.returncode == 0
|
||||
if internet_ping.poll() is not None:
|
||||
internet_last = internet_ping.returncode == 0
|
||||
wifi_data = wifi()
|
||||
result = {
|
||||
"wifi": wifi_data,
|
||||
"network": {
|
||||
"wlan0": netdev("wlan0"),
|
||||
"ezrinet": netdev("ezrinet"),
|
||||
"wg-mullvad": netdev("mullvad"),
|
||||
},
|
||||
"connection": {
|
||||
"ezrinet": ezrinet_last,
|
||||
"internet": internet_last
|
||||
},
|
||||
"trusted": trusted(wifi_data.get("ssid", None))
|
||||
}
|
||||
print(json.dumps(result), flush=True)
|
||||
counter += 1
|
||||
# Send pings every 10 seconds
|
||||
counter %= 3
|
||||
sleep(1)
|
||||
76
.config/eww##hostname.rocinante/modules/network/network.yuck
Normal file
76
.config/eww##hostname.rocinante/modules/network/network.yuck
Normal file
@ -0,0 +1,76 @@
|
||||
(deflisten network--data
|
||||
`~/.config/eww/modules/network/network.py`)
|
||||
|
||||
(defwidget network--wlan [device]
|
||||
(box :orientation "h"
|
||||
:halign "start"
|
||||
:space-evenly false
|
||||
:spacing 10
|
||||
(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['wifi']['ssid']}")
|
||||
(label :visible {!network--data.connection.internet && network--data.network[device].online}
|
||||
:class "highlight"
|
||||
:text "- no internet"
|
||||
)))
|
||||
|
||||
(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.connection.internet && (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 vpn-network []
|
||||
(box :orientation "v"
|
||||
:halign "start"
|
||||
:space-evenly false
|
||||
:spacing 0
|
||||
(label :class "highlight"
|
||||
:text "offline"
|
||||
:visible {!network--data.connection.ezrinet})
|
||||
(label :class "green"
|
||||
:text "connected"
|
||||
:visible {network--data.connection.ezrinet})
|
||||
"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--wlan :device "wlan0")
|
||||
(network--proxy-vpn :device "wg-mullvad" :required {!network--data.trusted}))
|
||||
"communications"))
|
||||
|
||||
56
.config/eww##hostname.rocinante/modules/system/system.py
Executable file
56
.config/eww##hostname.rocinante/modules/system/system.py
Executable file
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from time import sleep
|
||||
import json
|
||||
import psutil
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
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": per_core,
|
||||
"core_count": len(per_core)
|
||||
}
|
||||
|
||||
def sensor(chip: str):
|
||||
return { temp.label: temp.current for temp in psutil.sensors_temperatures()[chip] }
|
||||
|
||||
def memory():
|
||||
mem = psutil.virtual_memory()
|
||||
return {
|
||||
'used': mem.used,
|
||||
'available': mem.available,
|
||||
'total': mem.total,
|
||||
'percent': mem.percent
|
||||
}
|
||||
|
||||
def swap():
|
||||
mem = psutil.swap_memory()
|
||||
return {
|
||||
'used': mem.used,
|
||||
'total': mem.total,
|
||||
'percent': mem.percent
|
||||
}
|
||||
|
||||
def reboot():
|
||||
running = os.uname().release
|
||||
file_proc = subprocess.run("file /boot/vmlinuz-linux | cut -d',' -f2 | cut -d' ' -f3", shell=True, capture_output=True)
|
||||
installed = file_proc.stdout.strip().decode('utf-8')
|
||||
return running != installed
|
||||
|
||||
sensor_list = ['coretemp']
|
||||
|
||||
while True:
|
||||
result = {
|
||||
"cpu": cpu(),
|
||||
"sensors": { chip: sensor(chip) for chip in sensor_list },
|
||||
"memory": memory(),
|
||||
"swap": swap(),
|
||||
"reboot": reboot()
|
||||
}
|
||||
print(json.dumps(result), flush=True)
|
||||
sleep(1)
|
||||
20
.config/eww##hostname.rocinante/modules/system/system.yuck
Normal file
20
.config/eww##hostname.rocinante/modules/system/system.yuck
Normal file
@ -0,0 +1,20 @@
|
||||
(defpoll system--hostname :interval "30s"
|
||||
`bash -c 'eval $(cat /etc/machine-info); echo $PRETTY_HOSTNAME'`)
|
||||
|
||||
(defvar system--battery-text "")
|
||||
|
||||
(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 <= 30 ? 'highlight' : 'special'}
|
||||
:text "${EWW_BATTERY[battery].capacity}%")
|
||||
(label :text "${EWW_BATTERY[battery].status == 'Charging' || EWW_BATTERY[battery].capacity == 100 ? 'external' : EWW_BATTERY[battery].capacity <= 15 ? 'emergency' : EWW_BATTERY[battery].capacity <= 30 ? 'reserve' : 'internal'} power")))
|
||||
|
||||
(defwidget system-name []
|
||||
(label :halign "center"
|
||||
:valign "center"
|
||||
:class "module text big"
|
||||
:text {system--hostname}))
|
||||
50
.config/eww##hostname.rocinante/modules/volume/volume.py
Executable file
50
.config/eww##hostname.rocinante/modules/volume/volume.py
Executable 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())
|
||||
28
.config/eww##hostname.rocinante/modules/volume/volume.yuck
Normal file
28
.config/eww##hostname.rocinante/modules/volume/volume.yuck
Normal 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"))
|
||||
213
.config/eww##hostname.rocinante/modules/workspaces/hyprland.py
Executable file
213
.config/eww##hostname.rocinante/modules/workspaces/hyprland.py
Executable 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())
|
||||
184
.config/eww##hostname.rocinante/modules/workspaces/sway.py
Executable file
184
.config/eww##hostname.rocinante/modules/workspaces/sway.py
Executable 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())
|
||||
@ -0,0 +1,89 @@
|
||||
(deflisten sway--data :initial '{mode: "default"}'
|
||||
`~/.config/eww/modules/workspaces/sway.py`)
|
||||
|
||||
(deflisten hypr--data :initial '{}'
|
||||
`~/.config/eww/modules/workspaces/hyprland.py`)
|
||||
|
||||
(defwidget sway--workspace [ws]
|
||||
(button :onclick "swaymsg workspace ${ws['index']}"
|
||||
(overlay :width 20
|
||||
:height 20
|
||||
(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 20
|
||||
:thickness 1
|
||||
(box :class 'fill' :visible {ws.focused}))
|
||||
)))
|
||||
|
||||
(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 20
|
||||
: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 {sway--data.mode != "default"}
|
||||
(label :class "special"
|
||||
:text "${hypr--data.mode}")
|
||||
"sway mode"))
|
||||
|
||||
(defwidget sway-workspace [group]
|
||||
(box :orientation "v"
|
||||
:halign "center"
|
||||
:space-evenly false
|
||||
:spacing 0
|
||||
:class 'module text'
|
||||
(label :class '${sway--data.visible[group].focused ? "special" : "offline"}'
|
||||
:text '${sway--data.visible[group].name}')
|
||||
(label :text "current workspace")))
|
||||
|
||||
(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 sway-workspaces-v [group]
|
||||
(box :orientation "v"
|
||||
:halign "center"
|
||||
:valign "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))))
|
||||
@ -0,0 +1,89 @@
|
||||
(deflisten sway--data :initial '{mode: "default"}'
|
||||
`~/.config/eww/modules/sway.py`)
|
||||
|
||||
(deflisten hypr--data :initial '{}'
|
||||
`~/.config/eww/modules/workspaces/hyprland.py`)
|
||||
|
||||
(defwidget sway--workspace [ws]
|
||||
(button :onclick "swaymsg workspace ${ws['index']}"
|
||||
(overlay :width 20
|
||||
:height 20
|
||||
(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 20
|
||||
:thickness 1
|
||||
(box :class 'fill' :visible {ws.focused}))
|
||||
)))
|
||||
|
||||
(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 20
|
||||
: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 {sway--data.mode != "default"}
|
||||
(label :class "special"
|
||||
:text "${hypr--data.mode}")
|
||||
"sway mode"))
|
||||
|
||||
(defwidget sway-workspace [group]
|
||||
(box :orientation "v"
|
||||
:halign "center"
|
||||
:space-evenly false
|
||||
:spacing 0
|
||||
:class 'module text'
|
||||
(label :class '${sway--data.visible[group].focused ? "special" : "offline"}'
|
||||
:text '${sway--data.visible[group].name}')
|
||||
(label :text "current workspace")))
|
||||
|
||||
(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 sway-workspaces-v [group]
|
||||
(box :orientation "v"
|
||||
:halign "center"
|
||||
:valign "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))))
|
||||
@ -1 +0,0 @@
|
||||
swayidle##class.desktop
|
||||
Loading…
x
Reference in New Issue
Block a user