desktop building?

This commit is contained in:
mjallen18
2025-07-20 18:35:14 -05:00
parent 91be65bc39
commit 608a6ce9b8
51 changed files with 535 additions and 299 deletions

View File

@@ -0,0 +1,534 @@
{ config, lib, ... }:
with lib;
let
cfg = config.mjallen.programs.waybar;
nord = import ../../desktop/theme/nord.nix;
defaultOpacity = "opacity: 0.85;";
defaultBorderRadius = "border-radius: 1rem;";
defaultCenterOptions = ''
padding-top: 0.2rem;
padding-bottom: 0.2rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
margin: 3px 0;
'';
borderRight = ''
padding-top: 0.2rem;
padding-bottom: 0.2rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
margin: 3px 0;
border-radius: 0rem 1rem 1rem 0rem;
margin-right: 0.5rem;
'';
borderLeft = ''
padding-top: 0.2rem;
padding-bottom: 0.2rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
margin: 3px 0;
border-radius: 1rem 0rem 0rem 1rem;
margin-left: 0.5rem;
'';
in
{
imports = [
./options.nix
./scripts/hass.nix
./scripts/weather.nix
];
config = mkIf cfg.enable {
# https://github.com/Alexays/Waybar/wiki/Module:-Hyprland
# https://www.nerdfonts.com/cheat-sheet
programs.waybar = {
enable = true;
systemd.enable = true;
settings = {
mainBar = {
layer = cfg.layer;
position = "top";
mod = "dock";
exclusive = true;
passthrough = false;
gtk-layer-shell = true;
height = 0;
# Module Layout
modules-left = [ "hyprland/workspaces" ];
modules-center = [ "hyprland/window" ];
modules-right = cfg.modules-right;
# Module Definitions
# Left
"hyprland/workspaces" = {
disable-scroll = true;
all-outputs = true;
on-click = "activate";
persistent_workspaces = {
"*" = 1;
};
};
# Center
"hyprland/window" = {
separate-outputs = true;
format = { };
};
# Left
tray = {
icon-size = 16;
spacing = 10;
};
temperature = {
hwmon-path = "/sys/class/hwmon/hwmon4/temp1_input";
critical-threshold = 110;
format-critical = "{temperatureC}°C ";
format = "{temperatureC}°C {icon}";
format-icons = [
"" # fa-temperature-empty
"" # fa-temperature-quarter
"" # fa-temperature-half
"" # fa-temperature-three-quarters
"" # fa-temperature-full
];
tooltip-format = "CPU: {temperatureC}°C";
};
"temperature#gpu" = {
hwmon-path = "/sys/class/hwmon/hwmon0/temp1_input";
critical-threshold = 110;
format-critical = "{temperatureC}°C ";
format = "{temperatureC}°C {icon}";
format-icons = [
"" # fa-temperature-empty
"" # fa-temperature-quarter
"" # fa-temperature-half
"" # fa-temperature-three-quarters
"" # fa-temperature-full
];
on-click = "lact";
tooltip-format = "GPU: {temperatureC}°C";
};
"keyboard-state#capslock" = {
capslock = true;
format = "{icon}";
tooltip-format = "Caps Lock {state}";
format-icons = {
locked = "󰬶";
unlocked = "󰬵";
};
};
"keyboard-state#numlock" = {
numlock = true;
format = "{icon}";
tooltip-format = "Num Lock {state}";
format-icons = {
locked = "󰎠";
unlocked = "󱧓";
};
};
"wireplumber#sink" = {
format = "{icon} {volume}%";
tooltip = false;
format-muted = "{icon} Muted";
on-click = "wpctl set-mute @DEFAULT_SINK@ toggle";
on-click-right = "pavucontrol -t 1";
on-scroll-up = "wpctl set-volume @DEFAULT_SINK@ 5%+";
on-scroll-down = "wpctl set-volume @DEFAULT_SINK@ 5%-";
scroll-step = 5;
format-icons = {
headphone = "󰋋";
headphone-muted = "󰟎";
hands-free = "󰋋";
headset = "󰋋";
phone = "";
portable = "󰋋";
car = "";
default = [
""
""
""
""
];
};
};
"wireplumber#source" = {
node-type = "Audio/Source";
format = "";
format-muted = "";
tooltip = false;
# tooltip-format = "{source_desc} | {source_volume}%";
on-click = "wpctl set-mute @DEFAULT_SOURCE@ toggle";
on-click-right = "pavucontrol -t 2";
on-scroll-up = "wpctl set-volume @DEFAULT_SOURCE@ 5%+";
on-scroll-down = "wpctl set-volume @DEFAULT_SOURCE@ 5%-";
scroll-step = 5;
};
"bluetooth" = {
on-click = "overskride";
tooltip = true;
format = "{icon}";
tooltip-format = "{status}";
tooltip-format-disabled = "{status}";
tooltip-format-off = "{status}";
tooltip-format-on = "{status}";
tooltip-format-connected = "{status}";
tooltip-format-enumerate-connected = { };
format-icons = {
disabled = "󰂲";
off = "󰂲";
on = "󰂯";
connected = "󰂱";
};
};
network = {
interface = cfg.networkInterface;
on-click = "nm-connection-editor";
format = "{icon}";
tooltip-format = "{ifname} via {gwaddr} 󰊗";
tooltip-format-wifi = ''
{essid} ({signalStrength}%) {icon}
{bandwidthDownBits} {bandwidthUpBits}
'';
tooltip-format-ethernet = "{ifname} ";
tooltip-format-disconnected = "Disconnected";
max-length = 50;
format-icons = {
wifi = [
"󰤯"
"󰤟"
"󰤢"
"󰤥"
"󰤨"
];
ethernet = "󰈀";
linked = "󰤫";
disconnected = "󰤫";
};
};
idle_inhibitor = {
format = "{icon}";
format-icons = {
activated = "󰐂";
deactivated = "󱩜";
};
};
clock = {
format = "{:%I:%M %p}";
tooltip-format = "<tt><small>{calendar}</small></tt>";
calendar = {
mode = "month";
format = {
months = "<span color='${nord.frost.nord9}'><b>{}</b></span>";
days = "<span color='${nord.frost.nord10}'><b>{}</b></span>";
weekdays = "<span color='${nord.frost.nord8}'><b>{}</b></span>";
today = "<span color='${nord.aurora.nord14}'><b><u>{}</u></b></span>";
};
};
};
battery = {
# bat = "macsmc-battery";
interval = 60;
# statuses: 'Charging', 'Discharging'
states = {
warning = 30;
critical = 15;
};
format = "{capacity}% {icon}";
format-icons = {
default = [
"󰂃" # critical
"󰁺" # 10%
"󰁻" # 20%
"󰁼" # 30%
"󰁽" # 40%
"󰁾" # 50%
"󰁿" # 60%
"󰂀" # 70%
"󰂁" # 80%
"󰂂" # 90%
"󰁹" # 100%
];
charging = [
"󰢟" # critical
"󰢜" # 10%
"󰂆" # 20%
"󰂇" # 30%
"󰂈" # 40%
"󰢝" # 50%
"󰂉" # 60%
"󰢞" # 70%
"󰂊" # 80%
"󰂋" # 90%
"󰂅" # 100%
];
};
max-length = 25;
};
"custom/weather" = {
tooltip = true;
format = { };
interval = 30;
exec = "waybar-weather";
return-type = "json";
markup = "pango";
};
} // cfg.extraModules;
};
# * { font-size: 13px; }
# window.eDP-1 * { font-size: 10px; }
style = ''
.blink_me {
animation: blinker 1s linear infinite;
}
@keyframes blinker {
50% {
color: ${nord.aurora.nord11};
}
}
* {
font-family:
Jetbrains Mono Nerd Font,
monospace;
font-size: 14px;
min-height: 0;
}
#waybar {
background: transparent;
color: ${nord.snowStorm.nord6};
margin: 5px 5px;
}
#workspaces {
background-color: ${nord.polarNight.nord0};
${defaultBorderRadius}
${defaultOpacity}
${defaultCenterOptions}
margin-left: 0.6rem;
}
#workspaces button {
color: ${nord.frost.nord10};
${defaultBorderRadius}
padding: 0.4rem;
}
#workspaces button.active {
color: ${nord.frost.nord8};
${defaultBorderRadius}
}
#workspaces button:hover {
color: ${nord.frost.nord7};
${defaultBorderRadius}
}
#workspaces button.focused {
color: ${nord.snowStorm.nord6};
background: ${nord.aurora.nord13};
${defaultBorderRadius}
}
#workspaces button.urgent {
color: ${nord.polarNight.nord0};
background: ${nord.snowStorm.nord6};
${defaultBorderRadius}
}
#tooltip {
background: ${nord.polarNight.nord0};
border-color: ${nord.polarNight.nord0};
${defaultBorderRadius}
border-width: 1rem;
border-style: solid;
}
#window {
color: ${nord.aurora.nord15};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultBorderRadius}
${defaultCenterOptions}
margin-left: 4rem
margin-right: 4rem
}
/* make window module transparent when no windows present */
#window.empty {
background-color: transparent;
}
#custom-weather {
color: ${nord.frost.nord10};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${borderRight}
}
#battery {
color: ${nord.aurora.nord15};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
min-width: 3rem;
}
#clock {
color: ${nord.frost.nord9};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${borderLeft}
}
/* ------------- */
#idle_inhibitor {
color: ${nord.frost.nord10};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${borderRight}
padding-right: 1rem;
}
#idle_inhibitor:hover {
background: ${nord.polarNight.nord3};
}
#network {
color: ${nord.aurora.nord15};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
padding-right: 15px;
}
#network:hover {
background: ${nord.polarNight.nord3};
}
#bluetooth {
color: ${nord.frost.nord9};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#bluetooth:hover {
background: ${nord.polarNight.nord3};
}
#wireplumber.source {
color: ${nord.frost.nord8};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#wireplumber.source.muted {
animation-name: blinker;
animation-duration: 2s;
animation-timing-function: linear;
animation-iteration-count: infinite;
padding-right: 1rem;
}
#wireplumber.source:hover {
background: ${nord.polarNight.nord3};
}
#wireplumber.sink {
color: ${nord.frost.nord7};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#wireplumber.sink.muted {
animation-name: blinker;
animation-duration: 5s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
#wireplumber.sink:hover {
background: ${nord.polarNight.nord3};
}
#keyboard-state.numlock {
color: ${nord.frost.nord8};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#keyboard-state.capslock {
color: ${nord.frost.nord9};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#temperature.gpu {
color: ${nord.frost.nord10};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#temperature.gpu:hover {
background: ${nord.polarNight.nord3};
}
#temperature {
color: ${nord.frost.nord9};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0
}
/* ------------- */
#tray {
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
${defaultBorderRadius}
margin-right: 0.6rem;
}
/* ------------- */
'' + cfg.extraModulesStyle or '''';
};
};
}

View File

@@ -0,0 +1,32 @@
{ lib, ... }:
with lib;
{
options.mjallen.programs.waybar = {
enable = mkEnableOption "enable waybar";
layer = mkOption {
type = types.str;
default = "top";
};
modules-right = mkOption {
type = with types; listOf str;
default = [];
};
networkInterface = mkOption {
type = types.str;
default = "wlan0";
};
extraModules = mkOption {
type = types.attrs;
default = { };
};
extraModulesStyle = mkOption {
type = types.str;
default = "";
};
};
}

View File

@@ -0,0 +1,76 @@
{ config, lib, namespace, pkgs, ... }:
let
cfg = config.mjallen.programs.waybar;
pythonEnv = pkgs.python3.withPackages (ps: [
pkgs.${namespace}.homeassistant-api
]);
waybar-hass = pkgs.writeScriptBin "waybar-hass" ''
#!${pythonEnv}/bin/python
"""run with the special python"""
import argparse
import time
import json
from homeassistant_api import WebsocketClient
HASS_URL = 'ws://homeassistant.local:8123/api/websocket'
parser = argparse.ArgumentParser(prog='hass python wrapper')
parser.add_argument('--toggle_light')
parser.add_argument('--get_light')
args = parser.parse_args()
def load_key():
"""Read the api key"""
token_path = "/run/secrets/desktop/hass_token"
with open(token_path, "r") as key_file:
key = key_file.readline()
return key
def get_light_state(client, light):
"""Get light status"""
light_entity = client.get_entity(entity_id=light)
state = light_entity.get_state()
if state.state == 'on':
return "󰛨"
return "󰹏"
def toggle_light(client, light):
"""Toggle light status"""
lights = client.get_domain("light")
lights.toggle(entity_id=light)
time.sleep(0.5)
light_entity = client.get_entity(entity_id=light)
state = light_entity.get_state()
if state.state == 'on':
return "󰛨"
return "󰹏"
def main():
"""Main"""
token = load_key()
status = "err"
with WebsocketClient(HASS_URL, token) as client:
# toggle a light
if args.toggle_light is not None:
status = toggle_light(client=client, light=args.toggle_light)
# get the current light status
if args.get_light is not None:
status = get_light_state(client=client, light=args.get_light)
print(json.dumps({ "text": status }))
main()
'';
in
{
imports = [ ../options.nix ];
config = lib.mkIf cfg.enable {
home.packages = [ waybar-hass ];
};
}

View File

@@ -0,0 +1,439 @@
{ config, lib, pkgs, ... }:
let
cfg = config.mjallen.programs.waybar;
waybar-weather = pkgs.writeScriptBin "waybar-weather" ''
#!/usr/bin/env nix-shell
#! nix-shell -i python3 --pure
#! nix-shell -p python3 python3Packages.requests
"""get waybar weather"""
import os
import json
import shutil
from datetime import datetime, timedelta
import requests
WWO_CODE = {
"113": "Sunny",
"116": "PartlyCloudy",
"119": "Cloudy",
"122": "VeryCloudy",
"143": "Fog",
"176": "LightShowers",
"179": "LightSleetShowers",
"182": "LightSleet",
"185": "LightSleet",
"200": "ThunderyShowers",
"227": "LightSnow",
"230": "HeavySnow",
"248": "Fog",
"260": "Fog",
"263": "LightShowers",
"266": "LightRain",
"281": "LightSleet",
"284": "LightSleet",
"293": "LightRain",
"296": "LightRain",
"299": "HeavyShowers",
"302": "HeavyRain",
"305": "HeavyShowers",
"308": "HeavyRain",
"311": "LightSleet",
"314": "LightSleet",
"317": "LightSleet",
"320": "LightSnow",
"323": "LightSnowShowers",
"326": "LightSnowShowers",
"329": "HeavySnow",
"332": "HeavySnow",
"335": "HeavySnowShowers",
"338": "HeavySnow",
"350": "LightSleet",
"353": "LightShowers",
"356": "HeavyShowers",
"359": "HeavyRain",
"362": "LightSleetShowers",
"365": "LightSleetShowers",
"368": "LightSnowShowers",
"371": "HeavySnowShowers",
"374": "LightSleetShowers",
"377": "LightSleet",
"386": "ThunderyShowers",
"389": "ThunderyHeavyRain",
"392": "ThunderySnowShowers",
"395": "HeavySnowShowers",
}
WEATHER_SYMBOL = {
"Unknown": "",
"Cloudy": "",
"Fog": "",
"HeavyRain": "",
"HeavyShowers": "",
"HeavySnow": "",
"HeavySnowShowers": "",
"LightRain": "",
"LightShowers": "",
"LightSleet": "",
"LightSleetShowers": "",
"LightSnow": "",
"LightSnowShowers": "",
"PartlyCloudy": "󰖕",
"Sunny": "",
"ThunderyHeavyRain": "",
"ThunderyShowers": "",
"ThunderySnowShowers": "",
"VeryCloudy": "",
}
WEATHER_CODES = {key: WEATHER_SYMBOL[value] for key, value in WWO_CODE.items()}
WIND_DIRECTION = {
"S": "",
"SW": "",
"W": "",
"NW": "",
"N": "",
"NE": "",
"E": "",
"SE": "",
}
MOON_PHASES = (
"󰽤", "󰽧", "󰽡", "󰽨", "󰽢", "󰽦", "󰽣", "󰽥"
)
WEATHER_SYMBOL_WI_DAY = {
"Unknown": "",
"Cloudy": "",
"Fog": "",
"HeavyRain": "",
"HeavyShowers": "",
"HeavySnow": "",
"HeavySnowShowers": "",
"LightRain": "",
"LightShowers": "",
"LightSleet": "",
"LightSleetShowers": "",
"LightSnow": "",
"LightSnowShowers": "",
"PartlyCloudy": "󰖕",
"Sunny": "",
"ThunderyHeavyRain": "",
"ThunderyShowers": "",
"ThunderySnowShowers": "",
"VeryCloudy": "",
}
WEATHER_CODES_WI_DAY = {key: WEATHER_SYMBOL_WI_DAY[value] for key, value in WWO_CODE.items()}
WEATHER_SYMBOL_WI_NIGHT = {
"Unknown": "",
"Cloudy": "",
"Fog": "",
"HeavyRain": "",
"HeavyShowers": "",
"HeavySnow": "",
"HeavySnowShowers": "",
"LightRain": "",
"LightShowers": "",
"LightSleet": "",
"LightSleetShowers": "",
"LightSnow": "",
"LightSnowShowers": "",
"PartlyCloudy": "󰼱",
"Sunny": "󰖔",
"ThunderyHeavyRain": "",
"ThunderyShowers": "",
"ThunderySnowShowers": "",
"VeryCloudy": "",
}
WEATHER_CODES_WI_NIGHT = {key: WEATHER_SYMBOL_WI_NIGHT[value] for key, value in WWO_CODE.items()}
WEATHER_SYMBOL_WEGO = {
"Unknown": [
" .-. ",
" __) ",
" ( ",
" `- ",
" "],
"Sunny": [
'<span foreground=\"#FFFF00\"> \\ / </span>',
'<span foreground=\"#FFFF00\"> .-. </span>',
'<span foreground=\"#FFFF00\"> ( ) </span>',
'<span foreground=\"#FFFF00\"> `- </span>',
'<span foreground=\"#FFFF00\"> / \\ </span>'],
"PartlyCloudy": [
'<span foreground=\"#FFFF00\"> \\ / </span>',
'<span foreground=\"#FFFF00\"> _ /\'\'</span>"<span foreground=\"#BBBBBB\">.-. </span>',
'<span foreground=\"#FFFF00\"> \\_</span>"<span foreground=\"#BBBBBB\">( ). </span>',
'<span foreground=\"#FFFF00\"> /</span>"<span foreground=\"#BBBBBB\">(___(__) </span>',
' '
],
"Cloudy": [
' ',
'<span foreground=\"#BBBBBB\"> .--. </span>',
'<span foreground=\"#BBBBBB\"> .-( ). </span>',
'<span foreground=\"#BBBBBB\"> (___.__)__) </span>',
' '],
"VeryCloudy": [
' ',
'<span foreground=\"#585858\"; font-weight: bold;"> .--. </span>',
'<span foreground=\"#585858\"; font-weight: bold;"> .-( ). </span>',
'<span foreground=\"#585858\"; font-weight: bold;"> (___.__)__) </span>',
' '],
"LightShowers": [
'<span foreground=\"#FFFF00\"> _`/\'\'</span>"<span foreground=\"#BBBBBB\">.-. </span>',
'<span foreground=\"#FFFF00\"> ,\\_</span>"<span foreground=\"#BBBBBB\">( ). </span>',
'<span foreground=\"#FFFF00\"> /</span>"<span foreground=\"#BBBBBB\">(___(__) </span>',
'<span foreground=\"#87afff\";"> </span>',
'<span foreground=\"#87afff\";"> </span>'],
"HeavyShowers": [
'<span foreground=\"#FFFF00\"> _`/\'\'</span>"<span foreground=\"#585858\"; font-weight: bold;">.-. </span>',
'<span foreground=\"#FFFF00\"> ,\\_</span>"<span foreground=\"#585858\"; font-weight: bold;">( ). </span>',
'<span foreground=\"#FFFF00\"> /</span>"<span foreground=\"#585858\"; font-weight: bold;">(___(__) </span>',
'<span foreground=\"#0000ff\"; font-weight: bold;"> </span>',
'<span foreground=\"#0000ff\"; font-weight: bold;"> </span>'],
"LightSnowShowers": [
'<span foreground=\"#FFFF00\"> _`/\'\'</span>"<span foreground=\"#BBBBBB\">.-. </span>',
'<span foreground=\"#FFFF00\"> ,\\_</span>"<span foreground=\"#BBBBBB\">( ). </span>',
'<span foreground=\"#FFFF00\"> /</span>"<span foreground=\"#BBBBBB\">(___(__) </span>',
'<span foreground=\"#eeeeee\";"> * * * </span>',
'<span foreground=\"#eeeeee\";"> * * * </span>'],
"HeavySnowShowers": [
'<span foreground=\"#FFFF00\"> _`/\'\'</span>"<span foreground=\"#585858\"; font-weight: bold;">.-. </span>',
'<span foreground=\"#FFFF00\"> ,\\_</span>"<span foreground=\"#585858\"; font-weight: bold;">( ). </span>',
'<span foreground=\"#FFFF00\"> /</span>"<span foreground=\"#585858\"; font-weight: bold;">(___(__) </span>',
'<span foreground=\"#eeeeee\"; font-weight: bold;"> * * * * </span>',
'<span foreground=\"#eeeeee\"; font-weight: bold;"> * * * * </span>'],
"LightSleetShowers": [
'<span foreground=\"#FFFF00\"> _`/\'\'</span>"<span foreground=\"#BBBBBB\">.-. </span>',
'<span foreground=\"#FFFF00\"> ,\\_</span>"<span foreground=\"#BBBBBB\">( ). </span>',
'<span foreground=\"#FFFF00\"> /</span>"<span foreground=\"#BBBBBB\">(___(__) </span>',
'<span foreground=\"#87afff\";"> </span>"<span foreground=\"#eeeeee\";">*</span>"<span foreground=\"#87afff\";"> </span>"<span foreground=\"#eeeeee\";">* </span>',
'<span foreground=\"#eeeeee\";"> *</span>"<span foreground=\"#87afff\";"> </span>"<span foreground=\"#eeeeee\";">*</span>"<span foreground=\"#87afff\";"> </span>'],
"ThunderyShowers": [
'<span foreground=\"#FFFF00\"> _`/\'\'</span>"<span foreground=\"#BBBBBB\">.-. </span>',
'<span foreground=\"#FFFF00\"> ,\\_</span>"<span foreground=\"#BBBBBB\">( ). </span>',
'<span foreground=\"#FFFF00\"> /</span>"<span foreground=\"#BBBBBB\">(___(__) </span>',
'<span foreground=\"#ffff87\";"> \\</span>"<span foreground=\"#87afff\";"> </span>"<span foreground=\"#ffff87\";">\\</span>"<span foreground=\"#87afff\";"> </span>',
'<span foreground=\"#87afff\";"> </span>'],
"ThunderyHeavyRain": [
'<span foreground=\"#585858\"; font-weight: bold;"> .-. </span>',
'<span foreground=\"#585858\"; font-weight: bold;"> ( ). </span>',
'<span foreground=\"#585858\"; font-weight: bold;"> (___(__) </span>',
'<span foreground=\"#0000ff\"; font-weight: bold;"> </span>"<span foreground=\"#ffff87\";">\\</span>"<span foreground=\"#0000ff\";"></span>"<span foreground=\"#ffff87\";">\\</span>"<span foreground=\"#0000ff\";"> </span>',
'<span foreground=\"#0000ff\"; font-weight: bold;"> </span>"<span foreground=\"#ffff87\";">\\</span>"<span foreground=\"#0000ff\";"> </span>'],
"ThunderySnowShowers": [
'<span foreground=\"#FFFF00\"> _`/\'\'</span>"<span foreground=\"#BBBBBB\">.-. </span>',
'<span foreground=\"#FFFF00\"> ,\\_</span>"<span foreground=\"#BBBBBB\">( ). </span>',
'<span foreground=\"#FFFF00\"> /</span>"<span foreground=\"#BBBBBB\">(___(__) </span>',
'<span foreground=\"#eeeeee\";"> *</span>"<span foreground=\"#ffff87\";">\\</span>"<span foreground=\"#eeeeee\";">*</span>"<span foreground=\"#ffff87\";">\\</span>"<span foreground=\"#eeeeee\";">* </span>',
'<span foreground=\"#eeeeee\";"> * * * </span>'],
"LightRain": [
'<span foreground=\"#BBBBBB\"> .-. </span>',
'<span foreground=\"#BBBBBB\"> ( ). </span>',
'<span foreground=\"#BBBBBB\"> (___(__) </span>',
'<span foreground=\"#87afff\";"> </span>',
'<span foreground=\"#87afff\";"> </span>'],
"HeavyRain": [
'<span foreground=\"#585858\"; font-weight: bold;"> .-. </span>',
'<span foreground=\"#585858\"; font-weight: bold;"> ( ). </span>',
'<span foreground=\"#585858\"; font-weight: bold;"> (___(__) </span>',
'<span foreground=\"#0000ff\"; font-weight: bold;"> </span>',
'<span foreground=\"#0000ff\"; font-weight: bold;"> </span>'],
"LightSnow": [
'<span foreground=\"#BBBBBB\"> .-. </span>',
'<span foreground=\"#BBBBBB\"> ( ). </span>',
'<span foreground=\"#BBBBBB\"> (___(__) </span>',
'<span foreground=\"#eeeeee\";"> * * * </span>',
'<span foreground=\"#eeeeee\";"> * * * </span>'],
"HeavySnow": [
'<span foreground=\"#585858\"; font-weight: bold;"> .-. </span>',
'<span foreground=\"#585858\"; font-weight: bold;"> ( ). </span>',
'<span foreground=\"#585858\"; font-weight: bold;"> (___(__) </span>',
'<span foreground=\"#eeeeee\"; font-weight: bold;"> * * * * </span>',
'<span foreground=\"#eeeeee\"; font-weight: bold;"> * * * * </span>'],
"LightSleet": [
'<span foreground=\"#BBBBBB\"> .-. </span>',
'<span foreground=\"#BBBBBB\"> ( ). </span>',
'<span foreground=\"#BBBBBB\"> (___(__) </span>',
'<span foreground=\"#87afff\";"> </span>"<span foreground=\"#eeeeee\";">*</span>"<span foreground=\"#87afff\";"> </span>"<span foreground=\"#eeeeee\";">* </span>',
'<span foreground=\"#eeeeee\";"> *</span>"<span foreground=\"#87afff\";"> </span>"<span foreground=\"#eeeeee\";">*</span>"<span foreground=\"#87afff\";"> </span>'],
"Fog": [
' ',
'<span foreground=\"#c0c0c0\";"> _ - _ - _ - </span>',
'<span foreground=\"#c0c0c0\";"> _ - _ - _ </span>',
'<span foreground=\"#c0c0c0\";"> _ - _ - _ - </span>',
' '],
}
WEATHER_CODES_WEGO = {key: WEATHER_SYMBOL_WEGO[value] for key, value in WWO_CODE.items()}
data = {}
def format_time(time):
"""get the time formatted"""
return datetime.strptime(format_24_time(time), "%H").strftime("%I %p")
def format_24_time(time):
"""get the time formatted"""
return time.replace("00", "").zfill(2)
def format_temp(temp):
"""get the temp formatted"""
return (temp + "°").ljust(3)
def format_chances(hour):
"""get the chances formatted"""
chances = {
"chanceoffog": "Fog",
"chanceoffrost": "Frost",
"chanceofovercast": "Overcast",
"chanceofrain": "Rain",
"chanceofsnow": "Snow",
"chanceofsunshine": "Sunshine",
"chanceofthunder": "Thunder",
"chanceofwindy": "Wind",
}
conditions = []
for chance, event in chances.items():
if int(hour[chance]) > 0:
conditions.append(event + " " + hour[chance] + "%")
return ", ".join(conditions)
def build_text(current_condition):
"""build the text string"""
feels_like_f = current_condition["FeelsLikeF"]
weather_code = current_condition["weatherCode"]
tempint = int(feels_like_f)
extrachar = ""
if 0 < tempint < 10:
extrachar = "+"
current_weather = f"{WEATHER_CODES[weather_code]} {extrachar} {feels_like_f}°F"
return current_weather
def build_tooltip(current_condition, astronomy, moon_icon):
"""build the tooltip text"""
weather_description = current_condition['weatherDesc'][0]['value']
feels_like_f = current_condition["FeelsLikeF"]
temp_f = current_condition['temp_F']
humidity = current_condition['humidity']
wind_speed = current_condition['windspeedMiles']
wind_dir = current_condition['winddir16Point']
moon_phase = astronomy['moon_phase']
wego = WEATHER_CODES_WEGO[current_condition['weatherCode']]
tooltip = f"{wego[0]}{weather_description} {temp_f}°\n"
tooltip += f"{wego[1]}Feels like: {feels_like_f}°\n"
tooltip += f"{wego[2]}Wind: {wind_speed}mph {WIND_DIRECTION[wind_dir]}\n"
tooltip += f"{wego[3]}Humidity: {humidity}%\n"
tooltip += f"{wego[4]}Moon phase: {moon_phase} " + moon_icon + "\n"
return tooltip
def build_forecast(weather):
"""build a 3 day forecast"""
tooltip = "\n"
for i, day in enumerate(weather):
# determine day
if i == 0:
tooltip += "Today, "
if i == 1:
tooltip += "Tomorrow, "
# format the date
date = datetime.strptime(day['date'], "%Y-%m-%d").strftime("%a %b %d %Y")
tooltip += f"<b>{date}</b>\n"
# set the high and low
max_temp = day['maxtempF']
min_temp = day['mintempF']
tooltip += f" {max_temp}°F {min_temp}°F"
sunrise = day['astronomy'][0]['sunrise']
sunset = day['astronomy'][0]['sunset']
tooltip += f" {sunrise} {sunset}\n"
tooltip += build_hourly_forecast(i, day['hourly'], sunrise, sunset)
return tooltip
def build_hourly_forecast(day_num, hourly, sunrise, sunset):
"""build an hourly forecast"""
sunrise_hour = datetime.strptime(sunrise, "%I:%M %p").hour
sunset_hour = datetime.strptime(sunset, "%I:%M %p").hour
current_hour = datetime.now().hour
tooltip = ""
for hour in hourly:
time_24_hr = int(format_24_time(hour["time"]))
if day_num == 0:
if time_24_hr < current_hour - 2:
continue
# determine which code to use
if is_night_hour(time_24_hr, sunrise_hour, sunset_hour):
codes = WEATHER_CODES_WI_NIGHT
else:
codes = WEATHER_CODES_WI_DAY
current_time = format_time(hour['time'])
current_weather_code = codes[hour['weatherCode']]
feels_like = format_temp(hour['FeelsLikeF'])
weather_desc = hour['weatherDesc'][0]['value']
current_chances = format_chances(hour)
tooltip += f"{current_time} {current_weather_code} "
tooltip += f"{feels_like} {weather_desc}, {current_chances}\n"
return tooltip
def is_night_hour(time_24_hr, sunrise_hour, sunset_hour):
"""returns true if the hour is night"""
before_sunrise = time_24_hr < sunrise_hour
after_sunset = time_24_hr > sunset_hour
return after_sunset or before_sunrise
def get_wttr_json():
"""get the weather json"""
weather = requests.get("https://wttr.in/?u&format=j1", timeout=30).json()
moon = requests.get("https://wttr.in/?format=%m", timeout=30)
moon_icon = moon.text
current_condition = weather["current_condition"][0]
astronomy = weather["weather"][0]['astronomy'][0]
data["text"] = build_text(current_condition)
data["tooltip"] = build_tooltip(current_condition, astronomy, moon_icon)
data["tooltip"] += build_forecast(weather["weather"])
return json.dumps(data)
def main():
"""main"""
try:
print(get_wttr_json())
except Exception as e:
print(e)
main()
'';
in
{
imports = [ ../options.nix ];
config = lib.mkIf cfg.enable {
home.packages = [ waybar-weather ];
};
}