diff --git a/scc/actions.py b/scc/actions.py index f234e4f8..945494c0 100644 --- a/scc/actions.py +++ b/scc/actions.py @@ -4,11 +4,11 @@ stick, pad or trigger is generated - typicaly what emulated button, stick or trigger should be pressed. """ +from __future__ import annotations from scc.tools import _ from typing import TYPE_CHECKING if TYPE_CHECKING: from scc.mapper import Mapper - from scc.tools import ensure_size, quat2euler, anglediff from scc.tools import circle_to_square, clamp, nameof from scc.uinput import Keys, Axes, Rels @@ -94,7 +94,7 @@ class Action(object): def __init__(self, *parameters): self.parameters = parameters - self.name = None + self.name: str = None self.delay_after = DEFAULT_DELAY # internal, insignificant and never saved value used only by editor. # Has to be set to iterable of callbacks to do something usefull; @@ -250,7 +250,7 @@ def compress(self): return self - def button_press(self, mapper: "Mapper"): + def button_press(self, mapper: Mapper): """ Called when action is executed by pressing physical gamepad button. 'button_release' will be called later. @@ -258,7 +258,7 @@ def button_press(self, mapper: "Mapper"): log.warning("Action %s can't handle button press event", self.__class__.__name__) - def button_release(self, mapper: "Mapper"): + def button_release(self, mapper: Mapper): """ Called when action executed by pressing physical gamepad button is expected to stop. @@ -266,7 +266,7 @@ def button_release(self, mapper: "Mapper"): log.warning("Action %s can't handle button release event", self.__class__.__name__) - def axis(self, mapper: "Mapper", position, what): + def axis(self, mapper: Mapper, position, what): """ Called when action is executed by moving physical stickm when stick has different actions for different axes defined. @@ -278,7 +278,7 @@ def axis(self, mapper: "Mapper", position, what): log.warning("Action %s can't handle axis event", self.__class__.__name__) - def pad(self, mapper: "Mapper", position, what): + def pad(self, mapper: Mapper, position, what): """ Called when action is executed by touching physical pad, when pad has different actions for different axes defined. @@ -291,16 +291,16 @@ def pad(self, mapper: "Mapper", position, what): return self.axis(mapper, position, what) - def gyro(self, mapper: "Mapper", pitch, yaw, roll, q1, q2, q3, q4) -> None: - """ - Called when action is set by rotating gyroscope. + def gyro(self, mapper: Mapper, pitch, yaw, roll, q1, q2, q3, q4) -> None: + """Called when action is set by rotating gyroscope. + 'pitch', 'yaw' and 'roll' represents change in gyroscope rotations. 'q1' to 'q4' represents current rotations expressed as quaterion. """ pass - def whole(self, mapper: "Mapper", x, y, what) -> None: + def whole(self, mapper: Mapper, x, y, what) -> None: """ Called when action is executed by moving physical stick or touching physical pad, when one action is defined for whole pad or stick. @@ -312,7 +312,7 @@ def whole(self, mapper: "Mapper", x, y, what) -> None: log.warning("Action %s can't handle whole stick event", self.__class__.__name__) - def whole_blocked(self, mapper: "Mapper", x, y, what) -> None: + def whole_blocked(self, mapper: Mapper, x, y, what) -> None: """ Special case called when ClickModifier is used and prevents 'whole' to be called because finger moves over non-pressed pad. @@ -320,7 +320,7 @@ def whole_blocked(self, mapper: "Mapper", x, y, what) -> None: pass - def add(self, mapper: "Mapper", dx, dy): + def add(self, mapper: Mapper, dx, dy): """ Called from BallModifier while virtual "ball" is rolling. @@ -329,7 +329,7 @@ def add(self, mapper: "Mapper", dx, dy): self.whole(mapper, dx, dy, None) - def change(self, mapper: "Mapper", dx, dy, what) -> None: + def change(self, mapper: Mapper, dx, dy, what) -> None: """ Called from CircularModifier to indicate incremental (or decremental) change in value. @@ -339,7 +339,7 @@ def change(self, mapper: "Mapper", dx, dy, what) -> None: log.warning("Action %s can't handle incremental changes", self.__class__.__name__) - def cancel(self, mapper: "Mapper") -> None: + def cancel(self, mapper: Mapper) -> None: """ Called when profile is changed to give action chance to cancel long-running effects it may have created. @@ -388,7 +388,7 @@ def _encode_parameter(parameter): return nameof(parameter) - def trigger(self, mapper: "Mapper", position, old_position): + def trigger(self, mapper: Mapper, position, old_position): """ Called when action is executed by pressing (or releasing) physical trigger. @@ -450,46 +450,46 @@ def __init__(self, what, op, value): else: raise ValueError("'%s' is not trigger nor axis" % (nameof(what), )) - def cmp_or(self, mapper: "Mapper"): + def cmp_or(self, mapper: Mapper): return any([ x(mapper) for x in self.children ]) - def cmp_gt(self, mapper: "Mapper"): + def cmp_gt(self, mapper: Mapper): if mapper.state is None: return False state = float(getattr(mapper.state, self.axis_name)) / self.max return state > self.value - def cmp_lt(self, mapper: "Mapper"): + def cmp_lt(self, mapper: Mapper): if mapper.state is None: return False state = float(getattr(mapper.state, self.axis_name)) / self.max return state < self.value - def cmp_ge(self, mapper: "Mapper"): + def cmp_ge(self, mapper: Mapper): if mapper.state is None: return False state = float(getattr(mapper.state, self.axis_name)) / self.max return state >= self.value - def cmp_le(self, mapper: "Mapper"): + def cmp_le(self, mapper: Mapper): if mapper.state is None: return False state = float(getattr(mapper.state, self.axis_name)) / self.max return state <= self.value - def cmp_labs(self, mapper: "Mapper"): + def cmp_labs(self, mapper: Mapper): if mapper.state is None: return False state = float(getattr(mapper.state, self.axis_name)) / self.max return abs(state) < self.value - def cmp_gabs(self, mapper: "Mapper"): + def cmp_gabs(self, mapper: Mapper): if mapper.state is None: return False state = float(getattr(mapper.state, self.axis_name)) / self.max return abs(state) > self.value - def __call__(self, mapper: "Mapper"): + def __call__(self, mapper: Mapper): return self.op_method(mapper) @@ -537,7 +537,7 @@ class SpecialAction(object): """ SA = "" - def execute_named(self, name, mapper: "Mapper", *a): + def execute_named(self, name, mapper: Mapper, *a): sa = mapper.get_special_actions_handler() h_name = "on_sa_%s" % (name,) if sa is None: @@ -547,12 +547,12 @@ def execute_named(self, name, mapper: "Mapper", *a): else: log.warning("Mapper can't handle '%s' action" % (name,)) - def execute(self, mapper: "Mapper", *a): + def execute(self, mapper: Mapper, *a): return self.execute_named(self.SA, mapper, *a) # Prevent warnings when special action is bound to button - def button_press(self, mapper: "Mapper"): pass - def button_release(self, mapper: "Mapper"): pass + def button_press(self, mapper: Mapper): pass + def button_release(self, mapper: Mapper): pass class AxisAction(Action): @@ -662,12 +662,12 @@ def describe(self, context): return axis - def button_press(self, mapper: "Mapper"): + def button_press(self, mapper: Mapper): mapper.gamepad.axisEvent(self.id, AxisAction.clamp_axis(self.id, self.max)) mapper.syn_list.add(mapper.gamepad) - def button_release(self, mapper: "Mapper"): + def button_release(self, mapper: Mapper): mapper.gamepad.axisEvent(self.id, AxisAction.clamp_axis(self.id, self.min)) mapper.syn_list.add(mapper.gamepad) @@ -685,7 +685,7 @@ def clamp_axis(id, value): return int(max(OUTPUT_360_STICK_MIN, min(OUTPUT_360_STICK_MAX, value))) - def axis(self, mapper: "Mapper", position, what): + def axis(self, mapper: Mapper, position, what): p = float(position * self.speed - STICK_PAD_MIN) / (STICK_PAD_MAX - STICK_PAD_MIN) p = int((p * (self.max - self.min)) + self.min) p = AxisAction.clamp_axis(self.id, p) @@ -694,7 +694,7 @@ def axis(self, mapper: "Mapper", position, what): mapper.syn_list.add(mapper.gamepad) - def change(self, mapper: "Mapper", dx, dy, what): + def change(self, mapper: Mapper, dx, dy, what): """ Called from CircularModifier """ p = AxisAction.old_positions[self.id] p = clamp(-STICK_PAD_MAX, p + dx, STICK_PAD_MAX) @@ -702,12 +702,12 @@ def change(self, mapper: "Mapper", dx, dy, what): self.axis(mapper, p, None) - def add(self, mapper: "Mapper", dx, dy): + def add(self, mapper: Mapper, dx, dy): """ Called from BallModifier """ self.axis(mapper, clamp(STICK_PAD_MIN, dx, STICK_PAD_MAX), None) - def trigger(self, mapper: "Mapper", position, old_position): + def trigger(self, mapper: Mapper, position, old_position): p = float(position * self.speed - TRIGGER_MIN) / (TRIGGER_MAX - TRIGGER_MIN) p = int((p * (self.max - self.min)) + self.min) p = AxisAction.clamp_axis(self.id, p) @@ -785,7 +785,7 @@ def __init__(self): self.reset_wholehaptic() - def change(self, mapper: "Mapper", dx, dy, what): + def change(self, mapper: Mapper, dx, dy, what): self._ax += dx self._ay += dy @@ -795,7 +795,7 @@ def change(self, mapper: "Mapper", dx, dy, what): mapper.send_feedback(self.haptic) - def add(self, mapper: "Mapper", dx, dy): + def add(self, mapper: Mapper, dx, dy): self.change(mapper, dx, dy, None) @@ -857,7 +857,7 @@ def describe(self, context): return _("Mouse %s") % (self._mouse_axis.name.split("_", 1)[-1],) - def button_press(self, mapper: "Mapper"): + def button_press(self, mapper: Mapper): # This is generaly bad idea... if self._mouse_axis in (Rels.REL_WHEEL, Rels.REL_HWHEEL): self.change(mapper, 100000, 0, None) @@ -865,17 +865,17 @@ def button_press(self, mapper: "Mapper"): self.change(mapper, 100, 0, None) - def button_release(self, mapper: "Mapper"): + def button_release(self, mapper: Mapper): # Nothing pass - def axis(self, mapper: "Mapper", position, what): + def axis(self, mapper: Mapper, position, what): self.change(mapper, position * MouseAbsAction.MOUSE_FACTOR, 0, what) mapper.force_event.add(FE_STICK) - def pad(self, mapper: "Mapper", position, what): + def pad(self, mapper: Mapper, position, what): if mapper.is_touched(what): # Initial pad touch if not mapper.was_touched(what): @@ -890,11 +890,11 @@ def pad(self, mapper: "Mapper", position, what): self._old_pos = None - def change(self, mapper: "Mapper", dx, dy, what): + def change(self, mapper: Mapper, dx, dy, what): self.add(mapper, dx, dy) - def add(self, mapper: "Mapper", dx, dy): + def add(self, mapper: Mapper, dx, dy): """ Called from BallModifier """ if self.haptic: WholeHapticAction.change(self, mapper, dx, dy, None) @@ -912,7 +912,7 @@ def add(self, mapper: "Mapper", dx, dy): mapper.mouse_wheel(dx, 0) - def whole(self, mapper: "Mapper", x, y, what): + def whole(self, mapper: Mapper, x, y, what): #if what == STICK: # mapper.mouse_move(x * self.speed[0] * 0.01, y * self.speed[1] * 0.01) # mapper.force_event.add(FE_STICK) @@ -936,13 +936,13 @@ def whole(self, mapper: "Mapper", x, y, what): self._old_pos = None - def gyro(self, mapper: "Mapper", pitch, yaw, roll, *a): + def gyro(self, mapper: Mapper, pitch, yaw, roll, *a): if self._mouse_axis == YAW: mapper.mouse_move(yaw * -self.speed[0], pitch * -self.speed[1]) else: mapper.mouse_move(roll * -self.speed[0], pitch * -self.speed[1]) - def trigger(self, mapper: "Mapper", position, old_position): + def trigger(self, mapper: Mapper, position, old_position): delta = position - old_position self.add(mapper, delta, delta) # add() will figure out the axis from the action parameters @@ -996,7 +996,7 @@ def describe(self, context): return _("Mouse %s") % (self._mouse_axis.name.split("_", 1)[-1],) - def axis(self, mapper: "Mapper", position, what): + def axis(self, mapper: Mapper, position, what): mapper.force_event.add(FE_STICK) p = position * self.speed[0] * MouseAbsAction.MOUSE_FACTOR @@ -1011,7 +1011,7 @@ def axis(self, mapper: "Mapper", position, what): pad = axis - def whole(self, mapper: "Mapper", x, y, what): + def whole(self, mapper: Mapper, x, y, what): dx = x * self.speed[0] * MouseAbsAction.MOUSE_FACTOR dy = y * self.speed[0] * MouseAbsAction.MOUSE_FACTOR mapper.mouse.moveEvent(dx, dy, mapper.time_elapsed) @@ -1045,7 +1045,7 @@ def get_compatible_modifiers(self): return OSDEnabledAction.get_compatible_modifiers(self) - def transform_coords(self, mapper: "Mapper"): + def transform_coords(self, mapper: Mapper): """ Transform coordinates specified as action arguments in whatever current class represents into rectangle in pixels. @@ -1063,7 +1063,7 @@ class represents into rectangle in pixels. return self.coords - def transform_osd_coords(self, mapper: "Mapper"): + def transform_osd_coords(self, mapper: Mapper): """ Same as transform_coords, but returns coordinates in screen space even if action sets mouse position relative to window. @@ -1073,7 +1073,7 @@ def transform_osd_coords(self, mapper: "Mapper"): return self.transform_coords(mapper) - def set_mouse(self, mapper: "Mapper", x, y): + def set_mouse(self, mapper: Mapper, x, y): """ Performs final mouse position setting. Overrided by subclasses. @@ -1081,7 +1081,7 @@ def set_mouse(self, mapper: "Mapper", x, y): X.set_mouse_pos(mapper.get_xdisplay(), x, y) - def update_osd_area(self, area, mapper: "Mapper"): + def update_osd_area(self, area, mapper: Mapper): """ Updates area instance directly instead of calling daemon and letting it talking through socket. @@ -1090,7 +1090,7 @@ def update_osd_area(self, area, mapper: "Mapper"): area.update(int(x1), int(y1), int(x2-x1), int(y2-y1)) - def whole(self, mapper: "Mapper", x, y, what): + def whole(self, mapper: Mapper, x, y, what): if mapper.get_xdisplay() is None: log.warning("XServer is not available, cannot use 'AreaAction") return @@ -1127,7 +1127,7 @@ def whole(self, mapper: "Mapper", x, y, what): class RelAreaAction(AreaAction): COMMAND = "relarea" - def transform_coords(self, mapper: "Mapper"): + def transform_coords(self, mapper: Mapper): screen = X.get_screen_size(mapper.get_xdisplay()) x1, y1, x2, y2 = self.coords x1 = screen[0] * x1 @@ -1140,7 +1140,7 @@ def transform_coords(self, mapper: "Mapper"): class WinAreaAction(AreaAction): COMMAND = "winarea" - def transform_coords(self, mapper: "Mapper"): + def transform_coords(self, mapper: Mapper): if self.needs_query_screen: w_size = X.get_window_size(mapper.get_xdisplay(), mapper.get_current_window()) x1, y1, x2, y2 = self.coords @@ -1152,7 +1152,7 @@ def transform_coords(self, mapper: "Mapper"): return self.coords - def transform_osd_coords(self, mapper: "Mapper"): + def transform_osd_coords(self, mapper: Mapper): wx, wy, ww, wh = X.get_window_geometry(mapper.get_xdisplay(), mapper.get_current_window()) x1, y1, x2, y2 = self.coords x1 = wx + x1 if x1 >= 0 else wx + ww + x1 @@ -1162,14 +1162,14 @@ def transform_osd_coords(self, mapper: "Mapper"): return x1, y1, x2, y2 - def set_mouse(self, mapper: "Mapper", x, y): + def set_mouse(self, mapper: Mapper, x, y): X.set_mouse_pos(mapper.get_xdisplay(), x, y, mapper.get_current_window()) class RelWinAreaAction(WinAreaAction): COMMAND = "relwinarea" - def transform_coords(self, mapper: "Mapper"): + def transform_coords(self, mapper: Mapper): w_size = X.get_window_size(mapper.get_xdisplay(), mapper.get_current_window()) x1, y1, x2, y2 = self.coords x1 = w_size[0] * x1 @@ -1179,7 +1179,7 @@ def transform_coords(self, mapper: "Mapper"): return x1, y1, x2, y2 - def transform_osd_coords(self, mapper: "Mapper"): + def transform_osd_coords(self, mapper: Mapper): wx, wy, ww, wh = X.get_window_geometry(mapper.get_xdisplay(), mapper.get_current_window()) x1, y1, x2, y2 = self.coords x1 = wx + float(ww) * x1 @@ -1211,11 +1211,11 @@ def get_speed(self): return self.speed - def gyro(self, mapper: "Mapper", *pyr): + def gyro(self, mapper: Mapper, *pyr): for i in (0, 1, 2): axis = self.axes[i] # 'gyro' cannot map to mouse, but 'mouse' does that. - if axis in Axes.__members__.values() or type(axis) == int: + if axis in Axes.__members__.values() or type(axis) is int: mapper.gamepad.axisEvent(axis, AxisAction.clamp_axis(axis, pyr[i] * self.speed[i] * -10)) mapper.syn_list.add(mapper.gamepad) @@ -1261,7 +1261,7 @@ def get_previewable(self) -> bool: return True GYROAXES = (0, 1, 2) - def gyro(self, mapper: "Mapper", pitch, yaw, roll, q1, q2, q3, q4): + def gyro(self, mapper: Mapper, pitch, yaw, roll, q1, q2, q3, q4): if mapper.get_controller().flags & ControllerFlags.EUREL_GYROS: pyr = [q1 / 10430.37, q2 / 10430.37, q3 / 10430.37] # 2**15 / PI else: @@ -1310,7 +1310,7 @@ class ResetGyroAction(Action): """ COMMAND = "resetgyro" - def button_press(self, mapper: "Mapper"): + def button_press(self, mapper: Mapper): mapper.reset_gyros() @@ -1331,7 +1331,7 @@ def get_child_actions(self): return self.actions - def cancel(self, mapper: "Mapper"): + def cancel(self, mapper: Mapper): for a in self.actions: a.cancel(mapper) @@ -1389,7 +1389,7 @@ def get_compatible_modifiers(self): return Action.MOD_SENSITIVITY | Action.MOD_SENS_Z - def gyro(self, mapper: "Mapper", *pyr): + def gyro(self, mapper: Mapper, *pyr): q1, q2, q3, q4 = pyr[-4:] pyr = quat2euler(q1 / 32767.0, q2 / 32767.0, q3 / 32767.0, q4 / 32767.0) for j in (0, 1, 2): @@ -1550,7 +1550,7 @@ def get_compatible_modifiers(self): @staticmethod - def _button_press(mapper: "Mapper", button, immediate=False, haptic=None): + def _button_press(mapper: Mapper, button, immediate=False, haptic=None): if button in mapper.pressed and mapper.pressed[button] > 0: # Virtual button is already pressed - generate release event first pc = mapper.pressed[button] @@ -1576,7 +1576,7 @@ def _button_press(mapper: "Mapper", button, immediate=False, haptic=None): @staticmethod - def _button_release(mapper: "Mapper", button, immediate=False): + def _button_release(mapper: Mapper, button, immediate=False): if button in mapper.pressed: if mapper.pressed[button] > 1: # More than one action pressed this virtual button - decrease @@ -1600,17 +1600,17 @@ def _button_release(mapper: "Mapper", button, immediate=False): mapper.keyrelease_list.append(button) - def button_press(self, mapper: "Mapper"): + def button_press(self, mapper: Mapper): ButtonAction._button_press(mapper, self.button, haptic=self.haptic) if self.haptic: mapper.send_feedback(self.haptic) - def button_release(self, mapper: "Mapper"): + def button_release(self, mapper: Mapper): ButtonAction._button_release(mapper, self.button) - def whole(self, mapper: "Mapper", x, y, what): + def whole(self, mapper: Mapper, x, y, what): if what == STICK: # Stick used used as one big button (probably as part of ring bindings) if abs(x) < ButtonAction.STICK_DEADZONE and abs(y) < ButtonAction.STICK_DEADZONE: @@ -1636,7 +1636,7 @@ def whole(self, mapper: "Mapper", x, y, what): self.button_release(mapper) - def axis(self, mapper: "Mapper", position, what): + def axis(self, mapper: Mapper, position, what): # Choses which key or button should be pressed or released based on # current stick position. @@ -1656,7 +1656,7 @@ def axis(self, mapper: "Mapper", position, what): self._pressed_key = self.button2 - def trigger(self, mapper: "Mapper", p, old_p): + def trigger(self, mapper: Mapper, p, old_p): # Choses which key or button should be pressed or released based on # current trigger position. # TODO: Remove this, call to TriggerAction instead @@ -1691,7 +1691,7 @@ def trigger(self, mapper: "Mapper", p, old_p): self._released = True - def change(self, mapper: "Mapper", dx, dy, what): + def change(self, mapper: Mapper, dx, dy, what): """ Makes sense with circular() modifier """ self._change += dx if self._change < -ButtonAction.CIRCULAR_INTERVAL: @@ -1992,7 +1992,7 @@ def compute_side(self, x, y): return side - def whole(self, mapper: "Mapper", x, y, what): + def whole(self, mapper: Mapper, x, y, what): if self.haptic: # Called like this just so there is not same code on two places side = self.whole_blocked(mapper, x, y, what) @@ -2010,7 +2010,7 @@ def whole(self, mapper: "Mapper", x, y, what): self.dpad_state[i] = side[i] - def whole_blocked(self, mapper: "Mapper", x, y, what): + def whole_blocked(self, mapper: Mapper, x, y, what): if self.haptic: side = self.compute_side(x, y) if self.side_before != side: @@ -2020,7 +2020,7 @@ def whole_blocked(self, mapper: "Mapper", x, y, what): return None - def change(self, mapper: "Mapper", dx, dy, what): + def change(self, mapper: Mapper, dx, dy, what): self.whole(mapper, dx, -dy, what) @@ -2053,7 +2053,7 @@ def describe(self, context): return "8-Way DPad" - def whole(self, mapper: "Mapper", x, y, what): + def whole(self, mapper: Mapper, x, y, what): side = self.compute_side(x, y) # 8-way dpad presses only one button at time, so only one index @@ -2129,7 +2129,7 @@ def to_string(self, multiline=False, pad=0): return MultichildAction.to_string(self, multiline, pad) - def whole(self, mapper: "Mapper", x, y, what): + def whole(self, mapper: Mapper, x, y, what): if what == STICK or mapper.is_touched(what): angle = atan2(x, y) distance = sqrt(x*x + y*y) @@ -2202,8 +2202,7 @@ def get_compatible_modifiers(self): | self.x.get_compatible_modifiers() | self.y.get_compatible_modifiers() ) - if isinstance(self.x, AxisAction) and isinstance(self.y, AxisAction): - if self.x.get_axis() in (Axes.ABS_X, Axes.ABS_Y, Axes.ABS_RX, Axes.ABS_RY): + if isinstance(self.x, AxisAction) and isinstance(self.y, AxisAction) and self.x.get_axis() in (Axes.ABS_X, Axes.ABS_Y, Axes.ABS_RX, Axes.ABS_RY): mods = (mods | Action.MOD_BALL) & ~Action.MOD_SMOOTH return mods @@ -2266,7 +2265,7 @@ def get_previewable(self): return self.x.get_previewable() and self.y.get_previewable() - def _add(self, mapper: "Mapper", x, y): + def _add(self, mapper: Mapper, x, y): """ Not always available """ if self.haptic: WholeHapticAction.add(self, mapper, x, y) @@ -2278,7 +2277,7 @@ def _add(self, mapper: "Mapper", x, y): WholeHapticAction.add(self, mapper, x, y) - def whole(self, mapper: "Mapper", x, y, what): + def whole(self, mapper: Mapper, x, y, what): if self.haptic: distance = sqrt(x*x + y*y) is_close = distance > STICK_PAD_MAX * 2 / 3 @@ -2359,7 +2358,7 @@ def describe(self, context): return _("Joystick Camera") - def whole(self, mapper: "Mapper", x, y, what): + def whole(self, mapper: Mapper, x, y, what): if what in (LEFT, RIGHT, CPAD): if not mapper.is_touched(what): return XYAction.whole(self, mapper, 0, 0, what) @@ -2418,7 +2417,7 @@ def compress(self): return self - def _press(self, mapper: "Mapper"): + def _press(self, mapper: Mapper): """ Called when trigger level enters active zone """ self.pressed = True if self.haptic: @@ -2427,7 +2426,7 @@ def _press(self, mapper: "Mapper"): self.action.button_press(mapper) - def _release(self, mapper: "Mapper", old_position): + def _release(self, mapper: Mapper, old_position): """ Called when trigger level leaves active zone """ self.pressed = False if self.child_is_axis: @@ -2436,7 +2435,7 @@ def _release(self, mapper: "Mapper", old_position): self.action.button_release(mapper) - def trigger(self, mapper: "Mapper", position, old_position): + def trigger(self, mapper: Mapper, position, old_position): # There are 3 modes that TriggerAction can work in if self.release_level > self.press_level: # Mode 1, action is 'pressed' if current level is @@ -2544,7 +2543,7 @@ def compress(self): self.fullpress_action = self.fullpress_action.compress() return self - def on_timeout(self, mapper: "Mapper", *a): + def on_timeout(self, mapper: Mapper, *a): if self.waiting_task: self.waiting_task = None if self.range == "PARTIALPRESS": @@ -2553,31 +2552,31 @@ def on_timeout(self, mapper: "Mapper", *a): mapper.send_feedback(self.haptic) self._partial_press(mapper) - def _partial_press(self, mapper: "Mapper"): + def _partial_press(self, mapper: Mapper): self.partialpress_active = True if self.haptic: mapper.send_feedback(self.haptic) self.partialpress_action.button_press(mapper) - def _partial_release(self, mapper: "Mapper"): + def _partial_release(self, mapper: Mapper): self.partialpress_active = False if self.haptic: mapper.send_feedback(self.haptic) self.partialpress_action.button_release(mapper) - def _full_press(self, mapper: "Mapper"): + def _full_press(self, mapper: Mapper): if self.haptic: mapper.send_feedback(self.haptic) self.fullpress_action.button_press(mapper) - def _full_release(self, mapper: "Mapper"): + def _full_release(self, mapper: Mapper): if self.haptic: mapper.send_feedback(self.haptic) self.fullpress_action.button_release(mapper) - def trigger(self, mapper: "Mapper", position, old_position): + def trigger(self, mapper: Mapper, position, old_position): # Checks the current position of the trigger and apply the action based on three possible range: [None, PARTIALPRESS, FULLPRESS] # Checks full press first to prevent unnecessary conditional evaluation diff --git a/scc/drivers/hiddrv.py b/scc/drivers/hiddrv.py index f13dd12f..8023b620 100644 --- a/scc/drivers/hiddrv.py +++ b/scc/drivers/hiddrv.py @@ -3,6 +3,7 @@ Borrows bit of code and configuration from evdevdrv. """ from __future__ import annotations + import ctypes import json import logging @@ -12,6 +13,9 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from usb1 import USBDeviceHandle + + from scc.device_monitor import DeviceMonitor from scc.sccdaemon import SCCDaemon from scc.constants import STICK_PAD_MAX, STICK_PAD_MIN, ControllerFlags, SCButtons @@ -49,9 +53,9 @@ BLACKLIST = [ # List of devices known to pretend to be HID compatible but breaking horribly with HID # vendor, product - (0x045e, 0x0719), # Xbox controller - (0x045e, 0x028e), # Xbox wireless adapter - (0x0738, 0x4716), # Mad Catz, Inc controller + (0x045e, 0x0719), # Xbox controller + (0x045e, 0x028e), # Xbox wireless adapter + (0x0738, 0x4716), # Mad Catz, Inc controller ] @@ -64,28 +68,28 @@ class UnparsableDescriptor(HIDDrvError): class HIDControllerInput(ctypes.Structure): _fields_ = [ - ('buttons', ctypes.c_uint32), + ("buttons", ctypes.c_uint32), # Note: Axis order is same as in AxisType enum - ('lpad_x', ctypes.c_int32), - ('lpad_y', ctypes.c_int32), - ('rpad_x', ctypes.c_int32), - ('rpad_y', ctypes.c_int32), - ('stick_x', ctypes.c_int32), - ('stick_y', ctypes.c_int32), - ('ltrig', ctypes.c_int32), - ('rtrig', ctypes.c_int32), - ('accel_x', ctypes.c_int32), - ('accel_y', ctypes.c_int32), - ('accel_z', ctypes.c_int32), - ('gpitch', ctypes.c_int32), - ('groll', ctypes.c_int32), - ('gyaw', ctypes.c_int32), - ('q1', ctypes.c_int32), - ('q2', ctypes.c_int32), - ('q3', ctypes.c_int32), - ('q4', ctypes.c_int32), - ('cpad_x', ctypes.c_int32), - ('cpad_y', ctypes.c_int32), + ("lpad_x", ctypes.c_int32), + ("lpad_y", ctypes.c_int32), + ("rpad_x", ctypes.c_int32), + ("rpad_y", ctypes.c_int32), + ("stick_x", ctypes.c_int32), + ("stick_y", ctypes.c_int32), + ("ltrig", ctypes.c_int32), + ("rtrig", ctypes.c_int32), + ("accel_x", ctypes.c_int32), + ("accel_y", ctypes.c_int32), + ("accel_z", ctypes.c_int32), + ("gpitch", ctypes.c_int32), + ("groll", ctypes.c_int32), + ("gyaw", ctypes.c_int32), + ("q1", ctypes.c_int32), + ("q2", ctypes.c_int32), + ("q3", ctypes.c_int32), + ("q4", ctypes.c_int32), + ("cpad_x", ctypes.c_int32), + ("cpad_y", ctypes.c_int32), ] @@ -196,7 +200,7 @@ class HIDDecoder(ctypes.Structure): HIDDecoderPtr = ctypes.POINTER(HIDDecoder) -_lib = find_library('libhiddrv') +_lib = find_library("libhiddrv") _lib.decode.restype = bool _lib.decode.argtypes = [ HIDDecoderPtr, ctypes.c_char_p ] @@ -207,7 +211,7 @@ class HIDController(USBDevice, Controller): | ControllerFlags.HAS_DPAD | ControllerFlags.NO_GRIPS ) - def __init__(self, device, daemon: "SCCDaemon", handle, config_file, config, test_mode=False): + def __init__(self, device: USBDevice, daemon: SCCDaemon, handle: USBDeviceHandle, config_file: str, config: dict, test_mode: bool = False): USBDevice.__init__(self, device, handle) self._ready = False self.daemon = daemon @@ -225,7 +229,7 @@ def __init__(self, device, daemon: "SCCDaemon", handle, config_file, config, tes max_size = endpoint.getMaxPacketSize() if id is None: - raise NotHIDDevice() + raise NotHIDDevice log.debug("Endpoint: %s", id) @@ -255,17 +259,17 @@ def __init__(self, device, daemon: "SCCDaemon", handle, config_file, config, tes self._ready = True - def _load_hid_descriptor(self, config, max_size, vid: int, pid: int, test_mode): + def _load_hid_descriptor(self, config: dict, max_size: int, vid: int, pid: int, test_mode) -> None: hid_descriptor = HIDController.find_sys_devices_descriptor(vid, pid) if hid_descriptor is None: hid_descriptor = self.handle.getRawDescriptor( LIBUSB_DT_REPORT, 0, 512) - open("report", "wb").write(bytes([x for x in hid_descriptor ])) + open("report", "wb").write(bytes(list(hid_descriptor))) self._build_hid_decoder(hid_descriptor, config, max_size) self._packet_size = self._decoder.packet_size - def _build_button_map(self, config): + def _build_button_map(self, config: dict): """Return button map readed from configuration, in format situable for HIDDecoder.buttons.button_map field. Generates default if config is not available. @@ -273,7 +277,7 @@ def _build_button_map(self, config): if config: # Last possible value is default "maps-to-nothing" mapping buttons = [BUTTON_COUNT - 1] * BUTTON_COUNT - for keycode, value in config.get('buttons', {}).items(): + for keycode, value in config.get("buttons", {}).items(): keycode = int(keycode) - FIRST_BUTTON if keycode < 0 or keycode >= BUTTON_COUNT: # Out of range @@ -290,7 +294,7 @@ def _build_button_map(self, config): @staticmethod - def button_to_bit(sc): + def button_to_bit(sc) -> int: sc, bit = int(sc), 0 while sc and (sc & 1 == 0): sc >>= 1 @@ -300,12 +304,12 @@ def button_to_bit(sc): return BUTTON_COUNT - 1 - def _build_axis_maping(self, axis, config, mode = AxisMode.AXIS): + def _build_axis_maping(self, axis, config: dict, mode = AxisMode.AXIS): """Convert configuration mapping for _one_ axis to value situable for self._decoder.axes field.""" axis_config = config.get("axes", {}).get(str(int(axis))) if axis_config: try: - target = ( list([ x for (x, y) in HIDControllerInput._fields_ ]) + target = ( [ x for (x, y) in HIDControllerInput._fields_ ] .index(axis_config.get("axis")) - 1 ) except Exception: # Maps to unknown axis @@ -331,8 +335,8 @@ def _build_axis_maping(self, axis, config, mode = AxisMode.AXIS): data = AxisDataUnion( hatswitch = HatswitchModeData( button = button, - max = axis_config['max'], - min = axis_config['min'] + max = axis_config["max"], + min = axis_config["min"] ) ) ) @@ -342,7 +346,7 @@ def _build_axis_maping(self, axis, config, mode = AxisMode.AXIS): return None, None - def _build_hid_decoder(self, data, config, max_size): + def _build_hid_decoder(self, data, config: dict, max_size: int) -> None: size, count, total, kind = 1, 0, 0, None next_axis = AxisType.AXIS_LPAD_X self._decoder = HIDDecoder() @@ -444,12 +448,11 @@ def find_sys_devices_descriptor(vid: int, pid: int) -> str | None: as some controllers are presenting descriptor that are completly broken and kernel already deals with it. """ - def recursive_search(pattern: str, path: str): + def recursive_search(pattern: str, path: str) -> str | None: for name in os.listdir(path): full_path = os.path.join(path, name) - if name == "report_descriptor": - if pattern in os.path.split(path)[-1].lower(): - return full_path + if name == "report_descriptor" and pattern in os.path.split(path)[-1].lower(): + return full_path try: if os.path.islink(full_path): # Recursive stuff in /sys ftw... @@ -490,11 +493,10 @@ def get_type(self) -> str: def _generate_id(self) -> str: - """ - ID is generated as 'hid0000:1111' where first number is vendor and - 2nd product id. If two or more controllers with same vendor/product - IDs are added, ':X' is added, where 'X' starts as 1 and increases - as controllers with same ids are connected. + """ID is generated as 'hid0000:1111' where first number is vendor and 2nd product id. + + If two or more controllers with same vendor/product IDs are added, + ':X' is added, where 'X' starts as 1 and increases as controllers with same ids are connected. """ magic_number = 1 vid, pid = self.device.getVendorID(), self.device.getProductID() @@ -509,7 +511,7 @@ def get_id(self) -> str: return self._id - def get_gui_config_file(self): + def get_gui_config_file(self) -> str: return self.config_file @@ -518,7 +520,7 @@ def __repr__(self) -> str: return "" % (vid, pid) - def test_input(self, endpoint, data): + def test_input(self, endpoint: int, data): if not _lib.decode(ctypes.byref(self._decoder), data): # Returns True if anything changed return @@ -544,7 +546,7 @@ def test_input(self, endpoint, data): sys.stdout.flush() - def input(self, endpoint, data): + def input(self, endpoint: int, data): if _lib.decode(ctypes.byref(self._decoder), data): if self.mapper: self.mapper.input(self, @@ -576,7 +578,7 @@ def set_gyro_enabled(self, enabled): class HIDDrv(object): - def __init__(self, daemon): + def __init__(self, daemon: SCCDaemon): self.registered = set() self.config_files = {} self.configs = {} @@ -584,20 +586,16 @@ def __init__(self, daemon): self.daemon = daemon - def hotplug_cb(self, device, handle): + def hotplug_cb(self, device: USBDevice, handle: USBDeviceHandle) -> HIDController | None: vid, pid = device.getVendorID(), device.getProductID() if (vid, pid) in self.configs: - controller = HIDController(device, self.daemon, handle, - self.config_files[vid, pid], self.configs[vid, pid]) - return controller + return HIDController(device, self.daemon, handle, self.config_files[vid, pid], self.configs[vid, pid]) + return None - def scan_files(self): - """ - Goes through ~/.config/scc/devices and enables hotplug callback for - every known HID device - """ + def scan_files(self) -> None: + """Go through ~/.config/scc/devices and enable hotplug callback for every known HID device.""" path = os.path.join(get_config_path(), "devices") if not os.path.exists(path): # Nothing to do @@ -634,14 +632,14 @@ def scan_files(self): if (vid, pid) in self.configs: del self.configs[vid, pid] -def hiddrv_test(cls, args): - """ - Small input test used by GUI while setting up the device. +def hiddrv_test(cls, args) -> None: + """Small input test used by GUI while setting up the device. + Basically, if HID device works with this, it will work with daemon as well. """ - from scc.poller import Poller - from scc.drivers.usb import _usb from scc.device_monitor import create_device_monitor + from scc.drivers.usb import _usb + from scc.poller import Poller from scc.scripts import InvalidArguments try: @@ -654,26 +652,27 @@ def hiddrv_test(cls, args): class FakeDaemon(object): - def __init__(self): + def __init__(self) -> None: self.poller = Poller() self.dev_monitor = create_device_monitor(self) self.exitcode = -1 - def get_device_monitor(self): + def get_device_monitor(self) -> DeviceMonitor: return self.dev_monitor - def add_error(self, id, error): + def add_error(self, id, error: str) -> None: fake_daemon.exitcode = 2 log.error(error) - def remove_error(*a): pass + def remove_error(*a) -> None: + pass - def get_poller(self): + def get_poller(self) -> Poller: return self.poller fake_daemon = FakeDaemon() - def cb(device, handle): + def cb(device: USBDevice, handle: USBDeviceHandle): try: return cls(device, None, handle, None, None, test_mode=True) except NotHIDDevice: @@ -701,15 +700,15 @@ def cb(device, handle): return fake_daemon.exitcode -def init(daemon, config: dict) -> bool: - """ Called from scc-daemon """ +def init(daemon: SCCDaemon, config: dict) -> bool: + """Called from scc-daemon.""" d = HIDDrv(daemon) daemon.add_on_rescan(d.scan_files) return True if __name__ == "__main__": - """ Called when executed as script """ + """Called when executed as script.""" from scc.tools import init_logging, set_logging_level init_logging() set_logging_level(True, True) diff --git a/scc/drivers/usb.py b/scc/drivers/usb.py index 2e1d59a7..50a42203 100644 --- a/scc/drivers/usb.py +++ b/scc/drivers/usb.py @@ -18,6 +18,8 @@ import usb1 if TYPE_CHECKING: + from usb1 import USBDeviceHandle + from scc.sccdaemon import SCCDaemon log = logging.getLogger("USB") @@ -25,7 +27,7 @@ class USBDevice(object): """Base class for all handled usb devices.""" - def __init__(self, device, handle): + def __init__(self, device: USBDevice, handle: USBDeviceHandle) -> None: self.device = device self.handle = handle self._claimed = [] @@ -79,10 +81,7 @@ def send_control(self, index, data): def overwrite_control(self, index, data): - """ - Similar to send_control, but this one checks and overwrites - already scheduled controll for same device/index. - """ + """Similar to send_control, but this one checks and overwrites already scheduled controll for same device/index.""" for x in self._cmsg: x_index, x_data, x_timeout = x[-3:] # First 3 bytes are for PacketType, size and ConfigType @@ -93,8 +92,8 @@ def overwrite_control(self, index, data): def make_request(self, index, callback, data, size=64): - """ - Schedules synchronous request that requires response. + """Schedule a synchronous request that requires response. + Request is done ASAP and provided callback is called with received data. """ self._rmsg.append(( @@ -108,7 +107,7 @@ def make_request(self, index, callback, data, size=64): def flush(self): - """ Flushes all prepared control messages to the device """ + """Flushes all prepared control messages to the device.""" while len(self._cmsg): msg = self._cmsg.pop() self.handle.controlWrite(*msg) @@ -126,8 +125,8 @@ def flush(self): def force_restart(self): - """ - Restarts device, closes handle and tries to re-grab it again. + """Restart device, close handle and try to re-grab it again. + Don't use unless absolutelly necessary. """ tp = self.device.getVendorID(), self.device.getProductID() @@ -164,7 +163,7 @@ def claim_by(self, klass, subclass, protocol): def unclaim(self): - """ Unclaims all claimed interfaces """ + """Unclaim all claimed interfaces.""" for number in self._claimed: try: self.handle.releaseInterface(number) @@ -176,7 +175,7 @@ def unclaim(self): def close(self): - """ Called after device is disconnected """ + """Called after device is disconnected.""" try: self.unclaim() except Exception: diff --git a/scc/gui/action_editor.py b/scc/gui/action_editor.py index a29efec1..19fb5903 100644 --- a/scc/gui/action_editor.py +++ b/scc/gui/action_editor.py @@ -1,39 +1,49 @@ -#!/usr/bin/env python3 -""" -SC-Controller - Action Editor +"""SC-Controller - Action Editor. Also doubles as Menu Item Editor in some cases """ -from scc.tools import _ +from __future__ import annotations +import importlib +import logging +import math +import os +import types from gi.repository import Gtk, Gdk, GLib -from scc.actions import Action, XYAction, NoAction, RingAction, TriggerAction -from scc.special_actions import OSDAction, GesturesAction, MenuAction -from scc.modifiers import SmoothModifier, NameModifier, BallModifier -from scc.modifiers import Modifier, ClickModifier, ModeModifier -from scc.modifiers import SensitivityModifier, FeedbackModifier -from scc.modifiers import DeadzoneModifier, RotateInputModifier -from scc.constants import HapticPos, SCButtons -from scc.constants import CUT, ROUND, LINEAR, MINIMUM + +from scc.actions import Action, NoAction, RingAction, TriggerAction, XYAction +from scc.constants import CUT, LINEAR, MINIMUM, ROUND, HapticPos, SCButtons from scc.controller import HapticData -from scc.profile import Profile -from scc.macros import Macro -from scc.tools import nameof -from scc.gui.controller_widget import PRESSABLE, TRIGGERS, PADS -from scc.gui.controller_widget import STICKS, GYROS, BUTTONS -from scc.gui.modeshift_editor import ModeshiftEditor -from scc.gui.parser import InvalidAction, GuiActionParser -from scc.gui.simple_chooser import SimpleChooser -from scc.gui.macro_editor import MacroEditor -from scc.gui.ring_editor import RingEditor -from scc.gui.dwsnc import headerbar from scc.gui.ae import AEComponent +from scc.gui.controller_widget import BUTTONS, GYROS, PADS, PRESSABLE, STICKS, TRIGGERS +from scc.gui.dwsnc import headerbar from scc.gui.editor import Editor -import os, logging, math, importlib, types +from scc.gui.macro_editor import MacroEditor +from scc.gui.modeshift_editor import ModeshiftEditor +from scc.gui.parser import GuiActionParser, InvalidAction +from scc.gui.ring_editor import RingEditor +from scc.gui.simple_chooser import SimpleChooser +from scc.macros import Macro +from scc.modifiers import ( + BallModifier, + ClickModifier, + DeadzoneModifier, + FeedbackModifier, + ModeModifier, + Modifier, + NameModifier, + RotateInputModifier, + SensitivityModifier, + SmoothModifier, +) +from scc.profile import Profile +from scc.special_actions import GesturesAction, MenuAction, OSDAction +from scc.tools import _, nameof + log = logging.getLogger("ActionEditor") -COMPONENTS = ( # List of known modules (components) in scc.gui.ae package +COMPONENTS = ( # List of known modules (components) in scc.gui.ae package 'axis', 'axis_action', 'buttons', @@ -50,10 +60,10 @@ 'osk_action', 'osk_buttons', ) -XYZ = "XYZ" # Sensitivity settings keys -AFP = ("Amplitude", "Frequency", "Period") # Feedback settings keys -SMT = ("Level", "Weight", "Filter") # Smoothing setting keys -DZN = ("Lower", "Upper") # Deadzone settings key +XYZ = "XYZ" # Sensitivity settings keys +AFP = ("Amplitude", "Frequency", "Period") # Feedback settings keys +SMT = ("Level", "Weight", "Filter") # Smoothing setting keys +DZN = ("Lower", "Upper") # Deadzone settings key FEEDBACK_SIDES = [ HapticPos.LEFT, HapticPos.RIGHT, HapticPos.BOTH ] DEADZONE_MODES = [ CUT, ROUND, LINEAR, MINIMUM ] @@ -68,43 +78,43 @@ class ActionEditor(Editor): # Specified which modifiers are compatibile with which editor mode. # That way, stuff like Rotation settings is not shown when editor # is used to edit menu actions. - Action.AC_BUTTON : Action.MOD_OSD | Action.MOD_FEEDBACK, - Action.AC_TRIGGER : Action.MOD_OSD | Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK | Action.MOD_DEADZONE, - Action.AC_STICK : Action.MOD_OSD | Action.MOD_CLICK | Action.MOD_DEADZONE | Action.MOD_ROTATE | Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK | Action.MOD_SMOOTH, - Action.AC_PAD : Action.MOD_OSD | Action.MOD_CLICK | Action.MOD_DEADZONE | Action.MOD_ROTATE | Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK | Action.MOD_SMOOTH | Action.MOD_BALL, - Action.AC_GYRO : Action.MOD_OSD | Action.MOD_SENSITIVITY | Action.MOD_SENS_Z | Action.MOD_DEADZONE | Action.MOD_FEEDBACK, - Action.AC_OSK : 0, - Action.AC_MENU : Action.MOD_OSD, - AEC_MENUITEM : 0, + Action.AC_BUTTON: Action.MOD_OSD | Action.MOD_FEEDBACK, + Action.AC_TRIGGER: Action.MOD_OSD | Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK | Action.MOD_DEADZONE, + Action.AC_STICK: Action.MOD_OSD | Action.MOD_CLICK | Action.MOD_DEADZONE | Action.MOD_ROTATE | Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK | Action.MOD_SMOOTH, + Action.AC_PAD: Action.MOD_OSD | Action.MOD_CLICK | Action.MOD_DEADZONE | Action.MOD_ROTATE | Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK | Action.MOD_SMOOTH | Action.MOD_BALL, + Action.AC_GYRO: Action.MOD_OSD | Action.MOD_SENSITIVITY | Action.MOD_SENS_Z | Action.MOD_DEADZONE | Action.MOD_FEEDBACK, + Action.AC_OSK: 0, + Action.AC_MENU: Action.MOD_OSD, + AEC_MENUITEM: 0, } - def __init__(self, app, callback): + def __init__(self, app, callback) -> None: 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.friction = -1 # -1 for 'disabled' - self.click = False # Click modifier value. None for disabled - self.rotation_angle = 0 # RotateInputModifier angle - self.osd = False # 'OSD enabled' value. + 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, 1.0, 1.0] # Sensitivity slider values + self.sens_defaults = [1.0, 1.0, 1.0] # Clear button clears to this + self.feedback = [0.0, 0.0, 0.0] # Feedback slider values, set later + self.deadzone = [0, 0] # 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.friction = -1 # -1 for 'disabled' + self.click = False # Click modifier value. None for disabled + self.rotation_angle = 0 # RotateInputModifier angle + self.osd = False # 'OSD enabled' value. self.first_page_allowed = False self.setup_widgets() self.load_components() - self.ac_callback = callback # This is different callback than ButtonChooser uses + self.ac_callback = callback # This is different callback than ButtonChooser uses Editor.install_error_css() self._action = NoAction() self._replaced_action = None @@ -140,7 +150,7 @@ def setup_widgets(self): self.builder.get_object("lblSmooth%s" % (key,)), self.builder.get_object("sclSmooth%s" % (key,)), self.builder.get_object("btClearSmooth%s" % (key,)), - self.builder.get_object("sclSmooth%s" % (key,)).get_value() + self.builder.get_object("sclSmooth%s" % (key,)).get_value(), )) for key in DZN: i = DZN.index(key) @@ -149,7 +159,7 @@ def setup_widgets(self): self.builder.get_object("lblDZ%s" % (key,)), self.builder.get_object("sclDZ%s" % (key,)), self.builder.get_object("btClearDZ%s" % (key,)), - self.deadzone[i] # default value + self.deadzone[i], # default value )) if self.app.osd_mode: @@ -164,9 +174,9 @@ def load_components(self): self._selected_component = None - def load_component(self, class_name): - """ - Loads and adds new component to editor. + def load_component(self, class_name: str): + """Loads and adds new component to editor. + Returns component instance. """ if class_name in self.loaded_components: @@ -196,9 +206,7 @@ def on_Dialog_key_press_event(self, window, event): def set_osd_enabled(self, value): - """ - Sets value of OSD modifier checkbox, without firing any more events. - """ + """Sets value of OSD modifier checkbox, without firing any more events.""" self._recursing = True self.osd = value self.builder.get_object("cbOSD").set_active(value) @@ -215,7 +223,7 @@ def close(self): def get_id(self): - """ Returns ID of input that is being edited """ + """Returns ID of input that is being edited.""" return self.id @@ -253,9 +261,7 @@ def cb(): log.warning("Activated unknown link: %s", link) def on_action_type_changed(self, clicked_button): - """ - Called when user clicks on one of Action Type buttons. - """ + """Called when user clicks on one of Action Type buttons.""" # Prevent recurson if self._recursing : return self._recursing = True @@ -287,9 +293,9 @@ def on_action_type_changed(self, clicked_button): stActionModes.show_all() - def force_page(self, component, remove_rest=False): - """ - Forces action editor to display page with specified component. + def force_page(self, component, remove_rest: bool = False): + """Force action editor to display page with the specified component. + If 'remove_rest' is True, removes all other pages. Returns 'component' @@ -321,18 +327,18 @@ def force_page(self, component, remove_rest=False): def get_name(self): - """ Returns action name as set in editor entry """ + """Returns action name as set in editor entry.""" entName = self.builder.get_object("entName") return entName.get_text().strip(" \t") def get_current_page(self): - """ Returns currently displayed page (component) """ + """Returns currently displayed page (component).""" return self._selected_component def _set_title(self): - """ Copies title from text entry into action instance """ + """Copies title from text entry into action instance.""" entName = self.builder.get_object("entName") name = entName.get_text().strip(" \t\r\n") if len(name) < 1: @@ -425,8 +431,8 @@ def hide_editor(self): def hide_name(self): - """ - Hides (and clears) name field. + """Hide (and clear) name field. + Used when displaying ActionEditor from MacroEditor """ self.builder.get_object("lblName").set_visible(False) @@ -435,7 +441,7 @@ def hide_name(self): def hide_clear(self): - """ Hides clear buttton """ + """Hide clear buttton.""" self.builder.get_object("btClear").set_visible(False) @@ -528,9 +534,7 @@ def on_exMore_activate(self, ex, *a): def update_modifiers(self, *a): - """ - Called when sensitivity, feedback or other modifier setting changes. - """ + """Called when sensitivity, feedback or other modifier setting changes.""" if self._recursing : return cbRequireClick = self.builder.get_object("cbRequireClick") cbFeedbackSide = self.builder.get_object("cbFeedbackSide") @@ -558,7 +562,7 @@ def update_modifiers(self, *a): set_action = True # Sensitivity - for i in range(0, len(self.sens)): + for i in range(len(self.sens)): target = self.sens_widgets[i][0].get_value() if self.sens_widgets[i][3].get_active(): target = -target @@ -575,12 +579,12 @@ def update_modifiers(self, *a): self.feedback_position = feedback_position set_action = True - for i in range(0, len(self.feedback)): + for i in range(len(self.feedback)): if self.feedback[i] != self.feedback_widgets[i][0].get_value(): self.feedback[i] = self.feedback_widgets[i][0].get_value() set_action = True - for i in range(0, len(self.feedback)): + for i in range(len(self.feedback)): if self.feedback[i] != self.feedback_widgets[i][0].get_value(): self.feedback[i] = self.feedback_widgets[i][0].get_value() set_action = True @@ -592,7 +596,7 @@ def update_modifiers(self, *a): self.deadzone_mode = mode set_action = True - for i in range(0, len(self.deadzone)): + for i in range(len(self.deadzone)): if self.deadzone[i] != self.deadzone_widgets[i][1].get_value(): self.deadzone[i] = self.deadzone_widgets[i][1].get_value() set_action = True @@ -632,12 +636,10 @@ def update_modifiers(self, *a): self._selected_component.modifier_updated() - def generate_modifiers(self, action, from_custom=False): - """ - Returns Action with all modifiers from UI applied. - """ + def generate_modifiers(self, action, from_custom: bool = False): + """Return Action with all modifiers from UI applied.""" if not self._modifiers_enabled and not from_custom: - # Editing in custom aciton dialog, don't meddle with that + # Editing in custom action dialog, don't meddle with that return action if isinstance(action, ModeModifier): @@ -651,8 +653,7 @@ def generate_modifiers(self, action, from_custom=False): cm = action.get_compatible_modifiers() - if (cm & Action.MOD_BALL) != 0: - if self.friction >= 0: + if (cm & Action.MOD_BALL) != 0 and self.friction >= 0: action = BallModifier(round(self.friction, 3), action) if (cm & Action.MOD_SENSITIVITY) != 0: @@ -707,12 +708,10 @@ def generate_modifiers(self, action, from_custom=False): return action @staticmethod - def is_editable_modifier(action): - """ - Returns True if provided action is instance of modifier that - ActionEditor can display and edit. - Returns False for everything else, even if it is instalce of Modifier - subclass. + def is_editable_modifier(action) -> bool: + """Return True if provided action is instance of modifier that ActionEditor can display and edit. + + Return False for everything else, even if it is instalce of Modifier subclass. """ if isinstance(action, (ClickModifier, SensitivityModifier, DeadzoneModifier, FeedbackModifier, RotateInputModifier, @@ -726,9 +725,7 @@ def is_editable_modifier(action): @staticmethod def strip_modifiers(action): - """ - Returns action stripped of all modifiers that are editable by editor. - """ + """Return action stripped of all modifiers that are editable by editor.""" while action: if ActionEditor.is_editable_modifier(action): action = action.action @@ -737,10 +734,10 @@ def strip_modifiers(action): return action - def load_modifiers(self, action, index=-1): - """ - Parses action for modifiers and updates UI accordingly. - Returns action without parsed modifiers. + def load_modifiers(self, action, index: int = -1): + """Parse action for modifiers and update UI accordingly. + + Return action without parsed modifiers. """ cbRequireClick = self.builder.get_object("cbRequireClick") sclRotation = self.builder.get_object("sclRotation") @@ -772,7 +769,7 @@ def load_modifiers(self, action, index=-1): action = action.action if isinstance(action, SensitivityModifier): if index < 0: - for i in range(0, len(self.sens)): + for i in range(len(self.sens)): self.sens[i] = action.speeds[i] else: self.sens[index] = action.speeds[0] @@ -846,26 +843,18 @@ def load_modifiers(self, action, index=-1): return action - def allow_first_page(self): - """ - Allows first page to be used - """ + def allow_first_page(self) -> None: + """Allow the first page to be used.""" self.first_page_allowed = True - def reset_active_component(self): - """ - Forgets what component was selected so next call to set_action - selects new one. - """ + def reset_active_component(self) -> None: + """Forget what component was selected so next call to set_action selects new one.""" self._selected_component = None - def set_action(self, action, from_custom=False): - """ - Updates Action field(s) on bottom and recolors apropriate image area, - if such area exists. - """ + def set_action(self, action, from_custom: bool = False) -> None: + """Update Action field(s) on bottom and recolors apropriate image area, if such area exists.""" entAction = self.builder.get_object("entAction") cbPreview = self.builder.get_object("cbPreview") btOK = self.builder.get_object("btOK") @@ -1004,11 +993,11 @@ def enable_modifiers(self, action): cbOSD.set_sensitive(cm & Action.MOD_OSD != 0) - def set_sensitivity(self, x, y=1.0, z=1.0): - """ Sets sensitivity for edited action """ + def set_sensitivity(self, x: float, y: float = 1.0, z: float = 1.0): + """Sets sensitivity for edited action.""" self._recursing = True xyz = [ x, y, z ] - for i in range(0, len(self.sens)): + for i in range(len(self.sens)): self.sens[i] = xyz[i] self.sens_widgets[i][3].set_active(self.sens[i] < 0) self.sens_widgets[i][0].set_value(abs(self.sens[i])) @@ -1017,16 +1006,13 @@ def set_sensitivity(self, x, y=1.0, z=1.0): self._selected_component.modifier_updated() - def get_sensitivity(self): + def get_sensitivity(self) -> tuple[float, float, float]: """ Returns sensitivity currently set in editor """ return tuple(self.sens) - def set_default_sensitivity(self, x, y=1.0, z=1.0): - """ - Sets default sensitivity values and, if sensitivity - is currently set to defaults, updates it to these values - """ + def set_default_sensitivity(self, x: float, y: float = 1.0, z: float = 1.0): + """Sets default sensitivity values and, if sensitivity is currently set to defaults, updates it to these values.""" xyz = x, y, z update = False self._recursing = True @@ -1096,8 +1082,8 @@ def on_btClearFriction_clicked(self, *a): def set_input(self, id, action, mode=None): - """ - Setups action editor for editing specified input. + """Setups action editor for editing specified input. + Mode (buttton/axis/trigger...) is either provided or chosen based on id. Also sets title, but that can be overriden by calling set_title after. """ @@ -1159,9 +1145,8 @@ def set_input(self, id, action, mode=None): self.hide_ring() - def set_menu_item(self, item, title_for_name_label=None): - """ - Setups action editor in way that allows editing only action name. + def set_menu_item(self, item, title_for_name_label: bool | None = None): + """Setups action editor in way that allows editing only action name. In this mode, callback is called with editor instance instead of generated action as 2nd argument. @@ -1181,7 +1166,7 @@ def set_menu_item(self, item, title_for_name_label=None): self.builder.get_object("lblName").set_label(title_for_name_label) - def set_modifiers_enabled(self, enabled): + def set_modifiers_enabled(self, enabled: bool) -> None: exMore = self.builder.get_object("exMore") rvMore = self.builder.get_object("rvMore") if self._modifiers_enabled != enabled and not enabled: diff --git a/scc/gui/ae/gyro_action.py b/scc/gui/ae/gyro_action.py index 549b3e2b..1f49c1c1 100644 --- a/scc/gui/ae/gyro_action.py +++ b/scc/gui/ae/gyro_action.py @@ -1,24 +1,20 @@ -#!/usr/bin/env python3 -""" -SC-Controller - Action Editor - Gyro -> Joystick or Mouse component -""" -from scc.tools import _ - -from scc.actions import Action, NoAction, MouseAction, MultiAction, RangeOP -from scc.actions import GyroAction, GyroAbsAction, MouseAbsAction -from scc.special_actions import CemuHookAction +"""SC-Controller - Action Editor - Gyro -> Joystick or Mouse component.""" +import itertools +import logging +import re + +from scc.actions import Action, GyroAbsAction, GyroAction, MouseAbsAction, MouseAction, MultiAction, NoAction, RangeOP +from scc.constants import ROLL, STICK, YAW, SCButtons +from scc.gui.ae import AEComponent +from scc.gui.parser import GuiActionParser from scc.modifiers import ModeModifier, SensitivityModifier +from scc.special_actions import CemuHookAction +from scc.tools import _, nameof from scc.uinput import Axes, Rels -from scc.constants import SCButtons, STICK, YAW, ROLL -from scc.gui.parser import GuiActionParser -from scc.gui.ae import AEComponent -from scc.tools import nameof -import logging, re -import itertools log = logging.getLogger("AE.GyroAction") -__all__ = [ 'GyroActionComponent' ] +__all__ = [ "GyroActionComponent" ] TRIGGERS = ( nameof(SCButtons.LT), nameof(SCButtons.RT) ) @@ -64,8 +60,9 @@ def __init__(self, app, editor): self.parser = GuiActionParser() - def load(self): - if self.loaded : return + def load(self) -> None: + if self.loaded: + return AEComponent.load(self) self._recursing = True cbGyroButton = self.builder.get_object("cbGyroButton") @@ -127,7 +124,7 @@ def set_action(self, mode, action): self.modifier_updated() - def modifier_updated(self): + def modifier_updated(self) -> None: cbInvertY = self.builder.get_object("cbInvertY") sens = self.editor.get_sensitivity() inverted = len(sens) >= 2 and sens[1] < 0 @@ -140,7 +137,8 @@ def modifier_updated(self): def cbInvertY_toggled_cb(self, cb, *a): - if self._recursing: return + if self._recursing: + return sens = list(self.editor.get_sensitivity()) # Ensure that editor accepts Y sensitivity if len(sens) >= 2: diff --git a/scc/gui/ae/trigger.py b/scc/gui/ae/trigger.py index c836c6ca..a63a7fd4 100644 --- a/scc/gui/ae/trigger.py +++ b/scc/gui/ae/trigger.py @@ -1,27 +1,36 @@ -#!/usr/bin/env python3 -""" -SC-Controller - Action Editor - Trigger-as-button Component +"""SC-Controller - Action Editor - Trigger-as-button Component. Assigns one or two emulated buttons to trigger """ -from scc.tools import _ - -from gi.repository import Gtk, Gdk, GLib -from scc.constants import TRIGGER_MIN, TRIGGER_HALF, TRIGGER_CLICK, TRIGGER_MAX -from scc.constants import HIPFIRE_NORMAL, HIPFIRE_SENSIBLE, HIPFIRE_EXCLUSIVE -from scc.actions import TriggerAction, ButtonAction, AxisAction, MouseAction, HipfireAction -from scc.actions import Action, NoAction, MultiAction +import logging + +from scc.actions import ( + Action, + AxisAction, + ButtonAction, + HipfireAction, + MouseAction, + MultiAction, + NoAction, + TriggerAction, +) +from scc.constants import ( + HIPFIRE_EXCLUSIVE, + HIPFIRE_NORMAL, + HIPFIRE_SENSIBLE, + TRIGGER_CLICK, + TRIGGER_HALF, + TRIGGER_MAX, + TRIGGER_MIN, +) from scc.gui.ae import AEComponent, describe_action -from scc.gui.area_to_action import action_to_area -from scc.gui.simple_chooser import SimpleChooser from scc.gui.binding_editor import BindingEditor -from scc.modifiers import FeedbackModifier -from scc.gui.parser import InvalidAction +from scc.gui.simple_chooser import SimpleChooser +from scc.tools import _ -import os, logging log = logging.getLogger("AE.TriggerAB") -__all__ = [ 'TriggerComponent' ] +__all__ = [ "TriggerComponent" ] class TriggerComponent(AEComponent, BindingEditor): @@ -47,8 +56,8 @@ def handles(self, mode, action): @staticmethod def _split(action): - """ - Splits passed action so it can be displayed in UI. + """Split 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. @@ -110,8 +119,8 @@ def _split(action): @staticmethod def _strip_trigger(action): - """ - If passed action is TriggerAction, returns its child action. + """If passed action is TriggerAction, returns its child action. + Returns passed action otherwise. """ if isinstance(action, TriggerAction): @@ -120,8 +129,8 @@ def _strip_trigger(action): @staticmethod def _strip_hipfire(action): - """ - If passed action is HipfireAction, returns its childs action. + """If passed action is HipfireAction, returns its childs action. + Returns passed action otherwise. """ if isinstance(action, HipfireAction): @@ -244,7 +253,7 @@ def on_btFullPress_clicked(self, *a): def on_btAnalog_clicked(self, *a): - """ 'Analog Output' handler """ + """'Analog Output' handler.""" b = SimpleChooser(self.app, "axis", lambda action: self.on_action_chosen("analog", action) ) b.set_title(_("Select Analog Axis")) b.display_action(Action.AC_STICK, AxisAction(self.analog)) diff --git a/scc/osd/binding_display.py b/scc/osd/binding_display.py index eba0fdf4..9300d963 100644 --- a/scc/osd/binding_display.py +++ b/scc/osd/binding_display.py @@ -52,7 +52,7 @@ def __init__(self, config=None): self.c.set_name("osd-keyboard-container") - def on_profile_changed(self, daemon, filename): + def on_profile_changed(self, daemon: DaemonManager, filename: str): profile = Profile(TalkingActionParser()).load(filename) Generator(SVGEditor(self.background), profile)