Files
Thinkpad-Hyprland-Dotfiles/config/hypr/Scripts/power-menu.py
2026-04-28 03:59:07 +02:00

212 lines
6.3 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import shlex
import subprocess
from pathlib import Path
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gdk, Gtk # noqa: E402
HYPR_DIR = Path.home() / ".config" / "hypr"
THEME_DIR = HYPR_DIR / "Themes"
CURRENT_WALLPAPER = HYPR_DIR / "current-wallpaper"
DEFAULT_THEME = {
"NAME": "Power",
"ACCENT": "#f38ba8",
"ACCENT_2": "#cba6f7",
"BACKGROUND_HEX": "#18141f",
"PANEL_HEX": "#313244",
"FOREGROUND": "#f5e0dc",
"MUTED": "#a6adc8",
"SELECTED_TEXT": "#11111b",
}
def parse_theme_file(path):
theme = {}
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
try:
parsed = shlex.split(value)
theme[key] = parsed[0] if parsed else ""
except ValueError:
theme[key] = value.strip("\"'")
return theme
def load_current_theme():
current_wallpaper = ""
if CURRENT_WALLPAPER.exists():
current_wallpaper = CURRENT_WALLPAPER.read_text(encoding="utf-8", errors="ignore").strip()
for theme_file in sorted(THEME_DIR.glob("*.theme")):
theme = parse_theme_file(theme_file)
if theme.get("WALLPAPER") == current_wallpaper:
return {**DEFAULT_THEME, **theme}
rose = THEME_DIR / "rose-night.theme"
if rose.exists():
return {**DEFAULT_THEME, **parse_theme_file(rose)}
return DEFAULT_THEME
def run(command):
subprocess.Popen(command, start_new_session=True)
Gtk.main_quit()
class PowerMenu(Gtk.Window):
def __init__(self):
super().__init__(title="Power")
self.theme = load_current_theme()
self.set_decorated(False)
self.set_resizable(False)
self.set_keep_above(True)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
self.set_skip_taskbar_hint(True)
self.set_skip_pager_hint(True)
self.set_app_paintable(True)
self.connect("key-press-event", self.on_key_press)
self.connect("focus-out-event", lambda *_: Gtk.main_quit())
overlay = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=18)
overlay.get_style_context().add_class("power-shell")
title = Gtk.Label(label=self.theme.get("NAME", "Power"))
title.get_style_context().add_class("power-title")
overlay.pack_start(title, False, False, 0)
grid = Gtk.Grid()
grid.set_column_spacing(14)
grid.set_row_spacing(14)
grid.set_halign(Gtk.Align.CENTER)
actions = [
("", "Sperren", ["hyprlock"]),
("󰤄", "Ruhezustand", ["systemctl", "suspend"]),
("󰗽", "Abmelden", ["hyprctl", "dispatch", "exit"]),
("󰜉", "Neustart", ["systemctl", "reboot"]),
("", "Ausschalten", ["systemctl", "poweroff"]),
("󰗼", "Abbrechen", None),
]
for index, (icon, label, command) in enumerate(actions):
button = self.make_button(icon, label, command)
grid.attach(button, index % 3, index // 3, 1, 1)
overlay.pack_start(grid, False, False, 0)
self.add(overlay)
self.apply_css()
def make_button(self, icon, label, command):
button = Gtk.Button()
button.get_style_context().add_class("power-button")
button.set_size_request(150, 118)
button.set_relief(Gtk.ReliefStyle.NONE)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
box.set_halign(Gtk.Align.CENTER)
box.set_valign(Gtk.Align.CENTER)
icon_label = Gtk.Label(label=icon)
icon_label.get_style_context().add_class("power-icon")
text_label = Gtk.Label(label=label)
text_label.get_style_context().add_class("power-label")
box.pack_start(icon_label, False, False, 0)
box.pack_start(text_label, False, False, 0)
button.add(box)
if command:
button.connect("clicked", lambda *_: run(command))
else:
button.connect("clicked", lambda *_: Gtk.main_quit())
return button
def apply_css(self):
css = f"""
window {{
background: transparent;
}}
.power-shell {{
margin: 0;
padding: 24px;
border-radius: 26px;
border: 1px solid {self.theme["ACCENT"]};
background: alpha({self.theme["BACKGROUND_HEX"]}, 0.94);
box-shadow: 0 22px 70px alpha(#000000, 0.55);
}}
.power-title {{
color: {self.theme["FOREGROUND"]};
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
font-size: 18px;
font-weight: 800;
}}
.power-button {{
color: {self.theme["FOREGROUND"]};
background: alpha({self.theme["PANEL_HEX"]}, 0.82);
border: 1px solid alpha({self.theme["ACCENT_2"]}, 0.42);
border-radius: 18px;
transition: 160ms ease;
}}
.power-button:hover,
.power-button:focus {{
color: {self.theme["SELECTED_TEXT"]};
background: linear-gradient(135deg, {self.theme["ACCENT"]}, {self.theme["ACCENT_2"]});
border-color: {self.theme["ACCENT"]};
}}
.power-icon {{
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
font-size: 34px;
font-weight: 800;
}}
.power-label {{
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
font-size: 13px;
font-weight: 800;
}}
"""
provider = Gtk.CssProvider()
provider.load_from_data(css.encode("utf-8"))
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
)
def on_key_press(self, _widget, event):
if event.keyval in (Gdk.KEY_Escape, Gdk.KEY_q):
Gtk.main_quit()
if __name__ == "__main__":
window = PowerMenu()
window.show_all()
Gtk.main()