class ModeModifier(Modifier): COMMAND = "mode" PROFILE_KEYS = ("modes",) MIN_TRIGGER = 2 # When trigger is bellow this position, list of held_triggers is cleared MIN_STICK = 2 # When abs(stick) < MIN_STICK, stick is considered released and held_sticks is cleared PROFILE_KEY_PRIORITY = 2 def __init__(self, *stuff): Modifier.__init__(self) self.default = None self.mods = OrderedDict() self.held_buttons = set() self.held_sticks = set() self.held_triggers = {} self.old_action = None self.timeout = DoubleclickModifier.DEAFAULT_TIMEOUT button = None for i in stuff: if self.default is not None: # Default has to be last parameter raise ValueError("Invalid parameters for 'mode'") if isinstance(i, Action) and button is None: self.default = i elif isinstance(i, Action): self.mods[button] = i button = None elif isinstance(i, RangeOP) or i in SCButtons: button = i else: raise ValueError("Invalid parameter for 'mode': %s" % (i,)) self.make_checks() if self.default is None: self.default = NoAction() def make_checks(self): self.checks = [] for button, action in self.mods.items(): if isinstance(button, RangeOP): self.checks.append(( button, action )) else: self.checks.append(( self.make_button_check(button), action )) def get_child_actions(self): if self.default is None: return self.mods.values() else: return [ self.default ] + self.mods.values() @staticmethod def decode(data, a, parser, *b): args = [] for button in data[ModeModifier.PROFILE_KEYS[0]]: if hasattr(SCButtons, button): args += [ getattr(SCButtons, button), parser.from_json_data(data[ModeModifier.PROFILE_KEYS[0]][button]) ] if a: args += [ a ] mm = ModeModifier(*args) if "name" in data: mm.name = data["name"] return mm def get_compatible_modifiers(self): rv = 0 for action in self.mods.values(): rv |= action.get_compatible_modifiers() if self.default: rv |= self.default.get_compatible_modifiers() return rv def strip(self): # Returns default action or action assigned to first modifier if self.default: return self.default.strip() if len(self.mods): return self.mods.values()[0].strip() # Empty ModeModifier return NoAction() def compress(self): if self.default: self.default = self.default.compress() for check in self.mods: self.mods[check] = self.mods[check].compress() self.make_checks() return self def __str__(self): rv = [ ] for check in self.mods: rv += [ nameof(check), self.mods[check] ] if self.default is not None: rv += [ self.default ] return "<Modifier '%s', %s>" % (self.COMMAND, rv) __repr__ = __str__ def describe(self, context): if self.name: return self.name l = [] if self.default : l.append(self.default) for check in self.mods: l.append(self.mods[check]) return "\n".join([ x.describe(context) for x in l ]) def to_string(self, multiline=False, pad=0): if multiline: rv = [ (" " * pad) + "mode(" ] for check in self.mods: a_str = NameModifier.unstrip(self.mods[check]).to_string(True).split("\n") a_str[0] = (" " * pad) + " " + (nameof(check) + ",").ljust(11) + a_str[0] # Key has to be one of SCButtons for i in xrange(1, len(a_str)): a_str[i] = (" " * pad) + " " + a_str[i] a_str[-1] = a_str[-1] + "," rv += a_str if self.default is not None: a_str = [ (" " * pad) + " " + x for x in NameModifier.unstrip(self.default).to_string(True).split("\n") ] rv += a_str if rv[-1][-1] == ",": rv[-1] = rv[-1][0:-1] rv += [ (" " * pad) + ")" ] return "\n".join(rv) else: rv = [ ] for check in self.mods: rv += [ nameof(check), NameModifier.unstrip(self.mods[check]).to_string(False) ] if self.default is not None: rv += [ NameModifier.unstrip(self.default).to_string(False) ] return "mode(" + ", ".join(rv) + ")" def cancel(self, mapper): for action in self.mods.values(): action.cancel(mapper) self.default.cancel(mapper) def select(self, mapper): """ Selects action by pressed button. """ for check, action in self.checks: if check(mapper): return action return self.default def select_w_check(self, mapper): """ As select, but returns matched check as well. """ for check, action in self.checks: if check(mapper): return check, action return lambda *a:True, self.default @staticmethod def make_button_check(button): def cb(mapper): return mapper.is_pressed(button) cb.name = button.name # So nameof() still works on keys in self.mods return cb def button_press(self, mapper): sel = self.select(mapper) self.held_buttons.add(sel) return sel.button_press(mapper) def button_release(self, mapper): # Releases all held buttons, not just button that matches # currently pressed modifier for b in self.held_buttons: b.button_release(mapper) def trigger(self, mapper, position, old_position): if position < ModeModifier.MIN_TRIGGER: for b in self.held_triggers: b.trigger(mapper, 0, self.held_triggers[b]) self.held_triggers = {} return False else: sel = self.select(mapper) self.held_triggers[sel] = position return sel.trigger(mapper, position, old_position) def axis(self, mapper, position, what): return self.select(mapper).axis(mapper, position, what) def gyro(self, mapper, pitch, yaw, roll, *q): sel = self.select(mapper) if sel is not self.old_action: if self.old_action: self.old_action.gyro(mapper, 0, 0, 0, *q) self.old_action = sel return sel.gyro(mapper, pitch, yaw, roll, *q) def pad(self, mapper, position, what): return self.select(mapper).pad(mapper, position, what) def whole(self, mapper, x, y, what): if what == STICK: if abs(x) < ModeModifier.MIN_STICK and abs(y) < ModeModifier.MIN_STICK: for check, action in self.held_sticks: action.whole(mapper, 0, 0, what) self.held_sticks.clear() else: ac, active = self.select_w_check(mapper) self.held_sticks.add(( ac, active )) for check, action in list(self.held_sticks): if check == ac or check(mapper): action.whole(mapper, x, y, what) else: action.whole(mapper, 0, 0, what) self.held_sticks.remove(( check, action )) mapper.force_event.add(FE_STICK) else: sel = self.select(mapper) if sel is not self.old_action: mapper.set_button(what, False) if self.old_action: self.old_action.whole(mapper, 0, 0, what) self.old_action = sel rv = sel.whole(mapper, x, y, what) mapper.set_button(what, True) return rv else: return sel.whole(mapper, x, y, what)
class ModeModifier(Modifier): COMMAND = "mode" PROFILE_KEYS = ("modes",) MIN_TRIGGER = 2 # When trigger is bellow this position, list of held_triggers is cleared MIN_STICK = 2 # When abs(stick) < MIN_STICK, stick is considered released and held_sticks is cleared PROFILE_KEY_PRIORITY = 2 def __init__(self, *stuff): # TODO: Better documentation for this. For now, using shell # TODO: and range as condition is not documented Modifier.__init__(self) self.default = None self.mods = OrderedDict() self.held_buttons = set() self.held_sticks = set() self.held_triggers = {} self.old_action = None self.shell_commands = {} self.shell_timeout = 0.5 self.timeout = DoubleclickModifier.DEAFAULT_TIMEOUT # ShellCommandAction cannot be imported normally, it would create # import cycle of hell ShellCommandAction = Action.ALL['shell'] button = None for i in stuff: if self.default is not None: # Default has to be last parameter raise ValueError("Invalid parameters for 'mode'") if isinstance(i, ShellCommandAction) and button is None: # 'shell' can be used instead of button button = i elif isinstance(i, Action) and button is None: self.default = i elif isinstance(i, Action): self.mods[button] = i button = None elif isinstance(i, RangeOP) or i in SCButtons: button = i else: raise ValueError("Invalid parameter for 'mode': %s" % (i,)) self.make_checks() if self.default is None: if isinstance(button, ShellCommandAction): self.default = button else: self.default = NoAction() def make_checks(self): self.checks = [] self.shell_commands = {} ShellCommandAction = Action.ALL['shell'] for c, action in self.mods.items(): if isinstance(c, RangeOP): self.checks.append(( c, action )) elif isinstance(c, ShellCommandAction): self.shell_commands[c.command] = c self.checks.append(( self.make_shell_check(c), action )) else: self.checks.append(( self.make_button_check(c), action )) def get_child_actions(self): rv = list(self.mods.values()) + list(self.shell_commands.values()) if self.default is not None: rv += [ self.default ] return rv @staticmethod def decode(data, a, parser, *b): args = [] for button in data[ModeModifier.PROFILE_KEYS[0]]: if hasattr(SCButtons, button): args += [ getattr(SCButtons, button), parser.from_json_data(data[ModeModifier.PROFILE_KEYS[0]][button]) ] if a: args += [ a ] mm = ModeModifier(*args) if "name" in data: mm.name = data["name"] return mm def get_compatible_modifiers(self): rv = 0 for action in self.mods.values(): rv |= action.get_compatible_modifiers() if self.default: rv |= self.default.get_compatible_modifiers() return rv def strip(self): # Returns default action or action assigned to first modifier if self.default: return self.default.strip() if len(self.mods): return self.mods.values()[0].strip() # Empty ModeModifier return NoAction() def compress(self): if self.default: self.default = self.default.compress() for check in self.mods: self.mods[check] = self.mods[check].compress() self.make_checks() return self def __str__(self): rv = [ ] for check in self.mods: rv += [ nameof(check), self.mods[check] ] if self.default is not None: rv += [ self.default ] return "<Modifier '%s', %s>" % (self.COMMAND, rv) __repr__ = __str__ def describe(self, context): if self.name: return self.name l = [] if self.default : l.append(self.default) for check in self.mods: l.append(self.mods[check]) return "\n".join([ x.describe(context) for x in l ]) def to_string(self, multiline=False, pad=0): if multiline: rv = [ (" " * pad) + "mode(" ] for check in self.mods: a_str = NameModifier.unstrip(self.mods[check]).to_string(True).split("\n") a_str[0] = (" " * pad) + " " + (nameof(check) + ",").ljust(11) + a_str[0] # Key has to be one of SCButtons for i in xrange(1, len(a_str)): a_str[i] = (" " * pad) + " " + a_str[i] a_str[-1] = a_str[-1] + "," rv += a_str if self.default is not None: a_str = [ (" " * pad) + " " + x for x in NameModifier.unstrip(self.default).to_string(True).split("\n") ] rv += a_str if rv[-1][-1] == ",": rv[-1] = rv[-1][0:-1] rv += [ (" " * pad) + ")" ] return "\n".join(rv) else: rv = [ ] for check in self.mods: rv += [ nameof(check), NameModifier.unstrip(self.mods[check]).to_string(False) ] if self.default is not None: rv += [ NameModifier.unstrip(self.default).to_string(False) ] return "mode(" + ", ".join(rv) + ")" def cancel(self, mapper): for action in self.mods.values(): action.cancel(mapper) self.default.cancel(mapper) def select(self, mapper): """ Selects action by pressed button. """ for check, action in self.checks: if check(mapper): return action return self.default def select_w_check(self, mapper): """ As select, but returns matched check as well. """ for check, action in self.checks: if check(mapper): return check, action return lambda *a:True, self.default @staticmethod def make_button_check(button): def cb(mapper): return mapper.is_pressed(button) cb.name = button.name # So nameof() still works on keys in self.mods return cb @staticmethod def make_shell_check(c): def cb(mapper): try: return c.__proc.poll() == 0 except: return False c.name = cb.name = c.to_string() # So nameof() still works on keys in self.mods c.__proc = None return cb def button_press(self, mapper): if len(self.shell_commands) > 0: # https://github.com/kozec/sc-controller/issues/427 # If 'shell' is used as any condition, all shell commands # are executed and ModeShift waits up to 500ms for them # to terminate. Then, if command returned zero exit code # it's considered as 'true' condition. for c in self.shell_commands.values(): c.__proc = c.button_press(mapper) self.shell_timeout = 0.5 mapper.schedule(0, self.check_shell_commands) return sel = self.select(mapper) self.held_buttons.add(sel) return sel.button_press(mapper) def check_shell_commands(self, mapper): for c in self.shell_commands.values(): if c.__proc and c.__proc.poll() == 0: sel = self.select(mapper) self.kill_shell_commands() self.held_buttons.add(sel) return sel.button_press(mapper) self.shell_timeout -= 0.05 if self.shell_timeout > 0: mapper.schedule(0.05, self.check_shell_commands) else: # time is up, kill all processes and execute what's left self.kill_shell_commands() sel = self.select(mapper) self.held_buttons.add(sel) return sel.button_press(mapper) def kill_shell_commands(self): for c in self.shell_commands.values(): try: if c.__proc: c.__proc.kill() except: pass c.__proc = None def button_release(self, mapper): # Releases all held buttons, not just button that matches # currently pressed modifier for b in self.held_buttons: b.button_release(mapper) def trigger(self, mapper, position, old_position): if position < ModeModifier.MIN_TRIGGER: for b in self.held_triggers: b.trigger(mapper, 0, self.held_triggers[b]) self.held_triggers = {} return False else: sel = self.select(mapper) self.held_triggers[sel] = position return sel.trigger(mapper, position, old_position) def axis(self, mapper, position, what): return self.select(mapper).axis(mapper, position, what) def gyro(self, mapper, pitch, yaw, roll, *q): sel = self.select(mapper) if sel is not self.old_action: if self.old_action: self.old_action.gyro(mapper, 0, 0, 0, *q) self.old_action = sel return sel.gyro(mapper, pitch, yaw, roll, *q) def pad(self, mapper, position, what): return self.select(mapper).pad(mapper, position, what) def whole(self, mapper, x, y, what): if what == STICK: if abs(x) < ModeModifier.MIN_STICK and abs(y) < ModeModifier.MIN_STICK: for check, action in self.held_sticks: action.whole(mapper, 0, 0, what) self.held_sticks.clear() else: ac, active = self.select_w_check(mapper) self.held_sticks.add(( ac, active )) for check, action in list(self.held_sticks): if check == ac or check(mapper): action.whole(mapper, x, y, what) else: action.whole(mapper, 0, 0, what) self.held_sticks.remove(( check, action )) mapper.force_event.add(FE_STICK) else: sel = self.select(mapper) if sel is not self.old_action: mapper.set_button(what, False) if self.old_action: self.old_action.whole(mapper, 0, 0, what) self.old_action = sel rv = sel.whole(mapper, x, y, what) mapper.set_button(what, True) return rv else: return sel.whole(mapper, x, y, what)
class Modifier(Action): def __init__(self, *params): Action.__init__(self, *params) params = list(params) for p in params: if isinstance(p, Action): self.action = p params.remove(p) break else: self.action = NoAction() self._mod_init(*params) def get_compatible_modifiers(self): return self.action.get_compatible_modifiers() def cancel(self, mapper): self.action.cancel(mapper) def get_child_actions(self): return (self.action, ) def _mod_init(self): """ Initializes modifier with rest of parameters, after action parameter was taken from it and stored in self.action """ pass # not needed by default def _mod_to_string(self, params, multiline, pad): """ Adds action at end of params list and generates string """ if multiline: childstr = self.action.to_string(True, pad + 2) if len(params) > 0: return "%s%s(%s,%s%s)" % ( " " * pad, self.COMMAND, ", ".join([ nameof(s) for s in params ]), '\n' if '\n' in childstr else ' ', childstr ) return "%s%s(%s)" % ( " " * pad, self.COMMAND, childstr.strip() ) childstr = self.action.to_string(False, pad) if len(params) > 0: return "%s%s(%s, %s)" % ( " " * pad, self.COMMAND, ", ".join([ nameof(s) for s in params ]), childstr ) return "%s%s(%s)" % ( " " * pad, self.COMMAND, childstr ) def strip_defaults(self): """ Overrides Action.strip_defaults; Uses defaults from _mod_init instead of __init__, but does NOT include last of original parameters - action. """ argspec = inspect.getargspec(self.__class__._mod_init) required_count = len(argspec.args) - len(argspec.defaults) - 1 l = list(self.parameters[0:-1]) d = list(argspec.defaults)[0:len(l)] while len(d) and len(l) > required_count and d[-1] == l[-1]: d, l = d[:-1], l[:-1] return l def strip(self): return self.action.strip() def compress(self): if self.action: self.action = self.action.compress() return self def __str__(self): return "<Modifier '%s', %s>" % (self.COMMAND, self.action) __repr__ = __str__