1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
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()
|