move-mouse-to-pos.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #!/usr/bin/env python3
  2. """近距离一次到位(略慢于瞬移);远距离沿直线分多段移到目标,段间微停顿,便于看清轨迹。对外入口为 ``start``。"""
  3. from __future__ import annotations
  4. import math
  5. import random
  6. import sys
  7. import time
  8. from pathlib import Path
  9. _ROOT = Path(__file__).resolve().parent.parent
  10. if str(_ROOT) not in sys.path:
  11. sys.path.insert(0, str(_ROOT))
  12. from workplace import pyautogui as pa # noqa: E402
  13. # -----------------------------------------------------------------------------
  14. # 可配置:最终落点相对传入 ``(x, y)`` 的随机微小偏差(屏幕像素)。
  15. # 每轴独立在 ``[-FINAL_LANDING_JITTER_MAX_ABS_PX, +FINAL_LANDING_JITTER_MAX_ABS_PX]`` 内均匀取样,
  16. # 再夹到屏幕范围内。设为 ``0`` 则关闭微调(严格落在传入整数坐标)。
  17. # -----------------------------------------------------------------------------
  18. FINAL_LANDING_JITTER_MAX_ABS_PX: float = 2.5
  19. def _final_screen_xy_with_optional_micro_jitter(
  20. logical_target_x: int,
  21. logical_target_y: int,
  22. *,
  23. screen_width_pixels: int,
  24. screen_height_pixels: int,
  25. ) -> tuple[int, int]:
  26. jm = float(FINAL_LANDING_JITTER_MAX_ABS_PX)
  27. if jm <= 0.0:
  28. fx = logical_target_x
  29. fy = logical_target_y
  30. else:
  31. fx = int(
  32. round(logical_target_x + random.uniform(-jm, jm)),
  33. )
  34. fy = int(
  35. round(logical_target_y + random.uniform(-jm, jm)),
  36. )
  37. fx = max(0, min(screen_width_pixels - 1, fx))
  38. fy = max(0, min(screen_height_pixels - 1, fy))
  39. return fx, fy
  40. def _jitter_offset_tangent_and_perpendicular(
  41. delta_x: float,
  42. delta_y: float,
  43. path_length_pixels: float,
  44. *,
  45. along_max_abs: float,
  46. perp_max_abs: float,
  47. ) -> tuple[float, float]:
  48. """在路径局部坐标系下抖动:沿前进方向略小、法向略大,更接近手抖轨迹。"""
  49. if path_length_pixels <= 1e-9:
  50. return 0.0, 0.0
  51. tx = delta_x / path_length_pixels
  52. ty = delta_y / path_length_pixels
  53. px, py = -ty, tx
  54. along = random.uniform(-along_max_abs, along_max_abs)
  55. perp = random.uniform(-perp_max_abs, perp_max_abs)
  56. return tx * along + px * perp, ty * along + py * perp
  57. def start(
  58. x: int,
  59. y: int,
  60. *,
  61. near_distance_threshold_pixels: float = 120.0,
  62. minimum_number_of_move_segments_for_far_distance: int = 3,
  63. maximum_number_of_move_segments_for_far_distance: int = 4,
  64. ) -> None:
  65. """
  66. 从当前光标位置移到 ``(x, y)``(屏幕逻辑像素)。
  67. 剩余距离 ≤ ``near_distance_threshold_pixels`` 且略远时:先 ``move_to`` 至带手抖偏差的途经点,再
  68. ``move_to`` 至落点(见模块顶部 ``FINAL_LANDING_JITTER_MAX_ABS_PX``);极短距离仍为单次移动。
  69. 更远:``3``~``4`` 段移动,中间点在切向/法向组合抖动,最后一段落在目标附近(同上微调);段间短停。
  70. """
  71. target_screen_x, target_screen_y = int(x), int(y)
  72. current_pointer_x, current_pointer_y = pa.raw.position()
  73. current_pointer_x = float(current_pointer_x)
  74. current_pointer_y = float(current_pointer_y)
  75. delta_x_to_target = target_screen_x - current_pointer_x
  76. delta_y_to_target = target_screen_y - current_pointer_y
  77. distance_to_target_pixels = math.hypot(delta_x_to_target, delta_y_to_target)
  78. if distance_to_target_pixels <= 1e-6:
  79. return
  80. screen_width_pixels, screen_height_pixels = pa.screen_size()
  81. if distance_to_target_pixels <= near_distance_threshold_pixels:
  82. # 先移到「靠近目标但略偏」的一点(法向抖动为主),再落到带微偏差的终点像素。
  83. if distance_to_target_pixels <= 10.0:
  84. land_x, land_y = _final_screen_xy_with_optional_micro_jitter(
  85. target_screen_x,
  86. target_screen_y,
  87. screen_width_pixels=screen_width_pixels,
  88. screen_height_pixels=screen_height_pixels,
  89. )
  90. pa.move_to(
  91. land_x,
  92. land_y,
  93. duration=random.uniform(0.18, 0.40),
  94. )
  95. return
  96. approach_fraction_along_segment = random.uniform(0.55, 0.88)
  97. base_x = (
  98. current_pointer_x + delta_x_to_target * approach_fraction_along_segment
  99. )
  100. base_y = (
  101. current_pointer_y + delta_y_to_target * approach_fraction_along_segment
  102. )
  103. jx, jy = _jitter_offset_tangent_and_perpendicular(
  104. delta_x_to_target,
  105. delta_y_to_target,
  106. distance_to_target_pixels,
  107. along_max_abs=2.8,
  108. perp_max_abs=5.5,
  109. )
  110. waypoint_x = base_x + jx
  111. waypoint_y = base_y + jy
  112. waypoint_x = max(0, min(screen_width_pixels - 1, waypoint_x))
  113. waypoint_y = max(0, min(screen_height_pixels - 1, waypoint_y))
  114. pa.move_to(
  115. int(round(waypoint_x)),
  116. int(round(waypoint_y)),
  117. duration=random.uniform(0.14, 0.30),
  118. )
  119. land_x, land_y = _final_screen_xy_with_optional_micro_jitter(
  120. target_screen_x,
  121. target_screen_y,
  122. screen_width_pixels=screen_width_pixels,
  123. screen_height_pixels=screen_height_pixels,
  124. )
  125. pa.move_to(
  126. land_x,
  127. land_y,
  128. duration=random.uniform(0.10, 0.24),
  129. )
  130. return
  131. number_of_segments = random.randint(
  132. minimum_number_of_move_segments_for_far_distance,
  133. maximum_number_of_move_segments_for_far_distance,
  134. )
  135. for step_index in range(1, number_of_segments + 1):
  136. fraction_along_line_from_start_to_target = step_index / number_of_segments
  137. if step_index == number_of_segments:
  138. land_x, land_y = _final_screen_xy_with_optional_micro_jitter(
  139. target_screen_x,
  140. target_screen_y,
  141. screen_width_pixels=screen_width_pixels,
  142. screen_height_pixels=screen_height_pixels,
  143. )
  144. next_pointer_x = float(land_x)
  145. next_pointer_y = float(land_y)
  146. else:
  147. next_pointer_x = (
  148. current_pointer_x
  149. + delta_x_to_target * fraction_along_line_from_start_to_target
  150. )
  151. next_pointer_y = (
  152. current_pointer_y
  153. + delta_y_to_target * fraction_along_line_from_start_to_target
  154. )
  155. jx, jy = _jitter_offset_tangent_and_perpendicular(
  156. delta_x_to_target,
  157. delta_y_to_target,
  158. distance_to_target_pixels,
  159. along_max_abs=3.2,
  160. perp_max_abs=6.0,
  161. )
  162. next_pointer_x += jx
  163. next_pointer_y += jy
  164. next_pointer_x = max(
  165. 0, min(screen_width_pixels - 1, next_pointer_x)
  166. )
  167. next_pointer_y = max(
  168. 0, min(screen_height_pixels - 1, next_pointer_y)
  169. )
  170. pa.move_to(
  171. int(round(next_pointer_x)),
  172. int(round(next_pointer_y)),
  173. duration=random.uniform(0.14, 0.30),
  174. )
  175. if step_index < number_of_segments:
  176. time.sleep(random.uniform(0.045, 0.075))