Compare commits
10 Commits
2317eee72a
...
7d4d54c5b8
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d4d54c5b8 | |||
|
|
f0adb2a291 | ||
|
|
01133b78f0 | ||
|
|
50f84afb66 | ||
|
|
59c39260d1 | ||
|
|
0ba6bbd181 | ||
|
|
1690ec5eb4 | ||
|
|
2f3fc71ab7 | ||
|
|
40850161a5 | ||
|
|
8ee25d7853 |
@@ -2,7 +2,7 @@
|
|||||||
Name=Waybar
|
Name=Waybar
|
||||||
Comment=violet-chaton status bar
|
Comment=violet-chaton status bar
|
||||||
Type=Application
|
Type=Application
|
||||||
Exec=waybar
|
Exec=bash -c "exec 9>/tmp/waybar-start.lock; flock -n 9 || exit 0; exec waybar"
|
||||||
Hidden=false
|
Hidden=false
|
||||||
NoDisplay=false
|
NoDisplay=false
|
||||||
X-GNOME-Autostart-enabled=true
|
X-GNOME-Autostart-enabled=true
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
"custom/launcher",
|
"custom/launcher",
|
||||||
"custom/sep",
|
"custom/sep",
|
||||||
"cpu",
|
"cpu",
|
||||||
"temperature",
|
"custom/cpu-temp",
|
||||||
"custom/gpu",
|
"custom/gpu",
|
||||||
"memory",
|
"memory",
|
||||||
"disk",
|
"custom/disks",
|
||||||
"custom/sep",
|
"custom/sep",
|
||||||
"custom/network"
|
"custom/network"
|
||||||
],
|
],
|
||||||
@@ -77,15 +77,13 @@
|
|||||||
"interval": 2
|
"interval": 2
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Température ─────────────────────────────────────────────────────────
|
// ── Température CPU (auto-détection) ────────────────────────────────────
|
||||||
|
|
||||||
"temperature": {
|
"custom/cpu-temp": {
|
||||||
"thermal-zone": 9,
|
"exec": "~/.config/waybar/scripts/cpu-temp.sh",
|
||||||
"format": " {temperatureC}°",
|
"return-type": "json",
|
||||||
"format-critical": " {temperatureC}°",
|
"interval": 2,
|
||||||
"critical-threshold": 80,
|
"format": "{}"
|
||||||
"tooltip": false,
|
|
||||||
"interval": 2
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── GPU ─────────────────────────────────────────────────────────────────
|
// ── GPU ─────────────────────────────────────────────────────────────────
|
||||||
@@ -109,12 +107,13 @@
|
|||||||
"interval": 2
|
"interval": 2
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Disque ──────────────────────────────────────────────────────────────
|
// ── Disques (auto-détection) ─────────────────────────────────────────────
|
||||||
|
|
||||||
"disk": {
|
"custom/disks": {
|
||||||
"format": " {used}",
|
"exec": "~/.config/waybar/scripts/disks.sh",
|
||||||
"tooltip-format": " Disque /\n{used} / {total}\n{percentage_used}% utilisé",
|
"return-type": "json",
|
||||||
"interval": 30
|
"interval": 30,
|
||||||
|
"format": "{}"
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Réseau ──────────────────────────────────────────────────────────────
|
// ── Réseau ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
43
INSTALL/configs/waybar/scripts/cpu-temp.sh
Executable file
43
INSTALL/configs/waybar/scripts/cpu-temp.sh
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# cpu-temp.sh — température CPU auto-détection → JSON waybar
|
||||||
|
# Priorité 1 : thermal zone x86_pkg_temp (Intel) ou k10temp (AMD)
|
||||||
|
# Priorité 2 : hwmon coretemp / k10temp / zenpower
|
||||||
|
# Retourne vide si aucune source trouvée
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
local temp=$1
|
||||||
|
local cls="normal"
|
||||||
|
(( temp >= 80 )) && cls="critical"
|
||||||
|
(( temp >= 65 && temp < 80 )) && cls="warning"
|
||||||
|
printf '{"text":" %d°","tooltip":"CPU %d°C","class":"%s","percentage":%d}\n' \
|
||||||
|
"$temp" "$temp" "$cls" "$temp"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Priorité 1 — thermal_zone x86_pkg_temp (Intel) / TCPU / k10temp (AMD)
|
||||||
|
for zone in /sys/class/thermal/thermal_zone*/; do
|
||||||
|
zone_type=$(cat "${zone}type" 2>/dev/null) || continue
|
||||||
|
case "$zone_type" in
|
||||||
|
x86_pkg_temp|k10temp|TCPU|cpu_thermal)
|
||||||
|
temp_raw=$(cat "${zone}temp" 2>/dev/null) || continue
|
||||||
|
emit $(( temp_raw / 1000 ))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Priorité 2 — hwmon coretemp (Intel desktop) ou k10temp (AMD)
|
||||||
|
for hw in /sys/class/hwmon/hwmon*/; do
|
||||||
|
hw_name=$(cat "${hw}name" 2>/dev/null) || continue
|
||||||
|
case "$hw_name" in
|
||||||
|
coretemp|k10temp|zenpower|amd_energy)
|
||||||
|
for f in "${hw}temp1_input" "${hw}temp2_input"; do
|
||||||
|
[[ -r "$f" ]] || continue
|
||||||
|
temp_raw=$(cat "$f" 2>/dev/null) || continue
|
||||||
|
emit $(( temp_raw / 1000 ))
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Aucune source — module masqué
|
||||||
|
printf '{"text":"","class":"unavailable"}\n'
|
||||||
58
INSTALL/configs/waybar/scripts/disks.sh
Executable file
58
INSTALL/configs/waybar/scripts/disks.sh
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# disks.sh — liste les vrais systèmes de fichiers montés → JSON waybar
|
||||||
|
# Exclut tmpfs, devtmpfs, squashfs (snap), overlay, efi, etc.
|
||||||
|
|
||||||
|
TEXT=""
|
||||||
|
TOOLTIP=" Disques\n"
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
fs=$(awk '{print $1}' <<< "$line")
|
||||||
|
size=$(awk '{print $2}' <<< "$line")
|
||||||
|
used=$(awk '{print $3}' <<< "$line")
|
||||||
|
avail=$(awk '{print $4}' <<< "$line")
|
||||||
|
pct=$(awk '{print $5}' <<< "$line")
|
||||||
|
mnt=$(awk '{print $6}' <<< "$line")
|
||||||
|
|
||||||
|
# Exclure mounts sans intérêt
|
||||||
|
[[ "$mnt" == /snap/* ]] && continue
|
||||||
|
[[ "$mnt" == /boot/efi ]] && continue
|
||||||
|
[[ "$mnt" == /boot ]] && continue
|
||||||
|
[[ "$mnt" == /recovery ]] && continue
|
||||||
|
[[ "$mnt" == /run* ]] && continue
|
||||||
|
[[ "$mnt" == /sys* ]] && continue
|
||||||
|
[[ "$mnt" == /proc* ]] && continue
|
||||||
|
[[ "$mnt" == /dev* ]] && continue
|
||||||
|
|
||||||
|
# Icône selon le point de montage
|
||||||
|
case "$mnt" in
|
||||||
|
/) icon="" ;;
|
||||||
|
/home) icon="" ;;
|
||||||
|
/data*) icon="" ;;
|
||||||
|
/media*) icon="" ;;
|
||||||
|
/mnt*) icon="" ;;
|
||||||
|
*) icon="" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Texte compact : icône + montage court + espace utilisé
|
||||||
|
label=$(basename "$mnt")
|
||||||
|
[[ "$mnt" == "/" ]] && label="/"
|
||||||
|
[[ -n "$TEXT" ]] && TEXT+=" "
|
||||||
|
TEXT+="${icon} ${label}: ${used}"
|
||||||
|
|
||||||
|
TOOLTIP+="${icon} ${mnt}\n Utilisé : ${used} / ${size} (${pct})\n Libre : ${avail}\n"
|
||||||
|
|
||||||
|
done < <(df -hP --exclude-type=tmpfs \
|
||||||
|
--exclude-type=devtmpfs \
|
||||||
|
--exclude-type=squashfs \
|
||||||
|
--exclude-type=overlay \
|
||||||
|
--exclude-type=fuse.portal \
|
||||||
|
--exclude-type=efivarfs \
|
||||||
|
2>/dev/null | tail -n +2 | sort -k6)
|
||||||
|
|
||||||
|
if [[ -z "$TEXT" ]]; then
|
||||||
|
printf '{"text":" N/A","tooltip":"Aucun disque détecté","class":"unavailable"}\n'
|
||||||
|
else
|
||||||
|
# Échapper uniquement les guillemets pour JSON (\n reste tel quel = saut de ligne)
|
||||||
|
TOOLTIP_JSON=$(printf '%s' "$TOOLTIP" | sed 's/"/\\"/g')
|
||||||
|
printf '{"text":"%s","tooltip":"%s"}\n' "$TEXT" "$TOOLTIP_JSON"
|
||||||
|
fi
|
||||||
@@ -3,19 +3,17 @@
|
|||||||
|
|
||||||
STATE_FILE="/tmp/waybar_net_state"
|
STATE_FILE="/tmp/waybar_net_state"
|
||||||
|
|
||||||
# Détecter l'interface active
|
# Détecter l'interface active via la route par défaut (portable sur tous les PC)
|
||||||
IFACE=""
|
IFACE=$(ip route get 1.1.1.1 2>/dev/null \
|
||||||
for candidate in enp7s0 enp6s0 eth0; do
|
| awk '/dev/{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' \
|
||||||
if [[ -d "/sys/class/net/$candidate" && "$(cat /sys/class/net/$candidate/operstate 2>/dev/null)" == "up" ]]; then
|
| head -1)
|
||||||
IFACE="$candidate"; TYPE="eth"; break
|
|
||||||
|
if [[ -n "$IFACE" ]]; then
|
||||||
|
if [[ -d "/sys/class/net/$IFACE/wireless" || -d "/sys/class/net/$IFACE/phy80211" ]]; then
|
||||||
|
TYPE="wifi"
|
||||||
|
else
|
||||||
|
TYPE="eth"
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
if [[ -z "$IFACE" ]]; then
|
|
||||||
for candidate in wlp8s0 wlp0s20f3 wlan0; do
|
|
||||||
if [[ -d "/sys/class/net/$candidate" && "$(cat /sys/class/net/$candidate/operstate 2>/dev/null)" == "up" ]]; then
|
|
||||||
IFACE="$candidate"; TYPE="wifi"; break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$IFACE" ]]; then
|
if [[ -z "$IFACE" ]]; then
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ fi
|
|||||||
|
|
||||||
# ── Affichage JSON ────────────────────────────────────────────────────────────
|
# ── Affichage JSON ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# PC fixe ou VM sans gestion de profil → module masqué
|
||||||
|
if [[ ! -f /sys/firmware/acpi/platform_profile ]]; then
|
||||||
|
printf '{"text":"","class":"unavailable"}\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
PROFILE=$(cat /sys/firmware/acpi/platform_profile 2>/dev/null || echo "unknown")
|
PROFILE=$(cat /sys/firmware/acpi/platform_profile 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
case "$PROFILE" in
|
case "$PROFILE" in
|
||||||
|
|||||||
@@ -1,245 +0,0 @@
|
|||||||
#!/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()
|
|
||||||
@@ -3,9 +3,16 @@
|
|||||||
# Lancé depuis le clic sur wireplumber OU backlight
|
# Lancé depuis le clic sur wireplumber OU backlight
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
import math
|
||||||
|
import threading
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
gi.require_version('GtkLayerShell', '0.1')
|
gi.require_version('GtkLayerShell', '0.1')
|
||||||
from gi.repository import Gtk, Gdk, GtkLayerShell, GLib
|
gi.require_version('GdkPixbuf', '2.0')
|
||||||
|
gi.require_version('Pango', '1.0')
|
||||||
|
gi.require_version('PangoCairo', '1.0')
|
||||||
|
from gi.repository import Gtk, Gdk, GtkLayerShell, GLib, GdkPixbuf, Pango, PangoCairo
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -135,6 +142,31 @@ scale.audio.muted slider {
|
|||||||
border-color: rgba(108, 112, 134, 0.60);
|
border-color: rgba(108, 112, 134, 0.60);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Sélecteur de périphérique de sortie ────────────────────────────────────── */
|
||||||
|
|
||||||
|
#device-btn {
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgba(248, 248, 242, 0.65);
|
||||||
|
font-family: "JetBrainsMono Nerd Font";
|
||||||
|
font-size: 11px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#device-btn:hover {
|
||||||
|
background-color: rgba(91, 70, 113, 0.55);
|
||||||
|
color: #f8f8f2;
|
||||||
|
border-color: rgba(92, 73, 108, 0.60);
|
||||||
|
}
|
||||||
|
|
||||||
|
#device-btn.active {
|
||||||
|
background-color: rgba(255, 121, 198, 0.14);
|
||||||
|
color: #ff79c6;
|
||||||
|
border-color: rgba(255, 121, 198, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Slider luminosité (cyan) ───────────────────────────────────────────────── */
|
/* ── Slider luminosité (cyan) ───────────────────────────────────────────────── */
|
||||||
|
|
||||||
scale.bright {
|
scale.bright {
|
||||||
@@ -176,14 +208,55 @@ scale.bright slider:hover {
|
|||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
min-width: 28px;
|
min-width: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Section MPRIS ────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
#mpris-title {
|
||||||
|
color: #f8f8f2;
|
||||||
|
font-family: "JetBrainsMono Nerd Font";
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mpris-artist {
|
||||||
|
color: rgba(248, 248, 242, 0.55);
|
||||||
|
font-family: "JetBrainsMono Nerd Font";
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mpris-btn {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #e79cfe;
|
||||||
|
font-family: "JetBrainsMono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mpris-btn:hover {
|
||||||
|
background-color: rgba(231, 156, 254, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#mpris-btn.play {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #ff79c6;
|
||||||
|
padding: 4px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mpris-btn.play:hover {
|
||||||
|
background-color: rgba(255, 121, 198, 0.15);
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
POPUP_WIDTH = 310
|
POPUP_WIDTH = 310
|
||||||
|
|
||||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def run(cmd, **kw):
|
def run(cmd, env=None, **kw):
|
||||||
return subprocess.run(cmd, capture_output=True, text=True, timeout=2, **kw)
|
return subprocess.run(cmd, capture_output=True, text=True, timeout=2,
|
||||||
|
env=env, **kw)
|
||||||
|
|
||||||
def get_sink_volume():
|
def get_sink_volume():
|
||||||
r = run(['wpctl', 'get-volume', '@DEFAULT_AUDIO_SINK@'])
|
r = run(['wpctl', 'get-volume', '@DEFAULT_AUDIO_SINK@'])
|
||||||
@@ -239,6 +312,93 @@ def _wob(msg):
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_sinks():
|
||||||
|
"""Retourne [(sink_name, description, is_default)] — exclut SUSPENDED."""
|
||||||
|
env = {**os.environ, 'LANG': 'C', 'LC_ALL': 'C'}
|
||||||
|
r_default = run(['pactl', 'get-default-sink'], env=env)
|
||||||
|
default_name = r_default.stdout.strip()
|
||||||
|
|
||||||
|
r_full = run(['pactl', 'list', 'sinks'], env=env)
|
||||||
|
sinks, state, name, desc = [], None, None, None
|
||||||
|
for line in r_full.stdout.splitlines():
|
||||||
|
st = re.search(r'^\s+State:\s+(\S+)', line)
|
||||||
|
nm = re.search(r'^\s+Name:\s+(.+)$', line)
|
||||||
|
ds = re.search(r'^\s+Description:\s+(.+)$', line)
|
||||||
|
if st: state = st.group(1)
|
||||||
|
elif nm: name = nm.group(1).strip()
|
||||||
|
elif ds and name:
|
||||||
|
desc = ds.group(1).strip()
|
||||||
|
if state != 'SUSPENDED':
|
||||||
|
sinks.append((name, desc, name == default_name))
|
||||||
|
state, name, desc = None, None, None
|
||||||
|
return sinks
|
||||||
|
|
||||||
|
def set_default_sink(name):
|
||||||
|
run(['pactl', 'set-default-sink', name])
|
||||||
|
|
||||||
|
def get_sources():
|
||||||
|
"""Retourne [(source_name, description, is_default)] — exclut les .monitor."""
|
||||||
|
env = {**os.environ, 'LANG': 'C', 'LC_ALL': 'C'}
|
||||||
|
r_default = run(['pactl', 'get-default-source'], env=env)
|
||||||
|
default_name = r_default.stdout.strip()
|
||||||
|
|
||||||
|
r_full = run(['pactl', 'list', 'sources'], env=env)
|
||||||
|
sources, name, desc = [], None, None
|
||||||
|
for line in r_full.stdout.splitlines():
|
||||||
|
nm = re.search(r'^\s+Name:\s+(.+)$', line)
|
||||||
|
ds = re.search(r'^\s+Description:\s+(.+)$', line)
|
||||||
|
if nm:
|
||||||
|
name = nm.group(1).strip()
|
||||||
|
elif ds and name:
|
||||||
|
desc = ds.group(1).strip()
|
||||||
|
if '.monitor' not in name:
|
||||||
|
sources.append((name, desc, name == default_name))
|
||||||
|
name, desc = None, None
|
||||||
|
return sources
|
||||||
|
|
||||||
|
def set_default_source(name):
|
||||||
|
run(['pactl', 'set-default-source', name])
|
||||||
|
|
||||||
|
def get_mpris_info():
|
||||||
|
"""Retourne dict ou None si pas de lecteur actif."""
|
||||||
|
try:
|
||||||
|
r = run(['playerctl', 'metadata', '--format',
|
||||||
|
'{{title}}||{{artist}}||{{mpris:artUrl}}||{{status}}'])
|
||||||
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||||
|
return None
|
||||||
|
if r.returncode != 0 or not r.stdout.strip():
|
||||||
|
return None
|
||||||
|
parts = r.stdout.strip().split('||', 3)
|
||||||
|
if len(parts) < 4:
|
||||||
|
return None
|
||||||
|
title, artist, art_url, status = [p.strip() for p in parts]
|
||||||
|
if not title:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
'title': title,
|
||||||
|
'artist': artist,
|
||||||
|
'art_url': art_url,
|
||||||
|
'status': status.lower(), # 'playing' | 'paused' | 'stopped'
|
||||||
|
}
|
||||||
|
|
||||||
|
def source_icon(name, desc):
|
||||||
|
s = (name + desc).lower()
|
||||||
|
if 'bluetooth' in s or 'bluez' in s: return ''
|
||||||
|
if 'usb' in s: return ''
|
||||||
|
if 'headset' in s or 'headphone' in s: return ''
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def sink_icon(name, desc):
|
||||||
|
s = (name + desc).lower()
|
||||||
|
if 'hdmi' in s or 'dp-' in s or 'displayport' in s: return ''
|
||||||
|
if 'bluetooth' in s or 'bluez' in s: return ''
|
||||||
|
if 'usb' in s: return ''
|
||||||
|
if 'headphone' in s or 'headset' in s: return ''
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def short_desc(desc, maxlen=16):
|
||||||
|
return desc if len(desc) <= maxlen else desc[:maxlen - 1] + '…'
|
||||||
|
|
||||||
def vol_icon(muted):
|
def vol_icon(muted):
|
||||||
return '' if muted else ''
|
return '' if muted else ''
|
||||||
|
|
||||||
@@ -250,26 +410,109 @@ def bright_icon(pct):
|
|||||||
if pct < 67: return ''
|
if pct < 67: return ''
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
# ── Art widget (album art / miniature YouTube) ────────────────────────────────
|
||||||
|
|
||||||
|
class ArtWidget(Gtk.DrawingArea):
|
||||||
|
SIZE = 72
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._pixbuf = None
|
||||||
|
self._url = None
|
||||||
|
self.set_size_request(self.SIZE, self.SIZE)
|
||||||
|
self.connect('draw', self._on_draw)
|
||||||
|
|
||||||
|
def load_url(self, url):
|
||||||
|
if url == self._url:
|
||||||
|
return
|
||||||
|
self._url = url
|
||||||
|
self._pixbuf = None
|
||||||
|
self.queue_draw()
|
||||||
|
if not url:
|
||||||
|
return
|
||||||
|
threading.Thread(target=self._fetch, args=(url,), daemon=True).start()
|
||||||
|
|
||||||
|
def _fetch(self, url):
|
||||||
|
try:
|
||||||
|
if url.startswith('file://'):
|
||||||
|
raw = GdkPixbuf.Pixbuf.new_from_file(url[7:])
|
||||||
|
else:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url, headers={'User-Agent': 'Mozilla/5.0'})
|
||||||
|
with urllib.request.urlopen(req, timeout=5) as resp:
|
||||||
|
data = resp.read()
|
||||||
|
loader = GdkPixbuf.PixbufLoader()
|
||||||
|
loader.write(data)
|
||||||
|
loader.close()
|
||||||
|
raw = loader.get_pixbuf()
|
||||||
|
|
||||||
|
if raw:
|
||||||
|
s = self.SIZE
|
||||||
|
ow, oh = raw.get_width(), raw.get_height()
|
||||||
|
scale = min(s / ow, s / oh)
|
||||||
|
nw = max(1, int(ow * scale))
|
||||||
|
nh = max(1, int(oh * scale))
|
||||||
|
pixbuf = raw.scale_simple(nw, nh, GdkPixbuf.InterpType.BILINEAR)
|
||||||
|
else:
|
||||||
|
pixbuf = None
|
||||||
|
except Exception:
|
||||||
|
pixbuf = None
|
||||||
|
GLib.idle_add(self._set_pixbuf, pixbuf, url)
|
||||||
|
|
||||||
|
def _set_pixbuf(self, pixbuf, url):
|
||||||
|
if url == self._url:
|
||||||
|
self._pixbuf = pixbuf
|
||||||
|
self.queue_draw()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _on_draw(self, _widget, cr):
|
||||||
|
s, r = self.SIZE, 10
|
||||||
|
|
||||||
|
# Coins arrondis (clip)
|
||||||
|
cr.new_sub_path()
|
||||||
|
cr.arc(r, r, r, math.pi, 3 * math.pi / 2)
|
||||||
|
cr.arc(s - r, r, r, -math.pi / 2, 0)
|
||||||
|
cr.arc(s - r, s - r, r, 0, math.pi / 2)
|
||||||
|
cr.arc(r, s - r, r, math.pi / 2, math.pi)
|
||||||
|
cr.close_path()
|
||||||
|
cr.clip()
|
||||||
|
|
||||||
|
# Fond violet
|
||||||
|
cr.set_source_rgba(73/255, 49/255, 97/255, 0.85)
|
||||||
|
cr.paint()
|
||||||
|
|
||||||
|
if self._pixbuf:
|
||||||
|
pw = self._pixbuf.get_width()
|
||||||
|
ph = self._pixbuf.get_height()
|
||||||
|
Gdk.cairo_set_source_pixbuf(cr, self._pixbuf,
|
||||||
|
(s - pw) / 2, (s - ph) / 2)
|
||||||
|
cr.paint()
|
||||||
|
else:
|
||||||
|
# Icône note de musique (placeholder)
|
||||||
|
layout = PangoCairo.create_layout(cr)
|
||||||
|
layout.set_markup('<span font="JetBrainsMono Nerd Font 24"></span>')
|
||||||
|
lw, lh = layout.get_size()
|
||||||
|
cr.set_source_rgba(231/255, 156/255, 254/255, 0.45)
|
||||||
|
cr.move_to((s - lw / Pango.SCALE) / 2,
|
||||||
|
(s - lh / Pango.SCALE) / 2)
|
||||||
|
PangoCairo.show_layout(cr, layout)
|
||||||
|
|
||||||
|
|
||||||
# ── Popup ─────────────────────────────────────────────────────────────────────
|
# ── Popup ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
class MediaPopup(Gtk.Window):
|
class MediaPopup(Gtk.Window):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._blk = False
|
self._blk = False
|
||||||
|
self._has_mpris = False
|
||||||
|
|
||||||
# ── Position ─────────────────────────────────────────────────────────
|
# ── Position — ancré à droite, toujours dans l'écran ─────────────────
|
||||||
display = Gdk.Display.get_default()
|
|
||||||
monitor = display.get_primary_monitor() if display else None
|
|
||||||
screen_w = monitor.get_geometry().width if monitor else 1920
|
|
||||||
module_center = screen_w - 16 - 210
|
|
||||||
margin_left = max(0, module_center - POPUP_WIDTH // 2)
|
|
||||||
|
|
||||||
GtkLayerShell.init_for_window(self)
|
GtkLayerShell.init_for_window(self)
|
||||||
GtkLayerShell.set_layer(self, GtkLayerShell.Layer.OVERLAY)
|
GtkLayerShell.set_layer(self, GtkLayerShell.Layer.OVERLAY)
|
||||||
GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.TOP, True)
|
GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.TOP, True)
|
||||||
GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.LEFT, True)
|
GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.RIGHT, True)
|
||||||
GtkLayerShell.set_margin(self, GtkLayerShell.Edge.TOP, 66)
|
GtkLayerShell.set_margin(self, GtkLayerShell.Edge.TOP, 66)
|
||||||
GtkLayerShell.set_margin(self, GtkLayerShell.Edge.LEFT, margin_left)
|
GtkLayerShell.set_margin(self, GtkLayerShell.Edge.RIGHT, 12)
|
||||||
GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.ON_DEMAND)
|
GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.ON_DEMAND)
|
||||||
GtkLayerShell.set_exclusive_zone(self, -1)
|
GtkLayerShell.set_exclusive_zone(self, -1)
|
||||||
self.set_decorated(False)
|
self.set_decorated(False)
|
||||||
@@ -295,10 +538,22 @@ class MediaPopup(Gtk.Window):
|
|||||||
box.set_name('container')
|
box.set_name('container')
|
||||||
self.add(box)
|
self.add(box)
|
||||||
|
|
||||||
|
# ╔═══ LECTURE (MPRIS) — affiché seulement si lecteur actif ════════════╗
|
||||||
|
mpris_info = get_mpris_info()
|
||||||
|
if mpris_info:
|
||||||
|
self._build_mpris_section(box, mpris_info)
|
||||||
|
sep0 = Gtk.Label(label='─' * 34)
|
||||||
|
sep0.set_name('separator')
|
||||||
|
box.pack_start(sep0, False, False, 0)
|
||||||
|
|
||||||
# ╔═══ SORTIE ══════════════════════════════════════════════════════════╗
|
# ╔═══ SORTIE ══════════════════════════════════════════════════════════╗
|
||||||
|
sinks = get_sinks()
|
||||||
box.pack_start(self._section_header('SORTIE', ''), False, False, 0)
|
box.pack_start(self._section_header('SORTIE', ''), False, False, 0)
|
||||||
box.pack_start(self._device_label(
|
self.sink_device_lbl = self._device_label(
|
||||||
get_node_name('@DEFAULT_AUDIO_SINK@')), False, False, 2)
|
get_node_name('@DEFAULT_AUDIO_SINK@'))
|
||||||
|
box.pack_start(self.sink_device_lbl, False, False, 2)
|
||||||
|
if len(sinks) > 1:
|
||||||
|
box.pack_start(self._sink_selector(sinks), False, False, 4)
|
||||||
sink_row, self.sink_scale, self.sink_pct, self.sink_icon = \
|
sink_row, self.sink_scale, self.sink_pct, self.sink_icon = \
|
||||||
self._slider_row(sink_vol, sink_muted, 'audio', vol_icon(sink_muted),
|
self._slider_row(sink_vol, sink_muted, 'audio', vol_icon(sink_muted),
|
||||||
self._toggle_sink_mute, '@DEFAULT_AUDIO_SINK@')
|
self._toggle_sink_mute, '@DEFAULT_AUDIO_SINK@')
|
||||||
@@ -309,9 +564,13 @@ class MediaPopup(Gtk.Window):
|
|||||||
sep1.set_name('separator')
|
sep1.set_name('separator')
|
||||||
box.pack_start(sep1, False, False, 0)
|
box.pack_start(sep1, False, False, 0)
|
||||||
|
|
||||||
|
sources = get_sources()
|
||||||
box.pack_start(self._section_header('ENTRÉE', ''), False, False, 0)
|
box.pack_start(self._section_header('ENTRÉE', ''), False, False, 0)
|
||||||
box.pack_start(self._device_label(
|
self.src_device_lbl = self._device_label(
|
||||||
get_node_name('@DEFAULT_AUDIO_SOURCE@')), False, False, 2)
|
get_node_name('@DEFAULT_AUDIO_SOURCE@'))
|
||||||
|
box.pack_start(self.src_device_lbl, False, False, 2)
|
||||||
|
if len(sources) > 1:
|
||||||
|
box.pack_start(self._source_selector(sources), False, False, 4)
|
||||||
src_row, self.src_scale, self.src_pct, self.src_icon = \
|
src_row, self.src_scale, self.src_pct, self.src_icon = \
|
||||||
self._slider_row(src_vol, src_muted, 'audio', mic_icon(src_muted),
|
self._slider_row(src_vol, src_muted, 'audio', mic_icon(src_muted),
|
||||||
self._toggle_src_mute, '@DEFAULT_AUDIO_SOURCE@')
|
self._toggle_src_mute, '@DEFAULT_AUDIO_SOURCE@')
|
||||||
@@ -333,23 +592,167 @@ class MediaPopup(Gtk.Window):
|
|||||||
self.connect('focus-out-event', lambda *_: self.destroy())
|
self.connect('focus-out-event', lambda *_: self.destroy())
|
||||||
self.show_all()
|
self.show_all()
|
||||||
# GTK3 bug : set_value() avant réalisation → highlight width=0 à max
|
# GTK3 bug : set_value() avant réalisation → highlight width=0 à max
|
||||||
# Forcer un re-calcul après que les widgets sont visibles
|
|
||||||
GLib.idle_add(self._redraw_scales)
|
GLib.idle_add(self._redraw_scales)
|
||||||
self.grab_focus()
|
self.grab_focus()
|
||||||
|
|
||||||
def _redraw_scales(self):
|
# ── MPRIS section builder ─────────────────────────────────────────────────
|
||||||
"""Force GTK3 à recalculer les highlights.
|
|
||||||
set_value(même_valeur) est un no-op — on oscille ±1 pour déclencher
|
def _build_mpris_section(self, box, info):
|
||||||
un vrai recalcul de la position du highlight dans le trough."""
|
box.pack_start(self._section_header('LECTURE', ''), False, False, 0)
|
||||||
|
|
||||||
|
content = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
|
||||||
|
content.set_margin_top(4)
|
||||||
|
content.set_margin_bottom(4)
|
||||||
|
|
||||||
|
# Album art
|
||||||
|
self.mpris_art = ArtWidget()
|
||||||
|
content.pack_start(self.mpris_art, False, False, 0)
|
||||||
|
|
||||||
|
# Infos + contrôles
|
||||||
|
info_col = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||||
|
info_col.set_valign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
|
self.mpris_title = Gtk.Label(label=info['title'])
|
||||||
|
self.mpris_title.set_name('mpris-title')
|
||||||
|
self.mpris_title.set_halign(Gtk.Align.START)
|
||||||
|
self.mpris_title.set_ellipsize(3)
|
||||||
|
self.mpris_title.set_max_width_chars(20)
|
||||||
|
info_col.pack_start(self.mpris_title, False, False, 0)
|
||||||
|
|
||||||
|
self.mpris_artist = Gtk.Label(label=info['artist'] or '—')
|
||||||
|
self.mpris_artist.set_name('mpris-artist')
|
||||||
|
self.mpris_artist.set_halign(Gtk.Align.START)
|
||||||
|
self.mpris_artist.set_ellipsize(3)
|
||||||
|
self.mpris_artist.set_max_width_chars(20)
|
||||||
|
info_col.pack_start(self.mpris_artist, False, False, 0)
|
||||||
|
|
||||||
|
# Contrôles prev / play-pause / next
|
||||||
|
ctrl = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
|
||||||
|
ctrl.set_margin_top(4)
|
||||||
|
|
||||||
|
btn_prev = Gtk.Button(label='')
|
||||||
|
btn_prev.set_name('mpris-btn')
|
||||||
|
btn_prev.connect('clicked', lambda _: run(['playerctl', 'previous']))
|
||||||
|
|
||||||
|
self.btn_play = Gtk.Button(
|
||||||
|
label='' if info['status'] == 'playing' else '')
|
||||||
|
self.btn_play.set_name('mpris-btn')
|
||||||
|
self.btn_play.get_style_context().add_class('play')
|
||||||
|
self.btn_play.connect('clicked', self._on_play_pause)
|
||||||
|
|
||||||
|
btn_next = Gtk.Button(label='')
|
||||||
|
btn_next.set_name('mpris-btn')
|
||||||
|
btn_next.connect('clicked', lambda _: run(['playerctl', 'next']))
|
||||||
|
|
||||||
|
ctrl.pack_start(btn_prev, False, False, 0)
|
||||||
|
ctrl.pack_start(self.btn_play, False, False, 0)
|
||||||
|
ctrl.pack_start(btn_next, False, False, 0)
|
||||||
|
info_col.pack_start(ctrl, False, False, 0)
|
||||||
|
|
||||||
|
content.pack_start(info_col, True, True, 0)
|
||||||
|
box.pack_start(content, False, False, 4)
|
||||||
|
|
||||||
|
# Charger l'artwork en arrière-plan
|
||||||
|
if info.get('art_url'):
|
||||||
|
self.mpris_art.load_url(info['art_url'])
|
||||||
|
|
||||||
|
self._has_mpris = True
|
||||||
|
self._mpris_status = info['status']
|
||||||
|
|
||||||
|
def _on_play_pause(self, _btn):
|
||||||
|
run(['playerctl', 'play-pause'])
|
||||||
|
info = get_mpris_info()
|
||||||
|
if info and self._has_mpris:
|
||||||
|
self._mpris_status = info['status']
|
||||||
|
self.btn_play.set_label(
|
||||||
|
'' if info['status'] == 'playing' else '')
|
||||||
|
|
||||||
|
# ── Sélecteur de sortie ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _sink_selector(self, sinks):
|
||||||
|
self._sink_btns = {}
|
||||||
|
self._sink_descs = {}
|
||||||
|
col = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||||
|
for name, desc, is_default in sinks:
|
||||||
|
self._sink_descs[name] = desc
|
||||||
|
btn = Gtk.Button()
|
||||||
|
btn.set_name('device-btn')
|
||||||
|
btn.set_hexpand(True)
|
||||||
|
lbl = Gtk.Label(label=self._sink_label(name, desc, is_default))
|
||||||
|
lbl.set_halign(Gtk.Align.START)
|
||||||
|
btn.add(lbl)
|
||||||
|
if is_default:
|
||||||
|
btn.get_style_context().add_class('active')
|
||||||
|
btn.connect('clicked', self._on_sink_selected, name)
|
||||||
|
col.pack_start(btn, False, True, 0)
|
||||||
|
self._sink_btns[name] = btn
|
||||||
|
return col
|
||||||
|
|
||||||
|
def _sink_label(self, name, desc, active):
|
||||||
|
check = ' ' if active else ''
|
||||||
|
return f'{sink_icon(name, desc)} {desc}{check}'
|
||||||
|
|
||||||
|
def _source_selector(self, sources):
|
||||||
|
self._src_btns = {}
|
||||||
|
self._src_descs = {}
|
||||||
|
col = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||||
|
for name, desc, is_default in sources:
|
||||||
|
self._src_descs[name] = desc
|
||||||
|
btn = Gtk.Button()
|
||||||
|
btn.set_name('device-btn')
|
||||||
|
btn.set_hexpand(True)
|
||||||
|
lbl = Gtk.Label(label=self._src_label(name, desc, is_default))
|
||||||
|
lbl.set_halign(Gtk.Align.START)
|
||||||
|
btn.add(lbl)
|
||||||
|
if is_default:
|
||||||
|
btn.get_style_context().add_class('active')
|
||||||
|
btn.connect('clicked', self._on_source_selected, name)
|
||||||
|
col.pack_start(btn, False, True, 0)
|
||||||
|
self._src_btns[name] = btn
|
||||||
|
return col
|
||||||
|
|
||||||
|
def _src_label(self, name, desc, active):
|
||||||
|
check = ' ' if active else ''
|
||||||
|
return f'{source_icon(name, desc)} {desc}{check}'
|
||||||
|
|
||||||
|
def _on_source_selected(self, _btn, name):
|
||||||
|
for n, b in self._src_btns.items():
|
||||||
|
b.get_style_context().remove_class('active')
|
||||||
|
b.get_child().set_label(self._src_label(n, self._src_descs[n], False))
|
||||||
|
self._src_btns[name].get_style_context().add_class('active')
|
||||||
|
self._src_btns[name].get_child().set_label(
|
||||||
|
self._src_label(name, self._src_descs[name], True))
|
||||||
|
set_default_source(name)
|
||||||
|
self.src_device_lbl.set_label(self._src_descs[name])
|
||||||
|
vol, muted = get_source_volume()
|
||||||
self._blk = True
|
self._blk = True
|
||||||
for scale in [self.sink_scale, self.src_scale, self.bright_scale]:
|
self.src_scale.set_value(vol)
|
||||||
v = scale.get_value()
|
self.src_pct.set_label(f'{vol}%')
|
||||||
adj = scale.get_adjustment()
|
|
||||||
lo = adj.get_lower()
|
|
||||||
scale.set_value(max(lo, v - 1)) # valeur différente → GTK recalcule
|
|
||||||
scale.set_value(v) # retour à la valeur réelle
|
|
||||||
self._blk = False
|
self._blk = False
|
||||||
return False
|
self._src_muted = muted
|
||||||
|
self._apply_mute(self.src_scale, self.src_icon, muted, mic_icon)
|
||||||
|
subprocess.Popen(['pkill', '-RTMIN+1', 'waybar'],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
def _on_sink_selected(self, _btn, name):
|
||||||
|
for n, b in self._sink_btns.items():
|
||||||
|
b.get_style_context().remove_class('active')
|
||||||
|
b.get_child().set_label(
|
||||||
|
self._sink_label(n, self._sink_descs[n], False))
|
||||||
|
self._sink_btns[name].get_style_context().add_class('active')
|
||||||
|
self._sink_btns[name].get_child().set_label(
|
||||||
|
self._sink_label(name, self._sink_descs[name], True))
|
||||||
|
set_default_sink(name)
|
||||||
|
self.sink_device_lbl.set_label(self._sink_descs[name])
|
||||||
|
vol, muted = get_sink_volume()
|
||||||
|
self._blk = True
|
||||||
|
self.sink_scale.set_value(vol)
|
||||||
|
self.sink_pct.set_label(f'{vol}%')
|
||||||
|
self._blk = False
|
||||||
|
self._sink_muted = muted
|
||||||
|
self._apply_mute(self.sink_scale, self.sink_icon, muted, vol_icon)
|
||||||
|
subprocess.Popen(['pkill', '-RTMIN+1', 'waybar'],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
# ── Builders UI ───────────────────────────────────────────────────────────
|
# ── Builders UI ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -376,7 +779,6 @@ class MediaPopup(Gtk.Window):
|
|||||||
"""Retourne (row, scale, pct_label, icon_btn)"""
|
"""Retourne (row, scale, pct_label, icon_btn)"""
|
||||||
row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
||||||
|
|
||||||
# Icône = bouton mute
|
|
||||||
icon_btn = Gtk.Button(label=icon_char)
|
icon_btn = Gtk.Button(label=icon_char)
|
||||||
icon_btn.set_name('mute-icon' if target == '@DEFAULT_AUDIO_SINK@' else 'mic-icon')
|
icon_btn.set_name('mute-icon' if target == '@DEFAULT_AUDIO_SINK@' else 'mic-icon')
|
||||||
if muted:
|
if muted:
|
||||||
@@ -384,7 +786,6 @@ class MediaPopup(Gtk.Window):
|
|||||||
icon_btn.connect('clicked', mute_cb)
|
icon_btn.connect('clicked', mute_cb)
|
||||||
row.pack_start(icon_btn, False, False, 0)
|
row.pack_start(icon_btn, False, False, 0)
|
||||||
|
|
||||||
# Slider
|
|
||||||
scale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 100, 5)
|
scale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 100, 5)
|
||||||
scale.set_value(val)
|
scale.set_value(val)
|
||||||
scale.set_draw_value(False)
|
scale.set_draw_value(False)
|
||||||
@@ -396,7 +797,6 @@ class MediaPopup(Gtk.Window):
|
|||||||
self._on_audio_changed(s, pct_lbl, t))
|
self._on_audio_changed(s, pct_lbl, t))
|
||||||
row.pack_start(scale, True, True, 0)
|
row.pack_start(scale, True, True, 0)
|
||||||
|
|
||||||
# %
|
|
||||||
pct_lbl = Gtk.Label(label=f'{val}%')
|
pct_lbl = Gtk.Label(label=f'{val}%')
|
||||||
pct_lbl.set_name('pct-label')
|
pct_lbl.set_name('pct-label')
|
||||||
pct_lbl.set_halign(Gtk.Align.END)
|
pct_lbl.set_halign(Gtk.Align.END)
|
||||||
@@ -469,7 +869,20 @@ class MediaPopup(Gtk.Window):
|
|||||||
self._apply_mute(self.src_scale, self.src_icon,
|
self._apply_mute(self.src_scale, self.src_icon,
|
||||||
self._src_muted, mic_icon)
|
self._src_muted, mic_icon)
|
||||||
|
|
||||||
|
def _redraw_scales(self):
|
||||||
|
"""Force GTK3 à recalculer les highlights."""
|
||||||
|
self._blk = True
|
||||||
|
for scale in [self.sink_scale, self.src_scale, self.bright_scale]:
|
||||||
|
v = scale.get_value()
|
||||||
|
adj = scale.get_adjustment()
|
||||||
|
lo = adj.get_lower()
|
||||||
|
scale.set_value(max(lo, v - 1))
|
||||||
|
scale.set_value(v)
|
||||||
|
self._blk = False
|
||||||
|
return False
|
||||||
|
|
||||||
def _refresh(self):
|
def _refresh(self):
|
||||||
|
# ── Audio ──────────────────────────────────────────────────────────────
|
||||||
sink_vol, sink_muted = get_sink_volume()
|
sink_vol, sink_muted = get_sink_volume()
|
||||||
src_vol, src_muted = get_source_volume()
|
src_vol, src_muted = get_source_volume()
|
||||||
|
|
||||||
@@ -489,6 +902,20 @@ class MediaPopup(Gtk.Window):
|
|||||||
self.src_pct.set_label(f'{src_vol}%')
|
self.src_pct.set_label(f'{src_vol}%')
|
||||||
self._blk = False
|
self._blk = False
|
||||||
|
|
||||||
|
# ── MPRIS ──────────────────────────────────────────────────────────────
|
||||||
|
if self._has_mpris:
|
||||||
|
info = get_mpris_info()
|
||||||
|
if info:
|
||||||
|
self.mpris_title.set_label(info['title'])
|
||||||
|
self.mpris_artist.set_label(info['artist'] or '—')
|
||||||
|
if info['status'] != self._mpris_status:
|
||||||
|
self._mpris_status = info['status']
|
||||||
|
self.btn_play.set_label(
|
||||||
|
'' if info['status'] == 'playing' else '')
|
||||||
|
cur_url = info.get('art_url', '')
|
||||||
|
if cur_url != self.mpris_art._url:
|
||||||
|
self.mpris_art.load_url(cur_url)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,422 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# vc-volume-popup.py — Popup volume slider violet-chaton
|
|
||||||
# Lancé par le clic sur le module wireplumber 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#vol-icon {
|
|
||||||
color: #ff79c6;
|
|
||||||
font-family: "JetBrainsMono Nerd Font";
|
|
||||||
font-size: 18px;
|
|
||||||
min-width: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#vol-title {
|
|
||||||
color: rgba(248, 248, 242, 0.55);
|
|
||||||
font-family: "JetBrainsMono Nerd Font";
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sink-name {
|
|
||||||
color: #8be9fd;
|
|
||||||
font-family: "JetBrainsMono Nerd Font";
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#vol-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: #ff79c6;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale slider {
|
|
||||||
background-color: #f8f8f2;
|
|
||||||
border-radius: 50%;
|
|
||||||
min-width: 18px;
|
|
||||||
min-height: 18px;
|
|
||||||
border: 2px solid rgba(255, 121, 198, 0.80);
|
|
||||||
box-shadow: none;
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale slider:hover {
|
|
||||||
background-color: #e79cfe;
|
|
||||||
border-color: #ff79c6;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mute-btn {
|
|
||||||
background: rgba(73, 49, 97, 0.50);
|
|
||||||
border: 1px solid rgba(92, 73, 108, 0.60);
|
|
||||||
border-radius: 8px;
|
|
||||||
color: rgba(248, 248, 242, 0.65);
|
|
||||||
font-family: "JetBrainsMono Nerd Font";
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 5px 16px;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mute-btn:hover {
|
|
||||||
background: rgba(255, 121, 198, 0.18);
|
|
||||||
border-color: rgba(255, 121, 198, 0.45);
|
|
||||||
color: #ff79c6;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mute-btn.muted {
|
|
||||||
color: #f38ba8;
|
|
||||||
border-color: rgba(243, 139, 168, 0.45);
|
|
||||||
background: rgba(243, 139, 168, 0.10);
|
|
||||||
}
|
|
||||||
|
|
||||||
#mic-btn {
|
|
||||||
background: rgba(73, 49, 97, 0.50);
|
|
||||||
border: 1px solid rgba(139, 233, 253, 0.35);
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #8be9fd;
|
|
||||||
font-family: "JetBrainsMono Nerd Font";
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 5px 16px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mic-btn:hover {
|
|
||||||
background: rgba(139, 233, 253, 0.12);
|
|
||||||
border-color: rgba(139, 233, 253, 0.60);
|
|
||||||
color: #8be9fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mic-btn.muted {
|
|
||||||
color: #f38ba8;
|
|
||||||
border-color: rgba(243, 139, 168, 0.45);
|
|
||||||
background: rgba(243, 139, 168, 0.10);
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
POPUP_WIDTH = 300
|
|
||||||
|
|
||||||
# ── Audio helpers ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def get_volume():
|
|
||||||
"""Retourne (volume 0-100, is_muted)"""
|
|
||||||
try:
|
|
||||||
r = subprocess.run(
|
|
||||||
['wpctl', 'get-volume', '@DEFAULT_AUDIO_SINK@'],
|
|
||||||
capture_output=True, text=True, timeout=2
|
|
||||||
)
|
|
||||||
parts = r.stdout.strip().split()
|
|
||||||
vol = int(float(parts[1]) * 100)
|
|
||||||
muted = '[MUTED]' in r.stdout
|
|
||||||
return min(max(vol, 0), 100), muted
|
|
||||||
except Exception:
|
|
||||||
return 50, False
|
|
||||||
|
|
||||||
def get_sink_name():
|
|
||||||
"""Retourne le nom humain de la sortie audio active."""
|
|
||||||
try:
|
|
||||||
r = subprocess.run(
|
|
||||||
['wpctl', 'inspect', '@DEFAULT_AUDIO_SINK@'],
|
|
||||||
capture_output=True, text=True, timeout=2
|
|
||||||
)
|
|
||||||
# Chercher node.description en priorité, sinon node.nick
|
|
||||||
for field in ('node.description', 'node.nick'):
|
|
||||||
m = re.search(rf'{field}\s*=\s*"([^"]+)"', r.stdout)
|
|
||||||
if m:
|
|
||||||
return m.group(1)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return 'Sortie audio'
|
|
||||||
|
|
||||||
def set_volume(vol):
|
|
||||||
subprocess.run(
|
|
||||||
['wpctl', 'set-volume', '-l', '1.0', '@DEFAULT_AUDIO_SINK@', f'{vol}%'],
|
|
||||||
capture_output=True
|
|
||||||
)
|
|
||||||
# Feedback wob (non-bloquant)
|
|
||||||
fifo = '/tmp/wob.fifo'
|
|
||||||
if os.path.exists(fifo):
|
|
||||||
try:
|
|
||||||
fd = os.open(fifo, os.O_WRONLY | os.O_NONBLOCK)
|
|
||||||
os.write(fd, f'{vol}\n'.encode())
|
|
||||||
os.close(fd)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def toggle_mute():
|
|
||||||
subprocess.run(
|
|
||||||
['wpctl', 'set-mute', '@DEFAULT_AUDIO_SINK@', 'toggle'],
|
|
||||||
capture_output=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_mic_muted():
|
|
||||||
"""Retourne True si le micro actif est muté."""
|
|
||||||
try:
|
|
||||||
r = subprocess.run(
|
|
||||||
['wpctl', 'get-volume', '@DEFAULT_AUDIO_SOURCE@'],
|
|
||||||
capture_output=True, text=True, timeout=2
|
|
||||||
)
|
|
||||||
return '[MUTED]' in r.stdout
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def toggle_mic_mute():
|
|
||||||
subprocess.run(
|
|
||||||
['wpctl', 'set-mute', '@DEFAULT_AUDIO_SOURCE@', 'toggle'],
|
|
||||||
capture_output=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def vol_icon(vol, muted):
|
|
||||||
if muted or vol == 0:
|
|
||||||
return ''
|
|
||||||
if vol < 50:
|
|
||||||
return ''
|
|
||||||
return ''
|
|
||||||
|
|
||||||
# ── Popup ─────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
class VolumePopup(Gtk.Window):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self._blocked = False
|
|
||||||
|
|
||||||
# ── Position : centré sous le module wireplumber ──────────────────────
|
|
||||||
# Wireplumber = 1er module de la pill droite (côté droit de l'écran).
|
|
||||||
# On centre le popup horizontalement sous ce module.
|
|
||||||
display = Gdk.Display.get_default()
|
|
||||||
monitor = display.get_primary_monitor() if display else None
|
|
||||||
if monitor:
|
|
||||||
screen_w = monitor.get_geometry().width
|
|
||||||
else:
|
|
||||||
screen_w = 1920 # fallback
|
|
||||||
|
|
||||||
# La pill droite a ~16px de marge depuis le bord droit.
|
|
||||||
# Le module wireplumber est le 1er élément : ~180px depuis le bord droit.
|
|
||||||
module_center = screen_w - 16 - 180
|
|
||||||
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 ──────────────────────────────────────────────────────
|
|
||||||
vol, muted = get_volume()
|
|
||||||
self._muted = muted
|
|
||||||
self._mic_muted = get_mic_muted()
|
|
||||||
sink = get_sink_name()
|
|
||||||
|
|
||||||
# ── Layout ────────────────────────────────────────────────────────────
|
|
||||||
container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
|
||||||
container.set_name('container')
|
|
||||||
self.add(container)
|
|
||||||
|
|
||||||
# Ligne sink (sortie active)
|
|
||||||
sink_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
|
||||||
sink_icon = Gtk.Label(label='')
|
|
||||||
sink_icon.set_name('vol-title')
|
|
||||||
sink_row.pack_start(sink_icon, False, False, 0)
|
|
||||||
self.sink_label = Gtk.Label(label=sink)
|
|
||||||
self.sink_label.set_name('sink-name')
|
|
||||||
self.sink_label.set_halign(Gtk.Align.START)
|
|
||||||
self.sink_label.set_ellipsize(3) # PANGO_ELLIPSIZE_END
|
|
||||||
sink_row.pack_start(self.sink_label, True, True, 0)
|
|
||||||
container.pack_start(sink_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 volume : icône + "Volume" + %
|
|
||||||
header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
|
|
||||||
self.icon = Gtk.Label(label=vol_icon(vol, muted))
|
|
||||||
self.icon.set_name('vol-icon')
|
|
||||||
header.pack_start(self.icon, False, False, 0)
|
|
||||||
|
|
||||||
title = Gtk.Label(label='Volume')
|
|
||||||
title.set_name('vol-title')
|
|
||||||
title.set_halign(Gtk.Align.START)
|
|
||||||
header.pack_start(title, True, True, 0)
|
|
||||||
|
|
||||||
self.pct = Gtk.Label(label=f'{vol}%')
|
|
||||||
self.pct.set_name('vol-pct')
|
|
||||||
self.pct.set_halign(Gtk.Align.END)
|
|
||||||
header.pack_end(self.pct, False, False, 0)
|
|
||||||
container.pack_start(header, False, False, 0)
|
|
||||||
|
|
||||||
# Slider
|
|
||||||
self.scale = Gtk.Scale.new_with_range(
|
|
||||||
Gtk.Orientation.HORIZONTAL, 0, 100, 5
|
|
||||||
)
|
|
||||||
self.scale.set_value(vol)
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Bouton mute
|
|
||||||
self.mute_btn = Gtk.Button(label=f' {"Remettre le son" if muted else "Muet"}')
|
|
||||||
self.mute_btn.set_name('mute-btn')
|
|
||||||
self.mute_btn.set_halign(Gtk.Align.CENTER)
|
|
||||||
if muted:
|
|
||||||
self.mute_btn.get_style_context().add_class('muted')
|
|
||||||
self.mute_btn.connect('clicked', self._on_mute)
|
|
||||||
container.pack_start(self.mute_btn, False, False, 0)
|
|
||||||
|
|
||||||
# ── Section micro ─────────────────────────────────────────────────────
|
|
||||||
sep2 = Gtk.Label(label='─' * 30)
|
|
||||||
sep2.set_name('separator')
|
|
||||||
container.pack_start(sep2, False, False, 4)
|
|
||||||
|
|
||||||
mic_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
|
|
||||||
mic_icon = Gtk.Label(label='')
|
|
||||||
mic_icon.set_name('vol-title')
|
|
||||||
mic_icon.set_markup('<span font_family="JetBrainsMono Nerd Font" size="large"></span>')
|
|
||||||
mic_row.pack_start(mic_icon, False, False, 0)
|
|
||||||
|
|
||||||
mic_title = Gtk.Label(label='Micro')
|
|
||||||
mic_title.set_name('vol-title')
|
|
||||||
mic_title.set_halign(Gtk.Align.START)
|
|
||||||
mic_row.pack_start(mic_title, True, True, 0)
|
|
||||||
|
|
||||||
mic_label = ' Coupé' if self._mic_muted else ' Actif'
|
|
||||||
self.mic_btn = Gtk.Button(label=mic_label)
|
|
||||||
self.mic_btn.set_name('mic-btn')
|
|
||||||
if self._mic_muted:
|
|
||||||
self.mic_btn.get_style_context().add_class('muted')
|
|
||||||
self.mic_btn.connect('clicked', self._on_mic_mute)
|
|
||||||
mic_row.pack_end(self.mic_btn, False, False, 0)
|
|
||||||
|
|
||||||
container.pack_start(mic_row, False, False, 0)
|
|
||||||
|
|
||||||
# ── Refresh périodique (détecte changement de sink/micro) ─────────────
|
|
||||||
GLib.timeout_add(2000, self._refresh_sink)
|
|
||||||
|
|
||||||
# ── 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
|
|
||||||
vol = int(scale.get_value())
|
|
||||||
self.pct.set_label(f'{vol}%')
|
|
||||||
self.icon.set_label(vol_icon(vol, self._muted))
|
|
||||||
set_volume(vol)
|
|
||||||
|
|
||||||
def _on_mute(self, btn):
|
|
||||||
toggle_mute()
|
|
||||||
_, self._muted = get_volume()
|
|
||||||
vol = int(self.scale.get_value())
|
|
||||||
self.icon.set_label(vol_icon(vol, self._muted))
|
|
||||||
if self._muted:
|
|
||||||
btn.get_style_context().add_class('muted')
|
|
||||||
btn.set_label(' Remettre le son')
|
|
||||||
else:
|
|
||||||
btn.get_style_context().remove_class('muted')
|
|
||||||
btn.set_label(' Muet')
|
|
||||||
|
|
||||||
def _refresh_sink(self):
|
|
||||||
"""Met à jour la sortie et l'état du micro si changement détecté."""
|
|
||||||
# Sortie audio
|
|
||||||
sink = get_sink_name()
|
|
||||||
if self.sink_label.get_label() != sink:
|
|
||||||
self.sink_label.set_label(sink)
|
|
||||||
vol, muted = get_volume()
|
|
||||||
self._blocked = True
|
|
||||||
self.scale.set_value(vol)
|
|
||||||
self._blocked = False
|
|
||||||
self.pct.set_label(f'{vol}%')
|
|
||||||
self._muted = muted
|
|
||||||
self.icon.set_label(vol_icon(vol, muted))
|
|
||||||
|
|
||||||
# Micro
|
|
||||||
mic_muted = get_mic_muted()
|
|
||||||
if mic_muted != self._mic_muted:
|
|
||||||
self._mic_muted = mic_muted
|
|
||||||
if mic_muted:
|
|
||||||
self.mic_btn.get_style_context().add_class('muted')
|
|
||||||
self.mic_btn.set_label(' Coupé')
|
|
||||||
else:
|
|
||||||
self.mic_btn.get_style_context().remove_class('muted')
|
|
||||||
self.mic_btn.set_label(' Actif')
|
|
||||||
|
|
||||||
return True # continuer le timer
|
|
||||||
|
|
||||||
def _on_mic_mute(self, btn):
|
|
||||||
toggle_mic_mute()
|
|
||||||
self._mic_muted = get_mic_muted()
|
|
||||||
if self._mic_muted:
|
|
||||||
btn.get_style_context().add_class('muted')
|
|
||||||
btn.set_label(' Coupé')
|
|
||||||
else:
|
|
||||||
btn.get_style_context().remove_class('muted')
|
|
||||||
btn.set_label(' Actif')
|
|
||||||
|
|
||||||
def _on_key(self, _widget, event):
|
|
||||||
if event.keyval == Gdk.KEY_Escape:
|
|
||||||
self.destroy()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
win = VolumePopup()
|
|
||||||
win.connect('destroy', Gtk.main_quit)
|
|
||||||
Gtk.main()
|
|
||||||
@@ -12,6 +12,14 @@ export INSTALL_LOG="$HOME/violet-chaton-install-$(date +%Y%m%d-%H%M%S).log"
|
|||||||
|
|
||||||
source "$SCRIPT_DIR/scripts/lib.sh"
|
source "$SCRIPT_DIR/scripts/lib.sh"
|
||||||
|
|
||||||
|
# ── Refus root ────────────────────────────────────────────────────────────────
|
||||||
|
if [ "$EUID" -eq 0 ]; then
|
||||||
|
echo -e "${RED}${BOLD} ERREUR : Ne pas lancer ce script en tant que root !${RESET}"
|
||||||
|
echo -e " Lance-le en tant qu'utilisateur normal : ${CYAN}bash install.sh${RESET}"
|
||||||
|
echo -e " ${MUTED}(sudo sera demandé automatiquement quand nécessaire)${RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Vérifications préalables ──────────────────────────────────────────────────
|
# ── Vérifications préalables ──────────────────────────────────────────────────
|
||||||
check_requirements() {
|
check_requirements() {
|
||||||
local ok=true
|
local ok=true
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ PACKAGES=(
|
|||||||
chafa
|
chafa
|
||||||
jq
|
jq
|
||||||
libgtk-3-bin
|
libgtk-3-bin
|
||||||
|
adw-gtk3
|
||||||
nemo
|
nemo
|
||||||
nemo-fileroller
|
nemo-fileroller
|
||||||
# fastfetch → installé via .deb GitHub (voir 02-packages-manual.sh)
|
# fastfetch → installé via .deb GitHub (voir 02-packages-manual.sh)
|
||||||
@@ -36,6 +37,8 @@ PACKAGES=(
|
|||||||
python3-gi
|
python3-gi
|
||||||
gir1.2-gtk-3.0
|
gir1.2-gtk-3.0
|
||||||
gir1.2-gtklayershell-0.1
|
gir1.2-gtklayershell-0.1
|
||||||
|
gir1.2-gdkpixbuf-2.0
|
||||||
|
gir1.2-pango-1.0
|
||||||
# ── Fun & utils ──────────────────────────────────────────────────────────
|
# ── Fun & utils ──────────────────────────────────────────────────────────
|
||||||
cmatrix
|
cmatrix
|
||||||
toilet
|
toilet
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ deploy_file() {
|
|||||||
local src="$1"
|
local src="$1"
|
||||||
local dst="$2"
|
local dst="$2"
|
||||||
ensure_dir "$(dirname "$dst")"
|
ensure_dir "$(dirname "$dst")"
|
||||||
if [ -f "$dst" ]; then
|
if [ -L "$dst" ]; then
|
||||||
|
# Symlink géré par COSMIC : sauvegarder la cible réelle puis supprimer le lien
|
||||||
|
local real; real=$(readlink -f "$dst")
|
||||||
|
local rel="${dst#"$HOME/"}"
|
||||||
|
ensure_dir "$BACKUP_DIR/$(dirname "$rel")"
|
||||||
|
cp "$real" "$BACKUP_DIR/$rel" 2>/dev/null
|
||||||
|
rm "$dst"
|
||||||
|
elif [ -f "$dst" ]; then
|
||||||
local rel="${dst#"$HOME/"}"
|
local rel="${dst#"$HOME/"}"
|
||||||
ensure_dir "$BACKUP_DIR/$(dirname "$rel")"
|
ensure_dir "$BACKUP_DIR/$(dirname "$rel")"
|
||||||
cp "$dst" "$BACKUP_DIR/$rel" 2>/dev/null
|
cp "$dst" "$BACKUP_DIR/$rel" 2>/dev/null
|
||||||
@@ -118,11 +125,27 @@ else
|
|||||||
fail "CosmicTerm"
|
fail "CosmicTerm"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── GTK3 — thème violet-chaton ─────────────────────────────────────────────
|
# ── GTK3 / GTK4 — thème violet-chaton ─────────────────────────────────────
|
||||||
section "GTK3 — thème violet-chaton"
|
section "GTK — thème violet-chaton"
|
||||||
|
|
||||||
|
step "Thème GTK3 (adw-gtk3-dark + couleurs violet-chaton)..."
|
||||||
ensure_dir "$HOME/.config/gtk-3.0"
|
ensure_dir "$HOME/.config/gtk-3.0"
|
||||||
deploy_file "$THEMES/violet-chaton-gtk.css" "$HOME/.config/gtk-3.0/gtk.css"
|
deploy_file "$THEMES/violet-chaton-gtk.css" "$HOME/.config/gtk-3.0/gtk.css"
|
||||||
|
|
||||||
|
step "Thème GTK4 / libadwaita (couleurs violet-chaton)..."
|
||||||
|
ensure_dir "$HOME/.config/gtk-4.0"
|
||||||
|
deploy_file "$THEMES/violet-chaton-gtk.css" "$HOME/.config/gtk-4.0/gtk.css"
|
||||||
|
|
||||||
|
step "Activation adw-gtk3-dark + dark mode (gsettings)..."
|
||||||
|
if has_cmd gsettings; then
|
||||||
|
gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3-dark' 2>/dev/null && \
|
||||||
|
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' 2>/dev/null && \
|
||||||
|
ok "gtk-theme=adw-gtk3-dark, color-scheme=prefer-dark" || \
|
||||||
|
warn "gsettings GTK échoué — thème à appliquer manuellement"
|
||||||
|
else
|
||||||
|
warn "gsettings non disponible — thème GTK à appliquer manuellement"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Nemo — gestionnaire de fichiers ────────────────────────────────────────
|
# ── Nemo — gestionnaire de fichiers ────────────────────────────────────────
|
||||||
section "Nemo — configuration et thème"
|
section "Nemo — configuration et thème"
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
"cosmic-light"
|
"Violet-chaton"
|
||||||
@@ -1,247 +1,122 @@
|
|||||||
/* ── violet-chaton GTK3 theme — Nemo & GTK apps ───────────────────────────────
|
/* ── violet-chaton GTK theme (adw-gtk3-dark compatible) ────────────────────
|
||||||
*
|
*
|
||||||
* Couleurs extraites du thème COSMIC violet-chaton :
|
* Contenu identique au dark.css généré par COSMIC pour le thème violet-chaton.
|
||||||
* bg #341C4A background.base
|
* adw-gtk3-dark et libadwaita lisent ces variables @define-color.
|
||||||
* surface #493161 background.component.base
|
* Sur le PC principal, COSMIC gère ce fichier via symlink — ce fichier
|
||||||
* hover #5B4671 background.component.hover
|
* sert de fallback lors de la première installation.
|
||||||
* accent #E79CFE accent.base
|
|
||||||
* text #FCFCF6 background.on
|
|
||||||
* muted #7F849C neutral_7
|
|
||||||
* border #5C496C background.divider
|
|
||||||
* sidebar #2B1540 (bg légèrement plus sombre)
|
|
||||||
* ─────────────────────────────────────────────────────────────────────────── */
|
* ─────────────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
@define-color theme_bg_color #341C4A;
|
@define-color window_bg_color rgba(52, 28, 74, 1.00);
|
||||||
@define-color theme_fg_color #FCFCF6;
|
@define-color window_fg_color rgba(252, 252, 246, 1.00);
|
||||||
@define-color theme_base_color #493161;
|
|
||||||
@define-color theme_selected_bg_color #E79CFE;
|
|
||||||
@define-color theme_selected_fg_color #341C4A;
|
|
||||||
@define-color theme_text_color #FCFCF6;
|
|
||||||
@define-color borders #5C496C;
|
|
||||||
|
|
||||||
/* ── Fenêtre principale ────────────────────────────────────────────────────── */
|
@define-color view_bg_color rgba(56, 35, 75, 1.00);
|
||||||
window, .background {
|
@define-color view_fg_color rgba(193, 193, 187, 1.00);
|
||||||
background-color: #341C4A;
|
|
||||||
color: #FCFCF6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Barre de titre / headerbar ───────────────────────────────────────────── */
|
@define-color headerbar_bg_color rgba(52, 28, 74, 1.00);
|
||||||
headerbar, .titlebar {
|
@define-color headerbar_fg_color rgba(252, 252, 246, 1.00);
|
||||||
background-color: #493161;
|
@define-color headerbar_border_color_color rgba(92, 73, 108, 1.00);
|
||||||
color: #FCFCF6;
|
@define-color headerbar_backdrop_color rgba(52, 28, 74, 1.00);
|
||||||
border-bottom: 1px solid #5C496C;
|
|
||||||
}
|
|
||||||
|
|
||||||
headerbar button, .titlebar button {
|
@define-color sidebar_bg_color rgba(56, 35, 75, 1.00);
|
||||||
background-color: transparent;
|
@define-color sidebar_fg_color rgba(193, 193, 187, 1.00);
|
||||||
color: #FCFCF6;
|
@define-color sidebar_shade_color rgba(0, 0, 0, 0.08);
|
||||||
border: none;
|
@define-color sidebar_backdrop_color rgba(72, 53, 89, 1.00);
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
headerbar button:hover, .titlebar button:hover {
|
@define-color secondary_sidebar_bg_color rgba(69, 71, 90, 1.00);
|
||||||
background-color: #5B4671;
|
@define-color secondary_sidebar_fg_color rgba(225, 225, 219, 1.00);
|
||||||
}
|
@define-color secondary_sidebar_shade_color rgba(0, 0, 0, 0.08);
|
||||||
|
@define-color secondary_sidebar_backdrop_color rgba(84, 86, 103, 1.00);
|
||||||
|
|
||||||
headerbar button:active, .titlebar button:active {
|
@define-color card_bg_color rgba(73, 49, 97, 1.00);
|
||||||
background-color: #E79CFE;
|
@define-color card_fg_color rgba(212, 212, 206, 1.00);
|
||||||
color: #341C4A;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Sidebar Nemo (paneau des emplacements) ───────────────────────────────── */
|
@define-color thumbnail_bg_color rgba(73, 49, 97, 1.00);
|
||||||
.sidebar, placessidebar {
|
@define-color thumbnail_fg_color rgba(212, 212, 206, 1.00);
|
||||||
background-color: #2B1540;
|
|
||||||
color: #FCFCF6;
|
|
||||||
border-right: 1px solid #5C496C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar row, placessidebar row {
|
@define-color dialog_bg_color rgba(56, 35, 75, 1.00);
|
||||||
border-radius: 6px;
|
@define-color dialog_fg_color rgba(193, 193, 187, 1.00);
|
||||||
padding: 2px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar row:hover, placessidebar row:hover {
|
@define-color popover_bg_color rgba(73, 49, 97, 1.00);
|
||||||
background-color: #5B4671;
|
@define-color popover_fg_color rgba(212, 212, 206, 1.00);
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar row:selected, placessidebar row:selected {
|
@define-color shade_color rgba(0, 0, 0, 0.32);
|
||||||
background-color: #E79CFE;
|
@define-color scrollbar_outline_color rgba(52, 28, 74, 0.50);
|
||||||
color: #341C4A;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar .sidebar-section-header, placessidebar .sidebar-section-header {
|
@define-color accent_color rgba(231, 156, 254, 1.00);
|
||||||
color: #7F849C;
|
@define-color accent_bg_color rgba(231, 156, 254, 1.00);
|
||||||
font-size: smaller;
|
@define-color accent_fg_color rgba(0, 0, 0, 1.00);
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Vue fichiers (icônes + liste) ───────────────────────────────────────── */
|
@define-color destructive_color rgba(243, 139, 168, 1.00);
|
||||||
.view, iconview, treeview {
|
@define-color destructive_bg_color rgba(243, 139, 168, 1.00);
|
||||||
background-color: #341C4A;
|
@define-color destructive_fg_color rgba(0, 0, 0, 1.00);
|
||||||
color: #FCFCF6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view:selected, iconview:selected,
|
@define-color warning_color rgba(249, 226, 175, 1.00);
|
||||||
treeview:selected, .view:focus:selected {
|
@define-color warning_bg_color rgba(249, 226, 175, 1.00);
|
||||||
background-color: #E79CFE;
|
@define-color warning_fg_color rgba(0, 0, 0, 1.00);
|
||||||
color: #341C4A;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* En-têtes de colonnes (vue liste) */
|
@define-color success_color rgba(166, 227, 161, 1.00);
|
||||||
treeview header button {
|
@define-color success_bg_color rgba(166, 227, 161, 1.00);
|
||||||
background-color: #493161;
|
@define-color success_fg_color rgba(0, 0, 0, 1.00);
|
||||||
color: #FCFCF6;
|
|
||||||
border: none;
|
|
||||||
border-right: 1px solid #5C496C;
|
|
||||||
border-bottom: 1px solid #5C496C;
|
|
||||||
}
|
|
||||||
|
|
||||||
treeview header button:hover {
|
@define-color accent_color rgba(231, 156, 254, 1.00);
|
||||||
background-color: #5B4671;
|
@define-color accent_bg_color rgba(231, 156, 254, 1.00);
|
||||||
}
|
@define-color accent_fg_color rgba(0, 0, 0, 1.00);
|
||||||
|
|
||||||
/* ── Barre d'outils / pathbar ─────────────────────────────────────────────── */
|
@define-color error_color rgba(243, 139, 168, 1.00);
|
||||||
toolbar, .path-bar {
|
@define-color error_bg_color rgba(243, 139, 168, 1.00);
|
||||||
background-color: #493161;
|
@define-color error_fg_color rgba(0, 0, 0, 1.00);
|
||||||
border-bottom: 1px solid #5C496C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-bar button {
|
@define-color blue_1 rgba(151, 195, 255, 1.00);
|
||||||
background-color: transparent;
|
@define-color blue_2 rgba(144, 187, 255, 1.00);
|
||||||
color: #FCFCF6;
|
@define-color blue_3 rgba(137, 180, 250, 1.00);
|
||||||
border: none;
|
@define-color blue_4 rgba(114, 156, 224, 1.00);
|
||||||
border-radius: 6px;
|
@define-color blue_5 rgba(91, 132, 199, 1.00);
|
||||||
}
|
|
||||||
|
|
||||||
.path-bar button:hover {
|
@define-color green_1 rgba(175, 236, 170, 1.00);
|
||||||
background-color: #5B4671;
|
@define-color green_2 rgba(171, 232, 165, 1.00);
|
||||||
}
|
@define-color green_3 rgba(166, 227, 161, 1.00);
|
||||||
|
@define-color green_4 rgba(139, 199, 134, 1.00);
|
||||||
|
@define-color green_5 rgba(113, 171, 108, 1.00);
|
||||||
|
|
||||||
.path-bar button:checked {
|
@define-color yellow_1 rgba(254, 231, 180, 1.00);
|
||||||
background-color: #5B4671;
|
@define-color yellow_2 rgba(252, 229, 178, 1.00);
|
||||||
color: #E79CFE;
|
@define-color yellow_3 rgba(249, 226, 175, 1.00);
|
||||||
}
|
@define-color yellow_4 rgba(219, 196, 146, 1.00);
|
||||||
|
@define-color yellow_5 rgba(189, 167, 118, 1.00);
|
||||||
|
|
||||||
/* ── Barre de recherche / entrée texte ────────────────────────────────────── */
|
@define-color red_1 rgba(255, 154, 183, 1.00);
|
||||||
entry {
|
@define-color red_2 rgba(252, 147, 176, 1.00);
|
||||||
background-color: #5B4671;
|
@define-color red_3 rgba(243, 139, 168, 1.00);
|
||||||
color: #FCFCF6;
|
@define-color red_4 rgba(217, 116, 145, 1.00);
|
||||||
border: 1px solid #5C496C;
|
@define-color red_5 rgba(191, 93, 122, 1.00);
|
||||||
border-radius: 6px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry:focus {
|
@define-color orange_1 rgba(255, 190, 146, 1.00);
|
||||||
border-color: #E79CFE;
|
@define-color orange_2 rgba(255, 185, 140, 1.00);
|
||||||
box-shadow: 0 0 0 2px alpha(#E79CFE, 0.3);
|
@define-color orange_3 rgba(250, 179, 135, 1.00);
|
||||||
}
|
@define-color orange_4 rgba(222, 153, 110, 1.00);
|
||||||
|
@define-color orange_5 rgba(195, 128, 85, 1.00);
|
||||||
|
|
||||||
entry placeholder {
|
@define-color purple_1 rgba(192, 202, 255, 1.00);
|
||||||
color: #7F849C;
|
@define-color purple_2 rgba(186, 196, 255, 1.00);
|
||||||
}
|
@define-color purple_3 rgba(180, 190, 254, 1.00);
|
||||||
|
@define-color purple_4 rgba(155, 164, 226, 1.00);
|
||||||
|
@define-color purple_5 rgba(130, 139, 200, 1.00);
|
||||||
|
@define-color light_0 rgba(0, 0, 0, 1.00);
|
||||||
|
@define-color light_1 rgba(3, 3, 16, 1.00);
|
||||||
|
@define-color light_2 rgba(24, 25, 43, 1.00);
|
||||||
|
@define-color light_3 rgba(50, 53, 72, 1.00);
|
||||||
|
@define-color light_4 rgba(79, 82, 103, 1.00);
|
||||||
|
@define-color dark_0 rgba(110, 114, 135, 1.00);
|
||||||
|
@define-color dark_1 rgba(143, 147, 169, 1.00);
|
||||||
|
@define-color dark_2 rgba(177, 181, 205, 1.00);
|
||||||
|
@define-color dark_3 rgba(213, 217, 241, 1.00);
|
||||||
|
@define-color dark_4 rgba(255, 255, 255, 1.00);
|
||||||
|
|
||||||
/* ── Scrollbar ────────────────────────────────────────────────────────────── */
|
/* ── Variables GTK3 classiques (compat apps legacy) ─────────────────────── */
|
||||||
scrollbar {
|
@define-color theme_bg_color rgba(52, 28, 74, 1.00);
|
||||||
background-color: transparent;
|
@define-color theme_fg_color rgba(252, 252, 246, 1.00);
|
||||||
}
|
@define-color theme_base_color rgba(73, 49, 97, 1.00);
|
||||||
|
@define-color theme_selected_bg_color rgba(231, 156, 254, 1.00);
|
||||||
scrollbar slider {
|
@define-color theme_selected_fg_color rgba(52, 28, 74, 1.00);
|
||||||
background-color: #5C496C;
|
@define-color theme_text_color rgba(252, 252, 246, 1.00);
|
||||||
border-radius: 10px;
|
@define-color borders rgba(92, 73, 108, 1.00);
|
||||||
min-width: 6px;
|
|
||||||
min-height: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollbar slider:hover {
|
|
||||||
background-color: #E79CFE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Menu contextuel ──────────────────────────────────────────────────────── */
|
|
||||||
menu, .context-menu, .popup {
|
|
||||||
background-color: #493161;
|
|
||||||
color: #FCFCF6;
|
|
||||||
border: 1px solid #5C496C;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menuitem {
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menuitem:hover {
|
|
||||||
background-color: #E79CFE;
|
|
||||||
color: #341C4A;
|
|
||||||
}
|
|
||||||
|
|
||||||
menuitem accelerator {
|
|
||||||
color: #7F849C;
|
|
||||||
}
|
|
||||||
|
|
||||||
separator, menuitem separator {
|
|
||||||
background-color: #5C496C;
|
|
||||||
min-height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Barre de statut ──────────────────────────────────────────────────────── */
|
|
||||||
.statusbar, statusbar {
|
|
||||||
background-color: #493161;
|
|
||||||
color: #7F849C;
|
|
||||||
border-top: 1px solid #5C496C;
|
|
||||||
font-size: smaller;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Boutons génériques ───────────────────────────────────────────────────── */
|
|
||||||
button {
|
|
||||||
background-color: #5B4671;
|
|
||||||
color: #FCFCF6;
|
|
||||||
border: 1px solid #5C496C;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 4px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #E79CFE;
|
|
||||||
color: #341C4A;
|
|
||||||
border-color: #E79CFE;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.suggested-action {
|
|
||||||
background-color: #E79CFE;
|
|
||||||
color: #341C4A;
|
|
||||||
border-color: #E79CFE;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.destructive-action {
|
|
||||||
background-color: #F38BA8;
|
|
||||||
color: #341C4A;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Notebooks / onglets ─────────────────────────────────────────────────── */
|
|
||||||
notebook tab {
|
|
||||||
background-color: #493161;
|
|
||||||
color: #7F849C;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
padding: 4px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
notebook tab:checked {
|
|
||||||
background-color: #5B4671;
|
|
||||||
color: #FCFCF6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Popover ─────────────────────────────────────────────────────────────── */
|
|
||||||
popover {
|
|
||||||
background-color: #493161;
|
|
||||||
color: #FCFCF6;
|
|
||||||
border: 1px solid #5C496C;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Tooltip ─────────────────────────────────────────────────────────────── */
|
|
||||||
tooltip {
|
|
||||||
background-color: #5B4671;
|
|
||||||
color: #FCFCF6;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -102,9 +102,11 @@ window#waybar {
|
|||||||
|
|
||||||
#cpu,
|
#cpu,
|
||||||
#temperature,
|
#temperature,
|
||||||
|
#custom-cpu-temp,
|
||||||
#custom-gpu,
|
#custom-gpu,
|
||||||
#memory,
|
#memory,
|
||||||
#disk,
|
#disk,
|
||||||
|
#custom-disks,
|
||||||
#custom-network,
|
#custom-network,
|
||||||
#clock,
|
#clock,
|
||||||
#custom-date,
|
#custom-date,
|
||||||
@@ -140,7 +142,8 @@ window#waybar {
|
|||||||
|
|
||||||
/* ── Température ──────────────────────────────────────────────────────────── */
|
/* ── Température ──────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
#temperature {
|
#temperature,
|
||||||
|
#custom-cpu-temp {
|
||||||
color: rgba(139, 233, 253, 0.60);
|
color: rgba(139, 233, 253, 0.60);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@@ -148,12 +151,17 @@ window#waybar {
|
|||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#temperature.critical {
|
#temperature.critical,
|
||||||
|
#custom-cpu-temp.critical {
|
||||||
color: #f38ba8;
|
color: #f38ba8;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
animation: pulse-critical 0.8s linear infinite;
|
animation: pulse-critical 0.8s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#custom-cpu-temp.warning {
|
||||||
|
color: #f9e2af;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── GPU ──────────────────────────────────────────────────────────────────── */
|
/* ── GPU ──────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
#custom-gpu {
|
#custom-gpu {
|
||||||
@@ -186,7 +194,8 @@ window#waybar {
|
|||||||
|
|
||||||
/* ── Disque ───────────────────────────────────────────────────────────────── */
|
/* ── Disque ───────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
#disk {
|
#disk,
|
||||||
|
#custom-disks {
|
||||||
color: rgba(255, 121, 198, 0.70);
|
color: rgba(255, 121, 198, 0.70);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@@ -405,6 +414,8 @@ tooltip label {
|
|||||||
#cpu:hover,
|
#cpu:hover,
|
||||||
#memory:hover,
|
#memory:hover,
|
||||||
#disk:hover,
|
#disk:hover,
|
||||||
|
#custom-disks:hover,
|
||||||
|
#custom-cpu-temp:hover,
|
||||||
#custom-network:hover,
|
#custom-network:hover,
|
||||||
#wireplumber:hover,
|
#wireplumber:hover,
|
||||||
#backlight:hover,
|
#backlight:hover,
|
||||||
|
|||||||
436
README.md
436
README.md
@@ -1,416 +1,86 @@
|
|||||||
# violet-chaton — setup automatique
|
# violet-chaton
|
||||||
|
|
||||||
Environnement terminal complet aux couleurs violet-chaton, pensé pour Pop!_OS / Ubuntu avec COSMIC Desktop.
|
> Rice Pop!_OS complet aux couleurs violet-chaton — compatible **COSMIC** et **Hyprland**
|
||||||
(Bonus thème Vesktop / Discord)
|
|
||||||
|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Démarrage rapide
|
## Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash install.sh
|
git clone https://git.tetardtek.com/Tetardtek/dotfiles-violet-chaton.git
|
||||||
|
cd dotfiles-violet-chaton
|
||||||
|
bash INSTALL/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Un menu s'affiche. Choisis **1** pour une installation complète.
|
Choisir **1** (complète) ou **4** (configs uniquement si les outils sont déjà là).
|
||||||
|
Prérequis : `sudo apt install -y curl git unzip`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Prérequis
|
## Ce que tu obtiens
|
||||||
|
|
||||||
- **Distribution :** Debian, Ubuntu, Pop!_OS (ou dérivé apt)
|
**Shell** — zsh · starship · atuin · zinit · autosuggestions · syntax-highlighting
|
||||||
- **Droits :** compte avec `sudo`
|
**CLI** — eza · bat · fd · fzf · zoxide · ripgrep · lazygit · delta · yazi · btop
|
||||||
- **Connexion internet** (téléchargement des binaires)
|
**Terminal** — fastfetch avec sprite Pokémon via chafa · LS_COLORS patché depuis catppuccin-mocha via vivid · cava · pipes.sh
|
||||||
- **Outils de base :**
|
**Desktop** — Waybar 3-pills glassmorphism · Wofi · Rofi · wob (OSD volume/luminosité)
|
||||||
|
**Polices** — JetBrainsMono NL + 0xProto Nerd Fonts · icônes candy-icons
|
||||||
```bash
|
**Apps** — Vivaldi (thème injecté auto) · Vesktop/Discord · Nemo · GTK3/GTK4
|
||||||
sudo apt install -y curl git unzip
|
|
||||||
```
|
|
||||||
|
|
||||||
> Pour COSMIC Desktop : Pop!_OS 24.04 ou supérieur.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Ce que fait le script — étape par étape
|
## COSMIC
|
||||||
|
|
||||||
### Étape 1 — Paquets apt (`01-packages-apt.sh`)
|
Déploiement automatique complet :
|
||||||
|
|
||||||
Installe les outils via le gestionnaire de paquets système :
|
- Thème COSMIC Dark/Light — palette violet-chaton
|
||||||
|
- CosmicTerm — police, couleurs, profil
|
||||||
| Outil | Rôle |
|
- CosmicTk — polices UI + icônes candy-icons
|
||||||
|------------------|------|
|
- `cosmic-osd` désactivé, remplacé par `wob`
|
||||||
| `zsh` | Shell principal (remplace bash) |
|
- Waybar + autostart configurés
|
||||||
| `eza` | Remplacement de `ls` avec icônes et couleurs |
|
|
||||||
| `bat` | Remplacement de `cat` avec coloration syntaxique |
|
|
||||||
| `fd-find` | Remplacement de `find`, plus rapide et intuitif |
|
|
||||||
| `fzf` | Fuzzy finder — recherche floue de fichiers et dossiers |
|
|
||||||
| `zoxide` | Remplacement de `cd` avec mémoire des dossiers fréquents |
|
|
||||||
| `git-delta` | Pager git avec diff coloré côte à côte |
|
|
||||||
| `vivid` | Générateur de LS_COLORS |
|
|
||||||
| `ripgrep` | Remplacement de `grep`, très rapide |
|
|
||||||
| `ncdu` | Analyse d'espace disque en TUI |
|
|
||||||
| `thefuck` | Correction automatique de la dernière commande ratée |
|
|
||||||
| `lolcat` | Arc-en-ciel sur n'importe quel output |
|
|
||||||
| `cbonsai` | Bonsaï ASCII animé |
|
|
||||||
| `chafa` | Affichage d'images dans le terminal (logo fastfetch) |
|
|
||||||
| `cava` | Visualiseur audio animé |
|
|
||||||
| `btop` | Moniteur système en TUI |
|
|
||||||
| `nemo` | Explorateur de fichiers GUI |
|
|
||||||
| `jq` | Processeur JSON en ligne de commande |
|
|
||||||
| `vivaldi-stable` | Navigateur — dépôt officiel ajouté automatiquement |
|
|
||||||
| `gh` | CLI GitHub (auth, PR, issues) — dépôt officiel ajouté automatiquement |
|
|
||||||
| `cmatrix` | Pluie de caractères style Matrix |
|
|
||||||
| `toilet` | Texte en gros ASCII art coloré (bannières dans le terminal) |
|
|
||||||
| `w3m` | Navigateur web en mode texte dans le terminal |
|
|
||||||
| `jp2a` | Conversion d'images JPEG/PNG en ASCII art |
|
|
||||||
| `qalc` | Calculatrice CLI — unités, conversions, expressions complexes |
|
|
||||||
| `waybar` | Barre de statut Wayland 3-pills glassmorphism |
|
|
||||||
| `wob` | Overlay volume/luminosité animé |
|
|
||||||
| `wofi` | Launcher d'applications et menu power |
|
|
||||||
| `brightnessctl` | Contrôle de la luminosité rétroéclairage |
|
|
||||||
| `playerctl` | Contrôle MPRIS (lecture/pause, titre en cours) |
|
|
||||||
| `wireplumber` | Gestionnaire audio PipeWire (`wpctl`) |
|
|
||||||
| `python3-gi` | Bindings Python GTK3 (popups volume/luminosité) |
|
|
||||||
| `gir1.2-gtk-3.0` | Introspection GTK3 pour Python |
|
|
||||||
| `gir1.2-gtklayershell-0.1` | Layer-shell Wayland pour popups GTK flottants |
|
|
||||||
|
|
||||||
Définit aussi **zsh comme shell par défaut** via `chsh`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Étape 2 — Binaires manuels (`02-packages-manual.sh`)
|
## Hyprland
|
||||||
|
|
||||||
Télécharge les versions les plus récentes depuis GitHub et les installe dans `~/.local/bin/` :
|
Les configs shell, waybar, wofi, rofi, kitty et zsh fonctionnent **tels quels** sous Hyprland.
|
||||||
|
Ce qui reste à câbler côté Hyprland :
|
||||||
|
|
||||||
| Outil | Rôle |
|
```
|
||||||
|--------------|------|
|
hyprland.conf — keybinds, moniteurs, règles fenêtres
|
||||||
| `lazygit` | Interface git complète en TUI (`lg`) |
|
hyprpaper / swww — fond d'écran
|
||||||
| `yazi` | Gestionnaire de fichiers en TUI |
|
hyprlock — écran de verrouillage
|
||||||
| `glow` | Rendu Markdown dans le terminal |
|
mako — notifications
|
||||||
| `tldr` | Man pages simplifiées avec exemples (tealdeer) |
|
```
|
||||||
| `navi` | Cheatsheets interactives |
|
|
||||||
| `pipes.sh` | Animation de tuyaux dans le terminal |
|
|
||||||
| `fastfetch` | Infos système au démarrage du terminal — `.deb` depuis GitHub |
|
|
||||||
| `uv` / `uvx` | Gestionnaire de paquets Python ultra-rapide — script officiel astral.sh |
|
|
||||||
|
|
||||||
Installe également :
|
> Ces configs ne sont pas (encore) dans le repo — les contributions sont les bienvenues.
|
||||||
- **starship** et **atuin** via leurs scripts officiels
|
|
||||||
- **zinit** (gestionnaire de plugins zsh) via git clone
|
|
||||||
- **Nerd Fonts** — JetBrainsMono NL et 0xProto, vers `~/.local/share/fonts/NerdFonts/`
|
|
||||||
- **candy-icons** — thème d'icônes, vers `~/.local/share/icons/candy-icons-master/`
|
|
||||||
|
|
||||||
> **nomachine** — à installer manuellement depuis [nomachine.com](https://www.nomachine.com/download) (comme VSCode et Vesktop)
|
|
||||||
|
|
||||||
Met à jour le cache des pages tldr et le cache de polices (`fc-cache`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Étape 3 — Déploiement des configs et thèmes (`03-deploy-configs.sh`)
|
## Palette
|
||||||
|
|
||||||
Copie les fichiers de config et thèmes aux bons emplacements.
|
| | Hex | Rôle |
|
||||||
|
|-|-----|------|
|
||||||
Avant chaque déploiement, les fichiers existants sont sauvegardés dans :
|
|  | `#261537` | Background |
|
||||||
```
|
|  | `#341c4a` | BG medium |
|
||||||
~/.config/violet-chaton-backups/YYYYMMDD-HHMMSS/
|
|  | `#3d2454` | BG high · sélection |
|
||||||
```
|
|  | `#ff79c6` | Pink — accents · bordures |
|
||||||
Le chemin exact est affiché à la fin du script si des fichiers ont été sauvegardés.
|
|  | `#e79cfe` | Purple — accents secondaires |
|
||||||
|
|  | `#8be9fd` | Cyan — commandes · highlights |
|
||||||
**Configs shell :**
|
|  | `#f8f8f2` | Text |
|
||||||
- `~/.zshrc` — configuration zsh complète
|
|
||||||
- `~/.bashrc` — configuration bash minimale (PATH + `exec zsh`)
|
|
||||||
- `~/.gitconfig` — git avec delta comme pager
|
|
||||||
|
|
||||||
> `user.name` et `user.email` présents dans le gitconfig existant sont **automatiquement préservés** après le déploiement.
|
|
||||||
|
|
||||||
**Configs outils :**
|
|
||||||
- `~/.config/starship.toml` — prompt 2 lignes violet-chaton
|
|
||||||
- `~/.config/bat/config` — thème violet-chaton, style header
|
|
||||||
- `~/.config/btop/btop.conf` — moniteur avec thème violet-chaton
|
|
||||||
- `~/.config/fastfetch/config.jsonc` — modules système + logo chafa
|
|
||||||
- `~/.config/atuin/config.toml` — historique fuzzy, colonnes, thème
|
|
||||||
- `~/.config/lazygit/config.yml` — couleurs violet-chaton + delta
|
|
||||||
- `~/.config/yazi/yazi.toml` — config gestionnaire de fichiers
|
|
||||||
- `~/.config/glow/glow.yml` — style markdown dark
|
|
||||||
|
|
||||||
**Thèmes CLI :**
|
|
||||||
- `~/.config/bat/themes/violet-chaton.tmTheme`
|
|
||||||
- `~/.config/btop/themes/violet-chaton.theme`
|
|
||||||
- `~/.config/atuin/themes/violet-chaton.toml`
|
|
||||||
- `~/.config/cava/config`
|
|
||||||
- `~/.config/yazi/theme.toml`
|
|
||||||
- `~/.config/vesktop/themes/violet-chaton.theme.css` — Vesktop natif (toujours déployé)
|
|
||||||
- `~/.var/app/dev.vencord.Vesktop/config/vesktop/themes/` — Vesktop Flatpak (si installé)
|
|
||||||
|
|
||||||
**GTK3 et Nemo :**
|
|
||||||
- `~/.config/gtk-3.0/gtk.css` — thème GTK3 violet-chaton (Nemo et applications GTK)
|
|
||||||
- Nemo défini comme gestionnaire de fichiers par défaut (`xdg-mime`)
|
|
||||||
- Préférences Nemo appliquées via `gsettings` : vue icônes, miniatures, zoom standard
|
|
||||||
- Thème d'icônes **candy-icons** activé via `gsettings`
|
|
||||||
|
|
||||||
**COSMIC Desktop (entièrement automatique) :**
|
|
||||||
- `~/.config/cosmic/com.system76.CosmicTheme.Dark/v1/` — palette violet-chaton complète
|
|
||||||
- `~/.config/cosmic/com.system76.CosmicTheme.Light/v1/` — palette violet-chaton (mode clair)
|
|
||||||
- `~/.config/cosmic/com.system76.CosmicTheme.Mode/v1/is_dark` — mode sombre activé
|
|
||||||
- `~/.config/cosmic/com.system76.CosmicTerm/v1/` — police JetBrains Mono, couleurs, profil
|
|
||||||
- `~/.config/cosmic/com.system76.CosmicTk/v1/` — icônes candy-icons, polices UI 0xProto
|
|
||||||
|
|
||||||
**Waybar — island floating 3 pills :**
|
|
||||||
- `~/.config/waybar/config` — modules left/center/right
|
|
||||||
- `~/.config/waybar/style.css` — glassmorphism, bordures roses, animations
|
|
||||||
- `~/.config/waybar/cava-waybar.cfg` — config CAVA dédié waybar
|
|
||||||
- `~/.config/waybar/scripts/` — tous les scripts (gpu, network, power-profile, cava, wob, popups GTK)
|
|
||||||
- `~/.config/autostart/waybar.desktop` — démarrage automatique avec COSMIC
|
|
||||||
- `~/.config/autostart/wob.desktop` — démarrage automatique de l'overlay wob
|
|
||||||
|
|
||||||
**Wofi — launcher + menu power :**
|
|
||||||
- `~/.config/wofi/config` — configuration wofi
|
|
||||||
- `~/.config/wofi/style.css` — thème violet-chaton (launcher apps)
|
|
||||||
- `~/.config/wofi/power-style.css` — thème violet-chaton (menu power)
|
|
||||||
|
|
||||||
**wob — overlay volume/luminosité :**
|
|
||||||
- `~/.config/wob.ini` — couleurs violet-chaton, position bas de l'écran
|
|
||||||
|
|
||||||
**Système (avec sudo) :**
|
|
||||||
- `/etc/sudoers.d/waybar-power-profile` — changement de profil énergie sans mot de passe
|
|
||||||
- `/etc/udev/rules.d/90-platform-profile.rules` — permissions groupe `video` sur platform_profile
|
|
||||||
|
|
||||||
**Vivaldi (avec pause de sécurité) :**
|
|
||||||
- Si Vivaldi n'a pas encore été lancé, le script s'arrête et demande de le démarrer une fois
|
|
||||||
- Le thème **Rice Violet-Chaton** est ensuite injecté directement dans `~/.config/vivaldi/Default/Preferences`
|
|
||||||
|
|
||||||
Reconstruit également le **cache bat** pour activer le thème de coloration syntaxique.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Étapes manuelles après installation
|
## Raccourcis
|
||||||
|
|
||||||
### zinit — premier démarrage
|
| Touche | Action |
|
||||||
|
|--------|--------|
|
||||||
|
| `Ctrl+R` | Historique atuin |
|
||||||
|
| `Ctrl+G` | Fichier (fzf) |
|
||||||
|
| `Ctrl+F` | Dossier (fzf) |
|
||||||
|
| `Ctrl+Space` | Accepter suggestion |
|
||||||
|
|
||||||
Au premier lancement de zsh, zinit télécharge automatiquement les plugins :
|
## Alias clés
|
||||||
- `zsh-autosuggestions` — suggestions en gris au fil de la frappe
|
|
||||||
- `zsh-syntax-highlighting` — coloration des commandes en temps réel
|
|
||||||
- `zsh-completions` — complétion étendue
|
|
||||||
|
|
||||||
Cela peut prendre quelques secondes lors du tout premier démarrage.
|
`ls` → eza · `cat` → bat · `fd` → fdfind · `man` → tldr · `lg` → lazygit · `disk` → ncdu · `fetch` → fastfetch+chafa
|
||||||
|
|
||||||
### Polices — vérification
|
|
||||||
|
|
||||||
Si les icônes ne s'affichent pas correctement dans le terminal, forcer la reconstruction du cache de polices :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
fc-cache -f -v
|
|
||||||
```
|
|
||||||
|
|
||||||
Puis sélectionner **JetBrainsMono NL Nerd Font** dans les préférences du terminal.
|
|
||||||
|
|
||||||
### atuin — synchronisation (optionnel)
|
|
||||||
|
|
||||||
atuin peut synchroniser l'historique entre machines. Pour activer la sync :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
atuin register # créer un compte
|
|
||||||
atuin sync # synchroniser
|
|
||||||
```
|
|
||||||
|
|
||||||
Sans compte, atuin fonctionne en local uniquement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Log d'installation
|
|
||||||
|
|
||||||
Chaque installation génère un fichier log horodaté :
|
|
||||||
|
|
||||||
```
|
|
||||||
~/violet-chaton-install-YYYYMMDD-HHMMSS.log
|
|
||||||
```
|
|
||||||
|
|
||||||
En cas d'erreur, consulter ce fichier pour identifier l'étape qui a échoué.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Icônes ne s'affichent pas
|
|
||||||
|
|
||||||
- Vérifier que la police **JetBrainsMono NL Nerd Font** est sélectionnée dans le terminal
|
|
||||||
- Relancer `fc-cache -f` puis rouvrir le terminal
|
|
||||||
|
|
||||||
### zinit ne se lance pas
|
|
||||||
|
|
||||||
- Vérifier que `~/.local/share/zinit/zinit.git/zinit.zsh` existe
|
|
||||||
- Relancer le script d'installation étape 3
|
|
||||||
|
|
||||||
### Injection Vivaldi échoue
|
|
||||||
|
|
||||||
- Lancer Vivaldi une première fois, le fermer complètement, puis relancer `bash install.sh` → option 4
|
|
||||||
|
|
||||||
### Thème bat ne s'applique pas
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bat cache --build
|
|
||||||
```
|
|
||||||
|
|
||||||
### candy-icons ne s'affiche pas dans Nemo
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gtk-update-icon-cache ~/.local/share/icons/candy-icons-master
|
|
||||||
gsettings set org.gnome.desktop.interface icon-theme 'candy-icons-master'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Waybar ne démarre pas
|
|
||||||
|
|
||||||
```bash
|
|
||||||
waybar & # tester manuellement, lire les erreurs
|
|
||||||
pkill -SIGUSR2 waybar # recharger la config à chaud
|
|
||||||
```
|
|
||||||
|
|
||||||
### Popups volume/luminosité ne s'ouvrent pas
|
|
||||||
|
|
||||||
Vérifier que les dépendances Python sont installées :
|
|
||||||
```bash
|
|
||||||
python3 -c "import gi; gi.require_version('GtkLayerShell', '0.1'); print('OK')"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Profil énergie ne change pas au clic
|
|
||||||
|
|
||||||
Vérifier que la règle sudoers et les permissions udev sont en place :
|
|
||||||
```bash
|
|
||||||
ls -la /sys/firmware/acpi/platform_profile # doit être g+w groupe video
|
|
||||||
cat /etc/sudoers.d/waybar-power-profile
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Raccourcis configurés
|
|
||||||
|
|
||||||
| Raccourci | Action |
|
|
||||||
|----------------|--------|
|
|
||||||
| `Ctrl+R` | Historique atuin (fuzzy search) |
|
|
||||||
| `Ctrl+G` | Rechercher un fichier (fzf) |
|
|
||||||
| `Ctrl+F` | Naviguer vers un dossier (fzf) |
|
|
||||||
| `Ctrl+Space` | Accepter la suggestion autosuggestions |
|
|
||||||
| `→` | Accepter la suggestion mot par mot |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Alias configurés
|
|
||||||
|
|
||||||
| Alias | Commande réelle |
|
|
||||||
|----------|-----------------|
|
|
||||||
| `ls` | `eza --icons --git --group-directories-first` |
|
|
||||||
| `ll` | `eza -l --icons --git` |
|
|
||||||
| `lt` | `eza --tree --icons` |
|
|
||||||
| `cat` | `batcat --paging=never` |
|
|
||||||
| `bat` | `batcat` |
|
|
||||||
| `fd` | `fdfind` |
|
|
||||||
| `man` | `tldr` |
|
|
||||||
| `lg` | `lazygit` |
|
|
||||||
| `rg` | `rg --color=always` |
|
|
||||||
| `disk` | `ncdu` |
|
|
||||||
| `fetch` | `fastfetch` avec logo chafa |
|
|
||||||
| `pipes` | `pipes.sh` |
|
|
||||||
| `cd` | `zoxide` (avec mémoire) |
|
|
||||||
| `fuck` | correction auto thefuck |
|
|
||||||
| `grep` | `grep --color=auto` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Structure du dossier INSTALL/
|
|
||||||
|
|
||||||
```
|
|
||||||
INSTALL/
|
|
||||||
├── install.sh script principal — menu interactif
|
|
||||||
├── scripts/
|
|
||||||
│ ├── lib.sh couleurs et fonctions partagées
|
|
||||||
│ ├── 01-packages-apt.sh installation apt + Vivaldi
|
|
||||||
│ ├── 02-packages-manual.sh binaires GitHub + Nerd Fonts + candy-icons
|
|
||||||
│ └── 03-deploy-configs.sh configs, thèmes, COSMIC, Vivaldi
|
|
||||||
├── configs/ copies des fichiers de configuration
|
|
||||||
│ ├── zshrc
|
|
||||||
│ ├── bashrc
|
|
||||||
│ ├── gitconfig
|
|
||||||
│ ├── starship.toml
|
|
||||||
│ ├── bat.conf
|
|
||||||
│ ├── btop.conf
|
|
||||||
│ ├── fastfetch.jsonc
|
|
||||||
│ ├── atuin.toml
|
|
||||||
│ ├── lazygit.yml
|
|
||||||
│ ├── yazi.toml
|
|
||||||
│ ├── glow.yml
|
|
||||||
│ ├── autostart/
|
|
||||||
│ │ ├── waybar.desktop démarrage automatique waybar
|
|
||||||
│ │ └── wob.desktop démarrage automatique wob
|
|
||||||
│ ├── waybar/
|
|
||||||
│ │ ├── config modules 3-pills
|
|
||||||
│ │ ├── cava-waybar.cfg config CAVA dédiée waybar
|
|
||||||
│ │ └── scripts/ gpu, network, power-profile, cava, wob, popups GTK
|
|
||||||
│ ├── wofi/
|
|
||||||
│ │ └── config config wofi
|
|
||||||
│ └── wob/
|
|
||||||
│ └── wob.ini overlay volume/luminosité
|
|
||||||
├── assets/
|
|
||||||
│ └── violet-chaton-logo.png logo fastfetch (1024×1024)
|
|
||||||
└── themes/ tous les fichiers de thème violet-chaton
|
|
||||||
├── violet-chaton-bat.tmTheme
|
|
||||||
├── violet-chaton-btop.theme
|
|
||||||
├── violet-chaton-atuin.toml
|
|
||||||
├── violet-chaton-cava.conf
|
|
||||||
├── violet-chaton-yazi.toml
|
|
||||||
├── violet-chaton-gtk.css thème GTK3 (Nemo + applications GTK)
|
|
||||||
├── violet-chaton-ls-colors.sh
|
|
||||||
├── violet-chaton-vivaldi.json thème Rice Violet-Chaton pour Vivaldi
|
|
||||||
├── violet-chaton.theme.css thème Discord/Vesktop compilé
|
|
||||||
├── violet-chaton-waybar.css CSS 3-pills glassmorphism
|
|
||||||
├── violet-chaton-wofi.css thème wofi launcher
|
|
||||||
├── violet-chaton-wofi-power.css thème wofi menu power
|
|
||||||
├── cosmic/ configs COSMIC déployées automatiquement
|
|
||||||
│ ├── com.system76.CosmicTheme.Dark/v1/
|
|
||||||
│ ├── com.system76.CosmicTheme.Light/v1/
|
|
||||||
│ ├── com.system76.CosmicTheme.Mode/v1/
|
|
||||||
│ ├── com.system76.CosmicTerm/v1/
|
|
||||||
│ └── com.system76.CosmicTk/v1/
|
|
||||||
└── violet-chaton-discord-theme/ sources SCSS du thème (gitignored)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Mettre à jour les configs
|
|
||||||
|
|
||||||
Après avoir modifié un fichier de config sur ta machine, re-copier vers INSTALL/ :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Exemple : mettre à jour la config starship
|
|
||||||
cp ~/.config/starship.toml ~/Documents/config-violet-chaton/INSTALL/configs/starship.toml
|
|
||||||
|
|
||||||
# Mettre à jour les configs COSMIC
|
|
||||||
cp ~/.config/cosmic/com.system76.CosmicTerm/v1/* \
|
|
||||||
~/Documents/config-violet-chaton/INSTALL/themes/cosmic/com.system76.CosmicTerm/v1/
|
|
||||||
|
|
||||||
# Mettre à jour la config ou le CSS waybar
|
|
||||||
cp ~/.config/waybar/config \
|
|
||||||
~/Documents/config-violet-chaton/INSTALL/configs/waybar/config
|
|
||||||
cp ~/.config/waybar/style.css \
|
|
||||||
~/Documents/config-violet-chaton/INSTALL/themes/violet-chaton-waybar.css
|
|
||||||
|
|
||||||
# Mettre à jour un script waybar
|
|
||||||
cp ~/.config/waybar/scripts/power-profile.sh \
|
|
||||||
~/Documents/config-violet-chaton/INSTALL/configs/waybar/scripts/power-profile.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Palette violet-chaton
|
|
||||||
|
|
||||||
| Rôle | Hex |
|
|
||||||
|------------|-----------|
|
|
||||||
| Background | `#261537` |
|
|
||||||
| BG medium | `#341c4a` |
|
|
||||||
| BG high | `#3d2454` |
|
|
||||||
| Pink | `#ff79c6` |
|
|
||||||
| Purple | `#e79cfe` |
|
|
||||||
| Cyan | `#8be9fd` |
|
|
||||||
| Text | `#f8f8f2` |
|
|
||||||
| Muted | `#6c7086` |
|
|
||||||
| Overlay | `#9399b2` |
|
|
||||||
| Success | `#a6e3a1` |
|
|
||||||
| Warning | `#f9e2af` |
|
|
||||||
| Danger | `#f38ba8` |
|
|
||||||
|
|||||||
Reference in New Issue
Block a user