import pygame import time from pynput.mouse import Controller, Button pygame.init() pygame.joystick.init() # Screen mapping size based on mouse position (my screen ins 2560 x 1600) SCREEN_WIDTH = 1680 SCREEN_HEIGHT = 1050 # Check for connected joystick if pygame.joystick.get_count() == 0: print("No joystick detected. Please connect a game controller.") exit() joystick = pygame.joystick.Joystick(0) joystick.init() mouse = Controller() # Get controller information button_count = joystick.get_numbuttons() print(f"Joystick detected: {joystick.get_name()} with {button_count} buttons") # Initial calibration phase print("Calibrating joystick. Please don't touch the controller...") time.sleep(1) # Wait for 1 second to make sure joystick is at rest # Get initial position as offset pygame.event.pump() x_offset = joystick.get_axis(0) y_offset = joystick.get_axis(1) print(f"Center calibration complete. Offset values: X={x_offset}, Y={y_offset}") # Full calibration phase to get min/max values print("\n--- FULL CALIBRATION ---") print("Please move the joystick to its maximum extents in all directions.") print("Move in circles covering the entire range for 5 seconds...") # Initialize min/max values x_min, x_max = 0, 0 y_min, y_max = 0, 0 # Calibration duration cal_start_time = time.time() cal_duration = 5 # seconds while time.time() - cal_start_time < cal_duration: pygame.event.pump() # Get calibrated values x_val = joystick.get_axis(0) - x_offset y_val = joystick.get_axis(1) - y_offset # Update min/max x_min = min(x_min, x_val) x_max = max(x_max, x_val) y_min = min(y_min, y_val) y_max = max(y_max, y_val) # Show progress progress = int((time.time() - cal_start_time) / cal_duration * 20) print(f"\rCalibrating: [{'#' * progress}{' ' * (20-progress)}] Current ranges: X({x_min:.2f} to {x_max:.2f}) Y({y_min:.2f} to {y_max:.2f})", end="") time.sleep(0.1) print("\nCalibration complete!") print(f"X range: {x_min:.2f} to {x_max:.2f}") print(f"Y range: {y_min:.2f} to {y_max:.2f}") # Dead zone DEAD_ZONE = 0.05 # Ignore small movements (5% of range) SMOOTHING_FACTOR = 0.85 # Higher values (closer to 1) make movement smoother but less responsive # Function to map joystick value to screen coordinates def map_to_screen(value, in_min, in_max, out_min, out_max): # Clamp input value to range value = max(min(value, in_max), in_min) # Map input range to output range return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min running = True # Store the last mouse position to calculate deltas last_screen_x, last_screen_y = SCREEN_WIDTH/2, SCREEN_HEIGHT/2 # Variables for smoothing smooth_x = 0 smooth_y = 0 # Set initial mouse position to center of screen mouse.position = (int(last_screen_x), int(last_screen_y)) # Dictionary to track button states to detect changes button_states = {i: False for i in range(button_count)} # Define controller buttons to mouse mapping RIGHT_CLICK_BUTTON = 4 LEFT_CLICK_BUTTON = 5 print(f"\nButton mapping:\n- Button {LEFT_CLICK_BUTTON}: Left mouse click\n- Button {RIGHT_CLICK_BUTTON}: Right mouse click") print(f"Smoothing factor: {SMOOTHING_FACTOR} (higher = smoother but less responsive)") while running: pygame.event.pump() # Process events to ensure button presses are detected for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Read joystick axes and apply calibration x_axis = joystick.get_axis(0) - x_offset y_axis = joystick.get_axis(1) - y_offset # Apply dead zone if abs(x_axis) < DEAD_ZONE: x_axis = 0 if abs(y_axis) < DEAD_ZONE: y_axis = 0 # Apply smoothing with exponential moving average smooth_x = SMOOTHING_FACTOR * smooth_x + (1 - SMOOTHING_FACTOR) * x_axis smooth_y = SMOOTHING_FACTOR * smooth_y + (1 - SMOOTHING_FACTOR) * y_axis # Map smoothed joystick to screen coordinates screen_x = map_to_screen(smooth_x, x_min, x_max, 0, SCREEN_WIDTH) screen_y = map_to_screen(smooth_y, y_min, y_max, 0, SCREEN_HEIGHT) # Use absolute positioning instead of relative movement if abs(x_axis) >= DEAD_ZONE or abs(y_axis) >= DEAD_ZONE: # Set the mouse position directly to the calculated screen coordinates mouse.position = (int(screen_x), int(screen_y)) # Check all button states button_changes = False button_status = [] for i in range(button_count): button_value = joystick.get_button(i) # Handle mouse button clicks if i == LEFT_CLICK_BUTTON: if button_value and not button_states[i]: mouse.press(Button.left) button_status.append(f"Button {i}: LEFT CLICK") elif not button_value and button_states[i]: mouse.release(Button.left) elif i == RIGHT_CLICK_BUTTON: if button_value and not button_states[i]: mouse.press(Button.right) button_status.append(f"Button {i}: RIGHT CLICK") elif not button_value and button_states[i]: mouse.release(Button.right) # Track button state changes for all buttons if button_value != button_states[i]: button_changes = True button_states[i] = button_value # Add status for non-mouse buttons if button_value and i != LEFT_CLICK_BUTTON and i != RIGHT_CLICK_BUTTON: button_status.append(f"Button {i}: PRESSED") # Only display button information if there are changes or buttons pressed if button_changes or any(button_states.values()): print("\nButton Status:") if not button_status: print("No buttons currently pressed") else: for status in button_status: print(status) # Display the scaled joystick position print(f"Scaled position: ({int(screen_x)}, {int(screen_y)}) | Raw: ({x_axis:.2f}, {y_axis:.2f}) | Smoothed: ({smooth_x:.2f}, {smooth_y:.2f})") time.sleep(0.05) pygame.quit()