mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
cli/notify: notify a waiting prompt through the desktop or the terminal
A prompt that has waited `grace_seconds` is now announced, and where the platform allows it the notification carries the answer: allow / deny / review for a confirmation, a reply field for an `ask_user_*` question. Picking "review" withdraws the notification and keeps waiting at the terminal, and a notification is dismissed as soon as the prompt returns — answered or not. Two channels. The desktop one is the optional `notify` extra, run on its own event loop in a daemon thread because a notification's callbacks only fire while the loop that owns it is running (the library's synchronous wrapper does not provide that). The terminal one needs nothing: it writes OSC 777 / OSC 9 to `/dev/tty` — so a redirect cannot swallow it — picking the protocol from the environment and wrapping it for tmux / screen, falling back to a plain bell. kitty speaks OSC 99 only, which is not implemented, so it keeps the bell. Nothing here can break a prompt: a missing extra, a desktop that refuses, a failed write all fall through to the next channel and never raise. Titles are flattened and stripped of control characters, and the reason line (which can name a command or a URL) only travels with `include_reason`.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# Minimal stubs for the optional ``desktop-notifier`` extra.
|
||||
# Only the surface ``plyngent.cli.notify`` uses is declared; the real package is
|
||||
# installed with ``pip install plyngent[notify]`` and is absent by default.
|
||||
|
||||
from collections.abc import Sequence
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
class Capability(Enum):
|
||||
APP_NAME = ...
|
||||
TITLE = ...
|
||||
MESSAGE = ...
|
||||
URGENCY = ...
|
||||
ICON = ...
|
||||
BUTTONS = ...
|
||||
REPLY_FIELD = ...
|
||||
ATTACHMENT = ...
|
||||
ON_DISPATCHED = ...
|
||||
ON_CLICKED = ...
|
||||
ON_DISMISSED = ...
|
||||
SOUND = ...
|
||||
THREAD = ...
|
||||
TIMEOUT = ...
|
||||
|
||||
class Button:
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
on_pressed: Any = None,
|
||||
identifier: str | None = None,
|
||||
) -> None: ...
|
||||
|
||||
class ReplyField:
|
||||
def __init__(
|
||||
self,
|
||||
title: str = ...,
|
||||
button_title: str = ...,
|
||||
on_replied: Any = None,
|
||||
) -> None: ...
|
||||
|
||||
class DesktopNotifier:
|
||||
def __init__(
|
||||
self,
|
||||
app_name: str = ...,
|
||||
app_icon: Any = ...,
|
||||
notification_limit: int | None = None,
|
||||
) -> None: ...
|
||||
async def get_capabilities(self) -> frozenset[Capability]: ...
|
||||
async def send(
|
||||
self,
|
||||
title: str,
|
||||
message: str,
|
||||
urgency: Any = ...,
|
||||
icon: Any = ...,
|
||||
buttons: Sequence[Button] = ...,
|
||||
reply_field: ReplyField | None = None,
|
||||
on_dispatched: Any = None,
|
||||
on_clicked: Any = None,
|
||||
on_dismissed: Any = None,
|
||||
attachment: Any = ...,
|
||||
sound: Any = ...,
|
||||
thread: str | None = None,
|
||||
timeout: int = -1, # noqa: ASYNC109 - the library's own parameter name
|
||||
) -> str: ...
|
||||
async def clear(self, identifier: str) -> None: ...
|
||||
async def clear_all(self) -> None: ...
|
||||
Reference in New Issue
Block a user