feat(waybar): island floating 3-pills + popups + deploy
- Config waybar 3-pills glassmorphism (left/center/right) - Scripts : gpu, network, power-profile (toggle+luminosité/profil), cava daemon+reader, wob (volume/luminosité), rofi-launcher - Popup media GTK3 : volume sortie+entrée + luminosité (vc-media-popup.py) - Profil énergie : cycle balanced→low-power→performance avec brightnessctl - Autostart COSMIC : waybar.desktop + wob.desktop - Thème COSMIC Light ajouté (accent violet-chaton) - deploy : +autostart, +sudoers platform_profile, +udev platform_profile, +scripts .py waybar, +CosmicTheme.Light
This commit is contained in:
245
INSTALL/configs/waybar/scripts/vc-brightness-popup.py
Executable file
245
INSTALL/configs/waybar/scripts/vc-brightness-popup.py
Executable file
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env python3
|
||||
# vc-brightness-popup.py — Popup luminosité violet-chaton
|
||||
# Lancé par le clic sur le module backlight de waybar
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('GtkLayerShell', '0.1')
|
||||
from gi.repository import Gtk, Gdk, GtkLayerShell, GLib
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
|
||||
# ── CSS ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
CSS = b"""
|
||||
window {
|
||||
background-color: rgba(52, 28, 74, 0.93);
|
||||
border: 3px solid rgba(255, 121, 198, 0.78);
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
#container {
|
||||
padding: 14px 20px 16px 20px;
|
||||
}
|
||||
|
||||
#bright-icon {
|
||||
color: #8be9fd;
|
||||
font-family: "JetBrainsMono Nerd Font";
|
||||
font-size: 18px;
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
#bright-title {
|
||||
color: rgba(248, 248, 242, 0.55);
|
||||
font-family: "JetBrainsMono Nerd Font";
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#device-name {
|
||||
color: #8be9fd;
|
||||
font-family: "JetBrainsMono Nerd Font";
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#bright-pct {
|
||||
color: #f8f8f2;
|
||||
font-family: "JetBrainsMono Nerd Font";
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
min-width: 44px;
|
||||
}
|
||||
|
||||
#separator {
|
||||
color: rgba(92, 73, 108, 0.60);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
scale trough {
|
||||
background-color: rgba(92, 73, 108, 0.55);
|
||||
border-radius: 3px;
|
||||
min-height: 6px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
scale highlight {
|
||||
background-color: #8be9fd;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
scale slider {
|
||||
background-color: #f8f8f2;
|
||||
border-radius: 50%;
|
||||
min-width: 18px;
|
||||
min-height: 18px;
|
||||
border: 2px solid rgba(139, 233, 253, 0.80);
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
scale slider:hover {
|
||||
background-color: #8be9fd;
|
||||
border-color: #8be9fd;
|
||||
}
|
||||
"""
|
||||
|
||||
POPUP_WIDTH = 300
|
||||
|
||||
# ── Brightness helpers ────────────────────────────────────────────────────────
|
||||
|
||||
def get_brightness():
|
||||
"""Retourne (valeur 0-100, nom du device)."""
|
||||
try:
|
||||
r = subprocess.run(
|
||||
['brightnessctl', 'info'],
|
||||
capture_output=True, text=True, timeout=2
|
||||
)
|
||||
pct_match = re.search(r'\((\d+)%\)', r.stdout)
|
||||
dev_match = re.search(r"Device '([^']+)'", r.stdout)
|
||||
pct = int(pct_match.group(1)) if pct_match else 50
|
||||
dev = dev_match.group(1) if dev_match else 'Écran'
|
||||
# Rendre le nom plus lisible
|
||||
dev = dev.replace('_', ' ').replace('backlight', '').strip().title()
|
||||
return pct, dev
|
||||
except Exception:
|
||||
return 50, 'Écran'
|
||||
|
||||
def set_brightness(pct):
|
||||
pct = max(1, min(100, pct)) # minimum 1% pour ne pas éteindre l'écran
|
||||
subprocess.run(
|
||||
['brightnessctl', 'set', f'{pct}%', '-q'],
|
||||
capture_output=True
|
||||
)
|
||||
# Feedback wob
|
||||
fifo = '/tmp/wob.fifo'
|
||||
if os.path.exists(fifo):
|
||||
try:
|
||||
fd = os.open(fifo, os.O_WRONLY | os.O_NONBLOCK)
|
||||
os.write(fd, f'{pct}\n'.encode())
|
||||
os.close(fd)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def bright_icon(pct):
|
||||
if pct < 34:
|
||||
return ''
|
||||
if pct < 67:
|
||||
return ''
|
||||
return ''
|
||||
|
||||
# ── Popup ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class BrightnessPopup(Gtk.Window):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._blocked = False
|
||||
|
||||
# ── Position : centré sous le module backlight ────────────────────────
|
||||
display = Gdk.Display.get_default()
|
||||
monitor = display.get_primary_monitor() if display else None
|
||||
screen_w = monitor.get_geometry().width if monitor else 1920
|
||||
|
||||
# Backlight est le 2e module de la pill droite (~250px depuis le bord)
|
||||
module_center = screen_w - 16 - 250
|
||||
margin_left = max(0, module_center - POPUP_WIDTH // 2)
|
||||
|
||||
GtkLayerShell.init_for_window(self)
|
||||
GtkLayerShell.set_layer(self, GtkLayerShell.Layer.OVERLAY)
|
||||
GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.TOP, True)
|
||||
GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.LEFT, True)
|
||||
GtkLayerShell.set_margin(self, GtkLayerShell.Edge.TOP, 66)
|
||||
GtkLayerShell.set_margin(self, GtkLayerShell.Edge.LEFT, margin_left)
|
||||
GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.ON_DEMAND)
|
||||
GtkLayerShell.set_exclusive_zone(self, -1)
|
||||
|
||||
self.set_decorated(False)
|
||||
self.set_resizable(False)
|
||||
self.set_default_size(POPUP_WIDTH, -1)
|
||||
|
||||
# ── CSS ───────────────────────────────────────────────────────────────
|
||||
provider = Gtk.CssProvider()
|
||||
provider.load_from_data(CSS)
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
Gdk.Screen.get_default(),
|
||||
provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
|
||||
# ── État initial ──────────────────────────────────────────────────────
|
||||
pct, dev = get_brightness()
|
||||
|
||||
# ── Layout ────────────────────────────────────────────────────────────
|
||||
container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
||||
container.set_name('container')
|
||||
self.add(container)
|
||||
|
||||
# Ligne device
|
||||
dev_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
||||
dev_icon = Gtk.Label(label='')
|
||||
dev_icon.set_name('bright-title')
|
||||
dev_row.pack_start(dev_icon, False, False, 0)
|
||||
dev_label = Gtk.Label(label=dev)
|
||||
dev_label.set_name('device-name')
|
||||
dev_label.set_halign(Gtk.Align.START)
|
||||
dev_label.set_ellipsize(3)
|
||||
dev_row.pack_start(dev_label, True, True, 0)
|
||||
container.pack_start(dev_row, False, False, 0)
|
||||
|
||||
# Séparateur
|
||||
sep = Gtk.Label(label='─' * 30)
|
||||
sep.set_name('separator')
|
||||
container.pack_start(sep, False, False, 4)
|
||||
|
||||
# En-tête : icône + "Luminosité" + %
|
||||
header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
|
||||
self.icon = Gtk.Label(label=bright_icon(pct))
|
||||
self.icon.set_name('bright-icon')
|
||||
header.pack_start(self.icon, False, False, 0)
|
||||
|
||||
title = Gtk.Label(label='Luminosité')
|
||||
title.set_name('bright-title')
|
||||
title.set_halign(Gtk.Align.START)
|
||||
header.pack_start(title, True, True, 0)
|
||||
|
||||
self.pct = Gtk.Label(label=f'{pct}%')
|
||||
self.pct.set_name('bright-pct')
|
||||
self.pct.set_halign(Gtk.Align.END)
|
||||
header.pack_end(self.pct, False, False, 0)
|
||||
container.pack_start(header, False, False, 0)
|
||||
|
||||
# Slider (min 1% pour ne pas éteindre l'écran)
|
||||
self.scale = Gtk.Scale.new_with_range(
|
||||
Gtk.Orientation.HORIZONTAL, 1, 100, 5
|
||||
)
|
||||
self.scale.set_value(pct)
|
||||
self.scale.set_draw_value(False)
|
||||
self.scale.set_hexpand(True)
|
||||
self.scale.connect('value-changed', self._on_changed)
|
||||
container.pack_start(self.scale, False, False, 0)
|
||||
|
||||
# ── Fermeture ─────────────────────────────────────────────────────────
|
||||
self.connect('key-press-event', self._on_key)
|
||||
self.connect('focus-out-event', lambda *_: self.destroy())
|
||||
|
||||
self.show_all()
|
||||
self.grab_focus()
|
||||
|
||||
def _on_changed(self, scale):
|
||||
if self._blocked:
|
||||
return
|
||||
pct = int(scale.get_value())
|
||||
self.pct.set_label(f'{pct}%')
|
||||
self.icon.set_label(bright_icon(pct))
|
||||
set_brightness(pct)
|
||||
|
||||
def _on_key(self, _widget, event):
|
||||
if event.keyval == Gdk.KEY_Escape:
|
||||
self.destroy()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
win = BrightnessPopup()
|
||||
win.connect('destroy', Gtk.main_quit)
|
||||
Gtk.main()
|
||||
Reference in New Issue
Block a user