def _split(action): """ Splits passed action so it can be displayed in UI. Returns (sucess, half, full, analog), with three actions for each UI element. Note that each returned action may be TriggerAction. If passed action cannot be decoded, 'sucess' element of tuple is set to False """ half, full, analog = NoAction(), NoAction(), NoAction() actions = action.actions if isinstance(action, MultiAction) else [ action ] for a in actions: effective = TriggerComponent._strip_trigger(a).strip() if isinstance(effective, AxisAction): if analog: # UI can do only one analog action per trigger return False, half, full, analog analog = a elif isinstance(effective, MouseAction): if analog: # UI can do only one analog action per trigger return False, half, full, analog analog = a elif isinstance(a, TriggerAction): if full and half: # UI can handle only one full and # one half-press action return False, half, full, analog if a.release_level == TRIGGER_MAX: if full and a.press_level < full.press_level: if half: # UI can handle only one half-press action return False, half, full, analog half = a elif full: if half: # UI can handle only one half-press action return False, half, full, analog half, full = full, a else: full = a else: if half: # UI can handle only one half-press action return False, half, full, analog half = a elif isinstance(a, HipfireAction): hipfire_actions = TriggerComponent._strip_hipfire(a) half, full = (x.strip() for x in hipfire_actions ) elif isinstance(a, NoAction): # Ignore theese pass else: # Unhandled action type return False, half, full, analog if full and not half: full, half = NoAction(), full return True, half, full, analog
def test_noaction_is_false(self): """ Tests if None can be used as False boolean value. """ assert not NoAction() if NoAction(): raise Exception("NoAction is True :(")
def set_action(self, mode, action): self.half, self.full, self.analog = NoAction(), NoAction(), NoAction() sucess, half, full, analog = TriggerComponent._split(action) if sucess: self._recursing = True cb = self.builder.get_object("cbActionType") if isinstance(action, HipfireAction): self.half, self.full = (TriggerComponent._strip_hipfire(x) for x in (half, full)) if half and full: self.builder.get_object("sclPartialLevel").set_value(action.partialpress_level) self.builder.get_object("sclFullLevel").set_value(action.fullpress_level) trigger_style = action.mode self.set_cb(cb, "HIPFIRE_" + trigger_style, 1) self.builder.get_object("sclTimeOut").set_value(action.timeout) else: self.half, self.full, self.analog = (TriggerComponent._strip_trigger(x) for x in (half, full, analog)) if half: self.builder.get_object("sclPartialLevel").set_value(half.press_level) trigger_style = "NORMAL_EXCLUSIVE" if (half.release_level < TRIGGER_MAX) else "NORMAL" self.set_cb(cb, trigger_style, 1) if full: self.builder.get_object("sclFullLevel").set_value(full.press_level) if isinstance(analog, TriggerAction): self.builder.get_object("sclARangeStart").set_value(analog.press_level) self.builder.get_object("sclARangeEnd").set_value(analog.release_level) self._recursing = False self.update()
def update(self): cb = self.builder.get_object("cbActionType") key = cb.get_model().get_value(cb.get_active_iter(), 1) if key == "dpad8": # 8-way dpad self.editor.set_action(DPad8Action(*self.actions)) elif key == "dpad": # 4-way dpad self.editor.set_action(DPadAction(*self.actions[0:4])) elif key == "wsad": # special case of 4-way dpad a = DPadAction(ButtonAction(Keys.KEY_W), ButtonAction(Keys.KEY_S), ButtonAction(Keys.KEY_A), ButtonAction(Keys.KEY_D)) self.actions = [ NoAction() ] * 8 self.editor.set_action(a) self.update_button_desc(a) elif key == "arrows": # special case of 4-way dpad a = DPadAction(ButtonAction(Keys.KEY_UP), ButtonAction(Keys.KEY_DOWN), ButtonAction(Keys.KEY_LEFT), ButtonAction(Keys.KEY_RIGHT)) self.actions = [ NoAction() ] * 8 self.editor.set_action(a) self.update_button_desc(a) else: # Menu self.on_cbMenus_changed()
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 __init__(self, app, editor): AEComponent.__init__(self, app, editor) BindingEditor.__init__(self, app) self._recursing = False self.half = NoAction() self.full = NoAction() self.analog = NoAction()
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 load_circular_action(self, action): cbAxisOutput = self.builder.get_object("cbAxisOutput") btCircularAxis = self.builder.get_object("btCircularAxis") btCircularButton0 = self.builder.get_object("btCircularButton0") btCircularButton1 = self.builder.get_object("btCircularButton1") # Turn action into list of subactions (even if it's just single action) if isinstance(action.action, MultiAction): actions = action.action.actions else: actions = [action.action] # Parse that list self.circular_axis, self.circular_buttons = NoAction(), [None, None] for action in actions: if isinstance(action, ButtonAction): self.circular_buttons = [action.button, action.button2] else: self.circular_axis = action # Set labels b0, b1 = self.circular_buttons btCircularButton0.set_label(ButtonAction.describe_button(b0)) btCircularButton1.set_label(ButtonAction.describe_button(b1)) btCircularAxis.set_label(self.circular_axis.describe(Action.AC_PAD)) self.set_cb(cbAxisOutput, "circular", 2)
def set_input(self, id, action, mode=Action.AC_BUTTON): btDefault = self.builder.get_object("btDefault") self.id = id self.mode = mode self.set_title("Modeshift for %s" % (id.name if id in SCButtons else str(id), )) if isinstance(action, ModeModifier): self._load_modemod(0, action) self._set_nomod_button(0, action.default) self._set_nomod_button(1, NoAction()) self._set_nomod_button(2, NoAction()) elif isinstance(action, DoubleclickModifier): # includes HoldModifier self._set_nomod_button(0, action.normalaction) self._set_nomod_button(1, action.holdaction) self._set_nomod_button(2, action.action) self.builder.get_object("adjTime").set_value(action.timeout) if mode == Action.AC_OSK: # This is kinda bad, but allowing Custom Editor # from OSK editor is in TODO self.builder.get_object("btCustomActionEditor").set_visible(False) if mode != Action.AC_BUTTON: for w in ("vbHold", "vbDoubleClick", "lblHold", "lblDoubleClick"): self.builder.get_object(w).set_sensitive(False)
def from_json_data(self, data, key=None): """ Converts dict stored in profile file into action. May throw ParseError. """ if key is not None: # Don't fail if called for non-existent key, return NoAction instead. # Using this is sorter than # calling 'if button in data["buttons"]: ...' everywhere if key in data: return self.from_json_data(data[key], None) else: return NoAction() if "action" in data: a = self.restart(data["action"]).parse() or NoAction() else: a = NoAction() decoders = set() for key in data: if key in Action.PKEYS: decoders.add(Action.PKEYS[key]) if decoders: for cls in sorted(decoders, key=lambda a: a.PROFILE_KEY_PRIORITY): a = cls.decode(data, a, self, 0) # Profile version is not yet used anywhere return a
def on_btClearCircularButtons_clicked(self, *a): btCircularButton0 = self.builder.get_object("btCircularButton0") btCircularButton1 = self.builder.get_object("btCircularButton1") self.circular_buttons = [None, None] btCircularButton0.set_label(NoAction().describe(Action.AC_PAD)) btCircularButton1.set_label(NoAction().describe(Action.AC_PAD)) self.editor.set_action(self.make_circular_action())
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 __init__(self, app, callback): self.app = app self.id = None self.mode = Action.AC_BUTTON self.ac_callback = callback self.current_page = 0 self.actions = ([], [], []) self.nomods = [NoAction(), NoAction(), NoAction()] self.setup_widgets()
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 __init__(self, app, callback): Editor.__init__(self) self.app = app self.id = None self.mode = Action.AC_BUTTON self.ac_callback = callback self.radius = 0.5 self.actions = [NoAction(), NoAction()] self.setup_widgets()
def on_btAddAction_clicked(self, *a): cbButtonChooser = self.builder.get_object("cbButtonChooser") item = cbButtonChooser.get_model().get_value(cbButtonChooser.get_active_iter(), 0) if item.startswith("Soft"): b = getattr(SCButtons, item.split(" ")[-1]) rng = RangeOP(b, ">=", -1) self._add_action(self.current_page, rng, NoAction()) else: b = getattr(SCButtons, item) self._add_action(self.current_page, b, NoAction())
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 __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 from_json_data(self, data, key=None): """ Converts dict stored in profile file into action. May throw ParseError. """ if key is not None: # Don't fail if called for non-existent key, return NoAction instead. # Using this is sorter than # calling 'if button in data["buttons"]: ...' everywhere if key in data: return self.from_json_data(data[key], None) else: return NoAction() a = NoAction() if "action" in data: a = self.restart(data["action"]).parse() or NoAction() if "X" in data or "Y" in data: # "action" is ignored if either "X" or "Y" is there x = self.from_json_data(data["X"]) if "X" in data else NoAction() y = self.from_json_data(data["Y"]) if "Y" in data else NoAction() a = XYAction(x, y) if "deadzone" in data: lower = data["deadzone"]["lower"] if "lower" in data["deadzone"] else STICK_PAD_MIN upper = data["deadzone"]["upper"] if "upper" in data["deadzone"] else STICK_PAD_MAX a = DeadzoneModifier(lower, upper, a) if "sensitivity" in data: args = data["sensitivity"] args.append(a) a = SensitivityModifier(*args) if "feedback" in data: args = data["feedback"] if hasattr(HapticPos, args[0]): args[0] = getattr(HapticPos, args[0]) args.append(a) a = FeedbackModifier(*args) if "osd" in data: a = OSDAction(a) if data["osd"] is not True: a.timeout = float(data["osd"]) if "click" in data: a = ClickModifier(a) if "name" in data: a.name = data["name"] if "modes" in data: args = [] for button in data['modes']: if hasattr(SCButtons, button): args += [ getattr(SCButtons, button), self.from_json_data(data['modes'][button]) ] if a: args += [ a ] a = ModeModifier(*args) return a
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 _make_action(self): """ Generates and returns Action instance """ cbMode = self.builder.get_object("cbMode") key = cbMode.get_model().get_value(cbMode.get_active_iter(), 0) if key == "inner": return MultiAction( self.actions[1], RingAction(self.radius, self.actions[0], NoAction())) elif key == "outer": return MultiAction( self.actions[0], RingAction(self.radius, NoAction(), self.actions[1])) else: return RingAction(self.radius, *self.actions)
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 _set_mode(self, mode, id, action): btDefault = self.builder.get_object("btDefault") self.id = id self.mode = mode if isinstance(action, ModeModifier): self._load_modemod(0, action) self._set_nomod_button(0, action.default) self._set_nomod_button(1, NoAction()) self._set_nomod_button(2, NoAction()) elif isinstance(action, DoubleclickModifier): # includes HoldModifier self._set_nomod_button(0, action.normalaction) self._set_nomod_button(1, action.holdaction) self._set_nomod_button(2, action.action)
def __init__(self, *parameters): # TODO: remove self.speeds self.speeds = [] action = NoAction() for p in parameters: if type(p) in (int, float) and len(self.speeds) < 3: self.speeds.append(float(p)) else: if isinstance(p, Action): action = p while len(self.speeds) < 3: self.speeds.append(1.0) Modifier.__init__(self, action) action.set_speed(*self.speeds) self.parameters = parameters
def __init__(self, *parameters): # TODO: remove self.speeds self.speeds = [] action = NoAction() for p in parameters: if type(p) in (int, float) and len(self.speeds) < 3: self.speeds.append(float(p)) else: if isinstance(p, Action): action = p while len(self.speeds) < 3: self.speeds.append(1.0) Modifier.__init__(self, action) action.set_speed(*self.speeds) self.parameters = parameters
def set_input(self, id, action, mode=None): btDefault = self.builder.get_object("btDefault") lblPressAlone = self.builder.get_object("lblPressAlone") cbHoldFeedback = self.builder.get_object("cbHoldFeedback") sclHoldFeedback = self.builder.get_object("sclHoldFeedback") self.id = id self._fill_button_chooser() if id in STICKS: lblPressAlone.set_label(_("(no button pressed)")) self.mode = mode = mode or Action.AC_STICK elif id in PADS: lblPressAlone.set_label(_("(no button pressed)")) self.mode = mode = mode or Action.AC_PAD else: lblPressAlone.set_label(_("(pressed alone)")) self.mode = mode = mode or Action.AC_BUTTON self.set_title("Modeshift for %s" % (nameof(id) if id in SCButtons else str(id), )) if isinstance(action, FeedbackModifier): cbHoldFeedback.set_active(True) sclHoldFeedback.set_value(action.haptic.get_amplitude()) action = action.action else: cbHoldFeedback.set_active(False) sclHoldFeedback.set_value(512) if isinstance(action, ModeModifier): self._load_modemod(0, action) self._set_nomod_button(0, action.default) self._set_nomod_button(1, NoAction()) self._set_nomod_button(2, NoAction()) elif isinstance(action, DoubleclickModifier): # includes HoldModifier self._set_nomod_button(0, action.normalaction) self._set_nomod_button(1, action.holdaction) self._set_nomod_button(2, action.action) self.builder.get_object("adjTime").set_value(action.timeout) if mode == Action.AC_OSK: # This is kinda bad, but allowing Custom Editor # from OSK editor is in TODO self.builder.get_object("btCustomActionEditor").set_visible(False) if mode != Action.AC_BUTTON: for w in ("vbHold", "vbDoubleClick", "lblHold", "lblDoubleClick"): self.builder.get_object(w).set_sensitive(False)
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 on_btAddAction_clicked(self, *a): cbButtonChooser = self.builder.get_object("cbButtonChooser") b = getattr( SCButtons, cbButtonChooser.get_model().get_value( cbButtonChooser.get_active_iter(), 0)) self._add_action(self.current_page, b, NoAction())
def __init__(self, app, editor): AEComponent.__init__(self, app, editor) MenuActionCofC.__init__(self) BindingEditor.__init__(self, app) self._recursing = False self._userdata_load_started = False self.actions = [ NoAction() ] * 8
def on_cbActionType_changed(self, *a): cbActionType = self.builder.get_object("cbActionType") stActionData = self.builder.get_object("stActionData") key = cbActionType.get_model().get_value(cbActionType.get_active_iter(), 0) if key == "shell": stActionData.set_visible_child(self.builder.get_object("vbShell")) self.on_enCommand_changed() elif key == "profile": stActionData.set_visible_child(self.builder.get_object("vbProfile")) self.on_cbProfile_changed() elif key == "keyboard": stActionData.set_visible_child(self.builder.get_object("nothing")) if not self._recursing: self.editor.set_action(KeyboardAction()) elif key == "resetgyro": stActionData.set_visible_child(self.builder.get_object("nothing")) if not self._recursing: self.editor.set_action(ResetGyroAction()) elif key == "osd": stActionData.set_visible_child(self.builder.get_object("vbOSD")) if not self._recursing: self.editor.set_action(OSDAction("")) elif key == "menu": stActionData.set_visible_child(self.builder.get_object("grMenu")) self.on_cbMenus_changed() elif key == "turnoff": stActionData.set_visible_child(self.builder.get_object("nothing")) if not self._recursing: self.editor.set_action(TurnOffAction()) else: # none stActionData.set_visible_child(self.builder.get_object("nothing")) if not self._recursing: self.editor.set_action(NoAction())
def set_action(self, profile, id, action): """ Stores action in profile. Returns formely stored action. """ before = NoAction() if id == SCButtons.STICKPRESS and Profile.STICK in self.button_widgets: before, profile.buttons[id] = profile.buttons[id], action self.button_widgets[Profile.STICK].update() elif id in BUTTONS: before, profile.buttons[id] = profile.buttons[id], action self.button_widgets[id].update() elif id in PRESSABLE: before, profile.buttons[id] = profile.buttons[id], action self.button_widgets[id.name].update() elif id in TRIGGERS: before, profile.triggers[id] = profile.triggers[id], action self.button_widgets[id].update() elif id in GYROS: before, profile.gyro = profile.gyro, action self.button_widgets[id].update() elif id in STICKS + PADS: if id in STICKS: before, profile.stick = profile.stick, action elif id == "LPAD": before, profile.pads[Profile.LEFT] = profile.pads[Profile.LEFT], action else: before, profile.pads[Profile.RIGHT] = profile.pads[Profile.RIGHT], action self.button_widgets[id].update() return before
def send(self, *a): if self._recursing: return cbMode = self.builder.get_object("cbMode") cbYawRoll = self.builder.get_object("cbYawRoll") cbGyroButton = self.builder.get_object("cbGyroButton") cbInvertGyro = self.builder.get_object("cbInvertGyro") action = cbMode.get_model().get_value(cbMode.get_active_iter(), 0) key = cbMode.get_model().get_value(cbMode.get_active_iter(), 2) yawroll = cbYawRoll.get_model().get_value(cbYawRoll.get_active_iter(), 0) button = cbGyroButton.get_model().get_value( cbGyroButton.get_active_iter(), 0) match = re.match(r"([^\[]+)\[([^\|]+)\|([^\]]+)\](.*)", action) if match: grps = match.groups() if yawroll == YAW: action = "%s%s%s" % (grps[0], grps[1], grps[3]) else: action = "%s%s%s" % (grps[0], grps[2], grps[3]) action = self.parser.restart(action).parse() if button and action: if cbInvertGyro.get_active(): action = ModeModifier(getattr(SCButtons, button), NoAction(), action) else: action = ModeModifier(getattr(SCButtons, button), action) if key == "mouse": self.editor.set_default_sensitivity(3.5, 3.5, 3.5) else: self.editor.set_default_sensitivity(1, 1, 1) self.editor.set_action(action)
def send(self, *a): if self._recursing: return cbGyroButton = self.builder.get_object("cbGyroButton") button = cbGyroButton.get_model().get_value( cbGyroButton.get_active_iter(), 0) normal, n_set = [None, None, None], False absolute, a_set = [None, None, None], False for i in xrange(0, 3): if self.axes[i] is not None: if self.cbs[i].get_active(): absolute[i] = self.axes[i] a_set = True else: normal[i] = self.axes[i] n_set = True if n_set and a_set: action = MultiAction(GyroAction(*normal), GyroAbsAction(*absolute)) elif n_set: action = GyroAction(*normal) elif a_set: action = GyroAbsAction(*absolute) else: action = NoAction() if button and action: action = ModeModifier(getattr(SCButtons, button), action) self.editor.set_action(action)
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 load_circular_action(self, action): cbAxisOutput = self.builder.get_object("cbAxisOutput") btCircularAxis = self.builder.get_object("btCircularAxis") btCircularButton0 = self.builder.get_object("btCircularButton0") btCircularButton1 = self.builder.get_object("btCircularButton1") # Turn action into list of subactions (even if it's just single action) if isinstance(action.action, MultiAction): actions = action.action.actions else: actions = [ action.action ] # Parse that list self.circular_axis, self.circular_buttons = NoAction(), [ None, None ] for action in actions: if isinstance(action, ButtonAction): self.circular_buttons = [ action.button, action.button2 ] else: self.circular_axis = action # Set labels b0, b1 = self.circular_buttons btCircularButton0.set_label(ButtonAction.describe_button(b0)) btCircularButton1.set_label(ButtonAction.describe_button(b1)) btCircularAxis.set_label(self.circular_axis.describe(Action.AC_PAD)) self.set_cb(cbAxisOutput, "circular", 2)
def __init__(self, app, callback): Editor.__init__(self) self.app = app self.id = None self.components = [] # List of available components self.loaded_components = {} # by class name self.c_buttons = {} # Component-to-button dict self.sens_widgets = [] # Sensitivity sliders, labels and 'clear' buttons self.feedback_widgets = [] # Feedback settings sliders, labels and 'clear' buttons, plus default value as last item self.smoothing_widgets = [] # Smoothing settings sliders, labels and 'clear' buttons, plus default value as last item self.deadzone_widgets = [] # Deadzone settings sliders, labels and 'clear' buttons, plus default value as last item self.sens = [1.0] * 3 # Sensitivity slider values self.sens_defaults = [1.0] * 3 # Clear button clears to this self.feedback = [0.0] * 3 # Feedback slider values, set later self.deadzone = [0] * 2 # Deadzone slider values, set later self.deadzone_mode = None # None for 'disabled' self.feedback_position = None # None for 'disabled' self.smoothing = None # None for 'disabled' self.click = False # Click modifier value. None for disabled self.rotation_angle = 0 # RotateInputModifier angle self.osd = False # 'OSD enabled' value. self.setup_widgets() self.load_components() self.ac_callback = callback # This is different callback than ButtonChooser uses Editor.install_error_css() self._action = NoAction() self._replaced_action = None self._selected_component = None self._modifiers_enabled = True self._multiparams = [ None ] * 8 self._mode = None self._recursing = False
def __init__(self, app, callback): self.app = app self.id = None self.mode = Action.AC_BUTTON self.ac_callback = callback self.setup_widgets() self.actions = [] self.default = NoAction()
def on_btSave_clicked(self, *a): tvItems = self.builder.get_object("tvItems") cbProfile = self.builder.get_object("cbProfile") entTitle = self.builder.get_object("entTitle") entClass = self.builder.get_object("entClass") cbMatchTitle = self.builder.get_object("cbMatchTitle") cbMatchClass = self.builder.get_object("cbMatchClass") cbExactTitle = self.builder.get_object("cbExactTitle") cbRegExp = self.builder.get_object("cbRegExp") rbProfile = self.builder.get_object("rbProfile") rbTurnOff = self.builder.get_object("rbTurnOff") rbRestart = self.builder.get_object("rbRestart") ce = self.builder.get_object("ConditionEditor") # Build condition data = {} if cbMatchTitle.get_active() and entTitle.get_text(): if cbExactTitle.get_active(): data['exact_title'] = entTitle.get_text() elif cbRegExp.get_active(): data['regexp'] = entTitle.get_text() else: data['title'] = entTitle.get_text() if cbMatchClass.get_active() and entClass.get_text(): data['wm_class'] = entClass.get_text() condition = Condition(**data) # Grab selected action model, iter = cbProfile.get_model(), cbProfile.get_active_iter() action = NoAction() if rbProfile.get_active(): action = ChangeProfileAction(model.get_value(iter, 0)) elif rbTurnOff.get_active(): action = TurnOffAction() elif rbRestart.get_active(): action = RestartDaemonAction() # Grab & update current row model, iter = tvItems.get_selection().get_selected() o = model.get_value(iter, 0) o.condition = condition o.action = action model.set_value(iter, 1, condition.describe()) model.set_value(iter, 2, action.describe(Action.AC_SWITCHER)) self.hide_dont_destroy(ce) self.save_config()
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 __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 __init__(self, app, editor): AEComponent.__init__(self, app, editor) TimerManager.__init__(self) self._recursing = False self.relative_area = False self.osd_area_instance = None self.on_wayland = False self.circular_axis = MouseAction(Rels.REL_WHEEL) self.circular_buttons = [ None, None ] self.button = None self.parser = GuiActionParser()
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 _make_action(self): """ Generates and returns Action instance """ entName = self.builder.get_object("entName") cbMacroType = self.builder.get_object("cbMacroType") pars = [ x[0] for x in self.actions ] if len(pars) == 0: # No action is actually set action = NoAction() elif cbMacroType.get_active() == 2: # Cycle pars = filter(lambda a : not isinstance(a, SleepAction), pars) action = Cycle(*pars) elif cbMacroType.get_active() == 1: # Repeating macro action = Macro(*pars) action.repeat = True elif len(pars) == 1: # Only one action action = pars[0] else: # Macro action = Macro(*pars) if entName.get_text().decode("utf-8").strip() != "": action.name = entName.get_text().decode("utf-8").strip() return action
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()
class Modifier(Action): def __init__(self, *params): Action.__init__(self, *params) params = list(params) for p in params: if isinstance(p, Action): self.action = p params.remove(p) break else: self.action = NoAction() self._mod_init(*params) def get_compatible_modifiers(self): return self.action.get_compatible_modifiers() def cancel(self, mapper): self.action.cancel(mapper) def get_child_actions(self): return (self.action, ) def _mod_init(self): """ Initializes modifier with rest of parameters, after action parameter was taken from it and stored in self.action """ pass # not needed by default def _mod_to_string(self, params, multiline, pad): """ Adds action at end of params list and generates string """ if multiline: childstr = self.action.to_string(True, pad + 2) if len(params) > 0: return "%s%s(%s,%s%s)" % ( " " * pad, self.COMMAND, ", ".join([ nameof(s) for s in params ]), '\n' if '\n' in childstr else ' ', childstr ) return "%s%s(%s)" % ( " " * pad, self.COMMAND, childstr.strip() ) childstr = self.action.to_string(False, pad) if len(params) > 0: return "%s%s(%s, %s)" % ( " " * pad, self.COMMAND, ", ".join([ nameof(s) for s in params ]), childstr ) return "%s%s(%s)" % ( " " * pad, self.COMMAND, childstr ) def strip_defaults(self): """ Overrides Action.strip_defaults; Uses defaults from _mod_init instead of __init__, but does NOT include last of original parameters - action. """ argspec = inspect.getargspec(self.__class__._mod_init) required_count = len(argspec.args) - len(argspec.defaults) - 1 l = list(self.parameters[0:-1]) d = list(argspec.defaults)[0:len(l)] while len(d) and len(l) > required_count and d[-1] == l[-1]: d, l = d[:-1], l[:-1] return l def strip(self): return self.action.strip() def compress(self): if self.action: self.action = self.action.compress() return self def __str__(self): return "<Modifier '%s', %s>" % (self.COMMAND, self.action) __repr__ = __str__
class ModeshiftEditor(Editor): GLADE = "modeshift_editor.glade" BUTTONS = ( # in order as displayed in combobox (SCButtons.A, _('A') ), (SCButtons.B, _('B') ), (SCButtons.X, _('X') ), (SCButtons.Y, _('Y') ), (None, None), (SCButtons.BACK, _('Back (select)') ), (SCButtons.C, _('Center') ), (SCButtons.START, _('Start') ), (None, None), (SCButtons.LGRIP, _('Left Grip') ), (SCButtons.RGRIP, _('Right Grip') ), (None, None), (SCButtons.LB, _('Left Bumper') ), (SCButtons.RB, _('Right Bumper') ), (SCButtons.LT, _('Left Trigger') ), (SCButtons.RT, _('Right Trigger') ), (None, None), (SCButtons.LPAD, _('Left Pad Pressed') ), (SCButtons.RPAD, _('Right Pad Pressed') ), (SCButtons.LPADTOUCH, _('Left Pad Touched') ), (SCButtons.RPADTOUCH, _('Right Pad Touched') ), ) def __init__(self, app, callback): self.app = app self.id = None self.mode = Action.AC_BUTTON self.ac_callback = callback self.setup_widgets() self.actions = [] self.default = NoAction() def setup_widgets(self): self.builder = Gtk.Builder() self.builder.add_from_file(os.path.join(self.app.gladepath, self.GLADE)) self.window = self.builder.get_object("Dialog") self.builder.connect_signals(self) cbButtonChooser = self.builder.get_object("cbButtonChooser") cbButtonChooser.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) is None ) model = cbButtonChooser.get_model() for button, text in self.BUTTONS: model.append(( None if button is None else button.name, text )) cbButtonChooser.set_active(0) headerbar(self.builder.get_object("header")) def _add_action(self, button, action): grActions = self.builder.get_object("grActions") cbButtonChooser = self.builder.get_object("cbButtonChooser") model = cbButtonChooser.get_model() for row in model: if model.get_value(row.iter, 0) == button.name: model.remove(row.iter) break try: while model.get_value(model[0].iter, 0) is None: model.remove(model[0].iter) cbButtonChooser.set_active(0) except: pass i = len(self.actions) + 1 l = Gtk.Label() l.set_markup("<b>%s</b>" % (button.name,)) l.set_xalign(0.0) b = Gtk.Button.new_with_label(action.describe(self.mode)) b.set_property("hexpand", True) b.connect('clicked', self.on_actionb_clicked, button) clearb = Gtk.Button() clearb.set_image(Gtk.Image.new_from_stock("gtk-delete", Gtk.IconSize.SMALL_TOOLBAR)) clearb.set_relief(Gtk.ReliefStyle.NONE) clearb.connect('clicked', self.on_clearb_clicked, button) grActions.attach(l, 0, i, 1, 1) grActions.attach(b, 1, i, 1, 1) grActions.attach(clearb, 2, i, 1, 1) self.actions.append([ button, action, l, b, clearb ]) grActions.show_all() def on_clearb_clicked(self, trash, button): grActions = self.builder.get_object("grActions") cbButtonChooser = self.builder.get_object("cbButtonChooser") model = cbButtonChooser.get_model() # Remove requested action from the list for i in xrange(0, len(self.actions)): if self.actions[i][0] == button: button, action, l, b, clearb = self.actions[i] for w in (l, b, clearb): grActions.remove(w) del self.actions[i] break # Move everything after that action one position up # - remove it for j in xrange(i, len(self.actions)): button, action, l, b, clearb = self.actions[j] for w in (l, b, clearb): grActions.remove(w) # - add it again for j in xrange(i, len(self.actions)): button, action, l, b, clearb = self.actions[j] grActions.attach(l, 0, j + 1, 1, 1) grActions.attach(b, 1, j + 1, 1, 1) grActions.attach(clearb, 2, j + 1, 1, 1) # Regenereate combobox with removed button added back to it # - Store acive item from in combobox active, i, index = None, 0, -1 try: active = model.get_value(cbButtonChooser.get_active_iter(), 0) except: pass # Clear entire combobox model.clear() # Fill it again for button, text in self.BUTTONS: model.append(( None if button is None else button.name, text )) if button is not None: if button.name == active: index = i i += 1 # Reselect formely active item if index >= 0: cbButtonChooser.set_active(index) def _setup_editor(self, ae, action): if self.mode == Action.AC_BUTTON: ae.set_button(self.id, action) elif self.mode == Action.AC_TRIGGER: ae.set_trigger(self.id, action) elif self.mode == Action.AC_STICK: ae.set_stick(action) elif self.mode == Action.AC_GYRO: ae.set_gyro(action) elif self.mode == Action.AC_PAD: ae.set_pad(self.id, action) def _choose_editor(self, action, cb): if isinstance(action, Macro): from scc.gui.macro_editor import MacroEditor # Cannot be imported @ top e = MacroEditor(self.app, cb) e.set_title(_("Edit Macro")) else: from scc.gui.action_editor import ActionEditor # Cannot be imported @ top e = ActionEditor(self.app, cb) e.set_title(_("Edit Action")) e.hide_modeshift() return e def on_actionb_clicked(self, trash, clicked_button): for i in self.actions: button, action, l, b, clearb = i if button == clicked_button: def on_chosen(id, action, reopen=False): b.set_label(action.describe(self.mode)) i[1] = action if reopen: self.on_actionb_clicked(trash, clicked_button) ae = self._choose_editor(action, on_chosen) self._setup_editor(ae, action) ae.show(self.window) return def on_btDefault_clicked(self, *a): btDefault = self.builder.get_object("btDefault") def on_chosen(id, action, reopen=False): btDefault.set_label(action.describe(self.mode)) self.default = action if reopen: self.on_btDefault_clicked() ae = self._choose_editor(self.default, on_chosen) self._setup_editor(ae, self.default) ae.show(self.window) def on_btClearDefault_clicked(self, *a): self.default = NoAction() btDefault = self.builder.get_object("btDefault") btDefault.set_label(self.default.describe(self.mode)) def on_btAddAction_clicked(self, *a): cbButtonChooser = self.builder.get_object("cbButtonChooser") b = getattr(SCButtons, cbButtonChooser.get_model().get_value(cbButtonChooser.get_active_iter(), 0)) self._add_action(b, NoAction()) def on_btClear_clicked (self, *a): """ Handler for clear button """ action = NoAction() if self.ac_callback is not None: self.ac_callback(self.id, action) self.close() def on_btOK_clicked(self, *a): """ Handler for OK button """ pars = [] for button, action, l, b, clearb in self.actions: pars += [ button, action ] if self.default: pars += [ self.default ] action = ModeModifier(*pars) if len(pars) == 0: # No action is actually set action = NoAction() elif len(pars) == 1: # Only default action left action = self.default if self.ac_callback is not None: self.ac_callback(self.id, action) self.close() def _set_mode(self, mode, id, action): btDefault = self.builder.get_object("btDefault") self.id = id self.mode = mode for key in action.mods: self._add_action(key, action.mods[key]) self.default = action.default btDefault.set_label(self.default.describe(self.mode)) def set_button(self, id, action): """ Setups editor as editor for button action """ self._set_mode(Action.AC_BUTTON, id, action) def set_trigger(self, id, action): """ Setups editor as editor for trigger action """ self._set_mode(Action.AC_TRIGGER, id, action) def set_stick(self, action): """ Setups action editor as editor for stick action """ self._set_mode(Action.AC_STICK, Profile.STICK, action) def set_pad(self, id, action): """ Setups action editor as editor for pad action """ self._set_mode(Action.AC_PAD, id, action) def set_gyro(self, action): """ Setups editor as editor for button action """ self._set_mode(Action.AC_GYRO, Profile.GYRO, action)
def on_btClearDefault_clicked(self, *a): self.default = NoAction() btDefault = self.builder.get_object("btDefault") btDefault.set_label(self.default.describe(self.mode))
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()
def on_btClearCircularAxis_clicked(self, *a): btCircularAxis = self.builder.get_object("btCircularAxis") self.circular_axis = NoAction() btCircularAxis.set_label(self.circular_axis.describe(Action.AC_PAD)) self.editor.set_action(self.make_circular_action())
class Profile(object): VERSION = 1.1 # Current profile version. When loading profile file # with version lower than this, auto-conversion may happen LEFT = LEFT RIGHT = RIGHT WHOLE = WHOLE STICK = STICK GYRO = GYRO X, Y, Z = "X", "Y", "Z" STICK_AXES = { X : "lpad_x", Y : "lpad_y" } LPAD_AXES = STICK_AXES RPAD_AXES = { X : "rpad_x", Y : "rpad_y" } TRIGGERS = [ LEFT, RIGHT ] def __init__(self, parser): self.parser = parser self.buttons = { x : NoAction() for x in SCButtons } self.menus = {} self.stick = NoAction() self.triggers = { Profile.LEFT : NoAction(), Profile.RIGHT : NoAction() } self.pads = { Profile.LEFT : NoAction(), Profile.RIGHT : NoAction() } self.gyro = NoAction() def save(self, filename): """ Saves profile into file. Returns self """ data = { 'buttons' : {}, 'stick' : self.stick, 'gyro' : self.gyro, 'trigger_left' : self.triggers[Profile.LEFT], 'trigger_right' : self.triggers[Profile.RIGHT], "pad_left" : self.pads[Profile.LEFT], "pad_right" : self.pads[Profile.RIGHT], "menus" : { id : self.menus[id].encode() for id in self.menus }, "version" : Profile.VERSION } for i in self.buttons: if self.buttons[i]: data['buttons'][i.name] = self.buttons[i] # Generate & save json jstr = Encoder(sort_keys=True, indent=4).encode(data) open(filename, "w").write(jstr) return self def load(self, filename): """ Loads profile from file. Returns self """ data = json.loads(open(filename, "r").read()) # Version try: version = int(data["version"]) except: version = 0 # Buttons self.buttons = {} for x in SCButtons: self.buttons[x] = self.parser.from_json_data(data["buttons"], x.name) # Stick & gyro self.stick = self.parser.from_json_data(data, "stick") self.gyro = self.parser.from_json_data(data, "gyro") if "triggers" in data: # Old format # Triggers self.triggers = ({ x : self.parser.from_json_data(data["triggers"], x) for x in Profile.TRIGGERS }) # Pads self.pads = { Profile.LEFT : self.parser.from_json_data(data, "left_pad"), Profile.RIGHT : self.parser.from_json_data(data, "right_pad"), } else: # New format # Triggers self.triggers = { Profile.LEFT : self.parser.from_json_data(data, "trigger_left"), Profile.RIGHT : self.parser.from_json_data(data, "trigger_right"), } # Pads self.pads = { Profile.LEFT : self.parser.from_json_data(data, "pad_left"), Profile.RIGHT : self.parser.from_json_data(data, "pad_right"), } # Menus self.menus = {} if "menus" in data: for id in data["menus"]: for invalid_char in ".:/": if invalid_char in id: raise ValueError("Invalid character '%s' in menu id '%s'" % (invalid_char, id)) self.menus[id] = MenuData.from_json_data(data["menus"][id], self.parser) # Conversion if version < Profile.VERSION: self._convert(version) return self def compress(self): """ Calls compress on every action to throw out some redundant stuff. Note that calling save() after compress() will break stuff. """ for dct in (self.buttons, self.triggers, self.pads): for x in dct: dct[x] = dct[x].compress() self.stick = self.stick.compress() self.gyro = self.gyro.compress() def _convert(self, from_version): """ Performs conversion from older profile version """ if from_version < 1: from scc.modifiers import ModeModifier, HoldModifier from scc.special_actions import MenuAction # Add 'display Default.menu if center button is held' for old profiles c = self.buttons[SCButtons.C] if not c: # Nothing set to C button self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction = MenuAction("Default.menu") ) elif hasattr(c, "holdaction") and c.holdaction: # Already set to something, don't overwrite it pass elif c.to_string().startswith("OSK."): # Special case, don't touch this either pass else: self.buttons[SCButtons.C] = HoldModifier( MenuAction("Default.menu"), normalaction = self.buttons[SCButtons.C] ) if from_version < 1.1: # Convert old scrolling wheel to new representation from scc.modifiers import FeedbackModifier, BallModifier from scc.actions import MouseAction, XYAction from scc.uinput import Rels iswheelaction = ( lambda x : isinstance(x, MouseAction) and x.parameters[0] in (Rels.REL_HWHEEL, Rels.REL_WHEEL) ) for p in (Profile.LEFT, Profile.RIGHT): a, feedback = self.pads[p], None if isinstance(a, FeedbackModifier): feedback = a.haptic.get_position() a = a.action if isinstance(a, XYAction): if iswheelaction(a.x) or iswheelaction(a.y): n = BallModifier(XYAction(a.x, a.y)) if feedback: n = FeedbackModifier(feedback, 4096, 16, n) self.pads[p] = n log.info("Converted %s to %s", a.to_string(), n.to_string())
class DoubleclickModifier(Modifier): COMMAND = "doubleclick" DEAFAULT_TIMEOUT = 0.2 TIMEOUT_KEY = "time" PROFILE_KEY_PRIORITY = 3 def __init__(self, doubleclickaction, normalaction=None, time=None): Modifier.__init__(self) self.action = doubleclickaction self.normalaction = normalaction or NoAction() self.holdaction = NoAction() self.timeout = time or DoubleclickModifier.DEAFAULT_TIMEOUT self.waiting = False self.pressed = False self.active = None def encode(self): if self.normalaction: rv = self.normalaction.encode() else: rv = {} rv[DoubleclickModifier.COMMAND] = self.action.encode() if self.holdaction: rv[HoldModifier.COMMAND] = self.holdaction.encode() if self.timeout != DoubleclickModifier.DEAFAULT_TIMEOUT: rv[DoubleclickModifier.TIMEOUT_KEY] = self.timeout if self.name: rv[NameModifier.COMMAND] = self.name return rv @staticmethod def decode(data, a, parser, *b): args = [ parser.from_json_data(data[DoubleclickModifier.COMMAND]), a ] a = DoubleclickModifier(*args) if DoubleclickModifier.TIMEOUT_KEY in data: a.timeout = data[DoubleclickModifier.TIMEOUT_KEY] return a def strip(self): if self.holdaction: return self.holdaction.strip() return self.action.strip() def compress(self): self.action = self.action.compress() self.holdaction = self.holdaction.compress() self.normalaction = self.normalaction.compress() if isinstance(self.normalaction, DoubleclickModifier): self.action = self.action.compress() or self.normalaction.action.compress() self.holdaction = self.holdaction.compress() or self.normalaction.holdaction.compress() self.normalaction = self.normalaction.normalaction.compress() elif isinstance(self.action, HoldModifier): self.holdaction = self.action.holdaction.compress() self.action = self.action.normalaction.compress() elif isinstance(self.holdaction, DoubleclickModifier): self.action = self.holdaction.action.compress() self.holdaction = self.holdaction.normalaction.compress() elif isinstance(self.holdaction, DoubleclickModifier): self.action = self.action.compress() or self.holdaction.action.compress() self.normalaction = self.normalaction.compress() or self.holdaction.normalaction.compress() self.holdaction = self.holdaction.holdaction.compress() return self def __str__(self): l = [ self.action ] if self.normalaction: l += [ self.normalaction ] return "<Modifier %s dbl='%s' hold='%s' normal='%s'>" % ( self.COMMAND, self.action, self.holdaction, self.normalaction ) __repr__ = __str__ def describe(self, context): l = [ ] if self.action: l += [ self.action ] if self.holdaction: l += [ self.holdaction ] if self.normalaction: l += [ self.normalaction ] return "\n".join([ x.describe(context) for x in l ]) def to_string(self, multiline=False, pad=0): return self._mod_to_string(Action.strip_defaults(self), multiline, pad) def button_press(self, mapper): self.pressed = True if self.waiting: # Double-click happened mapper.remove_scheduled(self.on_timeout) self.waiting = False self.active = self.action self.active.button_press(mapper) else: # First click, start the timer self.waiting = True mapper.schedule(self.timeout, self.on_timeout) def button_release(self, mapper): self.pressed = False if self.waiting and self.active is None and not self.action: # In HoldModifier, button released before timeout mapper.remove_scheduled(self.on_timeout) self.waiting = False if self.normalaction: self.normalaction.button_press(mapper) self.normalaction.button_release(mapper) elif self.active: # Released held button self.active.button_release(mapper) self.active = None def on_timeout(self, mapper, *a): if self.waiting: self.waiting = False if self.pressed: # Timeouted while button is still pressed self.active = self.holdaction if self.holdaction else self.normalaction self.active.button_press(mapper) elif self.normalaction: # User did short click and nothing else self.normalaction.button_press(mapper) self.normalaction.button_release(mapper)
class 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 AxisActionComponent(AEComponent, TimerManager): GLADE = "ae/axis_action.glade" NAME = "axis_action" CTXS = Action.AC_STICK | Action.AC_PAD PRIORITY = 3 def __init__(self, app, editor): AEComponent.__init__(self, app, editor) TimerManager.__init__(self) self._recursing = False self.relative_area = False self.osd_area_instance = None self.on_wayland = False self.circular_axis = MouseAction(Rels.REL_WHEEL) self.circular_buttons = [ None, None ] self.button = None self.parser = GuiActionParser() def load(self): if self.loaded : return AEComponent.load(self) cbAreaType = self.builder.get_object("cbAreaType") cbAreaType.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) == "-" ) self.on_wayland = "WAYLAND_DISPLAY" in os.environ or not isinstance(Gdk.Display.get_default(), GdkX11.X11Display) if self.on_wayland: self.builder.get_object("lblArea").set_text(_("Note: Mouse Region option is not available with Wayland-based display server")) self.builder.get_object("grArea").set_sensitive(False) # Remove options that are not applicable to currently editted input if self.editor.get_id() in STICKS: # Remove "Mouse Region", "Mouse" and "Mouse (Emulate Stick)" options # when editing stick bindings cb = self.builder.get_object("cbAxisOutput") for row in cb.get_model(): if row[2] in ("wheel_pad", "area", "mouse", "mouse_pad"): cb.get_model().remove(row.iter) else: # Remove "Mouse" option when editing pads # (it's effectivelly same as Trackpad) cb = self.builder.get_object("cbAxisOutput") for row in cb.get_model(): if row[2] in ("wheel_stick", "mouse_stick"): cb.get_model().remove(row.iter) def hidden(self): self.update_osd_area(None) def set_action(self, mode, action): if self.handles(mode, action): cb = self.builder.get_object("cbAxisOutput") if isinstance(action, AreaAction): self.load_area_action(action) self.set_cb(cb, "area", 2) self.update_osd_area(action) return self.update_osd_area(None) if isinstance(action, MouseAction): self.load_mouse_action(action) elif isinstance(action, CircularModifier): self.load_circular_action(action) elif isinstance(action, ButtonAction): self.load_button_action(action) elif isinstance(action, XYAction): if self.editor.friction > 0: self.load_mouse_action(action) else: p = [ None, None ] for x in (0, 1): if len(action.actions[0].strip().parameters) >= x: if len(action.actions[x].strip().parameters) > 0: p[x] = action.actions[x].strip().parameters[0] if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y: self.set_cb(cb, "lstick", 2) elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY: if isinstance(action, RelXYAction): self.set_cb(cb, "rstick_rel", 2) else: self.set_cb(cb, "rstick", 2) elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL: self.set_cb(cb, "wheel_pad", 2) self.set_cb(cb, "wheel_stick", 2) else: self.set_cb(cb, "none", 2) def update_osd_area(self, action): """ Updates preview area displayed on screen """ if action: if self.osd_area_instance is None: if self.on_wayland: # Cannot display preview with non-X11 backends return self.osd_area_instance = Area() self.osd_area_instance.show() action.update_osd_area(self.osd_area_instance, FakeMapper(self.editor)) self.timer("area", 0.5, self.update_osd_area, action) elif self.osd_area_instance: self.osd_area_instance.quit() self.osd_area_instance = None self.cancel_timer("area") def load_circular_action(self, action): cbAxisOutput = self.builder.get_object("cbAxisOutput") btCircularAxis = self.builder.get_object("btCircularAxis") btCircularButton0 = self.builder.get_object("btCircularButton0") btCircularButton1 = self.builder.get_object("btCircularButton1") # Turn action into list of subactions (even if it's just single action) if isinstance(action.action, MultiAction): actions = action.action.actions else: actions = [ action.action ] # Parse that list self.circular_axis, self.circular_buttons = NoAction(), [ None, None ] for action in actions: if isinstance(action, ButtonAction): self.circular_buttons = [ action.button, action.button2 ] else: self.circular_axis = action # Set labels b0, b1 = self.circular_buttons btCircularButton0.set_label(ButtonAction.describe_button(b0)) btCircularButton1.set_label(ButtonAction.describe_button(b1)) btCircularAxis.set_label(self.circular_axis.describe(Action.AC_PAD)) self.set_cb(cbAxisOutput, "circular", 2) def load_button_action(self, action): self.button = action cbAxisOutput = self.builder.get_object("cbAxisOutput") btSingleButton = self.builder.get_object("btSingleButton") btSingleButton.set_label(self.button.describe(Action.AC_PAD)) self.set_cb(cbAxisOutput, "button", 2) def load_mouse_action(self, action): cbMouseOutput = self.builder.get_object("cbMouseOutput") cbAxisOutput = self.builder.get_object("cbAxisOutput") self._recursing = True if isinstance(action, MouseAction): self.set_cb(cbMouseOutput, "mouse", 1) self.set_cb(cbAxisOutput, "mouse", 2) elif isinstance(action, XYAction): if isinstance(action.x, AxisAction): if action.x.parameters[0] == Axes.ABS_X: self.set_cb(cbMouseOutput, "left", 1) else: self.set_cb(cbMouseOutput, "right", 1) self.set_cb(cbAxisOutput, "mouse", 2) elif isinstance(action.x, MouseAction): if self.editor.get_id() in STICKS: self.set_cb(cbAxisOutput, "wheel_stick", 2) else: self.set_cb(cbAxisOutput, "wheel_pad", 2) self._recursing = False def load_area_action(self, action): """ Load AreaAction values into UI. """ cbAreaType = self.builder.get_object("cbAreaType") x1, y1, x2, y2 = action.coords self.relative_area = False if isinstance(action, RelAreaAction): key = "screensize" self.relative_area = True x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0 elif isinstance(action, RelWinAreaAction): key = "windowsize" self.relative_area = True x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0 else: t1 = "1" if x1 < 0 and x2 < 0 else "0" t2 = "1" if y1 < 0 and y2 < 0 else "0" x1, y1, x2, y2 = abs(x1), abs(y1), abs(x2), abs(y2) if x2 < x1 : x1, x2 = x2, x1 if y2 < y1 : y1, y2 = y2, y1 if isinstance(action, WinAreaAction): key = "window-%s%s" % (t1, t2) else: key = "screen-%s%s" % (t1, t2) self._recursing = True self.builder.get_object("sbAreaX1").set_value(x1) self.builder.get_object("sbAreaY1").set_value(y1) self.builder.get_object("sbAreaX2").set_value(x2) self.builder.get_object("sbAreaY2").set_value(y2) self.builder.get_object("cbAreaOSDEnabled").set_active(self.editor.osd) self.builder.get_object("cbAreaClickEnabled").set_active(self.pressing_pad_clicks()) for row in cbAreaType.get_model(): if key == row[1]: cbAreaType.set_active_iter(row.iter) break self._recursing = False def on_btCircularAxis_clicked(self, *a): def cb(action): self.circular_axis = action btCircularAxis = self.builder.get_object("btCircularAxis") btCircularAxis.set_label(action.describe(Action.AC_PAD)) self.editor.set_action(self.make_circular_action()) b = SimpleChooser(self.app, "axis", cb) b.set_title(_("Select Axis")) b.display_action(Action.AC_STICK, self.circular_axis) b.show(self.editor.window) def on_btCircularButton_clicked(self, button, *a): index = 0 if button == self.builder.get_object("btCircularButton0") else 1 def cb(action): self.circular_buttons[index] = action.button btCircularButton = self.builder.get_object("btCircularButton%s" % (index, )) btCircularButton.set_label(action.describe(Action.AC_PAD)) self.editor.set_action(self.make_circular_action()) b = SimpleChooser(self.app, "buttons", cb) b.set_title(_("Select Button")) b.display_action(Action.AC_STICK, self.circular_axis) b.show(self.editor.window) def on_btClearCircularAxis_clicked(self, *a): btCircularAxis = self.builder.get_object("btCircularAxis") self.circular_axis = NoAction() btCircularAxis.set_label(self.circular_axis.describe(Action.AC_PAD)) self.editor.set_action(self.make_circular_action()) def on_btClearCircularButtons_clicked(self, *a): btCircularButton0 = self.builder.get_object("btCircularButton0") btCircularButton1 = self.builder.get_object("btCircularButton1") self.circular_buttons = [ None, None ] btCircularButton0.set_label(NoAction().describe(Action.AC_PAD)) btCircularButton1.set_label(NoAction().describe(Action.AC_PAD)) self.editor.set_action(self.make_circular_action()) def on_btSingleButton_clicked(self, *a): def cb(action): self.button = action btSingleButton = self.builder.get_object("btSingleButton") btSingleButton.set_label(self.button.describe(Action.AC_PAD)) self.editor.set_action(self.button) b = SimpleChooser(self.app, "buttons", cb) b.set_title(_("Select Button")) b.display_action(Action.AC_STICK, self.circular_axis) b.show(self.editor.window) def on_cbAreaOSDEnabled_toggled(self, *a): self.editor.builder.get_object("cbOSD").set_active( self.builder.get_object("cbAreaOSDEnabled").get_active()) def pressing_pad_clicks(self): """ Returns True if currently edited pad is set to press left mouse button when pressed. (yes, this is used somewhere) """ side = getattr(SCButtons, self.editor.get_id()) c_action = self.app.current.buttons[side] if isinstance(c_action, ButtonAction): return c_action.button == Keys.BTN_LEFT return False def on_ok(self, action): if isinstance(action.strip(), AreaAction): # Kinda hacky way to set action on LPAD press or RPAD press # when user selects Mouse Area as ouput and checks # 'Pressing the Pad Clicks' checkbox side = getattr(SCButtons, self.editor.get_id()) clicks = self.pressing_pad_clicks() if self.builder.get_object("cbAreaClickEnabled").get_active(): if not clicks: # Turn pad press into mouse clicks self.app.set_action(self.app.current, side, ButtonAction(Keys.BTN_LEFT)) else: if clicks: # Clear action created above if checkbox is uncheck self.app.set_action(self.app.current, side, NoAction()) def on_mouse_options_changed(self, *a): if self._recursing : return action = self.make_mouse_action() self.editor.set_action(action) def make_mouse_action(self): """ Loads values from UI into trackball-related action """ cbMouseOutput = self.builder.get_object("cbMouseOutput") a_str = cbMouseOutput.get_model().get_value(cbMouseOutput.get_active_iter(), 2) return self.parser.restart(a_str).parse() def make_circular_action(self): """ Constructs Circular Modifier """ if self.circular_axis and any(self.circular_buttons): return CircularModifier(MultiAction( self.circular_axis, ButtonAction(*self.circular_buttons))) elif any(self.circular_buttons): return CircularModifier(ButtonAction(*self.circular_buttons)) else: return CircularModifier(self.circular_axis) def make_area_action(self): """ Loads values from UI into new AreaAction or subclass. """ # Prepare cbAreaType = self.builder.get_object("cbAreaType") # Read numbers x1 = self.builder.get_object("sbAreaX1").get_value() y1 = self.builder.get_object("sbAreaY1").get_value() x2 = self.builder.get_object("sbAreaX2").get_value() y2 = self.builder.get_object("sbAreaY2").get_value() # Determine exact action type by looking into Area Type checkbox # (this part may seem little crazy) # ... numbers key = cbAreaType.get_model().get_value(cbAreaType.get_active_iter(), 1) if "-" in key: if key[-2] == "1": # Before-last character ius "1", that means that X coords are # counted from other side and has to be negated x1, x2 = -x1, -x2 if key[-1] == "1": # Key ends with "1". Same thing as above but for Y coordinate y1, y2 = -y1, -y2 if "size" in key: x1, y1, x2, y2 = x1 / 100.0, y1 / 100.0, x2 / 100.0, y2 / 100.0 # ... class if "window-" in key: cls = WinAreaAction self.relative_area = False elif "screensize" == key: cls = RelAreaAction self.relative_area = True elif "windowsize" == key: cls = RelWinAreaAction self.relative_area = True else: # "screen" in key cls = AreaAction self.relative_area = False if not self.relative_area: x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) return cls(x1, y1, x2, y2) def get_button_title(self): return _("Joystick / Mouse") def handles(self, mode, action): if isinstance(action, (NoAction, MouseAction, CircularModifier, InvalidAction, AreaAction, ButtonAction)): return True if isinstance(action, BallModifier): if isinstance(action.action, XYAction): return ( isinstance(action.action.x, (AxisAction, MouseAction)) and isinstance(action.action.x, (AxisAction, MouseAction)) ) return isinstance(action.action, MouseAction) if isinstance(action, XYAction): p = [ None, None ] for x in (0, 1): if len(action.actions[0].strip().parameters) >= x: if len(action.actions[x].strip().parameters) > 0: p[x] = action.actions[x].strip().parameters[0] if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y: return True elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY: return True elif p[0] == Axes.ABS_HAT0X and p[1] == Axes.ABS_HAT0Y: return True elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL: return True return False def on_area_options_changed(self, *a): if self._recursing : return action = self.make_area_action() self.editor.set_action(action) self.update_osd_area(action) for x in ('sbAreaX1', 'sbAreaX2', 'sbAreaY1', 'sbAreaY2'): spin = self.builder.get_object(x) if self.relative_area: spin.get_adjustment().set_upper(100) else: spin.get_adjustment().set_upper(1000) self.on_sbArea_output(spin) def on_sbArea_output(self, button, *a): if self.relative_area: button.set_text("%s %%" % (button.get_value())) else: button.set_text("%s px" % (int(button.get_value()))) def on_sbArea_focus_out_event(self, button, *a): GLib.idle_add(self.on_sbArea_output, button) def on_sbArea_changed(self, button, *a): self.on_sbArea_output(button) self.on_area_options_changed(button) def on_lblSetupTrackball_activate_link(self, trash, link): self.editor.on_link(link) def on_cbAxisOutput_changed(self, *a): cbAxisOutput = self.builder.get_object("cbAxisOutput") stActionData = self.builder.get_object("stActionData") key = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 2) if key == 'area': stActionData.set_visible_child(self.builder.get_object("grArea")) action = self.make_area_action() self.update_osd_area(action) elif key == "button": stActionData.set_visible_child(self.builder.get_object("vbButton")) self.button = self.button or ButtonAction(Keys.BTN_GAMEPAD) action = self.button elif key == "circular": stActionData.set_visible_child(self.builder.get_object("grCircular")) action = self.make_circular_action() elif key == 'mouse': stActionData.set_visible_child(self.builder.get_object("vbMose")) if self.editor.friction == 0: # When switching to mouse, enable trackball by default self.editor.friction = 10 action = self.make_mouse_action() else: stActionData.set_visible_child(self.builder.get_object("nothing")) action = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 0) action = self.parser.restart(action).parse() self.update_osd_area(None) self.editor.set_action(action)
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 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 ModeModifier(Modifier): COMMAND = "mode" MIN_TRIGGER = 2 # When trigger is bellow this position, list of held_triggers is cleared MIN_STICK = 2 # When abs(stick) < MIN_STICK, stick is considered released and held_sticks is cleared def __init__(self, *stuff): Modifier.__init__(self) self.default = None self.mods = {} self.held_buttons = set() self.held_sticks = set() self.held_triggers = {} self.order = [] self.old_gyro = None button = None for i in stuff: if self.default is not None: # Default has to be last parameter raise ValueError("Invalid parameters for 'mode'") if isinstance(i, Action): if button is None: self.default = i continue self.mods[button] = i self.order.append(button) button = None elif i in SCButtons: button = i else: raise ValueError("Invalid parameter for 'mode': %s" % (i,)) if self.default is None: self.default = NoAction() def set_haptic(self, hapticdata): supports = False if self.default: supports = self.default.set_haptic(hapticdata) or supports for a in self.mods.values(): supports = a.set_haptic(hapticdata) or supports return supports def set_speed(self, x, y, z): supports = False if self.default: supports = self.default.set_speed(x, y, z) or supports for a in self.mods.values(): supports = a.set_speed(x, y, z) or supports return supports def strip(self): # Returns default action or action assigned to first modifier if self.default: return self.default.strip() if len(self.order) > 0: return self.mods[self.order[0]].strip() # Empty ModeModifier return NoAction() def compress(self): if self.default: self.default = self.default.compress() for button in self.mods: self.mods[button] = self.mods[button].compress() return self def __str__(self): rv = [ ] for key in self.mods: rv += [ key.name, self.mods[key] ] if self.default is not None: rv += [ self.default ] return "<Modifier '%s', %s>" % (self.COMMAND, rv) __repr__ = __str__ def describe(self, context): l = [] if self.default : l.append(self.default) for x in self.order: l.append(self.mods[x]) return "\n".join([ x.describe(context) for x in l ]) def to_string(self, multiline=False, pad=0): if multiline: rv = [ (" " * pad) + "mode(" ] for key in self.mods: a_str = self.mods[key].to_string(True).split("\n") a_str[0] = (" " * pad) + " " + (key.name + ",").ljust(11) + a_str[0] # Key has to be one of SCButtons for i in xrange(1, len(a_str)): a_str[i] = (" " * pad) + " " + a_str[i] a_str[-1] = a_str[-1] + "," rv += a_str if self.default is not None: a_str = [ (" " * pad) + " " + x for x in self.default.to_string(True).split("\n") ] rv += a_str if rv[-1][-1] == ",": rv[-1] = rv[-1][0:-1] rv += [ (" " * pad) + ")" ] return "\n".join(rv) else: rv = [ ] for key in self.mods: rv += [ key.name, self.mods[key].to_string(False) ] if self.default is not None: rv += [ self.default.to_string(False) ] return "mode(" + ", ".join(rv) + ")" def encode(self): rv = self.default.encode() rv['modes'] = {} for key in self.mods: rv['modes'][key.name] = self.mods[key].encode() if self.name: rv['name'] = self.name return rv def select(self, mapper): """ Selects action by pressed button. """ for b in self.order: if mapper.is_pressed(b): return self.mods[b] return self.default def select_b(self, mapper): """ Same as select but returns button as well. """ for b in self.order: if mapper.is_pressed(b): return b, self.mods[b] return None, self.default def button_press(self, mapper): sel = self.select(mapper) self.held_buttons.add(sel) return sel.button_press(mapper) def button_release(self, mapper): # Releases all held buttons, not just button that matches # currently pressed modifier for b in self.held_buttons: b.button_release(mapper) def trigger(self, mapper, position, old_position): if position < ModeModifier.MIN_TRIGGER: for b in self.held_triggers: b.trigger(mapper, 0, self.held_triggers[b]) self.held_triggers = {} return False else: sel = self.select(mapper) self.held_triggers[sel] = position return sel.trigger(mapper, position, old_position) def axis(self, mapper, position, what): return self.select(mapper).axis(mapper, position, what) def gyro(self, mapper, pitch, yaw, roll, *q): sel = self.select(mapper) if sel is not self.old_gyro: if self.old_gyro: self.old_gyro.gyro(mapper, 0, 0, 0, *q) self.old_gyro = sel return sel.gyro(mapper, pitch, yaw, roll, *q) def pad(self, mapper, position, what): return self.select(mapper).pad(mapper, position, what) def whole(self, mapper, x, y, what): if what == STICK: if abs(x) < ModeModifier.MIN_STICK and abs(y) < ModeModifier.MIN_STICK: for b in self.held_sticks: b.whole(mapper, 0, 0, what) self.held_sticks.clear() else: self.held_sticks.add(self.select(mapper)) for b in self.held_sticks: b.whole(mapper, x, y, what) else: return self.select(mapper).whole(mapper, x, y, what)
class ModeModifier(Modifier): COMMAND = "mode" PROFILE_KEYS = ("modes",) MIN_TRIGGER = 2 # When trigger is bellow this position, list of held_triggers is cleared MIN_STICK = 2 # When abs(stick) < MIN_STICK, stick is considered released and held_sticks is cleared PROFILE_KEY_PRIORITY = 2 def __init__(self, *stuff): 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)
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