diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-27 20:05:55 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-27 20:05:55 +0200 |
| commit | b928c3e5bdb01536b60d43f1bc3dd5a9ddb41057 (patch) | |
| tree | 2b8b6c0fd0455f6c9a8ee297110b260b233aa76b | |
| parent | 223292883da497b46caa0e01be0f4a1a3f134ea8 (diff) | |
| download | cal-notif-b928c3e5bdb01536b60d43f1bc3dd5a9ddb41057.tar.gz cal-notif-b928c3e5bdb01536b60d43f1bc3dd5a9ddb41057.zip | |
Add the udt-themed rofi picker for per-event alarms
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rwxr-xr-x | cal-notif | 51 | ||||
| -rw-r--r-- | test_cal_notif.py | 12 |
2 files changed, 63 insertions, 0 deletions
@@ -394,10 +394,61 @@ def daemon(): # ---- picker ---- +def rofi(lines, prompt, theme, mesg=None, index=False): + # udt theming: list.rasi keeps the inputbar (free-text durations) and + # the message widget (-mesg); menu.rasi has neither. + cmd = ["rofi", "-dmenu", "-i", "-theme", str(Path(theme).expanduser()), "-p", prompt] + cmd += ["-mesg", mesg] if mesg else [] + cmd += ["-format", "i"] if index else [] + r = subprocess.run(cmd, input="\n".join(lines), capture_output=True, text=True) + return r.stdout.strip() if r.returncode == 0 else None # Escape: cancel + + +def parse_choice(s): + """Returns a list of offsets, or None for 'reset'. Raises ValueError.""" + s = s.strip() + if s == "reset": + return None + if s == "none": + return [] + return [fmt_duration(parse_duration(p)) for p in s.split(",")] + + +def pick(): + cfg, ov = load_config(), load_overrides() + now = datetime.now().astimezone() + occs = [o for o in load_events(cfg["calendars_dir"], now, now + timedelta(days=PICK_DAYS)) + if o.start >= now] + fmt = lambda o: f"{o.start:%d.%m} " if o.allday else f"{o.start:%d.%m %H:%M}" + lines = [f"{fmt(o)} · {o.cal} · {o.summary} · [{', '.join(current_offsets(o, cfg, ov))}]" + for o in occs] + i = rofi(lines, "evento", cfg["rofi_theme"], index=True) + if i is None or not i.lstrip("-").isdigit() or int(i) < 0: + return + o, mesg = occs[int(i)], lines[int(i)] + presets = ["10m", "30m", "1h", "1d", "1d, 1h", "none", "reset"] + while (s := rofi(presets, "avvisi", cfg["rofi_theme"], mesg=mesg)) is not None: + try: + offs = parse_choice(s) + except ValueError as e: + mesg = f"{lines[int(i)]}\n{e}" + continue + if offs is None: + ov.pop(o.uid, None) + else: + ov[o.uid] = offs + save_overrides(ov) + return + + def main(): cmd = sys.argv[1:2] if cmd == ["daemon"]: daemon() + elif cmd == ["pick"]: + if not shutil.which("rofi"): + sys.exit("cal-notif: rofi not found") + pick() elif cmd == ["say"] and len(sys.argv) > 2: say(" ".join(sys.argv[2:]), load_config()["voice"]) else: diff --git a/test_cal_notif.py b/test_cal_notif.py index f22fbc3..0bb647c 100644 --- a/test_cal_notif.py +++ b/test_cal_notif.py @@ -193,6 +193,18 @@ def test_due(): assert cn.announce(o, t + 5 * M, cn.DEFAULTS["voice"]) == "Adesso: x" +def test_choice(): + assert cn.parse_choice("1d, 1h") == ["1d", "1h"] + assert cn.parse_choice("90m") == ["1h 30m"] + assert cn.parse_choice("none") == [] + assert cn.parse_choice("reset") is None + try: + cn.parse_choice("soon") + raise AssertionError + except ValueError: + pass + + if __name__ == "__main__": for name, fn in list(globals().items()): if name.startswith("test_"): |
