@@ -1182,6 +1182,24 @@ def _dismiss_notifications(self, requests_module) -> None:
11821182 pass # Best-effort; don't fail reset if notification kill fails
11831183 logger .debug ("Dismissed system notifications" )
11841184
1185+ def _clamp_pixel_coords (self , x : int , y : int ) -> tuple [int , int ]:
1186+ """Clamp pixel coordinates to a safe margin from screen edges.
1187+
1188+ Prevents PyAutoGUI fail-safe by keeping the mouse at least 5px from
1189+ any screen corner. If both coordinates are 0, the action would
1190+ target the top-left corner -- the most common fail-safe trigger.
1191+
1192+ Returns:
1193+ Clamped (x, y) tuple.
1194+ """
1195+ screen_w , screen_h = self ._actual_screen_size or (
1196+ self .config .screen_width , self .config .screen_height ,
1197+ )
1198+ margin = 5
1199+ x = max (margin , min (x , screen_w - margin ))
1200+ y = max (margin , min (y , screen_h - margin ))
1201+ return x , y
1202+
11851203 def _translate_action (self , action : BenchmarkAction ) -> str | None :
11861204 """Translate BenchmarkAction to element-based command for WAA's Computer.
11871205
@@ -1242,29 +1260,41 @@ def _translate_action(self, action: BenchmarkAction) -> str | None:
12421260 return f"import pyautogui; pyautogui.scroll({ clicks } )"
12431261
12441262 if action .type == "drag" :
1245- # Get start position
1246- start_x , start_y = 0 , 0
1263+ # Get start position -- skip drag entirely if no coordinates
1264+ start_x , start_y = None , None
12471265 if action .target_node_id is not None :
12481266 elem_id = str (action .target_node_id )
12491267 if elem_id in self ._current_rects :
12501268 rect = self ._current_rects [elem_id ]
12511269 start_x = (rect [0 ] + rect [2 ]) // 2
12521270 start_y = (rect [1 ] + rect [3 ]) // 2
1253- elif action .x is not None and action .y is not None :
1271+ if start_x is None and action .x is not None and action .y is not None :
12541272 screen_w , screen_h = self ._actual_screen_size or (self .config .screen_width , self .config .screen_height )
12551273 start_x = action .x if not isinstance (action .x , float ) or action .x > 1 else int (action .x * screen_w )
12561274 start_y = action .y if not isinstance (action .y , float ) or action .y > 1 else int (action .y * screen_h )
12571275
12581276 # Get end position
12591277 screen_w , screen_h = self ._actual_screen_size or (self .config .screen_width , self .config .screen_height )
1260- end_x = action .end_x or 0
1261- end_y = action .end_y or 0
1262- if isinstance (end_x , float ) and 0 <= end_x <= 1 :
1278+ end_x = action .end_x
1279+ end_y = action .end_y
1280+ if end_x is not None and isinstance (end_x , float ) and 0 <= end_x <= 1 :
12631281 end_x = int (end_x * screen_w )
1264- if isinstance (end_y , float ) and 0 <= end_y <= 1 :
1282+ if end_y is not None and isinstance (end_y , float ) and 0 <= end_y <= 1 :
12651283 end_y = int (end_y * screen_h )
12661284
1267- return f"import pyautogui; pyautogui.moveTo({ int (start_x )} , { int (start_y )} ); pyautogui.drag({ int (end_x - start_x )} , { int (end_y - start_y )} , duration=0.5)"
1285+ # Skip drag if coordinates are missing or both are at origin
1286+ if start_x is None or end_x is None or end_y is None :
1287+ logger .warning ("Drag action missing coordinates, skipping" )
1288+ return "pass # drag skipped: missing coordinates"
1289+ if int (start_x ) == 0 and int (start_y ) == 0 and int (end_x ) == 0 and int (end_y ) == 0 :
1290+ logger .warning ("Drag action has all-zero coordinates, skipping" )
1291+ return "pass # drag skipped: all-zero coordinates"
1292+
1293+ # Clamp to safe margin
1294+ start_x , start_y = self ._clamp_pixel_coords (int (start_x ), int (start_y ))
1295+ end_x , end_y = self ._clamp_pixel_coords (int (end_x ), int (end_y ))
1296+
1297+ return f"import pyautogui; pyautogui.moveTo({ start_x } , { start_y } ); pyautogui.drag({ end_x - start_x } , { end_y - start_y } , duration=0.5)"
12681298
12691299 logger .warning (f"Unknown action type: { action .type } " )
12701300 return None
@@ -1294,6 +1324,7 @@ def _translate_click_action(self, action: BenchmarkAction, click_method: str) ->
12941324 rect = self ._current_rects [elem_id ]
12951325 cx = (rect [0 ] + rect [2 ]) // 2
12961326 cy = (rect [1 ] + rect [3 ]) // 2
1327+ cx , cy = self ._clamp_pixel_coords (cx , cy )
12971328 return f"import pyautogui; pyautogui.{ pyautogui_method } ({ cx } , { cy } )"
12981329 else :
12991330 logger .warning (f"Element ID '{ elem_id } ' not found in rects, falling back to coordinates" )
@@ -1315,7 +1346,8 @@ def _translate_click_action(self, action: BenchmarkAction, click_method: str) ->
13151346 if isinstance (y , float ) and 0 <= y <= 1 :
13161347 y = int (y * screen_h )
13171348
1318- return f"import pyautogui; pyautogui.{ pyautogui_method } ({ int (x )} , { int (y )} )"
1349+ x , y = self ._clamp_pixel_coords (int (x ), int (y ))
1350+ return f"import pyautogui; pyautogui.{ pyautogui_method } ({ x } , { y } )"
13191351
13201352 def _translate_key_action (self , action : BenchmarkAction ) -> str :
13211353 """Translate key press action using pyautogui (no grounding needed)."""
0 commit comments