- 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
246 lines
8.2 KiB
Python
Executable File
246 lines
8.2 KiB
Python
Executable File
#!/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()
|