diff --git a/src/python/matrix_client.py b/src/python/matrix_client.py index 704f8f55..dd84acd0 100644 --- a/src/python/matrix_client.py +++ b/src/python/matrix_client.py @@ -19,6 +19,7 @@ from . import __about__, utils from .html_filter import HTML_FILTER from .models.items import Account, Event, Member, Room, TypeSpecifier from .models.model_store import ModelStore +from .pyotherside_events import AlertRequested class UploadError(Enum): @@ -52,6 +53,7 @@ class MatrixClient(nio.AsyncClient): self.sync_task: Optional[asyncio.Future] = None self.first_sync_happened: asyncio.Event = asyncio.Event() + self.first_sync_date: Optional[datetime] = None self.send_locks: DefaultDict[str, asyncio.Lock] = \ DefaultDict(asyncio.Lock) # {room_id: lock} @@ -299,6 +301,17 @@ class MatrixClient(nio.AsyncClient): # Functions to register data into models + async def event_is_past(self, ev: Union[nio.Event, Event]) -> bool: + if not self.first_sync_date: + return True + + if isinstance(ev, Event): + return ev.date < self.first_sync_date + + date = datetime.fromtimestamp(ev.server_timestamp / 1000) + return date < self.first_sync_date + + async def set_room_last_event(self, room_id: str, item: Event) -> None: room = self.models[Room, self.user_id][room_id] @@ -446,6 +459,9 @@ class MatrixClient(nio.AsyncClient): with suppress(KeyError): item.client_id = f"echo-{client.resolved_echoes[ev.event_id]}" + elif not await self.event_is_past(ev): + AlertRequested() + self.models[Event, self.user_id, room.room_id][item.client_id] = item await self.set_room_last_event(room.room_id, item) @@ -476,7 +492,8 @@ class MatrixClient(nio.AsyncClient): if not self.first_sync_happened.is_set(): asyncio.ensure_future(self.load_rooms_without_visible_events()) - self.first_sync_happened.set() + self.first_sync_happened.set() + self.first_sync_date = datetime.now() async def onErrorResponse(self, resp: nio.ErrorResponse) -> None: diff --git a/src/python/pyotherside_events.py b/src/python/pyotherside_events.py index 4c685900..0c5f8574 100644 --- a/src/python/pyotherside_events.py +++ b/src/python/pyotherside_events.py @@ -8,6 +8,8 @@ from .models import SyncId @dataclass class PyOtherSideEvent: + """Event that will be sent to QML by PyOtherSide.""" + def __post_init__(self) -> None: # CPython >= 3.6 or any Python >= 3.7 needed for correct dict order args = [ @@ -27,17 +29,36 @@ class PyOtherSideEvent: @dataclass class ExitRequested(PyOtherSideEvent): + """Request for the application to exit.""" + exit_code: int = 0 +@dataclass +class AlertRequested(PyOtherSideEvent): + """Request an alert to be shown for msec milliseconds. + If msec is 0 (default), the alert should be shown indefinitely until + the window is focused. + + The Alert state for example sets the urgency hint on X11/Wayland, + or flashes the taskbar icon on Windows. + """ + + msec: int = 0 + + @dataclass class CoroutineDone(PyOtherSideEvent): + """Indicate that an asyncio coroutine finished.""" + uuid: str = field() result: Any = None @dataclass class ModelUpdated(PyOtherSideEvent): + """Indicate that a backend model's data changed.""" + sync_id: SyncId = field() data: List[Dict[str, Any]] = field() diff --git a/src/qml/event_handlers.js b/src/qml/event_handlers.js index 0f9f95e6..78c1b120 100644 --- a/src/qml/event_handlers.js +++ b/src/qml/event_handlers.js @@ -6,6 +6,11 @@ function onExitRequested(exitCode) { } +function onAlertRequested(msec) { + window.alert(msec) +} + + function onCoroutineDone(uuid, result) { py.pendingCoroutines[uuid](result) delete pendingCoroutines[uuid]