#!/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()