Dotfiles/desktopenvs/hyprdrive/scripts/beaconctl

142 lines
4.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""beaconctl — a small CLI for beacon's D-Bus surface, the "dunstctl" beacon
never had (screenrec.sh has its own note on this gap). Talks to:
org.freedesktop.Notifications (CloseNotification)
eu.abdelbaki.beacon.History1 (List/Get/Pop/PopLatest/Remove/Clear)
`close-all` is the one exception: it isn't a D-Bus call (beacon doesn't
expose window.close_all() over the bus), it's the same SIGUSR1 signal the
Super+Ctrl+C keybind already sends.
"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gio, GLib # noqa: E402
FDN_NAME = "org.freedesktop.Notifications"
FDN_PATH = "/org/freedesktop/Notifications"
FDN_IFACE = "org.freedesktop.Notifications"
HISTORY_IFACE = "eu.abdelbaki.beacon.History1"
def _proxy(iface: str) -> Gio.DBusProxy:
return Gio.DBusProxy.new_for_bus_sync(
Gio.BusType.SESSION, Gio.DBusProxyFlags.NONE, None,
FDN_NAME, FDN_PATH, iface, None)
def cmd_list(args: argparse.Namespace) -> int:
proxy = _proxy(HISTORY_IFACE)
(entries,) = proxy.call_sync(
"List", None, Gio.DBusCallFlags.NONE, -1, None).unpack()
if args.json:
print(json.dumps(entries, indent=2))
return 0
if not entries:
print("(history is empty)")
return 0
for e in entries:
print(f"{e['id']:>4} {(e.get('app_name') or '?'):<20} {e.get('summary', '')}")
return 0
def cmd_get(args: argparse.Namespace) -> int:
proxy = _proxy(HISTORY_IFACE)
(entry,) = proxy.call_sync(
"Get", GLib.Variant("(u)", (args.id,)), Gio.DBusCallFlags.NONE, -1, None).unpack()
print(json.dumps(entry, indent=2))
return 0
def cmd_pop(args: argparse.Namespace) -> int:
proxy = _proxy(HISTORY_IFACE)
if args.id is None:
(new_id,) = proxy.call_sync(
"PopLatest", None, Gio.DBusCallFlags.NONE, -1, None).unpack()
else:
(new_id,) = proxy.call_sync(
"Pop", GLib.Variant("(u)", (args.id,)), Gio.DBusCallFlags.NONE, -1, None).unpack()
print(new_id)
return 0
def cmd_remove(args: argparse.Namespace) -> int:
proxy = _proxy(HISTORY_IFACE)
proxy.call_sync(
"Remove", GLib.Variant("(u)", (args.id,)), Gio.DBusCallFlags.NONE, -1, None)
return 0
def cmd_clear(_args: argparse.Namespace) -> int:
proxy = _proxy(HISTORY_IFACE)
proxy.call_sync("Clear", None, Gio.DBusCallFlags.NONE, -1, None)
return 0
def cmd_close(args: argparse.Namespace) -> int:
proxy = _proxy(FDN_IFACE)
proxy.call_sync(
"CloseNotification", GLib.Variant("(u)", (args.id,)),
Gio.DBusCallFlags.NONE, -1, None)
return 0
def cmd_close_all(_args: argparse.Namespace) -> int:
# bracket trick so pkill's own argv doesn't self-match, same as the
# Super+Ctrl+C keybind in hypr/usr/binds.lua
subprocess.run(["pkill", "-USR1", "-f", "[b]eacon/main.py"])
return 0
def main() -> int:
p = argparse.ArgumentParser(prog="beaconctl", description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
sub = p.add_subparsers(dest="command", required=True)
sp = sub.add_parser("list", help="list notification history, newest first")
sp.add_argument("--json", action="store_true")
sp.set_defaults(func=cmd_list)
sp = sub.add_parser("get", help="show one history entry as JSON")
sp.add_argument("id", type=int)
sp.set_defaults(func=cmd_get)
sp = sub.add_parser(
"pop", help="re-display a history entry (most recent if id omitted)")
sp.add_argument("id", type=int, nargs="?", default=None)
sp.set_defaults(func=cmd_pop)
sp = sub.add_parser("remove", help="remove one entry from history")
sp.add_argument("id", type=int)
sp.set_defaults(func=cmd_remove)
sp = sub.add_parser("clear", help="clear all history")
sp.set_defaults(func=cmd_clear)
sp = sub.add_parser("close", help="dismiss a live notification by id")
sp.add_argument("id", type=int)
sp.set_defaults(func=cmd_close)
sp = sub.add_parser("close-all", help="dismiss every visible notification")
sp.set_defaults(func=cmd_close_all)
args = p.parse_args()
try:
return args.func(args)
except GLib.GError as e:
print(f"beaconctl: {e.message}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())