diff --git a/scripts/ocd-waybar b/scripts/ocd-waybar index 26e33f3..73c8d94 100755 --- a/scripts/ocd-waybar +++ b/scripts/ocd-waybar @@ -20,6 +20,7 @@ import signal import socket import subprocess import sys +import threading import time from datetime import datetime, timezone from html import escape @@ -303,6 +304,11 @@ class Daemon: self.last_key = None self.sock = None self.running = True + # The ocd poll runs in a worker thread so the main loop never + # blocks on a slow network fetch. Rotation and clicks stay live. + self._fetch_thread = None + self._fresh_report = None + self._fetch_lock = threading.Lock() def event_deadline(self): now = time.monotonic() @@ -327,14 +333,27 @@ class Daemon: def lock(self): self.locked_until = time.monotonic() + LOCK_SECS + def _fetch_worker(self): + rep = fetch_ocd() + if rep: + with self._fetch_lock: + self._fresh_report = rep + def maybe_tick(self): now = time.monotonic() - if now >= self.next_poll: + # Kick off a poll on a worker thread; never block the main loop. + if now >= self.next_poll and (self._fetch_thread is None or not self._fetch_thread.is_alive()): self.next_poll = now + POLL_SECS - rep = fetch_ocd() + self._fetch_thread = threading.Thread(target=self._fetch_worker, daemon=True) + self._fetch_thread.start() + # Adopt a finished poll's result if one is ready. + if self._fetch_thread is not None and not self._fetch_thread.is_alive(): + self._fetch_thread = None + with self._fetch_lock: + rep = self._fresh_report + self._fresh_report = None if rep: self.report = rep - # keep index in range n = len(rep.get("accounts") or []) if n and self.index >= n: self.index %= n diff --git a/scripts/ocd-waybar-ctl b/scripts/ocd-waybar-ctl index f347f46..2e7f5bb 100755 --- a/scripts/ocd-waybar-ctl +++ b/scripts/ocd-waybar-ctl @@ -1,30 +1,33 @@ #!/bin/sh # ocd-waybar-ctl: send a command to the ocd-waybar daemon. # Usage: ocd-waybar-ctl {advance|lock|poll|quit} -# wired into the waybar custom/ocd module as on-click / on-click-right. +# Wired into the waybar custom/ocd module as on-click / on-click-right. +# +# This must never hang: waybar runs it on a click and stops updating the +# module until it returns. socat's UNIX-SENDTO relay can block waiting on +# the datagram socket, so we use a small python sendto with a hard timeout +# instead. python3 is guaranteed present because the daemon itself needs it. set -eu cmd="${1:-advance}" -sock="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/ocd-waybar.sock" - -if [ "$#" -gt 0 ]; then - cmd="$1" -fi - case "$cmd" in advance|lock|poll|quit) ;; *) echo "ocd-waybar-ctl: unknown command '$cmd'" >&2; exit 2 ;; esac -if [ ! -S "$sock" ]; then - echo "ocd-waybar-ctl: daemon socket not found at $sock" >&2 - exit 1 -fi +sock="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/ocd-waybar.sock" +[ -S "$sock" ] || { echo "ocd-waybar-ctl: daemon socket not found at $sock" >&2; exit 1; } -printf '%s\n' "$cmd" | socat - UNIX-SENDTO:"$sock" 2>/dev/null \ - || printf '%s\n' "$cmd" | nc -U -u -w1 "$sock" 2>/dev/null \ - || python3 - "$sock" "$cmd" <<'PY' +python3 - "$sock" "$cmd" <<'PY' import socket, sys +sock, cmd = sys.argv[1], sys.argv[2] s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) -s.sendto((sys.argv[2] + "\n").encode(), sys.argv[1]) +s.settimeout(2.0) +try: + s.sendto((cmd + "\n").encode(), sock) +except (OSError, socket.timeout) as e: + print("ocd-waybar-ctl: send failed: %s" % e, file=sys.stderr) + sys.exit(1) +finally: + s.close() PY \ No newline at end of file