Add swaync control center, wifi/bluetooth/vpn rofi menus, waybar notification button

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 10:06:04 +05:00
co-authored by Claude Sonnet 5.5
parent 447528532e
commit bee45f21ad
12 changed files with 364 additions and 7 deletions
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Bluetooth menu (rofi): power, connect/disconnect known devices, scan and pair new ones.
THEME=~/.config/rofi/catppuccin.rasi
menu() { rofi -dmenu -i -p "$1" -theme "$THEME" "${@:2}"; }
if ! bluetoothctl show | grep -q "Powered: yes"; then
choice=$(printf '%s\n' "Включить Bluetooth" | menu "Bluetooth") || exit 0
[[ $choice == "Включить Bluetooth" ]] && bluetoothctl power on >/dev/null && notify-send "Bluetooth" "Включён"
exit 0
fi
build() {
local list="" mac name mark
while read -r _ mac name; do
[[ -z $mac ]] && continue
mark="○"; bluetoothctl info "$mac" | grep -q "Connected: yes" && mark="●"
list+="$mark $name [$mac]"$'\n'
done < <(bluetoothctl devices)
printf '%s' "$list"
}
choice=$(printf '%s%s\n%s\n' "$(build)" "Найти новые устройства" "Выключить Bluetooth" | sed '/^$/d' | menu "Bluetooth") || exit 0
[[ -z $choice ]] && exit 0
case $choice in
"Выключить Bluetooth") bluetoothctl power off >/dev/null; notify-send "Bluetooth" "Выключен"; exit 0 ;;
"Найти новые устройства")
notify-send "Bluetooth" "Поиск устройств (8 с)…"
bluetoothctl --timeout 8 scan on >/dev/null 2>&1
choice=$(build | menu "Bluetooth: найденные") || exit 0
[[ -z $choice ]] && exit 0
mac=${choice##*[}; mac=${mac%]}
bluetoothctl pair "$mac" >/dev/null 2>&1; bluetoothctl trust "$mac" >/dev/null 2>&1
bluetoothctl connect "$mac" >/dev/null 2>&1 && notify-send "Bluetooth" "Подключено" || notify-send -u critical "Bluetooth" "Не удалось подключиться"
exit 0 ;;
esac
mac=${choice##*[}; mac=${mac%]}
name=${choice:2}; name=${name% [*}
if [[ ${choice:0:1} == "●" ]]; then
bluetoothctl disconnect "$mac" >/dev/null && notify-send "Bluetooth" "Отключено: $name"
else
bluetoothctl connect "$mac" >/dev/null 2>&1 && notify-send "Bluetooth" "Подключено: $name" || notify-send -u critical "Bluetooth" "Не удалось подключиться: $name"
fi
+1 -1
View File
@@ -26,7 +26,7 @@ client.placeholder $base $base $text $base $base
client.background $base
# Autostart
exec_always sh -c 'pkill -x dunst; exec dunst'
exec_always sh -c 'pkill -x dunst; pkill -x swaync; exec swaync'
exec_always sh -c 'pkill -x nm-applet; exec nm-applet --indicator'
bindsym --to-code $mod+Shift+x exec ~/.config/sway/lock.sh
+2
View File
@@ -33,6 +33,8 @@ titlebar_separator disable
layer_effects "waybar" blur enable; blur_ignore_transparent enable; corner_radius 14; shadows enable
layer_effects "rofi" blur enable; blur_ignore_transparent enable; corner_radius 20; shadows enable
layer_effects "notifications" blur enable; blur_ignore_transparent enable; corner_radius 14; shadows enable
layer_effects "swaync-control-center" blur enable; blur_ignore_transparent enable; corner_radius 20; shadows enable
layer_effects "swaync-notification-window" blur enable; blur_ignore_transparent enable; corner_radius 14; shadows enable
layer_effects "swaynag" blur enable; blur_ignore_transparent enable; corner_radius 12
# Translucent terminals, glass shows through
+3
View File
@@ -32,3 +32,6 @@ bindsym --to-code $mod+Shift+s exec ~/.config/sway/screenshot-menu.sh
bindsym --to-code $mod+F1 exec ~/.config/sway/cheatsheet.sh
# Performance mode: toggle blur + shadows
bindsym --to-code $mod+Shift+b blur toggle; shadows toggle
# Control center (notifications + quick settings)
bindsym --to-code $mod+n exec swaync-client -t -sw
bindsym --to-code $mod+Shift+n exec swaync-client -d -sw
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# VPN menu (rofi): lists NetworkManager vpn/wireguard connections, click to connect/disconnect.
# Secrets are asked by nm-applet (secret agent).
THEME=~/.config/rofi/catppuccin.rasi
menu() { rofi -dmenu -i -p "$1" -theme "$THEME" "${@:2}"; }
active=$(nmcli -t -e no -f NAME,TYPE connection show --active)
list=""
while IFS= read -r line; do
type=${line##*:}; name=${line%:*}
[[ $type == vpn || $type == wireguard ]] || continue
mark="○"; grep -Fxq "$line" <<<"$active" && mark="●"
list+="$mark $name ($type)"$'\n'
done < <(nmcli -t -e no -f NAME,TYPE connection show)
[[ -z $list ]] && { notify-send "VPN" "Нет настроенных VPN"; exit 0; }
choice=$(printf '%s%s\n' "$list" "Расширенные настройки…" | menu "VPN") || exit 0
[[ -z $choice ]] && exit 0
[[ $choice == "Расширенные настройки…" ]] && exec nm-connection-editor
name=${choice:2}; name=${name% (*}
if [[ ${choice:0:1} == "●" ]]; then
nmcli connection down id "$name" >/dev/null && notify-send "VPN" "Отключено: $name"
else
nmcli connection up id "$name" >/dev/null 2>&1 && notify-send "VPN" "Подключено: $name" || notify-send -u critical "VPN" "Не удалось подключиться: $name"
fi
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Wi-Fi menu (rofi): toggle radio, connect/disconnect, prompt for password on new networks.
THEME=~/.config/rofi/catppuccin.rasi
menu() { rofi -dmenu -i -p "$1" -theme "$THEME" "${@:2}"; }
radio=$(nmcli radio wifi)
if [[ $radio != enabled ]]; then
choice=$(printf '%s\n' "Включить Wi-Fi" | menu "Wi-Fi")
[[ $choice == "Включить Wi-Fi" ]] && nmcli radio wifi on && notify-send "Wi-Fi" "Включён"
exit 0
fi
declare -A seen
list=""
while IFS=: read -r use sig sec ssid; do
[[ -z $ssid || -n ${seen[$ssid]} ]] && continue
seen[$ssid]=1
mark="○"; [[ $use == '*' ]] && mark="●"
lock=""; [[ -n $sec && $sec != "--" ]] && lock=" (защищена)"
list+="$mark $ssid ${sig}%$lock"$'\n'
done < <(nmcli -t -e no -f IN-USE,SIGNAL,SECURITY,SSID dev wifi list --rescan auto | sort -t: -k2 -nr)
choice=$(printf '%s%s\n%s\n' "$list" "Выключить Wi-Fi" "Расширенные настройки…" | sed '/^$/d' | menu "Wi-Fi") || exit 0
[[ -z $choice ]] && exit 0
case $choice in
"Выключить Wi-Fi") nmcli radio wifi off; notify-send "Wi-Fi" "Выключен"; exit 0 ;;
"Расширенные настройки…") exec nm-connection-editor ;;
esac
ssid=${choice:2}; ssid=${ssid%% *}
if [[ ${choice:0:1} == "●" ]]; then
nmcli connection down id "$ssid" >/dev/null && notify-send "Wi-Fi" "Отключено от $ssid"
elif nmcli -t -f NAME connection show | grep -Fxq "$ssid"; then
nmcli connection up id "$ssid" >/dev/null 2>&1 && notify-send "Wi-Fi" "Подключено к $ssid" || notify-send -u critical "Wi-Fi" "Не удалось подключиться к $ssid"
else
if [[ $choice == *"(защищена)"* ]]; then
pw=$(menu "Пароль для $ssid" -password -lines 0) || exit 0
out=$(nmcli dev wifi connect "$ssid" password "$pw" 2>&1)
else
out=$(nmcli dev wifi connect "$ssid" 2>&1)
fi
[[ $? -eq 0 ]] && notify-send "Wi-Fi" "Подключено к $ssid" || notify-send -u critical "Wi-Fi" "Ошибка: $out"
fi
+98
View File
@@ -0,0 +1,98 @@
{
"$schema": "/etc/xdg/swaync/configSchema.json",
"ignore-gtk-theme": true,
"positionX": "right",
"positionY": "top",
"layer": "overlay",
"control-center-layer": "top",
"layer-shell": true,
"layer-shell-cover-screen": false,
"cssPriority": "user",
"control-center-margin-top": 6,
"control-center-margin-bottom": 8,
"control-center-margin-right": 8,
"control-center-margin-left": 0,
"control-center-width": 460,
"control-center-height": 620,
"fit-to-screen": false,
"notification-window-width": 380,
"notification-icon-size": 48,
"notification-body-image-height": 100,
"notification-body-image-width": 200,
"timeout": 8,
"timeout-low": 5,
"timeout-critical": 0,
"transition-time": 180,
"hide-on-clear": false,
"hide-on-action": true,
"keyboard-shortcuts": true,
"notification-grouping": true,
"relative-timestamps": true,
"text-empty": "Нет уведомлений",
"widgets": [
"title",
"buttons-grid",
"volume",
"backlight",
"mpris",
"dnd",
"notifications"
],
"widget-config": {
"title": {
"text": "Центр управления",
"clear-all-button": true,
"button-text": "Очистить"
},
"dnd": {
"text": "Не беспокоить"
},
"buttons-grid": {
"actions": [
{
"label": " Wi-Fi",
"command": "sh -c 'swaync-client -cp -sw; ~/.config/sway/wifi-menu.sh'"
},
{
"label": " Bluetooth",
"command": "sh -c 'swaync-client -cp -sw; ~/.config/sway/bt-menu.sh'"
},
{
"label": " VPN",
"command": "sh -c 'swaync-client -cp -sw; ~/.config/sway/vpn-menu.sh'"
},
{
"label": " Микрофон",
"type": "toggle",
"active": true,
"command": "sh -c '[[ $SWAYNC_TOGGLE_STATE == true ]] && wpctl set-mute @DEFAULT_AUDIO_SOURCE@ 0 || wpctl set-mute @DEFAULT_AUDIO_SOURCE@ 1'",
"update-command": "sh -c 'wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED && echo false || echo true'"
},
{
"label": " Блок",
"command": "sh -c 'swaync-client -cp -sw; ~/.config/sway/lock.sh'"
},
{
"label": " Выкл",
"command": "sh -c 'swaync-client -cp -sw; ~/.config/rofi/powermenu.sh'"
}
]
},
"volume": {
"label": "",
"show-per-app": true,
"show-per-app-icon": true,
"show-per-app-label": false
},
"backlight": {
"label": "",
"device": "intel_backlight"
},
"mpris": {
"image-size": 64,
"image-radius": 12,
"autohide": true,
"show-album-art": "when-available"
}
}
}
+117
View File
@@ -0,0 +1,117 @@
/* swaync glass theme: matches rofi/waybar, purple accent from kitty.
Icons come from FontAwesome (the same glyphs the waybar uses). */
@define-color text #f2f2f2;
@define-color sub #b4b4b4;
@define-color accent #bb9af7;
@define-color red #f7768e;
* {
font-family: "JetBrains Mono", "FontAwesome", "Font Awesome 6 Brands", monospace;
font-size: 14px;
color: @text;
text-shadow: none;
box-shadow: none;
}
/* ---------- Panel ---------- */
.control-center {
background: rgba(14, 14, 18, 0.80);
border: 1px solid rgba(232, 232, 232, 0.18);
border-radius: 20px;
padding: 16px;
}
.control-center .widget-title { margin: 2px 2px 12px 2px; }
.widget-title > label { font-size: 18px; font-weight: bold; }
.widget-title > button {
background: rgba(255, 255, 255, 0.10);
border: none; border-radius: 12px; padding: 6px 16px;
}
.widget-title > button label { color: @text; }
.widget-title > button:hover { background: rgba(255, 255, 255, 0.22); }
/* ---------- Toggle tiles ---------- */
.widget-buttons-grid { background: transparent; margin: 0 0 10px 0; padding: 0; }
.widget-buttons-grid > flowbox > flowboxchild > button {
background: rgba(255, 255, 255, 0.09);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 14px;
min-width: 120px; min-height: 46px; margin: 4px;
font-size: 14px;
}
.widget-buttons-grid > flowbox > flowboxchild > button label { font-weight: bold; }
.widget-buttons-grid > flowbox > flowboxchild > button:hover { background: rgba(255, 255, 255, 0.20); }
.widget-buttons-grid > flowbox > flowboxchild > button:checked {
background: @accent; border-color: @accent;
}
.widget-buttons-grid > flowbox > flowboxchild > button:checked label { color: #11111b; }
/* ---------- Sliders ---------- */
.widget-volume, .widget-backlight {
background: rgba(255, 255, 255, 0.09);
border-radius: 14px; padding: 10px 16px; margin: 4px 0;
}
.widget-volume label, .widget-backlight label { font-size: 18px; min-width: 26px; }
scale trough {
min-height: 8px; border-radius: 6px;
background: rgba(255, 255, 255, 0.20);
}
scale highlight { background: @accent; border-radius: 6px; }
scale slider {
min-width: 18px; min-height: 18px; margin: -6px;
background: @text; border-radius: 50%; border: none;
}
.widget-volume button { background: rgba(255, 255, 255, 0.10); border: none; border-radius: 10px; }
.per-app-volume { background: transparent; padding: 4px 0; }
/* ---------- Player ---------- */
.widget-mpris {
background: rgba(255, 255, 255, 0.09);
border-radius: 14px; padding: 8px; margin: 4px 0;
}
.widget-mpris-title { font-weight: bold; font-size: 15px; }
.widget-mpris-subtitle { color: @sub; font-size: 13px; }
.widget-mpris button { background: transparent; border: none; border-radius: 12px; }
.widget-mpris button:hover { background: rgba(255, 255, 255, 0.18); }
/* ---------- Do not disturb ---------- */
.widget-dnd { margin: 10px 4px; font-size: 14px; }
.widget-dnd > label { font-weight: bold; }
.widget-dnd > switch {
background: rgba(255, 255, 255, 0.20);
border: none; border-radius: 14px;
}
.widget-dnd > switch:checked { background: @accent; }
.widget-dnd > switch slider { background: @text; border-radius: 12px; }
/* ---------- Notifications (popups + list) ---------- */
.notification-row { outline: none; }
.notification {
background: rgba(14, 14, 18, 0.85);
border: 1px solid rgba(232, 232, 232, 0.22);
border-radius: 14px; margin: 6px 8px; padding: 0;
}
.control-center .notification { background: rgba(255, 255, 255, 0.09); border: none; }
.notification-content { background: transparent; padding: 10px; }
.notification-default-action, .notification-action { background: transparent; border-radius: 14px; }
.notification-default-action:hover { background: rgba(255, 255, 255, 0.09); }
.summary { font-weight: bold; font-size: 15px; }
.body { color: #d8d8d8; font-size: 14px; }
.time { color: @sub; font-size: 12px; }
.image { border-radius: 10px; }
.close-button {
background: rgba(255, 255, 255, 0.14);
border: none; border-radius: 50%; margin: 6px; padding: 2px;
}
.close-button:hover { background: @red; }
.critical { border: 1px solid @red; }
.notification-group-headers label { color: @sub; font-weight: bold; }
.widget-notifications, .notification-group { background: transparent; }
/* empty state: text only, no broken icon */
.notifications-placeholder, .control-center-list placeholder { color: @sub; font-size: 14px; }
.notifications-placeholder image, .control-center-list placeholder image { opacity: 0; min-height: 0; min-width: 0; -gtk-icon-size: 0; }
.notifications-placeholder > *:first-child,
.notifications-placeholder picture,
.notifications-placeholder > box > image,
.control-center-list-placeholder image,
.empty-label image { opacity: 0; min-height: 0; min-width: 0; margin: 0; }
+21 -1
View File
@@ -8,7 +8,7 @@
"spacing": 6,
"modules-left": ["sway/workspaces", "sway/mode", "sway/window"],
"modules-center": ["clock", "mpris"],
"modules-right": ["idle_inhibitor", "sway/language", "group/hw", "backlight", "pulseaudio", "network", "battery", "tray", "custom/power"],
"modules-right": ["idle_inhibitor", "sway/language", "group/hw", "backlight", "pulseaudio", "network", "battery", "tray", "custom/notification", "custom/power"],
"sway/workspaces": { "disable-scroll": true, "all-outputs": true, "format": "{icon}", "format-icons": {"1": "", "2": "", "3": "", "4": "", "5": "", "urgent": "", "default": ""} },
"sway/window": { "max-length": 45, "format": "{title}", "rewrite": { "(.*) — Mozilla Firefox": " $1", "(.*) - Chromium": " $1" } },
@@ -42,5 +42,25 @@
"format-icons": ["", "", "", "", ""]
},
"tray": { "spacing": 8 },
"custom/notification": {
"tooltip": false,
"format": "{icon}",
"format-icons": {
"notification": "\uf0f3<span foreground='#f7768e'><sup>\uf111</sup></span>",
"none": "\uf0f3",
"dnd-notification": "\uf1f6<span foreground='#f7768e'><sup>\uf111</sup></span>",
"dnd-none": "\uf1f6",
"inhibited-notification": "\uf0f3<span foreground='#f7768e'><sup>\uf111</sup></span>",
"inhibited-none": "\uf0f3",
"dnd-inhibited-notification": "\uf1f6<span foreground='#f7768e'><sup>\uf111</sup></span>",
"dnd-inhibited-none": "\uf1f6"
},
"return-type": "json",
"exec-if": "which swaync-client",
"exec": "swaync-client -swb",
"on-click": "swaync-client -t -sw",
"on-click-right": "swaync-client -d -sw",
"escape": true
},
"custom/power": { "format": "", "tooltip": false, "on-click": "~/.config/rofi/powermenu.sh" }
}
+2 -1
View File
@@ -18,7 +18,7 @@ window#waybar { background: transparent; color: @text; }
/* glass pills: translucent so swayfx blur shows through */
#workspaces, #window, #clock, #language, #cpu, #memory, #pulseaudio, #network, #battery,
#tray, #mode, #custom-power, #backlight, #idle_inhibitor, #temperature, #mpris {
#tray, #mode, #custom-power, #custom-notification, #backlight, #idle_inhibitor, #temperature, #mpris {
background: alpha(@base, 0.55);
border: 1px solid alpha(@text, 0.08);
border-radius: 14px;
@@ -58,6 +58,7 @@ window#waybar.empty #window { background: transparent; border-color: transparent
#battery.warning:not(.charging) { color: @yellow; }
#battery.critical:not(.charging) { color: @red; animation: blink 1s steps(2) infinite; }
#temperature.critical { color: @red; }
#custom-notification { padding: 0 14px; }
#custom-power { padding: 0 14px; }
#custom-power:hover { background: @red; color: @base; border-color: @red; }