From ba775bd5988e565832d68de39632b7b6a378fa56 Mon Sep 17 00:00:00 2001 From: CptAwsm Date: Fri, 13 Dec 2024 18:40:04 +0000 Subject: [PATCH 1/6] Update ControllerManager.py Added Controller fix --- src/lib/ControllerManager.py | 121 +++++++++++++++++------------------ 1 file changed, 58 insertions(+), 63 deletions(-) diff --git a/src/lib/ControllerManager.py b/src/lib/ControllerManager.py index 97b0db59..85340124 100644 --- a/src/lib/ControllerManager.py +++ b/src/lib/ControllerManager.py @@ -1,54 +1,55 @@ import threading import time from typing import Callable, Any - -import pygame +from inputs import get_gamepad from pynput.keyboard import Controller as KeyboardController, Listener as KeyboardListener from pynput.mouse import Controller as MouseController, Listener as MouseListener - class ControllerManager: def __init__(self): self.keyboard_controller = KeyboardController() self.mouse_controller = MouseController() - self.is_pressed = False self.last_press = None self.last_release = None + + self.button_states = { + 'BTN_SOUTH': 'A', + 'BTN_EAST': 'B', + 'BTN_WEST': 'X', + 'BTN_NORTH': 'Y', + 'BTN_START': 'Start', + 'BTN_SELECT': 'Select', + 'BTN_TL': 'LB', + 'BTN_TR': 'RB', + 'BTN_THUMBL': 'LS', + 'BTN_THUMBR': 'RS', + 'ABS_HAT0X': {-1: 'DPad.Left', 1: 'DPad.Right'}, + 'ABS_HAT0Y': {-1: 'DPad.Up', 1: 'DPad.Down'} + } + + self.controller_thread = None + self.controller_running = False - pygame.init() - pygame.joystick.init() - self.joysticks = [] - for i in range(0,pygame.joystick.get_count()): - joystick = pygame.joystick.Joystick(i) - joystick.init() - self.joysticks.append(joystick) - - self.capture_event_thread = None - self.capture_event_thread_running = False - - # PPT: Registers event listener for ptt def register_hotkey(self, key: str, on_press: Callable[[str], Any], on_release: Callable[[str], Any]) -> None: def on_press_wrapper(k): if k == key and not self.is_pressed: self.is_pressed = True on_press(k) - return True # Keep listening after capturing the event + return True def on_release_wrapper(k): if k == key: self.is_pressed = False on_release(k) - return True # Keep listening after capturing the event + return True self._start_listeners(on_press_wrapper, on_release_wrapper) - - - # PTT: Captures mouse, keyboard, and controller inputs to save them as PTT key def listen_hotkey(self, callback: Callable[[str], any]): self.last_press = None self.last_release = None + def on_press(key): self.last_press = str(key) return False @@ -57,7 +58,6 @@ def on_release(key): self.last_release = str(key) if self.last_press and self.last_release == self.last_press: self._stop_listeners() - key = self.last_press self.last_press = None self.last_release = None @@ -66,11 +66,8 @@ def on_release(key): self._start_listeners(on_press, on_release) - def _start_listeners(self, on_press, on_release): self._stop_listeners() - for _event in pygame.event.get(): - pass def on_key_press(key): on_press(str(key)) @@ -87,78 +84,76 @@ def on_mouse_click(x, y, key, down): on_release(str(key)) return True - # Start listeners for keyboard and mouse self.keyboard_listener = KeyboardListener(on_press=on_key_press, on_release=on_key_release) self.keyboard_listener.start() - self.mouse_listener = MouseListener(on_click=on_mouse_click) self.mouse_listener.start() - def capture_event(): - while self.joystick_listener_running: - events = pygame.event.get() - for event in events: - if event.type == pygame.JOYBUTTONDOWN: - on_press(str(event.instance_id)+':'+str(event.button)) - if event.type == pygame.JOYBUTTONUP: - on_release(str(event.instance_id)+':'+str(event.button)) - time.sleep(0.01) # Small sleep to prevent high CPU usage - - # Start a thread for capturing game controller events - self.joystick_listener_running = True - self.joystick_listener = threading.Thread(target=capture_event, daemon=True) - self.joystick_listener.start() + def capture_controller(): + while self.controller_running: + try: + events = get_gamepad() + for event in events: + if event.ev_type in ['Key', 'Absolute']: + if event.code in self.button_states: + button_mapping = self.button_states[event.code] + + # Handle D-pad + if isinstance(button_mapping, dict): + if event.state in button_mapping: + button_name = button_mapping[event.state] + on_press(f"Controller.{button_name}") + else: + # Release all directions when centered + for direction in button_mapping.values(): + on_release(f"Controller.{direction}") + # Handle regular buttons + else: + if event.state == 1: + on_press(f"Controller.{button_mapping}") + else: + on_release(f"Controller.{button_mapping}") + except: + pass + time.sleep(0.01) + + self.controller_running = True + self.controller_thread = threading.Thread(target=capture_controller, daemon=True) + self.controller_thread.start() def _stop_listeners(self): - # Stop existing listeners try: self.keyboard_listener.stop() except: pass - try: self.mouse_listener.stop() except: pass + self.controller_running = False - self.joystick_listener_running = False - - # TBD def emulate_hotkey(self, key: str) -> None: try: - # Try to interpret key as a Key object key_obj = eval(key) self.keyboard_controller.press(key_obj) self.keyboard_controller.release(key_obj) except (NameError, SyntaxError): - # If not a keyboard key, check for mouse button if key.startswith("Button."): button = eval(key) self.mouse_controller.click(button) else: print(f"Unknown key type: {key}") -# Example Usage: if __name__ == "__main__": manager = ControllerManager() - - - # Registering a hotkey example + def on_press(key): print(f"Hotkey pressed: {key}") - - + def on_release(key): print(f"Hotkey released: {key}") - manager.register_hotkey("Key.space", on_press, on_release) - - # Listening for a hotkey - print(f"Captured hotkey: {manager.listen_hotkey()}") - - # Emulating a hotkey - manager.emulate_hotkey("Key.space") - + while True: - time.sleep(0.1) \ No newline at end of file + time.sleep(0.1) From f7d1aad264796a1b2bdd99da2d1259a3bb2b0181 Mon Sep 17 00:00:00 2001 From: CptAwsm Date: Fri, 13 Dec 2024 19:23:10 +0000 Subject: [PATCH 2/6] Update ControllerManager.py Added comments --- src/lib/ControllerManager.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/lib/ControllerManager.py b/src/lib/ControllerManager.py index 85340124..79b07b03 100644 --- a/src/lib/ControllerManager.py +++ b/src/lib/ControllerManager.py @@ -7,12 +7,14 @@ class ControllerManager: def __init__(self): + # Initialize controllers for keyboard and mouse emulation self.keyboard_controller = KeyboardController() self.mouse_controller = MouseController() self.is_pressed = False self.last_press = None self.last_release = None + # Map controller buttons to their corresponding names self.button_states = { 'BTN_SOUTH': 'A', 'BTN_EAST': 'B', @@ -22,30 +24,32 @@ def __init__(self): 'BTN_SELECT': 'Select', 'BTN_TL': 'LB', 'BTN_TR': 'RB', - 'BTN_THUMBL': 'LS', - 'BTN_THUMBR': 'RS', - 'ABS_HAT0X': {-1: 'DPad.Left', 1: 'DPad.Right'}, - 'ABS_HAT0Y': {-1: 'DPad.Up', 1: 'DPad.Down'} + 'BTN_THUMBL': 'LS', # Left stick button + 'BTN_THUMBR': 'RS', # Right stick button + 'ABS_HAT0X': {-1: 'DPad.Left', 1: 'DPad.Right'}, # D-pad horizontal axis + 'ABS_HAT0Y': {-1: 'DPad.Up', 1: 'DPad.Down'} # D-pad vertical axis } self.controller_thread = None self.controller_running = False - + + # Register event listeners for push-to-talk functionality def register_hotkey(self, key: str, on_press: Callable[[str], Any], on_release: Callable[[str], Any]) -> None: def on_press_wrapper(k): if k == key and not self.is_pressed: self.is_pressed = True on_press(k) - return True + return True # Keep listening after capturing the event def on_release_wrapper(k): if k == key: self.is_pressed = False on_release(k) - return True + return True # Keep listening after capturing the event self._start_listeners(on_press_wrapper, on_release_wrapper) - + + # Capture and save push-to-talk key from mouse, keyboard, or controller input def listen_hotkey(self, callback: Callable[[str], any]): self.last_press = None self.last_release = None @@ -66,6 +70,7 @@ def on_release(key): self._start_listeners(on_press, on_release) + # Initialize and start all input listeners def _start_listeners(self, on_press, on_release): self._stop_listeners() @@ -84,11 +89,13 @@ def on_mouse_click(x, y, key, down): on_release(str(key)) return True + # Start keyboard and mouse listeners self.keyboard_listener = KeyboardListener(on_press=on_key_press, on_release=on_key_release) self.keyboard_listener.start() self.mouse_listener = MouseListener(on_click=on_mouse_click) self.mouse_listener.start() + # Controller event capture thread def capture_controller(): while self.controller_running: try: @@ -98,16 +105,16 @@ def capture_controller(): if event.code in self.button_states: button_mapping = self.button_states[event.code] - # Handle D-pad + # Handle D-pad inputs if isinstance(button_mapping, dict): if event.state in button_mapping: button_name = button_mapping[event.state] on_press(f"Controller.{button_name}") else: - # Release all directions when centered + # Release all directions when D-pad is centered for direction in button_mapping.values(): on_release(f"Controller.{direction}") - # Handle regular buttons + # Handle standard button inputs else: if event.state == 1: on_press(f"Controller.{button_mapping}") @@ -121,6 +128,7 @@ def capture_controller(): self.controller_thread = threading.Thread(target=capture_controller, daemon=True) self.controller_thread.start() + # Stop all active input listeners def _stop_listeners(self): try: self.keyboard_listener.stop() @@ -132,6 +140,7 @@ def _stop_listeners(self): pass self.controller_running = False + # Emulate keyboard or mouse input based on the provided key def emulate_hotkey(self, key: str) -> None: try: key_obj = eval(key) @@ -153,6 +162,7 @@ def on_press(key): def on_release(key): print(f"Hotkey released: {key}") + # Example usage with spacebar as hotkey manager.register_hotkey("Key.space", on_press, on_release) while True: From 7f8fc47e5f797d0e89dcd8c01dbd4d32aad586a8 Mon Sep 17 00:00:00 2001 From: CptAwsm Date: Fri, 13 Dec 2024 19:31:43 +0000 Subject: [PATCH 3/6] Update ControllerManager.py More code comments --- src/lib/ControllerManager.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/lib/ControllerManager.py b/src/lib/ControllerManager.py index 79b07b03..5a09349e 100644 --- a/src/lib/ControllerManager.py +++ b/src/lib/ControllerManager.py @@ -33,7 +33,7 @@ def __init__(self): self.controller_thread = None self.controller_running = False - # Register event listeners for push-to-talk functionality + # PPT: Registers event listener for ptt def register_hotkey(self, key: str, on_press: Callable[[str], Any], on_release: Callable[[str], Any]) -> None: def on_press_wrapper(k): if k == key and not self.is_pressed: @@ -49,7 +49,7 @@ def on_release_wrapper(k): self._start_listeners(on_press_wrapper, on_release_wrapper) - # Capture and save push-to-talk key from mouse, keyboard, or controller input + # PTT: Captures mouse, keyboard, and controller inputs to save them as PTT key def listen_hotkey(self, callback: Callable[[str], any]): self.last_press = None self.last_release = None @@ -89,7 +89,7 @@ def on_mouse_click(x, y, key, down): on_release(str(key)) return True - # Start keyboard and mouse listeners + # Start listeners for keyboard and mouse self.keyboard_listener = KeyboardListener(on_press=on_key_press, on_release=on_key_release) self.keyboard_listener.start() self.mouse_listener = MouseListener(on_click=on_mouse_click) @@ -122,13 +122,14 @@ def capture_controller(): on_release(f"Controller.{button_mapping}") except: pass - time.sleep(0.01) - + time.sleep(0.01) # Small sleep to prevent high CPU usage + + # Start a thread for capturing game controller events self.controller_running = True self.controller_thread = threading.Thread(target=capture_controller, daemon=True) self.controller_thread.start() - # Stop all active input listeners + # Stop existing listeners def _stop_listeners(self): try: self.keyboard_listener.stop() @@ -143,10 +144,12 @@ def _stop_listeners(self): # Emulate keyboard or mouse input based on the provided key def emulate_hotkey(self, key: str) -> None: try: + # Try to interpret key as a Key object key_obj = eval(key) self.keyboard_controller.press(key_obj) self.keyboard_controller.release(key_obj) except (NameError, SyntaxError): + # If not a keyboard key, check for mouse button if key.startswith("Button."): button = eval(key) self.mouse_controller.click(button) From 29738a74f43c499b6b068610df869dcf7cce5c26 Mon Sep 17 00:00:00 2001 From: CptAwsm Date: Sat, 14 Dec 2024 14:31:32 +0000 Subject: [PATCH 4/6] Update ControllerManager.py Updated to include analog functions, with a threshold. --- src/lib/ControllerManager.py | 43 +++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/lib/ControllerManager.py b/src/lib/ControllerManager.py index 5a09349e..c62817a8 100644 --- a/src/lib/ControllerManager.py +++ b/src/lib/ControllerManager.py @@ -24,10 +24,16 @@ def __init__(self): 'BTN_SELECT': 'Select', 'BTN_TL': 'LB', 'BTN_TR': 'RB', - 'BTN_THUMBL': 'LS', # Left stick button - 'BTN_THUMBR': 'RS', # Right stick button - 'ABS_HAT0X': {-1: 'DPad.Left', 1: 'DPad.Right'}, # D-pad horizontal axis - 'ABS_HAT0Y': {-1: 'DPad.Up', 1: 'DPad.Down'} # D-pad vertical axis + 'BTN_THUMBL': 'LS', + 'BTN_THUMBR': 'RS', + 'ABS_HAT0X': {-1: 'DPad.Left', 1: 'DPad.Right'}, + 'ABS_HAT0Y': {-1: 'DPad.Up', 1: 'DPad.Down'}, + 'ABS_X': {'press': 0.6, 'release': 0.4, -1: 'LeftStick.Left', 1: 'LeftStick.Right'}, + 'ABS_Y': {'press': 0.6, 'release': 0.4, -1: 'LeftStick.Up', 1: 'LeftStick.Down'}, + 'ABS_RX': {'press': 0.6, 'release': 0.4, -1: 'RightStick.Left', 1: 'RightStick.Right'}, + 'ABS_RY': {'press': 0.6, 'release': 0.4, -1: 'RightStick.Up', 1: 'RightStick.Down'}, + 'ABS_Z': {'press': 0.6, 'release': 0.4, 1: 'LeftTrigger'}, # Left Trigger + 'ABS_RZ': {'press': 0.6, 'release': 0.4, 1: 'RightTrigger'} # Right Trigger } self.controller_thread = None @@ -105,15 +111,30 @@ def capture_controller(): if event.code in self.button_states: button_mapping = self.button_states[event.code] - # Handle D-pad inputs + # Handle D-pad and analog inputs if isinstance(button_mapping, dict): - if event.state in button_mapping: - button_name = button_mapping[event.state] - on_press(f"Controller.{button_name}") + if 'press' in button_mapping: + # Handle analog sticks and triggers + normalized_value = abs(event.state) / 32768 + direction = 1 if event.state > 0 else -1 + + if normalized_value > button_mapping['press']: + button_name = button_mapping[direction] + on_press(f"Controller.{button_name}") + elif normalized_value < button_mapping['release']: + # Release both directions when below release threshold + for direction in [-1, 1]: + if direction in button_mapping: + on_release(f"Controller.{button_mapping[direction]}") else: - # Release all directions when D-pad is centered - for direction in button_mapping.values(): - on_release(f"Controller.{direction}") + # Handle D-pad + if event.state in button_mapping: + button_name = button_mapping[event.state] + on_press(f"Controller.{button_name}") + else: + # Release all directions when D-pad is centered + for direction in button_mapping.values(): + on_release(f"Controller.{direction}") # Handle standard button inputs else: if event.state == 1: From 877d532cbba9eb5463b8b00f9be75add708b86ca Mon Sep 17 00:00:00 2001 From: CptAwsm Date: Mon, 16 Dec 2024 02:39:35 +0000 Subject: [PATCH 5/6] Update build requirements --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d20dfbd8..37872124 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,6 +25,7 @@ huggingface-hub==0.23.5 humanfriendly==10.0 idna==3.7 importlib-metadata==8.2.0 +inputs==0.5 keyboard==0.13.5 kthread==0.2.3 miniaudio==1.61 @@ -75,4 +76,4 @@ tqdm==4.66.4 typing-extensions==4.12.2 urllib3==2.2.2 zipp==3.19.2 -EDMesg @ git+https://github.com/RatherRude/EDMesg.git@00cfd54 \ No newline at end of file +EDMesg @ git+https://github.com/RatherRude/EDMesg.git@00cfd54 From e63d60e96e2f8b11f9b1e911bd62671510d759d2 Mon Sep 17 00:00:00 2001 From: CptAwsm Date: Mon, 16 Dec 2024 13:34:54 +0000 Subject: [PATCH 6/6] Update ControllerManager to better handle analog input --- src/lib/ControllerManager.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/lib/ControllerManager.py b/src/lib/ControllerManager.py index c62817a8..2ee01715 100644 --- a/src/lib/ControllerManager.py +++ b/src/lib/ControllerManager.py @@ -115,17 +115,29 @@ def capture_controller(): if isinstance(button_mapping, dict): if 'press' in button_mapping: # Handle analog sticks and triggers - normalized_value = abs(event.state) / 32768 - direction = 1 if event.state > 0 else -1 - - if normalized_value > button_mapping['press']: - button_name = button_mapping[direction] - on_press(f"Controller.{button_name}") - elif normalized_value < button_mapping['release']: - # Release both directions when below release threshold - for direction in [-1, 1]: - if direction in button_mapping: - on_release(f"Controller.{button_mapping[direction]}") + if 'press' in button_mapping: + if event.code == 'ABS_Z': # Left Trigger + normalized_value = event.state / 255 + elif event.code == 'ABS_RZ': # Right Trigger + normalized_value = event.state / 255 + else: # Sticks + if event.code.endswith('Y'): # Vertical axes + normalized_value = -event.state / 32768 # Invert vertical + else: # Horizontal axes + normalized_value = event.state / 32768 # Normal horizontal + + if abs(normalized_value) < 0.1: # Deadzone + normalized_value = 0 + direction = 1 if normalized_value > 0 else -1 + + if abs(normalized_value) > button_mapping['press']: + button_name = button_mapping[direction] + on_press(f"Controller.{button_name}") + elif abs(normalized_value) < button_mapping['release']: + # Release both directions when below release threshold + for direction in [-1, 1]: + if direction in button_mapping: + on_release(f"Controller.{button_mapping[direction]}") else: # Handle D-pad if event.state in button_mapping: