def start_capture(self): """Begin listening for output from the stenotype machine.""" self._initializing() try: self._keyboard_capture = KeyboardCapture() self._keyboard_capture.key_down = self._key_down self._keyboard_capture.key_up = self._key_up self._suppress() self._keyboard_capture.start() except: self._error() raise self._ready()
def start_capture(self): """Begin listening for output from the stenotype machine.""" self._released_keys.clear() self._last_stroke_key_down_count = 0 self._initializing() try: self._keyboard_capture = KeyboardCapture() self._keyboard_capture.key_down = self._key_down self._keyboard_capture.key_up = self._key_up self._keyboard_capture.start() except: self._error() raise self._ready()
class EditKeysDialog(wx.Dialog): def __init__(self, parent, action, keys): super(EditKeysDialog, self).__init__(parent) self.sizer = wx.BoxSizer(wx.VERTICAL) instructions = wx.StaticText( self, label='Press on the key you want to add/remove.') self.sizer.Add(instructions, border=UI_BORDER, flag=wx.ALL | wx.ALIGN_CENTER_HORIZONTAL) self.message = wx.StaticText(self) self.sizer.Add(self.message, border=UI_BORDER, flag=wx.ALL | wx.ALIGN_CENTER_HORIZONTAL) buttons = self.CreateButtonSizer(wx.OK | wx.CANCEL) self.sizer.Add(buttons, border=UI_BORDER, flag=wx.ALL | wx.ALIGN_CENTER_HORIZONTAL) self.SetSizer(self.sizer) self.sizer.Fit(self) self.action = action self.keys = set(keys) self.original_keys = self.keys.copy() self.capture = KeyboardCapture(KeyboardCapture.SUPPORTED_KEYS) self.capture.key_down = lambda key: wx.CallAfter( self.on_capture_key, key) def ShowModal(self): self.update_message() self.capture.start() self.capture.suppress_keyboard(True) try: code = super(EditKeysDialog, self).ShowModal() finally: self.capture.suppress_keyboard(False) self.capture.cancel() return code def update_message(self): message = '\nKeys for %s: ' % self.action message += ' '.join(sorted(self.keys)) if self.keys else 'None' message += '\n\nChanges: ' changes = [] for key in sorted(self.keys.union(self.original_keys)): if key not in self.original_keys: changes.append('+' + key) elif key not in self.keys: changes.append('-' + key) message += ' '.join(changes) if changes else 'None' message += '\n' self.message.SetLabelText(message) self.sizer.Fit(self) self.sizer.Layout() def on_capture_key(self, key): if key in self.keys: self.keys.remove(key) else: self.keys.add(key) self.update_message()
class EditKeysDialog(wx.Dialog): def __init__(self, parent, action, keys): super(EditKeysDialog, self).__init__(parent) self.sizer = wx.BoxSizer(wx.VERTICAL) instructions = wx.StaticText(self, label='Press on the key you want to add/remove.') self.sizer.Add(instructions, border=UI_BORDER, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL) self.message = wx.StaticText(self) self.sizer.Add(self.message, border=UI_BORDER, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL) buttons = self.CreateButtonSizer(wx.OK|wx.CANCEL) self.sizer.Add(buttons, border=UI_BORDER, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL) self.SetSizer(self.sizer) self.sizer.Fit(self) self.action = action self.keys = set(keys) self.original_keys = self.keys.copy() self.capture = KeyboardCapture(KeyboardCapture.SUPPORTED_KEYS) self.capture.key_down = lambda key: wx.CallAfter(self.on_capture_key, key) def ShowModal(self): self.update_message() self.capture.start() self.capture.suppress_keyboard(True) try: code = super(EditKeysDialog, self).ShowModal() finally: self.capture.suppress_keyboard(False) self.capture.cancel() return code def update_message(self): message = '\nKeys for %s: ' % self.action message += ' '.join(sorted(self.keys)) if self.keys else 'None' message += '\n\nChanges: ' changes = [] for key in sorted(self.keys.union(self.original_keys)): if key not in self.original_keys: changes.append('+' + key) elif key not in self.keys: changes.append('-' + key) message += ' '.join(changes) if changes else 'None' message += '\n' self.message.SetLabelText(message) self.sizer.Fit(self) self.sizer.Layout() def on_capture_key(self, key): if key in self.keys: self.keys.remove(key) else: self.keys.add(key) self.update_message()
def __init__(self, parent, action, keys): super(EditKeysDialog, self).__init__(parent) self.sizer = wx.BoxSizer(wx.VERTICAL) sizer_flags = wx.SizerFlags().Border(wx.ALL, UI_BORDER).Center() instructions = wx.StaticText( self, label='Press on the key you want to add/remove.') self.sizer.AddF(instructions, sizer_flags) self.message = wx.StaticText(self) self.sizer.AddF(self.message, sizer_flags) buttons = self.CreateButtonSizer(wx.OK | wx.CANCEL) clear_button = wx.Button(self, id=wx.ID_CLEAR) clear_button.Bind(wx.EVT_BUTTON, self.on_clear) buttons.InsertF(0, clear_button, sizer_flags.Left()) self.sizer.AddF(buttons, sizer_flags.Expand()) self.SetSizerAndFit(self.sizer) self.action = action self.keys = set(keys) self.original_keys = self.keys.copy() self.capture = KeyboardCapture() self.capture.key_down = lambda key: wx.CallAfter( self.on_capture_key, key)
class EditKeysDialog(wx.Dialog): def __init__(self, parent, action, keys): super(EditKeysDialog, self).__init__(parent) self.sizer = wx.BoxSizer(wx.VERTICAL) sizer_flags = wx.SizerFlags().Border(wx.ALL, UI_BORDER).Center() instructions = wx.StaticText(self, label='Press on the key you want to add/remove.') self.sizer.AddF(instructions, sizer_flags) self.message = wx.StaticText(self) self.sizer.AddF(self.message, sizer_flags) buttons = self.CreateButtonSizer(wx.OK|wx.CANCEL) clear_button = wx.Button(self, id=wx.ID_CLEAR) clear_button.Bind(wx.EVT_BUTTON, self.on_clear) buttons.InsertF(0, clear_button, sizer_flags.Left()) self.sizer.AddF(buttons, sizer_flags.Expand()) self.SetSizerAndFit(self.sizer) self.action = action self.keys = set(keys) self.original_keys = self.keys.copy() self.capture = KeyboardCapture() self.capture.key_down = lambda key: wx.CallAfter(self.on_capture_key, key) def ShowModal(self): self.update_message() self.capture.start() try: # Prevent dialog from stealing some key events. self.capture.suppress_keyboard(('space', 'Escape', 'Return', 'Tab')) code = super(EditKeysDialog, self).ShowModal() finally: self.capture.cancel() return code def update_message(self): message = '\nKeys for %s: ' % self.action message += ' '.join(sorted(self.keys)) if self.keys else 'None' message += '\n\nChanges: ' changes = [] for key in sorted(self.keys.union(self.original_keys)): if key not in self.original_keys: changes.append('+' + key) elif key not in self.keys: changes.append('-' + key) message += ' '.join(changes) if changes else 'None' message += '\n' self.message.SetLabelText(message) self.sizer.Fit(self) self.sizer.Layout() def on_clear(self, event): self.keys.clear() self.update_message() def on_capture_key(self, key): if key in self.keys: self.keys.remove(key) else: self.keys.add(key) self.update_message()
def __init__(self, parent, action, keys): super(EditKeysDialog, self).__init__(parent) self.sizer = wx.BoxSizer(wx.VERTICAL) instructions = wx.StaticText( self, label='Press on the key you want to add/remove.') self.sizer.Add(instructions, border=UI_BORDER, flag=wx.ALL | wx.ALIGN_CENTER_HORIZONTAL) self.message = wx.StaticText(self) self.sizer.Add(self.message, border=UI_BORDER, flag=wx.ALL | wx.ALIGN_CENTER_HORIZONTAL) buttons = self.CreateButtonSizer(wx.OK | wx.CANCEL) self.sizer.Add(buttons, border=UI_BORDER, flag=wx.ALL | wx.ALIGN_CENTER_HORIZONTAL) self.SetSizer(self.sizer) self.sizer.Fit(self) self.action = action self.keys = set(keys) self.original_keys = self.keys.copy() self.capture = KeyboardCapture(KeyboardCapture.SUPPORTED_KEYS) self.capture.key_down = lambda key: wx.CallAfter( self.on_capture_key, key)
def __init__(self, parent, action, keys): super(EditKeysDialog, self).__init__(parent) self.sizer = wx.BoxSizer(wx.VERTICAL) instructions = wx.StaticText(self, label='Press on the key you want to add/remove.') self.sizer.Add(instructions, border=UI_BORDER, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL) self.message = wx.StaticText(self) self.sizer.Add(self.message, border=UI_BORDER, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL) buttons = self.CreateButtonSizer(wx.OK|wx.CANCEL) self.sizer.Add(buttons, border=UI_BORDER, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL) self.SetSizer(self.sizer) self.sizer.Fit(self) self.action = action self.keys = set(keys) self.original_keys = self.keys.copy() self.capture = KeyboardCapture() self.capture.key_down = lambda key: wx.CallAfter(self.on_capture_key, key)
def __init__(self, parent, action, keys): super(EditKeysDialog, self).__init__(parent) self.sizer = wx.BoxSizer(wx.VERTICAL) sizer_flags = wx.SizerFlags().Border(wx.ALL, UI_BORDER).Center() instructions = wx.StaticText(self, label='Press on the key you want to add/remove.') self.sizer.AddF(instructions, sizer_flags) self.message = wx.StaticText(self) self.sizer.AddF(self.message, sizer_flags) buttons = self.CreateButtonSizer(wx.OK|wx.CANCEL) clear_button = wx.Button(self, id=wx.ID_CLEAR) clear_button.Bind(wx.EVT_BUTTON, self.on_clear) buttons.InsertF(0, clear_button, sizer_flags.Left()) self.sizer.AddF(buttons, sizer_flags.Expand()) self.SetSizerAndFit(self.sizer) self.action = action self.keys = set(keys) self.original_keys = self.keys.copy() self.capture = KeyboardCapture() self.capture.key_down = lambda key: wx.CallAfter(self.on_capture_key, key)
def __init__(self, params): """Monitor the keyboard's events.""" StenotypeBase.__init__(self) self.arpeggiate = params['arpeggiate'] self.keymap = params['keymap'].to_dict() self._arpeggiate_key = None for key, mapping in self.keymap.items(): if 'no-op' == mapping: self.keymap[key] = None if 'arpeggiate' == mapping: if self.arpeggiate: self.keymap[key] = None self._arpeggiate_key = key else: # Don't suppress arpeggiate key if it's not used. del self.keymap[key] self._down_keys = set() self._released_keys = set() self._keyboard_capture = KeyboardCapture() self._keyboard_capture.key_down = self._key_down self._keyboard_capture.key_up = self._key_up self._last_stroke_key_down_count = 0
class Keyboard(StenotypeBase): """Standard stenotype interface for a computer keyboard. This class implements the three methods necessary for a standard stenotype interface: start_capture, stop_capture, and add_callback. """ KEYS_LAYOUT = KeyboardCapture.SUPPORTED_KEYS_LAYOUT ACTIONS = ('arpeggiate',) def __init__(self, params): """Monitor the keyboard's events.""" super(Keyboard, self).__init__() self._arpeggiate = params['arpeggiate'] self._is_suppressed = False self._bindings = {} self._down_keys = set() self._released_keys = set() self._keyboard_capture = None self._last_stroke_key_down_count = 0 self._stroke_key_down_count = 0 self._update_bindings() def _suppress(self): if self._keyboard_capture is None: return suppressed_keys = self._bindings.keys() if self._is_suppressed else () self._keyboard_capture.suppress_keyboard(suppressed_keys) def _update_bindings(self): self._bindings = dict(self.keymap.get_bindings()) for key, mapping in list(self._bindings.items()): if 'no-op' == mapping: self._bindings[key] = None elif 'arpeggiate' == mapping: if self._arpeggiate: self._bindings[key] = None self._arpeggiate_key = key else: # Don't suppress arpeggiate key if it's not used. del self._bindings[key] self._suppress() def set_keymap(self, keymap): super(Keyboard, self).set_keymap(keymap) self._update_bindings() def start_capture(self): """Begin listening for output from the stenotype machine.""" self._released_keys.clear() self._stroke_key_down_count = 0 self._initializing() try: self._keyboard_capture = KeyboardCapture() self._keyboard_capture.key_down = self._key_down self._keyboard_capture.key_up = self._key_up self._suppress() self._keyboard_capture.start() except: self._error() raise self._ready() def stop_capture(self): """Stop listening for output from the stenotype machine.""" if self._keyboard_capture is not None: self._is_suppressed = False self._suppress() self._keyboard_capture.cancel() self._keyboard_capture = None self._stopped() def set_suppression(self, enabled): self._is_suppressed = enabled self._suppress() def suppress_last_stroke(self, send_backspaces): send_backspaces(self._last_stroke_key_down_count) self._last_stroke_key_down_count = 0 def _key_down(self, key): """Called when a key is pressed.""" assert key is not None if key in self._bindings: self._stroke_key_down_count += 1 steno_key = self._bindings.get(key) if steno_key is not None: self._down_keys.add(steno_key) def _key_up(self, key): """Called when a key is released.""" assert key is not None steno_key = self._bindings.get(key) if steno_key is not None: # Process the newly released key. self._released_keys.add(steno_key) # Remove invalid released keys. self._released_keys = self._released_keys.intersection(self._down_keys) # A stroke is complete if all pressed keys have been released. # If we are in arpeggiate mode then only send stroke when spacebar is pressed. send_strokes = bool(self._down_keys and self._down_keys == self._released_keys) if self._arpeggiate: send_strokes &= key == self._arpeggiate_key if send_strokes: self._last_stroke_key_down_count = self._stroke_key_down_count steno_keys = list(self._down_keys) self._down_keys.clear() self._released_keys.clear() self._stroke_key_down_count = 0 self._notify(steno_keys) @classmethod def get_option_info(cls): bool_converter = lambda s: s == 'True' return { 'arpeggiate': (False, bool_converter), }
class Keyboard(StenotypeBase): """Standard stenotype interface for a computer keyboard. This class implements the three methods necessary for a standard stenotype interface: start_capture, stop_capture, and add_callback. """ KEYS_LAYOUT = KeyboardCapture.SUPPORTED_KEYS_LAYOUT ACTIONS = ('arpeggiate',) def __init__(self, params): """Monitor the keyboard's events.""" super(Keyboard, self).__init__() self._arpeggiate = params['arpeggiate'] self._is_suppressed = False # Currently held keys. self._down_keys = set() # All keys part of the stroke. self._stroke_keys = set() self._keyboard_capture = None self._last_stroke_key_down_count = 0 self._stroke_key_down_count = 0 self._update_bindings() self._key_timeout = None def _suppress(self): if self._keyboard_capture is None: return suppressed_keys = self._bindings.keys() if self._is_suppressed else () self._keyboard_capture.suppress_keyboard(suppressed_keys) def _update_bindings(self): self._arpeggiate_key = None self._bindings = dict(self.keymap.get_bindings()) for key, mapping in list(self._bindings.items()): if 'no-op' == mapping: self._bindings[key] = None elif 'arpeggiate' == mapping: if self._arpeggiate: self._bindings[key] = None self._arpeggiate_key = key else: # Don't suppress arpeggiate key if it's not used. del self._bindings[key] self._suppress() def set_keymap(self, keymap): super(Keyboard, self).set_keymap(keymap) self._update_bindings() def start_capture(self): """Begin listening for output from the stenotype machine.""" self._initializing() try: self._keyboard_capture = KeyboardCapture() self._keyboard_capture.key_down = self._key_down self._keyboard_capture.key_up = self._key_up self._suppress() self._keyboard_capture.start() except: self._error() raise self._ready() def stop_capture(self): """Stop listening for output from the stenotype machine.""" if self._keyboard_capture is not None: self._is_suppressed = False self._suppress() self._keyboard_capture.cancel() self._keyboard_capture = None self._stopped() def set_suppression(self, enabled): self._is_suppressed = enabled self._suppress() def suppress_last_stroke(self, send_backspaces): send_backspaces(self._last_stroke_key_down_count) self._last_stroke_key_down_count = 0 def _key_down(self, key): """Called when a key is pressed.""" assert key is not None self._stroke_key_down_count += 1 self._stroke_keys.add(key) # # HACK emit per keydown if key not in self._down_keys: steno_keys = set(self._bindings.get(k) for k in self._stroke_keys) steno_keys -= {None} if steno_keys: self._notify(steno_keys) self._down_keys.add(key) # # pause = respect keyups if self._key_timeout: self._key_timeout.cancel() def timeout_keys(self): """Keys have been held released for a while""" print('Timing out {} to {}'.format(self._stroke_keys, self._down_keys)) self._stroke_keys = set(self._down_keys) steno_keys = set(self._bindings.get(k) for k in self._stroke_keys) steno_keys -= {None} if steno_keys: self._notify(steno_keys) def _key_up(self, key): """Called when a key is released.""" assert key is not None self._down_keys.discard(key) # # pause = respect keyups if self._key_timeout: self._key_timeout.cancel() # A stroke is complete if all pressed keys have been released, # and — when arpeggiate mode is enabled — the arpeggiate key # is part of it. if ( self._down_keys or not self._stroke_keys or (self._arpeggiate and self._arpeggiate_key not in self._stroke_keys) ): # TODO configure timeout length self._key_timeout = Timer(0.5, self.timeout_keys).start() return self._last_stroke_key_down_count = self._stroke_key_down_count steno_keys = set(self._bindings.get(k) for k in self._stroke_keys) steno_keys -= {None} if steno_keys: steno_keys.add('!') self._notify(steno_keys) self._stroke_keys.clear() self._stroke_key_down_count = 0 @classmethod def get_option_info(cls): bool_converter = lambda s: s == 'True' return { 'arpeggiate': (False, bool_converter), }
class Stenotype(StenotypeBase): """Standard stenotype interface for a computer keyboard. This class implements the three methods necessary for a standard stenotype interface: start_capture, stop_capture, and add_callback. """ KEYS_LAYOUT = KeyboardCapture.SUPPORTED_KEYS_LAYOUT ACTIONS = StenotypeBase.ACTIONS + ('arpeggiate', ) def __init__(self, params): """Monitor the keyboard's events.""" super(Stenotype, self).__init__() self.arpeggiate = params['arpeggiate'] self._bindings = {} self._down_keys = set() self._released_keys = set() self._keyboard_capture = None self._last_stroke_key_down_count = 0 self._update_bindings() def _update_bindings(self): self._bindings = dict(self.keymap.get_bindings()) for key, mapping in self._bindings.items(): if 'no-op' == mapping: self._bindings[key] = None elif 'arpeggiate' == mapping: if self.arpeggiate: self._bindings[key] = None self._arpeggiate_key = key else: # Don't suppress arpeggiate key if it's not used. del self._bindings[key] def set_mappings(self, mappings): super(Stenotype, self).set_mappings(mappings) self._update_bindings() def start_capture(self): """Begin listening for output from the stenotype machine.""" self._released_keys.clear() self._last_stroke_key_down_count = 0 self._initializing() try: self._keyboard_capture = KeyboardCapture() self._keyboard_capture.key_down = self._key_down self._keyboard_capture.key_up = self._key_up self._keyboard_capture.start() except: self._error() raise self._ready() def stop_capture(self): """Stop listening for output from the stenotype machine.""" if self._keyboard_capture is not None: self._keyboard_capture.cancel() self._keyboard_capture = None self._stopped() def set_suppression(self, enabled): suppressed_keys = self._bindings.keys() if enabled else () self._keyboard_capture.suppress_keyboard(suppressed_keys) def suppress_last_stroke(self, send_backspaces): send_backspaces(self._last_stroke_key_down_count) def _key_down(self, key): """Called when a key is pressed.""" assert key is not None if key in self._bindings: self._last_stroke_key_down_count += 1 steno_key = self._bindings.get(key) if steno_key is not None: self._down_keys.add(steno_key) def _key_up(self, key): """Called when a key is released.""" assert key is not None steno_key = self._bindings.get(key) if steno_key is not None: # Process the newly released key. self._released_keys.add(steno_key) # Remove invalid released keys. self._released_keys = self._released_keys.intersection( self._down_keys) # A stroke is complete if all pressed keys have been released. # If we are in arpeggiate mode then only send stroke when spacebar is pressed. send_strokes = bool(self._down_keys and self._down_keys == self._released_keys) if self.arpeggiate: send_strokes &= key == self._arpeggiate_key if send_strokes: steno_keys = list(self._down_keys) if steno_keys: self._down_keys.clear() self._released_keys.clear() self._notify(steno_keys) self._last_stroke_key_down_count = 0 @classmethod def get_option_info(cls): bool_converter = lambda s: s == 'True' return { 'arpeggiate': (False, bool_converter), }
class EditKeysDialog(wx.Dialog): def __init__(self, parent, action, keys): super(EditKeysDialog, self).__init__(parent) self.sizer = wx.BoxSizer(wx.VERTICAL) sizer_flags = wx.SizerFlags().Border(wx.ALL, UI_BORDER).Center() instructions = wx.StaticText( self, label='Press on the key you want to add/remove.') self.sizer.AddF(instructions, sizer_flags) self.message = wx.StaticText(self) self.sizer.AddF(self.message, sizer_flags) buttons = self.CreateButtonSizer(wx.OK | wx.CANCEL) clear_button = wx.Button(self, id=wx.ID_CLEAR) clear_button.Bind(wx.EVT_BUTTON, self.on_clear) buttons.InsertF(0, clear_button, sizer_flags.Left()) self.sizer.AddF(buttons, sizer_flags.Expand()) self.SetSizerAndFit(self.sizer) self.action = action self.keys = set(keys) self.original_keys = self.keys.copy() self.capture = KeyboardCapture() self.capture.key_down = lambda key: wx.CallAfter( self.on_capture_key, key) def ShowModal(self): self.update_message() self.capture.start() try: # Prevent dialog from stealing some key events. self.capture.suppress_keyboard( ('space', 'Escape', 'Return', 'Tab')) code = super(EditKeysDialog, self).ShowModal() finally: self.capture.cancel() return code def update_message(self): message = '\nKeys for %s: ' % self.action message += ' '.join(sorted(self.keys)) if self.keys else 'None' message += '\n\nChanges: ' changes = [] for key in sorted(self.keys.union(self.original_keys)): if key not in self.original_keys: changes.append('+' + key) elif key not in self.keys: changes.append('-' + key) message += ' '.join(changes) if changes else 'None' message += '\n' self.message.SetLabelText(message) self.sizer.Fit(self) self.sizer.Layout() def on_clear(self, event): self.keys.clear() self.update_message() def on_capture_key(self, key): if key in self.keys: self.keys.remove(key) else: self.keys.add(key) self.update_message()
class Stenotype(StenotypeBase): """Standard stenotype interface for a computer keyboard. This class implements the three methods necessary for a standard stenotype interface: start_capture, stop_capture, and add_callback. """ def __init__(self, params): """Monitor the keyboard's events.""" StenotypeBase.__init__(self) self.arpeggiate = params['arpeggiate'] self.keymap = params['keymap'].to_dict() self._arpeggiate_key = None for key, mapping in self.keymap.items(): if 'no-op' == mapping: self.keymap[key] = None if 'arpeggiate' == mapping: if self.arpeggiate: self.keymap[key] = None self._arpeggiate_key = key else: # Don't suppress arpeggiate key if it's not used. del self.keymap[key] self._down_keys = set() self._released_keys = set() self._keyboard_capture = KeyboardCapture() self._keyboard_capture.key_down = self._key_down self._keyboard_capture.key_up = self._key_up self._last_stroke_key_down_count = 0 def start_capture(self): """Begin listening for output from the stenotype machine.""" self._keyboard_capture.start() self._ready() def stop_capture(self): """Stop listening for output from the stenotype machine.""" self._keyboard_capture.cancel() self._stopped() def set_suppression(self, enabled): suppressed_keys = self.keymap.keys() if enabled else () self._keyboard_capture.suppress_keyboard(suppressed_keys) def suppress_last_stroke(self, send_backspaces): send_backspaces(self._last_stroke_key_down_count) def _key_down(self, key): """Called when a key is pressed.""" assert key is not None if key in self.keymap: self._last_stroke_key_down_count += 1 steno_key = self.keymap.get(key) if steno_key is not None: self._down_keys.add(steno_key) def _key_up(self, key): """Called when a key is released.""" assert key is not None steno_key = self.keymap.get(key) if steno_key is not None: # Process the newly released key. self._released_keys.add(steno_key) # Remove invalid released keys. self._released_keys = self._released_keys.intersection(self._down_keys) # A stroke is complete if all pressed keys have been released. # If we are in arpeggiate mode then only send stroke when spacebar is pressed. send_strokes = bool(self._down_keys and self._down_keys == self._released_keys) if self.arpeggiate: send_strokes &= key == self._arpeggiate_key if send_strokes: steno_keys = list(self._down_keys) if steno_keys: self._down_keys.clear() self._released_keys.clear() self._notify(steno_keys) self._last_stroke_key_down_count = 0 @staticmethod def get_option_info(): bool_converter = lambda s: s == 'True' keymap_converter = lambda s: Keymap.from_string(s) return { 'arpeggiate': (False, bool_converter), 'keymap': (Keymap.default(), keymap_converter), }