1111 from collections .abc import Generator
1212 from typing import Any , ClassVar
1313
14+ from typing_extensions import TypeIs
15+
1416 from streamdeck .models .events import EventBase
1517
1618
19+
1720logger = getLogger ("streamdeck.event_listener" )
1821
1922
23+ class _SENTINAL :
24+ """A sentinel object used to signal the end of the event stream.
25+
26+ Not meant to be instantiated, but rather used as a singleton (e.g. `_SENTINAL`).
27+ """
28+ @classmethod
29+ def is_sentinal (cls , event : str | bytes | type [_SENTINAL ]) -> TypeIs [type [_SENTINAL ]]:
30+ """Check if an event is the sentinal object. Provided to enable better type-checking."""
31+ return event is cls
32+
2033
2134class EventListenerManager :
2235 """Manages event listeners and provides a shared event queue for them to push events into.
@@ -25,7 +38,7 @@ class EventListenerManager:
2538 This allows for us to listen for not only Stream Deck events, but also other events plugin-developer -defined events.
2639 """
2740 def __init__ (self ) -> None :
28- self .event_queue : Queue [str | bytes ] = Queue ()
41+ self .event_queue : Queue [str | bytes | type [ _SENTINAL ] ] = Queue ()
2942 self .listeners_lookup_by_thread : dict [threading .Thread , EventListener ] = {}
3043 self ._running = False
3144
@@ -60,7 +73,11 @@ def stop(self) -> None:
6073
6174 Listeners will check the running flag if implemented to stop listening.
6275 """
76+ # Set the running flag to False to stop the listeners running in separate threads.
6377 self .running = False
78+ # Push the sentinel to immediately unblock the queue.get() in event_stream.
79+ self .event_queue .put (_SENTINAL )
80+
6481 for thread in self .listeners_lookup_by_thread :
6582 self .listeners_lookup_by_thread [thread ].stop ()
6683 thread .join ()
@@ -70,21 +87,21 @@ def stop(self) -> None:
7087 def event_stream (self ) -> Generator [str | bytes , None , None ]:
7188 """Starts all registered listeners, sets the running flag to True, and yields events from the shared queue."""
7289 logger .info ("Starting event stream." )
90+ # Set the running flag to True and start the listeners in their separate threads.
7391 self .running = True
74-
7592 for thread in self .listeners_lookup_by_thread :
7693 thread .start ()
7794
7895 try :
79- while self .running :
80- if not self .event_queue .empty ():
81- yield self .event_queue .get ()
96+ while True :
97+ event = self .event_queue .get ()
98+ if _SENTINAL .is_sentinal (event ):
99+ break # Exit loop immediately if the sentinal is received
100+ yield event
82101 finally :
83102 self .stop ()
84103
85104
86-
87-
88105class EventListener (ABC ):
89106 """Base class for event listeners.
90107
0 commit comments