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
Exemple #2
0
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)
Exemple #4
0
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
Exemple #6
0
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)
Exemple #7
0
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__
Exemple #8
0
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())
Exemple #9
0
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)
Exemple #10
0
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()
Exemple #12
0
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)
Exemple #13
0
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
Exemple #14
0
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)
Exemple #16
0
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)
Exemple #18
0
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)