diff --git a/flake.nix b/flake.nix index 64aa7c4..3139890 100644 --- a/flake.nix +++ b/flake.nix @@ -131,6 +131,21 @@ default = "emacsclient"; description = "Path to the emacsclient binary"; }; + debt_query = lib.mkOption { + type = lib.types.str; + default = "tag:unread and date:..1w"; + description = "notmuch query whose count drives the mail-debt severity color on the bar widget"; + }; + debt_warn_at = lib.mkOption { + type = lib.types.int; + default = 1; + description = "Debt count at which the widget switches to the warn (orange) color"; + }; + debt_alarm_at = lib.mkOption { + type = lib.types.int; + default = 6; + description = "Debt count at which the widget switches to the alarm (red) color"; + }; }; screenrec = { enable = lib.mkOption { @@ -210,6 +225,9 @@ enable = true; notmuch_path = "notmuch"; emacsclient_command = "emacsclient"; + debt_query = "tag:unread and date:..1w"; + debt_warn_at = 1; + debt_alarm_at = 6; }; screenrec = { enable = false; diff --git a/sims/main.py b/sims/main.py index 5c1e72d..5623372 100644 --- a/sims/main.py +++ b/sims/main.py @@ -131,7 +131,7 @@ def open_screenshot_menu(): @Application.action() def refresh_notmuch(): if notmuch_widget is not None: - notmuch_widget.service.update_unread_count() + notmuch_widget.service.update_counts() @Application.action() diff --git a/sims/modules/notmuch.py b/sims/modules/notmuch.py index 992be54..a518388 100644 --- a/sims/modules/notmuch.py +++ b/sims/modules/notmuch.py @@ -12,27 +12,33 @@ from loguru import logger from sims.config import NOTMUCH +DEFAULT_DEBT_QUERY = "tag:unread and date:..1w" +DEFAULT_DEBT_WARN_AT = 1 +DEFAULT_DEBT_ALARM_AT = 6 + + class NotmuchService: def __init__(self, update_interval=60000): # 1 minute default self.unread_count = 0 + self.debt_count = 0 self.callbacks = [] self._update_interval = update_interval self._timer_id = None # Initial load - self.update_unread_count() + self.update_counts() # Start periodic updates self.start_monitoring() def connect(self, signal_name, callback): """Simple callback system to replace signals""" - if signal_name == "unread-changed": + if signal_name == "counts-changed": self.callbacks.append(callback) - def emit_unread_changed(self, count): - """Emit unread changed to all callbacks""" + def emit_counts_changed(self): + """Emit counts changed to all callbacks""" for callback in self.callbacks: - callback(self, count) + callback(self, self.unread_count, self.debt_count) def start_monitoring(self): """Start periodic unread count updates""" @@ -57,21 +63,38 @@ class NotmuchService: def _periodic_update(self): """Periodic update callback""" - logger.info("[Notmuch] Performing periodic unread count update") - self.update_unread_count() + logger.info("[Notmuch] Performing periodic count update") + self.update_counts() return True # Keep the timer running def get_cached_count(self): """Get cached unread count without triggering update""" return self.unread_count - def update_unread_count(self): - """Fetch unread email count from notmuch""" + def get_cached_debt_count(self): + """Get cached debt count without triggering update""" + return self.debt_count + + def _run_count(self, notmuch_path, query): + cmd = [notmuch_path, "count", query] + logger.info(f"[Notmuch] Running command: {' '.join(cmd)}") + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + out = result.stdout.strip() + return int(out) if out else 0 + + def update_counts(self): + """Fetch unread + debt counts from notmuch""" # Check if notmuch is enabled if not NOTMUCH.get("enable", True): logger.info("[Notmuch] Notmuch is disabled in config") self.unread_count = 0 - self.emit_unread_changed(self.unread_count) + self.debt_count = 0 + self.emit_counts_changed() return # Get notmuch path from config @@ -81,42 +104,34 @@ class NotmuchService: if not shutil.which(notmuch_path): logger.warning(f"[Notmuch] notmuch not found at '{notmuch_path}'. Please install notmuch or configure the correct path.") self.unread_count = 0 - self.emit_unread_changed(self.unread_count) + self.debt_count = 0 + self.emit_counts_changed() return + debt_query = NOTMUCH.get("debt_query", DEFAULT_DEBT_QUERY) + try: - # Get unread email count - cmd = [notmuch_path, "count", "tag:unread"] - logger.info(f"[Notmuch] Running command: {' '.join(cmd)}") - result = subprocess.run( - cmd, - capture_output=True, - text=True, - check=True, + self.unread_count = self._run_count(notmuch_path, "tag:unread") + self.debt_count = self._run_count(notmuch_path, debt_query) + logger.info( + f"[Notmuch] {self.unread_count} unread, {self.debt_count} aging (debt query: {debt_query!r})" ) - logger.info(f"[Notmuch] Command stdout: '{result.stdout.strip()}'") - logger.info(f"[Notmuch] Command stderr: '{result.stderr.strip()}'") - - if result.stdout.strip(): - self.unread_count = int(result.stdout.strip()) - logger.info(f"[Notmuch] Found {self.unread_count} unread emails") - self.emit_unread_changed(self.unread_count) - else: - self.unread_count = 0 - self.emit_unread_changed(self.unread_count) - + self.emit_counts_changed() except subprocess.CalledProcessError as e: - logger.error(f"[Notmuch] Failed to fetch unread count: {e}") + logger.error(f"[Notmuch] Failed to fetch counts: {e}") self.unread_count = 0 - self.emit_unread_changed(self.unread_count) + self.debt_count = 0 + self.emit_counts_changed() except ValueError as e: - logger.error(f"[Notmuch] Error parsing unread count: {e}") + logger.error(f"[Notmuch] Error parsing count: {e}") self.unread_count = 0 - self.emit_unread_changed(self.unread_count) + self.debt_count = 0 + self.emit_counts_changed() except Exception as e: - logger.error(f"[Notmuch] Error getting unread count: {e}") + logger.error(f"[Notmuch] Error getting counts: {e}") self.unread_count = 0 - self.emit_unread_changed(self.unread_count) + self.debt_count = 0 + self.emit_counts_changed() class NotmuchWidget(Button): @@ -141,12 +156,14 @@ class NotmuchWidget(Button): # Initialize the service self.service = NotmuchService() - self.service.connect("unread-changed", self.update_display) + self.service.connect("counts-changed", self.update_display) logger.info("[Notmuch] Notmuch widget initialized") # Initial update - self.update_display(self.service, self.service.unread_count) + self.update_display( + self.service, self.service.unread_count, self.service.debt_count + ) def open_email_client(self, button=None): """Open notmuch in emacsclient""" @@ -161,18 +178,31 @@ class NotmuchWidget(Button): except Exception as e: logger.error(f"[Notmuch] Failed to open notmuch in emacsclient '{emacsclient_command}': {e}") - def update_display(self, service, count): - """Update the widget display with unread count""" - # Only show count if there are unread emails - if count > 0: - self.label.set_text(str(count)) + def update_display(self, service, unread, debt): + """Update the widget display with unread + debt counts""" + warn_at = NOTMUCH.get("debt_warn_at", DEFAULT_DEBT_WARN_AT) + alarm_at = NOTMUCH.get("debt_alarm_at", DEFAULT_DEBT_ALARM_AT) + + classes = ["notmuch-widget"] + if unread > 0: + self.label.set_text(str(unread)) self.label.set_visible(True) self.icon.set_property("icon-name", "mail-unread-symbolic") - self.set_style_classes(["notmuch-widget", "has-unread"]) + classes.append("has-unread") else: self.label.set_text("") self.label.set_visible(False) self.icon.set_property("icon-name", "mail-read-symbolic") - self.set_style_classes(["notmuch-widget", "no-unread"]) + classes.append("no-unread") - logger.info(f"[Notmuch] Updated display: {count} unread emails") \ No newline at end of file + if debt >= alarm_at: + classes.append("debt-alarm") + elif debt >= warn_at: + classes.append("debt-warn") + + self.set_style_classes(classes) + self.set_tooltip_text(f"{unread} unread · {debt} aging") + + logger.info( + f"[Notmuch] Updated display: {unread} unread, {debt} aging — classes={classes}" + ) \ No newline at end of file diff --git a/sims/styles/notmuch.css b/sims/styles/notmuch.css index a4ce92b..8a7d0b9 100644 --- a/sims/styles/notmuch.css +++ b/sims/styles/notmuch.css @@ -23,6 +23,22 @@ background-color: var(--module-bg); } +#notmuch-widget.debt-warn { + background-color: var(--orange); +} + +#notmuch-widget.debt-warn:hover { + background-color: var(--gold); +} + +#notmuch-widget.debt-alarm { + background-color: var(--red); +} + +#notmuch-widget.debt-alarm:hover { + background-color: var(--pink); +} + #unread-count { color: var(--foreground); font-size: 14px; @@ -30,6 +46,8 @@ min-width: 16px; } -#notmuch-widget.has-unread #unread-count { +#notmuch-widget.has-unread #unread-count, +#notmuch-widget.debt-warn #unread-count, +#notmuch-widget.debt-alarm #unread-count { color: var(--background); } \ No newline at end of file