183 lines
7.2 KiB
Python
183 lines
7.2 KiB
Python
"""Builds the 5-category MenuItem tree: Power, Tools, Scripts, Management, Windows.
|
||
|
||
Power/Tools/Management are static lists. Scripts and Windows are `dynamic=True` —
|
||
orbit_menu.py never caches a dynamic node's page, so both are rebuilt fresh (current
|
||
~/Documents/Scripts listing, current `hyprctl clients -j`) every time they're
|
||
entered; that dynamism cascades to their children too (see OrbitMenu._cache_key),
|
||
so a window's own action ring and its "send to workspace" submenu are always live.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
import subprocess
|
||
|
||
import actions
|
||
from orbit_menu import MenuItem
|
||
from paths import SCRIPTS_DIR
|
||
|
||
|
||
def _hyprctl_json(*args: str):
|
||
try:
|
||
out = subprocess.run(["hyprctl", *args, "-j"], capture_output=True,
|
||
text=True, timeout=1.5, check=True).stdout
|
||
return json.loads(out)
|
||
except (subprocess.SubprocessError, json.JSONDecodeError, OSError):
|
||
return None
|
||
|
||
|
||
# -- Power ------------------------------------------------------------------
|
||
|
||
def power_root() -> MenuItem:
|
||
return MenuItem(label="Power", icon="⏻", children=[
|
||
MenuItem(label="Sleep", icon="☾", danger=True,
|
||
action=lambda: actions.hyprshutdown("systemctl suspend")),
|
||
MenuItem(label="Reboot", icon="⟳", danger=True,
|
||
action=lambda: actions.hyprshutdown("systemctl reboot")),
|
||
MenuItem(label="Power Off", icon="⏻", danger=True,
|
||
action=lambda: actions.hyprshutdown("systemctl poweroff")),
|
||
MenuItem(label="Soft Reboot", icon="⟲", danger=True,
|
||
action=lambda: actions.hyprshutdown("systemctl soft-reboot")),
|
||
])
|
||
|
||
|
||
# -- Tools --------------------------------------------------------------------
|
||
# The little utilities also reachable via their own keybinds (see binds.lua) —
|
||
# gathered here as one launcher so they don't all need to be memorised.
|
||
|
||
def tools_root() -> MenuItem:
|
||
def kitty(tag: str, cmd: str) -> str:
|
||
return f"[tag {tag}] kitty {cmd}"
|
||
|
||
return MenuItem(label="Tools", icon="⚙", children=[
|
||
MenuItem(label="Timer",
|
||
action=lambda: actions.launch(
|
||
kitty("+centered-S", "bash ~/.config/scripts/timer-pick"))),
|
||
MenuItem(label="SSH Manager",
|
||
action=lambda: actions.launch(
|
||
kitty("+centered-L", "-e ~/.config/scripts/amssh"))),
|
||
MenuItem(label="System Monitor",
|
||
action=lambda: actions.launch(kitty("+centered-L", "btop"))),
|
||
MenuItem(label="Screen Recorder",
|
||
action=lambda: actions.launch("~/.config/scripts/screenrec.sh")),
|
||
MenuItem(label="Color Picker",
|
||
action=lambda: actions.launch("hyprpicker | wl-copy")),
|
||
MenuItem(label="Caffeine",
|
||
action=lambda: actions.launch("~/.config/scripts/caffeine.sh")),
|
||
MenuItem(label="On-Screen Kbd",
|
||
action=lambda: actions.launch("~/.config/scripts/onscreenkb.sh")),
|
||
MenuItem(label="Calculator",
|
||
action=lambda: actions.launch("wofi-calc")),
|
||
MenuItem(label="Keybind Help",
|
||
action=lambda: actions.launch(
|
||
kitty("+centered", "~/.config/scripts/helpmenu.sh"))),
|
||
])
|
||
|
||
|
||
# -- Scripts (dynamic) ---------------------------------------------------------
|
||
|
||
def _script_children() -> list[MenuItem]:
|
||
if not SCRIPTS_DIR.is_dir():
|
||
return [MenuItem(label=f"(create {SCRIPTS_DIR})")]
|
||
entries = sorted(p for p in SCRIPTS_DIR.iterdir() if p.is_file())
|
||
if not entries:
|
||
return [MenuItem(label="(no scripts found)")]
|
||
|
||
items = []
|
||
for p in entries:
|
||
if os.access(p, os.X_OK):
|
||
cmd = f"[tag +centered-L] kitty {p}"
|
||
elif p.suffix == ".py":
|
||
cmd = f"[tag +centered-L] kitty python3 {p}"
|
||
elif p.suffix in (".sh", ".bash"):
|
||
cmd = f"[tag +centered-L] kitty bash {p}"
|
||
else:
|
||
continue
|
||
items.append(MenuItem(label=p.stem, action=lambda c=cmd: actions.launch(c)))
|
||
return items or [MenuItem(label="(no runnable scripts)")]
|
||
|
||
|
||
def scripts_root() -> MenuItem:
|
||
return MenuItem(label="Scripts", icon="❯", dynamic=True, children=_script_children)
|
||
|
||
|
||
# -- Management -----------------------------------------------------------------
|
||
|
||
def management_root() -> MenuItem:
|
||
return MenuItem(label="Management", icon="⛶", children=[
|
||
MenuItem(label="Wallpaper Picker",
|
||
action=lambda: actions.launch(
|
||
"[tag +centered-L] kitty -e ~/.config/scripts/wallpaper-picker ~/Pictures")),
|
||
MenuItem(label="Monitor Manager",
|
||
action=lambda: actions.launch(
|
||
"[tag +centered-L] kitty -e ~/.config/scripts/monitor-manager")),
|
||
])
|
||
|
||
|
||
# -- Windows (dynamic) -----------------------------------------------------------
|
||
|
||
def _window_label(w: dict) -> str:
|
||
return (w.get("title") or w.get("class") or "?")[:28]
|
||
|
||
|
||
def _workspace_children(address: str) -> list[MenuItem]:
|
||
workspaces = _hyprctl_json("workspaces") or []
|
||
if not workspaces:
|
||
return [MenuItem(label="(no workspaces)")]
|
||
items = []
|
||
for ws in sorted(workspaces, key=lambda w: w.get("id", 0)):
|
||
wsid = ws.get("id")
|
||
name = ws.get("name") or str(wsid)
|
||
items.append(MenuItem(label=name, icon="⇒",
|
||
action=lambda w=wsid: actions.send_to_workspace(address, w)))
|
||
return items
|
||
|
||
|
||
def _window_actions(w: dict) -> list[MenuItem]:
|
||
address = w["address"]
|
||
pid = w.get("pid", -1)
|
||
active = _hyprctl_json("activeworkspace") or {}
|
||
active_ws = active.get("id", w.get("workspace", {}).get("id", 0))
|
||
title = _window_label(w)
|
||
return [
|
||
MenuItem(label="Go To Window", icon="➤",
|
||
action=lambda: actions.focus_window(address)),
|
||
MenuItem(label="Bring Here", icon="⇤",
|
||
action=lambda: actions.bring_here(address, active_ws)),
|
||
MenuItem(label="Close Window", icon="✕",
|
||
action=lambda: actions.close_window(address)),
|
||
MenuItem(label="Force Kill", icon="⊘", danger=True,
|
||
action=lambda: actions.force_kill(pid)),
|
||
MenuItem(label="Get PID", icon="#",
|
||
action=lambda: actions.report_pid(pid, title)),
|
||
MenuItem(label="Send To Workspace", icon="⇒",
|
||
dynamic=True, children=lambda: _workspace_children(address)),
|
||
]
|
||
|
||
|
||
def _window_children() -> list[MenuItem]:
|
||
clients = _hyprctl_json("clients") or []
|
||
wins = [w for w in clients if w.get("mapped", True) and w.get("class")]
|
||
if not wins:
|
||
return [MenuItem(label="(no open windows)")]
|
||
wins.sort(key=lambda w: (w.get("workspace", {}).get("id", 0), _window_label(w).lower()))
|
||
return [MenuItem(label=_window_label(w), dynamic=True,
|
||
children=lambda w=w: _window_actions(w)) for w in wins]
|
||
|
||
|
||
def windows_root() -> MenuItem:
|
||
return MenuItem(label="Windows", icon="▣", dynamic=True, children=_window_children)
|
||
|
||
|
||
# -- Roots ------------------------------------------------------------------
|
||
|
||
def full_menu_root() -> MenuItem:
|
||
return MenuItem(label="Menu", children=[
|
||
power_root(), tools_root(), scripts_root(), management_root(), windows_root(),
|
||
])
|
||
|
||
|
||
def power_only_root() -> MenuItem:
|
||
return power_root()
|