def match(self, sequence): # The ANSI sequence for a mouse event in mode 1006 is '<ESC>[B;Col;RowM' (last char is 'm' if button-release) m = re.match( r"\x1b\[\<(?P<button>\d+);(?P<column>\d+);(?P<row>\d+)(?P<press>[Mm])", sequence) if not m: return None params = m.groupdict() pressed = params["press"] == "M" button = _button_map.get(int(params["button"]) & (~0x20), None) moving = bool(int(params["button"]) & 0x20) col = int(params["column"]) - 1 row = int(params["row"]) - 1 click_event = event = None # TBD: check for different buttons in press events and send combined button events if moving: event = Event(EventTypes.MouseMove, pos=V2(col, row), buttons=button) elif pressed: ts = time.time() event = Event(EventTypes.MousePress, pos=V2(col, row), buttons=button, time=ts) self.last_press = ( ts, button, ) else: ts = time.time() event = Event(EventTypes.MouseRelease, pos=V2(col, row), buttons=button) if ts - self.last_press[ 0] < self.CLICK_THRESHOLD and button == self.last_press[1]: Event(EventTypes.MouseClick, pos=V2(col, row), buttons=button, time=ts) if ts - self.last_click[ 0] < self.DOUBLE_CLICK_THRESHOLD and button == self.last_click[ 1]: Event(EventTypes.MouseDoubleClick, pos=V2(col, row), buttons=button, time=ts) self.last_click = ( ts, button, ) return event
def test_event_system_select_events(): subscription = Subscription([0, 1]) Event(2) process() assert not subscription.queue Event(0) process() assert subscription.queue subscription.queue.clear() Event(1) process() assert subscription.queue
def _posix_inkey(break_=True, clear=True): """Return currently pressed key as a string This is the implemenation of old 8-bit basic "inkey" and "inkey$" builtins, and also the heart of the keybard input system. Args: - break\_ (bool): Boolean parameter specifying whether "CTRL + C" (\x03) should raise KeyboardInterrupt or be returned as a keycode. Defaults to True. -clear (bool): clears the keyboard buffer contents. If False, queued keyboard codes are returned in order, for each call Otherwise queued codes are discarded and only the last-pressed character if returned. Even when "clear" is True, one keyboard event is generated for each token. Also, note that calling Screen.update will cause this to be called with clear=True, to flush all keypress events. Applications using 'inkey' to read all input should make all the calls between 'updates' defaults to True *Important*: This function only works inside a :any:`keyboard` managed context. (Posix) Code values or code sequences for non-character keys, like ESC, direction arrows, fkeys are kept as constants in the "KeyCodes" class. Unfortunatelly, due to the nature of console streaming, this can't receive "keypress" or "keyup" events, and repeat-rate is not configurable, nor can it detect modifier keys, or simultaneous key-presses. """ # In this context, 'token' is either a single-char string, representing an # 'ordinary' keypress or an escape sequence representing a special key or mouse event. old_keycode = "" if not clear: buffer = sys.stdin else: # read all buffer, generate events for all tokens # but return only the last event # (so, the caller to "inkey" have info on the last pressed key) buffer = io.StringIO(sys.stdin.read(10000)) buffer.seek(0) while True: keycode, stream_eof = _posix_scan_code(buffer) if stream_eof and not keycode: keycode = old_keycode if keycode == '\x03' and break_: raise KeyboardInterrupt() if list_subscriptions(EventTypes.KeyPress): Event(EventTypes.KeyPress, key=keycode) if not clear or stream_eof: # next characters will be consumed in next calls break old_keycode = keycode return keycode
def inkey(self, break_=True, clear=True, consume=True): """Return currently pressed key as a string Args: - break\_ (bool): Boolean parameter specifying whether "CTRL + C" (\x03) should raise KeyboardInterrupt or be returned as a keycode. Defaults to True. - clear (bool): clears the keyboard buffer contents [WIP: latest updates adding support for the event system on the posix side where not reflected here] [TODO: Dispatch mouse event handling from here] """ if not msvcrt.kbhit(): return "" code = msvcrt.getwch() if code in "\x00à": # and msvcrt.kbhit(): code += msvcrt.getwch() if list_subscriptions(EventTypes.KeyPress): Event(EventTypes.KeyPress, key=keycode) return code
def flush_clicks(self): to_kill = [] for i, click in enumerate(self.on_hold_clicks): if click["time"] - time.time() > 0.1: Event(EventTypes.MouseClick, **click) to_kill.append(i) for index in reversed(to_kill): self.on_hold_clicks.pop(index)
def test_event_system_subscripton_queue_works(): subscription = Subscription(EventTypes.Tick) assert subscription.queue is not None and not subscription.queue e = Event(EventTypes.Tick) process() assert subscription.queue and subscription.queue.popleft() is e
def tick_forward(): global root_context, Event, EventTypes # Lazy imports: if not root_context: from terminedia import context as root_context if not Event: from terminedia.events import Event, EventTypes current = root_context.ticks = get_current_tick() + 1 # All events have "tick" automatically, but passing it explicitly avoids "get_current_tick" to be called again. Event(EventTypes.Tick, tick=current)
def test_event_system_subscripton_callback_works(): callback_called = False def callback(event): nonlocal callback_called callback_called = True subscription = Subscription(EventTypes.Tick, callback=callback) assert subscription.queue is None and subscription.callback e = Event(EventTypes.Tick) process() assert callback_called assert subscription.queue is None
def _win32_inkey(break_=True, clear=True): """Return currently pressed key as a string Args: - break\_ (bool): Boolean parameter specifying whether "CTRL + C" (\x03) should raise KeyboardInterrupt or be returned as a keycode. Defaults to True. - clear (bool): clears the keyboard buffer contents *Important*: This function only works inside a :any:`keyboard` managed context. (Posix) Code values or code sequences for non-character keys, like ESC, direction arrows, fkeys are kept as constants in the "KeyCodes" class. Unfortunatelly, due to the nature of console streaming, this can't receive "keypress" or "keyup" events, and repeat-rate is not configurable, nor can it detect modifier keys, or simultaneous key-presses. [WIP: latest updates adding support for the event system on the posix side where not reflected here] """ if not msvcrt.kbhit(): return "" code = msvcrt.getwch() if code in "\x00à": # and msvcrt.kbhit(): code += msvcrt.getwch() if list_subscriptions(EventTypes.KeyPress): Event(EventTypes.KeyPress, key=keycode) return code
def inkey(self, break_=True, clear=True, consume=True): """Return currently pressed key as a string This is the implemenation of old 8-bit basic "inkey" and "inkey$" builtins, and also the heart of the keybard input system. Args: - break\_ (bool): Boolean parameter specifying whether "CTRL + C" (\x03) should raise KeyboardInterrupt or be returned as a keycode. Defaults to True. - clear (bool): clears the keyboard buffer contents. If False, queued keyboard codes are returned in order, for each call Otherwise queued codes are discarded and only the last-pressed character if returned. Even when "clear" is True, one keyboard event is generated for each token. Also, note that calling Screen.update will cause this to be called with clear=True, to flush all keypress events. Applications using 'inkey' to read all input should make all the calls between 'updates' defaults to True - consume: remove the received key from keypresses. When using an event-based approach, this function is responsible for dispatching the events, and have to be called. The default behavior, however, will make keypresses go missing when "inkey" is called to read the keyboard with the even system on. TL;DR: the calls to inkey made by the inner event system should pass False to this parameter, otherwise leave it as is. *Important*: This function only works inside a :any:`keyboard` managed context. (Posix) Code values or code sequences for non-character keys, like ESC, direction arrows, fkeys are kept as constants in the "KeyCodes" class. Unfortunatelly, due to the nature of console streaming, this can't receive "keypress" or "keyup" events, and repeat-rate is not configurable, nor can it detect modifier keys, or simultaneous key-presses. """ # In this context, 'token' is either a single-char string, representing an # 'ordinary' keypress or an escape sequence representing a special key or mouse event. if not self.enabled: raise RuntimeError( "keyboard context manager must be entered to enable non-blocking keyboard reads" ) if self.not_consumed and consume: return self.not_consumed.popleft() last_emitted = old_keycode = "" if not clear: buffer = sys.stdin else: # read all buffer, generate events for all tokens # but return only the last event # (so, the caller to "inkey" have info on the last pressed key) buffer = io.StringIO(sys.stdin.read(10000)) buffer.seek(0) while True: keycode, stream_eof = self._scan_code(buffer) if stream_eof and not keycode: keycode = old_keycode if keycode == '\x03' and break_: raise KeyboardInterrupt() if keycode and list_subscriptions(EventTypes.KeyPress): if not (last_emitted == keycode and old_keycode == keycode): Event(EventTypes.KeyPress, key=keycode) last_emitted = keycode if not clear or stream_eof: # next characters will be consumed in next calls break old_keycode = keycode if not consume: self.not_consumed.append(keycode) if mouse.enabled and mouse.on_hold_clicks: mouse.flush_clicks() return keycode
def keys(event): global char if event.key == "\x1b": Event(EventTypes.QuitLoop) return