From 51b4b123ddc1a3cd6dd2ae0904f65880a89ced55 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Wed, 23 Sep 2026 00:44:36 +0500 Subject: [PATCH] add waybar and notifcaitons --- config/sway/config | 8 +- config/swaync/config.json | 54 ++ config/swaync/style.css | 218 ++++++++ config/waybar/config | 576 ++++++++++++++++++++ config/waybar/scripts/mediaplayer.py | 68 +++ config/waybar/scripts/monitor-brightness.sh | 70 +++ config/waybar/scripts/power-menu.sh | 44 ++ config/waybar/scripts/vpn-check.sh | 68 +++ config/waybar/scripts/weather.sh | 26 + config/waybar/style.css | 329 +++++++++++ home/packages.nix | 2 + home/shihaam.nix | 2 + home/swaync.nix | 29 + home/waybar.nix | 10 + 14 files changed, 1503 insertions(+), 1 deletion(-) create mode 100644 config/swaync/config.json create mode 100644 config/swaync/style.css create mode 100644 config/waybar/config create mode 100755 config/waybar/scripts/mediaplayer.py create mode 100755 config/waybar/scripts/monitor-brightness.sh create mode 100755 config/waybar/scripts/power-menu.sh create mode 100755 config/waybar/scripts/vpn-check.sh create mode 100755 config/waybar/scripts/weather.sh create mode 100644 config/waybar/style.css create mode 100644 home/swaync.nix create mode 100644 home/waybar.nix diff --git a/config/sway/config b/config/sway/config index 9b1bf27..ade0963 100644 --- a/config/sway/config +++ b/config/sway/config @@ -129,7 +129,7 @@ exec dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DE exec blueman-applet exec wl-paste -t text --watch clipman store # Clipboard manager -exec mako # Notification daemon +exec swaync # Notification daemon (see home/swaync.nix) exec nm-applet # Network Manager applet exec kdeconnect-indicator @@ -157,4 +157,10 @@ exec xfce4-power-manager #exec /usr/bin/uxplay -avdec bindsym $mod+l exec /home/shihaam/scripts/lock.sh +# Notification centre: $mod+n opens the panel (past notifications, mpris, +# DND toggle), $mod+Shift+n flips do-not-disturb. -sw closes it again on the +# second press instead of stacking. +bindsym $mod+n exec swaync-client -t -sw +bindsym $mod+Shift+n exec swaync-client -d -sw + exec_always export QT_WAYLAND_DISABLE_WINDOWDECORATION=1 diff --git a/config/swaync/config.json b/config/swaync/config.json new file mode 100644 index 0000000..a977c2b --- /dev/null +++ b/config/swaync/config.json @@ -0,0 +1,54 @@ +{ + "positionX": "right", + "positionY": "top", + "layer": "overlay", + "control-center-layer": "top", + "layer-shell": true, + "cssPriority": "user", + + "notification-window-width": 420, + "notification-icon-size": 48, + "notification-body-image-height": 160, + "notification-body-image-width": 200, + "image-visibility": "when-available", + "transition-time": 200, + + "timeout": 8, + "timeout-low": 4, + "timeout-critical": 0, + + "control-center-width": 440, + "control-center-height": 700, + "control-center-margin-top": 8, + "control-center-margin-bottom": 8, + "control-center-margin-right": 8, + "control-center-margin-left": 0, + "fit-to-screen": true, + + "keyboard-shortcuts": true, + "hide-on-clear": false, + "hide-on-action": true, + "script-fail-notify": true, + + "widgets": [ + "title", + "dnd", + "mpris", + "notifications" + ], + + "widget-config": { + "title": { + "text": "Notifications", + "clear-all-button": true, + "button-text": "Clear all" + }, + "dnd": { + "text": "Do not disturb" + }, + "mpris": { + "image-size": 88, + "image-radius": 8 + } + } +} diff --git a/config/swaync/style.css b/config/swaync/style.css new file mode 100644 index 0000000..1d153d4 --- /dev/null +++ b/config/swaync/style.css @@ -0,0 +1,218 @@ +/* Material Design SwayNC theme -- same palette as config/waybar/style.css: + #1E1E1E background, #2D2D2D surface, #E1E1E1 text, #90CAF9 accent, + #CF6679 critical. */ + +* { + font-family: "Roboto", "JetBrainsMono Nerd Font", "Material Design Icons", sans-serif; + font-size: 14px; +} + +/* ============= Popup notifications ============= */ + +/* The toplevel window must stay transparent or the whole layer surface paints + a rectangle around the rounded cards. */ +.control-center, +.notification-row, +.notification-background, +.floating-notifications.background { + background: transparent; +} + +.notification { + margin: 8px; + padding: 0; + border-radius: 12px; + background: #2D2D2D; + color: #E1E1E1; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4); +} + +.notification-content { + padding: 12px; +} + +.summary { + font-size: 14px; + font-weight: 500; + color: #E1E1E1; +} + +.time { + font-size: 12px; + color: #B0B0B0; +} + +.body { + font-size: 13px; + color: #B0B0B0; +} + +.notification.critical { + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4), inset 3px 0 0 #CF6679; +} + +.notification.low .summary { + color: #B0B0B0; +} + +/* ============= Action buttons ============= + What bluetooth pairing prompts, Nextcloud-style "Accept/Reject" and browser + notifications render into. .notification-action is one button per action; + the first child gets the left rounding, the last the right. */ + +.notification-action { + margin: 0; + padding: 8px 12px; + border-top: 1px solid #383838; + background: #2D2D2D; + color: #90CAF9; + font-weight: 500; + transition: background 200ms cubic-bezier(0.4, 0, 0.2, 1); +} + +.notification-action:hover { + background: rgba(144, 202, 249, 0.12); + color: #90CAF9; +} + +.notification-action:active { + background: rgba(144, 202, 249, 0.2); +} + +.notification-action:first-child { + border-radius: 0 0 0 12px; +} + +.notification-action:last-child { + border-radius: 0 0 12px 0; + border-right: none; +} + +.notification-default-action { + border-radius: 12px; +} + +/* Rounding only collapses on the bottom when action buttons sit below. */ +.notification-default-action:not(:only-child) { + border-radius: 12px 12px 0 0; +} + +.close-button { + margin: 6px; + padding: 0; + min-width: 24px; + min-height: 24px; + border-radius: 12px; + background: transparent; + color: #B0B0B0; + box-shadow: none; +} + +.close-button:hover { + background: #CF6679; + color: #1E1E1E; +} + +.inline-reply { + margin-top: 8px; +} + +.inline-reply-entry { + padding: 6px 8px; + border: 1px solid #383838; + border-radius: 6px; + background: #1E1E1E; + color: #E1E1E1; +} + +.inline-reply-button { + margin-left: 4px; + padding: 6px 12px; + border: none; + border-radius: 6px; + background: #90CAF9; + color: #1E1E1E; +} + +/* ============= Control center ============= */ + +.control-center { + margin: 8px; + padding: 8px; + border-radius: 12px; + background: #1E1E1E; + color: #E1E1E1; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); +} + +.control-center .notification { + margin: 4px 0; +} + +.widget-title { + margin: 8px 8px 4px 8px; + font-size: 15px; + font-weight: 500; + color: #E1E1E1; +} + +.widget-title > button { + padding: 4px 12px; + border: none; + border-radius: 6px; + background: #2D2D2D; + color: #90CAF9; + font-size: 13px; +} + +.widget-title > button:hover { + background: rgba(144, 202, 249, 0.12); +} + +.widget-dnd { + margin: 4px 8px 8px 8px; + font-size: 13px; + color: #B0B0B0; +} + +.widget-dnd > switch { + border-radius: 12px; + background: #383838; + box-shadow: none; +} + +.widget-dnd > switch:checked { + background: #90CAF9; +} + +.widget-dnd > switch slider { + border-radius: 12px; + background: #E1E1E1; +} + +.widget-mpris { + margin: 4px 0; + padding: 8px; + border-radius: 12px; + background: #2D2D2D; + color: #E1E1E1; +} + +.widget-mpris-player { + padding: 8px; +} + +.widget-mpris-title { + font-size: 14px; + font-weight: 500; +} + +.widget-mpris-subtitle { + font-size: 12px; + color: #B0B0B0; +} + +.widget-mpris button:hover { + background: rgba(144, 202, 249, 0.12); + color: #90CAF9; +} diff --git a/config/waybar/config b/config/waybar/config new file mode 100644 index 0000000..282bf2d --- /dev/null +++ b/config/waybar/config @@ -0,0 +1,576 @@ +[ + { + "output": "DP-1", + "position": "bottom", + "height": 42, + "layer": "top", + "spacing": 4, + + "modules-left": [ + "sway/workspaces", + "sway/mode", + "custom/media" + ], + + "modules-center": [ + "sway/window" + ], + + "modules-right": [ + "idle_inhibitor", + "privacy", + "custom/updates", + "custom/weather", + "bluetooth", + "network", + "custom/vpn", + "pulseaudio", + "custom/monitor-brightness", + "cpu", + "memory", + "temperature", + "battery", + "custom/ibus", + "tray", + "clock", + "custom/power" + ], + + "sway/workspaces": { + "disable-scroll": false, + "all-outputs": false, + "format": "{name}" + }, + + "sway/mode": { + "format": " {}", + "tooltip": false + }, + + "sway/window": { + "format": "{}", + "max-length": 50, + "icon": true, + "icon-size": 16, + "rewrite": { + "(.*) — Mozilla Firefox": "󰈹 $1", + "(.*) - Visual Studio Code": "ó°Øž $1", + "(.*) - vim": " $1", + "(.*) - zsh": " $1" + } + }, + + "custom/media": { + "format": " {}", + "return-type": "json", + "max-length": 40, + "escape": true, + "exec": "$HOME/.config/waybar/scripts/mediaplayer.py 2> /dev/null", + "on-click": "playerctl play-pause", + "on-click-right": "playerctl next", + "on-scroll-up": "playerctl previous", + "on-scroll-down": "playerctl next" + }, + + "clock": { + "interval": 1, + "format": "{:%H:%M:%S}", + "format-alt": "{:%A, %B %d, %Y (%R)}", + "tooltip-format": "{calendar}", + "calendar": { + "mode": "year", + "mode-mon-col": 3, + "weeks-pos": "right", + "on-scroll": 1, + "on-click-right": "mode", + "format": { + "months": "{}", + "days": "{}", + "weeks": "W{}", + "weekdays": "{}", + "today": "{}" + } + }, + "actions": { + "on-click-right": "mode", + "on-scroll-up": "shift_up", + "on-scroll-down": "shift_down" + } + }, + + "idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + }, + "tooltip-format-activated": "Idle inhibitor: ON", + "tooltip-format-deactivated": "Idle inhibitor: OFF" + }, + + "privacy": { + "icon-spacing": 4, + "icon-size": 18, + "transition-duration": 250, + "modules": [ + { + "type": "screenshare", + "tooltip": true, + "tooltip-icon-size": 24 + }, + { + "type": "audio-in", + "tooltip": true, + "tooltip-icon-size": 24 + } + ] + }, + + "custom/updates": { + "format": "{} ", + "interval": 3600, + "exec": "checkupdates 2>/dev/null | wc -l", + "exec-if": "exit 0", + "on-click": "alacritty -e paru -Syu", + "tooltip": true, + "tooltip-format": "{} pending updates" + }, + + "custom/weather": { + "format": "{}", + "tooltip": true, + "interval": 1800, + "exec": "$HOME/.config/waybar/scripts/weather.sh", + "return-type": "json", + "on-click": "xdg-open https://weather.com" + }, + + "bluetooth": { + "format": " {status}", + "format-disabled": "", + "format-connected": " {num_connections}", + "tooltip-format": "{controller_alias}\t{controller_address}", + "tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{device_enumerate}", + "tooltip-format-enumerate-connected": "{device_alias}\t{device_address}", + "on-click": "blueman-manager" + }, + + "network": { + "interval": 5, + "format-wifi": "šŸ“¶ {signalStrength}%", + "format-ethernet": "šŸ”Œ {ipaddr}", + "format-linked": "šŸ”Œ {ifname} (No IP)", + "format-disconnected": "āŒ Disconnected", + "tooltip-format": "{ifname}: {ipaddr}/{cidr}\nGateway: {gwaddr}\nStrength: {signalStrength}%\nFrequency: {frequency}MHz\nUp: {bandwidthUpBits} Down: {bandwidthDownBits}", + "tooltip-format-wifi": "šŸ“¶ {essid} ({signalStrength}%)\n{ipaddr}/{cidr}\nGateway: {gwaddr}\nFrequency: {frequency}MHz\nUp: {bandwidthUpBits} Down: {bandwidthDownBits}", + "tooltip-format-ethernet": "šŸ”Œ {ifname}\n{ipaddr}/{cidr}\nGateway: {gwaddr}\nUp: {bandwidthUpBits} Down: {bandwidthDownBits}", + "on-click": "nm-connection-editor", + "on-click-right": "alacritty -e nmtui" + }, + + "custom/vpn": { + "format": "{}", + "interval": 5, + "exec": "$HOME/.config/waybar/scripts/vpn-check.sh", + "return-type": "json", + "on-click": "nm-connection-editor", + "tooltip": true + }, + + "pulseaudio": { + "scroll-step": 5, + "format": "{icon} {volume}%", + "format-bluetooth": "{icon} {volume}%", + "format-bluetooth-muted": " {icon}", + "format-muted": " {volume}%", + "format-icons": { + "headphone": "", + "hands-free": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] + }, + "on-click": "if pgrep -x pavucontrol > /dev/null; then pkill pavucontrol; else pavucontrol; fi", + "on-click-right": "pactl set-sink-mute @DEFAULT_SINK@ toggle", + "on-scroll-up": "pactl set-sink-volume @DEFAULT_SINK@ +5%", + "on-scroll-down": "pactl set-sink-volume @DEFAULT_SINK@ -5%", + "tooltip-format": "{desc}\nVolume: {volume}%" + }, + + "custom/monitor-brightness": { + "format": " {}%", + "interval": 2, + "exec": "$HOME/.config/waybar/scripts/monitor-brightness.sh get", + "on-click": "gddccontrol", + "on-scroll-up": "$HOME/.config/waybar/scripts/monitor-brightness.sh up && pkill -RTMIN+10 waybar", + "on-scroll-down": "$HOME/.config/waybar/scripts/monitor-brightness.sh down && pkill -RTMIN+10 waybar", + "signal": 10, + "tooltip": true, + "tooltip-format": "Monitor Brightness: {}%" + }, + + "cpu": { + "interval": 2, + "format": " {usage}%", + "max-length": 10, + "on-click": "alacritty -e htop", + "tooltip": true + }, + + "memory": { + "interval": 5, + "format": " {}%", + "tooltip-format": "RAM: {used:0.1f}G / {total:0.1f}G\nSwap: {swapUsed:0.1f}G / {swapTotal:0.1f}G", + "on-click": "alacritty -e htop" + }, + + "temperature": { + "thermal-zone": 2, + "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input", + "critical-threshold": 80, + "interval": 5, + "format": "{icon} {temperatureC}°C", + "format-icons": ["", "", "", "", ""], + "tooltip": true + }, + + "battery": { + "states": { + "good": 95, + "warning": 30, + "critical": 15 + }, + "interval": 30, + "format": "{icon} {capacity}%", + "format-charging": " {capacity}%", + "format-plugged": " {capacity}%", + "format-alt": "{icon} {time}", + "format-icons": ["", "", "", "", ""], + "tooltip-format": "{timeTo}\nCapacity: {capacity}%\nHealth: {health}%\nPower: {power}W" + }, + + "custom/ibus": { + "exec": "ibus engine", + "interval": 1, + "format": "⌨ {}", + "on-click": "ibus engine Thaana", + "tooltip-format": "Current keyboard: {}" + }, + + "tray": { + "icon-size": 18, + "spacing": 8, + "show-passive-items": true + }, + + "custom/power": { + "format": "ā»", + "on-click": "$HOME/.config/waybar/scripts/power-menu.sh", + "tooltip": false + } + }, + { + "output": ["DP-2", "DP-3", "DP-4", "DP-5", "DP-6", "DP-7", "DP-8", "HDMI-A-1", "HDMI-A-2", "HDMI-A-3"], + "position": "bottom", + "height": 42, + "layer": "top", + "spacing": 4, + + "modules-left": [ + "sway/workspaces", + "sway/mode" + ], + + "modules-center": [ + "sway/window" + ], + + "modules-right": [ + "privacy", + "network", + "pulseaudio", + "battery", + "tray", + "clock", + "custom/power" + ], + + "sway/workspaces": { + "disable-scroll": false, + "all-outputs": false, + "format": "{name}" + }, + + "sway/mode": { + "format": " {}", + "tooltip": false + }, + + "sway/window": { + "format": "{}", + "max-length": 50, + "icon": true, + "icon-size": 16, + "rewrite": { + "(.*) — Mozilla Firefox": "󰈹 $1", + "(.*) - Visual Studio Code": "ó°Øž $1", + "(.*) - vim": " $1", + "(.*) - zsh": " $1" + } + }, + + "privacy": { + "icon-spacing": 4, + "icon-size": 18, + "transition-duration": 250, + "modules": [ + { + "type": "screenshare", + "tooltip": true, + "tooltip-icon-size": 24 + }, + { + "type": "audio-in", + "tooltip": true, + "tooltip-icon-size": 24 + } + ] + }, + + "network": { + "interval": 5, + "format-wifi": "šŸ“¶ {signalStrength}%", + "format-ethernet": "šŸ”Œ {ipaddr}", + "format-linked": "šŸ”Œ {ifname} (No IP)", + "format-disconnected": "āŒ Disconnected", + "tooltip-format": "{ifname}: {ipaddr}/{cidr}\nGateway: {gwaddr}\nStrength: {signalStrength}%\nFrequency: {frequency}MHz\nUp: {bandwidthUpBits} Down: {bandwidthDownBits}", + "tooltip-format-wifi": "šŸ“¶ {essid} ({signalStrength}%)\n{ipaddr}/{cidr}\nGateway: {gwaddr}\nFrequency: {frequency}MHz\nUp: {bandwidthUpBits} Down: {bandwidthDownBits}", + "tooltip-format-ethernet": "šŸ”Œ {ifname}\n{ipaddr}/{cidr}\nGateway: {gwaddr}\nUp: {bandwidthUpBits} Down: {bandwidthDownBits}", + "on-click": "nm-connection-editor", + "on-click-right": "alacritty -e nmtui" + }, + + "pulseaudio": { + "scroll-step": 5, + "format": "{icon} {volume}%", + "format-bluetooth": "{icon} {volume}%", + "format-bluetooth-muted": " {icon}", + "format-muted": " {volume}%", + "format-icons": { + "headphone": "", + "hands-free": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] + }, + "on-click": "if pgrep -x pavucontrol > /dev/null; then pkill pavucontrol; else pavucontrol; fi", + "on-click-right": "pactl set-sink-mute @DEFAULT_SINK@ toggle", + "on-scroll-up": "pactl set-sink-volume @DEFAULT_SINK@ +5%", + "on-scroll-down": "pactl set-sink-volume @DEFAULT_SINK@ -5%", + "tooltip-format": "{desc}\nVolume: {volume}%" + }, + + "battery": { + "states": { + "good": 95, + "warning": 30, + "critical": 15 + }, + "interval": 30, + "format": "{icon} {capacity}%", + "format-charging": " {capacity}%", + "format-plugged": " {capacity}%", + "format-alt": "{icon} {time}", + "format-icons": ["", "", "", "", ""], + "tooltip-format": "{timeTo}\nCapacity: {capacity}%\nHealth: {health}%\nPower: {power}W" + }, + + "clock": { + "interval": 1, + "format": "{:%H:%M:%S}", + "format-alt": "{:%A, %B %d, %Y (%R)}", + "tooltip-format": "{calendar}", + "calendar": { + "mode": "year", + "mode-mon-col": 3, + "weeks-pos": "right", + "on-scroll": 1, + "on-click-right": "mode", + "format": { + "months": "{}", + "days": "{}", + "weeks": "W{}", + "weekdays": "{}", + "today": "{}" + } + }, + "actions": { + "on-click-right": "mode", + "on-scroll-up": "shift_up", + "on-scroll-down": "shift_down" + } + }, + + "tray": { + "icon-size": 18, + "spacing": 8, + "show-passive-items": true + }, + + "custom/power": { + "format": "ā»", + "on-click": "$HOME/.config/waybar/scripts/power-menu.sh", + "tooltip": false + } + }, + { + "output": "eDP-1", + "position": "bottom", + "height": 42, + "layer": "top", + "spacing": 4, + + "modules-left": [ + "sway/workspaces" + ], + + "modules-center": [ + "sway/window" + ], + + "modules-right": [ + "privacy", + "pulseaudio", + "backlight", + "battery", + "tray", + "clock", + "custom/power" + ], + + "sway/workspaces": { + "disable-scroll": false, + "all-outputs": false, + "format": "{name}" + }, + + "sway/window": { + "format": "{}", + "max-length": 50, + "icon": true, + "icon-size": 16, + "rewrite": { + "(.*) — Mozilla Firefox": "󰈹 $1", + "(.*) - Visual Studio Code": "ó°Øž $1", + "(.*) - vim": " $1", + "(.*) - zsh": " $1" + } + }, + + "privacy": { + "icon-spacing": 4, + "icon-size": 18, + "transition-duration": 250, + "modules": [ + { + "type": "screenshare", + "tooltip": true, + "tooltip-icon-size": 24 + }, + { + "type": "audio-in", + "tooltip": true, + "tooltip-icon-size": 24 + } + ] + }, + + "pulseaudio": { + "scroll-step": 5, + "format": "{icon} {volume}%", + "format-bluetooth": "{icon} {volume}%", + "format-bluetooth-muted": " {icon}", + "format-muted": " {volume}%", + "format-icons": { + "headphone": "", + "hands-free": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] + }, + "on-click": "if pgrep -x pavucontrol > /dev/null; then pkill pavucontrol; else pavucontrol; fi", + "on-click-right": "pactl set-sink-mute @DEFAULT_SINK@ toggle", + "on-scroll-up": "pactl set-sink-volume @DEFAULT_SINK@ +5%", + "on-scroll-down": "pactl set-sink-volume @DEFAULT_SINK@ -5%", + "tooltip-format": "{desc}\nVolume: {volume}%" + }, + + "backlight": { + "device": "intel_backlight", + "format": "{icon} {percent}%", + "format-icons": ["", "", "", "", "", "", "", "", ""], + "on-scroll-up": "brightnessctl set +5%", + "on-scroll-down": "[ $(brightnessctl -m | cut -d',' -f4 | tr -d '%') -le 5 ] && brightnessctl set 1% || brightnessctl set 5%-", + "tooltip-format": "Brightness: {percent}%" + }, + + "battery": { + "states": { + "good": 95, + "warning": 30, + "critical": 15 + }, + "interval": 30, + "format": "{icon} {capacity}%", + "format-charging": " {capacity}%", + "format-plugged": " {capacity}%", + "format-alt": "{icon} {time}", + "format-icons": ["", "", "", "", ""], + "tooltip-format": "{timeTo}\nCapacity: {capacity}%\nHealth: {health}%\nPower: {power}W" + }, + + "clock": { + "interval": 1, + "format": "{:%H:%M:%S}", + "format-alt": "{:%A, %B %d, %Y (%R)}", + "tooltip-format": "{calendar}", + "calendar": { + "mode": "year", + "mode-mon-col": 3, + "weeks-pos": "right", + "on-scroll": 1, + "on-click-right": "mode", + "format": { + "months": "{}", + "days": "{}", + "weeks": "W{}", + "weekdays": "{}", + "today": "{}" + } + }, + "actions": { + "on-click-right": "mode", + "on-scroll-up": "shift_up", + "on-scroll-down": "shift_down" + } + }, + + "tray": { + "icon-size": 18, + "spacing": 8, + "show-passive-items": true + }, + + "custom/power": { + "format": "ā»", + "on-click": "$HOME/.config/waybar/scripts/power-menu.sh", + "tooltip": false + } + } +] diff --git a/config/waybar/scripts/mediaplayer.py b/config/waybar/scripts/mediaplayer.py new file mode 100755 index 0000000..9aa0167 --- /dev/null +++ b/config/waybar/scripts/mediaplayer.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import json +import subprocess +import sys +import signal + +def signal_handler(sig, frame): + sys.exit(0) + +signal.signal(signal.SIGTERM, signal_handler) +signal.signal(signal.SIGINT, signal_handler) + +def get_output(status, artist, title, player): + if not status or status == "Stopped" or not title: + return {"text": "", "tooltip": "No media playing", "class": "stopped"} + + if artist and title: + text = f"{artist} - {title}" + elif title: + text = title + else: + return {"text": "", "tooltip": "No media playing", "class": "stopped"} + + if status == "Playing": + tooltip = f"ā–¶ {text}" + display = text + elif status == "Paused": + tooltip = f"āø {text}" + display = f"āø {text}" + else: + tooltip = text + display = text + + return { + "text": display[:40] + "..." if len(display) > 40 else display, + "tooltip": tooltip, + "class": status.lower() if status else "stopped" + } + +def main(): + # Use playerctl follow to get real-time updates + try: + proc = subprocess.Popen( + ['playerctl', '--follow', 'metadata', '--format', '{{status}}|||{{artist}}|||{{title}}|||{{playerName}}'], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True + ) + + for line in proc.stdout: + line = line.strip() + if not line: + output = {"text": "", "tooltip": "No media playing", "class": "stopped"} + else: + parts = line.split('|||') + if len(parts) >= 4: + status, artist, title, player = parts[0], parts[1], parts[2], parts[3] + output = get_output(status, artist, title, player) + else: + output = {"text": "", "tooltip": "No media playing", "class": "stopped"} + + print(json.dumps(output), flush=True) + + except Exception as e: + print(json.dumps({"text": "", "tooltip": "No media playing", "class": "stopped"}), flush=True) + +if __name__ == "__main__": + main() diff --git a/config/waybar/scripts/monitor-brightness.sh b/config/waybar/scripts/monitor-brightness.sh new file mode 100755 index 0000000..664a8b8 --- /dev/null +++ b/config/waybar/scripts/monitor-brightness.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +CACHE_FILE="/tmp/monitor-brightness-cache" +PID_FILE="/tmp/monitor-brightness-pid" +SCROLL_FILE="/tmp/monitor-brightness-scroll" +LOCK_FILE="/tmp/monitor-brightness-lock" + +get_actual() { + # Prevent concurrent ddcutil calls + exec 9>"$LOCK_FILE" + flock -n 9 || { cat "$CACHE_FILE" 2>/dev/null || echo "50"; return; } + val=$(ddcutil getvcp 10 --display 1 2>/dev/null | grep -oP 'current value =\s*\K[0-9]+') + exec 9>&- + echo "$val" +} + +# Initialize cache if missing +[ ! -f "$CACHE_FILE" ] && echo "50" > "$CACHE_FILE" + +apply_brightness() { + local value=$1 + [ -f "$PID_FILE" ] && kill "$(cat "$PID_FILE")" 2>/dev/null + ( + echo $$ > "$PID_FILE" + sleep 0.3 + exec 9>"$LOCK_FILE" + flock 9 + ddcutil setvcp 10 "$value" --display 1 --noverify 2>/dev/null + exec 9>&- + rm -f "$PID_FILE" + ) & +} + +case "$1" in + get) + # If scrolled recently, use cache for instant feedback + if [ -f "$SCROLL_FILE" ]; then + age=$(( $(date +%s) - $(stat -c %Y "$SCROLL_FILE") )) + if [ "$age" -lt 3 ]; then + cat "$CACHE_FILE" + exit 0 + fi + rm -f "$SCROLL_FILE" + fi + # Otherwise fetch actual value + val=$(get_actual) + if [ -n "$val" ]; then + echo "$val" > "$CACHE_FILE" + echo "$val" + else + cat "$CACHE_FILE" + fi + ;; + up) + touch "$SCROLL_FILE" + current=$(cat "$CACHE_FILE") + new=$((current + 5)) + [ "$new" -gt 100 ] && new=100 + echo "$new" > "$CACHE_FILE" + apply_brightness "$new" + ;; + down) + touch "$SCROLL_FILE" + current=$(cat "$CACHE_FILE") + new=$((current - 5)) + [ "$new" -lt 0 ] && new=0 + echo "$new" > "$CACHE_FILE" + apply_brightness "$new" + ;; +esac diff --git a/config/waybar/scripts/power-menu.sh b/config/waybar/scripts/power-menu.sh new file mode 100755 index 0000000..2feb6cf --- /dev/null +++ b/config/waybar/scripts/power-menu.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# Power menu using rofi or wofi +# Detect which one is available +if command -v rofi &> /dev/null; then + MENU="rofi -dmenu -i -p Power" +elif command -v wofi &> /dev/null; then + MENU="wofi --dmenu -i -p Power" +else + notify-send "Power Menu" "Neither rofi nor wofi is installed" + exit 1 +fi + +# Menu options +OPTIONS="ā» Shutdown\n⟳ Reboot\nā¾ Suspend\n Lock\n Logout" + +# Show menu and get choice +CHOICE=$(echo -e "$OPTIONS" | $MENU) + +# Execute based on choice +case $CHOICE in + *Shutdown) + systemctl poweroff + ;; + *Reboot) + systemctl reboot + ;; + *Suspend) + systemctl suspend + ;; + *Lock) + # Try different lock commands + if command -v swaylock &> /dev/null; then + swaylock -f -c 000000 + elif command -v gtklock &> /dev/null; then + gtklock + else + notify-send "Lock" "No lock screen program found" + fi + ;; + *Logout) + swaymsg exit + ;; +esac diff --git a/config/waybar/scripts/vpn-check.sh b/config/waybar/scripts/vpn-check.sh new file mode 100755 index 0000000..19cecb2 --- /dev/null +++ b/config/waybar/scripts/vpn-check.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +# Check for VPN connections using nmcli device show + +# Initialize variables +vpn_status="" +vpn_tooltip="" +vpn_class="disconnected" +vpn_count=0 + +# Get all VPN devices and their details +while IFS= read -r device; do + if [ -n "$device" ]; then + # Get device type and connection + device_info=$(nmcli device show "$device" 2>/dev/null) + connection=$(echo "$device_info" | grep "GENERAL.CONNECTION:" | awk -F: '{print $2}' | xargs) + device_type=$(echo "$device_info" | grep "GENERAL.TYPE:" | awk -F: '{print $2}' | xargs) + ip_addr=$(echo "$device_info" | grep "IP4.ADDRESS" | head -1 | awk -F: '{print $2}' | xargs) + + if [ -n "$connection" ] && [ "$connection" != "--" ]; then + ((vpn_count++)) + + # Determine VPN type + if [[ "$device" == wg* ]]; then + vpn_type="WG" + vpn_icon="šŸ”’" + elif [[ "$device" == tun* ]]; then + vpn_type="OVPN" + vpn_icon="šŸ”’" + else + vpn_type="VPN" + vpn_icon="šŸ”’" + fi + + # Build status text + if [ -z "$vpn_status" ]; then + vpn_status="$vpn_icon $vpn_type" + vpn_tooltip="$vpn_type: $connection" + else + vpn_status="$vpn_status | $vpn_icon $vpn_type" + vpn_tooltip="$vpn_tooltip\n$vpn_type: $connection" + fi + + # Add IP if available + if [ -n "$ip_addr" ]; then + vpn_tooltip="$vpn_tooltip\nIP: $ip_addr" + fi + + # Set class based on first VPN type found + if [ "$vpn_class" = "disconnected" ]; then + if [[ "$device" == wg* ]]; then + vpn_class="connected-wg" + elif [[ "$device" == tun* ]]; then + vpn_class="connected-ovpn" + else + vpn_class="connected" + fi + fi + fi + fi +done < <(nmcli -t -f DEVICE device status | grep -E '^(tun|wg)' | cut -d: -f1) + +# If no VPN found, show empty +if [ $vpn_count -eq 0 ]; then + echo "{\"text\":\"\", \"tooltip\":\"No VPN connected\", \"class\":\"disconnected\"}" +else + echo "{\"text\":\"$vpn_status\", \"tooltip\":\"$vpn_tooltip\", \"class\":\"$vpn_class\"}" +fi diff --git a/config/waybar/scripts/weather.sh b/config/waybar/scripts/weather.sh new file mode 100755 index 0000000..e9e2c9a --- /dev/null +++ b/config/waybar/scripts/weather.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Get weather data from wttr.in with geolocation from ipinfo.io +# You can override with WEATHER_LOCATION environment variable +LOCATION="${WEATHER_LOCATION:-}" + +# If no location is set, fetch it from ipinfo.io +if [ -z "$LOCATION" ]; then + LOCATION=$(curl -s "https://ipinfo.io/" | jq -r '.city // empty') +fi + +# Fetch weather using wttr.in +if [ -n "$LOCATION" ]; then + WEATHER_DATA=$(curl -s "wttr.in/$LOCATION?format=%c+%t") + TOOLTIP=$(curl -s "wttr.in/$LOCATION?format=%l:+%c+%t+%w+%p+%h") +else + WEATHER_DATA=$(curl -s "wttr.in/?format=%c+%t") + TOOLTIP=$(curl -s "wttr.in/?format=%l:+%c+%t+%w+%p+%h") +fi + +# Check if curl was successful +if [ $? -eq 0 ] && [ -n "$WEATHER_DATA" ]; then + echo "{\"text\":\"$WEATHER_DATA\", \"tooltip\":\"$TOOLTIP\"}" +else + echo "{\"text\":\"🌔 N/A\", \"tooltip\":\"Weather data unavailable\"}" +fi diff --git a/config/waybar/style.css b/config/waybar/style.css new file mode 100644 index 0000000..a741132 --- /dev/null +++ b/config/waybar/style.css @@ -0,0 +1,329 @@ +/* Material Design Waybar Theme */ + +* { + font-family: "Roboto", "JetBrainsMono Nerd Font", "Material Design Icons", sans-serif; + font-size: 13px; + font-weight: 500; + border: none; + border-radius: 0; + min-height: 0; +} + +window#waybar { + background: #1E1E1E; + color: #E1E1E1; +} + +window#waybar.hidden { + opacity: 0; +} + +tooltip { + background: #2D2D2D; + border-radius: 8px; + padding: 12px 16px; + color: #E1E1E1; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4); +} + +tooltip label { + color: #E1E1E1; +} + +/* ============= Workspaces ============= */ +#workspaces { + margin: 4px 8px; + padding: 0; + background: #2D2D2D; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +#workspaces button { + padding: 0 16px; + margin: 4px; + background: transparent; + color: #B0B0B0; + border-radius: 6px; + transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1); +} + +#workspaces button:hover { + background: rgba(144, 202, 249, 0.12); + color: #90CAF9; +} + +#workspaces button.active, +#workspaces button.focused { + background: #90CAF9; + color: #1E1E1E; + box-shadow: 0 2px 4px rgba(144, 202, 249, 0.3); +} + +#workspaces button.urgent { + background: #CF6679; + color: #1E1E1E; +} + +/* ============= Mode & Window ============= */ +#mode { + margin: 4px 8px; + padding: 0 16px; + background: #CF6679; + color: #1E1E1E; + border-radius: 8px; + font-weight: 500; +} + +#window { + margin: 4px 8px; + padding: 0 16px; + color: #B0B0B0; +} + +/* ============= Module Cards ============= */ +#battery, +#cpu, +#memory, +#temperature, +#backlight, +#network, +#pulseaudio, +#bluetooth, +#idle_inhibitor, +#privacy, +#tray, +#custom-media, +#custom-updates, +#custom-weather, +#custom-vpn, +#custom-ibus, +#custom-monitor-brightness { + margin: 4px 2px; + padding: 0 12px; + background: #2D2D2D; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1); +} + +#battery:hover, +#cpu:hover, +#memory:hover, +#temperature:hover, +#backlight:hover, +#network:hover, +#pulseaudio:hover, +#bluetooth:hover, +#idle_inhibitor:hover, +#custom-media:hover, +#custom-updates:hover, +#custom-weather:hover, +#custom-vpn:hover, +#custom-ibus:hover, +#custom-monitor-brightness:hover { + background: #383838; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4); +} + +/* ============= Clock - Elevated Card ============= */ +#clock { + margin: 4px 8px; + padding: 0 20px; + background: #2D2D2D; + color: #E1E1E1; + border-radius: 8px; + font-weight: 500; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); + transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1); +} + +#clock:hover { + background: #383838; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); +} + +/* ============= Idle Inhibitor ============= */ +#idle_inhibitor { + color: #B0B0B0; +} + +#idle_inhibitor.activated { + background: #03DAC6; + color: #1E1E1E; +} + +/* ============= Privacy ============= */ +#privacy { + padding: 0 8px; +} + +#privacy-item { + padding: 0 8px; + margin: 0 2px; +} + +#privacy-item.screenshare { + color: #CF6679; +} + +#privacy-item.audio-in { + color: #03DAC6; +} + +#privacy-item.audio-out { + color: #90CAF9; +} + +/* ============= Updates ============= */ +#custom-updates { + color: #FFB74D; +} + +/* ============= Weather ============= */ +#custom-weather { + color: #90CAF9; +} + +/* ============= Bluetooth ============= */ +#bluetooth { + color: #90CAF9; +} + +#bluetooth.disabled { + color: #666666; +} + +#bluetooth.connected { + background: #90CAF9; + color: #1E1E1E; +} + +/* ============= Network ============= */ +#network { + color: #03DAC6; +} + +#network.disconnected { + color: #CF6679; +} + +/* ============= VPN ============= */ +#custom-vpn { + color: #81C784; +} + +#custom-vpn.connected, +#custom-vpn.connected-ovpn, +#custom-vpn.connected-wg { + background: #81C784; + color: #1E1E1E; +} + +#custom-vpn.disconnected { + background: transparent; + box-shadow: none; +} + +/* ============= Pulseaudio ============= */ +#pulseaudio { + color: #CE93D8; +} + +#pulseaudio.muted { + color: #666666; +} + +/* ============= Backlight & Monitor Brightness ============= */ +#backlight, +#custom-monitor-brightness { + color: #FFE082; +} + +/* ============= CPU ============= */ +#cpu { + color: #81C784; +} + +/* ============= Memory ============= */ +#memory { + color: #CE93D8; +} + +/* ============= Temperature ============= */ +#temperature { + color: #FFAB91; +} + +#temperature.critical { + background: #CF6679; + color: #1E1E1E; +} + +/* ============= Battery ============= */ +#battery { + color: #81C784; +} + +#battery.charging, +#battery.plugged { + background: #81C784; + color: #1E1E1E; +} + +#battery.warning:not(.charging) { + color: #FFB74D; +} + +#battery.critical:not(.charging) { + background: #CF6679; + color: #1E1E1E; + animation: blink 1s ease-in-out infinite; +} + +@keyframes blink { + 50% { opacity: 0.6; } +} + +/* ============= Media ============= */ +#custom-media { + color: #F48FB1; + min-width: 100px; +} + +/* ============= IBus ============= */ +#custom-ibus { + color: #90CAF9; +} + +/* ============= Tray ============= */ +#tray { + padding: 0 8px; +} + +#tray > .passive { + -gtk-icon-effect: dim; +} + +#tray > .needs-attention { + -gtk-icon-effect: highlight; + background: rgba(207, 102, 121, 0.3); + border-radius: 6px; +} + +/* ============= Power Button ============= */ +#custom-power { + margin: 4px 8px 4px 2px; + padding: 0 14px; + background: #2D2D2D; + color: #CF6679; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1); +} + +#custom-power:hover { + background: #CF6679; + color: #1E1E1E; + box-shadow: 0 4px 8px rgba(207, 102, 121, 0.4); +} diff --git a/home/packages.nix b/home/packages.nix index 9f03c7c..326c1ee 100644 --- a/home/packages.nix +++ b/home/packages.nix @@ -4,6 +4,8 @@ { home.packages = with pkgs; [ ripdrag + playerctl # waybar mediaplayer module + swaync's mpris widget + libnotify # notify-send, used by config/waybar/scripts/monitor-brightness.sh fastfetch p7zip unrar # unfree diff --git a/home/shihaam.nix b/home/shihaam.nix index a2bc75a..62915e5 100644 --- a/home/shihaam.nix +++ b/home/shihaam.nix @@ -10,7 +10,9 @@ ./nano.nix ./obs.nix ./sway.nix + ./swaync.nix ./theme.nix + ./waybar.nix ]; home.username = "shihaam"; diff --git a/home/swaync.nix b/home/swaync.nix new file mode 100644 index 0000000..3ea42ed --- /dev/null +++ b/home/swaync.nix @@ -0,0 +1,29 @@ +# SwayNotificationCenter -- the notification daemon, replacing mako. +# +# mako shows a notification's actions only as "click the popup to fire the +# default one"; swaync draws one button per action, which is what makes +# blueman's pairing prompts (Accept / Reject / Always accept), browser +# permission prompts and the like usable without hunting for a CLI. +# +# Nothing else may own org.freedesktop.Notifications -- two daemons racing for +# that name means whichever lost silently eats every notification. Sway starts +# it from its exec block (config/sway/config), same as waybar; there is no +# graphical-session.target running here, so a systemd user unit would never +# fire. +# +# Config stays in swaync's own syntax at config/swaync/ and is symlinked, like +# sway and waybar. cssPriority = "user" in config.json is what lets style.css +# override the GTK theme instead of losing to it. +# +# Iteration loop: edit config/swaync/... -> git add if new -> +# nixos-rebuild switch -> swaync-client -rs (reload config + css). +{ pkgs, ... }: + +{ + home.packages = with pkgs; [ swaynotificationcenter ]; + + xdg.configFile."swaync" = { + source = ../config/swaync; + recursive = true; + }; +} diff --git a/home/waybar.nix b/home/waybar.nix new file mode 100644 index 0000000..f044469 --- /dev/null +++ b/home/waybar.nix @@ -0,0 +1,10 @@ +{ ... }: + +{ + xdg.configFile."waybar" = { + source = ../config/waybar; + recursive = true; + }; +} +# Iteration loop: edit config/waybar/... -> git add if new -> +# doas nixos-rebuild switch --flake /home/shihaam/git/sargit/dotfiles#la2410 && pkill -SIGUSR2 waybar