Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ControllerManager.py #48

Closed
wants to merge 6 commits into from
Closed
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 67 additions & 59 deletions src/lib/ControllerManager.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,59 @@
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):
# 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

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


# Map controller buttons to their corresponding names
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', # 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

# 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 # Keep listening after capturing the event

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 # Keep listening after capturing the event

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
Expand All @@ -57,7 +62,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
Expand All @@ -66,11 +70,9 @@ 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()
for _event in pygame.event.get():
pass

def on_key_press(key):
on_press(str(key))
Expand All @@ -90,40 +92,56 @@ def on_mouse_click(x, y, key, down):
# 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

# Controller event capture thread
def capture_controller():
while self.controller_running:
try:
events = get_gamepad()
for event in events:
if event.ev_type in ['Key', 'Absolute']:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd assume the Absolute ev_type is for axis and linear actuators? its a bit weird to use those as PTT, but I guess you kinda have to? To they have a press "amount" property? maybe we should set a threshold for minimum press like 0.6 for press and 0.4 for release to avoid hysteresis?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a controller, absolute ev_type probably isn't too useful, but there are probably edge cases for HOTAS users, and maybe controller users that an axis is used for PTT. A threshold would be a great idea :)

if event.code in self.button_states:
button_mapping = self.button_states[event.code]

# 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 D-pad is centered
for direction in button_mapping.values():
on_release(f"Controller.{direction}")
# Handle standard button inputs
else:
if event.state == 1:
on_press(f"Controller.{button_mapping}")
else:
on_release(f"Controller.{button_mapping}")
except:
pass
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()
self.controller_running = True
self.controller_thread = threading.Thread(target=capture_controller, daemon=True)
self.controller_thread.start()

# Stop existing listeners
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
# 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
Expand All @@ -138,27 +156,17 @@ def emulate_hotkey(self, key: str) -> None:
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}")


# Example usage with spacebar as hotkey
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)
time.sleep(0.1)