s - toggle control ON/OFF m - toggle aim mode (ABSOLUTE <-> RELATIVE) f - cycle firing mode (tap -> hold -> burst) c - calibrate (current finger becomes center) l - toggle Windows low-level mouse (Windows only) + / - - increase / decrease sensitivity q / ESC - quit """ import time import math import platform import sys from collections import deque
import cv2 import numpy as np import mediapipe as mp import pyautogui
CAMERA_INDEX = 0 CAMERA_FLIP = True # mirror camera MAX_HANDS = 1
EST_FPS = 60.0
DEFAULT_SENS = 1.0
DEFAULT_DEADZONE = 0.02
DEFAULT_SMOOTH = 0.18
DEFAULT_PINCH_THRESH = 0.045
DEFAULT_BURST_COUNT = 3
DEFAULT_BURST_INTERVAL_MS = 60
SCREEN_W, SCREEN_H = pyautogui.size()
mp_hands = mp.solutions.hands mp_drawing = mp.solutions.drawing_utils INDEX_TIP = 8 THUMB_TIP = 4
class LowPass: def init(self, alpha=1.0, init_val=0.0): self.alpha = float(alpha) self.s = float(init_val) self.initialized = False
def filter(self, x):
if not self.initialized:
self.s = float(x)
self.initialized = True
return self.s
self.s = self.alpha * float(x) + (1.0 - self.alpha) * self.s
return self.s
def alpha(cutoff, dt): tau = 1.0 / (2.0 * math.pi * cutoff) return 1.0 / (1.0 + tau / dt)
class OneEuro: def init(self, freq=EST_FPS, min_cutoff=1.0, beta=0.007): self.freq = float(freq) self.min_cutoff = float(min_cutoff) self.beta = float(beta) self.x_prev = None self.dx_filter = None self.x_filter = None self.last_t = None
def __call__(self, x, t=None):
if t is None:
t = time.time()
if self.last_t is None:
dt = 1.0 / self.freq
else:
dt = max(1e-6, t - self.last_t)
self.last_t = t
if self.x_prev is None:
self.x_prev = x
self.dx_filter = LowPass(alpha=alpha(self.min_cutoff, dt), init_val=0.0)
self.x_filter = LowPass(alpha=alpha(self.min_cutoff, dt), init_val=x)
return x
dx = (x - self.x_prev) / dt
self.x_prev = x
dx_hat = self.dx_filter.filter(dx)
cutoff = self.min_cutoff + self.beta * abs(dx_hat)
a = alpha(cutoff, dt)
self.x_filter.alpha = a
return self.x_filter.filter(x)
IS_WINDOWS = platform.system().lower().startswith("win") low_level_enabled = False
if IS_WINDOWS: import ctypes from ctypes import wintypes PUL = ctypes.POINTER(ctypes.c_ulong) class MOUSEINPUT(ctypes.Structure): fields = [("dx", ctypes.c_long), ("dy", ctypes.c_long), ("mouseData", ctypes.c_ulong), ("dwFlags", ctypes.c_ulong), ("time", ctypes.c_ulong), ("dwExtraInfo", PUL)] class INPUT(ctypes.Structure): class _I(ctypes.Union): fields = [("mi", MOUSEINPUT)] anonymous = ("i",) fields = [("type", ctypes.c_ulong), ("i", _I)] SendInput = ctypes.windll.user32.SendInput MOUSEEVENTF_LEFTDOWN = 0x0002 MOUSEEVENTF_LEFTUP = 0x0004
def win_mouse_down():
inp = INPUT()
inp.type = 0 # INPUT_MOUSE
inp.mi = MOUSEINPUT(0, 0, 0, MOUSEEVENTF_LEFTDOWN, 0, None)
SendInput(1, ctypes.byref(inp), ctypes.sizeof(inp))
def win_mouse_up():
inp = INPUT()
inp.type = 0
inp.mi = MOUSEINPUT(0, 0, 0, MOUSEEVENTF_LEFTUP, 0, None)
SendInput(1, ctypes.byref(inp), ctypes.sizeof(inp))
else: def win_mouse_down(): raise RuntimeError("Not Windows") def win_mouse_up(): raise RuntimeError("Not Windows")
def mouse_down(): global low_level_enabled try: if IS_WINDOWS and low_level_enabled: win_mouse_down() else: pyautogui.mouseDown() except Exception: # ignore failures; permission issues may occur pass
def mouse_up(): global low_level_enabled try: if IS_WINDOWS and low_level_enabled: win_mouse_up() else: pyautogui.mouseUp() except Exception: pass
def norm_to_screen(nx, ny): sx = int(np.clip(nx, 0.0, 1.0) * SCREEN_W) sy = int(np.clip(ny, 0.0, 1.0) * SCREEN_H) return sx, sy
def dist_norm(a, b): return math.hypot(a[0] - b[0], a[1] - b[1])
WIN_NAME = "HandAim - Fixed (PRACTICE ONLY)" cv2.namedWindow(WIN_NAME, cv2.WINDOW_NORMAL) cv2.resizeWindow(WIN_NAME, 960, 540)
def nothing(x): pass
cv2.createTrackbar("Sensitivity x100", WIN_NAME, int(DEFAULT_SENS100), 500, nothing) cv2.createTrackbar("Deadzone x1000", WIN_NAME, int(DEFAULT_DEADZONE1000), 200, nothing) cv2.createTrackbar("Smoothing x100", WIN_NAME, int(DEFAULT_SMOOTH100), 90, nothing) cv2.createTrackbar("PinchThresh x1000", WIN_NAME, int(DEFAULT_PINCH_THRESH1000), 200, nothing) cv2.createTrackbar("BurstCount", WIN_NAME, DEFAULT_BURST_COUNT, 10, nothing) cv2.createTrackbar("BurstInterval ms", WIN_NAME, DEFAULT_BURST_INTERVAL_MS, 500, nothing) cv2.createTrackbar("FireMode (0 tap,1 hold,2 burst)", WIN_NAME, 0, 2, nothing)
def main(): global low_level_enabled
cap = cv2.VideoCapture(CAMERA_INDEX)
if not cap.isOpened():
print("ERROR: cannot open camera index", CAMERA_INDEX)
sys.exit(1)
hands = mp_hands.Hands(static_image_mode=False, max_num_hands=MAX_HANDS,
min_detection_confidence=0.6, min_tracking_confidence=0.6)
one_x = OneEuro(freq=EST_FPS)
one_y = OneEuro(freq=EST_FPS)
trail = deque(maxlen=12)
control_on = False
absolute_mode = True
firing_mode = 'tap' # tap / hold / burst
hold_active = False
last_click_time = 0.0
burst_remaining = 0
burst_last_time = 0.0
calib_offset_x = 0.0
calib_offset_y = 0.0
prev_norm = None
print("Started. Keys: s toggle, m mode, f cycle fire, c calibrate, l toggle low-level (Win), q quit")
try:
while True:
ret, frame = cap.read()
if not ret:
print("Camera read failed. Exiting.")
break
if CAMERA_FLIP:
frame = cv2.flip(frame, 1)
h, w, _ = frame.shape
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
res = hands.process(frame_rgb)
sens = cv2.getTrackbarPos("Sensitivity x100", WIN_NAME) / 100.0
deadzone = cv2.getTrackbarPos("Deadzone x1000", WIN_NAME) / 1000.0
smoothing = cv2.getTrackbarPos("Smoothing x100", WIN_NAME) / 100.0
pinch_thresh = cv2.getTrackbarPos("PinchThresh x1000", WIN_NAME) / 1000.0
burst_count = cv2.getTrackbarPos("BurstCount", WIN_NAME)
burst_interval = cv2.getTrackbarPos("BurstInterval ms", WIN_NAME) / 1000.0
firemode_tb = cv2.getTrackbarPos("FireMode (0 tap,1 hold,2 burst)", WIN_NAME)
if firemode_tb == 0:
firing_mode = 'tap'
elif firemode_tb == 1:
firing_mode = 'hold'
else:
firing_mode = 'burst'
index_pt = None
thumb_pt = None
pinch = False
pinch_dist = 999.0
if res.multi_hand_landmarks:
lm = res.multi_hand_landmarks[0]
idx = lm.landmark[INDEX_TIP]
th = lm.landmark[THUMB_TIP]
nx = idx.x + calib_offset_x
ny = idx.y + calib_offset_y
tx = th.x + calib_offset_x
ty = th.y + calib_offset_y
index_pt = (nx, ny)
thumb_pt = (tx, ty)
pinch_dist = math.hypot(nx - tx, ny - ty)
if pinch_dist < pinch_thresh:
pinch = True
mp_drawing.draw_landmarks(frame, lm, mp_hands.HAND_CONNECTIONS)
# small dots
cv2.circle(frame, (int(idx.x * w), int(idx.y * h)), 4, (0, 255, 0), -1)
cv2.circle(frame, (int(th.x * w), int(th.y * h)), 4, (0, 0, 255), -1)
now = time.time()
if control_on and index_pt is not None:
nx, ny = index_pt
cx = nx - 0.5
cy = ny - 0.5
if abs(cx) < deadzone:
cx = 0.0
if abs(cy) < deadzone:
cy = 0.0
if absolute_mode:
tx_n = (nx * sens) - (0.5 * (sens - 1.0))
ty_n = (ny * sens) - (0.5 * (sens - 1.0))
tx_n = float(np.clip(tx_n, 0.0, 1.0))
ty_n = float(np.clip(ty_n, 0.0, 1.0))
fx = one_x(tx_n, now)
fy = one_y(ty_n, now)
sx, sy = norm_to_screen(fx, fy)
try:
cur_x, cur_y = pyautogui.position()
new_x = cur_x + (sx - cur_x) * smoothing
new_y = cur_y + (sy - cur_y) * smoothing
pyautogui.moveTo(int(new_x), int(new_y), _pause=False)
except Exception:
cv2.putText(frame, "Mouse move blocked / requires permissions", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
else:
if prev_norm is None:
prev_norm = (nx, ny)
dx = (nx - prev_norm[0]) * SCREEN_W * sens
dy = (ny - prev_norm[1]) * SCREEN_H * sens
prev_norm = (nx, ny)
# apply basic smoothing scale for movement
dx = dx * (1.0 - smoothing)
dy = dy * (1.0 - smoothing)
try:
pyautogui.moveRel(int(dx), int(dy), _pause=False)
except Exception:
cv2.putText(frame, "Mouse rel blocked / requires permissions", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
fx_frame_x = int(nx * w)
fy_frame_y = int(ny * h)
trail.appendleft((fx_frame_x, fy_frame_y))
for i, p in enumerate(trail):
radius = max(1, 6 - i)
alpha = 1.0 - (i / max(1, len(trail)))
color = (0, int(200 * alpha) + 30, int(255 * alpha))
cv2.circle(frame, p, radius, color, -1)
else:
# when control off, reset prev_norm (so relative mode doesn't jump)
prev_norm = None
# Firing logic
if control_on and pinch:
if firing_mode == 'tap':
if (now - last_click_time) > 0.08:
last_click_time = now
try:
pyautogui.click()
except Exception:
pass
elif firing_mode == 'hold':
if not hold_active:
mouse_down()
hold_active = True
elif firing_mode == 'burst':
if burst_remaining == 0 and (now - last_click_time) > 0.08:
burst_remaining = max(1, burst_count)
burst_last_time = 0.0 # allow immediate first click
last_click_time = now
else:
# pinch released -> release hold if active
if hold_active:
mouse_up()
hold_active = False
# Burst processing (timed)
if burst_remaining > 0:
if burst_last_time == 0.0 or (now - burst_last_time) >= burst_interval:
try:
pyautogui.click()
except Exception:
pass
burst_remaining -= 1
burst_last_time = now
# Overlay UI text
mode_text = "ABSOLUTE" if absolute_mode else "RELATIVE"
status_text = f"Control: {'ON' if control_on else 'OFF'} Mode: {mode_text} Fire: {firing_mode.upper()}"
cv2.putText(frame, status_text, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 255, 200), 2)
cv2.putText(frame, f"Sens:{sens:.2f} Dead:{deadzone:.3f} Smooth:{smoothing:.2f} Pinch:{pinch_thresh:.3f}",
(10, 44), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 255, 200), 1)
cv2.putText(frame, f"Burst:{burst_count}x {int(burst_interval*1000)}ms PinchDist:{pinch_dist:.3f}",
(10, 64), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 255), 1)
# small center crosshair
cv2.drawMarker(frame, (int(w/2), int(h/2)), (255, 255, 255), markerType=cv2.MARKER_CROSS, markerSize=12, thickness=1)
cv2.imshow(WIN_NAME, frame)
key = cv2.waitKey(1) & 0xFF
if key == 27 or key == ord('q'):
break
elif key == ord('s'):
control_on = not control_on
prev_norm = None
# ensure any held mouse button is released
mouse_up()
print("Control toggled:", control_on)
elif key == ord('m'):
absolute_mode = not absolute_mode
prev_norm = None
print("Aiming mode:", "ABSOLUTE" if absolute_mode else "RELATIVE")
elif key == ord('f'):
# cycle firing mode
if firing_mode == 'tap':
firing_mode = 'hold'
elif firing_mode == 'hold':
firing_mode = 'burst'
else:
firing_mode = 'tap'
# update trackbar to reflect selection
tb_val = 0 if firing_mode == 'tap' else (1 if firing_mode == 'hold' else 2)
cv2.setTrackbarPos("FireMode (0 tap,1 hold,2 burst)", WIN_NAME, tb_val)
print("Firing mode:", firing_mode)
elif key == ord('c'):
if index_pt is not None:
nx, ny = index_pt
calib_offset_x = 0.5 - nx
calib_offset_y = 0.5 - ny
print("Calibrated offsets:", calib_offset_x, calib_offset_y)
else:
print("No hand detected to calibrate.")
elif key == ord('l'):
if IS_WINDOWS:
low_level_enabled = not low_level_enabled
print("Windows low-level mouse enabled:", low_level_enabled)
else:
print("Low-level mouse only available on Windows.")
elif key == ord('+') or key == ord('='):
cur = cv2.getTrackbarPos("Sensitivity x100", WIN_NAME)
cv2.setTrackbarPos("Sensitivity x100", WIN_NAME, min(500, cur + 5))
elif key == ord('-') or key == ord('_'):
cur = cv2.getTrackbarPos("Sensitivity x100", WIN_NAME)
cv2.setTrackbarPos("Sensitivity x100", WIN_NAME, max(1, cur - 5))
finally:
# ensure mouse button up and cleanup
try:
mouse_up()
except Exception:
pass
cap.release()
cv2.destroyAllWindows()
if name == "main": main()