Skip to content

Support on_timeout callback for Linux and Windows backends #236

@gaesa

Description

@gaesa

The library currently exposes callbacks for on_dispatched, on_clicked, and on_dismissed, but there is no callback for “notification timed out”. I would like to propose adding a new callback: on_timeout, parallel to on_dismissed.

Linux (Freedesktop)

The Freedesktop notification spec distinguishes:

  • Dismissed (2) — closed explicitly by the user
  • Expired (1) — closed automatically due to timeout

The current DBus backend already defines NOTIFICATION_CLOSED_EXPIRED = 1, but the value is unused.

The code path in _on_closed only handles the dismissed case:

if reason == NOTIFICATION_CLOSED_DISMISSED:
    self.handle_dismissed(identifier)

In recent versions of KDE (confirmed on Plasma 6.4.5 and 6.5.2), each notification whose action list is non-empty and even in length was immediately cleared when the client process exited. This behavior was not observed on Plasma 6.0.3, 6.2.1, or 6.3.4. (The condition—non-empty action list of even length—was introduced in this change.)

So not handling the expired case has practical consequences:

  • When a client process exits, its DBus connection is closed.
  • Any active notifications associated with that client disappear immediately.
  • Therefore, it is necessary to wait until the notification has actually timed out.
  • Without on_timeout being exposed, this cannot be implemented correctly.
  • Simply doing asyncio.sleep(timeout) (or similar) is unreliable: hovering over the notification pauses and resets the timeout in many DE. The process may therefore exit while the notification is still fully visible, causing it to vanish instantly.

Even without the actions issue, this feature is still valuable in certain scenarios.

Windows (WinRT Toast)

Windows Toast also distinguishes two close reasons:

  • UserCanceled: current on_dismissed
  • TimedOut: currently ignored by the backend

But currently _on_dismissed only handles ToastDismissalReason.USER_CANCELED

Although Windows toast notifications survive process exit, supporting TimedOut is still natural, trivial, and harmless — it provides consistent behaviour across platforms.

MacOS

Very hard to implement, the documentation of Apple is unclear, and it remains to be seen how much rubicon-objc can accomplish, maybe a substantial rewrite to this backend is required. Since macOS notifications persist after process exit anyway, the lack of a distinct timeout callback is far less problematic than on Linux and can safely be left unimplemented for now.


API proposal

Add an optional on_timeout callback parameter to DesktopNotifier.send, analogous to on_dismissed.

await notifier.send(
    ...,
    on_dismissed=...,
    on_timeout=...,      # new
)

Backend changes are minimal:

  • Linux DBus: map NOTIFICATION_CLOSED_EXPIRED to handle_timeout
  • Windows WinRT: map ToastDismissalReason.TIMED_OUT to handle_timeout
  • macOS: no-op

This change is completely backwards-compatible because:

  • _on_closed/_on_dismissed are private
  • Existing on_dismissed semantics remain "user explicitly closed"
  • No breaking changes to public API if we only add a new optional callback

I'm willing to submit a PR if you're open to the idea. Implementation on Linux + Windows should be <100 LOC and trivial.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions