diff --git a/home/hyprland/default.nix b/home/hyprland/default.nix
index 870b67c..3fe5a1e 100644
--- a/home/hyprland/default.nix
+++ b/home/hyprland/default.nix
@@ -11,6 +11,8 @@ in
{
imports = [
./kitty.nix # Terminal
+ ../waybar
+ ../rofi
];
diff --git a/home/waybar/config/config.jsonc b/home/waybar/config/config.jsonc
new file mode 100644
index 0000000..3614ba4
--- /dev/null
+++ b/home/waybar/config/config.jsonc
@@ -0,0 +1,607 @@
+// --// waybar config generated by wbarconfgen.sh //-- //
+
+[
+/*{
+ "layer": "top",
+ "position": "left",
+ "mod": "dock",
+ "width": 20,
+ "margin-top": 200,
+ "margin-bottom": 200,
+ "exclusive": true,
+ "passthrough": false,
+ "gtk-layer-shell": true,
+ "reload_style_on_change": true,
+ "output": "HDMI-A-2",
+
+ "modules-left": [],
+ "modules-center": ["wlr/taskbar"],
+ "modules-right": [],
+
+ "wlr/taskbar": {
+ "all-outputs": true,
+ "format": "{icon}",
+ "icon-size": 20,
+ "active-first": true
+ }
+},*/
+
+{
+// sourced from header module //
+
+ "layer": "top",
+ "position": "top",
+ "mod": "dock",
+ "margin-left": 10,
+ "margin-right": 10,
+ "margin-top": 7,
+ "margin-bottom": 0,
+ "exclusive": true,
+ "passthrough": false,
+ "gtk-layer-shell": true,
+ "reload_style_on_change": true,
+ "output": "DP-1",
+
+// positions generated based on config.ctl //
+
+ "modules-left": ["custom/smallspacer","hyprland/workspaces","custom/spacer","mpris"],
+ "modules-center": ["custom/padd","custom/l_end","custom/r_end","hyprland/window","custom/padd"],
+ "modules-right": ["custom/padd","custom/l_end","group/expand","network","group/expand-3","group/expand-2","group/expand-4","memory","cpu","clock","custom/notification","custom/padd"],
+
+
+// sourced from modules based on config.ctl //
+
+"custom/led": {
+ "format": " ",
+ "format-alt": " ",
+ "on-click": "~/mouse.sh",
+ "tooltip": false,
+},
+
+"upower": {
+ "icon-size": 20,
+ "format": "",
+ "format-alt": "{}[{time}]",
+ "tooltip": true,
+ "tooltip-spacing": 20,
+ "on-click-right": "pkill waybar & hyprctl dispatch exec waybar"
+},
+
+"upower#headset": {
+"format": " {percentage}",
+"native-path": "/org/freedesktop/UPower/devices/headset_dev_A6_98_9A_0D_D3_49",
+"show-icon": false,
+"tooltip": false,
+},
+
+"group/expand-4": {
+ "orientation": "horizontal",
+ "drawer": {
+ "transition-duration": 600,
+ "children-class": "not-power",
+ "transition-to-left": true,
+ "click-to-reveal": true
+ },
+ "modules": ["upower","upower/headset"]
+ },
+
+"custom/smallspacer":{
+"format": " ",
+},
+"memory": {
+ "interval": 1,
+ "rotate": 270,
+ "format": "{icon}",
+ "format-icons": ["","","","","","","","",""],
+ "max-length": 10
+},
+"cpu": {
+ "interval": 1,
+ "format": "{icon}",
+ "rotate": 270,
+ "format-icons": ["","","","","","","","",""],
+},
+
+
+"mpris": {
+ "format": "{player_icon} {dynamic}",
+ "format-paused": "{status_icon} {dynamic}",
+ "max-length": 100,
+ "player-icons": {
+ "default": "⏸",
+ "mpv": "🎵"
+ },
+ "status-icons": {
+ "paused": "▶"
+ },
+ // "ignored-players": ["firefox"]
+},
+"tray": {
+ "icon-size": 16,
+ "rotate": 0,
+ "spacing": 3
+ },
+
+ "group/expand": {
+ "orientation": "horizontal",
+ "drawer": {
+ "transition-duration": 600,
+ "children-class": "not-power",
+ "transition-to-left": true,
+ // "click-to-reveal": true
+ },
+ "modules": ["custom/menu","custom/spacer","tray"]
+ },
+
+ "custom/menu":{
+ "format": "",
+ "rotate": 90,
+ },
+
+
+"custom/notification": {
+ "tooltip": false,
+ "format": "{icon}",
+ "format-icons": {
+ "notification": "",
+ "none": "",
+ "dnd-notification": "",
+ "dnd-none": "",
+ "inhibited-notification": "",
+ "inhibited-none": "",
+ "dnd-inhibited-notification": "",
+ "dnd-inhibited-none": ""
+ },
+ "return-type": "json",
+ "exec-if": "which swaync-client",
+ "exec": "swaync-client -swb",
+ "on-click-right": "swaync-client -d -sw",
+ "on-click": "swaync-client -t -sw",
+ "escape": true
+ },
+
+ "hyprland/window": {
+//"format": "{}" // <--- these is the default value
+"format": "{class}",
+ "max-length": 120,
+ "icon": false,
+ "icon-size": 13,
+},
+
+ "custom/power": {
+ "format": "@{}",
+ "rotate": 0,
+ "on-click": "ags -t ControlPanel",
+ "on-click-right": "pkill ags",
+ "tooltip": true
+ },
+
+ "custom/spacer":{
+ "format": "|"
+ },
+
+
+
+
+
+"hyprland/workspaces": {
+ "format": "{icon}",
+ "format-icons": {
+ "default": "",
+ "active": "",
+ //"default": "○",
+ //"default": "●"
+ },
+},
+
+"wlr/workspaces": {
+ "persistent-workspaces": {
+ "3": [], // Always show a workspace with name '3', on all outputs if it does not exists
+ "4": ["eDP-1"], // Always show a workspace with name '4', on output 'eDP-1' if it does not exists
+ "5": ["eDP-1", "DP-2"] // Always show a workspace with name '5', on outputs 'eDP-1' and 'DP-2' if it does not exists
+ }
+},
+
+
+"cava": {
+ "cava_config": "~/.config/cava/config",
+ "framerate": 60,
+ "autosens": 1,
+ "bars": 14,
+ "lower_cutoff_freq": 50,
+ "higher_cutoff_freq": 10000,
+ "method": "pulse",
+ "source": "auto",
+ "stereo": true,
+ "reverse": false,
+ "bar_delimiter": 0,
+ "monstercat": false,
+ "waves": false,
+ "noise_reduction": 0.77,
+ "input_delay": 2,
+ "format-icons" : ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" ],
+ "actions": {
+ "on-click-right": "mode"
+ }
+ },
+
+ "custom/script": {
+ "on-click": "~/.config/waybar/volume.sh toggle",
+ "format": "",
+
+ },
+
+ "custom/cliphist": {
+ "format": "{}",
+ "rotate": 0,
+ "exec": "echo ; echo clipboard history",
+ "on-click": "sleep 0.1 && cliphist.sh c",
+ "on-click-right": "sleep 0.1 && cliphist.sh d",
+ "on-click-middle": "sleep 0.1 && cliphist.sh w",
+ "interval" : 86400, // once every day
+ "tooltip": true
+ },
+
+ "custom/wbar": {
+ "format": "𐌏{}", // //
+ "rotate": 0,
+ "exec": "echo ; echo show app menu",
+ "on-click": "wofi --show drun",
+ "on-click-right": "wbarconfgen.sh p",
+ "on-click-middle": "sleep 0.1 && quickapps.sh kitty firefox spotify code dolphin",
+ "interval" : 86400,
+ "tooltip": true
+ },
+
+ "custom/theme": {
+ "format": "{}",
+ "rotate": 0,
+ "exec": "echo ; echo pick color",
+ "on-click": "hyprpicker",
+ "on-click-right": "themeswitch.sh -p",
+ "on-click-middle": "sleep 0.1 && themeselect.sh",
+ "interval" : 86400, // once every day
+ "tooltip": true
+ },
+
+ "custom/wallchange": {
+ "format": "{}",
+ "rotate": 0,
+ "exec": "echo ; echo switch wallpaper",
+ "on-click": "swww img --transition-type grow --transition-pos 0.071,0.988 --transition-step 255 --transition-fps 60 /home/anik/Downloads/skyway.jpg",
+ "on-click-right": "swww img --transition-type grow --transition-pos 0.071,0.988 --transition-step 255 --transition-fps 60 /home/anik/Downloads/cloud.png",
+ "on-click-middle": "swww img --transition-type grow --transition-pos 0.071,0.988 --transition-step 255 --transition-fps 60 /home/anik/Downloads/gradient.jpg",
+ "on-scroll-up": "swww img --transition-type grow --transition-pos 0.071,0.988 --transition-step 255 --transition-fps 60 /home/anik/Downloads/torvalds.png",
+ "on-scroll-down": "swww img --transition-type grow --transition-pos 0.071,0.988 --transition-step 255 --transition-fps 60 /home/anik/Downloads/night.png",
+ "interval" : 86400, // once every day
+ "tooltip": true
+ },
+ "custom/mouse":
+ {
+ "format": "",
+ "format-alt": "",
+
+ "on-click": "m8mouse -dpi 1 -led 2 -speed 4",
+ "on-click-right": "m8mouse -dpi 1 -led 4 -speed 4",
+ "on-click-middle": "m8mouse -dpi 1 -led 7 -speed 4",
+ "tooltip": true
+ },
+
+ "wlr/taskbar": {
+ "format": "{icon}",
+ "rotate": 0,
+ "icon-size": 18,
+ "icon-theme": "Tela-circle-dracula",
+ "spacing": 0,
+ "tooltip-format": "{title}",
+ "on-click": "activate",
+ "on-click-middle": "close",
+ "ignore-list": [
+ "Alacritty"
+ ],
+ "app_ids-mapping": {
+ "firefoxdeveloperedition": "firefox-developer-edition"
+ }
+ },
+
+ "custom/spotify": {
+ "exec": "mediaplayer.py --player spotify",
+ "format": " {}",
+ "rotate": 0,
+ "return-type": "json",
+ "on-click": "playerctl play-pause --player spotify",
+ "on-click-right": "playerctl next --player spotify",
+ "on-click-middle": "playerctl previous --player spotify",
+ "on-scroll-up": "volumecontrol.sh -p spotify i",
+ "on-scroll-down": "volumecontrol.sh -p spotify d",
+ "max-length": 25,
+ "escape": true,
+ "tooltip": true
+ },
+
+ "idle_inhibitor": {
+ "format": "{icon}",
+ "rotate": 0,
+ "format-icons": {
+ "activated": "",
+ "deactivated": ""
+ }
+ },
+
+ "clock": {
+ "format": "{:%I:%M %p}",
+ "rotate": 0,
+ "on-click": "/usr/local/bin/ags -t ActivityCenter",
+ "tooltip-format": "{calendar}",
+ "calendar": {
+ "mode": "month",
+ "mode-mon-col": 3,
+ "on-scroll": 1,
+ "on-click-right": "mode",
+ "format": {
+ "months": "{}",
+ "weekdays": "{}",
+ "today": "{}"
+ }
+ },
+ "actions": {
+ "on-click-right": "mode",
+ "on-click-forward": "tz_up",
+ "on-click-backward": "tz_down",
+ "on-scroll-up": "shift_up",
+ "on-scroll-down": "shift_down"
+ }
+ },
+
+
+ "battery": {
+ "states": {
+ "good": 95,
+ "warning": 30,
+ "critical": 20
+ },
+ "format": "{icon}",
+ "rotate": 0,
+ "format-charging": "",
+ "format-plugged": "",
+ // "format-alt": "<{time} | {capacity}%",
+ "format-icons": ["","","","","","","","",""],
+ // "format-icons": ["","","","","","","",""],
+ //"format-icons": ["", "", "", "", "", "", "", "", "", "", ""],
+ "on-click-right": "pkill waybar & hyprctl dispatch exec waybar",
+ // "format-icons": [],
+
+ },
+
+ "backlight": {
+ "device": "intel_backlight",
+ "rotate": 0,
+ "format": "{icon}",
+ "format-icons": ["", "", "", ""],
+ "scroll-step": 1,
+ "min-length": 2
+ },
+
+ "group/expand-2": {
+ "orientation": "horizontal",
+ "drawer": {
+ "transition-duration": 600,
+ "children-class": "not-power",
+ "transition-to-left": true,
+ "click-to-reveal": true
+ },
+ "modules": ["backlight","backlight/slider","custom/smallspacer","custom/led"]
+ },
+
+ "group/expand-3": {
+ "orientation": "horizontal",
+ "drawer": {
+ "transition-duration": 600,
+ "children-class": "not-power",
+ "transition-to-left": true,
+ "click-to-reveal": true
+ },
+ "modules": ["pulseaudio","pulseaudio/slider"]
+ },
+
+ "network": {
+ "tooltip": true,
+ "format-wifi": "{icon} ",
+ "format-icons": ["", "", ""],
+ // "format-wifi": "",
+ "rotate": 0,
+ "format-ethernet": " ",
+ "tooltip-format": "Network: {essid}\nSignal strength: {signaldBm}dBm ({signalStrength}%)\nFrequency: {frequency}MHz\nInterface: {ifname}\nIP: {ipaddr}/{cidr}\nGateway: {gwaddr}\nNetmask: {netmask}",
+ "format-linked": " {ifname} (No IP)",
+ "format-disconnected": " ",
+ "tooltip-format-disconnected": "Disconnected",
+ "on-click": "/usr/local/bin/ags -t ControlPanel",
+ "interval": 2,
+ },
+
+ "pulseaudio": {
+ "format": "{icon}",
+ "rotate": 0,
+ "format-muted": "婢",
+ "tooltip-format": "{icon} {desc} // {volume}%",
+ "scroll-step": 5,
+ "format-icons": {
+ "headphone": "",
+ "hands-free": "",
+ "headset": "",
+ "phone": "",
+ "portable": "",
+ "car": "",
+ "default": ["", "", ""]
+ }
+ },
+
+ "pulseaudio#microphone": {
+ "format": "{format_source}",
+ "rotate": 0,
+ "format-source": "",
+ "format-source-muted": "",
+ "on-click": "pavucontrol -t 4",
+ "on-click-middle": "volumecontrol.sh -i m",
+ "on-scroll-up": "volumecontrol.sh -i i",
+ "on-scroll-down": "volumecontrol.sh -i d",
+ "tooltip-format": "{format_source} {source_desc} // {source_volume}%",
+ "scroll-step": 5
+ },
+
+ "custom/notifications": {
+ "tooltip": false,
+ "format": "{icon} {}",
+ "rotate": 0,
+ "format-icons": {
+ "email-notification": "",
+ "chat-notification": "",
+ "warning-notification": "",
+ "error-notification": "",
+ "network-notification": "",
+ "battery-notification": "",
+ "update-notification": "",
+ "music-notification": "",
+ "volume-notification": "",
+ "notification": "",
+ "none": ""
+ },
+ "return-type": "json",
+ "exec-if": "which dunstctl",
+ "exec": "notifications.py",
+ "on-click": "sleep 0.1 && dunstctl history-pop",
+ "on-click-middle": "dunstctl history-clear",
+ "on-click-right": "dunstctl close-all",
+ "interval": 1,
+ "tooltip": true,
+ "escape": true
+ },
+
+ "custom/keybindhint": {
+ "format": " ",
+ "rotate": 0,
+ "on-click": "keybinds_hint.sh"
+ },
+
+"custom/expand": {
+ "on-click":"~/.config/hypr/scripts/expand_toolbar",
+ "format":"{}",
+ "exec":"~/.config/hypr/scripts/tools/expand arrow-icon"
+ },
+
+// modules for padding //
+
+ "custom/l_end": {
+ "format": " ",
+ "interval" : "once",
+ "tooltip": false
+ },
+
+ "custom/r_end": {
+ "format": " ",
+ "interval" : "once",
+ "tooltip": false
+ },
+
+ "custom/sl_end": {
+ "format": " ",
+ "interval" : "once",
+ "tooltip": false
+ },
+
+ "custom/sr_end": {
+ "format": " ",
+ "interval" : "once",
+ "tooltip": false
+ },
+
+ "custom/rl_end": {
+ "format": " ",
+ "interval" : "once",
+ "tooltip": false
+ },
+
+ "custom/rr_end": {
+ "format": " ",
+ "interval" : "once",
+ "tooltip": false
+ },
+
+ "custom/padd": {
+ "format": " ",
+ "interval" : "once",
+ "tooltip": false
+ },
+
+
+"backlight/slider": {
+ "min": 5,
+ "max": 100,
+ "rotate": 0,
+ "device": "intel_backlight",
+ "scroll-step": 1,
+ },
+
+ "pulseaudio/slider": {
+ "min": 5,
+ "max": 100,
+ "rotate": 0,
+ "device": "pulseaudio",
+ "scroll-step": 1,
+ },
+},
+{
+// sourced from header module //
+
+ "layer": "top",
+ "position": "top",
+ "mod": "dock",
+ "margin-left": 10,
+ "margin-right": 10,
+ "margin-top": 7,
+ "margin-bottom": 0,
+ "exclusive": true,
+ "passthrough": false,
+ "gtk-layer-shell": true,
+ "reload_style_on_change": true,
+ "output": "HDMI-A-2",
+
+// positions generated based on config.ctl //
+
+ "modules-left": ["hyprland/workspaces"],
+ "modules-center": ["custom/weather"],
+ "modules-right": ["network"],
+
+
+// sourced from modules based on config.ctl //
+ "hyprland/workspaces": {
+ "format": "{icon}",
+ "format-icons": {
+ "default": "",
+ "active": "",
+ //"default": "○",
+ //"default": "●"
+ }
+ },
+
+ "custom/weather": {
+ "exec": "python3 ~/.config/waybar/weather.py waybar",
+ "restart-interval": 900,
+ "return-type": "json"
+ },
+
+ "network": {
+ "tooltip": true,
+ "format-wifi": "{icon} ",
+ "format-icons": ["", "", ""],
+ // "format-wifi": "",
+ "rotate": 0,
+ "format-ethernet": "{bandwidthTotalBits} {bandwidthUpBits} {bandwidthDownBits} ",
+ "tooltip-format": "Network: {essid}\nSignal strength: {signaldBm}dBm ({signalStrength}%)\nFrequency: {frequency}MHz\nInterface: {ifname}\nIP: {ipaddr}/{cidr}\nGateway: {gwaddr}\nNetmask: {netmask}",
+ "format-linked": " {ifname} (No IP)",
+ "format-disconnected": " ",
+ "tooltip-format-disconnected": "Disconnected",
+ "on-click": "/usr/local/bin/ags -t ControlPanel",
+ "interval": 1,
+ },
+}]
diff --git a/home/waybar/config/style.css b/home/waybar/config/style.css
new file mode 100644
index 0000000..dc23957
--- /dev/null
+++ b/home/waybar/config/style.css
@@ -0,0 +1,397 @@
+* {
+ font-family: "JetBrains Mono Nerd Font";
+ /*font-family: "JetBrainsMono Nerd Font";*/
+ font-weight: bold;
+ font-size: 15px;
+}
+
+#custom-notification {
+ font-family: "JetBrains Mono Nerd Font";
+ font-size: 17px;
+ color: #A1BDCE;
+ margin: 2px 0px 0px 0px;
+}
+
+window#waybar {
+ background: #092047;
+ /* border-radius: 15px; */
+ /* border: 2px solid #124323; */
+/* border: 0px solid #A1BDCE; */
+ border: 3px solid rgba(172, 97, 185, 1);
+ border-radius: 10px;
+}
+
+tooltip {
+ background: #171717;
+ color: #A1BDCE;
+ font-size: 13px;
+ border-radius: 7px;
+ border: 2px solid #101a24;
+
+
+ }
+#workspaces{
+background: rgba(23, 23, 23, 0.0);
+ color: #888789;
+ box-shadow: none;
+ text-shadow: none;
+ border-radius: 9px;
+ transition: 0.2s ease;
+ padding-left: 4px;
+ padding-right: 4px;
+ padding-top: 1px;
+}
+
+
+#workspaces button {
+background: rgba(23, 23, 23, 0.0);
+ color: #A1BDCE;
+ box-shadow: none;
+ text-shadow: none;
+ border-radius: 9px;
+ transition: 0.2s ease;
+ padding-left: 4px;
+ padding-right: 4px;
+ /* animation: ws_normal 20s ease-in-out 1; */
+}
+
+
+
+#workspaces button.active {
+
+
+ /* background-image: url("/home/anik/Documents/bar1.png");*/
+ color: #FF2A6D;
+ transition: all 0.3s ease;
+ padding-left: 4px;
+ padding-right: 4px;
+ /* transition: all 0.4s cubic-bezier(.55,0.68,.48,1.682); */
+}
+
+#workspaces button:hover {
+ background: none;
+ color: #65DC98;
+ animation: ws_hover 20s ease-in-out 1;
+ transition: all 0.5s cubic-bezier(.55,-0.68,.48,1.682);
+}
+
+#taskbar button {
+ box-shadow: none;
+ text-shadow: none;
+ font-size: 4px;
+ padding: 0px;
+ border-radius: 9px;
+ margin-bottom: 3px;
+ margin-left: 0px;
+ padding-left: 3px;
+ padding-right: 3px;
+ margin-right: 0px;
+ color: @wb-color;
+ animation: tb_normal 20s ease-in-out 1;
+}
+
+#taskbar button.active {
+ background: @wb-act-bg;
+ color: @wb-act-color;
+ margin-left: 3px;
+ padding-left: 12px;
+ padding-right: 12px;
+ margin-right: 3px;
+ animation: tb_active 20s ease-in-out 1;
+ transition: all 0.4s cubic-bezier(.55,-0.68,.48,1.682);
+ min-height: 9px;
+}
+
+#taskbar button:hover {
+ background: @wb-hvr-bg;
+ color: @wb-hvr-color;
+ animation: tb_hover 20s ease-in-out 1;
+ transition: all 0.3s cubic-bezier(.55,-0.68,.48,1.682);
+}
+
+#tray menu * {
+ min-height: 16px;
+ font-weight: bold;
+ font-size: 13px;
+ color: #9488e3;
+}
+
+#tray menu separator {
+ min-height: 10px
+}
+
+
+#custom-spacer{
+opacity: 0.0;
+}
+#custom-smallspacer{
+opacity: 0.0;
+}
+
+
+#custom-mouse{
+font-size: 14px;
+margin-bottom: 6px;
+background: #161320;
+}
+
+
+#custom-power{
+ font-size: 15px;
+ color: #FFFFFF;
+ background: rgba(22, 19, 32, 0.9);
+ margin: 6px 0px 6px 0px;
+ padding-left: 4px;
+ padding-right: 4px;
+ }
+
+#backlight{
+ color: #2096C0;
+ background: rgba(23, 23, 23, 0.0);
+ font-weight: normal;
+ font-size: 19px;
+ margin: 1px 0px 0px 0px;
+ padding-left: 0px;
+ padding-right: 2px;
+
+}
+#bluetooth,
+#custom-cliphist{
+ color: #E6E7E7;
+ background: #161320;
+ opacity: 1;
+ margin: 4px 0px 4px 0px;
+ padding-left: 4px;
+ padding-right: 4px;
+
+}
+#battery{
+ font-weight: normal;
+ font-size: 22px;
+ color: #a6d189;
+ background: rgba(23, 23, 23, 0.0);
+ opacity: 1;
+ margin: 0px 0px 0px 0px;
+ padding-left: 0px;
+ padding-right: 0px;
+
+}
+
+#idle_inhibitor{
+color: #24966e;
+background: @bar-bg;
+ opacity: 1;
+ margin: 4px 0px 4px 0px;
+ padding-left: 4px;
+ padding-right: 4px;
+
+}
+#clock{
+ color: #FDD870;
+ font-size: 15px;
+ font-weight: 900;
+ font-family: "JetBrains Mono Nerd Font";
+ background: rgba(23, 23, 23, 0.0);
+ opacity: 1;
+ margin: 3px 0px 0px 0px;
+ padding-left: 10px;
+ padding-right: 10px;
+ border: none;
+
+}
+#pulseaudio{
+font-weight: normal;
+font-size: 18px;
+color: #0A9CF5;
+ background: rgba(22, 19, 32, 0.0);
+ opacity: 1;
+ margin: 0px 0px 0px 0px;
+ padding-left: 3px;
+ padding-right: 3px;
+}
+#cpu{
+font-weight: normal;
+font-size: 22px;
+color: #FF184C;
+}
+#custom-led{
+background: #427287;
+color: #FFFFFF;
+margin-top: 7px;
+margin-bottom: 7px;
+padding-left: 6px;
+border-radius: 7px;
+margin-right: 6px;
+}
+#custom-gpuinfo,
+#custom-keybindhint,
+#language,
+#memory{
+font-weight: normal;
+font-size: 22px;
+color: #1AFE49;
+}
+#mpris{
+color: white;
+animation: repeat;
+ animation-name: blink;
+ animation-duration: 3s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+ animation-direction: alternate;
+}
+
+@keyframes blink {
+ to {
+ color: #4a4a4a;
+
+ }
+}
+#network{
+color: #FF6E27;
+font-weight: normal;
+font-size: 19px;
+padding-right: 0px;
+padding-left: 4px
+}
+#custom-notifications,
+#custom-spotify,
+#taskbar,
+#custom-theme,
+#custom-menu{
+color: #E8EDF0;
+background: rgba(23, 23, 23, 0.0);
+margin: 0px 0px 0px 0px;
+ padding-left: 1px;
+ padding-right: 1px;
+ opacity: 0.1
+}
+#tray,
+#custom-updates,
+#custom-wallchange,
+#custom-wbar,
+#window{
+color: #A1BDCE;
+font-family: "Martian Mono";
+}
+#custom-l_end,
+#custom-r_end,
+#custom-sl_end,
+#custom-sr_end,
+#custom-rl_end,
+#cava,
+#upower#headset,
+#upower{
+color: #a6d189;
+}
+#mpris{
+font-size: 15px;
+font-weight: bold
+}
+#custom-rr_end {
+font-weight: normal;
+ color: #E8EDF0;
+ background: rgba(23, 23, 23, 0.0);
+ opacity: 1;
+ margin: 0px 0px 0px 0px;
+ padding-left: 4px;
+ padding-right: 4px;
+ ;
+
+}
+
+#backlight-slider slider,
+#pulseaudio-slider slider {
+ background: #A1BDCE;
+ background-color: transparent;
+ box-shadow: none;
+ margin-right: 7px;
+}
+
+#backlight-slider trough,
+#pulseaudio-slider trough {
+ margin-top: -3px;
+ min-width: 90px;
+ min-height: 10px;
+ margin-bottom: -4px;
+ border-radius: 8px;
+ background: #343434;
+}
+
+#backlight-slider highlight,
+#pulseaudio-slider highlight {
+ border-radius: 8px;
+ background-color: #2096C0;
+}
+
+#battery.charging, #battery.plugged {
+ color: #E8EDF0;
+
+}
+
+
+#battery.critical:not(.charging) {
+ color: red;
+}
+
+
+#taskbar {
+ padding: 1px;
+}
+
+#custom-r_end {
+ border-radius: 0px 7px 7px 0px;
+ margin-right: 1px;
+ padding-right: 3px;
+}
+
+#custom-l_end {
+ border-radius: 7px 0px 0px 7px;
+ margin-left: 1px;
+ padding-left: 3px;
+}
+
+#custom-sr_end {
+ border-radius: 0px;
+ margin-right: 1px;
+ padding-right: 3px;
+}
+
+#custom-sl_end {
+ border-radius: 0px;
+ margin-left: 1px;
+ padding-left: 3px;
+}
+
+#custom-rr_end {
+ border-radius: 0px 7px 7px 0px;
+ margin-right: 1px;
+ padding-right: 3px;
+}
+
+#custom-rl_end {
+ border-radius: 7px 0px 0px 7px;
+ margin-left: 1px;
+ padding-left: 3px;
+}
+
+
+/* Style for launchers */
+
+#custom-expand {
+ min-width: 25px;
+ color: #A1BDCE;
+ font-size: 16px;
+}
+
+#group-minimized {
+ border-left: solid;
+ border-left-width: 0.5;
+}
+
+#custom-quote {
+ padding-top:0px;
+ color: #999999;
+ font-family: "JetBrains Mono Nerd Font";
+ font-size: 13px;
+}
diff --git a/home/waybar/config/weather.py b/home/waybar/config/weather.py
new file mode 100755
index 0000000..5dcf4b2
--- /dev/null
+++ b/home/waybar/config/weather.py
@@ -0,0 +1,528 @@
+#!/usr/bin/env python3
+
+import datetime
+import json
+import requests
+import statistics
+import sys
+
+
+
+# TODO
+# - snowfall data
+# - weather warnings
+
+
+
+### CONSTANTS ###
+
+# api key - get it at https://openweathermap.org/
+API_KEY = "bfca28ca110b5595380d9aad164d5ee5"
+
+# latitude and longitude of the city you want to query
+# can be obtained through `./weather.py geocoding `
+LATITUDE = 52.264149
+LONGITUDE = 10.526420
+
+# waybar colors
+GRAY = '#b7c1de'
+DARK = '#01012b'
+GREEN = '#65dc98'
+YELLOW = '#ffe69d'
+ORANGE = '#ff6e27'
+RED = '#ff124f'
+PURPLE = '#7a04eb'
+BLUE = '#8386f5'
+MOIST = '#e96d5e'
+
+
+### UTILITIES ###
+
+def print_error(msg: str):
+ '''
+ print an error message with appropriate prefix.
+
+ @param str: the message to print
+ '''
+ print('\x1b[90m[\x1b[31merr\x1b[90m]\x1b[0m', msg)
+
+
+def print_help():
+ '''
+ print help message, to be used for the `--help` flag and as response to
+ incorrect usage
+ '''
+ print('usage: \x1b[33m./weather.py [options]\x1b[0m')
+ print()
+ print('available subcommands:')
+ print(' - \x1b[32mgeocoding\x1b[0m : search for city to get its coordinates')
+ print(' - \x1b[32mcurrent\x1b[0m : print current weather information')
+ print(' - \x1b[32mforecast[-daily]\x1b[0m : print forecast for the next ~5 days')
+ print(' - \x1b[32mforecast-detail\x1b[0m : print detailed forecast in 3h intervals')
+ print(' - \x1b[32mwaybar\x1b[0m : get output for usage with waybar')
+
+
+def make_request(call: str) -> list | dict:
+ '''
+ make a request to an openweathermap api and returns the response as either
+ a list or a dictionary. the api key is added automatically. quits on error.
+
+ @param call: the api path, e.g. `data/2.5/weather?...`
+
+ @return response of the api request as a list or dict
+ '''
+ try:
+ req = requests.get(f'https://api.openweathermap.org/{call}&appid={API_KEY}')
+ return req.json()
+ except:
+ print_error(f'failed to make request to `/{call}`')
+ quit(1)
+
+
+def get_wind_direction(deg: int) -> str:
+ '''
+ turn a wind direction specified by meteorological degrees into a
+ human-readable form
+
+ @param deg: degrees. expected to be in range [0..360]
+
+ @return human-readable form (e.g. 'NE' for `deg == 45`)
+ '''
+ if deg < 22.5: return 'N'
+ if deg < 67.5: return 'NE'
+ if deg < 112.5: return 'E'
+ if deg < 157.5: return 'SE'
+ if deg < 202.5: return 'S'
+ if deg < 247.5: return 'SW'
+ if deg < 292.5: return 'W'
+ if deg < 337.5: return 'NW'
+ else: return 'N'
+
+
+def print_entry(label: str, content: str, indent: int = 0, label_width: int = 8):
+ '''
+ print an 'entry' that consists of a label (printed in gray) and some
+ content. the labels are automatically filled with whitespace to align
+ multiple lines properly.
+
+ @param label: the label of the line
+ @param content: content, printed after the label
+ @param indent: number of spaces to indent with
+ @param label_width: width of label
+ '''
+ label_with_whitespace = label.ljust(label_width)
+ print(f'{" " * indent}\x1b[90m{label_with_whitespace}\x1b[0m {content}')
+
+
+def get_weekday(date: str) -> str:
+ '''
+ get weekday from date
+
+ @param date: date as ISO-8601-formatted string (YYYY-mm-dd)
+
+ @return weekday as lowercase string (e.g. 'monday')
+ '''
+ return datetime.datetime.strptime(date, '%Y-%m-%d').strftime('%A').lower()
+
+
+def colorize(text: str, color: str) -> str:
+ '''
+ wrap `text` with pango markup to colorize it for usage with waybar.
+
+ @param text: the text to colorize
+ @param color: the color as string ('#rrggbb')
+ '''
+ return f'{text}'
+
+
+def waybar_entry(label: str, content: str, indent: int = 2, label_width: int = 9):
+ '''
+ create an 'entry' for a waybar tooltip that consists of a label (printed in
+ gray) and some content. the labels are automatically filled with whitespace
+ to align multiple lines properly.
+
+ @param label: the label of the line
+ @param content: content, printed after the label
+ @param indent: number of spaces to indent with
+ @param label_width: width of label
+
+ @return the entry for use within waybar
+ '''
+
+ label_with_whitespace = label.ljust(label_width)
+ return f'{" " * indent}{colorize(label_with_whitespace, GRAY)} {content}\n'
+
+
+
+### GEOCODING ###
+
+def geocoding(search: str):
+ '''
+ call openweathermap's geocoding api to find the coordinates of cities. can
+ be used to find the values required for the `LATITUDE` and `LONGITUDE`
+ constants within this script. the results are printed.
+
+ @param: search term in the format 'city[,state][,country]'
+ '''
+
+ res = make_request(f'geo/1.0/direct?q={search}&limit=5')
+ num_results = len(res)
+
+ if (num_results == 0):
+ print_error('no results found')
+ else:
+ print(f'found {num_results} result{"" if num_results == 1 else "s"}:')
+ for entry in res:
+ # obtain data
+ name = entry['name']
+ state = entry['state'] if 'state' in entry.keys() else None
+ country = entry['country']
+ latitude = entry['lat']
+ longitude = entry['lon']
+
+ # print data
+ print(f' - \x1b[32m{name}\x1b[90m',
+ f'({f"{state}, " if state else ""}{country})\x1b[0m:',
+ f'latitude = \x1b[35m{latitude}\x1b[0m,',
+ f'longitude = \x1b[35m{longitude}\x1b[0m')
+
+
+### CURRENT WEATHER ###
+
+def get_current_weather_data() -> dict:
+ '''
+ get current weather data from openweathermap api. when the `rain` is not
+ set (due to there not being any rain), it will be added with a rain amount
+ of 0 mm over the last hour.
+
+ @return api response
+ '''
+
+ res = make_request(f'data/2.5/weather?lat={LATITUDE}&lon={LONGITUDE}&units=metric')
+
+ if 'rain' not in res.keys():
+ res['rain'] = { '1h': 0 }
+
+ return res
+
+
+def current_weather():
+ '''
+ print current weather information
+ '''
+
+ data = get_current_weather_data()
+
+ # collect relevant data
+ weather = data['weather'][0]['description'].lower()
+ temperature = round(data['main']['temp'], 1)
+ temperature_felt = round(data['main']['feels_like'], 1)
+ humidity = data['main']['humidity']
+ wind_speed = round(data['wind']['speed'], 1)
+ wind_direction = get_wind_direction(data['wind']['deg'])
+ rainfall = data['rain']['1h']
+
+ # print data
+ print_entry('weather', f'\x1b[32m{weather}\x1b[0m')
+ print_entry('temp', f'\x1b[33m{temperature} °C\x1b[90m, feels like \x1b[33m{temperature_felt} °C\x1b[0m')
+ print_entry('humidity', f'\x1b[31m{humidity} % RH\x1b[0m')
+ print_entry('wind', f'\x1b[35m{wind_speed} m/s\x1b[90m ({wind_direction})\x1b[0m')
+ print_entry('rain', f'\x1b[34m{rainfall} mm\x1b[0m')
+
+
+
+### FORECAST ###
+
+def get_forecast_data() -> dict:
+ '''
+ get forecast data for the next ~5 days from the openweathermap api, with
+ data points separated by 3 hours. the data is grouped by date (the api does
+ not group the data by default and instead sends it as one sequence).
+
+ @return api response grouped by date
+ '''
+
+ res = make_request(f'data/2.5/forecast?lat={LATITUDE}&lon={LONGITUDE}&units=metric')
+
+ days = dict()
+ for i in res['list']:
+ day = i['dt_txt'].split(' ')[0]
+ if day not in days.keys():
+ days[day] = list()
+ if 'rain' not in i.keys():
+ i['rain'] = { '3h': 0 }
+ days[day].append(i)
+
+ return days
+
+
+def get_daily_forecast_data() -> dict[dict]:
+ '''
+ obtain forecast data for the next ~5 days, where values are grouped by day.
+ since the api only provides the data in 3h intervals, the properties of
+ different data points are combined in order to provide appropriate data for
+ each day.
+
+ @return the processed data as a dict with key = date and value = data as
+ another dict
+ '''
+
+ res = get_forecast_data()
+
+ output = dict()
+
+ for day in sorted(res):
+ data = res[day]
+ number_of_data_points = len(data)
+
+ # collect relevant from data for each day
+ temperatures = [i['main']['temp'] for i in data]
+ weather_descriptions = [i['weather'][0]['description'].lower() for i in data]
+ humidity = [i['main']['humidity'] for i in data]
+ rainfall = [i['rain']['3h'] for i in data]
+ precipitation_prob = [i['pop'] for i in data]
+ wind_speeds = [i['wind']['speed'] for i in data]
+ weekday = get_weekday(day)
+
+ # min and max temperature for the day
+ min_temperature = round(min(temperatures))
+ max_temperature = round(max(temperatures))
+
+ # obtain the average weather by finding the weather description with the highest number of occurances in `weather_descriptions`
+ weather_count = {i: weather_descriptions.count(i) for i in set(weather_descriptions)}
+ weather_count_max = max(weather_count.values())
+ weather_average = tuple(filter(lambda x: weather_count[x] == weather_count_max, weather_count.keys()))[0]
+
+ # humidiy
+ humidity_average = round(statistics.mean(humidity))
+
+ # total rainfall and probability of precipitation. also, estimate the total rainfall if not all data points for a day are available
+ rainfall_total = round(sum(rainfall), 1)
+ rainfall_total_estimated = round(rainfall_total / number_of_data_points * 8, 1)
+ max_precipitation_prob = round(max(precipitation_prob) * 100)
+
+ # average wind
+ wind_average = round(statistics.mean(wind_speeds), 1)
+
+ output[day] = {
+ 'number_of_data_points': number_of_data_points,
+ 'min_temperature': min_temperature,
+ 'max_temperature': max_temperature,
+ 'weather_average': weather_average,
+ 'humidity_average': humidity_average,
+ 'rainfall_total': rainfall_total,
+ 'rainfall_total_estimated': rainfall_total_estimated,
+ 'max_precipitation_prob': max_precipitation_prob,
+ 'wind_average': wind_average,
+ 'weekday': weekday,
+ }
+
+ return output
+
+
+def daily_forecast():
+ '''
+ print forecast data for the next ~5 days
+ '''
+
+ daily_data = get_daily_forecast_data()
+
+ for day in sorted(daily_data):
+ data = daily_data[day]
+
+ # only display data point if at least half of the data points are available
+ if data['number_of_data_points'] < 4:
+ continue
+
+ # print data
+ print(f'\x1b[1m{day}\x1b[0m ({data["weekday"]}):')
+ print_entry('weather', f'\x1b[32m{data["weather_average"]}\x1b[0m', indent = 2)
+ print_entry('temp', f'\x1b[33m{data["max_temperature"]} °C\x1b[0m / \x1b[33m{data["min_temperature"]} °C\x1b[0m', indent = 2)
+ print_entry('humidity', f'\x1b[31m{data["humidity_average"]} % RH\x1b[0m', indent = 2)
+ print_entry('wind', f'\x1b[35m{data["wind_average"]} m/s\x1b[0m', indent = 2)
+ print_entry('rain', f'\x1b[34m{data["rainfall_total_estimated"]} mm\x1b[0m' +
+ f'\x1b[90m{f""" ({data["max_precipitation_prob"]}%)""" if data["max_precipitation_prob"] > 0 else ""}' +
+ f'{f" (estimated)" if data["rainfall_total"] != data["rainfall_total_estimated"] else ""}\x1b[0m\n', indent = 2)
+
+
+
+### DETAILED FORECAST ###
+
+def detailed_forecast():
+ '''
+ print forecast data for the next ~5 days, where values are printed for
+ every 3h interval provided by the api. some data (e.g. humidity or wind
+ speeds) are omitted.
+ '''
+
+ res = get_forecast_data()
+
+ for day in sorted(res):
+ weekday = get_weekday(day)
+ print(f'\x1b[1m{day}\x1b[0m ({weekday})')
+
+ for entry in res[day]:
+ # collect data
+ weather = entry['weather'][0]['description'].lower()
+ temperature = round(entry['main']['temp'], 1)
+ rainfall = entry['rain']['3h']
+ precipitation_prob = round(entry['pop'] * 100)
+ time = f"{int(entry['dt_txt'].split(' ')[1].split(':')[0]):2}h"
+
+ # print data
+ output = ''
+ output += f'\x1b[33m{temperature:4} °C\x1b[90m, '
+ output += f'\x1b[32m{weather}\x1b[0m'
+ if rainfall > 0:
+ output += f'\x1b[90m: \x1b[34m{rainfall} mm \x1b[90m({precipitation_prob}%)\x1b[0m'
+ print_entry(time, output, indent = 2, label_width = 3)
+
+ print()
+
+
+
+### WAYBAR ###
+
+def waybar_widget(data: dict) -> str:
+ '''
+ get the widget component of the waybar output. contains the current weather
+ group and temperature.
+
+ @param current weather data
+
+ @return widget component
+ '''
+
+ weather = data['weather'][0]['main'].lower()
+ temperature = round(data['main']['temp'])
+
+ return f'{colorize(weather, MOIST)} {temperature}°'
+
+
+def waybar_current(data: dict) -> str:
+ '''
+ get the current weather overview for the tooltip of the waybar output.
+
+ @param current weather data
+
+ @return formatted current weather overview
+ '''
+
+ # retrieve relevant data
+ weather = data['weather'][0]['description'].lower()
+ temperature = round(data['main']['temp'], 1)
+ temperature_felt = round(data['main']['feels_like'], 1)
+ humidity = data['main']['humidity']
+ wind_speed = round(data['wind']['speed'], 1)
+ wind_direction = get_wind_direction(data['wind']['deg'])
+ rainfall = data['rain']['1h']
+
+ # generate output
+ output = ''
+ output += waybar_entry('weather', colorize(weather, YELLOW))
+ output += waybar_entry('temp', f'{colorize(f"{temperature} °C", ORANGE)}{colorize(", feels like ", GRAY)}{colorize(f"{temperature_felt} °C", ORANGE)}')
+ output += waybar_entry('humidity', colorize(f'{humidity} % RH', RED))
+ output += waybar_entry('wind', f'{colorize(f"{wind_speed} m/s", PURPLE)} {colorize(f"({wind_direction})", GRAY)}')
+ output += waybar_entry('rain', colorize(f"{rainfall} mm", BLUE))
+ return output
+
+
+def waybar_forecast(data: dict) -> str:
+ '''
+ get the daily forecast for the tooltip of the waybar output.
+
+ @param forecast weather data
+
+ @return formatted daily forecast
+ '''
+
+ output = ''
+
+ daily_data = get_daily_forecast_data()
+
+ for day in sorted(daily_data):
+ data = daily_data[day]
+
+ line_content = colorize(f'{data["max_temperature"]:2}°', ORANGE) + \
+ colorize(' / ', GRAY) + \
+ colorize(f'{data["min_temperature"]:2}°', ORANGE) + \
+ colorize(', ', GRAY) + \
+ colorize(data['weather_average'], YELLOW)
+
+ if data['rainfall_total_estimated'] > 0:
+ line_content += colorize(': ', GRAY) + \
+ colorize(f'{data["rainfall_total_estimated"]} mm ', BLUE) + \
+ colorize(f'({data["max_precipitation_prob"]}%)', GRAY)
+
+ output += waybar_entry(data['weekday'], line_content)
+
+ return output.rstrip()
+
+
+def waybar():
+ '''
+ get current and forecast weather data and output it formatted in a way that
+ allows it to be included as a widget in waybar. only shows weather category
+ and temperature in the widget, but reveals detailed weather information and
+ a ~5 day forecast in the tooltip.
+ '''
+
+ current_data = get_current_weather_data()
+ forecast_data = get_forecast_data()
+
+ widget = waybar_widget(current_data)
+ current = waybar_current(current_data)
+ forecast = waybar_forecast(forecast_data)
+ tooltip = colorize('current weather', GREEN) + '\n' + current + '\n' + \
+ colorize('forecast', GREEN) + '\n' + forecast
+
+ print(json.dumps({
+ 'text': widget,
+ 'tooltip': tooltip,
+ }))
+
+
+
+### MAIN ###
+
+def main():
+ '''
+ main function
+ '''
+
+ # no parameters or `--help`
+ if len(sys.argv) == 1 or sys.argv[1] in ('help', '-h', '--help'):
+ print_help()
+
+ # >= 1 parameter provided
+ else:
+ # geocoding
+ if sys.argv[1] == 'geocoding':
+ if len(sys.argv) == 3:
+ geocoding(sys.argv[2])
+ else:
+ print_error('expected argument ``')
+ print_help()
+ # constants not set
+ elif API_KEY is None or LATITUDE is None or LONGITUDE is None:
+ print_error('please modify the constants within the script before use.')
+ # current weather
+ elif sys.argv[1] == 'current':
+ current_weather()
+ # daily forecast
+ elif sys.argv[1] == 'forecast' or sys.argv[1] == 'forecast-daily':
+ daily_forecast()
+ # detailed forecast
+ elif sys.argv[1] == 'forecast-detail':
+ detailed_forecast()
+ # waybar
+ elif sys.argv[1] == 'waybar':
+ waybar()
+ # unknown command
+ else:
+ print_error(f'unknown command `{sys.argv[1]}`')
+ print_help()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/home/waybar/default.nix b/home/waybar/default.nix
new file mode 100644
index 0000000..341031e
--- /dev/null
+++ b/home/waybar/default.nix
@@ -0,0 +1,16 @@
+{
+ pkgs,
+ config,
+ ...
+}: {
+ programs.waybar = {
+ enable = true;
+ package = pkgs.waybar;
+ };
+
+ home.file.".config/waybar" = {
+ source = ./configs;
+ # copy the scripts directory recursively
+ recursive = true;
+ };
+}
diff --git a/users/phil/home.nix b/users/phil/home.nix
index e9a9c00..194bce7 100644
--- a/users/phil/home.nix
+++ b/users/phil/home.nix
@@ -10,7 +10,6 @@
../../home/hyprland
../../home/programs
- ../../home/rofi
];
programs.git = {