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 get_child_actions(self): return (self.action, ) def _mod_init(self): """ Initializes modifier with rest of parameters, after action nparameter 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__ def encode(self): rv = self.action.encode() if self.name: rv[NameModifier.COMMAND] = self.name return rv
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" 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 def __init__(self, *stuff): Modifier.__init__(self) self.default = None self.mods = {} self.held_buttons = set() self.held_sticks = set() self.held_triggers = {} self.order = [] self.old_gyro = None 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): if button is None: self.default = i continue self.mods[button] = i self.order.append(button) button = None elif i in SCButtons: button = i else: raise ValueError("Invalid parameter for 'mode': %s" % (i,)) if self.default is None: self.default = NoAction() def set_haptic(self, hapticdata): supports = False if self.default: supports = self.default.set_haptic(hapticdata) or supports for a in self.mods.values(): supports = a.set_haptic(hapticdata) or supports return supports def set_speed(self, x, y, z): supports = False if self.default: supports = self.default.set_speed(x, y, z) or supports for a in self.mods.values(): supports = a.set_speed(x, y, z) or supports return supports def strip(self): # Returns default action or action assigned to first modifier if self.default: return self.default.strip() if len(self.order) > 0: return self.mods[self.order[0]].strip() # Empty ModeModifier return NoAction() def compress(self): if self.default: self.default = self.default.compress() for button in self.mods: self.mods[button] = self.mods[button].compress() return self def __str__(self): rv = [ ] for key in self.mods: rv += [ key.name, self.mods[key] ] if self.default is not None: rv += [ self.default ] return "<Modifier '%s', %s>" % (self.COMMAND, rv) __repr__ = __str__ def describe(self, context): l = [] if self.default : l.append(self.default) for x in self.order: l.append(self.mods[x]) return "\n".join([ x.describe(context) for x in l ]) def to_string(self, multiline=False, pad=0): if multiline: rv = [ (" " * pad) + "mode(" ] for key in self.mods: a_str = self.mods[key].to_string(True).split("\n") a_str[0] = (" " * pad) + " " + (key.name + ",").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 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 key in self.mods: rv += [ key.name, self.mods[key].to_string(False) ] if self.default is not None: rv += [ self.default.to_string(False) ] return "mode(" + ", ".join(rv) + ")" def encode(self): rv = self.default.encode() rv['modes'] = {} for key in self.mods: rv['modes'][key.name] = self.mods[key].encode() if self.name: rv['name'] = self.name return rv def select(self, mapper): """ Selects action by pressed button. """ for b in self.order: if mapper.is_pressed(b): return self.mods[b] return self.default def select_b(self, mapper): """ Same as select but returns button as well. """ for b in self.order: if mapper.is_pressed(b): return b, self.mods[b] return None, self.default 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_gyro: if self.old_gyro: self.old_gyro.gyro(mapper, 0, 0, 0, *q) self.old_gyro = 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 b in self.held_sticks: b.whole(mapper, 0, 0, what) self.held_sticks.clear() else: self.held_sticks.add(self.select(mapper)) for b in self.held_sticks: b.whole(mapper, x, y, what) else: return self.select(mapper).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 Profile(object): VERSION = 1.2 # Current profile version. When loading profile file # with version lower than this, auto-conversion may happen LEFT = LEFT RIGHT = RIGHT WHOLE = WHOLE STICK = STICK GYRO = GYRO X, Y, Z = "X", "Y", "Z" STICK_AXES = {X: "lpad_x", Y: "lpad_y"} LPAD_AXES = STICK_AXES RPAD_AXES = {X: "rpad_x", Y: "rpad_y"} TRIGGERS = [LEFT, RIGHT] def __init__(self, parser): self.parser = parser self.clear() self.filename = None # UI-only values self.is_template = False self.description = "" def save(self, filename): """ Saves profile into file. Returns self """ fileobj = file(filename, "w") self.save_fileobj(fileobj) fileobj.close() return self def save_fileobj(self, fileobj): """ Saves profile into file-like object. Returns self """ data = { "_": (self.description if "\n" not in self.description else self.description.strip("\n").split("\n")), 'buttons': {}, 'stick': self.stick, 'gyro': self.gyro, 'trigger_left': self.triggers[Profile.LEFT], 'trigger_right': self.triggers[Profile.RIGHT], "pad_left": self.pads[Profile.LEFT], "pad_right": self.pads[Profile.RIGHT], "menus": {id: self.menus[id].encode() for id in self.menus}, "is_template": self.is_template, "version": Profile.VERSION, } for i in self.buttons: if self.buttons[i]: data['buttons'][i.name] = self.buttons[i] # Generate & save json jstr = Encoder(sort_keys=True, indent=4).encode(data) fileobj.write(jstr) return self def load(self, filename): """ Loads profile from file. Returns self """ fileobj = open(filename, "r") self.load_fileobj(fileobj) self.filename = filename return self def load_fileobj(self, fileobj): """ Loads profile from file-like object. Filename attribute is not set, what may cause some trouble if used in GUI. Returns self. """ data = json.loads(fileobj.read()) # Version try: version = int(data["version"]) except: version = 0 # Settings - Description # (stored in key "_", so it's serialized on top of JSON file) if "_" not in data: self.description = "" elif type(data["_"]) == list: self.description = "\n".join(data["_"]) else: self.description = data["_"] # Settings - Template self.is_template = bool( data["is_template"]) if "is_template" in data else False # Buttons self.buttons = {} for x in SCButtons: self.buttons[x] = self.parser.from_json_data( data["buttons"], x.name) # Pressing stick is interpreted as STICKPRESS button, # formely called just STICK if "STICK" in data["buttons"] and "STICKPRESS" not in data["buttons"]: self.buttons[SCButtons.STICKPRESS] = self.parser.from_json_data( data["buttons"], "STICK") # Stick & gyro self.stick = self.parser.from_json_data(data, "stick") self.gyro = self.parser.from_json_data(data, "gyro") if "triggers" in data: # Old format # Triggers self.triggers = ({ x: self.parser.from_json_data(data["triggers"], x) for x in Profile.TRIGGERS }) # Pads self.pads = { Profile.LEFT: self.parser.from_json_data(data, "left_pad"), Profile.RIGHT: self.parser.from_json_data(data, "right_pad"), } else: # New format # Triggers self.triggers = { Profile.LEFT: self.parser.from_json_data(data, "trigger_left"), Profile.RIGHT: self.parser.from_json_data(data, "trigger_right"), } # Pads self.pads = { Profile.LEFT: self.parser.from_json_data(data, "pad_left"), Profile.RIGHT: self.parser.from_json_data(data, "pad_right"), } # Menus self.menus = {} if "menus" in data: for id in data["menus"]: for invalid_char in ".:/": if invalid_char in id: raise ValueError( "Invalid character '%s' in menu id '%s'" % (invalid_char, id)) self.menus[id] = MenuData.from_json_data( data["menus"][id], self.parser) # Conversion if version < Profile.VERSION: self._convert(version) return self def clear(self): """ Clears all actions and adds default menu action on center button """ self.buttons = {x: NoAction() for x in SCButtons} self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction=MenuAction("Default.menu")) self.menus = {} self.stick = NoAction() self.is_template = False self.triggers = {Profile.LEFT: NoAction(), Profile.RIGHT: NoAction()} self.pads = {Profile.LEFT: NoAction(), Profile.RIGHT: NoAction()} self.gyro = NoAction() def get_all_actions(self): """ Returns generator with every action defined in this profile, including actions in menus. Recursively walks into macros, dpads and everything else that can have nested actions, so both parent and all child actions are yielded. May yield NoAction, but shouldn't yield None. Used for checks when profile is exported or imported. """ for dct in (self.buttons, self.triggers, self.pads): for k in dct: for i in dct[k].get_all_actions(): yield i for action in (self.stick, self.gyro): for i in action.get_all_actions(): yield i for id in self.menus: for i in self.menus[id].get_all_actions(): yield i def get_filename(self): """ Returns filename of last loaded file or None. """ return self.filename def compress(self): """ Calls compress on every action to throw out some redundant stuff. Note that calling save() after compress() will break stuff. """ for dct in (self.buttons, self.triggers, self.pads): for x in dct: dct[x] = dct[x].compress() self.stick = self.stick.compress() self.gyro = self.gyro.compress() for menu in self.menus.values(): menu.compress() def _convert(self, from_version): """ Performs conversion from older profile version """ if from_version < 1: from scc.modifiers import ModeModifier # Add 'display Default.menu if center button is held' for old profiles c = self.buttons[SCButtons.C] if not c: # Nothing set to C button self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction=MenuAction("Default.menu")) elif hasattr(c, "holdaction") and c.holdaction: # Already set to something, don't overwrite it pass elif c.to_string().startswith("OSK."): # Special case, don't touch this either pass else: self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction=self.buttons[SCButtons.C]) if from_version < 1.1: # Convert old scrolling wheel to new representation from scc.modifiers import FeedbackModifier, BallModifier from scc.actions import MouseAction, XYAction from scc.uinput import Rels iswheelaction = ( lambda x: isinstance(x, MouseAction) and x.parameters[0] in (Rels.REL_HWHEEL, Rels.REL_WHEEL)) for p in (Profile.LEFT, Profile.RIGHT): a, feedback = self.pads[p], None if isinstance(a, FeedbackModifier): feedback = a.haptic.get_position() a = a.action if isinstance(a, XYAction): if iswheelaction(a.x) or iswheelaction(a.y): n = BallModifier(XYAction(a.x, a.y)) if feedback is not None: n = FeedbackModifier(feedback, 4096, 16, n) self.pads[p] = n log.info("Converted %s to %s", a.to_string(), n.to_string()) if from_version < 1.2: # Convert old trigger settings that were done with ButtonAction # to new TriggerAction from scc.constants import TRIGGER_HALF, TRIGGER_MAX, TRIGGER_CLICK from scc.actions import ButtonAction, TriggerAction, MultiAction from scc.uinput import Keys for p in (Profile.LEFT, Profile.RIGHT): if isinstance(self.triggers[p], ButtonAction): buttons, numbers = [], [] n = None # There were one or two keys and zero to two numeric # parameters for old button action for param in self.triggers[p].parameters: if param in Keys: buttons.append(param) elif type(param) in (int, float): numbers.append(int(param)) if len(numbers) == 0: # Trigger range was not specified, assume defaults numbers = (TRIGGER_HALF, TRIGGER_CLICK) elif len(numbers) == 1: # Only lower range was specified, add default upper range numbers.append(TRIGGER_CLICK) if len(buttons) == 1: # If only one button was set, trigger should work like # one big button n = TriggerAction(numbers[0], ButtonAction(buttons[0])) elif len(buttons) == 2: # Both buttons were set n = MultiAction( TriggerAction(numbers[0], numbers[1], ButtonAction(buttons[0])), TriggerAction(numbers[1], TRIGGER_MAX, ButtonAction(buttons[1]))) if n: log.info("Converted %s to %s", self.triggers[p].to_string(), n.to_string()) self.triggers[p] = n
class DoubleclickModifier(Modifier): COMMAND = "doubleclick" DEAFAULT_TIMEOUT = 0.2 TIMEOUT_KEY = "time" PROFILE_KEY_PRIORITY = 3 def __init__(self, doubleclickaction, normalaction=None, time=None): Modifier.__init__(self) self.action = doubleclickaction self.normalaction = normalaction or NoAction() self.holdaction = NoAction() self.timeout = time or DoubleclickModifier.DEAFAULT_TIMEOUT self.waiting = False self.pressed = False self.active = None def encode(self): if self.normalaction: rv = self.normalaction.encode() else: rv = {} rv[DoubleclickModifier.COMMAND] = self.action.encode() if self.holdaction: rv[HoldModifier.COMMAND] = self.holdaction.encode() if self.timeout != DoubleclickModifier.DEAFAULT_TIMEOUT: rv[DoubleclickModifier.TIMEOUT_KEY] = self.timeout if self.name: rv[NameModifier.COMMAND] = self.name return rv @staticmethod def decode(data, a, parser, *b): args = [ parser.from_json_data(data[DoubleclickModifier.COMMAND]), a ] a = DoubleclickModifier(*args) if DoubleclickModifier.TIMEOUT_KEY in data: a.timeout = data[DoubleclickModifier.TIMEOUT_KEY] return a def strip(self): if self.holdaction: return self.holdaction.strip() return self.action.strip() def compress(self): self.action = self.action.compress() self.holdaction = self.holdaction.compress() self.normalaction = self.normalaction.compress() if isinstance(self.normalaction, DoubleclickModifier): self.action = self.action.compress() or self.normalaction.action.compress() self.holdaction = self.holdaction.compress() or self.normalaction.holdaction.compress() self.normalaction = self.normalaction.normalaction.compress() elif isinstance(self.action, HoldModifier): self.holdaction = self.action.holdaction.compress() self.action = self.action.normalaction.compress() elif isinstance(self.holdaction, DoubleclickModifier): self.action = self.holdaction.action.compress() self.holdaction = self.holdaction.normalaction.compress() elif isinstance(self.holdaction, DoubleclickModifier): self.action = self.action.compress() or self.holdaction.action.compress() self.normalaction = self.normalaction.compress() or self.holdaction.normalaction.compress() self.holdaction = self.holdaction.holdaction.compress() return self def __str__(self): l = [ self.action ] if self.normalaction: l += [ self.normalaction ] return "<Modifier %s dbl='%s' hold='%s' normal='%s'>" % ( self.COMMAND, self.action, self.holdaction, self.normalaction ) __repr__ = __str__ def describe(self, context): l = [ ] if self.action: l += [ self.action ] if self.holdaction: l += [ self.holdaction ] if self.normalaction: l += [ self.normalaction ] return "\n".join([ x.describe(context) for x in l ]) def to_string(self, multiline=False, pad=0): return self._mod_to_string(Action.strip_defaults(self), multiline, pad) def button_press(self, mapper): self.pressed = True if self.waiting: # Double-click happened mapper.remove_scheduled(self.on_timeout) self.waiting = False self.active = self.action self.active.button_press(mapper) else: # First click, start the timer self.waiting = True mapper.schedule(self.timeout, self.on_timeout) def button_release(self, mapper): self.pressed = False if self.waiting and self.active is None and not self.action: # In HoldModifier, button released before timeout mapper.remove_scheduled(self.on_timeout) self.waiting = False if self.normalaction: self.normalaction.button_press(mapper) self.normalaction.button_release(mapper) elif self.active: # Released held button self.active.button_release(mapper) self.active = None def on_timeout(self, mapper, *a): if self.waiting: self.waiting = False if self.pressed: # Timeouted while button is still pressed self.active = self.holdaction if self.holdaction else self.normalaction self.active.button_press(mapper) elif self.normalaction: # User did short click and nothing else self.normalaction.button_press(mapper) self.normalaction.button_release(mapper)
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__
class Profile(object): VERSION = 1.1 # Current profile version. When loading profile file # with version lower than this, auto-conversion may happen LEFT = LEFT RIGHT = RIGHT WHOLE = WHOLE STICK = STICK GYRO = GYRO X, Y, Z = "X", "Y", "Z" STICK_AXES = { X : "lpad_x", Y : "lpad_y" } LPAD_AXES = STICK_AXES RPAD_AXES = { X : "rpad_x", Y : "rpad_y" } TRIGGERS = [ LEFT, RIGHT ] def __init__(self, parser): self.parser = parser self.buttons = { x : NoAction() for x in SCButtons } self.menus = {} self.stick = NoAction() self.triggers = { Profile.LEFT : NoAction(), Profile.RIGHT : NoAction() } self.pads = { Profile.LEFT : NoAction(), Profile.RIGHT : NoAction() } self.gyro = NoAction() def save(self, filename): """ Saves profile into file. Returns self """ data = { 'buttons' : {}, 'stick' : self.stick, 'gyro' : self.gyro, 'trigger_left' : self.triggers[Profile.LEFT], 'trigger_right' : self.triggers[Profile.RIGHT], "pad_left" : self.pads[Profile.LEFT], "pad_right" : self.pads[Profile.RIGHT], "menus" : { id : self.menus[id].encode() for id in self.menus }, "version" : Profile.VERSION } for i in self.buttons: if self.buttons[i]: data['buttons'][i.name] = self.buttons[i] # Generate & save json jstr = Encoder(sort_keys=True, indent=4).encode(data) open(filename, "w").write(jstr) return self def load(self, filename): """ Loads profile from file. Returns self """ data = json.loads(open(filename, "r").read()) # Version try: version = int(data["version"]) except: version = 0 # Buttons self.buttons = {} for x in SCButtons: self.buttons[x] = self.parser.from_json_data(data["buttons"], x.name) # Stick & gyro self.stick = self.parser.from_json_data(data, "stick") self.gyro = self.parser.from_json_data(data, "gyro") if "triggers" in data: # Old format # Triggers self.triggers = ({ x : self.parser.from_json_data(data["triggers"], x) for x in Profile.TRIGGERS }) # Pads self.pads = { Profile.LEFT : self.parser.from_json_data(data, "left_pad"), Profile.RIGHT : self.parser.from_json_data(data, "right_pad"), } else: # New format # Triggers self.triggers = { Profile.LEFT : self.parser.from_json_data(data, "trigger_left"), Profile.RIGHT : self.parser.from_json_data(data, "trigger_right"), } # Pads self.pads = { Profile.LEFT : self.parser.from_json_data(data, "pad_left"), Profile.RIGHT : self.parser.from_json_data(data, "pad_right"), } # Menus self.menus = {} if "menus" in data: for id in data["menus"]: for invalid_char in ".:/": if invalid_char in id: raise ValueError("Invalid character '%s' in menu id '%s'" % (invalid_char, id)) self.menus[id] = MenuData.from_json_data(data["menus"][id], self.parser) # Conversion if version < Profile.VERSION: self._convert(version) return self def compress(self): """ Calls compress on every action to throw out some redundant stuff. Note that calling save() after compress() will break stuff. """ for dct in (self.buttons, self.triggers, self.pads): for x in dct: dct[x] = dct[x].compress() self.stick = self.stick.compress() self.gyro = self.gyro.compress() def _convert(self, from_version): """ Performs conversion from older profile version """ if from_version < 1: from scc.modifiers import ModeModifier, HoldModifier from scc.special_actions import MenuAction # Add 'display Default.menu if center button is held' for old profiles c = self.buttons[SCButtons.C] if not c: # Nothing set to C button self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction = MenuAction("Default.menu") ) elif hasattr(c, "holdaction") and c.holdaction: # Already set to something, don't overwrite it pass elif c.to_string().startswith("OSK."): # Special case, don't touch this either pass else: self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction = self.buttons[SCButtons.C] ) if from_version < 1.1: # Convert old scrolling wheel to new representation from scc.modifiers import FeedbackModifier, BallModifier from scc.actions import MouseAction, XYAction from scc.uinput import Rels iswheelaction = ( lambda x : isinstance(x, MouseAction) and x.parameters[0] in (Rels.REL_HWHEEL, Rels.REL_WHEEL) ) for p in (Profile.LEFT, Profile.RIGHT): a, feedback = self.pads[p], None if isinstance(a, FeedbackModifier): feedback = a.haptic.get_position() a = a.action if isinstance(a, XYAction): if iswheelaction(a.x) or iswheelaction(a.y): n = BallModifier(XYAction(a.x, a.y)) if feedback: n = FeedbackModifier(feedback, 4096, 16, n) self.pads[p] = n log.info("Converted %s to %s", a.to_string(), n.to_string())
class DoubleclickModifier(Modifier): COMMAND = "doubleclick" DEAFAULT_TIMEOUT = 0.2 def __init__(self, doubleclickaction, normalaction=None): Modifier.__init__(self) self.action = doubleclickaction self.normalaction = normalaction or NoAction() self.holdaction = NoAction() self.timeout = self.DEAFAULT_TIMEOUT self.waiting = False self.pressed = False self.active = None def set_haptic(self, hapticdata): supports = self.action.set_haptic(hapticdata) if self.normalaction: supports = self.normalaction.set_haptic(hapticdata) or supports if self.holdaction: supports = self.holdaction.set_haptic(hapticdata) or supports return supports def set_speed(self, x, y, z): supports = self.action.set_speed(x, y, z) if self.normalaction: supports = self.normalaction.set_speed(x, y, z) or supports if self.holdaction: supports = self.holdaction.set_speed(x, y, z) or supports return supports def strip(self): if self.holdaction: return self.holdaction.strip() return self.action.strip() def compress(self): self.action = self.action.compress() self.holdaction = self.holdaction.compress() self.normalaction = self.normalaction.compress() if isinstance(self.normalaction, DoubleclickModifier): self.action = self.action.compress() or self.normalaction.action.compress() self.holdaction = self.holdaction.compress() or self.normalaction.holdaction.compress() self.normalaction = self.normalaction.normalaction.compress() elif isinstance(self.action, HoldModifier): self.holdaction = self.action.holdaction.compress() self.action = self.action.normalaction.compress() elif isinstance(self.holdaction, DoubleclickModifier): self.action = self.holdaction.action.compress() self.holdaction = self.holdaction.normalaction.compress() elif isinstance(self.holdaction, DoubleclickModifier): self.action = self.action.compress() or self.holdaction.action.compress() self.normalaction = self.normalaction.compress() or self.holdaction.normalaction.compress() self.holdaction = self.holdaction.holdaction.compress() return self def __str__(self): l = [ self.action ] if self.normalaction: l += [ self.normalaction ] return "<Modifier %s dbl='%s' hold='%s' normal='%s'>" % ( self.COMMAND, self.action, self.holdaction, self.normalaction ) __repr__ = __str__ def describe(self, context): l = [ ] if self.action: l += [ self.action ] if self.holdaction: l += [ self.holdaction ] if self.normalaction: l += [ self.normalaction ] return "\n".join([ x.describe(context) for x in l ]) def to_string(self, multiline=False, pad=0): firstline, lastline = "", "" if self.action: firstline += DoubleclickModifier.COMMAND + "(" + self.action.to_string() + "," lastline += ")" if self.holdaction: firstline += HoldModifier.COMMAND + "(" + self.holdaction.to_string() + "," lastline += ")" if multiline: if self.normalaction: rv = [ (" " * pad) + firstline ] rv += self.normalaction.to_string(True, pad+2).split("\n") rv += [ (" " * pad) + lastline ] else: rv = [ firstline.strip(",") + lastline ] return "\n".join(rv) elif self.normalaction: return firstline + self.normalaction.to_string() + lastline else: return firstline.strip(",") + lastline def encode(self): if self.normalaction: rv = self.normalaction.encode() else: rv = {} rv['doubleclick'] = self.action.encode() if self.holdaction: rv['hold'] = self.holdaction.encode() if self.name: rv['name'] = self.name return rv def button_press(self, mapper): self.pressed = True if self.waiting: # Double-click happened mapper.remove_scheduled(self.on_timeout) self.waiting = False self.active = self.action self.active.button_press(mapper) else: # First click, start the timer self.waiting = True mapper.schedule(self.timeout, self.on_timeout) def button_release(self, mapper): self.pressed = False if self.waiting and self.active is None and not self.action: # In HoldModifier, button released before timeout mapper.remove_scheduled(self.on_timeout) self.waiting = False if self.normalaction: self.normalaction.button_press(mapper) self.normalaction.button_release(mapper) elif self.active: # Released held button self.active.button_release(mapper) self.active = None def on_timeout(self, mapper, *a): if self.waiting: self.waiting = False if self.pressed: # Timeouted while button is still pressed self.active = self.holdaction if self.holdaction else self.normalaction self.active.button_press(mapper) elif self.normalaction: # User did short click and nothing else self.normalaction.button_press(mapper) self.normalaction.button_release(mapper)
class DoubleclickModifier(Modifier): COMMAND = "doubleclick" DEAFAULT_TIMEOUT = 0.2 def __init__(self, doubleclickaction, normalaction=None): Modifier.__init__(self) self.action = doubleclickaction self.normalaction = normalaction or NoAction() self.holdaction = NoAction() self.timeout = self.DEAFAULT_TIMEOUT self.waiting = False self.pressed = False self.active = None def set_haptic(self, hapticdata): supports = self.action.set_haptic(hapticdata) if self.normalaction: supports = self.normalaction.set_haptic(hapticdata) or supports if self.holdaction: supports = self.holdaction.set_haptic(hapticdata) or supports return supports def set_speed(self, x, y, z): supports = self.action.set_speed(x, y, z) if self.normalaction: supports = self.normalaction.set_speed(x, y, z) or supports if self.holdaction: supports = self.holdaction.set_speed(x, y, z) or supports return supports def strip(self): if self.holdaction: return self.holdaction.strip() return self.action.strip() def compress(self): self.action = self.action.compress() self.holdaction = self.holdaction.compress() self.normalaction = self.normalaction.compress() if isinstance(self.normalaction, DoubleclickModifier): self.action = self.action.compress( ) or self.normalaction.action.compress() self.holdaction = self.holdaction.compress( ) or self.normalaction.holdaction.compress() self.normalaction = self.normalaction.normalaction.compress() elif isinstance(self.action, HoldModifier): self.holdaction = self.action.holdaction.compress() self.action = self.action.normalaction.compress() elif isinstance(self.holdaction, DoubleclickModifier): self.action = self.holdaction.action.compress() self.holdaction = self.holdaction.normalaction.compress() elif isinstance(self.holdaction, DoubleclickModifier): self.action = self.action.compress( ) or self.holdaction.action.compress() self.normalaction = self.normalaction.compress( ) or self.holdaction.normalaction.compress() self.holdaction = self.holdaction.holdaction.compress() return self def __str__(self): l = [self.action] if self.normalaction: l += [self.normalaction] return "<Modifier %s dbl='%s' hold='%s' normal='%s'>" % ( self.COMMAND, self.action, self.holdaction, self.normalaction) __repr__ = __str__ def describe(self, context): l = [] if self.action: l += [self.action] if self.holdaction: l += [self.holdaction] if self.normalaction: l += [self.normalaction] return "\n".join([x.describe(context) for x in l]) def to_string(self, multiline=False, pad=0): firstline, lastline = "", "" if self.action: firstline += DoubleclickModifier.COMMAND + "(" + self.action.to_string( ) + "," lastline += ")" if self.holdaction: firstline += HoldModifier.COMMAND + "(" + self.holdaction.to_string( ) + "," lastline += ")" if multiline: if self.normalaction: rv = [(" " * pad) + firstline] rv += self.normalaction.to_string(True, pad + 2).split("\n") rv += [(" " * pad) + lastline] else: rv = [firstline.strip(",") + lastline] return "\n".join(rv) elif self.normalaction: return firstline + self.normalaction.to_string() + lastline else: return firstline.strip(",") + lastline def encode(self): if self.normalaction: rv = self.normalaction.encode() else: rv = {} rv['doubleclick'] = self.action.encode() if self.holdaction: rv['hold'] = self.holdaction.encode() if self.name: rv['name'] = self.name return rv def button_press(self, mapper): self.pressed = True if self.waiting: # Double-click happened mapper.remove_scheduled(self.on_timeout) self.waiting = False self.active = self.action self.active.button_press(mapper) else: # First click, start the timer self.waiting = True mapper.schedule(self.timeout, self.on_timeout) def button_release(self, mapper): self.pressed = False if self.waiting and self.active is None and not self.action: # In HoldModifier, button released before timeout mapper.remove_scheduled(self.on_timeout) self.waiting = False if self.normalaction: self.normalaction.button_press(mapper) self.normalaction.button_release(mapper) elif self.active: # Released held button self.active.button_release(mapper) self.active = None def on_timeout(self, mapper, *a): if self.waiting: self.waiting = False if self.pressed: # Timeouted while button is still pressed self.active = self.holdaction if self.holdaction else self.normalaction self.active.button_press(mapper) elif self.normalaction: # User did short click and nothing else self.normalaction.button_press(mapper) self.normalaction.button_release(mapper)
class Profile(object): LEFT = LEFT RIGHT = RIGHT WHOLE = WHOLE STICK = STICK GYRO = GYRO X, Y, Z = "X", "Y", "Z" STICK_AXES = { X : "lpad_x", Y : "lpad_y" } LPAD_AXES = STICK_AXES RPAD_AXES = { X : "rpad_x", Y : "rpad_y" } TRIGGERS = [ LEFT, RIGHT ] def __init__(self, parser): self.parser = parser self.buttons = { x : NoAction() for x in SCButtons } self.menus = {} self.stick = NoAction() self.triggers = { Profile.LEFT : NoAction(), Profile.RIGHT : NoAction() } self.pads = { Profile.LEFT : NoAction(), Profile.RIGHT : NoAction() } self.gyro = NoAction() def save(self, filename): """ Saves profile into file. Returns self """ data = { 'buttons' : {}, 'stick' : self.stick, 'gyro' : self.gyro, 'trigger_left' : self.triggers[Profile.LEFT], 'trigger_right' : self.triggers[Profile.RIGHT], "pad_left" : self.pads[Profile.LEFT], "pad_right" : self.pads[Profile.RIGHT], "menus" : { id : self.menus[id].encode() for id in self.menus } } for i in self.buttons: if self.buttons[i]: data['buttons'][i.name] = self.buttons[i] # Generate & save json jstr = Encoder(sort_keys=True, indent=4).encode(data) open(filename, "w").write(jstr) return self def load(self, filename): """ Loads profile from file. Returns self """ data = json.loads(open(filename, "r").read()) # Buttons self.buttons = {} for x in SCButtons: self.buttons[x] = self.parser.from_json_data(data["buttons"], x.name) # Stick & gyro self.stick = self.parser.from_json_data(data, "stick") self.gyro = self.parser.from_json_data(data, "gyro") if "triggers" in data: # Old format # Triggers self.triggers = ({ x : self.parser.from_json_data(data["triggers"], x) for x in Profile.TRIGGERS }) # Pads self.pads = { Profile.LEFT : self.parser.from_json_data(data, "left_pad"), Profile.RIGHT : self.parser.from_json_data(data, "right_pad"), } else: # New format # Triggers self.triggers = { Profile.LEFT : self.parser.from_json_data(data, "trigger_left"), Profile.RIGHT : self.parser.from_json_data(data, "trigger_right"), } # Pads self.pads = { Profile.LEFT : self.parser.from_json_data(data, "pad_left"), Profile.RIGHT : self.parser.from_json_data(data, "pad_right"), } # Menus self.menus = {} if "menus" in data: for id in data["menus"]: for invalid_char in ".:/": if invalid_char in id: raise ValueError("Invalid character '%s' in menu id '%s'" % (invalid_char, id)) self.menus[id] = MenuData.from_json_data(data["menus"][id], self.parser) return self def compress(self): """ Calls compress on every action to throw out some redundant stuff. Note that calling save() after compress() will break stuff. """ for dct in (self.buttons, self.triggers, self.pads): for x in dct: dct[x] = dct[x].compress() self.stick = self.stick.compress() self.gyro = self.gyro.compress()
class ModeModifier(Modifier): COMMAND = "mode" 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 def __init__(self, *stuff): Modifier.__init__(self) self.default = None self.mods = {} self.held_buttons = set() self.held_sticks = set() self.held_triggers = {} self.order = [] self.old_gyro = None 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): if button is None: self.default = i continue self.mods[button] = i self.order.append(button) button = None elif i in SCButtons: button = i else: raise ValueError("Invalid parameter for 'mode': %s" % (i, )) if self.default is None: self.default = NoAction() def set_haptic(self, hapticdata): supports = False if self.default: supports = self.default.set_haptic(hapticdata) or supports for a in self.mods.values(): supports = a.set_haptic(hapticdata) or supports return supports def set_speed(self, x, y, z): supports = False if self.default: supports = self.default.set_speed(x, y, z) or supports for a in self.mods.values(): supports = a.set_speed(x, y, z) or supports return supports def strip(self): # Returns default action or action assigned to first modifier if self.default: return self.default.strip() if len(self.order) > 0: return self.mods[self.order[0]].strip() # Empty ModeModifier return NoAction() def compress(self): if self.default: self.default = self.default.compress() for button in self.mods: self.mods[button] = self.mods[button].compress() return self def __str__(self): rv = [] for key in self.mods: rv += [key.name, self.mods[key]] if self.default is not None: rv += [self.default] return "<Modifier '%s', %s>" % (self.COMMAND, rv) __repr__ = __str__ def describe(self, context): l = [] if self.default: l.append(self.default) for x in self.order: l.append(self.mods[x]) return "\n".join([x.describe(context) for x in l]) def to_string(self, multiline=False, pad=0): if multiline: rv = [(" " * pad) + "mode("] for key in self.mods: a_str = self.mods[key].to_string(True).split("\n") a_str[0] = (" " * pad) + " " + (key.name + ",").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 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 key in self.mods: rv += [key.name, self.mods[key].to_string(False)] if self.default is not None: rv += [self.default.to_string(False)] return "mode(" + ", ".join(rv) + ")" def encode(self): rv = self.default.encode() rv['modes'] = {} for key in self.mods: rv['modes'][key.name] = self.mods[key].encode() if self.name: rv['name'] = self.name return rv def select(self, mapper): """ Selects action by pressed button. """ for b in self.order: if mapper.is_pressed(b): return self.mods[b] return self.default def select_b(self, mapper): """ Same as select but returns button as well. """ for b in self.order: if mapper.is_pressed(b): return b, self.mods[b] return None, self.default 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_gyro: if self.old_gyro: self.old_gyro.gyro(mapper, 0, 0, 0, *q) self.old_gyro = 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 b in self.held_sticks: b.whole(mapper, 0, 0, what) self.held_sticks.clear() else: self.held_sticks.add(self.select(mapper)) for b in self.held_sticks: b.whole(mapper, x, y, what) else: return self.select(mapper).whole(mapper, x, y, what)
class Profile(object): VERSION = 1.4 # Current profile version. When loading profile file # with version lower than this, auto-conversion may happen LEFT = LEFT RIGHT = RIGHT LPAD = SCButtons.LPAD.name RPAD = SCButtons.RPAD.name CPAD = CPAD WHOLE = WHOLE STICK = STICK GYRO = GYRO X, Y, Z = "X", "Y", "Z" STICK_AXES = { X : "lpad_x", Y : "lpad_y" } LPAD_AXES = STICK_AXES RPAD_AXES = { X : "rpad_x", Y : "rpad_y" } TRIGGERS = [ LEFT, RIGHT ] def __init__(self, parser): self.parser = parser self.clear() self.filename = None # UI-only values self.is_template = False self.description = "" def save(self, filename): """ Saves profile into file. Returns self """ fileobj = file(filename, "w") self.save_fileobj(fileobj) fileobj.close() return self def save_fileobj(self, fileobj): """ Saves profile into file-like object. Returns self """ data = { "_" : (self.description if "\n" not in self.description else self.description.strip("\n").split("\n")), 'buttons' : {}, 'stick' : self.stick, 'gyro' : self.gyro, 'trigger_left' : self.triggers[Profile.LEFT], 'trigger_right' : self.triggers[Profile.RIGHT], "pad_left" : self.pads[Profile.LEFT], "pad_right" : self.pads[Profile.RIGHT], "cpad" : self.pads[Profile.CPAD], "menus" : { id : self.menus[id].encode() for id in self.menus }, "is_template" : self.is_template, "version" : Profile.VERSION, } for i in self.buttons: if self.buttons[i]: data['buttons'][i.name] = self.buttons[i] # Generate & save json jstr = Encoder(sort_keys=True, indent=4).encode(data) fileobj.write(jstr) return self def load(self, filename): """ Loads profile from file. Returns self """ fileobj = open(filename, "r") self.load_fileobj(fileobj) self.filename = filename return self def load_fileobj(self, fileobj): """ Loads profile from file-like object. Filename attribute is not set, what may cause some trouble if used in GUI. Returns self. """ data = json.loads(fileobj.read()) # Version try: version = float(data["version"]) except: version = 0 # Settings - Description # (stored in key "_", so it's serialized on top of JSON file) if "_" not in data: self.description = "" elif type(data["_"]) == list: self.description = "\n".join(data["_"]) else: self.description = data["_"] # Settings - Template self.is_template = bool(data["is_template"]) if "is_template" in data else False # Buttons self.buttons = {} for x in SCButtons: self.buttons[x] = self.parser.from_json_data(data["buttons"], x.name) # Pressing stick is interpreted as STICKPRESS button, # formely called just STICK if "STICK" in data["buttons"] and "STICKPRESS" not in data["buttons"]: self.buttons[SCButtons.STICKPRESS] = self.parser.from_json_data( data["buttons"], "STICK") # Stick & gyro self.stick = self.parser.from_json_data(data, "stick") self.gyro = self.parser.from_json_data(data, "gyro") if "triggers" in data: # Old format # Triggers self.triggers = ({ x : self.parser.from_json_data(data["triggers"], x) for x in Profile.TRIGGERS }) # Pads self.pads = { Profile.LEFT : self.parser.from_json_data(data, "left_pad"), Profile.RIGHT : self.parser.from_json_data(data, "right_pad"), Profile.CPAD : NoAction() } else: # New format # Triggers self.triggers = { Profile.LEFT : self.parser.from_json_data(data, "trigger_left"), Profile.RIGHT : self.parser.from_json_data(data, "trigger_right"), } # Pads self.pads = { Profile.LEFT : self.parser.from_json_data(data, "pad_left"), Profile.RIGHT : self.parser.from_json_data(data, "pad_right"), Profile.CPAD : self.parser.from_json_data(data, "cpad"), } # Menus self.menus = {} if "menus" in data: for id in data["menus"]: for invalid_char in ".:/": if invalid_char in id: raise ValueError("Invalid character '%s' in menu id '%s'" % (invalid_char, id)) self.menus[id] = MenuData.from_json_data(data["menus"][id], self.parser) # Conversion self.original_version = version # TODO: This is temporary if version < Profile.VERSION: self._convert(version) return self def clear(self): """ Clears all actions and adds default menu action on center button """ self.buttons = { x : NoAction() for x in SCButtons } self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction = MenuAction("Default.menu") ) self.menus = {} self.stick = NoAction() self.is_template = False self.triggers = { Profile.LEFT : NoAction(), Profile.RIGHT : NoAction() } self.pads = { Profile.LEFT : NoAction(), Profile.RIGHT : NoAction(), Profile.CPAD : NoAction() } self.gyro = NoAction() def get_all_actions(self): """ Returns generator with every action defined in this profile, including actions in menus. Recursively walks into macros, dpads and everything else that can have nested actions, so both parent and all child actions are yielded. May yield NoAction, but shouldn't yield None. Used for checks when profile is exported or imported. """ for action in self.get_actions(): for i in action.get_all_actions(): yield i for id in self.menus: for i in self.menus[id].get_all_actions(): yield i def get_actions(self): """ As get_all_actions, but returns only root actions, without children, and ignores menus. """ for dct in (self.buttons, self.triggers, self.pads): for k in dct: yield dct[k] for action in (self.stick, self.gyro): yield action def get_filename(self): """ Returns filename of last loaded file or None. """ return self.filename def compress(self): """ Calls compress on every action to throw out some redundant stuff. Note that calling save() after compress() will break stuff. """ for dct in (self.buttons, self.triggers, self.pads): for x in dct: dct[x] = dct[x].compress() self.stick = self.stick.compress() self.gyro = self.gyro.compress() for menu in self.menus.values(): menu.compress() def _convert(self, from_version): """ Performs conversion from older profile version """ if from_version < 1: from scc.modifiers import ModeModifier # Add 'display Default.menu if center button is held' for old profiles c = self.buttons[SCButtons.C] if not c: # Nothing set to C button self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction = MenuAction("Default.menu") ) elif hasattr(c, "holdaction") and c.holdaction: # Already set to something, don't overwrite it pass elif c.to_string().startswith("OSK."): # Special case, don't touch this either pass else: self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction = self.buttons[SCButtons.C] ) if from_version < 1.1: # Convert old scrolling wheel to new representation from scc.modifiers import FeedbackModifier, BallModifier from scc.actions import MouseAction, XYAction from scc.uinput import Rels iswheelaction = ( lambda x : isinstance(x, MouseAction) and x.parameters[0] in (Rels.REL_HWHEEL, Rels.REL_WHEEL) ) for p in (Profile.LEFT, Profile.RIGHT): a, feedback = self.pads[p], None if isinstance(a, FeedbackModifier): feedback = a.haptic.get_position() a = a.action if isinstance(a, XYAction): if iswheelaction(a.x) or iswheelaction(a.y): n = BallModifier(XYAction(a.x, a.y)) if feedback is not None: n = FeedbackModifier(feedback, 4096, 16, n) self.pads[p] = n log.info("Converted %s to %s", a.to_string(), n.to_string()) if from_version < 1.2: # Convert old trigger settings that were done with ButtonAction # to new TriggerAction from scc.constants import TRIGGER_HALF, TRIGGER_MAX, TRIGGER_CLICK from scc.actions import ButtonAction, TriggerAction, MultiAction from scc.uinput import Keys for p in (Profile.LEFT, Profile.RIGHT): if isinstance(self.triggers[p], ButtonAction): buttons, numbers = [], [] n = None # There were one or two keys and zero to two numeric # parameters for old button action for param in self.triggers[p].parameters: if param in Keys: buttons.append(param) elif type(param) in (int, float): numbers.append(int(param)) if len(numbers) == 0: # Trigger range was not specified, assume defaults numbers = ( TRIGGER_HALF, TRIGGER_CLICK ) elif len(numbers) == 1: # Only lower range was specified, add default upper range numbers.append(TRIGGER_CLICK) if len(buttons) == 1: # If only one button was set, trigger should work like # one big button n = TriggerAction(numbers[0], ButtonAction(buttons[0])) elif len(buttons) == 2: # Both buttons were set n = MultiAction( TriggerAction(numbers[0], numbers[1], ButtonAction(buttons[0])), TriggerAction(numbers[1], TRIGGER_MAX, ButtonAction(buttons[1])) ) if n: log.info("Converted %s to %s", self.triggers[p].to_string(), n.to_string()) self.triggers[p] = n if from_version < 1.3: # Action format completly changed in v0.4, but profile foramat is same. pass
class Profile(object): VERSION = 1.2 # Current profile version. When loading profile file # with version lower than this, auto-conversion may happen LEFT = LEFT RIGHT = RIGHT WHOLE = WHOLE STICK = STICK GYRO = GYRO X, Y, Z = "X", "Y", "Z" STICK_AXES = {X: "lpad_x", Y: "lpad_y"} LPAD_AXES = STICK_AXES RPAD_AXES = {X: "rpad_x", Y: "rpad_y"} TRIGGERS = [LEFT, RIGHT] def __init__(self, parser): self.parser = parser self.buttons = {x: NoAction() for x in SCButtons} self.menus = {} self.stick = NoAction() self.triggers = {Profile.LEFT: NoAction(), Profile.RIGHT: NoAction()} self.pads = {Profile.LEFT: NoAction(), Profile.RIGHT: NoAction()} self.gyro = NoAction() def save(self, filename): """ Saves profile into file. Returns self """ data = { 'buttons': {}, 'stick': self.stick, 'gyro': self.gyro, 'trigger_left': self.triggers[Profile.LEFT], 'trigger_right': self.triggers[Profile.RIGHT], "pad_left": self.pads[Profile.LEFT], "pad_right": self.pads[Profile.RIGHT], "menus": {id: self.menus[id].encode() for id in self.menus}, "version": Profile.VERSION } for i in self.buttons: if self.buttons[i]: data['buttons'][i.name] = self.buttons[i] # Generate & save json jstr = Encoder(sort_keys=True, indent=4).encode(data) open(filename, "w").write(jstr) return self def load(self, filename): """ Loads profile from file. Returns self """ data = json.loads(open(filename, "r").read()) # Version try: version = int(data["version"]) except: version = 0 # Buttons self.buttons = {} for x in SCButtons: self.buttons[x] = self.parser.from_json_data( data["buttons"], x.name) # Stick & gyro self.stick = self.parser.from_json_data(data, "stick") self.gyro = self.parser.from_json_data(data, "gyro") if "triggers" in data: # Old format # Triggers self.triggers = ({ x: self.parser.from_json_data(data["triggers"], x) for x in Profile.TRIGGERS }) # Pads self.pads = { Profile.LEFT: self.parser.from_json_data(data, "left_pad"), Profile.RIGHT: self.parser.from_json_data(data, "right_pad"), } else: # New format # Triggers self.triggers = { Profile.LEFT: self.parser.from_json_data(data, "trigger_left"), Profile.RIGHT: self.parser.from_json_data(data, "trigger_right"), } # Pads self.pads = { Profile.LEFT: self.parser.from_json_data(data, "pad_left"), Profile.RIGHT: self.parser.from_json_data(data, "pad_right"), } # Menus self.menus = {} if "menus" in data: for id in data["menus"]: for invalid_char in ".:/": if invalid_char in id: raise ValueError( "Invalid character '%s' in menu id '%s'" % (invalid_char, id)) self.menus[id] = MenuData.from_json_data( data["menus"][id], self.parser) # Conversion if version < Profile.VERSION: self._convert(version) return self def compress(self): """ Calls compress on every action to throw out some redundant stuff. Note that calling save() after compress() will break stuff. """ for dct in (self.buttons, self.triggers, self.pads): for x in dct: dct[x] = dct[x].compress() self.stick = self.stick.compress() self.gyro = self.gyro.compress() def _convert(self, from_version): """ Performs conversion from older profile version """ if from_version < 1: from scc.modifiers import ModeModifier, HoldModifier from scc.special_actions import MenuAction # Add 'display Default.menu if center button is held' for old profiles c = self.buttons[SCButtons.C] if not c: # Nothing set to C button self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction=MenuAction("Default.menu")) elif hasattr(c, "holdaction") and c.holdaction: # Already set to something, don't overwrite it pass elif c.to_string().startswith("OSK."): # Special case, don't touch this either pass else: self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction=self.buttons[SCButtons.C]) if from_version < 1.1: # Convert old scrolling wheel to new representation from scc.modifiers import FeedbackModifier, BallModifier from scc.actions import MouseAction, XYAction from scc.uinput import Rels iswheelaction = ( lambda x: isinstance(x, MouseAction) and x.parameters[0] in (Rels.REL_HWHEEL, Rels.REL_WHEEL)) for p in (Profile.LEFT, Profile.RIGHT): a, feedback = self.pads[p], None if isinstance(a, FeedbackModifier): feedback = a.haptic.get_position() a = a.action if isinstance(a, XYAction): if iswheelaction(a.x) or iswheelaction(a.y): n = BallModifier(XYAction(a.x, a.y)) if feedback is not None: n = FeedbackModifier(feedback, 4096, 16, n) self.pads[p] = n log.info("Converted %s to %s", a.to_string(), n.to_string()) if from_version < 1.2: # Convert old trigger settings that were done with ButtonAction # to new TriggerAction from scc.constants import TRIGGER_HALF, TRIGGER_MAX, TRIGGER_CLICK from scc.actions import ButtonAction, TriggerAction, MultiAction from scc.uinput import Keys for p in (Profile.LEFT, Profile.RIGHT): if isinstance(self.triggers[p], ButtonAction): buttons, numbers = [], [] n = None # There were one or two keys and zero to two numeric # parameters for old button action for param in self.triggers[p].parameters: if param in Keys: buttons.append(param) elif type(param) in (int, float): numbers.append(int(param)) if len(numbers) == 0: # Trigger range was not specified, assume defaults numbers = (TRIGGER_HALF, TRIGGER_CLICK) elif len(numbers) == 1: # Only lower range was specified, add default upper range numbers.append(TRIGGER_CLICK) if len(buttons) == 1: # If only one button was set, trigger should work like # one big button n = TriggerAction(numbers[0], ButtonAction(buttons[0])) elif len(buttons) == 2: # Both buttons were set n = MultiAction( TriggerAction(numbers[0], numbers[1], ButtonAction(buttons[0])), TriggerAction(numbers[1], TRIGGER_MAX, ButtonAction(buttons[1]))) print buttons print numbers if n: log.info("Converted %s to %s", self.triggers[p].to_string(), n.to_string()) self.triggers[p] = n
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 = {} self.held_buttons = set() self.held_sticks = set() self.held_triggers = {} self.order = [] self.old_gyro = 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): if button is None: self.default = i continue self.mods[button] = i self.order.append(button) button = None elif i in SCButtons: button = i else: raise ValueError("Invalid parameter for 'mode': %s" % (i, )) if self.default is None: self.default = NoAction() def get_child_actions(self): return [self.mods[key] for key in self.mods] def encode(self): rv = self.default.encode() modes = {} for key in self.mods: modes[key.name] = self.mods[key].encode() rv[ModeModifier.PROFILE_KEYS[0]] = modes if self.name: rv[NameModifier.COMMAND] = self.name 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 key in self.mods: rv |= self.mods[key].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.order) > 0: return self.mods[self.order[0]].strip() # Empty ModeModifier return NoAction() def compress(self): if self.default: self.default = self.default.compress() for button in self.mods: self.mods[button] = self.mods[button].compress() return self def __str__(self): rv = [] for key in self.mods: rv += [key.name, self.mods[key]] 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 x in self.order: l.append(self.mods[x]) return "\n".join([x.describe(context) for x in l]) def to_string(self, multiline=False, pad=0): if multiline: rv = [(" " * pad) + "mode("] for key in self.mods: a_str = self.mods[key].to_string(True).split("\n") a_str[0] = (" " * pad) + " " + (key.name + ",").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 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 key in self.mods: rv += [key.name, self.mods[key].to_string(False)] if self.default is not None: rv += [self.default.to_string(False)] return "mode(" + ", ".join(rv) + ")" def select(self, mapper): """ Selects action by pressed button. """ for b in self.order: if mapper.is_pressed(b): return self.mods[b] return self.default def select_b(self, mapper): """ Same as select but returns button as well. """ for b in self.order: if mapper.is_pressed(b): return b, self.mods[b] return None, self.default 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_gyro: if self.old_gyro: self.old_gyro.gyro(mapper, 0, 0, 0, *q) self.old_gyro = 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 b in self.held_sticks: b.whole(mapper, 0, 0, what) self.held_sticks.clear() else: self.held_sticks.add(self.select(mapper)) for b in self.held_sticks: b.whole(mapper, x, y, what) else: return self.select(mapper).whole(mapper, x, y, what)
class DoubleclickModifier(Modifier, HapticEnabledAction): COMMAND = "doubleclick" DEAFAULT_TIMEOUT = 0.2 TIMEOUT_KEY = "time" PROFILE_KEY_PRIORITY = 3 def __init__(self, doubleclickaction, normalaction=None, time=None): Modifier.__init__(self) HapticEnabledAction.__init__(self) self.action = doubleclickaction self.normalaction = normalaction or NoAction() self.holdaction = NoAction() self.actions = ( self.action, self.normalaction, self.holdaction ) self.timeout = time or DoubleclickModifier.DEAFAULT_TIMEOUT self.waiting_task = None self.pressed = False self.active = None def get_child_actions(self): return self.action, self.normalaction, self.holdaction @staticmethod def decode(data, a, parser, *b): args = [ parser.from_json_data(data[DoubleclickModifier.COMMAND]), a ] a = DoubleclickModifier(*args) if DoubleclickModifier.TIMEOUT_KEY in data: a.timeout = data[DoubleclickModifier.TIMEOUT_KEY] return a def strip(self): if self.holdaction: return self.holdaction.strip() return self.action.strip() def compress(self): self.action = self.action.compress() self.holdaction = self.holdaction.compress() self.normalaction = self.normalaction.compress() for a in (self.holdaction, self.normalaction): if isinstance(a, HoldModifier): self.holdaction = a.holdaction or self.holdaction self.normalaction = a.normalaction or self.normalaction if isinstance(self.action, HoldModifier): self.holdaction = self.action.holdaction self.action = self.action.normalaction return self def __str__(self): l = [ self.action ] if self.normalaction: l += [ self.normalaction ] return "<Modifier %s dbl='%s' hold='%s' normal='%s'>" % ( self.COMMAND, self.action, self.holdaction, self.normalaction ) __repr__ = __str__ def describe(self, context): l = [ ] if self.action: l += [ self.action ] if self.holdaction: l += [ self.holdaction ] if self.normalaction: l += [ self.normalaction ] return "\n".join([ x.describe(context) for x in l ]) def to_string(self, multiline=False, pad=0): timeout = "" if DoubleclickModifier.DEAFAULT_TIMEOUT != self.timeout: timeout = ", %s" % (self.timeout) if self.action and self.normalaction and self.holdaction: return "doubleclick(%s, hold(%s, %s)%s)" % ( NameModifier.unstrip(self.action).to_string(multiline, pad), NameModifier.unstrip(self.holdaction).to_string(multiline, pad), NameModifier.unstrip(self.normalaction).to_string(multiline, pad), timeout ) elif self.action and self.normalaction and not self.holdaction: return "doubleclick(%s, %s%s)" % ( NameModifier.unstrip(self.action).to_string(multiline, pad), NameModifier.unstrip(self.normalaction).to_string(multiline, pad), timeout ) elif not self.action and self.normalaction and self.holdaction: return "hold(%s, %s%s)" % ( NameModifier.unstrip(self.holdaction).to_string(multiline, pad), NameModifier.unstrip(self.normalaction).to_string(multiline, pad), timeout ) elif not self.action and not self.normalaction and self.holdaction: return "hold(None, %s%s)" % ( NameModifier.unstrip(self.holdaction).to_string(multiline, pad), timeout ) elif self.action and not self.normalaction and not self.holdaction: return "doubleclick(None, %s%s)" % ( NameModifier.unstrip(self.action).to_string(multiline, pad), timeout ) return NameModifier.unstrip( self.action or self.normalaction or self.holdaction ).to_string(multiline, pad) def button_press(self, mapper): self.pressed = True if self.waiting_task: # Double-click happened mapper.cancel_task(self.waiting_task) self.waiting_task = None self.active = self.action self.active.button_press(mapper) else: # First click, start the timer self.waiting_task = mapper.schedule(self.timeout, self.on_timeout) def button_release(self, mapper): self.pressed = False if self.waiting_task and self.active is None and not self.action: # In HoldModifier, button released before timeout mapper.cancel_task(self.waiting_task) self.waiting_task = None if self.normalaction: self.normalaction.button_press(mapper) mapper.schedule(0.02, self.normalaction.button_release) elif self.active: # Released held button self.active.button_release(mapper) self.active = None def on_timeout(self, mapper, *a): if self.waiting_task: self.waiting_task = None if self.pressed: # Timeouted while button is still pressed self.active = self.holdaction if self.holdaction else self.normalaction if self.haptic: mapper.send_feedback(self.haptic) self.active.button_press(mapper) elif self.normalaction: # User did short click and nothing else self.normalaction.button_press(mapper) mapper.schedule(0.02, self.normalaction.button_release)
class DoubleclickModifier(Modifier, HapticEnabledAction): COMMAND = "doubleclick" DEAFAULT_TIMEOUT = 0.2 TIMEOUT_KEY = "time" PROFILE_KEY_PRIORITY = 3 def __init__(self, doubleclickaction, normalaction=None, time=None): Modifier.__init__(self) HapticEnabledAction.__init__(self) self.action = doubleclickaction self.normalaction = normalaction or NoAction() self.holdaction = NoAction() self.actions = (self.action, self.normalaction, self.holdaction) self.timeout = time or DoubleclickModifier.DEAFAULT_TIMEOUT self.waiting = False self.pressed = False self.active = None def encode(self): if self.normalaction: rv = self.normalaction.encode() else: rv = {} rv[DoubleclickModifier.COMMAND] = self.action.encode() if self.holdaction: rv[HoldModifier.COMMAND] = self.holdaction.encode() if self.timeout != DoubleclickModifier.DEAFAULT_TIMEOUT: rv[DoubleclickModifier.TIMEOUT_KEY] = self.timeout if self.name: rv[NameModifier.COMMAND] = self.name return rv def get_child_actions(self): return self.actions @staticmethod def decode(data, a, parser, *b): args = [parser.from_json_data(data[DoubleclickModifier.COMMAND]), a] a = DoubleclickModifier(*args) if DoubleclickModifier.TIMEOUT_KEY in data: a.timeout = data[DoubleclickModifier.TIMEOUT_KEY] return a def strip(self): if self.holdaction: return self.holdaction.strip() return self.action.strip() def compress(self): self.action = self.action.compress() self.holdaction = self.holdaction.compress() self.normalaction = self.normalaction.compress() for a in (self.holdaction, self.normalaction): if isinstance(a, HoldModifier): self.holdaction = a.holdaction or self.holdaction self.normalaction = a.normalaction or self.normalaction if isinstance(self.action, HoldModifier): self.holdaction = self.action.holdaction self.action = self.action.normalaction return self def __str__(self): l = [self.action] if self.normalaction: l += [self.normalaction] return "<Modifier %s dbl='%s' hold='%s' normal='%s'>" % ( self.COMMAND, self.action, self.holdaction, self.normalaction) __repr__ = __str__ def describe(self, context): l = [] if self.action: l += [self.action] if self.holdaction: l += [self.holdaction] if self.normalaction: l += [self.normalaction] return "\n".join([x.describe(context) for x in l]) def to_string(self, multiline=False, pad=0): if self.action and self.normalaction and self.holdaction: return "doubleclick(%s, hold(%s, %s))" % ( self.action.to_string(multiline, pad), self.holdaction.to_string(multiline, pad), self.normalaction.to_string(multiline, pad), ) elif self.action and self.normalaction and not self.holdaction: return "doubleclick(%s, %s)" % ( self.action.to_string(multiline, pad), self.normalaction.to_string(multiline, pad), ) elif not self.action and self.normalaction and self.holdaction: return "hold(%s, %s)" % ( self.holdaction.to_string(multiline, pad), self.normalaction.to_string(multiline, pad), ) return ((self.action or self.normalaction or self.holdaction).to_string(multiline, pad)) def button_press(self, mapper): self.pressed = True if self.waiting: # Double-click happened mapper.remove_scheduled(self.on_timeout) self.waiting = False self.active = self.action self.active.button_press(mapper) else: # First click, start the timer self.waiting = True mapper.schedule(self.timeout, self.on_timeout) def button_release(self, mapper): self.pressed = False if self.waiting and self.active is None and not self.action: # In HoldModifier, button released before timeout mapper.remove_scheduled(self.on_timeout) self.waiting = False if self.normalaction: self.normalaction.button_press(mapper) self.normalaction.button_release(mapper) elif self.active: # Released held button self.active.button_release(mapper) self.active = None def on_timeout(self, mapper, *a): if self.waiting: self.waiting = False if self.pressed: # Timeouted while button is still pressed self.active = self.holdaction if self.holdaction else self.normalaction if self.haptic: mapper.send_feedback(self.haptic) self.active.button_press(mapper) elif self.normalaction: # User did short click and nothing else self.normalaction.button_press(mapper) self.normalaction.button_release(mapper)
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 = {} self.held_buttons = set() self.held_sticks = set() self.held_triggers = {} self.order = [] self.old_gyro = 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): if button is None: self.default = i continue self.mods[button] = i self.order.append(button) button = None elif i in SCButtons: button = i else: raise ValueError("Invalid parameter for 'mode': %s" % (i,)) if self.default is None: self.default = NoAction() def encode(self): rv = self.default.encode() modes = {} for key in self.mods: modes[key.name] = self.mods[key].encode() rv[ModeModifier.PROFILE_KEYS[0]] = modes if self.name: rv[NameModifier.COMMAND] = self.name 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 strip(self): # Returns default action or action assigned to first modifier if self.default: return self.default.strip() if len(self.order) > 0: return self.mods[self.order[0]].strip() # Empty ModeModifier return NoAction() def compress(self): if self.default: self.default = self.default.compress() for button in self.mods: self.mods[button] = self.mods[button].compress() return self def __str__(self): rv = [ ] for key in self.mods: rv += [ key.name, self.mods[key] ] 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 x in self.order: l.append(self.mods[x]) return "\n".join([ x.describe(context) for x in l ]) def to_string(self, multiline=False, pad=0): if multiline: rv = [ (" " * pad) + "mode(" ] for key in self.mods: a_str = self.mods[key].to_string(True).split("\n") a_str[0] = (" " * pad) + " " + (key.name + ",").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 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 key in self.mods: rv += [ key.name, self.mods[key].to_string(False) ] if self.default is not None: rv += [ self.default.to_string(False) ] return "mode(" + ", ".join(rv) + ")" def select(self, mapper): """ Selects action by pressed button. """ for b in self.order: if mapper.is_pressed(b): return self.mods[b] return self.default def select_b(self, mapper): """ Same as select but returns button as well. """ for b in self.order: if mapper.is_pressed(b): return b, self.mods[b] return None, self.default 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_gyro: if self.old_gyro: self.old_gyro.gyro(mapper, 0, 0, 0, *q) self.old_gyro = 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 b in self.held_sticks: b.whole(mapper, 0, 0, what) self.held_sticks.clear() else: self.held_sticks.add(self.select(mapper)) for b in self.held_sticks: b.whole(mapper, x, y, what) else: return self.select(mapper).whole(mapper, x, y, what)