212 lines
6.3 KiB
Python
Executable File
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()
|