iflow-mcp_bethington-cheat-engine-server-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/METADATA +16 -0
  2. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/RECORD +40 -0
  3. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/WHEEL +5 -0
  4. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/top_level.txt +1 -0
  7. server/cheatengine/__init__.py +19 -0
  8. server/cheatengine/ce_bridge.py +1670 -0
  9. server/cheatengine/lua_interface.py +460 -0
  10. server/cheatengine/table_parser.py +1221 -0
  11. server/config/__init__.py +20 -0
  12. server/config/settings.py +347 -0
  13. server/config/whitelist.py +378 -0
  14. server/gui_automation/__init__.py +43 -0
  15. server/gui_automation/core/__init__.py +8 -0
  16. server/gui_automation/core/integration.py +951 -0
  17. server/gui_automation/demos/__init__.py +8 -0
  18. server/gui_automation/demos/basic_demo.py +754 -0
  19. server/gui_automation/demos/notepad_demo.py +460 -0
  20. server/gui_automation/demos/simple_demo.py +319 -0
  21. server/gui_automation/tools/__init__.py +8 -0
  22. server/gui_automation/tools/mcp_tools.py +974 -0
  23. server/main.py +519 -0
  24. server/memory/__init__.py +0 -0
  25. server/memory/analyzer.py +0 -0
  26. server/memory/reader.py +0 -0
  27. server/memory/scanner.py +0 -0
  28. server/memory/symbols.py +0 -0
  29. server/process/__init__.py +16 -0
  30. server/process/launcher.py +608 -0
  31. server/process/manager.py +185 -0
  32. server/process/monitors.py +202 -0
  33. server/process/permissions.py +131 -0
  34. server/process_whitelist.json +119 -0
  35. server/pyautogui/__init__.py +0 -0
  36. server/utils/__init__.py +37 -0
  37. server/utils/data_types.py +368 -0
  38. server/utils/formatters.py +430 -0
  39. server/utils/validators.py +340 -0
  40. server/window_automation/__init__.py +59 -0
@@ -0,0 +1,951 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP Cheat Engine Server - PyAutoGUI Integration Module
4
+
5
+ This module provides comprehensive access to all PyAutoGUI functionality
6
+ through the MCP Cheat Engine Server, including:
7
+
8
+ 1. Screen automation (screenshots, pixel colors, image recognition)
9
+ 2. Mouse control (movement, clicking, dragging, scrolling)
10
+ 3. Keyboard automation (typing, key combinations, hotkeys)
11
+ 4. Window management and screen utilities
12
+ 5. Fail-safes and safety features
13
+
14
+ Design Principles:
15
+ - Complete PyAutoGUI API exposure through MCP tools
16
+ - Enhanced error handling and logging
17
+ - Security controls and validation
18
+ - Performance optimizations for image operations
19
+ - Integration with existing automation system
20
+ """
21
+
22
+ import os
23
+ import sys
24
+ import time
25
+ import logging
26
+ import threading
27
+ from typing import Dict, Any, List, Optional, Tuple, Union
28
+ from dataclasses import dataclass, field
29
+ from pathlib import Path
30
+ import base64
31
+ import io
32
+
33
+ # PyAutoGUI and image processing imports
34
+ try:
35
+ import pyautogui
36
+ import PIL.Image
37
+ import cv2
38
+ import numpy as np
39
+ PYAUTOGUI_AVAILABLE = True
40
+ except ImportError as e:
41
+ PYAUTOGUI_AVAILABLE = False
42
+ print(f"PyAutoGUI dependencies not available: {e}")
43
+
44
+ # Configure logging
45
+ logger = logging.getLogger(__name__)
46
+
47
+ @dataclass
48
+ class ScreenInfo:
49
+ """Screen information structure"""
50
+ width: int
51
+ height: int
52
+ primary_monitor: bool = True
53
+ monitor_count: int = 1
54
+
55
+ @dataclass
56
+ class MouseInfo:
57
+ """Mouse position and state information"""
58
+ x: int
59
+ y: int
60
+ timestamp: float = field(default_factory=time.time)
61
+
62
+ @dataclass
63
+ class ImageMatch:
64
+ """Image recognition match result"""
65
+ found: bool
66
+ x: int = 0
67
+ y: int = 0
68
+ width: int = 0
69
+ height: int = 0
70
+ confidence: float = 0.0
71
+ center_x: int = 0
72
+ center_y: int = 0
73
+
74
+ def __post_init__(self):
75
+ if self.found:
76
+ self.center_x = self.x + self.width // 2
77
+ self.center_y = self.y + self.height // 2
78
+
79
+ @dataclass
80
+ class AutomationResult:
81
+ """Standardized result for PyAutoGUI operations"""
82
+ success: bool
83
+ operation: str
84
+ data: Dict = field(default_factory=dict)
85
+ error: str = ""
86
+ timestamp: float = field(default_factory=time.time)
87
+
88
+ def to_dict(self) -> Dict[str, Any]:
89
+ """Convert result to dictionary for JSON serialization"""
90
+ return {
91
+ "success": self.success,
92
+ "operation": self.operation,
93
+ "data": self.data,
94
+ "error": self.error,
95
+ "timestamp": self.timestamp
96
+ }
97
+
98
+ class PyAutoGUIController:
99
+ """Main controller for PyAutoGUI functionality"""
100
+
101
+ def __init__(self):
102
+ if not PYAUTOGUI_AVAILABLE:
103
+ raise ImportError("PyAutoGUI and required dependencies are not installed")
104
+
105
+ # Configure PyAutoGUI settings
106
+ self._configure_pyautogui()
107
+
108
+ # Initialize state tracking
109
+ self.screen_info = self._get_screen_info()
110
+ self.last_screenshot = None
111
+ self.screenshot_cache = {}
112
+ self.image_templates = {}
113
+
114
+ logger.info("PyAutoGUI Controller initialized successfully")
115
+
116
+ def _configure_pyautogui(self):
117
+ """Configure PyAutoGUI with optimal settings"""
118
+ # Set fail-safe (move mouse to corner to abort)
119
+ pyautogui.FAILSAFE = True
120
+
121
+ # Set pause between actions (0.1 second default)
122
+ pyautogui.PAUSE = 0.1
123
+
124
+ # Set minimum duration for movements
125
+ pyautogui.MINIMUM_DURATION = 0.05
126
+
127
+ # Set minimum sleep time
128
+ pyautogui.MINIMUM_SLEEP = 0.05
129
+
130
+ logger.info("PyAutoGUI configured with safety settings")
131
+
132
+ def _get_screen_info(self) -> ScreenInfo:
133
+ """Get current screen information"""
134
+ size = pyautogui.size()
135
+ return ScreenInfo(
136
+ width=size.width,
137
+ height=size.height,
138
+ primary_monitor=True,
139
+ monitor_count=1 # PyAutoGUI primarily works with primary monitor
140
+ )
141
+
142
+ # ==========================================
143
+ # SCREEN CAPTURE AND ANALYSIS
144
+ # ==========================================
145
+
146
+ def take_screenshot(self, region: Optional[Tuple[int, int, int, int]] = None,
147
+ save_path: Optional[str] = None) -> AutomationResult:
148
+ """Take a screenshot of the screen or a specific region"""
149
+ try:
150
+ if region:
151
+ # Take screenshot of specific region (left, top, width, height)
152
+ screenshot = pyautogui.screenshot(region=region)
153
+ else:
154
+ # Take full screen screenshot
155
+ screenshot = pyautogui.screenshot()
156
+
157
+ # Convert to base64 for transport
158
+ img_buffer = io.BytesIO()
159
+ screenshot.save(img_buffer, format='PNG')
160
+ img_base64 = base64.b64encode(img_buffer.getvalue()).decode()
161
+
162
+ # Save to file if requested
163
+ if save_path:
164
+ screenshot.save(save_path)
165
+
166
+ # Cache the screenshot
167
+ self.last_screenshot = screenshot
168
+ cache_key = f"screenshot_{int(time.time())}"
169
+ self.screenshot_cache[cache_key] = screenshot
170
+
171
+ result_data = {
172
+ "image_base64": img_base64,
173
+ "width": screenshot.width,
174
+ "height": screenshot.height,
175
+ "region": region,
176
+ "save_path": save_path,
177
+ "cache_key": cache_key
178
+ }
179
+
180
+ return AutomationResult(
181
+ success=True,
182
+ operation="take_screenshot",
183
+ data=result_data
184
+ )
185
+
186
+ except Exception as e:
187
+ logger.error(f"Screenshot failed: {e}")
188
+ return AutomationResult(
189
+ success=False,
190
+ operation="take_screenshot",
191
+ error=str(e)
192
+ )
193
+
194
+ def get_pixel_color(self, x: int, y: int) -> AutomationResult:
195
+ """Get the RGB color of a pixel at the specified coordinates"""
196
+ try:
197
+ # Validate coordinates
198
+ if not (0 <= x < self.screen_info.width and 0 <= y < self.screen_info.height):
199
+ raise ValueError(f"Coordinates ({x}, {y}) are outside screen bounds")
200
+
201
+ # Get pixel color
202
+ pixel_color = pyautogui.pixel(x, y)
203
+
204
+ # Convert to RGB values
205
+ r, g, b = pixel_color
206
+ hex_color = f"#{r:02x}{g:02x}{b:02x}"
207
+
208
+ return AutomationResult(
209
+ success=True,
210
+ operation="get_pixel_color",
211
+ data={
212
+ "x": x,
213
+ "y": y,
214
+ "rgb": [r, g, b],
215
+ "hex": hex_color,
216
+ "rgb_tuple": pixel_color
217
+ }
218
+ )
219
+
220
+ except Exception as e:
221
+ logger.error(f"Get pixel color failed: {e}")
222
+ return AutomationResult(
223
+ success=False,
224
+ operation="get_pixel_color",
225
+ error=str(e)
226
+ )
227
+
228
+ def find_image_on_screen(self, image_path: str, confidence: float = 0.8,
229
+ region: Optional[Tuple[int, int, int, int]] = None) -> AutomationResult:
230
+ """Find an image on the screen using template matching"""
231
+ try:
232
+ if not os.path.exists(image_path):
233
+ raise FileNotFoundError(f"Image file not found: {image_path}")
234
+
235
+ # Attempt to locate the image
236
+ try:
237
+ if region:
238
+ location = pyautogui.locateOnScreen(image_path, confidence=confidence, region=region)
239
+ else:
240
+ location = pyautogui.locateOnScreen(image_path, confidence=confidence)
241
+
242
+ if location:
243
+ # Image found
244
+ center = pyautogui.center(location)
245
+
246
+ match = ImageMatch(
247
+ found=True,
248
+ x=location.left,
249
+ y=location.top,
250
+ width=location.width,
251
+ height=location.height,
252
+ confidence=confidence,
253
+ center_x=center.x,
254
+ center_y=center.y
255
+ )
256
+
257
+ return AutomationResult(
258
+ success=True,
259
+ operation="find_image_on_screen",
260
+ data={
261
+ "image_path": image_path,
262
+ "match": match.__dict__,
263
+ "region": region,
264
+ "search_confidence": confidence
265
+ }
266
+ )
267
+ else:
268
+ # Image not found
269
+ match = ImageMatch(found=False)
270
+
271
+ return AutomationResult(
272
+ success=True,
273
+ operation="find_image_on_screen",
274
+ data={
275
+ "image_path": image_path,
276
+ "match": match.__dict__,
277
+ "region": region,
278
+ "search_confidence": confidence
279
+ }
280
+ )
281
+
282
+ except pyautogui.ImageNotFoundException:
283
+ # Image not found exception
284
+ match = ImageMatch(found=False)
285
+
286
+ return AutomationResult(
287
+ success=True,
288
+ operation="find_image_on_screen",
289
+ data={
290
+ "image_path": image_path,
291
+ "match": match.__dict__,
292
+ "region": region,
293
+ "search_confidence": confidence
294
+ }
295
+ )
296
+
297
+ except Exception as e:
298
+ logger.error(f"Image search failed: {e}")
299
+ return AutomationResult(
300
+ success=False,
301
+ operation="find_image_on_screen",
302
+ error=str(e)
303
+ )
304
+
305
+ def find_all_images_on_screen(self, image_path: str, confidence: float = 0.8,
306
+ region: Optional[Tuple[int, int, int, int]] = None) -> AutomationResult:
307
+ """Find all instances of an image on the screen"""
308
+ try:
309
+ if not os.path.exists(image_path):
310
+ raise FileNotFoundError(f"Image file not found: {image_path}")
311
+
312
+ # Find all matches
313
+ try:
314
+ if region:
315
+ locations = list(pyautogui.locateAllOnScreen(image_path, confidence=confidence, region=region))
316
+ else:
317
+ locations = list(pyautogui.locateAllOnScreen(image_path, confidence=confidence))
318
+
319
+ matches = []
320
+ for location in locations:
321
+ center = pyautogui.center(location)
322
+ match = ImageMatch(
323
+ found=True,
324
+ x=location.left,
325
+ y=location.top,
326
+ width=location.width,
327
+ height=location.height,
328
+ confidence=confidence,
329
+ center_x=center.x,
330
+ center_y=center.y
331
+ )
332
+ matches.append(match.__dict__)
333
+
334
+ return AutomationResult(
335
+ success=True,
336
+ operation="find_all_images_on_screen",
337
+ data={
338
+ "image_path": image_path,
339
+ "matches": matches,
340
+ "match_count": len(matches),
341
+ "region": region,
342
+ "search_confidence": confidence
343
+ }
344
+ )
345
+
346
+ except pyautogui.ImageNotFoundException:
347
+ return AutomationResult(
348
+ success=True,
349
+ operation="find_all_images_on_screen",
350
+ data={
351
+ "image_path": image_path,
352
+ "matches": [],
353
+ "match_count": 0,
354
+ "region": region,
355
+ "search_confidence": confidence
356
+ }
357
+ )
358
+
359
+ except Exception as e:
360
+ logger.error(f"Find all images failed: {e}")
361
+ return AutomationResult(
362
+ success=False,
363
+ operation="find_all_images_on_screen",
364
+ error=str(e)
365
+ )
366
+
367
+ # ==========================================
368
+ # MOUSE CONTROL
369
+ # ==========================================
370
+
371
+ def get_mouse_position(self) -> AutomationResult:
372
+ """Get current mouse position"""
373
+ try:
374
+ x, y = pyautogui.position()
375
+
376
+ mouse_info = MouseInfo(x=x, y=y)
377
+
378
+ return AutomationResult(
379
+ success=True,
380
+ operation="get_mouse_position",
381
+ data=mouse_info.__dict__
382
+ )
383
+
384
+ except Exception as e:
385
+ logger.error(f"Get mouse position failed: {e}")
386
+ return AutomationResult(
387
+ success=False,
388
+ operation="get_mouse_position",
389
+ error=str(e)
390
+ )
391
+
392
+ def move_mouse(self, x: int, y: int, duration: float = 0.5,
393
+ relative: bool = False) -> AutomationResult:
394
+ """Move mouse to specified coordinates"""
395
+ try:
396
+ if relative:
397
+ # Move relative to current position
398
+ current_x, current_y = pyautogui.position()
399
+ target_x = current_x + x
400
+ target_y = current_y + y
401
+ else:
402
+ target_x, target_y = x, y
403
+
404
+ # Validate target coordinates
405
+ if not (0 <= target_x < self.screen_info.width and 0 <= target_y < self.screen_info.height):
406
+ raise ValueError(f"Target coordinates ({target_x}, {target_y}) are outside screen bounds")
407
+
408
+ # Move mouse
409
+ pyautogui.moveTo(target_x, target_y, duration=duration)
410
+
411
+ return AutomationResult(
412
+ success=True,
413
+ operation="move_mouse",
414
+ data={
415
+ "target_x": target_x,
416
+ "target_y": target_y,
417
+ "duration": duration,
418
+ "relative": relative,
419
+ "original_coords": [x, y] if relative else None
420
+ }
421
+ )
422
+
423
+ except Exception as e:
424
+ logger.error(f"Move mouse failed: {e}")
425
+ return AutomationResult(
426
+ success=False,
427
+ operation="move_mouse",
428
+ error=str(e)
429
+ )
430
+
431
+ def click_mouse(self, x: Optional[int] = None, y: Optional[int] = None,
432
+ button: str = 'left', clicks: int = 1, interval: float = 0.0) -> AutomationResult:
433
+ """Click mouse at specified coordinates"""
434
+ try:
435
+ # Validate button
436
+ valid_buttons = ['left', 'right', 'middle']
437
+ if button not in valid_buttons:
438
+ raise ValueError(f"Invalid button '{button}'. Must be one of: {valid_buttons}")
439
+
440
+ # Click at coordinates or current position
441
+ if x is not None and y is not None:
442
+ # Validate coordinates
443
+ if not (0 <= x < self.screen_info.width and 0 <= y < self.screen_info.height):
444
+ raise ValueError(f"Coordinates ({x}, {y}) are outside screen bounds")
445
+
446
+ pyautogui.click(x, y, clicks=clicks, interval=interval, button=button)
447
+ click_position = (x, y)
448
+ else:
449
+ pyautogui.click(clicks=clicks, interval=interval, button=button)
450
+ click_position = pyautogui.position()
451
+
452
+ return AutomationResult(
453
+ success=True,
454
+ operation="click_mouse",
455
+ data={
456
+ "position": click_position,
457
+ "button": button,
458
+ "clicks": clicks,
459
+ "interval": interval
460
+ }
461
+ )
462
+
463
+ except Exception as e:
464
+ logger.error(f"Mouse click failed: {e}")
465
+ return AutomationResult(
466
+ success=False,
467
+ operation="click_mouse",
468
+ error=str(e)
469
+ )
470
+
471
+ def drag_mouse(self, start_x: int, start_y: int, end_x: int, end_y: int,
472
+ duration: float = 1.0, button: str = 'left') -> AutomationResult:
473
+ """Drag mouse from start to end coordinates"""
474
+ try:
475
+ # Validate coordinates
476
+ coords_to_check = [(start_x, start_y), (end_x, end_y)]
477
+ for x, y in coords_to_check:
478
+ if not (0 <= x < self.screen_info.width and 0 <= y < self.screen_info.height):
479
+ raise ValueError(f"Coordinates ({x}, {y}) are outside screen bounds")
480
+
481
+ # Validate button
482
+ valid_buttons = ['left', 'right', 'middle']
483
+ if button not in valid_buttons:
484
+ raise ValueError(f"Invalid button '{button}'. Must be one of: {valid_buttons}")
485
+
486
+ # Perform drag operation
487
+ pyautogui.drag(end_x - start_x, end_y - start_y, duration=duration, button=button)
488
+
489
+ return AutomationResult(
490
+ success=True,
491
+ operation="drag_mouse",
492
+ data={
493
+ "start_position": [start_x, start_y],
494
+ "end_position": [end_x, end_y],
495
+ "duration": duration,
496
+ "button": button,
497
+ "distance": ((end_x - start_x)**2 + (end_y - start_y)**2)**0.5
498
+ }
499
+ )
500
+
501
+ except Exception as e:
502
+ logger.error(f"Mouse drag failed: {e}")
503
+ return AutomationResult(
504
+ success=False,
505
+ operation="drag_mouse",
506
+ error=str(e)
507
+ )
508
+
509
+ def scroll_mouse(self, clicks: int, x: Optional[int] = None, y: Optional[int] = None) -> AutomationResult:
510
+ """Scroll mouse wheel"""
511
+ try:
512
+ # Scroll at coordinates or current position
513
+ if x is not None and y is not None:
514
+ # Validate coordinates
515
+ if not (0 <= x < self.screen_info.width and 0 <= y < self.screen_info.height):
516
+ raise ValueError(f"Coordinates ({x}, {y}) are outside screen bounds")
517
+
518
+ pyautogui.scroll(clicks, x, y)
519
+ scroll_position = (x, y)
520
+ else:
521
+ pyautogui.scroll(clicks)
522
+ scroll_position = pyautogui.position()
523
+
524
+ return AutomationResult(
525
+ success=True,
526
+ operation="scroll_mouse",
527
+ data={
528
+ "clicks": clicks,
529
+ "position": scroll_position,
530
+ "direction": "up" if clicks > 0 else "down"
531
+ }
532
+ )
533
+
534
+ except Exception as e:
535
+ logger.error(f"Mouse scroll failed: {e}")
536
+ return AutomationResult(
537
+ success=False,
538
+ operation="scroll_mouse",
539
+ error=str(e)
540
+ )
541
+
542
+ # ==========================================
543
+ # KEYBOARD CONTROL
544
+ # ==========================================
545
+
546
+ def type_text(self, text: str, interval: float = 0.0) -> AutomationResult:
547
+ """Type text with specified interval between characters"""
548
+ try:
549
+ pyautogui.typewrite(text, interval=interval)
550
+
551
+ return AutomationResult(
552
+ success=True,
553
+ operation="type_text",
554
+ data={
555
+ "text": text,
556
+ "length": len(text),
557
+ "interval": interval,
558
+ "estimated_duration": len(text) * interval
559
+ }
560
+ )
561
+
562
+ except Exception as e:
563
+ logger.error(f"Type text failed: {e}")
564
+ return AutomationResult(
565
+ success=False,
566
+ operation="type_text",
567
+ error=str(e)
568
+ )
569
+
570
+ def press_key(self, key: str, presses: int = 1, interval: float = 0.0) -> AutomationResult:
571
+ """Press a key multiple times"""
572
+ try:
573
+ pyautogui.press(key, presses=presses, interval=interval)
574
+
575
+ return AutomationResult(
576
+ success=True,
577
+ operation="press_key",
578
+ data={
579
+ "key": key,
580
+ "presses": presses,
581
+ "interval": interval
582
+ }
583
+ )
584
+
585
+ except Exception as e:
586
+ logger.error(f"Press key failed: {e}")
587
+ return AutomationResult(
588
+ success=False,
589
+ operation="press_key",
590
+ error=str(e)
591
+ )
592
+
593
+ def key_combination(self, keys: List[str]) -> AutomationResult:
594
+ """Press a combination of keys simultaneously"""
595
+ try:
596
+ pyautogui.hotkey(*keys)
597
+
598
+ return AutomationResult(
599
+ success=True,
600
+ operation="key_combination",
601
+ data={
602
+ "keys": keys,
603
+ "combination": "+".join(keys)
604
+ }
605
+ )
606
+
607
+ except Exception as e:
608
+ logger.error(f"Key combination failed: {e}")
609
+ return AutomationResult(
610
+ success=False,
611
+ operation="key_combination",
612
+ error=str(e)
613
+ )
614
+
615
+ def hold_key(self, key: str, duration: float = 1.0) -> AutomationResult:
616
+ """Hold a key down for specified duration"""
617
+ try:
618
+ pyautogui.keyDown(key)
619
+ time.sleep(duration)
620
+ pyautogui.keyUp(key)
621
+
622
+ return AutomationResult(
623
+ success=True,
624
+ operation="hold_key",
625
+ data={
626
+ "key": key,
627
+ "duration": duration
628
+ }
629
+ )
630
+
631
+ except Exception as e:
632
+ logger.error(f"Hold key failed: {e}")
633
+ return AutomationResult(
634
+ success=False,
635
+ operation="hold_key",
636
+ error=str(e)
637
+ )
638
+
639
+ # ==========================================
640
+ # UTILITY FUNCTIONS
641
+ # ==========================================
642
+
643
+ def get_screen_info(self) -> AutomationResult:
644
+ """Get detailed screen information"""
645
+ try:
646
+ return AutomationResult(
647
+ success=True,
648
+ operation="get_screen_info",
649
+ data=self.screen_info.__dict__
650
+ )
651
+
652
+ except Exception as e:
653
+ logger.error(f"Get screen info failed: {e}")
654
+ return AutomationResult(
655
+ success=False,
656
+ operation="get_screen_info",
657
+ error=str(e)
658
+ )
659
+
660
+ def is_on_screen(self, x: int, y: int) -> AutomationResult:
661
+ """Check if coordinates are on screen"""
662
+ try:
663
+ on_screen = pyautogui.onScreen(x, y)
664
+
665
+ return AutomationResult(
666
+ success=True,
667
+ operation="is_on_screen",
668
+ data={
669
+ "x": x,
670
+ "y": y,
671
+ "on_screen": on_screen,
672
+ "screen_width": self.screen_info.width,
673
+ "screen_height": self.screen_info.height
674
+ }
675
+ )
676
+
677
+ except Exception as e:
678
+ logger.error(f"Is on screen check failed: {e}")
679
+ return AutomationResult(
680
+ success=False,
681
+ operation="is_on_screen",
682
+ error=str(e)
683
+ )
684
+
685
+ def set_pause(self, pause_duration: float) -> AutomationResult:
686
+ """Set the pause duration between PyAutoGUI actions"""
687
+ try:
688
+ old_pause = pyautogui.PAUSE
689
+ pyautogui.PAUSE = pause_duration
690
+
691
+ return AutomationResult(
692
+ success=True,
693
+ operation="set_pause",
694
+ data={
695
+ "old_pause": old_pause,
696
+ "new_pause": pause_duration
697
+ }
698
+ )
699
+
700
+ except Exception as e:
701
+ logger.error(f"Set pause failed: {e}")
702
+ return AutomationResult(
703
+ success=False,
704
+ operation="set_pause",
705
+ error=str(e)
706
+ )
707
+
708
+ def set_failsafe(self, enabled: bool) -> AutomationResult:
709
+ """Enable or disable PyAutoGUI failsafe"""
710
+ try:
711
+ old_failsafe = pyautogui.FAILSAFE
712
+ pyautogui.FAILSAFE = enabled
713
+
714
+ return AutomationResult(
715
+ success=True,
716
+ operation="set_failsafe",
717
+ data={
718
+ "old_failsafe": old_failsafe,
719
+ "new_failsafe": enabled
720
+ }
721
+ )
722
+
723
+ except Exception as e:
724
+ logger.error(f"Set failsafe failed: {e}")
725
+ return AutomationResult(
726
+ success=False,
727
+ operation="set_failsafe",
728
+ error=str(e)
729
+ )
730
+
731
+ # ==========================================
732
+ # ADVANCED FEATURES
733
+ # ==========================================
734
+
735
+ def create_image_template(self, name: str, x: int, y: int, width: int, height: int) -> AutomationResult:
736
+ """Create an image template from screen region for future recognition"""
737
+ try:
738
+ # Take screenshot of region
739
+ region = (x, y, width, height)
740
+ template_img = pyautogui.screenshot(region=region)
741
+
742
+ # Save template
743
+ self.image_templates[name] = {
744
+ 'image': template_img,
745
+ 'region': region,
746
+ 'created_at': time.time()
747
+ }
748
+
749
+ # Convert to base64 for return
750
+ img_buffer = io.BytesIO()
751
+ template_img.save(img_buffer, format='PNG')
752
+ img_base64 = base64.b64encode(img_buffer.getvalue()).decode()
753
+
754
+ return AutomationResult(
755
+ success=True,
756
+ operation="create_image_template",
757
+ data={
758
+ "template_name": name,
759
+ "region": region,
760
+ "image_base64": img_base64,
761
+ "width": template_img.width,
762
+ "height": template_img.height
763
+ }
764
+ )
765
+
766
+ except Exception as e:
767
+ logger.error(f"Create image template failed: {e}")
768
+ return AutomationResult(
769
+ success=False,
770
+ operation="create_image_template",
771
+ error=str(e)
772
+ )
773
+
774
+ def find_template_on_screen(self, template_name: str, confidence: float = 0.8) -> AutomationResult:
775
+ """Find a previously created template on the screen"""
776
+ try:
777
+ if template_name not in self.image_templates:
778
+ raise ValueError(f"Template '{template_name}' not found")
779
+
780
+ template_data = self.image_templates[template_name]
781
+ template_img = template_data['image']
782
+
783
+ # Save template temporarily for locateOnScreen
784
+ temp_path = f"temp_template_{template_name}.png"
785
+ template_img.save(temp_path)
786
+
787
+ try:
788
+ # Search for template
789
+ location = pyautogui.locateOnScreen(temp_path, confidence=confidence)
790
+
791
+ if location:
792
+ center = pyautogui.center(location)
793
+ match = ImageMatch(
794
+ found=True,
795
+ x=location.left,
796
+ y=location.top,
797
+ width=location.width,
798
+ height=location.height,
799
+ confidence=confidence,
800
+ center_x=center.x,
801
+ center_y=center.y
802
+ )
803
+ else:
804
+ match = ImageMatch(found=False)
805
+
806
+ return AutomationResult(
807
+ success=True,
808
+ operation="find_template_on_screen",
809
+ data={
810
+ "template_name": template_name,
811
+ "match": match.__dict__,
812
+ "search_confidence": confidence
813
+ }
814
+ )
815
+
816
+ finally:
817
+ # Clean up temporary file
818
+ if os.path.exists(temp_path):
819
+ os.remove(temp_path)
820
+
821
+ except Exception as e:
822
+ logger.error(f"Find template failed: {e}")
823
+ return AutomationResult(
824
+ success=False,
825
+ operation="find_template_on_screen",
826
+ error=str(e)
827
+ )
828
+
829
+ def get_available_keys(self) -> AutomationResult:
830
+ """Get list of all available keyboard keys"""
831
+ try:
832
+ # PyAutoGUI key names
833
+ available_keys = [
834
+ # Letters
835
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
836
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
837
+
838
+ # Numbers
839
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
840
+
841
+ # Function keys
842
+ 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12',
843
+
844
+ # Special keys
845
+ 'enter', 'return', 'space', 'tab', 'backspace', 'delete', 'esc', 'escape',
846
+ 'shift', 'ctrl', 'alt', 'win', 'cmd', 'option',
847
+
848
+ # Arrow keys
849
+ 'up', 'down', 'left', 'right',
850
+
851
+ # Navigation
852
+ 'home', 'end', 'pageup', 'pagedown', 'insert',
853
+
854
+ # Symbols
855
+ '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+',
856
+ '[', ']', '{', '}', '\\', '|', ';', ':', "'", '"', ',', '.', '<', '>',
857
+ '/', '?', '`', '~',
858
+
859
+ # Lock keys
860
+ 'capslock', 'numlock', 'scrolllock',
861
+
862
+ # Numpad
863
+ 'num0', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8', 'num9',
864
+ 'add', 'subtract', 'multiply', 'divide', 'decimal'
865
+ ]
866
+
867
+ # Organize by category
868
+ key_categories = {
869
+ 'letters': [k for k in available_keys if k.isalpha() and len(k) == 1],
870
+ 'numbers': [k for k in available_keys if k.isdigit()],
871
+ 'function_keys': [k for k in available_keys if k.startswith('f') and k[1:].isdigit()],
872
+ 'arrow_keys': ['up', 'down', 'left', 'right'],
873
+ 'modifier_keys': ['shift', 'ctrl', 'alt', 'win', 'cmd', 'option'],
874
+ 'navigation_keys': ['home', 'end', 'pageup', 'pagedown', 'insert'],
875
+ 'special_keys': ['enter', 'return', 'space', 'tab', 'backspace', 'delete', 'esc', 'escape'],
876
+ 'lock_keys': ['capslock', 'numlock', 'scrolllock'],
877
+ 'numpad_keys': [k for k in available_keys if k.startswith('num') or k in ['add', 'subtract', 'multiply', 'divide', 'decimal']],
878
+ 'symbols': [k for k in available_keys if not k.isalnum() and len(k) == 1]
879
+ }
880
+
881
+ return AutomationResult(
882
+ success=True,
883
+ operation="get_available_keys",
884
+ data={
885
+ "all_keys": available_keys,
886
+ "categories": key_categories,
887
+ "total_count": len(available_keys)
888
+ }
889
+ )
890
+
891
+ except Exception as e:
892
+ logger.error(f"Get available keys failed: {e}")
893
+ return AutomationResult(
894
+ success=False,
895
+ operation="get_available_keys",
896
+ error=str(e)
897
+ )
898
+
899
+ # Singleton instance for global access
900
+ _pyautogui_controller = None
901
+
902
+ def get_pyautogui_controller() -> PyAutoGUIController:
903
+ """Get the global PyAutoGUI controller instance"""
904
+ global _pyautogui_controller
905
+ if _pyautogui_controller is None:
906
+ _pyautogui_controller = PyAutoGUIController()
907
+ return _pyautogui_controller
908
+
909
+ # Convenience functions for direct access
910
+ def screenshot(region=None, save_path=None):
911
+ """Take a screenshot"""
912
+ return get_pyautogui_controller().take_screenshot(region, save_path)
913
+
914
+ def pixel(x, y):
915
+ """Get pixel color"""
916
+ return get_pyautogui_controller().get_pixel_color(x, y)
917
+
918
+ def locate_image(image_path, confidence=0.8, region=None):
919
+ """Find image on screen"""
920
+ return get_pyautogui_controller().find_image_on_screen(image_path, confidence, region)
921
+
922
+ def mouse_position():
923
+ """Get mouse position"""
924
+ return get_pyautogui_controller().get_mouse_position()
925
+
926
+ def click(x=None, y=None, button='left', clicks=1):
927
+ """Click mouse"""
928
+ return get_pyautogui_controller().click_mouse(x, y, button, clicks)
929
+
930
+ def type_text(text, interval=0.0):
931
+ """Type text"""
932
+ return get_pyautogui_controller().type_text(text, interval)
933
+
934
+ def press(key, presses=1):
935
+ """Press key"""
936
+ return get_pyautogui_controller().press_key(key, presses)
937
+
938
+ if __name__ == "__main__":
939
+ # Quick test
940
+ if PYAUTOGUI_AVAILABLE:
941
+ controller = PyAutoGUIController()
942
+ print("PyAutoGUI Controller initialized successfully!")
943
+
944
+ # Test basic functionality
945
+ screen_info = controller.get_screen_info()
946
+ print(f"Screen: {screen_info.data}")
947
+
948
+ mouse_pos = controller.get_mouse_position()
949
+ print(f"Mouse: {mouse_pos.data}")
950
+ else:
951
+ print("PyAutoGUI not available. Install with: pip install pyautogui pillow opencv-python")