This commit is contained in:
teraflops 2025-05-28 18:27:10 +02:00
commit 98cf630184
Signed by: teraflops
GPG Key ID: 2B77D97AF6F8968C
53 changed files with 4975 additions and 0 deletions

404
.config/waybar/config.jsonc Normal file
View File

@ -0,0 +1,404 @@
{
"layer": "top",
"position": "top",
"margin-top": 1,
"margin-left": 1,
"margin-right": 1,
"margin-bottom": 1,
"height": 54,
"spacing": 0,
"modules-left": [
"custom/launcher",
"hyprland/workspaces",
"cpu",
// "temperature",
"custom/temps",
"power-profiles-daemon",
"memory",
// "custom/updates",
"custom/fans",
"custom/weather"
// "custom/mpris-buttons"
],
"modules-center": [
"custom/calendar_clock"
],
"modules-right": [
//"mpd",
//"custom/rate",
// "image#test",
// "custom/dmp_nowplaying",
"custom/eww_nowplaying",
"custom/lyrics",
"mpris",
"custom/roon",
"tray",
"custom/wf-recorder",
//"image#gpu",
"bluetooth",
"network",
"wireplumber",
"custom/pwrate",
"battery",
"custom/powermenu"
//"hyprland/language"
//"pulseaudio/slider",
],
"custom/roon": {
"exec": "cat /tmp/waybar_roon_info.json",
"format": "{text}",
"tooltip": true,
"return-type": "json",
"signal": 3
},
"custom/temps": {
"exec": "~/.config/waybar/scripts/temps.sh",
"interval": 10,
"return-type": "json",
"tooltip": true,
"on-click": "~/.config/waybar/scripts/temps.sh click"
},
"custom/dmp_nowplaying": {
"exec": "~/.config/waybar/scripts/dmp_nowplaying.sh",
"return-type": "json",
"interval": 5,
"tooltip": true
},
"custom/eww_nowplaying": {
"exec": "echo '🎶 '",
"interval": 3600, // No es dinámico, solo muestra el ícono
"tooltip": false,
"on-click": "~/.config/eww/scripts/toggle_nowplaying.sh"
},
"custom/fans": {
"exec": "~/.config/waybar/scripts/fan_speed.sh",
"format": "{text}",
"return-type": "json",
"on-click": "~/.config/waybar/scripts/fan_speed.sh next",
"on-click-right": "~/.config/waybar/scripts/toggle_fans.sh",
"interval": 5,
"tooltip": true
},
"custom/updates": {
"format": "{text}",
"exec": "/bin/bash /home/teraflops/.config/waybar/scripts/updates.sh",
"interval": 300,
"return-type": "json",
"tooltip": true,
},
"hyprland/language": {
"format-es": "es",
"format-en": "us",
"keyboard-name": "asus-keyboard",
"keyboard-name": "melgeek-mojo68-c1-keyboard",
"keyboard-name": "mwstudio-mmkzoo65-keyboard",
"on-click": "hyprctl switchxkblayout mwstudio-mmkzoo65-keyboard next"
},
"custom/mpris-buttons": {
"exec": "~/.config/waybar/scripts/mpris_buttons.sh",
// "interval": 1,
"return-type": "json",
"on-click": "playerctl play-pause",
"on-click-right": "playerctl next",
"on-click-middle": "playerctl previous",
"signal": 24
},
"pulseaudio/slider": {
"min": 0,
"max": 100,
"orientation": "vertical"
},
"custom/network_speed": {
"exec": "/home/teraflops/.local/bin/network_speed_client.py",
"interval": 1,
"return-type": "json",
"format": "{}",
"tooltip": true,
},
"image#gpu": {
"exec": "/tmp/gpu.sh",
"size": 18,
// "interval": 1,
// "signal": 3,
"tooltip": true,
"on-click": "python /home/teraflops/.local/bin/comandos.py",
},
"custom/lyrics": {
"format": "{text}",
"exec": "/home/teraflops/.config/waybar/scripts/lyrics_module.sh",
"return-type": "json",
"escape": true,
"interval": 10,
"on-empty": "hide",
//"on-click": "termite -t playlist -e ncmpcpp",
"on-click": "/home/teraflops/.config/waybar/scripts/show_cover.py",
//"on-scroll-down": "/home/teraflops/.config/waybar/scripts/lyrics_scroll.sh",
"signal": 9
},
"mpris": {
"format": "{title}",
"format-paused": "⏸ {title}",
"tooltip-format": "{title}\n{artist} ",
//"title-len": 20,
"artist-len": 0,
"album-len": 0,
"dynamic-len": 20,
"ellipsis": "…",
"expand": false,
"max-length": 60,
// "min-length": 25,
"tooltip": true,
//"ignored-players": ["firefox"],
},
"hyprland/workspaces": {
"on-click": "activate",
"format": "{icon}",
"format-icons": {
"default": "",
"1": "",
"2": "",
"3": "",
"4": "",
"active": "󱓻",
"urgent": "󱓻",
},
"persistent-workspaces": {
"*": 4,
},
},
"custom/inetspeedup": {
"format": "{}",
"exec": "/home/teraflops/.config/waybar/scripts/inetup.py",
"on-click": "/home/teraflops/.config/waybar/scripts/inetup.py toggle",
"interval": 10,
"return-type": "json",
"signal": 20,
},
"custom/inetspeeddown": {
"format": "{}",
"exec": "/home/teraflops/.config/waybar/scripts/inetdown.py",
"on-click": "/home/teraflops/.config/waybar/scripts/inetdown.py toggle",
"interval": 10,
"return-type": "json",
"signal": 21,
},
"custom/borg": {
//pkill -RTMIN+12 waybar
"format": "BACKUP RUNNING",
"exec": "echo '{\"class\": \"running\"}'",
"exec-if": "pgrep -x pika-backup",
"return-type": "json",
"signal": 12,
},
"custom/wf-recorder": {
//pkill -RTMIN+11 waybar
"format": "• ",
"exec": "echo '{\"class\": \"recording\"}'",
"exec-if": "pgrep -x wf-recorder",
"return-type": "json",
"signal": 11,
},
"custom/tailscale": {
"format": "VPN",
"exec": "echo '{\"class\": \"vpn\"}'",
"exec-if": "pgrep -x tailscaled",
"return-type": "json",
"signal": 22,
},
"custom/pwrate": {
"exec": "/home/teraflops/.local/bin/pwrate3.sh",
"return-type": "json",
"format": "{}",
"signal": 23
},
"custom/rate": {
//pkill -RTMIN+15 waybar
//"exec": "/home/teraflops/.local/bin/show_rate.sh",
"exec": "python /home/teraflops/.local/bin/now_playing_tooltip.py",
//"exec": "echo '{\"text\":\"🎵\", \"tooltip\":\"Testing Tooltip\"}'", // Simple test module
"tooltip": true,
"return-type": "json",
"escape": false,
"on-click": "python /home/teraflops/.local/bin/now_playing_tooltip.py toggle",
"on-click-right": "pkill -Af now_playing_tooltip.py",
"signal": 15,
"class": "rate-module",
},
"backlight": {
"device": "amdgpu_bl1",
"format": "{icon}",
"format-icons": [
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
],
},
"custom/weather": {
"exec": "python ~/.config/waybar/scripts/weather.py",
"restart-interval": 300,
"return-type": "json",
// "on-click": "xdg-open https://weather.com/en-IN/weather/today/l/$(location_id)"
// "format-alt": "{alt}",
},
"bluetooth": {
"format": "",
"format-connected": "",
"format-connected-battery": "󰂴 ",
"tooltip-format": "{controller_alias}\t{controller_address}\n\n{num_connections} connected",
"tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}",
"tooltip-format-enumerate-connected": "{device_alias}\t{device_address}",
"tooltip-format-enumerate-connected-battery": "{device_alias}\t{device_address}\t{device_battery_percentage}%",
"on-click": "/home/teraflops/.config/waybar/scripts/bt_menu.py",
},
"hyprland/window": {
"max-length": 60,
"format": "<span font='11' rise='-4444'>{}</span>",
"separate-outputs": true,
},
"temperature": {
"hwmon-path": [
"/sys/class/thermal/thermal_zone0/temp"
],
"critical-threshold": 80,
"format-critical": "{temperatureC}°C",
"format": "{temperatureC}°C",
},
"memory": {
"interval": 3,
"format": "  {}% ",
"max-length": 10,
},
"cpu": {
"interval": 1,
"format": "{icon} {usage:>2}%",
"format-icons": [
"􏘭 ",
"􏘬 ",
"􏘩 ",
"􏘪 ",
"􏘫 "
],
},
"power-profiles-daemon": {
"format": "{icon}",
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
"tooltip": true,
"format-icons": {
"default": "BAL",
"performance": "PERF",
"balanced": "BAL",
"power-saver": "PSAV",
},
},
"custom/powermenu": {
"format": "⏻ ",
"tooltip": false,
"on-click": "/home/teraflops/.config/waybar/scripts/wlogout.sh",
},
"tray": {
"icon-size": 18,
"spacing": 10,
},
"custom/calendar_clock": {
"exec": "python3 /home/teraflops/.local/bin/list_events_v5_json.py",
"interval": 120,
"tooltip": true,
"return-type": "json",
},
"network": {
"format-wifi": "{icon}",
"format-disconnected": "󰤭 ",
"format-icons": [
"󰤯 ",
"󰤟 ",
"󰤢 ",
"󰤥 ",
"󰤨 "
],
"tooltip-format-wifi": "{essid}",
"tooltip-format-disconnected": "Disconnected",
"nospacing": 1,
"on-click": "/home/teraflops/.config/waybar/scripts/wifi_menu.py",
},
//"battery": {
// "bat": "BAT1", // Usa el identificador correcto de tu batería
//"interval": 60,
//"format": "{icon}",
//"tooltip": false,
//"format-icons": ["", "", "", "", ""],
//"menu": "on-click",
//"menu-file": "/home/teraflops/.config/waybar/menu/battery_menu.xml",
//"menu-actions": {
// "view_details": "notify send 1",
//"check_health": "notify send 2",
//"activate_saving_mode": "notify send 3",
//},
"battery": {
"interval": 60,
"states": {
"warning": 30,
"critical": 15,
},
"format": "{icon}",
"format-charging": " ",
"format-plugged": "{icon}",
"format-icons": [
" ",
" ",
" ",
" ",
" "
],
"max-length": 18,
"menu": "on-click",
"menu-file": "/home/teraflops/.config/waybar/menu/battery_menu.xml",
"menu-actions": {
"view_details": "/home/teraflops/.config/waybar/scripts/view_battery_details_yad.sh",
"check_health": "/home/teraflops/.config/waybar/scripts/check_battery_health.sh",
},
},
"wireplumber": {
"format": "{icon}",
"nospacing": 1,
"tooltip-format": "Volume : {volume}%",
"format-muted": "󰝟 ",
"format-icons": {
"headphone": " ",
"default": [
"󰕿 ",
"󰖀 ",
"󰕾 "
],
},
"on-click": "/home/teraflops/.config/waybar/scripts/volume_menu.py",
"on-click-right": "pavucontrol",
"on-update": "/usr/bin/pkill -RTMIN+23 waybar",
// "on-update": "/home/teraflops/.local/bin/show_rate.sh",
"scroll-step": 3,
},
"custom/launcher": {
"format": "  ",
"tooltip": false,
"on-click": "/home/teraflops/.config/rofi/launchers/misc/launcher.sh &",
},
}

View File

@ -0,0 +1,386 @@
{
"layer": "top",
"position": "top",
"margin-top": 1,
"margin-left": 1,
"margin-right": 1,
"margin-bottom": 1,
"height": 54,
"spacing": 0,
"modules-left": [
"custom/launcher",
"hyprland/workspaces",
"cpu",
"temperature",
"power-profiles-daemon",
"memory",
"custom/fans",
"custom/weather"
// "custom/updates"
// "custom/mpris-buttons"
],
"modules-center": [
"custom/calendar_clock"
],
"modules-right": [
//"mpd",
//"custom/rate",
// "image#test",
"custom/lyrics",
"mpris",
"custom/roon",
"tray",
"custom/wf-recorder",
//"image#gpu",
"bluetooth",
"network",
"wireplumber",
"custom/pwrate",
"battery",
"custom/powermenu"
//"hyprland/language"
//"pulseaudio/slider",
],
"custom/roon": {
"exec": "cat /tmp/waybar_roon_info.json",
"format": "{text}",
"tooltip": true,
"return-type": "json",
"signal": 3
},
"custom/fans": {
"exec": "~/.config/waybar/scripts/fan_speed.sh",
"format": "{text}",
"markup": "pango",
"return-type": "json",
"on-click": "~/.config/waybar/scripts/fan_speed.sh next",
"on-click-right": "~/.config/waybar/scripts/toggle_fans.sh",
"interval": 5,
"tooltip": true
},
"custom/updates": {
"format": "📦 {}",
"exec": "~/.config/waybar/scripts/updates.sh",
"interval": 18,
"return-type": "json",
"tooltip": true,
"tooltip-format": "{full_text}"
},
"hyprland/language": {
"format-es": "es",
"format-en": "us",
"keyboard-name": "asus-keyboard",
"keyboard-name": "melgeek-mojo68-c1-keyboard",
"keyboard-name": "mwstudio-mmkzoo65-keyboard",
"on-click": "hyprctl switchxkblayout mwstudio-mmkzoo65-keyboard next"
},
"custom/mpris-buttons": {
"exec": "~/.config/waybar/scripts/mpris_buttons.sh",
// "interval": 1,
"return-type": "json",
"on-click": "playerctl play-pause",
"on-click-right": "playerctl next",
"on-click-middle": "playerctl previous",
"signal": 24
},
"pulseaudio/slider": {
"min": 0,
"max": 100,
"orientation": "vertical"
},
"custom/network_speed": {
"exec": "/home/teraflops/.local/bin/network_speed_client.py",
"interval": 1,
"return-type": "json",
"format": "{}",
"tooltip": true,
},
"image#gpu": {
"exec": "/tmp/gpu.sh",
"size": 18,
// "interval": 1,
// "signal": 3,
"tooltip": true,
"on-click": "python /home/teraflops/.local/bin/comandos.py",
},
"custom/lyrics": {
"format": "{text}",
"exec": "/home/teraflops/.config/waybar/scripts/lyrics_module.sh",
"return-type": "json",
"escape": true,
"interval": 10,
"on-empty": "hide",
//"on-click": "termite -t playlist -e ncmpcpp",
"on-click": "/home/teraflops/.config/waybar/scripts/show_cover.py",
//"on-scroll-down": "/home/teraflops/.config/waybar/scripts/lyrics_scroll.sh",
"signal": 9
},
"mpris": {
"format": "{title}",
"format-paused": "⏸ {title}",
"tooltip-format": "{title}\n{artist} ",
//"title-len": 20,
"artist-len": 0,
"album-len": 0,
"dynamic-len": 20,
"ellipsis": "…",
"expand": false,
"max-length": 60,
// "min-length": 25,
"tooltip": true,
//"ignored-players": ["firefox"],
},
"hyprland/workspaces": {
"on-click": "activate",
"format": "{icon}",
"format-icons": {
"default": "",
"1": "",
"2": "",
"3": "",
"4": "",
"active": "󱓻",
"urgent": "󱓻",
},
"persistent-workspaces": {
"*": 4,
},
},
"custom/inetspeedup": {
"format": "{}",
"exec": "/home/teraflops/.config/waybar/scripts/inetup.py",
"on-click": "/home/teraflops/.config/waybar/scripts/inetup.py toggle",
"interval": 10,
"return-type": "json",
"signal": 20,
},
"custom/inetspeeddown": {
"format": "{}",
"exec": "/home/teraflops/.config/waybar/scripts/inetdown.py",
"on-click": "/home/teraflops/.config/waybar/scripts/inetdown.py toggle",
"interval": 10,
"return-type": "json",
"signal": 21,
},
"custom/borg": {
//pkill -RTMIN+12 waybar
"format": "BACKUP RUNNING",
"exec": "echo '{\"class\": \"running\"}'",
"exec-if": "pgrep -x pika-backup",
"return-type": "json",
"signal": 12,
},
"custom/wf-recorder": {
//pkill -RTMIN+11 waybar
"format": "• ",
"exec": "echo '{\"class\": \"recording\"}'",
"exec-if": "pgrep -x wf-recorder",
"return-type": "json",
"signal": 11,
},
"custom/tailscale": {
"format": "VPN",
"exec": "echo '{\"class\": \"vpn\"}'",
"exec-if": "pgrep -x tailscaled",
"return-type": "json",
"signal": 22,
},
"custom/pwrate": {
"exec": "/home/teraflops/.local/bin/pwrate3.sh",
"return-type": "json",
"format": "{}",
"signal": 23
},
"custom/rate": {
//pkill -RTMIN+15 waybar
//"exec": "/home/teraflops/.local/bin/show_rate.sh",
"exec": "python /home/teraflops/.local/bin/now_playing_tooltip.py",
//"exec": "echo '{\"text\":\"🎵\", \"tooltip\":\"Testing Tooltip\"}'", // Simple test module
"tooltip": true,
"return-type": "json",
"escape": false,
"on-click": "python /home/teraflops/.local/bin/now_playing_tooltip.py toggle",
"on-click-right": "pkill -Af now_playing_tooltip.py",
"signal": 15,
"class": "rate-module",
},
"backlight": {
"device": "amdgpu_bl1",
"format": "{icon}",
"format-icons": [
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
],
},
"custom/weather": {
"exec": "python ~/.config/waybar/scripts/weather.py",
"restart-interval": 300,
"return-type": "json",
// "on-click": "xdg-open https://weather.com/en-IN/weather/today/l/$(location_id)"
// "format-alt": "{alt}",
},
"bluetooth": {
"format": "",
"format-connected": "",
"format-connected-battery": "󰂴 ",
"tooltip-format": "{controller_alias}\t{controller_address}\n\n{num_connections} connected",
"tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}",
"tooltip-format-enumerate-connected": "{device_alias}\t{device_address}",
"tooltip-format-enumerate-connected-battery": "{device_alias}\t{device_address}\t{device_battery_percentage}%",
"on-click": "/home/teraflops/.config/waybar/scripts/bt_menu.py",
},
"hyprland/window": {
"max-length": 60,
"format": "<span font='11' rise='-4444'>{}</span>",
"separate-outputs": true,
},
"temperature": {
"hwmon-path": [
"/sys/class/thermal/thermal_zone0/temp"
],
"critical-threshold": 80,
"format": "<span foreground='#fa9f8e'>cpu:</span> <span foreground='#81a1c1'>{temperatureC}°C</span>",
"format-critical": "<span foreground='#fa9f8e'>cpu:</span> <span foreground='#81a1c1'>{temperatureC}°C</span>"
},
"memory": {
"interval": 3,
"format": "<span foreground='#fa9f8e'> </span> <span foreground='#81a1c1'>{}%</span>",
"markup": "pango",
"max-length": 10
},
"cpu": {
"interval": 1,
"format": "<span foreground='#fa9f8e'>cpu:</span> <span foreground='#81a1c1'>{usage}%</span>",
"format-icons": [
"􏘭 ",
"􏘬 ",
"􏘩 ",
"􏘪 ",
"􏘫 "
],
"markup": "pango"
},
"power-profiles-daemon": {
"format": "{icon}",
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
"tooltip": true,
"format-icons": {
"default": "BAL",
"performance": "PERF",
"balanced": "BAL",
"power-saver": "PSAV",
},
},
"custom/powermenu": {
"format": "⏻ ",
"tooltip": false,
"on-click": "/home/teraflops/.config/waybar/scripts/wlogout.sh",
},
"tray": {
"icon-size": 18,
"spacing": 10,
},
"custom/calendar_clock": {
"exec": "python3 /home/teraflops/.local/bin/list_events_v5_json.py",
"interval": 120,
"tooltip": true,
"return-type": "json",
},
"network": {
"format-wifi": "{icon}",
"format-disconnected": "󰤭 ",
"format-icons": [
"󰤯 ",
"󰤟 ",
"󰤢 ",
"󰤥 ",
"󰤨 "
],
"tooltip-format-wifi": "{essid}",
"tooltip-format-disconnected": "Disconnected",
"nospacing": 1,
"on-click": "/home/teraflops/.config/waybar/scripts/wifi_menu.py",
},
//"battery": {
// "bat": "BAT1", // Usa el identificador correcto de tu batería
//"interval": 60,
//"format": "{icon}",
//"tooltip": false,
//"format-icons": ["", "", "", "", ""],
//"menu": "on-click",
//"menu-file": "/home/teraflops/.config/waybar/menu/battery_menu.xml",
//"menu-actions": {
// "view_details": "notify send 1",
//"check_health": "notify send 2",
//"activate_saving_mode": "notify send 3",
//},
"battery": {
"interval": 60,
"states": {
"warning": 30,
"critical": 15,
},
"format": "{icon}",
"format-charging": " ",
"format-plugged": "{icon}",
"format-icons": [
" ",
" ",
" ",
" ",
" "
],
"max-length": 18,
"menu": "on-click",
"menu-file": "/home/teraflops/.config/waybar/menu/battery_menu.xml",
"menu-actions": {
"view_details": "/home/teraflops/.config/waybar/scripts/view_battery_details_yad.sh",
"check_health": "/home/teraflops/.config/waybar/scripts/check_battery_health_yad.sh",
},
},
"wireplumber": {
"format": "{icon}",
"nospacing": 1,
"tooltip-format": "Volume : {volume}%",
"format-muted": "󰝟 ",
"format-icons": {
"headphone": " ",
"default": [
"󰕿 ",
"󰖀 ",
"󰕾 "
],
},
"on-click": "/home/teraflops/.config/waybar/scripts/volume_menu.py",
"on-click-right": "pavucontrol",
"on-update": "/usr/bin/pkill -RTMIN+23 waybar && /usr/bin/pkill -RTMIN+24 waybar ",
// "on-update": "/home/teraflops/.local/bin/show_rate.sh",
"scroll-step": 3,
},
"custom/launcher": {
"format": "  ",
"tooltip": false,
"on-click": "/home/teraflops/.config/rofi/launchers/misc/launcher.sh &",
},
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkMenu" id="menu">
<child>
<object class="GtkMenuItem" id="view_details">
<property name="label">Ver Detalles de la Batería</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="check_health">
<property name="label">Verificar Salud de la Batería</property>
</object>
</child>
</object>
</interface>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkMenu" id="menu">
<!-- Sección para Batería -->
<child>
<object class="GtkMenuItem" id="battery_view_details">
<property name="label">Ver Detalles de la Batería</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="battery_check_health">
<property name="label">Verificar Salud de la Batería</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="battery_activate_saving_mode">
<property name="label">Activar Modo de Ahorro de Batería</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="battery_delimiter"/>
</child>
<!-- Sección para Wireplumber -->
<child>
<object class="GtkMenuItem" id="wireplumber_mute_unmute">
<property name="label">Mute/Unmute</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="wireplumber_create_sink">
<property name="label">Crear Sink</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="wireplumber_set_default_sink">
<property name="label">Establecer Sink Predeterminado</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="wireplumber_open_pavucontrol">
<property name="label">Abrir Pavucontrol</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="wireplumber_delimiter"/>
</child>
</object>
</interface>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkMenu" id="menu">
<child>
<object class="GtkMenuItem" id="mute/unmute">
<property name="label">Ver Detalles de la Batería</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="create sink">
<property name="label">Verificar Salud de la Batería</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="set default sink">
<property name="label">Crear Sink</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="open pavucontrol">
<property name="label">Abrir Pavucontrol</property>
</object>
</child>
</object>
</interface>

View File

@ -0,0 +1,23 @@
##[pylyzer] failed /home/teraflops/.config/waybar/scripts/tiling.py 1731075378 1588
.___v_desugar_1 = pyimport "<failure>"
.<failure> = pyimport "<failure>"
.Gtk: Never
.subprocess = pyimport "<failure>"
.gi = pyimport "__init__"
.__init__ = pyimport "__init__"
.layouts: Dict!({{"vertical"}: {"hyprctl dispatch layout vert"}, {"horizontal"}: {"hyprctl dispatch layout horiz"}, {"floating"}: {"hyprctl dispatch layout float"}, {"tabbed"}: {"hyprctl dispatch layout tabbed"}})
.LayoutSelector: ClassType
.LayoutSelector <: Never
.LayoutSelector.__call__: () -> tiling.LayoutSelector
.LayoutSelector.create_button: (self: tiling.LayoutSelector, label: Obj, image_path: Obj, command: Obj, grid: Obj, row: Obj, col: Obj) -> Never
.LayoutSelector.on_button_clicked: (self: tiling.LayoutSelector, widget: Obj, command: Obj) -> Never
tiling = pyimport "tiling"
.win: .LayoutSelector

View File

@ -0,0 +1,5 @@
#!/bin/bash
# Reducir el brillo al 50% (asegúrate de tener instalada xbacklight)
xbacklight -set 50
zenity --info --text="Modo de Ahorro Activado:\nBrillo reducido al 50%." --title="Modo de Ahorro Activado"

View File

@ -0,0 +1,37 @@
#!/bin/bash
# Obtiene las ventanas abiertas en el espacio de trabajo actual
windows=$(hyprctl clients -j | jq -r '.[] | .address')
# Cuenta las ventanas abiertas
window_count=$(echo "$windows" | wc -l)
# Define las posiciones y tamaños de cada ventana según la cuadrícula
case $window_count in
1)
hyprctl dispatch movewindow 50% 50% resizewindowpixel 100% 100% ;;
2)
hyprctl dispatch focuswindow $(echo "$windows" | sed -n 1p)
hyprctl dispatch movewindow 0 0 resizewindowpixel 50% 100%
hyprctl dispatch focuswindow $(echo "$windows" | sed -n 2p)
hyprctl dispatch movewindow 50% 0 resizewindowpixel 50% 100% ;;
3)
# Configura tres ventanas, dos arriba y una en la mitad inferior
hyprctl dispatch focuswindow $(echo "$windows" | sed -n 1p)
hyprctl dispatch movewindow 0 0 resizewindowpixel 50% 50%
hyprctl dispatch focuswindow $(echo "$windows" | sed -n 2p)
hyprctl dispatch movewindow 50% 0 resizewindowpixel 50% 50%
hyprctl dispatch focuswindow $(echo "$windows" | sed -n 3p)
hyprctl dispatch movewindow 0 50% resizewindowpixel 100% 50% ;;
4)
# Configura cuatro ventanas en una cuadrícula de 2x2
hyprctl dispatch focuswindow $(echo "$windows" | sed -n 1p)
hyprctl dispatch movewindow 0 0 resizewindowpixel 50% 50%
hyprctl dispatch focuswindow $(echo "$windows" | sed -n 2p)
hyprctl dispatch movewindow 50% 0 resizewindowpixel 50% 50%
hyprctl dispatch focuswindow $(echo "$windows" | sed -n 3p)
hyprctl dispatch movewindow 0 50% resizewindowpixel 50% 50%
hyprctl dispatch focuswindow $(echo "$windows" | sed -n 4p)
hyprctl dispatch movewindow 50% 50% resizewindowpixel 50% 50% ;;
esac

View File

@ -0,0 +1,76 @@
#!/usr/bin/env python3
import sys
import subprocess
def view_details():
try:
details = subprocess.check_output(
["upower", "-i", "/org/freedesktop/UPower/devices/battery_BAT1"],
text=True
)
# Extraer información relevante si es necesario
subprocess.run(["notify-send", "Detalles de la Batería", details])
except subprocess.CalledProcessError as e:
subprocess.run(
["notify-send", "Error", "No se pudieron obtener los detalles de la batería."])
def check_health():
try:
health = subprocess.check_output(
["upower", "-i", "/org/freedesktop/UPower/devices/battery_BAT1"],
text=True
)
# Extraer información específica sobre la salud
energy_full = ""
for line in health.splitlines():
if "energy-full:" in line:
energy_full = line.split(":", 1)[1].strip()
break
if energy_full:
subprocess.run(["notify-send", "Salud de la Batería",
f"La capacidad máxima actual es {energy_full}."])
else:
subprocess.run(["notify-send", "Salud de la Batería",
"No se pudo determinar la capacidad máxima."])
except subprocess.CalledProcessError as e:
subprocess.run(
["notify-send", "Error", "No se pudo verificar la salud de la batería."])
def save_mode():
try:
# Reducir el brillo al 50% (asegúrate de tener xbacklight instalado)
subprocess.run(["xbacklight", "-set", "50"])
subprocess.run(
["notify-send", "Modo de Ahorro Activado", "Brillo reducido al 50%."])
except Exception as e:
subprocess.run(
["notify-send", "Error", "No se pudo activar el modo de ahorro."])
def main():
if len(sys.argv) < 2:
subprocess.run(
["notify-send", "Error", "No se proporcionó ninguna acción."])
sys.exit(1)
action = sys.argv[1]
if action == "view_details":
view_details()
elif action == "check_health":
check_health()
elif action == "save_mode":
save_mode()
else:
subprocess.run(
["notify-send", "Error", f"Acción desconocida: {action}"])
sys.exit(1)
if __name__ == "__main__":
main()

304
.config/waybar/scripts/bt_menu.py Executable file
View File

@ -0,0 +1,304 @@
#!/usr/bin/env python3
from gi.repository import Gtk, GLib, Gio
import threading
from pydbus import SystemBus
import subprocess
class BluetoothMenu(Gtk.Application):
def __init__(self):
super().__init__(application_id="org.example.BluetoothMenu")
self.bus = SystemBus()
self.adapter = self.bus.get("org.bluez", "/org/bluez/hci0")
self.object_manager = self.bus.get("org.bluez", "/")
self.window = None
self.device_window = None
self.scanning = False
self.discovered_devices = {}
def do_activate(self):
if not self.window:
self.window = Gtk.ApplicationWindow(application=self)
self.window.set_default_size(200, 150)
self.window.set_title("Bluetooth Menu")
self.vbox = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=6)
if hasattr(self.window, "set_child"):
self.window.set_child(self.vbox)
else:
self.window.add(self.vbox)
self.build_menu()
self.update_bluetooth_buttons()
self.window.present()
def build_menu(self):
self.item_scan = Gtk.Button(label='Escanear Dispositivos')
self.item_scan.connect('clicked', self.scan_and_show_devices)
self.add_to_box(self.vbox, self.item_scan)
self.item_on = Gtk.Button(label='Encender Bluetooth')
self.item_on.connect('clicked', self.turn_on_bluetooth)
self.add_to_box(self.vbox, self.item_on)
self.item_off = Gtk.Button(label='Apagar Bluetooth')
self.item_off.connect('clicked', self.turn_off_bluetooth)
self.add_to_box(self.vbox, self.item_off)
self.item_send_file = Gtk.Button(label='Enviar Archivo')
self.item_send_file.connect('clicked', self.send_file_dialog)
self.add_to_box(self.vbox, self.item_send_file)
item_quit = Gtk.Button(label='Cerrar')
item_quit.connect('clicked', self.exit_app)
self.add_to_box(self.vbox, item_quit)
def add_to_box(self, box, widget):
if hasattr(box, "append"):
box.append(widget)
else:
box.pack_start(widget, True, True, 0)
def send_file_dialog(self, _):
dialog = Gtk.FileChooserDialog(
title="Selecciona un archivo para enviar",
transient_for=self.window,
action=Gtk.FileChooserAction.OPEN,
modal=True
)
dialog.add_buttons(
"Cancelar", Gtk.ResponseType.CANCEL,
"Abrir", Gtk.ResponseType.OK
)
dialog.connect("response", self.on_file_chosen)
dialog.show()
def on_file_chosen(self, dialog, response):
if response == Gtk.ResponseType.OK:
file = dialog.get_file() # Obtenemos el archivo directamente
if file:
file_path = file.get_path() # Obtenemos la ruta del archivo
# Oculta la ventana principal antes de abrir la ventana de selección de dispositivos
self.window.hide()
# Llama a la función para seleccionar el dispositivo y enviar el archivo
self.select_device_for_file_transfer(file_path)
dialog.destroy()
def select_device_for_file_transfer(self, file_path):
if self.device_window:
self.device_window.destroy()
self.device_window = Gtk.Window(title="Seleccionar dispositivo")
self.device_window.set_default_size(300, 400)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.device_window.set_child(vbox)
devices = self.get_discovered_devices()
for name, address in devices:
button = Gtk.Button(label=f"{name} ({address})")
button.connect("clicked", self.send_file_to_device,
file_path, address)
vbox.append(button)
# Añade un botón "Cerrar" al final de la ventana de selección de dispositivos
close_button = Gtk.Button(label="Cerrar")
close_button.connect("clicked", lambda _: self.device_window.destroy())
vbox.append(close_button)
# Muestra la ventana de selección de dispositivos
self.device_window.show()
# Restaura la ventana principal al cerrar la ventana de selección de dispositivos
self.device_window.connect("destroy", lambda w: self.window.show())
def send_file_to_device(self, button, file_path, address):
try:
# Ejecuta bluetooth-sendto con la dirección del dispositivo y el archivo
result = subprocess.run(
['bluetooth-sendto', '--device=' + address, file_path],
capture_output=True,
text=True
)
# Verifica si se produjo un error en la ejecución
if result.returncode == 0:
self.show_notification(f"Archivo enviado a {address}")
else:
self.show_notification(
f"Error al enviar archivo: {result.stderr}")
except Exception as e:
self.show_notification(f"Error al ejecutar bluetooth-sendto: {e}")
def update_bluetooth_buttons(self):
powered = self.adapter.Powered
self.item_on.set_sensitive(not powered)
self.item_off.set_sensitive(powered)
def scan_and_show_devices(self, _):
try:
# Inicia el escaneo solo si no está en proceso
if not self.scanning:
self.adapter.StartDiscovery()
self.scanning = True
self.show_notification(
"Escaneo de dispositivos Bluetooth iniciado")
# Muestra la ventana de dispositivos en tiempo real
self.show_device_list_window()
except Exception as e:
self.show_notification(f"Error iniciando el escaneo: {e}")
def show_device_list_window(self):
if self.device_window:
self.device_window.destroy()
self.device_window = Gtk.Window(title="Dispositivos Disponibles")
self.device_window.set_default_size(400, 500)
self.device_window.set_resizable(True)
self.device_window.set_transient_for(self.window)
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.set_min_content_height(400)
self.device_list_vbox = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=10)
if hasattr(scrolled_window, "set_child"):
scrolled_window.set_child(self.device_list_vbox)
else:
scrolled_window.add(self.device_list_vbox)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.add_to_box(vbox, scrolled_window)
refresh_button = Gtk.Button(label="Refrescar")
refresh_button.connect("clicked", self.refresh_device_list)
self.add_to_box(vbox, refresh_button)
close_button = Gtk.Button(label="Cerrar")
close_button.connect("clicked", self.stop_scan_and_close)
self.add_to_box(vbox, close_button)
if hasattr(self.device_window, "set_child"):
self.device_window.set_child(vbox)
else:
self.device_window.add(vbox)
self.device_window.present()
# Asegura que el temporizador se ejecute para actualizar la lista en tiempo real
if not hasattr(self, 'update_devices_timer'):
self.update_devices_timer = GLib.timeout_add_seconds(
2, self.update_device_list)
def get_discovered_devices(self):
devices = []
managed_objects = self.object_manager.GetManagedObjects()
for path, interfaces in managed_objects.items():
if "org.bluez.Device1" in interfaces:
device_properties = interfaces["org.bluez.Device1"]
if device_properties.get("Connected") or device_properties.get("Paired"):
# Omitir dispositivos emparejados o ya conectados
continue
name = device_properties.get("Name", "Desconocido")
address = device_properties.get("Address")
devices.append((name, address))
return devices
def refresh_device_list(self, _):
self.discovered_devices.clear()
if self.device_list_vbox and self.device_window:
self.clear_box_children(self.device_list_vbox)
self.show_notification("Lista de dispositivos actualizada")
def clear_box_children(self, box):
if hasattr(box, 'get_children'):
for child in box.get_children():
box.remove(child)
else:
child = box.get_first_child()
while child:
next_child = child.get_next_sibling()
box.remove(child)
child = next_child
def update_device_list(self):
devices = self.get_discovered_devices()
for name, address in devices:
if address not in self.discovered_devices:
self.discovered_devices[address] = name
if self.device_window and self.device_list_vbox:
button = Gtk.Button(label=f"{name} ({address})")
button.connect("clicked", self.pair_trust_connect, address)
self.add_to_box(self.device_list_vbox, button)
return True
def stop_scan_and_close(self, _):
if self.device_window:
self.device_window.destroy()
self.device_window = None
self.discovered_devices.clear()
def get_discovered_devices(self):
devices = []
managed_objects = self.object_manager.GetManagedObjects()
for path, interfaces in managed_objects.items():
if "org.bluez.Device1" in interfaces:
device_properties = interfaces["org.bluez.Device1"]
name = device_properties.get("Name", "Desconocido")
address = device_properties.get("Address")
devices.append((name, address))
return devices
def pair_trust_connect(self, button, address):
threading.Thread(target=self.pair_trust_connect_thread,
args=(address,)).start()
def pair_trust_connect_thread(self, address):
device_path = f"/org/bluez/hci0/dev_{address.replace(':', '_')}"
device = self.bus.get("org.bluez", device_path)
try:
device.Pair()
device.Trusted = True
device.Connect()
self.show_notification(f"Conectado a {address}")
except Exception as e:
self.show_notification(f"Error al conectar con {address}: {e}")
def turn_on_bluetooth(self, _):
self.adapter.Powered = True
self.show_notification("Bluetooth Encendido")
self.update_bluetooth_buttons()
def turn_off_bluetooth(self, _):
self.adapter.Powered = False
self.show_notification("Bluetooth Apagado")
self.update_bluetooth_buttons()
def exit_app(self, _):
if self.scanning:
self.adapter.StopDiscovery()
self.scanning = False
if hasattr(self, 'update_devices_timer'):
GLib.source_remove(self.update_devices_timer)
self.quit()
def show_notification(self, message):
subprocess.run(['notify-send', message])
def main():
app = BluetoothMenu()
app.run()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,4 @@
#!/bin/bash
HEALTH=$(upower -i /org/freedesktop/UPower/devices/battery_BAT1 | grep "energy-full")
zenity --info --text="Salud de la Batería:\nLa capacidad máxima actual es $HEALTH." --title="Salud de la Batería"

View File

@ -0,0 +1,9 @@
{
"roonstate": {
"tokens": {
"4000ff7f-2284-4810-9e5d-a0a7b1f7bc1a": "a979118e-66c9-48a0-8eae-c6899d5dca81",
"0754ce41-74ac-44bb-a9d5-e47d8f377f46": "77528fe8-8f9e-422b-84e7-794bfe5d8739"
},
"paired_core_id": "0754ce41-74ac-44bb-a9d5-e47d8f377f46"
}
}

View File

@ -0,0 +1,51 @@
from roonapi import RoonApi
import time
import subprocess
appinfo = {
"extension_id": "python_roon_waybar",
"display_name": "Waybar Roon Extension",
"display_version": "1.0.0",
"publisher": "gregd",
"email": "mygreat@emailaddress.com",
}
# Lee el core_id y el token desde los archivos
try:
core_id = open("/home/teraflops/apikeys/roon_core_id").read().strip()
token = open("/home/teraflops/apikeys/roon_core_token").read().strip()
except FileNotFoundError:
print("Error: core_id o token no encontrados. Debes autorizar usando discovery.py primero.")
exit()
# Conéctate a Roon usando el core_id y el token guardados
roonapi = RoonApi(appinfo, token, "192.168.1.37", 9330, True)
def my_state_callback(event, changed_ids):
"""Llamado cuando algo cambia en Roon."""
for zone_id in changed_ids:
zone = roonapi.zones[zone_id]
if zone['state'] == 'playing':
track_name = zone['now_playing']['three_line']['line1']
artist_name = zone['now_playing']['three_line']['line2']
print(f"Canción en reproducción: {track_name} - {artist_name}")
# Actualiza Waybar usando un archivo de texto o variable
with open("/tmp/waybar_roon_track", "w") as f:
f.write(f"{track_name} - {artist_name}")
# Envía notificación de la canción
subprocess.run([
"notify-send", "Roon Now Playing",
f"{track_name} - {artist_name}"
])
# Registra el callback para recibir actualizaciones
roonapi.register_state_callback(my_state_callback)
# Mantén el script corriendo para escuchar cambios
while True:
time.sleep(10)

View File

@ -0,0 +1,35 @@
#!/bin/bash
IP="192.168.1.140"
ENDPOINT="http://$IP:9529/ZidooMusicControl/v2/getState"
data=$(curl -s --max-time 2 "$ENDPOINT")
if [[ -n "$data" ]]; then
artist=$(echo "$data" | jq -r '.playingMusic.artist')
title=$(echo "$data" | jq -r '.playingMusic.title')
status=$(echo "$data" | jq -r '.everSoloPlayInfo.playStatus')
# Detalles extra para el tooltip
bitrate=$(echo "$data" | jq -r '.playingMusic.bitrate')
samplerate=$(echo "$data" | jq -r '.playingMusic.sampleRate')
quality=$(echo "$data" | jq -r '.playingMusic.audioQuality')
# Escapar caracteres especiales
artist="${artist//&/&amp;}"
title="${title//&/&amp;}"
bitrate="${bitrate//&/&amp;}"
samplerate="${samplerate//&/&amp;}"
quality="${quality//&/&amp;}"
tooltip="<b>${artist} - ${title}</b>\n${quality} | ${samplerate} | ${bitrate}"
if [[ "$status" == "1" && "$artist" != "null" && "$title" != "null" ]]; then
echo "{\"text\": \"🎵 $artist - $title\", \"tooltip\": \"$tooltip\"}"
else
echo "{\"text\": \"⏸️ Pausado o sin música\", \"tooltip\": \"No se está reproduciendo música.\"}"
fi
else
echo "{\"text\": \"⚠️ DMP-A6 desconectado\", \"tooltip\": \"No se pudo conectar al Eversolo.\"}"
fi

View File

@ -0,0 +1 @@
⬇ 548.60 Mbps

View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
STATE_FILE="/tmp/fancycle_state"
HWMON_PATH="/sys/class/hwmon/hwmon3" # Ajusta según tu caso
# Si no existe el archivo de estado, inícialo en 1 (fan1)
if [[ ! -f "$STATE_FILE" ]]; then
echo 1 > "$STATE_FILE"
fi
# Si el script es invocado con el argumento "next", incrementa el estado
if [[ "$1" == "next" ]]; then
current_state=$(cat "$STATE_FILE")
# Suponiendo que tienes 3 ventiladores y quieres ciclar 1->2->3->1->...
next_state=$(( (current_state % 3) + 1 ))
echo "$next_state" > "$STATE_FILE"
fi
# Lee el estado actual
state=$(cat "$STATE_FILE")
# Lee la info real de tus ventiladores
# Lee la info real de tus ventiladores
fan1_label=$(cat "$HWMON_PATH/fan1_label" 2>/dev/null | sed 's/_fan//I' | tr '[:lower:]' '[:upper:]')
fan1_speed=$(cat "$HWMON_PATH/fan1_input" 2>/dev/null)
fan2_label=$(cat "$HWMON_PATH/fan2_label" 2>/dev/null | sed 's/_fan//I' | tr '[:lower:]' '[:upper:]')
fan2_speed=$(cat "$HWMON_PATH/fan2_input" 2>/dev/null)
fan3_label=$(cat "$HWMON_PATH/fan3_label" 2>/dev/null | sed 's/_fan//I' | tr '[:lower:]' '[:upper:]')
fan3_speed=$(cat "$HWMON_PATH/fan3_input" 2>/dev/null)
# Prepara el tooltip con TODOS los ventiladores
tooltip="${fan1_label}: ${fan1_speed} RPM
${fan2_label}: ${fan2_speed} RPM
${fan3_label}: ${fan3_speed} RPM"
# Según el "estado", decide cuál ventilador se muestra en "text"
case "$state" in
1) text="${fan1_label}: ${fan1_speed} RPM" ;;
2) text="${fan2_label}: ${fan2_speed} RPM" ;;
3) text="${fan3_label}: ${fan3_speed} RPM" ;;
esac
# Usa jq para generar JSON
jq -c -n \
--arg text "$text" \
--arg tooltip "$tooltip" \
'{ "text": $text, "tooltip": $tooltip }'

View File

@ -0,0 +1,49 @@
#!/usr/bin/env bash
STATE_FILE="/tmp/fancycle_state"
HWMON_PATH="/sys/class/hwmon/hwmon3" # Ajusta según tu caso
# Si no existe el archivo de estado, inícialo en 1 (fan1)
if [[ ! -f "$STATE_FILE" ]]; then
echo 1 > "$STATE_FILE"
fi
# Si el script es invocado con el argumento "next", incrementa el estado
if [[ "$1" == "next" ]]; then
current_state=$(cat "$STATE_FILE")
# Suponiendo que tienes 3 ventiladores y quieres ciclar 1->2->3->1->...
next_state=$(( (current_state % 3) + 1 ))
echo "$next_state" > "$STATE_FILE"
fi
# Lee el estado actual
state=$(cat "$STATE_FILE")
# Lee la info real de tus ventiladores
fan1_label=$(cat "$HWMON_PATH/fan1_label" 2>/dev/null)
fan1_speed=$(cat "$HWMON_PATH/fan1_input" 2>/dev/null)
fan2_label=$(cat "$HWMON_PATH/fan2_label" 2>/dev/null)
fan2_speed=$(cat "$HWMON_PATH/fan2_input" 2>/dev/null)
fan3_label=$(cat "$HWMON_PATH/fan3_label" 2>/dev/null)
fan3_speed=$(cat "$HWMON_PATH/fan3_input" 2>/dev/null)
# Prepara el tooltip con TODOS los ventiladores
tooltip="${fan1_label}: ${fan1_speed} RPM
${fan2_label}: ${fan2_speed} RPM
${fan3_label}: ${fan3_speed} RPM"
# Según el "estado", decide cuál ventilador se muestra en "text"
case "$state" in
1) text="${fan1_label}: ${fan1_speed} RPM" ;;
2) text="${fan2_label}: ${fan2_speed} RPM" ;;
3) text="${fan3_label}: ${fan3_speed} RPM" ;;
esac
# Usa jq para generar JSON
jq -c -n \
--arg text "$text" \
--arg tooltip "$tooltip" \
'{ "text": $text, "tooltip": $tooltip }'

View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
STATE_FILE="/tmp/fancycle_state"
HWMON_PATH="/sys/class/hwmon/hwmon3" # Ajusta según tu caso
# Si no existe el archivo de estado, inícialo en 1 (fan1)
if [[ ! -f "$STATE_FILE" ]]; then
echo 1 > "$STATE_FILE"
fi
# Si el script es invocado con el argumento "next", incrementa el estado
if [[ "$1" == "next" ]]; then
current_state=$(cat "$STATE_FILE")
next_state=$(( (current_state % 3) + 1 ))
echo "$next_state" > "$STATE_FILE"
fi
state=$(cat "$STATE_FILE")
# Lee info de los ventiladores
fan1_label=$(cat "$HWMON_PATH/fan1_label" 2>/dev/null | sed 's/_fan//I')
fan1_speed=$(cat "$HWMON_PATH/fan1_input" 2>/dev/null)
fan2_label=$(cat "$HWMON_PATH/fan2_label" 2>/dev/null | sed 's/_fan//I')
fan2_speed=$(cat "$HWMON_PATH/fan2_input" 2>/dev/null)
fan3_label=$(cat "$HWMON_PATH/fan3_label" 2>/dev/null | sed 's/_fan//I')
fan3_speed=$(cat "$HWMON_PATH/fan3_input" 2>/dev/null)
tooltip="${fan1_label}: ${fan1_speed} RPM
${fan2_label}: ${fan2_speed} RPM
${fan3_label}: ${fan3_speed} RPM"
# Determinar texto con color según estado
case "$state" in
1)
text="<span foreground='#fa9f8e'>${fan1_label}:</span> <span foreground='#81a1c1'>${fan1_speed} RPM</span>"
;;
2)
text="<span foreground='#fa9f8e'>${fan2_label}:</span> <span foreground='#81a1c1'>${fan2_speed} RPM</span>"
;;
3)
text="<span foreground='#fa9f8e'>${fan3_label}:</span> <span foreground='#81a1c1'>${fan3_speed} RPM</span>"
;;
esac
jq -c -n \
--arg text "$text" \
--arg tooltip "$tooltip" \
'{ "text": $text, "tooltip": $tooltip }'

View File

@ -0,0 +1,49 @@
import time
from roonapi import RoonApi
appinfo = {
"extension_id": "python_roon_waybar",
"display_name": "Waybar Roon Extension",
"display_version": "1.0.0",
"publisher": "gregd",
"email": "mygreat@emailaddress.com",
}
core_id = open("/home/teraflops/apikeys/roon_core_id").read()
token = open("/home/teraflops/apikeys/roon_core_token").read()
roonapi = RoonApi(appinfo, token, "192.168.1.37", "9330", True)
def send_album_cover_notification(zone):
"""Envía una notificación con la carátula del álbum."""
album_cover_url = zone['now_playing']['image_key']
response = requests.get(
f"http://{roonapi.host}:{roonapi.port}/image/{album_cover_url}")
if response.status_code == 200:
cover_path = "/tmp/roon_album_cover.jpg"
with open(cover_path, "wb") as f:
f.write(response.content)
# Envía la notificación usando notify-send
subprocess.run([
"notify-send", "-i", cover_path, "Now Playing",
f"{zone['now_playing']['three_line']['line1']
} - {zone['now_playing']['three_line']['line2']}"
])
def get_current_track():
"""Obtiene la canción actual que está sonando en Roon."""
zones = roonapi.zones
for zone in zones.values():
if zone['state'] == 'playing':
track_name = zone['now_playing']['three_line']['line1']
artist_name = zone['now_playing']['three_line']['line2']
return f"{track_name} - {artist_name}"
return "No track playing"
if __name__ == "__main__":
print(get_current_track())

View File

@ -0,0 +1,70 @@
import sys
import requests
import json
import os
with open(os.path.expanduser("~/apikeys/musixmatch"), "r") as file:
musixmatch_token = file.read().strip()
def get_lyrics(artist, song_title):
search_url = "https://api.musixmatch.com/ws/1.1/track.search"
params = {
"q_track_artist": f"{artist} {song_title}",
"apikey": musixmatch_token,
"s_track_rating": "desc",
"f_has_lyrics": "1"
}
response = requests.get(search_url, params=params)
if response.status_code != 200:
return f"Error al buscar la canción: {response.status_code}"
data = response.json()
tracks = data.get("message", {}).get("body", {}).get("track_list", [])
if not tracks:
return "No results found for this track."
track_id = None
for t in tracks:
t_title = t["track"]["track_name"].lower()
t_artist = t["track"]["artist_name"].lower()
if song_title.lower() in t_title and artist.lower() in t_artist:
track_id = t["track"]["track_id"]
print("\n Filtered search result:")
print("Track found:", t["track"]["track_name"])
print("Artist foung:", t["track"]["artist_name"])
print("Track ID:", track_id)
print("---")
break
if not track_id:
track_id = tracks[0]["track"]["track_id"]
print("\n generic search result:")
print("Title found:", tracks[0]["track"]["track_name"])
print("Artist found:", tracks[0]["track"]["artist_name"])
print("Track ID:", track_id)
print("---")
lyrics_url = "https://api.musixmatch.com/ws/1.1/track.lyrics.get"
lyrics_params = {
"track_id": track_id,
"apikey": musixmatch_token
}
lyrics_response = requests.get(lyrics_url, params=lyrics_params)
if lyrics_response.status_code != 200:
return f"Error obtaining lyrics: {lyrics_response.status_code}"
lyrics_data = lyrics_response.json()
lyrics = lyrics_data.get("message", {}).get("body", {}).get("lyrics", {}).get("lyrics_body", "")
return lyrics or "no lyrics available."
if len(sys.argv) != 3:
print("Usage: python get_lyrics.py <artist> <song>")
sys.exit(1)
artist = sys.argv[1]
song_title = sys.argv[2]
lyrics = get_lyrics(artist, song_title)
print(lyrics)

View File

@ -0,0 +1,28 @@
#!/bin/bash
# Rutas y archivos temporales
CURRENT_TRACK_FILE="/tmp/current_track.txt"
LYRICS_FILE="/tmp/lyrics.txt"
LOG_FILE="/tmp/lyrics_error.log"
# Obtener la información actual del track usando `playerctl`
TRACK_INFO=$(playerctl metadata --format "{{artist}} - {{title}}" 2>>"$LOG_FILE")
# Validar si hay información del track
if [ -z "$TRACK_INFO" ]; then
echo "No track info available" > "$LYRICS_FILE"
exit 1
fi
# Extraer artista y título
ARTIST=$(echo "$TRACK_INFO" | cut -d '-' -f 1 | xargs)
TITLE=$(echo "$TRACK_INFO" | cut -d '-' -f 2 | xargs)
# Usar Python para obtener las letras
python3 ~/.config/waybar/scripts/get_lyrics.py "$ARTIST" "$TITLE" > "$LYRICS_FILE"
# Verificar si el archivo de letras está vacío
if [ ! -s "$LYRICS_FILE" ]; then
echo "Letras no encontradas para $ARTIST - $TITLE" > "$LYRICS_FILE"
fi

View File

@ -0,0 +1,4 @@
#!/bin/bash
profile = $(asusctl profile -p |awk '{print $4}')

2
.config/waybar/scripts/gpu.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
echo "/home/teraflops/Icons/nvidia.png"

View File

@ -0,0 +1,64 @@
#!/usr/bin/python3
import json
import os
import sys
def read_value_from_file(file_path):
try:
with open(file_path, 'r') as file:
value = file.read().strip()
return value
except Exception as e:
return f"Error: {e}"
def read_click_state(state_file):
try:
if os.path.exists(state_file):
with open(state_file, 'r') as file:
state = file.read().strip()
return state == "clicked"
else:
return False
except Exception as e:
return False
def toggle_click_state(state_file):
try:
if read_click_state(state_file):
with open(state_file, 'w') as file:
file.write("unclicked")
else:
with open(state_file, 'w') as file:
file.write("clicked")
except Exception as e:
pass
def generate_waybar_json(value, clicked):
if clicked:
data = {
"text": value,
#"tooltip": f"Download Speed: {value}",
#"class": "custom"
}
else:
data = {
"text": "DL",
"tooltip": f"Dpwnload Speed: {value}",
"class": "custom"
}
return json.dumps(data)
if __name__ == "__main__":
file_path = "/home/teraflops/.config/waybar/scripts/down.txt"
state_file = "/tmp/waybar_tooltip_state"
value = read_value_from_file(file_path)
# Check for the toggle argument
if len(sys.argv) > 1 and sys.argv[1] == "toggle":
toggle_click_state(state_file)
clicked = read_click_state(state_file)
waybar_json = generate_waybar_json(value, clicked)
print(waybar_json)

View File

@ -0,0 +1,64 @@
#!/usr/bin/python3
import json
import os
import sys
def read_value_from_file(file_path):
try:
with open(file_path, 'r') as file:
value = file.read().strip()
return value
except Exception as e:
return f"Error: {e}"
def read_click_state(state_file):
try:
if os.path.exists(state_file):
with open(state_file, 'r') as file:
state = file.read().strip()
return state == "clicked"
else:
return False
except Exception as e:
return False
def toggle_click_state(state_file):
try:
if read_click_state(state_file):
with open(state_file, 'w') as file:
file.write("unclicked")
else:
with open(state_file, 'w') as file:
file.write("clicked")
except Exception as e:
pass
def generate_waybar_json(value, clicked):
if clicked:
data = {
"text": value,
#"tooltip": f"Upload Speed: {value}",
#"class": "custom"
}
else:
data = {
"text": "UP",
"tooltip": f"Upload Speed: {value}",
"class": "custom"
}
return json.dumps(data)
if __name__ == "__main__":
file_path = "/home/teraflops/.config/waybar/scripts/up.txt"
state_file = "/tmp/waybar_tooltip_state"
value = read_value_from_file(file_path)
# Check for the toggle argument
if len(sys.argv) > 1 and sys.argv[1] == "toggle":
toggle_click_state(state_file)
clicked = read_click_state(state_file)
waybar_json = generate_waybar_json(value, clicked)
print(waybar_json)

View File

@ -0,0 +1,154 @@
# lastfm_cover_downloader.py
import os
import requests
import urllib.parse
from pathlib import Path
import logging
# Configuración básica de logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Obtener la ruta de la clave de API de Last.fm desde una variable de entorno
API_KEY_FILE = os.environ.get(
'LASTFM_API_KEY_PATH', os.path.expandvars('$HOME/apikeys/lastfm'))
def get_lastfm_api_key():
"""Leer la clave de API de Last.fm desde un archivo."""
try:
with open(API_KEY_FILE, 'r') as file:
api_key = file.readline().strip()
logger.info(f"Successfully read Last.fm API key from {
API_KEY_FILE}")
return api_key
except FileNotFoundError:
logger.error(f"API key file '{API_KEY_FILE}' not found.")
raise
except Exception as e:
logger.error(f"Error reading API key: {e}")
raise
# Obtener la clave de API de Last.fm
LASTFM_API_KEY = get_lastfm_api_key()
if not LASTFM_API_KEY:
raise RuntimeError("Last.fm API key could not be loaded.")
def get_largest_cover_image(data):
"""Seleccionar la imagen de portada más grande disponible en la respuesta de Last.fm."""
if 'album' in data and 'image' in data['album']:
# Definir el orden de prioridad de tamaños
size_priority = ["mega", "extralarge", "large", "medium", "small"]
for size in size_priority:
for img in data['album']['image']:
if img["size"] == size and img["#text"]:
logger.info(f"Using {size} cover: {img['#text']}")
return img["#text"]
return None
def download_image(cover_url, output_path):
"""Descargar la imagen de portada desde una URL y guardarla en la ruta especificada."""
try:
logger.info(f"Downloading image from {cover_url} to {output_path}")
response = requests.get(cover_url, stream=True)
if response.status_code == 200:
with open(output_path, 'wb') as f:
for chunk in response.iter_content(1024):
f.write(chunk)
logger.info(f"Image downloaded to {output_path}")
return output_path
else:
logger.error(f"Failed to download image. HTTP Status: {
response.status_code}")
raise Exception(f"HTTP Status {response.status_code}")
except Exception as e:
logger.error(f"Error downloading image: {e}")
raise
def download_cover_from_lastfm(artist, album, album_dir):
"""Descargar la portada del álbum desde Last.fm y guardarla en el directorio del álbum."""
try:
logger.info(f"Fetching cover for '{album}' by {
artist} from Last.fm...")
# URL de la API de Last.fm
url = f"http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={LASTFM_API_KEY}&artist={
urllib.parse.quote(artist)}&album={urllib.parse.quote(album)}&format=json"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
cover_url = get_largest_cover_image(data)
if cover_url:
# Asegurar que el directorio existe
album_dir.mkdir(parents=True, exist_ok=True)
local_cover_path = album_dir / "cover.jpg" # Guardar como 'cover.jpg'
download_image(cover_url, local_cover_path)
logger.info(f"Downloaded cover to {local_cover_path}")
return local_cover_path
logger.warning(f"No cover found for '{album}' by {artist} on Last.fm.")
raise Exception(f"No cover found for '{album}' by {artist}.")
except Exception as e:
logger.error(f"Error fetching cover from Last.fm: {e}")
raise
def cover_exists(album_dir):
"""Verificar si 'cover.jpg' o 'Cover.jpg' existe en el directorio del álbum."""
cover_path_lower = album_dir / "cover.jpg"
cover_path_upper = album_dir / "Cover.jpg"
exists = cover_path_lower.exists() or cover_path_upper.exists()
logger.info(f"Checking cover existence in '{album_dir}': {
'Found' if exists else 'Not found'}")
return exists
def get_local_album_dir(xesam_url, music_base_path=os.environ.get('MUSIC_LIBRARY_PATH', os.path.expandvars('$HOME/media/all'))):
"""Extraer el directorio del álbum desde la ruta `xesam:url`."""
if not xesam_url:
logger.warning(
"xesam:url is empty or missing. Skipping cover detection.")
return None
try:
logger.info(f"Raw xesam:url value: {xesam_url}")
# Convertir la URL xesam en una ruta de archivo local
local_path = urllib.parse.unquote(xesam_url).replace("file://", "")
logger.info(f"Converted local path: {local_path}")
# Eliminar '/trackXXXX' si es una ruta de archivo CUE
if "/track" in local_path and ".cue" in local_path:
local_path = local_path.split("/track")[0]
logger.info(f"Stripped CUE path: {local_path}")
# Si la ruta no es absoluta, agregar el directorio base de música
track_path = Path(local_path)
if not track_path.is_absolute():
track_path = Path(music_base_path) / track_path
logger.info(f"Updated to absolute path: {track_path}")
# Verificar si la ruta resultante existe
if not track_path.exists():
logger.warning(f"The path '{track_path}' does not exist.")
return None
# Manejar archivos CUE: Subir al directorio del álbum real
if track_path.suffix == ".cue":
return track_path.parent
# Manejo estándar de rutas
if track_path.is_file():
return track_path.parent
else:
logger.warning(f"Path is not a valid file: {track_path}")
except Exception as e:
logger.error(f"Error extracting local album directory: {e}")
return None

32
.config/waybar/scripts/live.py Executable file
View File

@ -0,0 +1,32 @@
import asyncio
from msgraph.core import GraphClient
from azure.identity import DeviceCodeCredential
# Reemplaza con tu CLIENT_ID
credential = DeviceCodeCredential(
client_id="b8d7f3bf-e9a7-4352-b379-833e99a79733"
)
scopes = ['Mail.Read']
graph_client = GraphClient(credential=credential, scopes=scopes)
async def get_mail():
response = await graph_client.get(
'/me/messages',
params={
'$select': 'sender,subject',
'$filter': 'isRead eq false'
}
)
if response.status_code == 200:
mail = response.json()
for message in mail.get('value', []):
sender = message['sender']['emailAddress']['address']
subject = message['subject']
print(f"De: {sender}, Asunto: {subject}")
else:
print(f"Error: {response.status_code}")
print(response.json())
asyncio.run(get_mail())

View File

@ -0,0 +1,95 @@
#!/bin/bash
#set -x # Activa el modo de depuración
# Paths y archivos temporales
CURRENT_TRACK_FILE="/tmp/current_track.txt"
LYRICS_FILE="/tmp/lyrics.txt"
LOG_FILE="/tmp/lyrics_error.log"
STATE_FILE="/tmp/lyrics_state.txt"
# Función para limpiar el título de la canción
clean_title() {
local title="$1"
# Eliminar prefijos como "B3 ", "A1 ", etc.
title=$(echo "$title" | sed -E 's/^[A-Za-z0-9]{1,3}[[:space:]]+//')
# Eliminar caracteres especiales como "$", "#", etc.
title=$(echo "$title" | tr -d '$#@!%^&*()[]{}<>?/\\|`~')
# Eliminar espacios adicionales al inicio y al final
title=$(echo "$title" | sed 's/^ *//;s/ *$//')
echo "$title"
}
# Obtener información de la pista actual
TRACK_INFO=$(playerctl metadata --format "{{artist}} - {{title}}" 2>>"$LOG_FILE")
STATUS=$(playerctl status 2>>"$LOG_FILE")
echo "STATUS: '$STATUS'" >> "$LOG_FILE"
# Si no hay información de pista o el estado no es 'Playing', salimos
if [ -z "$TRACK_INFO" ] || [ "$STATUS" != "Playing" ]; then
jq -c -n '{"text":"","tooltip":""}'
exit 0
fi
echo "TRACK_INFO: '$TRACK_INFO'" >> "$LOG_FILE"
if [ -z "$TRACK_INFO" ]; then
echo "No track info available" > "$LYRICS_FILE"
exit 1
fi
# Extraer artista y título
ARTIST=$(echo "$TRACK_INFO" | cut -d '-' -f 1 | sed 's/^ *//;s/ *$//')
TITLE=$(echo "$TRACK_INFO" | cut -d '-' -f 2- | sed 's/^ *//;s/ *$//')
echo "ARTIST: '$ARTIST'" >> "$LOG_FILE"
echo "TITLE Original: '$TITLE'" >> "$LOG_FILE"
# Limpiar el título de la canción
CLEAN_TITLE=$(clean_title "$TITLE")
echo "TITLE Limpio: '$CLEAN_TITLE'" >> "$LOG_FILE"
# Verificar si la pista ha cambiado
if [ "$TRACK_INFO" != "$(cat "$CURRENT_TRACK_FILE" 2>/dev/null)" ]; then
echo "$TRACK_INFO" > "$CURRENT_TRACK_FILE"
# Obtener letras usando el script Python
lyrics=$(python3 ~/.config/waybar/scripts/get_lyrics.py "$ARTIST" "$CLEAN_TITLE" 2>>"$LOG_FILE")
echo "lyrics: '$lyrics'" >> "$LOG_FILE"
if [[ "$lyrics" == "Letras no encontradas." ]]; then
lyrics="Letras no encontradas para $ARTIST - $CLEAN_TITLE"
fi
echo "$lyrics" > "$LYRICS_FILE"
else
lyrics=$(cat "$LYRICS_FILE")
fi
# Leer el estado
if [ -f "$STATE_FILE" ]; then
STATE=$(cat "$STATE_FILE")
else
STATE="full"
fi
# Ajustar las letras según el estado
if [ "$STATE" = "second_half" ]; then
total_lines=$(echo "$lyrics" | wc -l)
half_line=$(( (total_lines + 1) / 2 ))
lyrics=$(echo "$lyrics" | tail -n +$half_line)
fi
# Verificar si el player activo es Firefox
PLAYER_NAME=$(playerctl -a metadata --format '{{playerName}}' 2>>"$LOG_FILE" | head -n 1)
echo "PLAYER_NAME: '$PLAYER_NAME'" >> "$LOG_FILE"
if [[ "$PLAYER_NAME" == "firefox" ]]; then
jq -c -n '{"text":"","tooltip":""}'
exit 0
fi
# Generar JSON para Waybar
jq -c -n --arg text "🎵" --arg tooltip "$lyrics" '{"text":$text,"tooltip":$tooltip}'

View File

@ -0,0 +1,18 @@
#!/bin/bash
STATE_FILE="/tmp/lyrics_state.txt"
if [ ! -f "$STATE_FILE" ]; then
echo "second_half" > "$STATE_FILE"
else
STATE=$(cat "$STATE_FILE")
if [ "$STATE" = "second_half" ]; then
echo "full" > "$STATE_FILE"
else
echo "second_half" > "$STATE_FILE"
fi
fi
# Send signal to Waybar to update the module immediately
pkill -SIGRTMIN+9 waybar

View File

@ -0,0 +1,22 @@
#!/bin/bash
# Obtener las primeras 5 canciones de la lista de reproducción actual de MPD
playlist=$(mpc playlist | head -n 5)
# Verificar si la lista de reproducción no está vacía
if [[ -z "$playlist" ]]; then
playlist="No hay canciones en la lista de reproducción."
else
total=$(mpc playlist | wc -l)
if [[ "$total" -gt 5 ]]; then
playlist="$playlist\n...y más"
fi
fi
# Reemplazar saltos de línea por '\n' para que el tooltip los entienda
playlist=$(echo "$playlist" | sed ':a;N;$!ba;s/\n/\\n/g')
# Generar JSON compacto y seguro
jq -c -n --arg text "🎶" --arg tooltip "$playlist" '{"text":$text,"tooltip":$tooltip}'

View File

@ -0,0 +1,24 @@
#!/bin/bash
# Comprobar si hay algún reproductor activo
status=$(playerctl status 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo "" # No mostrar nada si no hay reproductor
exit 0
fi
# Obtener metadatos
artist=$(playerctl metadata artist 2>/dev/null)
title=$(playerctl metadata title 2>/dev/null)
playback_status=$(playerctl status)
# Iconos según el estado
if [[ "$playback_status" == "Playing" ]]; then
play_icon=" "
else
play_icon=""
fi
# Botones
echo "{\"text\": \" $play_icon  \", \"tooltip\": \"$artist - $title\"}"

View File

@ -0,0 +1,119 @@
const { RoonExtension } = require('roon-kit');
const fs = require('fs');
const { exec } = require('child_process');
const lyricsScriptPath = "/home/teraflops/.config/waybar/scripts/get_lyrics.py";
function log(msg) {
const timestamp = new Date().toISOString();
const fullMsg = `[${timestamp}] ${msg}`;
console.log(fullMsg);
fs.appendFileSync('/tmp/roon_debug.log', fullMsg + '\n');
}
const extension = new RoonExtension({
description: {
extension_id: 'roon-kit-now-playing',
display_name: "Roon Kit Now Playing",
display_version: "0.2.0",
publisher: 'roon-kit',
email: 'stevenic@microsoft.com',
website: 'https://github.com/Stevenic/roon-kit'
},
RoonApiBrowse: 'not_required',
RoonApiImage: 'required',
RoonApiTransport: 'required',
subscribe_outputs: false,
subscribe_zones: true,
log_level: 'none'
});
function cleanTitle(title) {
return title.replace(/^[A-Za-z0-9]{1,3}\s+/, '').replace(/[$#@!%^&*(){}\[\]<>?/\\|`~]/g, '').trim();
}
function getLyrics(artist, title, callback) {
const cmd = `python3 ${lyricsScriptPath} "${artist}" "${title}"`;
log(`🎤 Ejecutando: ${cmd}`);
exec(cmd, (error, stdout, stderr) => {
if (error) {
log(`❌ Error en getLyrics: ${stderr}`);
callback("Letras no disponibles.");
} else {
const lyrics = stdout.trim();
log(`✅ Letras obtenidas (${lyrics.length} chars)`);
callback(lyrics || "Letras no encontradas.");
}
});
}
extension.on("subscribe_zones", async (core, response, body) => {
log("🔄 Evento subscribe_zones recibido");
log(JSON.stringify(body, null, 2));
const changedZones = body.zones_changed ?? [];
const addedZones = body.zones_added ?? [];
const removedZones = body.zones_removed ?? [];
if (removedZones.length > 0) {
log("🧹 Zona eliminada, limpiando /tmp/waybar_roon_info.json");
fs.writeFileSync('/tmp/waybar_roon_info.json', JSON.stringify({ text: '', tooltip: '' }));
exec("/usr/bin/pkill -RTMIN+3 waybar");
}
for (const zone of [...addedZones, ...changedZones]) {
log(`🎧 Zona: ${zone.display_name}, estado: ${zone.state}`);
if (zone.state === 'playing') {
const track = cleanTitle(zone.now_playing?.one_line?.line1 || '');
const artist = zone.now_playing?.one_line?.line2 || '';
const album = zone.now_playing?.three_line?.line3 || '';
log(`🎵 Reproduciendo: ${track} - ${artist} (${album})`);
getLyrics(artist, track, async (lyrics) => {
const displayText = `${track} - ${artist}`;
const tooltip = `🎵 ${track}\n👤 ${artist}\n💿 ${album}\n\n${lyrics}`;
const data = {
text: displayText,
tooltip: tooltip
};
log(`✍️ Escribiendo JSON para Waybar: ${JSON.stringify(data)}`);
fs.writeFileSync('/tmp/waybar_roon_info.json', JSON.stringify(data));
exec("/usr/bin/pkill -RTMIN+3 waybar");
const image_key = zone.now_playing?.image_key;
if (image_key) {
try {
const imageData = await core.services.RoonApiImage.get_image(image_key, { width: 300, height: 300 });
const coverPath = '/tmp/roon_album_cover.jpg';
fs.writeFileSync(coverPath, imageData.image);
exec(`notify-send -i ${coverPath} "Now Playing" "${displayText}"`);
log("✅ Notificación enviada con carátula");
} catch (error) {
log(`⚠️ Error obteniendo imagen: ${error}`);
exec(`notify-send "Now Playing" "${displayText}"`);
}
} else {
exec(`notify-send "Now Playing" "${displayText}"`);
}
});
}
}
});
extension.start_discovery();
extension.set_status('Esperando conexión al Core de Roon...');
log("🔍 Buscando Core...");
(async () => {
const core = await extension.get_core();
if (core) {
log("✅ Core emparejado correctamente");
extension.set_status('Emparejado con el Core de Roon');
} else {
log("❌ No se emparejó con ningún Core");
}
})();

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Script para Waybar que muestra la canción actual de Roon
# Variables de configuración
PYTHON_SCRIPT="/home/teraflops/.config/waybar/scripts/get_current_track.py"
# Ejecuta el script de Python y captura la salida
TRACK_INFO=$(python3 "$PYTHON_SCRIPT")
# Imprime el nombre de la canción y el artista
echo "$TRACK_INFO"

View File

@ -0,0 +1,41 @@
const { RoonExtension } = require('roon-kit');
const fs = require('fs');
const outputTrackFile = "/tmp/roon_track.txt";
const extension = new RoonExtension({
description: {
extension_id: 'roon-track-writer',
display_name: "Roon Track Writer",
display_version: "0.1.0",
publisher: 'teraflops',
email: 'teraflops@example.com'
},
RoonApiTransport: 'required',
subscribe_zones: true
});
extension.on("subscribe_zones", async (core, response, body) => {
const changedZones = body.zones_changed ?? [];
const addedZones = body.zones_added ?? [];
for (const zone of [...changedZones, ...addedZones]) {
if (zone.state === 'playing') {
const artist = zone.now_playing?.one_line?.line2 || '';
const track = zone.now_playing?.one_line?.line1 || '';
const line = `${artist} - ${track}`;
fs.writeFileSync(outputTrackFile, line);
console.log(`🎵 Track escrito: ${line}`);
}
}
});
extension.start_discovery();
extension.set_status("Esperando conexión con Roon...");
(async () => {
const core = await extension.get_core();
extension.set_status("Emparejado con el Core de Roon");
})();

View File

@ -0,0 +1,194 @@
#!/usr/bin/env python3
import threading
from pathlib import Path
import asyncio
from lastfm_cover_downloader import download_cover_from_lastfm, cover_exists, get_local_album_dir
from dbus_next.aio import MessageBus
from dbus_next import Variant
from gi.repository import Gtk, GdkPixbuf, GLib
import os
import gi
gi.require_version("Gtk", "4.0")
os.environ["GDK_BACKEND"] = "x11" # O "wayland" si usas Wayland
class CoverPanel:
def __init__(self, music_base_path):
self.music_base_path = Path(music_base_path)
self.window = Gtk.Window(title="Album Cover")
self.window.set_default_size(300, 300)
self.window.set_resizable(False)
self.window.set_decorated(False)
self.window.set_opacity(0.8)
# Crear el widget Gtk.Picture para mostrar y escalar la imagen
self.picture = Gtk.Picture()
self.window.set_child(self.picture)
self.window.present()
print("CoverPanel window initialized successfully.")
def update_cover(self, cover_path):
print(f"Attempting to update cover with path: {cover_path}")
if cover_path and cover_path.exists():
print(f"Updating cover with path: {cover_path.resolve()}")
try:
# Configurar Gtk.Picture para mostrar la imagen y escalarla automáticamente
pixbuf = GdkPixbuf.Pixbuf.new_from_file(str(cover_path))
GLib.idle_add(self.picture.set_paintable,
Gtk.Picture.new_for_pixbuf(pixbuf).get_paintable())
print(f"Cover panel updated with: {cover_path.resolve()}")
except Exception as e:
print(f"Error loading image: {e}")
else:
print(f"Cover path does not exist or is invalid: {cover_path}")
def load_initial_cover(self):
"""Busca y carga la última carátula disponible en la biblioteca."""
cover_path = self.find_latest_cover()
if cover_path:
self.update_cover(cover_path)
def find_latest_cover(self):
"""Busca la carátula más reciente en la biblioteca de música."""
for album_dir in sorted(self.music_base_path.glob("*/"), reverse=True):
if cover_exists(album_dir):
return album_dir / "cover.jpg"
return None
class MprisCoverHandlerWithGTK:
def __init__(self, music_base_path):
print("Inicializando MprisCoverHandlerWithGTK.")
self.music_base_path = music_base_path
self.panel = CoverPanel(music_base_path)
self.bus = None
async def load_current_song_cover(self):
"""Intenta cargar la carátula de la canción actual usando MPRIS."""
try:
self.bus = await MessageBus().connect()
introspectable = await self.bus.introspect("org.mpris.MediaPlayer2.mpd", "/org/mpris/MediaPlayer2")
obj = self.bus.get_proxy_object(
"org.mpris.MediaPlayer2.mpd", "/org/mpris/MediaPlayer2", introspectable)
properties = obj.get_interface("org.freedesktop.DBus.Properties")
metadata_variant = await properties.call_get("org.mpris.MediaPlayer2.Player", "Metadata")
metadata = metadata_variant.value # Obtener el valor real del Variant
track_title = metadata.get('xesam:title', Variant('s', '')).value
artist = metadata.get('xesam:artist', Variant('as', [])).value
album = metadata.get('xesam:album', Variant('s', '')).value
xesam_url = metadata.get('xesam:url', Variant('s', '')).value
print(f"Metadata recibida: Track Title: {track_title}, Artist: {
artist}, Album: {album}, URL: {xesam_url}")
if not isinstance(artist, list):
artist = [str(artist)]
artist = [str(a) for a in artist]
if artist and album != 'Unknown' and xesam_url:
album_dir = get_local_album_dir(
xesam_url, self.music_base_path)
print(f"Determined album directory: {album_dir}")
if album_dir and cover_exists(album_dir):
cover_path = album_dir / "cover.jpg"
self.panel.update_cover(cover_path)
return
except Exception as e:
print(f"Error loading current song cover: {e}")
# Si no se encuentra la carátula de la canción actual, cargar la última carátula encontrada
print("No current song cover found. Loading last known cover.")
self.panel.load_initial_cover()
async def on_properties_changed(self, interface, changed, invalidated):
print("on_properties_changed: Detectando cambios de propiedades MPRIS.")
if 'Metadata' in changed:
metadata = changed['Metadata'].value
# Extraer los valores reales de cada Variant
track_title = metadata.get('xesam:title', Variant('s', '')).value
artist = metadata.get('xesam:artist', Variant('as', [])).value
album = metadata.get('xesam:album', Variant('s', '')).value
xesam_url = metadata.get('xesam:url', Variant('s', '')).value
print(f"Metadata recibida: Track Title: {track_title}, Artist: {
artist}, Album: {album}, URL: {xesam_url}")
if not isinstance(artist, list):
artist = [str(artist)]
artist = [str(a) for a in artist]
if not artist or album == 'Unknown' or not xesam_url:
print(f"Skipping track '{
track_title}' due to missing metadata.")
return
print(f"Now playing: {track_title} by {
', '.join(artist)} from the album '{album}'")
album_dir = get_local_album_dir(xesam_url, self.music_base_path)
print(f"Determined album directory: {album_dir}")
if not album_dir:
print(f"Could not determine album directory for '{
track_title}'. xesam:url: {xesam_url}")
return
if cover_exists(album_dir):
print(f"Cover already exists in '{
album_dir}', loading from local.")
cover_path = album_dir / "cover.jpg"
self.panel.update_cover(cover_path)
else:
print(f"No cover found in '{
album_dir}', attempting to download.")
try:
cover_path = download_cover_from_lastfm(
artist[0], album, album_dir)
if cover_path:
self.panel.update_cover(cover_path)
except Exception as e:
print(f"Error downloading cover: {e}")
await asyncio.sleep(0.5)
os.system("/usr/bin/pkill -RTMIN+23 waybar")
async def main(self):
print("Starting D-Bus listener with GTK panel...")
# Cargar la carátula de la canción actual al inicio
await self.load_current_song_cover()
introspectable = await self.bus.introspect("org.mpris.MediaPlayer2.mpd", "/org/mpris/MediaPlayer2")
obj = self.bus.get_proxy_object(
"org.mpris.MediaPlayer2.mpd", "/org/mpris/MediaPlayer2", introspectable)
properties = obj.get_interface("org.freedesktop.DBus.Properties")
await asyncio.sleep(0.5)
os.system("/usr/bin/pkill -RTMIN+23 waybar")
properties.on_properties_changed(self.on_properties_changed)
print("D-Bus listener with GTK panel started successfully.")
await asyncio.Future()
if __name__ == "__main__":
print("Iniciando script show_cover.py")
MUSIC_BASE_PATH = os.environ.get(
'MUSIC_LIBRARY_PATH', os.path.expandvars('$HOME/media/all'))
print(f"MUSIC_BASE_PATH: {MUSIC_BASE_PATH}")
fetcher = MprisCoverHandlerWithGTK(MUSIC_BASE_PATH)
# Ejecutar el ciclo de asyncio en un hilo separado
def run_asyncio():
asyncio.run(fetcher.main())
asyncio_thread = threading.Thread(target=run_asyncio)
asyncio_thread.start()
# Ejecutar el ciclo de GTK en el hilo principal
loop = GLib.MainLoop()
loop.run()

76
.config/waybar/scripts/temps.sh Executable file
View File

@ -0,0 +1,76 @@
#!/bin/bash
STATE_FILE="/tmp/waybar_temp_mode"
MODES=("cpu" "igpu" "dgpu")
# Detecta si la dGPU está realmente activa
dgpu_is_active() {
runtime_status=$(cat /sys/bus/pci/devices/0000:01:00.0/power/runtime_status 2>/dev/null)
[[ "$runtime_status" == "active" ]]
}
get_dgpu_temp() {
if dgpu_is_active; then
nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null || echo "N/A"
else
echo "N/A"
fi
}
get_cpu_temp() {
sensors | awk '/^Tctl:/ {print $2}' | sed 's/+//;s/°C//'
}
get_igpu_temp() {
sensors | awk '/amdgpu/ {found=1} found && /edge:/ {print $2; exit}' | sed 's/+//;s/°C//'
}
get_nvme_temp() {
sensors | awk '/nvme/ {found=1} found && /Composite:/ {print $2; exit}' | sed 's/+//;s/°C//'
}
get_board_temp() {
sensors | awk '/temp1:/ && /°C/ {print $2; exit}' | sed 's/+//;s/°C//'
}
# Leer estado actual
current_mode="cpu"
[[ -f "$STATE_FILE" ]] && current_mode=$(<"$STATE_FILE")
# Cambiar modo si se hizo clic
if [[ "$1" == "click" ]]; then
for i in "${!MODES[@]}"; do
if [[ "${MODES[$i]}" == "$current_mode" ]]; then
next_index=$(( (i + 1) % ${#MODES[@]} ))
echo "${MODES[$next_index]}" > "$STATE_FILE"
exit 0
fi
done
fi
# Obtener temperaturas
cpu=$(get_cpu_temp)
igpu=$(get_igpu_temp)
dgpu=$(get_dgpu_temp)
nvme=$(get_nvme_temp)
board=$(get_board_temp)
cpu=${cpu:-N/A}
igpu=${igpu:-N/A}
dgpu=${dgpu:-N/A}
nvme=${nvme:-N/A}
board=${board:-N/A}
# Definir ícono y valor visible
case "$current_mode" in
cpu) icon="CPU"; temp="$cpu" ;;
igpu) icon="iGPU"; temp="$igpu" ;;
dgpu) icon="dGPU"; temp="$dgpu" ;;
esac
# Tooltip con \n y escapado correcto
tooltip=$(printf "CPU: %s°C\niGPU: %s°C\ndGPU: %s°C\nNVMe: %s°C\nBoard: %s°C" "$cpu" "$igpu" "$dgpu" "$nvme" "$board")
tooltip_escaped=$(echo "$tooltip" | jq -Rs .)
# Salida final
echo "{\"text\": \"$icon: ${temp}°C\", \"tooltip\": ${tooltip_escaped}}"

View File

@ -0,0 +1,95 @@
#!/bin/bash
STATE_FILE="/tmp/waybar_temp_mode"
MODES=("cpu" "igpu" "dgpu")
# Detecta si la dGPU está realmente activa
dgpu_is_active() {
runtime_status=$(cat /sys/bus/pci/devices/0000:01:00.0/power/runtime_status 2>/dev/null)
[[ "$runtime_status" == "active" ]]
}
get_dgpu_temp() {
gpu_mode_file="/tmp/gpu_mode"
timestamp_file="/tmp/gpu_mode_timestamp"
echo_na() {
echo "N/A"
}
[[ ! -f "$gpu_mode_file" || ! -f "$timestamp_file" ]] && echo_na && return
mode=$(< "$gpu_mode_file")
last_change=$(< "$timestamp_file")
now=$(date +%s)
diff=$((now - last_change))
# Solo permitir nvidia-smi si:
# 1. El modo es nvidia
# 2. Han pasado más de 5s
# 3. La dGPU está realmente activa
if [[ "$mode" == "nvidia" && "$diff" -gt 5 && $(dgpu_is_active) ]]; then
nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null
else
echo_na
fi
}
get_cpu_temp() {
sensors | awk '/^Tctl:/ {print $2}' | sed 's/+//;s/°C//'
}
get_igpu_temp() {
sensors | awk '/amdgpu/ {found=1} found && /edge:/ {print $2; exit}' | sed 's/+//;s/°C//'
}
get_nvme_temp() {
sensors | awk '/nvme/ {found=1} found && /Composite:/ {print $2; exit}' | sed 's/+//;s/°C//'
}
get_board_temp() {
sensors | awk '/temp1:/ && /°C/ {print $2; exit}' | sed 's/+//;s/°C//'
}
# Leer estado actual
current_mode="cpu"
[[ -f "$STATE_FILE" ]] && current_mode=$(<"$STATE_FILE")
# Cambiar modo si se hizo clic
if [[ "$1" == "click" ]]; then
for i in "${!MODES[@]}"; do
if [[ "${MODES[$i]}" == "$current_mode" ]]; then
next_index=$(( (i + 1) % ${#MODES[@]} ))
echo "${MODES[$next_index]}" > "$STATE_FILE"
exit 0
fi
done
fi
# Obtener temperaturas
cpu=$(get_cpu_temp)
igpu=$(get_igpu_temp)
dgpu=$(get_dgpu_temp)
nvme=$(get_nvme_temp)
board=$(get_board_temp)
cpu=${cpu:-N/A}
igpu=${igpu:-N/A}
dgpu=${dgpu:-N/A}
nvme=${nvme:-N/A}
board=${board:-N/A}
# Definir ícono y valor visible
case "$current_mode" in
cpu) icon="CPU"; temp="$cpu" ;;
igpu) icon="iGPU"; temp="$igpu" ;;
dgpu) icon="dGPU"; temp="$dgpu" ;;
esac
# Tooltip con \n y escapado correcto
tooltip=$(printf "CPU: %s°C\niGPU: %s°C\ndGPU: %s°C\nNVMe: %s°C\nBoard: %s°C" "$cpu" "$igpu" "$dgpu" "$nvme" "$board")
tooltip_escaped=$(echo "$tooltip" | jq -Rs .)
# Salida final
echo "{\"text\": \"$icon: ${temp}°C\", \"tooltip\": ${tooltip_escaped}}"

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,22 @@
#!/bin/bash
PIDFILE="/tmp/dmp_widget.pid"
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
kill "$(cat "$PIDFILE")"
rm -f "$PIDFILE"
else
yad --title="DMP-A6 Info" \
--window-icon=audio-x-generic \
--on-top --skip-taskbar \
--undecorated \
--geometry=500x300+40+40 \
--no-buttons \
--image="/tmp/dmp_cover.jpg" \
--text="$(~/.config/eww/scripts/get_dmp_state.sh | jq -r '.title + "\n" + .artist + "\n" + .info')" \
--text-align=center \
--fontname="JetBrains Mono 11" \
--borders=12 &
echo $! > "$PIDFILE"
fi

View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Comprobamos el estado actual leyendo la línea de CPU en "-g"
status=$(asusctl fan-curve -g 2>/dev/null | grep "^CPU: enabled:")
# Si se encuentra "true" en esa línea, significa que las fan-curves están activas
# (asumimos que CPU/GPU/MID van sincronizados para el perfil Balanced).
if echo "$status" | grep -q "true"; then
echo "Fan-curve actualmente habilitado -> Lo deshabilitamos"
asusctl fan-curve -m Balanced -e false
else
echo "Fan-curve actualmente deshabilitado -> Lo habilitamos"
asusctl fan-curve -m Balanced -e true
fi

View File

@ -0,0 +1 @@
⬆ 525.94 Mbps

View File

@ -0,0 +1,70 @@
#!/bin/bash
SYNC_INTERVAL=1800
SYNC_TIMESTAMP="/tmp/last_checkupdates_sync"
NO_ZERO_OUTPUT=false
now=$(date +%s)
last=$(cat "$SYNC_TIMESTAMP" 2>/dev/null || echo 0)
if (( now - last >= SYNC_INTERVAL )); then
rm -rf "/tmp/checkup-db-$UID"
checkupdates > /dev/null 2>&1
echo "$now" > "$SYNC_TIMESTAMP"
fi
# Mapping: REPOS destino de cada paquete
declare -A update_repo_map
mapping=$(sudo pacman -Syuw --print-format '%r %n' --noconfirm | grep -v '^::' | grep -v '^descargando')
while read -r repo pkg; do
[[ -z "$repo" || -z "$pkg" ]] && continue
update_repo_map["$pkg"]="$repo"
done <<< "$mapping"
updates=$(checkupdates --nosync 2>/dev/null)
aur_updates=$(paru -Qua 2>/dev/null)
total_count=0
tooltip_lines=()
# OJO: este while debe ser así, ¡NUNCA uses tuberías aquí!
IFS=$'\n'
for line in $updates; do
[[ -z "$line" ]] && continue
pkg=$(awk '{print $1}' <<< "$line")
old=$(awk '{print $2}' <<< "$line")
new=$(awk '{print $4}' <<< "$line")
repo="${update_repo_map[$pkg]}"
[[ -z "$repo" ]] && repo="???"
formatted=$(printf "%-15s %-30s %s → %s" "$repo" "$pkg" "$old" "$new")
tooltip_lines+=("$formatted")
((total_count++))
done
# AUR
for line in $aur_updates; do
[[ -z "$line" ]] && continue
pkg=$(awk '{print $1}' <<< "$line")
old=$(awk '{print $2}' <<< "$line")
new=$(awk '{print $4}' <<< "$line")
formatted=$(printf "%-15s %-30s %s → %s" "aur" "$pkg" "$old" "$new")
tooltip_lines+=("$formatted")
((total_count++))
done
aur_rebuild=$(cat /tmp/aur_rebuild_count 2>/dev/null || echo 0)
if [[ "$aur_rebuild" -gt 0 ]]; then
tooltip_lines+=("")
tooltip_lines+=(" $aur_rebuild paquetes AUR necesitan recompilación")
fi
if [[ "$total_count" -eq 0 && "$aur_rebuild" -eq 0 ]]; then
if [[ "$NO_ZERO_OUTPUT" == true ]]; then
echo '{"text": "", "tooltip": "Todo actualizado", "class": "updated", "alt": "updated"}'
else
echo '{"text": "0", "tooltip": "Todo actualizado", "class": "updated", "alt": "updated"}'
fi
else
tooltip=$(printf "%s\n" "${tooltip_lines[@]}" | jq -R -s '.')
echo "{\"text\": \"$total_count\", \"tooltip\": $tooltip, \"class\": \"has-updates\", \"alt\": \"has-updates\"}"
fi

View File

@ -0,0 +1,5 @@
#!/bin/bash
DETAILS=$(upower -i /org/freedesktop/UPower/devices/battery_BAT1 | grep -E "state|percentage|energy|energy-full|energy-rate|voltage")
echo "$DETAILS" | zenity --text-info --title="Detalles de la Batería" --width=400 --height=300

View File

@ -0,0 +1,4 @@
#!/bin/bash
DETAILS=$(upower -i /org/freedesktop/UPower/devices/battery_BAT1 | grep -E "state|percentage|energy|energy-full|energy-rate|voltage")
echo "$DETAILS" | yad --text-info --title="Detalles de la Batería" --width=400 --height=300

View File

@ -0,0 +1,63 @@
#!/usr/bin/env python3
from gi.repository import AppIndicator3
from gi.repository import Gtk, GLib, GObject
import gi
import subprocess
import psutil
import sys
import os
gi.require_version("Gtk", "3.0")
class VolumeSlider(Gtk.Window):
def __init__(self):
super().__init__(title="Control de Volumen")
self.set_default_size(50, 300) # Tamaño de la ventana ajustado
# Crear el control deslizante vertical
self.slider = Gtk.Scale(orientation=Gtk.Orientation.VERTICAL)
self.slider.set_range(0, 100) # Rango de 0 a 100 para el volumen
self.slider.set_value(self.get_current_volume())
# Invertir para que vaya de abajo hacia arriba
self.slider.set_inverted(True)
self.slider.connect("value-changed", self.on_volume_changed)
# Ajustar el ancho del control deslizante
self.slider.set_size_request(30, -1) # Establece el ancho a 30 píxeles
# Añadir el control deslizante a la ventana
self.add(self.slider)
def get_current_volume(self):
"""Obtiene el volumen actual usando pamixer"""
result = subprocess.run(
["pamixer", "--get-volume"], capture_output=True, text=True)
# Default al 50%
return int(result.stdout.strip()) if result.returncode == 0 else 50
def on_volume_changed(self, slider):
"""Ajusta el volumen según la posición del control deslizante"""
volume = int(slider.get_value())
subprocess.run(["pamixer", "--set-volume", str(volume)])
def is_another_instance_running():
"""Comprueba si hay otra instancia de este script en ejecución"""
current_pid = os.getpid()
for process in psutil.process_iter(attrs=['pid', 'cmdline']):
if process.info['pid'] != current_pid and 'volume_slider.py' in process.info['cmdline']:
return True
return False
if __name__ == "__main__":
if is_another_instance_running():
print("Ya hay una instancia ejecutándose. Finalizando esta.")
sys.exit()
# Ejecutar la aplicación
win = VolumeSlider()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

229
.config/waybar/scripts/weather.py Executable file
View File

@ -0,0 +1,229 @@
#!/usr/bin/env python3
import os
import sys
import json
import gi
import requests
from datetime import datetime, timezone
gi.require_version('Geoclue', '2.0')
from gi.repository import Geoclue
# ─────────── ICONOS DEL CLIMA ─────────── #
weather_icons = {
"01d": "", "01n": "", "02d": "", "02n": "",
"03d": "", "03n": "", "04d": "", "04n": "",
"09d": "", "09n": "", "10d": "", "10n": "",
"11d": "", "11n": "", "13d": "", "13n": "",
"50d": "", "50n": "", "default": ""
}
# ─────────── API KEYS ─────────── #
try:
with open('/home/teraflops/apikeys/openweathermaps', 'r') as f:
api_key = f.read().strip()
with open('/home/teraflops/apikeys/google_geoip', 'r') as f:
google_key = f.read().strip()
except Exception:
print(json.dumps({"text": "Error al leer las API keys"}))
sys.exit(1)
# ─────────── OBTENER UBICACIÓN ─────────── #
def get_location_from_geoclue():
try:
clue = Geoclue.Simple.new_sync("weather-script", Geoclue.AccuracyLevel.EXACT, None)
location = clue.get_location()
lat = location.get_property('latitude')
lon = location.get_property('longitude')
desc = location.get_property('description')
acc = location.get_property('accuracy')
print(f"🛰️ GeoClue: {lat}, {lon} — Precisión: {acc:.1f} m — Fuente: {desc}")
return lat, lon, desc
except Exception as e:
print("GeoClue (GI): error al obtener ubicación:", e)
return None
def get_location_from_google():
try:
url = f"https://www.googleapis.com/geolocation/v1/geolocate?key={google_key}"
response = requests.post(url, json={})
response.raise_for_status()
data = response.json()
lat = data['location']['lat']
lon = data['location']['lng']
accuracy = data.get('accuracy', 0)
print(f"🛰️ GoogleGeo: {lat}, {lon} — Precisión: {accuracy:.0f} m")
return lat, lon, f"GoogleGeo ({accuracy:.0f} m)"
except Exception as e:
print("Google Geolocation API: fallo:", e)
return None
def reverse_geocode(lat, lon):
try:
url = "https://nominatim.openstreetmap.org/reverse"
params = {
"lat": lat,
"lon": lon,
"format": "json",
"zoom": 10,
"addressdetails": 1,
}
headers = {"User-Agent": "weather-waybar-script"}
response = requests.get(url, params=params, headers=headers)
response.raise_for_status()
data = response.json()
address = data.get("address", {})
ciudad = address.get("city") or address.get("town") or address.get("village") or "Desconocido"
pais = address.get("country", "Desconocido")
return ciudad, pais
except Exception as e:
print("Reverse geocode falló:", e)
return "Desconocido", "Desconocido"
# ─────────── ALERTA LLUVIA SOLO EN TOOLTIP ─────────── #
def check_rain_alert(forecast_data, threshold=60, max_horas=6):
ahora = datetime.utcnow()
mensaje_alerta = ""
for block in forecast_data['list']:
bloque_hora = datetime.strptime(block['dt_txt'], "%Y-%m-%d %H:%M:%S")
if bloque_hora <= ahora:
continue
if (bloque_hora - ahora).total_seconds() > max_horas * 3600:
continue
pop = block.get('pop', 0) * 100
if pop >= threshold:
hora_local = bloque_hora.replace(tzinfo=timezone.utc).astimezone().strftime("%H:%M")
mensaje_alerta = f"⚠️ Posible lluvia a las {hora_local} ({int(pop)}%)"
break
return mensaje_alerta
# ─────────── PRONÓSTICO EXTENDIDO ─────────── #
def get_daily_forecast(forecast_data):
from collections import defaultdict
dias = defaultdict(list)
for entry in forecast_data['list']:
fecha = entry['dt_txt'].split(' ')[0]
dias[fecha].append(entry)
resumen = []
for i, (dia, bloques) in enumerate(dias.items()):
if i >= 3: break
temps = [b['main']['temp'] for b in bloques]
pops = [b.get('pop', 0) * 100 for b in bloques]
estado = bloques[0]['weather'][0]['description'].capitalize()
resumen.append(f"📅 {dia}: {estado}, {round(min(temps))}{round(max(temps))}°C, ☔ {round(max(pops))}%")
return "\n".join(resumen)
# ─────────── UBICACIÓN ─────────── #
usar_ubicacion_manual = False # Cambia a True si quieres forzar ubicación
if usar_ubicacion_manual:
latitud, longitud = 40.4168, -3.7038 # Ejemplo: Madrid
region = "Manual"
print(f"🧪 Usando ubicación manual: {latitud}, {longitud}")
else:
loc = get_location_from_geoclue() or get_location_from_google()
if not loc:
print(json.dumps({"text": "Error al obtener la ubicación"}))
sys.exit(1)
latitud, longitud, region = loc
ciudad, pais = reverse_geocode(latitud, longitud)
# ─────────── API WEATHER ─────────── #
params = {
"lat": latitud,
"lon": longitud,
"appid": api_key,
"units": "metric",
"lang": "es",
}
weather_url = "https://api.openweathermap.org/data/2.5/weather"
response = requests.get(weather_url, params=params)
if response.status_code != 200:
print(json.dumps({"text": "Error al obtener datos del clima"}))
sys.exit(1)
data = response.json()
# ─────────── DATOS CLIMA ─────────── #
temp = f"{round(data['main']['temp'])}°C"
status = data['weather'][0]['description'].capitalize()
status_code = data['weather'][0]['icon']
icon = weather_icons.get(status_code, weather_icons["default"])
temp_feel = f"{round(data['main']['feels_like'])}°C"
temp_feel_text = f"Sensación de {temp_feel}"
temp_min = f"{round(data['main']['temp_min'])}°C"
temp_max = f"{round(data['main']['temp_max'])}°C"
temp_min_max = f"{temp_min}\t\t{temp_max}"
wind_speed = f"{round(data['wind']['speed'] * 3.6)} km/h"
wind_text = f"{wind_speed}"
humidity = f"{data['main']['humidity']}%"
humidity_text = f"{humidity}"
visibility = data.get('visibility', 0)
visibility_km = f"{visibility / 1000} km"
visibility_text = f"{visibility_km}"
# ─────────── CALIDAD DEL AIRE ─────────── #
aqi_url = "https://api.openweathermap.org/data/2.5/air_pollution"
aqi_response = requests.get(aqi_url, params=params)
if aqi_response.status_code == 200:
aqi = aqi_response.json()['list'][0]['main']['aqi']
aqi_levels = {1: "Bueno", 2: "Moderado", 3: "Perjudicial", 4: "Malo", 5: "Muy malo"}
air_quality_index = aqi_levels.get(aqi, "N/A")
else:
air_quality_index = "N/A"
# ─────────── PRONÓSTICO ─────────── #
forecast_url = "https://api.openweathermap.org/data/2.5/forecast"
forecast_response = requests.get(forecast_url, params=params)
if forecast_response.status_code == 200:
forecast_data = forecast_response.json()
precipitation = forecast_data['list'][0].get('pop', 0) * 100
prediction = f"\n\n  {precipitation:.0f}% probabilidad de lluvia"
alerta_lluvia = check_rain_alert(forecast_data)
pronostico_dias = get_daily_forecast(forecast_data)
else:
prediction = ""
alerta_lluvia = ""
pronostico_dias = ""
# ─────────── TOOLTIP ─────────── #
time_of_day = "Día" if status_code.endswith('d') else "Noche"
ubicacion = f"{ciudad}, {region}, {pais} ({latitud:.2f}, {longitud:.2f})"
tooltip_text = ""
if alerta_lluvia:
tooltip_text += f"{alerta_lluvia}\n\n"
tooltip_text += str.format(
"{}\n{}\n{}\n{}\n\n{}\n{}\n{}{}\n\n<small>{}</small>",
f'<span size="xx-large">{temp}</span>',
f"<big>{icon}</big>",
f"<big>{status} ({time_of_day})</big>",
f"<small>{temp_feel_text}</small>",
f"<big>{temp_min_max}</big>",
f"{wind_text}\t{humidity_text}",
f"{visibility_text}\tICA {air_quality_index}",
f"<i>{prediction}</i>",
f"Ubicación: {ubicacion}"
)
if pronostico_dias:
tooltip_text += f"\n\n<b>Próximos días:</b>\n{pronostico_dias}"
# ─────────── OUTPUT PARA WAYBAR ─────────── #
out_data = {
"text": f"{icon} {temp}",
"alt": f"{status} ({time_of_day})",
"tooltip": tooltip_text,
"class": status_code,
}
print(json.dumps(out_data))

View File

@ -0,0 +1,440 @@
#!/usr/bin/env python3
import dbus
import dbus.mainloop.glib
from gi.repository import AppIndicator3
from gi.repository import Gtk, GLib, GObject
import gi
import subprocess
import threading
import time
import sys
# Especificar las versiones antes de importar
gi.require_version('Gtk', '3.0')
class NetworkMenu(Gtk.Window):
def __init__(self):
super().__init__(title="Menú de Red") # Título estático
self.set_default_size(300, 200)
self.set_border_width(10)
# Crear una caja vertical para organizar los widgets
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.add(vbox)
# Etiqueta para mostrar el estado de la red
self.status_label = Gtk.Label(label="Estado de la red: Desconocido")
vbox.pack_start(self.status_label, False, False, 0)
# Construir el menú
self.build_menu(vbox)
# Inicializar D-Bus
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
self.bus = dbus.SystemBus()
try:
self.network_manager = self.bus.get_object(
'org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager')
self.network_manager_props = dbus.Interface(
self.network_manager, 'org.freedesktop.DBus.Properties')
except dbus.exceptions.DBusException as e:
print(
f"Error al conectar con NetworkManager a través de D-Bus: {e}", file=sys.stderr)
sys.exit(1)
# Suscribirse a las señales de cambio de propiedades
self.bus.add_signal_receiver(
self.on_properties_changed,
dbus_interface='org.freedesktop.DBus.Properties',
signal_name='PropertiesChanged',
arg0='org.freedesktop.NetworkManager',
path='/org/freedesktop/NetworkManager',
)
# Obtener estado inicial
self.update_status_initial()
def build_menu(self, vbox):
# Botón Conectar WiFi
self.item_connect = Gtk.Button(label='Conectar WiFi')
self.item_connect.connect('clicked', self.connect_wifi)
vbox.pack_start(self.item_connect, True, True, 0)
# Botón Desconectar WiFi
self.item_disconnect = Gtk.Button(label='Desconectar WiFi')
self.item_disconnect.connect('clicked', self.disconnect_wifi)
vbox.pack_start(self.item_disconnect, True, True, 0)
# Botón Ver Redes Disponibles
self.item_view_networks = Gtk.Button(label='Ver Redes Disponibles')
self.item_view_networks.connect('clicked', self.show_network_list)
vbox.pack_start(self.item_view_networks, True, True, 0)
# Botón Olvidar Red
self.item_forget_network = Gtk.Button(label='Olvidar Red')
self.item_forget_network.connect('clicked', self.forget_network)
vbox.pack_start(self.item_forget_network, True, True, 0)
# Botón Crear AP
self.item_create_ap = Gtk.Button(label='Crear AP')
self.item_create_ap.connect('clicked', self.create_ap)
vbox.pack_start(self.item_create_ap, True, True, 0)
# Separador
separator = Gtk.Separator()
vbox.pack_start(separator, False, False, 10)
# Botón Salir
close_button = Gtk.Button(label='Cerrar')
close_button.connect('clicked', self.quit)
vbox.pack_start(close_button, False, False, 0)
def create_ap(self, _):
"""Ejecuta el comando para abrir wihotspot-gui."""
try:
subprocess.run(['/usr/bin/wihotspot-gui'], check=True)
except subprocess.CalledProcessError:
self.show_notification("Error al iniciar wihotspot-gui")
def update_status_initial(self):
"""Inicializa el estado de la conexión WiFi al abrir la aplicación."""
try:
wifi_state = self.get_wifi_state()
ssid = self.get_connected_ssid()
self.set_status(wifi_state, ssid)
except dbus.exceptions.DBusException as e:
print(f"Error al obtener el estado inicial: {e}", file=sys.stderr)
def forget_network(self, _):
"""Función para olvidar una red guardada"""
# Obtener la lista de redes WiFi guardadas de manera confiable
saved_networks = subprocess.getoutput(
"nmcli -t -f NAME,TYPE connection show | grep ':802-11-wireless$' | cut -d: -f1").splitlines()
if not saved_networks:
self.show_notification("No hay redes guardadas para olvidar.")
return
# Crear una ventana para seleccionar la red a olvidar
window = Gtk.Window(title="Olvidar Red Guardada")
window.set_default_size(300, 200)
window.set_border_width(10)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
# Añadir botones para cada red guardada
for network in saved_networks:
button = Gtk.Button(label=network)
button.connect("clicked", self.delete_network_profile, network)
vbox.pack_start(button, True, True, 0)
# Botón para cerrar la ventana
close_button = Gtk.Button(label="Cerrar")
close_button.connect("clicked", lambda x: window.destroy())
vbox.pack_start(close_button, False, False, 0)
window.add(vbox)
window.show_all()
def delete_network_profile(self, button, network):
"""Elimina el perfil de conexión guardado para la red especificada"""
try:
subprocess.run(
['nmcli', 'connection', 'delete', network], check=True)
self.show_notification(f"Red '{network}' olvidada exitosamente.")
except subprocess.CalledProcessError:
self.show_notification(f"Error al olvidar la red '{network}'.")
def connect_wifi(self, _):
"""Función llamada al hacer clic en el botón 'Conectar WiFi'.
Muestra la lista de redes y permite al usuario seleccionar una."""
self.show_network_list(None)
def get_available_networks_list(self):
"""Obtiene la lista de redes WiFi junto con la intensidad de la señal y la banda (2.4 GHz o 5 GHz), sin duplicados y sin entradas vacías"""
try:
# Obtener SSID, señal y frecuencia
output = subprocess.getoutput(
"nmcli -t -f SSID,SIGNAL,FREQ dev wifi list")
networks = {}
for line in output.splitlines():
if line:
# Separar el SSID, señal y frecuencia en tres variables
parts = line.split(":")
# Validación para evitar entradas vacías en SSID
ssid = parts[0].strip()
if not ssid:
continue # Saltamos esta entrada si el SSID está vacío
signal_strength = int(parts[1].strip()) if len(
parts) > 1 and parts[1].isdigit() else 0
frequency = int(parts[2].strip().split()[0]) if len(
parts) > 2 and parts[2].strip().split()[0].isdigit() else 0
# Determinar la banda en función de la frecuencia
band = "5 GHz" if frequency >= 5000 else "2.4 GHz"
# Evitar duplicados: guardar solo la señal más alta para cada SSID
if ssid in networks:
if signal_strength > networks[ssid][0]:
networks[ssid] = (signal_strength, band)
else:
networks[ssid] = (signal_strength, band)
# Convertir el diccionario a una lista de tuplas (SSID, señal, banda)
return [(ssid, data[0], data[1]) for ssid, data in networks.items()]
except Exception as e:
print(f"Error al obtener la lista de redes disponibles: {
e}", file=sys.stderr)
return []
def show_network_list(self, _):
# Llamar a la función para obtener la lista de redes con señal y banda
networks = self.get_available_networks_list()
if not networks:
self.show_notification("No se encontraron redes disponibles.")
return
# Crear una ventana de lista de redes
window = Gtk.Window(title="Redes Disponibles")
window.set_default_size(300, 200)
window.set_border_width(10)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
# Añadir botones para cada red disponible con la intensidad de señal y la banda
for network, signal_strength, band in networks:
# Mostramos el nombre de la red junto con su intensidad de señal y la banda (2.4 GHz o 5 GHz)
button_label = f"{network} ({signal_strength}%, {band})"
button = Gtk.Button(label=button_label)
button.connect(
"clicked", self.prompt_password_and_connect, network)
vbox.pack_start(button, True, True, 0)
# Botón para cerrar la ventana
close_button = Gtk.Button(label="Cerrar")
close_button.connect("clicked", lambda x: window.destroy())
vbox.pack_start(close_button, False, False, 0)
window.add(vbox)
window.show_all()
def prompt_password_and_connect(self, button, network):
# Verificar si el perfil de la conexión ya existe
existing_profile = subprocess.getoutput(
f"nmcli -t -f NAME connection show | grep '^{network}$'")
if existing_profile:
# Si el perfil ya existe, intentar conectarse sin solicitar la contraseña
self.show_notification(f"Conectando a {network}...")
# None para indicar que no se requiere contraseña
self.connect_to_network_with_nmcli(network, None)
else:
# Si no existe el perfil, solicitar la contraseña
dialog = Gtk.Dialog(
title=f"Conectar a {network}",
transient_for=self,
flags=0
)
dialog.add_buttons(
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK
)
# Crear un campo de entrada de texto para la contraseña
box = dialog.get_content_area()
label = Gtk.Label(label="Introduce la contraseña:")
box.add(label)
password_entry = Gtk.Entry()
# Ocultar la contraseña al escribir
password_entry.set_visibility(False)
box.add(password_entry)
dialog.show_all()
# Ejecutar el diálogo y obtener la respuesta del usuario
response = dialog.run()
if response == Gtk.ResponseType.OK:
password = password_entry.get_text()
dialog.destroy()
# Intentar conectar a la red con nmcli y configuración de seguridad
self.connect_to_network_with_nmcli(network, password)
else:
dialog.destroy()
print("Conexión cancelada por el usuario.")
def connect_to_network_with_nmcli(self, ssid, password):
# Detectar la interfaz de red WiFi principal
try:
interface = subprocess.getoutput(
"nmcli device status | grep wifi | grep -v 'p2p' | awk '{print $1}'").strip()
if not interface:
self.show_notification(
"No se encontró una interfaz WiFi válida.")
return
except subprocess.CalledProcessError:
self.show_notification("Error al obtener la interfaz de red.")
return
try:
# Verificar si ya existe un perfil de conexión con el SSID
existing_profile = subprocess.getoutput(
f"nmcli -t -f NAME connection show | grep '^{ssid}$'")
if existing_profile:
# Si el perfil existe y se proporciona una contraseña, actualizarlo
if password:
subprocess.run([
'nmcli', 'connection', 'modify', ssid,
'wifi-sec.key-mgmt', 'wpa-psk', 'wifi-sec.psk', password,
'connection.interface-name', interface
], check=True)
self.show_notification(f"Perfil actualizado: {ssid}")
else:
# Si el perfil no existe, crearlo
subprocess.run([
'nmcli', 'connection', 'add', 'type', 'wifi',
'ifname', interface, 'con-name', ssid, 'ssid', ssid,
'wifi-sec.key-mgmt', 'wpa-psk', 'wifi-sec.psk', password
], check=True)
self.show_notification(f"Perfil creado: {ssid}")
# Intentar conectar usando el perfil existente o actualizado
subprocess.run(['nmcli', 'connection', 'up', ssid], check=True)
self.show_notification(f"Conectado a {ssid}")
except subprocess.CalledProcessError:
self.show_notification(f"Error al intentar conectar a {ssid}")
def disconnect_wifi(self, _):
"""Función para desconectar la red WiFi actual."""
try:
# Intentar desconectar la red activa
subprocess.run(['nmcli', 'networking', 'off'], check=True)
# Reactivar después de desconectar
subprocess.run(['nmcli', 'networking', 'on'], check=True)
self.show_notification("WiFi Desconectado.")
except subprocess.CalledProcessError:
self.show_notification("Error al desconectar WiFi.")
def quit(self, _):
Gtk.main_quit()
def get_available_networks_list(self):
"""Obtiene la lista de redes WiFi junto con la intensidad de la señal y la banda (2.4 GHz o 5 GHz), sin duplicados y sin entradas vacías"""
try:
# Obtener SSID, señal y frecuencia
output = subprocess.getoutput(
"nmcli -t -f SSID,SIGNAL,FREQ dev wifi list")
networks = {}
for line in output.splitlines():
if line:
# Separar el SSID, señal y frecuencia en tres variables
parts = line.split(":")
# Validación para evitar entradas vacías en SSID
ssid = parts[0].strip()
if not ssid:
continue # Saltamos esta entrada si el SSID está vacío
signal_strength = int(parts[1].strip()) if len(
parts) > 1 and parts[1].isdigit() else 0
frequency = int(parts[2].strip().split()[0]) if len(
parts) > 2 and parts[2].strip().split()[0].isdigit() else 0
# Determinar la banda en función de la frecuencia
band = "5 GHz" if frequency >= 5000 else "2.4 GHz"
# Evitar duplicados: guardar solo la señal más alta para cada SSID
if ssid in networks:
if signal_strength > networks[ssid][0]:
networks[ssid] = (signal_strength, band)
else:
networks[ssid] = (signal_strength, band)
# Convertir el diccionario a una lista de tuplas (SSID, señal, banda)
return [(ssid, data[0], data[1]) for ssid, data in networks.items()]
except Exception as e:
print(f"Error al obtener la lista de redes disponibles: {
e}", file=sys.stderr)
return []
def show_notification(self, message):
"""Muestra una notificación en el sistema"""
subprocess.run(['notify-send', message])
def on_properties_changed(self, interface, changed_properties, invalidated_properties):
if 'State' in changed_properties:
wifi_state = self.get_wifi_state()
ssid = self.get_connected_ssid()
self.set_status(wifi_state, ssid)
def set_status(self, wifi_state, ssid):
if wifi_state == 'disabled':
status_text = "WiFi Apagado"
# Deshabilitar el botón de Desconectar WiFi y habilitar Conectar WiFi
self.item_disconnect.set_sensitive(False)
self.item_connect.set_sensitive(True)
elif wifi_state == 'enabled' and not ssid:
status_text = "WiFi Encendido (No Conectado)"
# Habilitar ambos botones
self.item_disconnect.set_sensitive(True)
self.item_connect.set_sensitive(True)
elif wifi_state == 'enabled' and ssid:
status_text = f"Conectado a: {ssid}"
# Deshabilitar el botón de Conectar WiFi y habilitar Desconectar WiFi
self.item_connect.set_sensitive(False)
self.item_disconnect.set_sensitive(True)
else:
status_text = "WiFi Estado Desconocido"
# Habilitar ambos botones por defecto
self.item_connect.set_sensitive(True)
self.item_disconnect.set_sensitive(True)
print(f"Actualizando estado: '{status_text}'")
self.status_label.set_text(f"Estado de la red: {status_text}")
def get_wifi_state(self):
try:
output = subprocess.getoutput("nmcli radio wifi")
print(f"Estado WiFi: {output}")
return output.lower()
except Exception as e:
print(f"Error al obtener el estado de WiFi: {e}", file=sys.stderr)
return "unknown"
def get_connected_ssid(self):
try:
# Usar iwgetid para obtener el SSID conectado
ssid = subprocess.getoutput("iwgetid -r").strip()
if ssid:
print(f"SSID conectado obtenido con iwgetid: {ssid}")
return ssid
else:
# Fallback a nmcli si iwgetid no retorna SSID
output = subprocess.getoutput(
"nmcli -t -f ACTIVE,SSID dev wifi | grep '^yes' | cut -d':' -f2").strip()
if output:
print(f"SSID conectado obtenido con nmcli: {output}")
return output
else:
print(f"SSID conectado obtenido: None")
return None
except Exception as e:
print(f"Error al obtener SSID conectado: {e}", file=sys.stderr)
return None
def main():
# Para evitar múltiples instancias, usar una lock con D-Bus o similar
# Aquí, simplemente se muestra la ventana
window = NetworkMenu()
window.connect("destroy", Gtk.main_quit)
window.show_all()
Gtk.main()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
# wlogout (Power, Screen Lock, Suspend, etc)
# Set variables for parameters
A_2160=680
B_2160=750
A_1440=500
B_1440=550
A_1080=300
B_1080=380
A_720=50
B_720=50
# Check if wlogout is already running
if pgrep -x "wlogout" > /dev/null; then
pkill -x "wlogout"
exit 0
fi
# Detect monitor resolution and scaling factor
resolution=$(hyprctl -j monitors | jq -r '.[] | select(.focused==true) | .height / .scale' | awk -F'.' '{print $1}')
hypr_scale=$(hyprctl -j monitors | jq -r '.[] | select(.focused==true) | .scale')
echo "Detected Resolution: $resolution"
# Prints the screen and applies blur. Very slow, looking to make faster
#grim - | magick - -blur 0x8 /tmp/shot_blurred.png
#grim - | convert - -scale 2.5% -resize 4000% /tmp/shot_blurred.png
# Set parameters based on screen resolution and scaling factor
if ((resolution == 1353)); then
wlogout --protocol layer-shell -b 6 -T 600 -B 560 &
echo "Setting parameters for custom 1353p resolution"
exit 0
fi
if ((resolution >= 2160)); then
wlogout --protocol layer-shell -b 3 -T $(awk "BEGIN {printf \"%.0f\", $A_2160 * 2160 * $hypr_scale / $resolution}") -B $(awk "BEGIN {printf \"%.0f\", $B_2160 * 2160 * $hypr_scale / $resolution}") &
echo "Setting parameters for resolution >= 2160p"
elif ((resolution >= 1440)); then
wlogout --protocol layer-shell -b 6 -T $(awk "BEGIN {printf \"%.0f\", $A_1440 * 1440 * $hypr_scale / $resolution}") -B $(awk "BEGIN {printf \"%.0f\", $B_1440 * 1440 * $hypr_scale / $resolution}") &
echo "Setting parameters for resolution >= 1440p"
elif ((resolution >= 1080)); then
wlogout --protocol layer-shell -b 6 -T $(awk "BEGIN {printf \"%.0f\", $A_1080 * 1080 * $hypr_scale / $resolution}") -B $(awk "BEGIN {printf \"%.0f\", $B_1080 * 1080 * $hypr_scale / $resolution}") &
echo "Setting parameters for resolution >= 1080p"
elif ((resolution > 720)); then
wlogout --protocol layer-shell -b 3 -T $(awk "BEGIN {printf \"%.0f\", $A_720 * 720 * $hypr_scale / $resolution}") -B $(awk "BEGIN {printf \"%.0f\", $B_720 * 720 * $hypr_scale / $resolution}") &
echo "Setting parameters for resolution >= 720p"
else
wlogout &
echo "Setting default parameters for resolution <= 720p"
fi

572
.config/waybar/style.css Normal file
View File

@ -0,0 +1,572 @@
* {
font-family: "Ubuntu Nerd Font", "Font Awesome 6 Pro";
font-size: 15px;
}
window#waybar {
background-color: #242933;
color: #ffffff;
border-radius: 8px;
border-width: 3px;
border-color: #3b4252;
border-style: solid;
}
window#waybar.hidden {
opacity: 0.1;
}
#window {
color: #64727d;
margin-left: 50px;
margin-right: 50px;
margin-top: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#workspaces,
#custom-calendar_clock,
#custom-weather,
#cpu,
#memory,
#custom-updtes,
#custom-rate,
#custom-inetspeedup,
#custom-inetspeeddown,
#tray,
#custom-separator,
#mode,
#custom-lock,
#workspaces,
#idle_inhibitor,
#power-profiles-daemon,
#custom-fans,
#custom-powermenu,
#custom-launcher,
#custom-vpn,
#custom-borg,
#temperature,
#custom-temps,
#cpu {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#mpris {
color: #fa9f8e;
}
#custom-roon {
color: #fa9f8e;
}
#custom-dmp_nowplaying {
color: #fa9f8e;
}
#custom-lyrics {
margin-left: 10px;
margin-right: 10px;
}
#image {
margin-left: 10px;
margin-right: 10px;
}
#temperature {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#custom-temps {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#custom-tailscale {
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
#cutom-borg {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#power-profiles-daemon {
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
#custom-fans {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#memory {
font-size: 15px;
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
#custom-updates {
font-size: 15px;
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
#custom-inetspeeddown {
font-size: 15px;
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
#custom-inetspeedup {
font-size: 15px;
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
@keyframes button_activate {
from {
opacity: 0.3;
}
to {
opacity: 1;
}
}
#workspaces button {
border: none;
color: #d4d2a9;
padding: 3px;
background: transparent;
box-shadow: none;
}
#workspaces button.active {
color: #81a1c1;
background: radial-gradient(
circle,
rgba(20, 48, 187, 0.699) 5%,
rgba(36, 39, 54, 0.5) 18%,
rgba(132, 129, 156, 0) 23%,
rgba(70, 76, 98, 0.201) 24%,
rgba(132, 129, 156, 0) 30%
);
animation: button_activate 0.2s ease-in-out;
}
#workspaces button.urgent {
color: #fa9f8e;
}
#workspaces button.persistent {
color: #85909e;
}
#workspaces button:hover {
border: none;
background: radial-gradient(
circle,
rgba(25, 79, 255, 0.1) 20%,
rgba(132, 129, 156, 0) 30%
);
}
#workspaces button.active:hover,
#workspaces button.urgent:hover {
background: inherit;
}
#custom-launcher {
margin-left: 10px;
margin-right: 10px;
padding-right: 5px;
padding-left: 0px;
font-size: 22px;
color: #7a95c9;
margin-top: 1px;
margin-bottom: 1px;
border-radius: 8px 8px 8px 8px;
}
#workspaces,
#custom-calendar_clock,
#custom-weather,
#custom-network_speed,
#cpu,
#custom-song_name,
#custom-vpn,
#custom-borg,
#temperature,
#custom-temps,
#custom-fans,
#power-profiles-daemon,
#memory,
#custom-updates,
#custom-tailscale,
#custom-rate,
#custom-inetspeedup,
#custom-inetspeeddown,
#pulseaudio,
#custom-separator,
#tray #network {
background-color: #3b4252;
padding: 0em 2em;
font-size: 20px;
padding-left: 7.5px;
padding-right: 7.5px;
padding-top: 2px;
padding-bottom: 2px;
margin-top: 10px;
margin-bottom: 10px;
margin-right: 10px;
font-size: 19px;
}
#custom-separator {
color: #1b5e20;
margin: 0 5px;
}
#workspaces {
padding-right: 10px;
}
#custom-pwrate {
color: #81a1c1;
padding-left: 10px;
margin-right: 15px;
font-size: 13px;
border-radius: 0px 8px 8px 0px;
margin-left: -15px;
}
#wireplumber {
color: #81a1c1;
padding-left: 10px;
margin-right: 10px;
font-size: 23px;
border-radius: 0px 8px 8px 0px;
margin-left: -15px;
}
#wireplumber.muted {
color: #fb958b;
padding-left: 10px;
font-size: 23px;
border-radius: 0px 8px 8px 0px;
}
#backlight {
color: #81a1c1;
padding-left: 10px;
padding-right: 10px;
font-size: 24px;
border-radius: 8px 8px 8px 8px;
}
#custom-wf-recorder {
color: #ff0000;
}
#network {
padding-left: 0.2em;
color: #5e81ac;
border-radius: 8px 8px 8px 8px;
padding-left: 12px;
padding-right: 12px;
font-size: 19px;
}
#network.disconnected {
color: #fb958b;
}
#battery {
color: #81a1c1;
font-size: 19px;
}
#battery.critical,
#battery.warning,
#battery.full,
#battery.plugged {
color: #81a1c1;
font-size: 19px;
}
#battery.charging {
font-size: 19px;
}
#battery.full,
#battery.plugged {
font-size: 19px;
}
@keyframes blink {
to {
background-color: rgba(30, 34, 42, 0.5);
color: #abb2bf;
}
}
#battery.warning {
color: #ecd3a0;
}
#battery.critical:not(.charging) {
color: #fb958b;
}
#custom-lock {
color: #ecd3a0;
padding: 0 15px 0 15px;
margin-left: 7px;
margin-top: 7px;
margin-bottom: 7px;
}
#custom-calendar_clock {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#bluetooth {
color: #fb958b;
padding-left: 10px;
margin-right: -5px;
font-size: 18px;
border-radius: 0px 8px 8px 0px;
margin-left: -5px;
}
#bluetooth.on {
color: #fb958b;
padding-left: 10px;
margin-right: -5px;
font-size: 18px;
border-radius: 0px 8px 8px 0px;
margin-left: -5px;
}
#bluetooth.connected {
color: #81a1c1;
padding-left: 10px;
margin-right: -5px;
font-size: 18px;
border-radius: 0px 8px 8px 0px;
margin-left: -5px;
}
#bluetooth.off {
color: #8a909e;
padding-left: 10px;
margin-right: -5px;
font-size: 18px;
border-radius: 0px 8px 8px 0px;
margin-left: -5px;
}
#custom-powermenu {
color: #e78284;
margin-right: 12px;
margin-left: 12px;
border-radius: 8px;
padding: 0 6px 0 6.8px;
margin-top: 7px;
margin-bottom: 7px;
}
tooltip {
font-family: Ubuntu Nerd Font;
border-radius: 8px;
padding: 15px;
background-color: #242933;
}
tooltip label {
font-family: Ubuntu Nerd Font;
padding: 5px;
background-color: #242933;
}
#now_playing > tooltip {
background-color: red;
}
label:focus {
background-color: #242933;
}
#tray {
margin-right: 10px;
margin-top: 10px;
margin-bottom: 10px;
font-size: 30px;
border-radius: 8px 8px 8px 8px;
}
#tray > .passive {
-gtk-icon-effect: dim;
}
#tray > .needs-attention {
-gtk-icon-effect: highlight;
background-color: #eb4d4b;
}
#idle_inhibitor {
background-color: #242933;
}
#idle_inhibitor.activated {
background-color: #ecf0f1;
color: #2d3436;
}
#memory,
#custom-updates,
#custom-rate,
#custom-tailscale,
#custom-inetspeedup,
#custom-inetspeeddown,
#cpu {
font-size: 15px;
}
#mpris {
font-size: 15px;
}
#custom-roon {
font-size: 15px;
}
#custom-dmp_nowplaying {
font-size: 15px;
}
#power-profiles-daemon {
font-size: 15px;
}
#custom-fans {
font-size: 15px;
}
#temperature {
font-size: 15px;
}
#custom-temps {
font-size: 15px;
}
#custom-vpn {
font-size: 15px;
}
#custom-borg {
font-size: 15px;
}
#custom-song_name,
#custom-spotify,
#custom-next,
#custom-pre {
background-color: #3b4252;
font-size: 20px;
padding-right: 6.5px;
margin-top: 10px;
margin-bottom: 10px;
font-weight: bold;
}
#custom-weather.severe {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.sunnyDay {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.clearNight {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.cloudyFoggyDay,
#custom-weather.cloudyFoggyNight {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.rainyDay,
#custom-weather.rainyNight {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.showyIcyDay,
#custom-weather.snowyIcyNight {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-network_speed {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#waybar.info {
background-color: rgba(10, 10, 10, 0.0);
font-family: JetBrainsMonoNL Nerd Font Mono;
font-size: 14px;
}
#submap {
background-color: rgba(10, 10, 10, 0.8);
color: rgba(152, 239, 106, 1);
}

View File

@ -0,0 +1,549 @@
* {
font-family: "Ubuntu Nerd Font", "Font Awesome 6 Pro";
font-size: 15px;
}
window#waybar {
background-color: #242933;
color: #ffffff;
border-radius: 8px;
border-width: 3px;
border-color: #3b4252;
border-style: solid;
}
window#waybar.hidden {
opacity: 0.1;
}
#window {
color: #64727d;
margin-left: 50px;
margin-right: 50px;
margin-top: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#workspaces,
#custom-calendar_clock,
#custom-weather,
#cpu,
#memory,
#custom-rate,
#custom-inetspeedup,
#custom-inetspeeddown,
#tray,
#custom-separator,
#mode,
#custom-lock,
#workspaces,
#idle_inhibitor,
#power-profiles-daemon,
#custom-fans,
#custom-powermenu,
#custom-launcher,
#custom-vpn,
#custom-borg,
#cpu {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#mpris {
color: #fa9f8e;
}
#custom-roon {
color: #fa9f8e;
}
#custom-lyrics {
margin-left: 10px;
margin-right: 10px;
}
#image {
margin-left: 10px;
margin-right: 10px;
}
#temperature {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#custom-tailscale {
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
#cutom-borg {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#power-profiles-daemon {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#custom-fans {
color: #fa9f8e;
border-radius: 8px 8px 8px 8px;
}
#memory {
font-size: 15px;
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
#custom-inetspeeddown {
font-size: 15px;
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
#custom-inetspeedup {
font-size: 15px;
color: #81a1c1;
border-radius: 8px 8px 8px 8px;
}
@keyframes button_activate {
from {
opacity: 0.3;
}
to {
opacity: 1;
}
}
#workspaces button {
border: none;
color: #d4d2a9;
padding: 3px;
background: transparent;
box-shadow: none;
}
#workspaces button.active {
color: #81a1c1;
background: radial-gradient(
circle,
rgba(20, 48, 187, 0.699) 5%,
rgba(36, 39, 54, 0.5) 18%,
rgba(132, 129, 156, 0) 23%,
rgba(70, 76, 98, 0.201) 24%,
rgba(132, 129, 156, 0) 30%
);
animation: button_activate 0.2s ease-in-out;
}
#workspaces button.urgent {
color: #fa9f8e;
background: radial-gradient(
circle,
rgba(25, 79, 255, 0.3) 20%,
rgba(132, 129, 156, 0) 30%
);
}
#workspaces button.persistent {
color: #85909e;
}
#workspaces button:hover {
border: none;
background: radial-gradient(
circle,
rgba(25, 79, 255, 0.1) 20%,
rgba(132, 129, 156, 0) 30%
);
}
#workspaces button.active:hover,
#workspaces button.urgent:hover {
background: inherit;
}
#custom-launcher {
margin-left: 10px;
margin-right: 10px;
padding-right: 5px;
padding-left: 0px;
font-size: 22px;
color: #7a95c9;
margin-top: 1px;
margin-bottom: 1px;
border-radius: 8px 8px 8px 8px;
}
#workspaces,
#custom-calendar_clock,
#custom-weather,
#custom-network_speed,
#cpu,
#custom-song_name,
#custom-vpn,
#custom-borg,
#temperature,
#custom-fans,
#power-profiles-daemon,
#memory,
#custom-tailscale,
#custom-rate,
#custom-inetspeedup,
#custom-inetspeeddown,
#pulseaudio,
#custom-separator,
#tray #network {
background-color: #3b4252;
padding: 0em 2em;
font-size: 20px;
padding-left: 7.5px;
padding-right: 7.5px;
padding-top: 2px;
padding-bottom: 2px;
margin-top: 10px;
margin-bottom: 10px;
margin-right: 10px;
font-size: 19px;
}
#custom-separator {
color: #1b5e20;
margin: 0 5px;
}
#workspaces {
padding-right: 10px;
}
#custom-pwrate {
color: #81a1c1;
padding-left: 10px;
margin-right: 15px;
font-size: 13px;
border-radius: 0px 8px 8px 0px;
margin-left: -15px;
}
#wireplumber {
color: #81a1c1;
padding-left: 10px;
margin-right: 10px;
font-size: 23px;
border-radius: 0px 8px 8px 0px;
margin-left: -15px;
}
#wireplumber.muted {
color: #fb958b;
padding-left: 10px;
font-size: 23px;
border-radius: 0px 8px 8px 0px;
}
#backlight {
color: #81a1c1;
padding-left: 10px;
padding-right: 10px;
font-size: 24px;
border-radius: 8px 8px 8px 8px;
}
#custom-wf-recorder {
color: #ff0000;
}
#network {
padding-left: 0.2em;
color: #5e81ac;
border-radius: 8px 8px 8px 8px;
padding-left: 12px;
padding-right: 12px;
font-size: 19px;
}
#network.disconnected {
color: #fb958b;
}
#battery {
color: #81a1c1;
font-size: 19px;
}
#battery.critical,
#battery.warning,
#battery.full,
#battery.plugged {
color: #81a1c1;
font-size: 19px;
}
#battery.charging {
font-size: 19px;
}
#battery.full,
#battery.plugged {
font-size: 19px;
}
@keyframes blink {
to {
background-color: rgba(30, 34, 42, 0.5);
color: #abb2bf;
}
}
#battery.warning {
color: #ecd3a0;
}
#battery.critical:not(.charging) {
color: #fb958b;
}
#custom-lock {
color: #ecd3a0;
padding: 0 15px 0 15px;
margin-left: 7px;
margin-top: 7px;
margin-bottom: 7px;
}
#custom-calendar_clock {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#bluetooth {
color: #fb958b;
padding-left: 10px;
margin-right: -5px;
font-size: 18px;
border-radius: 0px 8px 8px 0px;
margin-left: -5px;
}
#bluetooth.on {
color: #fb958b;
padding-left: 10px;
margin-right: -5px;
font-size: 18px;
border-radius: 0px 8px 8px 0px;
margin-left: -5px;
}
#bluetooth.connected {
color: #81a1c1;
padding-left: 10px;
margin-right: -5px;
font-size: 18px;
border-radius: 0px 8px 8px 0px;
margin-left: -5px;
}
#bluetooth.off {
color: #8a909e;
padding-left: 10px;
margin-right: -5px;
font-size: 18px;
border-radius: 0px 8px 8px 0px;
margin-left: -5px;
}
#custom-powermenu {
color: #e78284;
margin-right: 12px;
margin-left: 12px;
border-radius: 8px;
padding: 0 6px 0 6.8px;
margin-top: 7px;
margin-bottom: 7px;
}
tooltip {
font-family: Ubuntu Nerd Font;
border-radius: 8px;
padding: 15px;
background-color: #242933;
}
tooltip label {
font-family: Ubuntu Nerd Font;
padding: 5px;
background-color: #242933;
}
#now_playing > tooltip {
background-color: red;
}
label:focus {
background-color: #242933;
}
#tray {
margin-right: 10px;
margin-top: 10px;
margin-bottom: 10px;
font-size: 30px;
border-radius: 8px 8px 8px 8px;
}
#tray > .passive {
-gtk-icon-effect: dim;
}
#tray > .needs-attention {
-gtk-icon-effect: highlight;
background-color: #eb4d4b;
}
#idle_inhibitor {
background-color: #242933;
}
#idle_inhibitor.activated {
background-color: #ecf0f1;
color: #2d3436;
}
#memory,
#custom-rate,
#custom-tailscale,
#custom-inetspeedup,
#custom-inetspeeddown,
#cpu {
font-size: 15px;
}
#mpris {
font-size: 15px;
}
#custom-roon {
font-size: 15px;
}
#power-profiles-daemon {
font-size: 15px;
}
#custom-fans {
font-size: 15px;
}
#temperature {
font-size: 15px;
}
#custom-vpn {
font-size: 15px;
}
#custom-borg {
font-size: 15px;
}
#custom-song_name,
#custom-spotify,
#custom-next,
#custom-pre {
background-color: #3b4252;
font-size: 20px;
padding-right: 6.5px;
margin-top: 10px;
margin-bottom: 10px;
font-weight: bold;
}
#custom-weather.severe {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.sunnyDay {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.clearNight {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.cloudyFoggyDay,
#custom-weather.cloudyFoggyNight {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.rainyDay,
#custom-weather.rainyNight {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather.showyIcyDay,
#custom-weather.snowyIcyNight {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-weather {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#custom-network_speed {
color: #8a909e;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px 8px 8px 8px;
}
#waybar.info {
background-color: rgba(10, 10, 10, 0.0);
font-family: JetBrainsMonoNL Nerd Font Mono;
font-size: 14px;
}
#submap {
background-color: rgba(10, 10, 10, 0.8);
color: rgba(152, 239, 106, 1);
}

210
.config/waybar/sway_config Normal file
View File

@ -0,0 +1,210 @@
{
"layer": "top",
"position": "top",
"margin-top": 10,
"margin-left": 10,
"margin-right": 10,
"height": 54,
"spacing": 0,
"modules-left": ["custom/launcher","sway/workspaces","cpu","temperature","power-profiles-daemon","memory","custom/vpn"],
"modules-center": ["sway/window"],
"modules-right": ["tray","custom/wf-recorder","bluetooth","network", "wireplumber","custom/weather","clock","battery","custom/powermenu"],
// "sway/workspaces": {
// "on-click": "activate",
// "format": "{icon}",
// "format-icons": {
// "default": "",
// "1": "",
// "2": "",
// "3": "",
// "4": "",
// "active": "󱓻",
// "urgent": "󱓻",
// },
// "persistent-workspaces": {
// "*": 4,
// }
// },
"sway/workspaces": {
"disable-scroll": true,
"all-outputs": true,
"format": "{icon}",
"format-icons": {
"1": "",
"2": "",
"3": "",
"4": "",
"urgent": "󱓻",
"focused": "󱓻",
"default": ""
},
"persistent-workspaces": {
"1": [],
"2": [],
"3": [],
"4": []
}
},
"custom/vpn": {
//pkill -RTMIN+10 waybar
"format": "VPN ",
"exec": "echo '{\"class\": \"connected\"}'",
"exec-if": "test -d /proc/sys/net/ipv4/conf/wg0",
"return-type": "json",
"signal": 10,
},
"custom/wf-recorder": {
//pkill -RTMIN+11 waybar
"format": "• ",
"exec": "echo '{\"class\": \"recording\"}'",
"exec-if": "pgrep -x wf-recorder >/dev/null",
"return-type": "json",
"signal": 11,
},
"backlight": {
"device": "amdgpu_bl1",
"format": "{icon}",
"format-icons": [" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "]
},
"custom/weather": {
"exec": "python ~/.config/waybar/scripts/weather.py",
"restart-interval": 300,
"return-type": "json",
// "on-click": "xdg-open https://weather.com/en-IN/weather/today/l/$(location_id)"
// "format-alt": "{alt}",
},
"bluetooth": {
"format": "",
"format-connected": "",
"format-connected-battery": "",
"tooltip-format": "{controller_alias}\t{controller_address}\n\n{num_connections} connected",
"tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}",
"tooltip-format-enumerate-connected": "{device_alias}\t{device_address}",
"tooltip-format-enumerate-connected-battery": "{device_alias}\t{device_address}\t{device_battery_percentage}%",
"on-click": "/home/teraflops/.local/bin/bt.sh",
"on-click-right" :"bluetoothctl connect 88:C9:E8:EF:69:1E",
},
"hyprland/window": {
"max-length": 80,
"format": "<span font='14' rise='-4444'>{}</span>",
"separate-outputs": true,
},
"temperature": {
"hwmon-path": ["/sys/class/thermal/thermal_zone0/temp"],
"critical-threshold": 80,
"format-critical": "{temperatureC}°C",
"format": "{temperatureC}°C"
},
"memory": {
"interval": 3,
"format": "  {}% ",
"max-length": 10
},
"cpu": {
"interval": 1,
"format": "  {}% ",
"max-length": 10
},
"power-profiles-daemon": {
"format": "{icon}",
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
"tooltip": true,
"format-icons": {
"default": "BAL",
"performance": "PERF",
"balanced": "BAL",
"power-saver": "PSAV"
}
},
"custom/powermenu": {
"format": "⏻ ",
"tooltip": false,
"on-click": "/home/teraflops/.config/waybar/scripts/wlogout.sh"
},
"tray": {
"spacing": 10
},
"clock": {
"tooltip-format": "<tt><small>{calendar}</small></tt>",
"format-alt": " {:%a, %d %b %Y} ",
"format": " {:%H:%M} ",
"calendar": {
"weeks-pos" : "none",
"on-scroll" : 1,
"format": {
"months": "<span color='#8fbcbb'><b>{}</b></span>",
"days": "<span color='#8a909e'>{}</span>",
"weeks": "<span color='#99ffdd'><b>W{}</b></span>",
"weekdays": "<span color='#81a1c1'><b>{}</b></span>",
"today": "<span color='#88c0d0'><b>{}</b></span>"
}
},
"actions": {
"on-click-right": "none",
"on-click-forward": "tz_up",
"on-click-backward": "tz_down",
"on-scroll-up": "shift_up",
"on-scroll-down": "shift_down"
}
},
"network": {
"format-wifi": "{icon}",
"format-disconnected": "󰤭 ",
"format-icons": ["󰤯 ","󰤟 ","󰤢 ","󰤥 ","󰤨 "],
"tooltip-format-wifi": "{essid}",
"tooltip-format-disconnected": "Disconnected",
"nospacing": 1,
},
"battery": {
"interval": 60,
"states": {
"warning": 30,
"critical": 15
},
"format": "{icon}",
"format-charging": " ",
"format-plugged": "{icon}",
"format-icons": [" ", " ", " ", " ", " "],
"max-length": 25,
"on-click": "gnome-terminal -- battop",
},
"wireplumber": {
"format": "{icon}",
"nospacing": 1,
"tooltip-format": "Volume : {volume}%",
"format-muted": "󰝟 ",
"format-icons": {
"headphone": " ",
"default": ["󰕿 ", "󰖀 ", "󰕾 "]
},
"on-click": "pamixer -t",
"on-click-right": "pavucontrol",
"scroll-step": 5,
},
"custom/launcher": {
"format": "  ",
"tooltip": false,
"on-click": "/home/teraflops/.config/rofi/launchers/misc/launcher.sh &"
},
}