aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/dot_config/waybar/executable_notification-picker.py
diff options
context:
space:
mode:
Diffstat (limited to 'dot_config/waybar/executable_notification-picker.py')
-rw-r--r--dot_config/waybar/executable_notification-picker.py114
1 files changed, 114 insertions, 0 deletions
diff --git a/dot_config/waybar/executable_notification-picker.py b/dot_config/waybar/executable_notification-picker.py
new file mode 100644
index 0000000..e6d6aed
--- /dev/null
+++ b/dot_config/waybar/executable_notification-picker.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+"""Notification picker.
+
+Lists currently-visible mako notifications. Selecting one copies its
+"summary\\nbody" to the clipboard and dismisses it via `makoctl dismiss
+-n <id>`. The picker re-opens after each selection so multiple
+notifications can be processed in one go; Esc closes it.
+"""
+
+from __future__ import annotations
+
+import re
+import subprocess
+from pathlib import Path
+from typing import Any
+
+RECORD_RE = re.compile(r"^Notification (\d+):\s?(.*)$")
+FIELD_RE = re.compile(r"^ ([A-Za-z][A-Za-z ]*?):\s*(.*)$")
+
+
+def list_notifications() -> list[dict[str, Any]]:
+ try:
+ out = subprocess.run(
+ ["makoctl", "list"], capture_output=True, text=True, check=True
+ ).stdout
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ return []
+ notifs: list[dict[str, Any]] = []
+ cur: dict[str, Any] | None = None
+ last_field: str | None = None
+ for line in out.splitlines():
+ m = RECORD_RE.match(line)
+ if m:
+ if cur is not None:
+ notifs.append(cur)
+ cur = {"id": int(m.group(1)), "summary": m.group(2).strip()}
+ last_field = "summary"
+ continue
+ if cur is None:
+ continue
+ m = FIELD_RE.match(line)
+ if m:
+ key = m.group(1).strip().lower().replace(" ", "_")
+ cur[key] = m.group(2)
+ last_field = key
+ continue
+ if last_field == "body" and line.startswith(" "):
+ cur["body"] = (str(cur.get("body", "")) + " " + line.strip()).strip()
+ if cur is not None:
+ notifs.append(cur)
+ return notifs
+
+
+def fmt_line(n: dict[str, Any]) -> str:
+ app = (str(n.get("app_name") or "?")).strip() or "?"
+ summary = str(n.get("summary") or "").strip()
+ body = str(n.get("body") or "").strip()
+ text = summary if not body else f"{summary} — {body}"
+ text = text.replace("\t", " ").replace("\r", " ")
+ return f"[{app}] {text}\t#{n['id']}"
+
+
+def parse_selection(line: str) -> int | None:
+ m = re.search(r"\t#(\d+)\s*$", line)
+ return int(m.group(1)) if m else None
+
+
+def run_wofi(input_text: str, lines: int) -> str:
+ style = Path.home() / ".config/wofi/style.css"
+ cmd = [
+ "wofi",
+ "--dmenu",
+ "--hide-search",
+ "--prompt",
+ "Notifications",
+ "--lines",
+ str(lines),
+ ]
+ if style.exists():
+ cmd += ["--style", str(style)]
+ proc = subprocess.run(
+ cmd, input=input_text, text=True, capture_output=True, check=False
+ )
+ return proc.stdout.strip()
+
+
+def main() -> None:
+ while True:
+ notifs = list_notifications()
+ if not notifs:
+ _ = run_wofi("(no notifications)\n", 1)
+ return
+
+ lines_text = "\n".join(fmt_line(n) for n in notifs) + "\n"
+ sel = run_wofi(lines_text, min(len(notifs), 15))
+ if not sel or sel.startswith("(no "):
+ return
+
+ nid = parse_selection(sel)
+ if nid is None:
+ return
+ notif = next((n for n in notifs if n["id"] == nid), None)
+ if notif is None:
+ return
+
+ summary = str(notif.get("summary") or "").strip()
+ body = str(notif.get("body") or "").strip()
+ clip_text = f"{summary}\n{body}".strip()
+ _ = subprocess.run(["wl-copy"], input=clip_text, text=True, check=False)
+ _ = subprocess.run(["makoctl", "dismiss", "-n", str(nid)], check=False)
+
+
+if __name__ == "__main__":
+ main()