ok-script 0.0.7__tar.gz

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 (127) hide show
  1. ok-script-0.0.7/PKG-INFO +64 -0
  2. ok-script-0.0.7/README.md +44 -0
  3. ok-script-0.0.7/autohelper/AutoHelper.py +116 -0
  4. ok-script-0.0.7/autohelper/__init__.py +0 -0
  5. ok-script-0.0.7/autohelper/capture/BaseCaptureMethod.py +30 -0
  6. ok-script-0.0.7/autohelper/capture/HwndWindow.py +138 -0
  7. ok-script-0.0.7/autohelper/capture/__init__.py +0 -0
  8. ok-script-0.0.7/autohelper/capture/adb/ADBCaptureMethod.py +29 -0
  9. ok-script-0.0.7/autohelper/capture/adb/DeviceManager.py +209 -0
  10. ok-script-0.0.7/autohelper/capture/adb/__init__.py +0 -0
  11. ok-script-0.0.7/autohelper/capture/adb/bin/__init__.py +0 -0
  12. ok-script-0.0.7/autohelper/capture/adb/targets.py +61 -0
  13. ok-script-0.0.7/autohelper/capture/adb/vbox.py +140 -0
  14. ok-script-0.0.7/autohelper/capture/windows/WindowsGraphicsCaptureMethod.py +228 -0
  15. ok-script-0.0.7/autohelper/capture/windows/__init__.py +0 -0
  16. ok-script-0.0.7/autohelper/capture/windows/d3d11.py +74 -0
  17. ok-script-0.0.7/autohelper/capture/windows/utils.py +149 -0
  18. ok-script-0.0.7/autohelper/capture/windows/window.py +36 -0
  19. ok-script-0.0.7/autohelper/color/Color.py +41 -0
  20. ok-script-0.0.7/autohelper/color/__init__.py +0 -0
  21. ok-script-0.0.7/autohelper/config/Config.py +79 -0
  22. ok-script-0.0.7/autohelper/config/InfoDict.py +16 -0
  23. ok-script-0.0.7/autohelper/config/__init__.py +0 -0
  24. ok-script-0.0.7/autohelper/feature/Box.py +199 -0
  25. ok-script-0.0.7/autohelper/feature/Feature.py +20 -0
  26. ok-script-0.0.7/autohelper/feature/FeatureSet.py +207 -0
  27. ok-script-0.0.7/autohelper/feature/FindFeature.py +52 -0
  28. ok-script-0.0.7/autohelper/feature/__init__.py +0 -0
  29. ok-script-0.0.7/autohelper/gui/App.py +120 -0
  30. ok-script-0.0.7/autohelper/gui/Communicate.py +22 -0
  31. ok-script-0.0.7/autohelper/gui/MainWindow.py +69 -0
  32. ok-script-0.0.7/autohelper/gui/TabTitles.py +56 -0
  33. ok-script-0.0.7/autohelper/gui/__init__.py +5 -0
  34. ok-script-0.0.7/autohelper/gui/debug/AspectRatioWidget.py +31 -0
  35. ok-script-0.0.7/autohelper/gui/debug/DebugTab.py +37 -0
  36. ok-script-0.0.7/autohelper/gui/debug/FrameWidget.py +125 -0
  37. ok-script-0.0.7/autohelper/gui/debug/InfoWidget.py +40 -0
  38. ok-script-0.0.7/autohelper/gui/debug/LoggerWidget.py +74 -0
  39. ok-script-0.0.7/autohelper/gui/debug/__init__.py +0 -0
  40. ok-script-0.0.7/autohelper/gui/i18n/__init__.py +0 -0
  41. ok-script-0.0.7/autohelper/gui/i18n/path.py +3 -0
  42. ok-script-0.0.7/autohelper/gui/icon/__init__.py +0 -0
  43. ok-script-0.0.7/autohelper/gui/icon/icon.py +6 -0
  44. ok-script-0.0.7/autohelper/gui/loading/LoadingWindow.py +142 -0
  45. ok-script-0.0.7/autohelper/gui/loading/__init__.py +0 -0
  46. ok-script-0.0.7/autohelper/gui/overlay/OverlayWindow.py +34 -0
  47. ok-script-0.0.7/autohelper/gui/overlay/__init__.py +0 -0
  48. ok-script-0.0.7/autohelper/gui/resources.py +485 -0
  49. ok-script-0.0.7/autohelper/gui/tasks/ConfigItemFactory.py +14 -0
  50. ok-script-0.0.7/autohelper/gui/tasks/StartButton.py +47 -0
  51. ok-script-0.0.7/autohelper/gui/tasks/TaskOpButton.py +33 -0
  52. ok-script-0.0.7/autohelper/gui/tasks/TaskTab.py +121 -0
  53. ok-script-0.0.7/autohelper/gui/tasks/TooltipTableWidget.py +39 -0
  54. ok-script-0.0.7/autohelper/gui/tasks/__init__.py +0 -0
  55. ok-script-0.0.7/autohelper/gui/util/Alert.py +20 -0
  56. ok-script-0.0.7/autohelper/gui/util/InitWorker.py +17 -0
  57. ok-script-0.0.7/autohelper/gui/util/__init__.py +0 -0
  58. ok-script-0.0.7/autohelper/gui/widget/ListTableWidgetItem.py +27 -0
  59. ok-script-0.0.7/autohelper/gui/widget/NumericTableWidgetItem.py +33 -0
  60. ok-script-0.0.7/autohelper/gui/widget/RoundCornerContainer.py +59 -0
  61. ok-script-0.0.7/autohelper/gui/widget/TabWidget.py +30 -0
  62. ok-script-0.0.7/autohelper/gui/widget/UpdateConfigWidgetItem.py +16 -0
  63. ok-script-0.0.7/autohelper/gui/widget/YesNonWidgetItem.py +23 -0
  64. ok-script-0.0.7/autohelper/gui/widget/__init__.py +0 -0
  65. ok-script-0.0.7/autohelper/interaction/ADBInteraction.py +33 -0
  66. ok-script-0.0.7/autohelper/interaction/BaseInteraction.py +36 -0
  67. ok-script-0.0.7/autohelper/interaction/Win32Interaction.py +56 -0
  68. ok-script-0.0.7/autohelper/interaction/__init__.py +0 -0
  69. ok-script-0.0.7/autohelper/logging/Logger.py +103 -0
  70. ok-script-0.0.7/autohelper/logging/__init__.py +0 -0
  71. ok-script-0.0.7/autohelper/ocr/OCR.py +50 -0
  72. ok-script-0.0.7/autohelper/ocr/__init__.py +0 -0
  73. ok-script-0.0.7/autohelper/predict/__init__.py +0 -0
  74. ok-script-0.0.7/autohelper/predict/predict.py +45 -0
  75. ok-script-0.0.7/autohelper/rotypes/Windows/Foundation/Collections.py +60 -0
  76. ok-script-0.0.7/autohelper/rotypes/Windows/Foundation/__init__.py +153 -0
  77. ok-script-0.0.7/autohelper/rotypes/Windows/Globalization/__init__.py +25 -0
  78. ok-script-0.0.7/autohelper/rotypes/Windows/Graphics/Capture/__init__.py +110 -0
  79. ok-script-0.0.7/autohelper/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +41 -0
  80. ok-script-0.0.7/autohelper/rotypes/Windows/Graphics/DirectX/__init__.py +167 -0
  81. ok-script-0.0.7/autohelper/rotypes/Windows/Graphics/Imaging/__init__.py +34 -0
  82. ok-script-0.0.7/autohelper/rotypes/Windows/Graphics/__init__.py +5 -0
  83. ok-script-0.0.7/autohelper/rotypes/Windows/Media/Ocr/__init__.py +76 -0
  84. ok-script-0.0.7/autohelper/rotypes/Windows/Media/__init__.py +0 -0
  85. ok-script-0.0.7/autohelper/rotypes/Windows/Security/Cryptography/__init__.py +18 -0
  86. ok-script-0.0.7/autohelper/rotypes/Windows/Security/__init__.py +0 -0
  87. ok-script-0.0.7/autohelper/rotypes/Windows/Storage/Streams/__init__.py +27 -0
  88. ok-script-0.0.7/autohelper/rotypes/Windows/Storage/__init__.py +0 -0
  89. ok-script-0.0.7/autohelper/rotypes/Windows/__init__.py +0 -0
  90. ok-script-0.0.7/autohelper/rotypes/__init__.py +4 -0
  91. ok-script-0.0.7/autohelper/rotypes/delegate.py +105 -0
  92. ok-script-0.0.7/autohelper/rotypes/export.py +21 -0
  93. ok-script-0.0.7/autohelper/rotypes/idldsl.py +272 -0
  94. ok-script-0.0.7/autohelper/rotypes/inspectable.py +79 -0
  95. ok-script-0.0.7/autohelper/rotypes/roapi.py +30 -0
  96. ok-script-0.0.7/autohelper/rotypes/types.py +77 -0
  97. ok-script-0.0.7/autohelper/rotypes/winstring.py +44 -0
  98. ok-script-0.0.7/autohelper/save/BlackBarProcessor.py +25 -0
  99. ok-script-0.0.7/autohelper/save/PostProcessor.py +6 -0
  100. ok-script-0.0.7/autohelper/save/SaveByInterval.py +30 -0
  101. ok-script-0.0.7/autohelper/save/SaveByKeyPress.py +27 -0
  102. ok-script-0.0.7/autohelper/save/SaveMethodBase.py +46 -0
  103. ok-script-0.0.7/autohelper/save/__init__.py +0 -0
  104. ok-script-0.0.7/autohelper/scene/FeatureScene.py +14 -0
  105. ok-script-0.0.7/autohelper/scene/Scene.py +21 -0
  106. ok-script-0.0.7/autohelper/scene/__init__.py +0 -0
  107. ok-script-0.0.7/autohelper/stats/StreamStats.py +34 -0
  108. ok-script-0.0.7/autohelper/stats/__init__.py +0 -0
  109. ok-script-0.0.7/autohelper/task/BaseTask.py +109 -0
  110. ok-script-0.0.7/autohelper/task/ExecutorOperation.py +119 -0
  111. ok-script-0.0.7/autohelper/task/FindFeatureTask.py +6 -0
  112. ok-script-0.0.7/autohelper/task/TaskExecutor.py +249 -0
  113. ok-script-0.0.7/autohelper/task/__init__.py +0 -0
  114. ok-script-0.0.7/autohelper/util/__init__.py +0 -0
  115. ok-script-0.0.7/autohelper/util/json.py +27 -0
  116. ok-script-0.0.7/autohelper/util/list.py +2 -0
  117. ok-script-0.0.7/autohelper/util/path.py +37 -0
  118. ok-script-0.0.7/autohelper/util/thread.py +12 -0
  119. ok-script-0.0.7/autohelper/util/win32_process.py +86 -0
  120. ok-script-0.0.7/autohelper/util/yaml.py +28 -0
  121. ok-script-0.0.7/ok_script.egg-info/PKG-INFO +64 -0
  122. ok-script-0.0.7/ok_script.egg-info/SOURCES.txt +125 -0
  123. ok-script-0.0.7/ok_script.egg-info/dependency_links.txt +1 -0
  124. ok-script-0.0.7/ok_script.egg-info/requires.txt +6 -0
  125. ok-script-0.0.7/ok_script.egg-info/top_level.txt +1 -0
  126. ok-script-0.0.7/setup.cfg +4 -0
  127. ok-script-0.0.7/setup.py +31 -0
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.1
2
+ Name: ok-script
3
+ Version: 0.0.7
4
+ Summary: Automation with Computer Vision for Python
5
+ Home-page: https://github.com/ok-oldking/ok-script
6
+ Author: ok-oldking
7
+ Author-email: firedcto@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Requires-Python: >=3.4
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: adbutils==2.2.1
15
+ Requires-Dist: numpy~=1.26.4
16
+ Requires-Dist: PyDirectInput~=1.0.4
17
+ Requires-Dist: pywin32==306
18
+ Requires-Dist: typing-extensions~=4.10.0
19
+ Requires-Dist: PySide6~=6.6.2
20
+
21
+ # autoui
22
+
23
+ This project aims to provide a universal framework for developing automation tool for PC/Android games, running in
24
+ windows,
25
+ emulator, or physical Android Phone. Currently still working in progress, there is a working
26
+
27
+ ## Showcase
28
+
29
+ ![docs/img_showcase.png](docs/img_showcase.png)
30
+
31
+ ## Example Usage
32
+
33
+ Basically you only need to write the Tasks and Scenes, the autoui framework will do all the heavy lifting.
34
+ [genshin/main.py](genshin/main.py)
35
+
36
+ ```python
37
+ # Defining game scenes to handle different in-game situations through automated tasks
38
+ task_executor.scenes.extend([
39
+ WorldScene(interaction, feature_set),
40
+ StartScene(interaction, feature_set),
41
+ MonthlyCardScene(interaction, feature_set),
42
+ DialogCloseButtonScene(interaction, feature_set),
43
+ DialogChoicesScene(interaction, feature_set),
44
+ DialogPlayingScene(interaction, feature_set),
45
+ BlackDialogScene(interaction, feature_set),
46
+ ])
47
+
48
+ # Adding automated tasks for gameplay, such as dialog navigation and item collection
49
+ task_executor.tasks.extend([
50
+ AutoPlayDialogTask(interaction, feature_set), # speeding up the dialogs
51
+ AutoChooseDialogTask(interaction, feature_set), # choose dialog options
52
+ AutoPickTask(interaction, feature_set), # pickup items in world scene
53
+ AutoLoginTask(interaction, feature_set), # auto login and claim rewards
54
+ ])
55
+ ```
56
+
57
+ ## Scenes
58
+
59
+ ## Tasks
60
+
61
+ ## Project Structure
62
+
63
+ - `autoui`: Framework code.
64
+ - `genshin`: Example Genshin Impact automation project
@@ -0,0 +1,44 @@
1
+ # autoui
2
+
3
+ This project aims to provide a universal framework for developing automation tool for PC/Android games, running in
4
+ windows,
5
+ emulator, or physical Android Phone. Currently still working in progress, there is a working
6
+
7
+ ## Showcase
8
+
9
+ ![docs/img_showcase.png](docs/img_showcase.png)
10
+
11
+ ## Example Usage
12
+
13
+ Basically you only need to write the Tasks and Scenes, the autoui framework will do all the heavy lifting.
14
+ [genshin/main.py](genshin/main.py)
15
+
16
+ ```python
17
+ # Defining game scenes to handle different in-game situations through automated tasks
18
+ task_executor.scenes.extend([
19
+ WorldScene(interaction, feature_set),
20
+ StartScene(interaction, feature_set),
21
+ MonthlyCardScene(interaction, feature_set),
22
+ DialogCloseButtonScene(interaction, feature_set),
23
+ DialogChoicesScene(interaction, feature_set),
24
+ DialogPlayingScene(interaction, feature_set),
25
+ BlackDialogScene(interaction, feature_set),
26
+ ])
27
+
28
+ # Adding automated tasks for gameplay, such as dialog navigation and item collection
29
+ task_executor.tasks.extend([
30
+ AutoPlayDialogTask(interaction, feature_set), # speeding up the dialogs
31
+ AutoChooseDialogTask(interaction, feature_set), # choose dialog options
32
+ AutoPickTask(interaction, feature_set), # pickup items in world scene
33
+ AutoLoginTask(interaction, feature_set), # auto login and claim rewards
34
+ ])
35
+ ```
36
+
37
+ ## Scenes
38
+
39
+ ## Tasks
40
+
41
+ ## Project Structure
42
+
43
+ - `autoui`: Framework code.
44
+ - `genshin`: Example Genshin Impact automation project
@@ -0,0 +1,116 @@
1
+ import sys
2
+ import threading
3
+ import time
4
+ from typing import Dict, Any
5
+
6
+ from PySide6.QtWidgets import QApplication
7
+
8
+ import autohelper
9
+ from autohelper.feature.FeatureSet import FeatureSet
10
+ from autohelper.gui.App import App
11
+ from autohelper.gui.Communicate import communicate
12
+ from autohelper.gui.overlay.OverlayWindow import OverlayWindow
13
+ from autohelper.gui.util.InitWorker import InitWorker
14
+ from autohelper.logging.Logger import get_logger, config_logger
15
+ from autohelper.task.TaskExecutor import TaskExecutor
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class AutoHelper:
21
+ executor: TaskExecutor
22
+ adb = None
23
+ adb_device = None
24
+ feature_set = None
25
+ hwnd = None
26
+ device_manager = None
27
+ ocr = None
28
+ overlay_window = None
29
+ app = None
30
+
31
+ def __init__(self, config: Dict[str, Any]):
32
+ print(f"AutoHelper init, config: {config}")
33
+ self.debug = config.get("debug", False)
34
+ self.exit_event = threading.Event()
35
+ self.config = config
36
+ self.init_device_manager()
37
+ if config.get("use_gui"):
38
+ self.app = App(self.config.get('gui_icon'), self.debug, self.config.get('gui_title'),
39
+ self.config['tasks'], self.exit_event)
40
+ autohelper.gui.app = self.app
41
+ self.app.show_loading()
42
+ self.worker = InitWorker(self.do_init)
43
+ self.worker.start()
44
+ self.app.exec()
45
+ else:
46
+ self.device_manager.set_preferred_device()
47
+ self.device_manager.start()
48
+ self.do_init()
49
+ self.task_executor.start()
50
+ if config.get("debug"):
51
+ self.app = QApplication(sys.argv)
52
+ self.overlay_window = OverlayWindow(autohelper.gui.device_manager.hwnd)
53
+ self.app.exec()
54
+ else:
55
+ try:
56
+ # Starting the task in a separate thread (optional)
57
+ # This allows the main thread to remain responsive to keyboard interrupts
58
+ task_thread = threading.Thread(target=self.wait_task())
59
+ task_thread.start()
60
+
61
+ # Wait for the task thread to end (which it won't, in this case, without an interrupt)
62
+ task_thread.join()
63
+ except KeyboardInterrupt:
64
+ self.exit_event.set()
65
+ logger.info("Keyboard interrupt received, exiting script.")
66
+ finally:
67
+ # Clean-up code goes here (if any)
68
+ # This block ensures that the script terminates gracefully,
69
+ # releasing resources or performing necessary clean-up operations.
70
+ logger.info("Script has terminated.")
71
+
72
+ def init_message(self, message: str, done=False):
73
+ communicate.init.emit(done, message)
74
+ if self.exit_event.is_set():
75
+ self.worker.quit()
76
+
77
+ def do_init(self):
78
+ logger.info(f"initializing {self.__class__.__name__}, config: {self.config}")
79
+ if self.config.get('ocr'):
80
+ self.init_message("RapidOCR init Start")
81
+ from rapidocr_onnxruntime import RapidOCR
82
+ self.ocr = RapidOCR()
83
+ self.init_message("RapidOCR init Complete")
84
+
85
+ config_logger(self.config)
86
+
87
+ if self.config.get('coco_feature_folder') is not None:
88
+ self.init_message("FeatureSet init Start")
89
+ coco_feature_folder = self.config.get('coco_feature_folder')
90
+ self.feature_set = FeatureSet(coco_feature_folder,
91
+ default_horizontal_variance=self.config.get('default_horizontal_variance', 0),
92
+ default_vertical_variance=self.config.get('default_vertical_variance', 0),
93
+ default_threshold=self.config.get('default_threshold', 0))
94
+ self.init_message("FeatureSet init Complete")
95
+
96
+ self.init_message("TaskExecutor init Start")
97
+ self.task_executor = TaskExecutor(self.device_manager, exit_event=self.exit_event,
98
+ tasks=self.config['tasks'], scenes=self.config['scenes'],
99
+ feature_set=self.feature_set,
100
+ ocr=self.ocr, config_folder=self.config.get("config_folder") or "config")
101
+ self.init_message("TaskExecutor init Done")
102
+
103
+ if self.app:
104
+ autohelper.gui.executor = self.task_executor
105
+
106
+ def wait_task(self):
107
+ while not self.exit_event.is_set():
108
+ time.sleep(1)
109
+
110
+ def init_device_manager(self):
111
+ if self.device_manager is None:
112
+ from autohelper.capture.adb.DeviceManager import DeviceManager
113
+ self.device_manager = DeviceManager(self.config.get("config_folder") or "config",
114
+ self.config.get('capture_window_title'), self.config.get("debug"),
115
+ self.exit_event)
116
+ autohelper.gui.device_manager = self.device_manager
File without changes
@@ -0,0 +1,30 @@
1
+ import numpy as np
2
+
3
+
4
+ class BaseCaptureMethod:
5
+ name = "None"
6
+ description = ""
7
+ last_captured_frame: np.ndarray
8
+ top_cut = 0
9
+ bottom_cut = 0
10
+ left_cut = 0
11
+ right_cut = 0
12
+ width = 0
13
+ height = 0
14
+
15
+ def __init__(self):
16
+ # Some capture methods don't need an initialization process
17
+ pass
18
+
19
+ def close(self):
20
+ # Some capture methods don't need an initialization process
21
+ pass
22
+
23
+ def get_frame(self) -> np.ndarray | None:
24
+ pass
25
+
26
+ def draw_rectangle(self):
27
+ pass
28
+
29
+ def clickable(self):
30
+ pass
@@ -0,0 +1,138 @@
1
+ # original https://github.com/dantmnf & https://github.com/hakaboom/winAuto
2
+ import re
3
+ import threading
4
+
5
+ from typing_extensions import override
6
+ from win32 import win32gui
7
+
8
+ from autohelper.capture.windows.window import is_foreground_window, get_window_bounds
9
+ from autohelper.gui.Communicate import communicate
10
+ from autohelper.logging.Logger import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class HwndWindow:
16
+ visible = True
17
+ x = 0
18
+ y = 0
19
+ width = 0
20
+ height = 0
21
+ title_height = 0
22
+ border = 0
23
+ scaling = 1
24
+ top_cut = 0
25
+ right_cut = 0
26
+ bottom_cut = 0
27
+ left_cut = 0
28
+ window_change_listeners = []
29
+ frame_aspect_ratio = 0
30
+ hwnd = None
31
+ frame_width = 0
32
+ frame_height = 0
33
+ exists = False
34
+
35
+ def __init__(self, title="", exit_event=threading.Event(), frame_width=0, frame_height=0):
36
+ super().__init__()
37
+ self.app_exit_event = exit_event
38
+ self.stop_event = threading.Event()
39
+ self.update_title_re(title)
40
+ self.visible = False
41
+ self.update_frame_size(frame_width, frame_height)
42
+ self.do_update_window_size()
43
+ self.thread = threading.Thread(target=self.update_window_size)
44
+ self.thread.start()
45
+
46
+ @override
47
+ def close(self):
48
+ self.app_exit_event.set()
49
+
50
+ def stop(self):
51
+ self.stop_event.set()
52
+
53
+ def update_title_re(self, title):
54
+ self.title = re.compile(title)
55
+
56
+ def update_frame_size(self, width, height):
57
+ if width != self.frame_width or height != self.frame_height:
58
+ self.frame_width = width
59
+ self.frame_height = height
60
+ if width > 0 and height > 0:
61
+ self.frame_aspect_ratio = width / height
62
+ logger.debug(f"HwndWindow: frame ratio:{self.frame_aspect_ratio} width: {width}, height: {height}")
63
+
64
+ def update_window_size(self):
65
+ while not self.app_exit_event.is_set() and not self.stop_event.is_set():
66
+ self.do_update_window_size()
67
+ self.app_exit_event.wait(0.1)
68
+
69
+ def get_abs_cords(self, x, y):
70
+ return int(self.x * self.scaling + (self.border * self.scaling + x)), int(
71
+ self.y * self.scaling + (y + self.title_height * self.scaling))
72
+
73
+ def get_top_left_frame_offset(self):
74
+ return int(self.border * self.scaling), int(
75
+ self.title_height * self.scaling)
76
+
77
+ def do_update_window_size(self):
78
+ visible, x, y, border, title_height, width, height, scaling = self.visible, self.x, self.y, self.border, self.title_height, self.width, self.height, self.scaling
79
+ if self.hwnd is None:
80
+ self.hwnd = find_hwnds_by_title(self.title)
81
+ if self.hwnd is not None:
82
+ self.exists = win32gui.IsWindow(self.hwnd)
83
+ if self.exists:
84
+ visible = is_foreground_window(self.hwnd)
85
+ x, y, border, title_height, width, height, scaling = get_window_bounds(
86
+ self.hwnd)
87
+ if self.frame_aspect_ratio != 0:
88
+ window_ratio = width / height
89
+ if window_ratio < self.frame_aspect_ratio:
90
+ cropped_window_height = int(width / self.frame_aspect_ratio)
91
+ title_height += height - cropped_window_height
92
+ height = cropped_window_height
93
+ height = height
94
+ width = width
95
+ title_height = title_height
96
+ else:
97
+ self.hwnd = None
98
+ changed = False
99
+ if visible != self.visible or self.scaling != scaling:
100
+ self.visible = visible
101
+ self.scaling = scaling
102
+ changed = True
103
+ if (
104
+ x != self.x or y != self.y or border != self.border or title_height != self.title_height or width != self.width or height != self.height or scaling != self.scaling) and (
105
+ (x >= 0 and y >= 0) or self.visible):
106
+ self.x, self.y, self.border, self.title_height, self.width, self.height = x, y, border, title_height, width, height
107
+ changed = True
108
+ if changed:
109
+ logger.debug(
110
+ f"do_update_window_size changed: {self.visible} {self.x} {self.y} {self.border} {self.width} {self.height} {self.scaling}")
111
+ communicate.window.emit(self.visible, self.x, self.y, self.border, self.title_height, self.width,
112
+ self.height, self.scaling)
113
+
114
+ def frame_ratio(self, size):
115
+ if self.frame_width > 0 and self.width > 0:
116
+ return int(size / self.frame_width * self.width)
117
+ else:
118
+ return size
119
+
120
+ def title_text(self):
121
+ if self.hwnd:
122
+ return win32gui.GetWindowText(self.hwnd)
123
+ return ""
124
+
125
+
126
+ def find_hwnds_by_title(title):
127
+ hwnds = []
128
+
129
+ def enum_windows_proc(hwnd, lParam):
130
+ if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd):
131
+ if re.search(title, win32gui.GetWindowText(hwnd)):
132
+ hwnds.append(hwnd)
133
+
134
+ win32gui.EnumWindows(enum_windows_proc, None)
135
+ if len(hwnds) > 0:
136
+ if len(hwnds) > 1:
137
+ logger.warning(f"Found multiple hwnds {len(hwnds)}")
138
+ return hwnds[0]
File without changes
@@ -0,0 +1,29 @@
1
+ # original https://github.com/Toufool/AutoSplit/blob/master/src/capture_method/WindowsGraphicsCaptureMethod.py
2
+ import numpy as np
3
+ from typing_extensions import override
4
+
5
+ from autohelper.capture.BaseCaptureMethod import BaseCaptureMethod
6
+ from autohelper.logging.Logger import get_logger
7
+
8
+ logger = get_logger(__name__)
9
+
10
+
11
+ class ADBCaptureMethod(BaseCaptureMethod):
12
+ name = "ADB command line Capture"
13
+ description = "use the adb screencap command, slow but works when in background/minimized, takes 300ms per frame"
14
+
15
+ def __init__(self, device_manager):
16
+ super().__init__()
17
+ self.device_manager = device_manager
18
+
19
+ @property
20
+ def width(self):
21
+ return self.device_manager.width
22
+
23
+ @property
24
+ def height(self):
25
+ return self.device_manager.height
26
+
27
+ @override
28
+ def get_frame(self) -> np.ndarray | None:
29
+ return self.device_manager.screencap()
@@ -0,0 +1,209 @@
1
+ import threading
2
+
3
+ import adbutils
4
+ import cv2
5
+ import numpy as np
6
+
7
+ from autohelper.capture.HwndWindow import HwndWindow
8
+ from autohelper.capture.adb.ADBCaptureMethod import ADBCaptureMethod
9
+ from autohelper.capture.adb.vbox import installed_emulator
10
+ from autohelper.capture.windows.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod
11
+ from autohelper.config.Config import Config
12
+ from autohelper.gui.Communicate import communicate
13
+ from autohelper.interaction.ADBInteraction import ADBBaseInteraction
14
+ from autohelper.interaction.Win32Interaction import Win32Interaction
15
+ from autohelper.logging.Logger import get_logger
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class DeviceManager:
21
+
22
+ def __init__(self, config_folder, hwnd_title=None, debug=False, exit_event=None):
23
+ self._device = None
24
+ self.adb = None
25
+ self.hwnd_title = hwnd_title
26
+ self.debug = debug
27
+ self.interaction = None
28
+ self.exit_event = exit_event
29
+ if hwnd_title is not None:
30
+ self.hwnd = HwndWindow(hwnd_title, exit_event)
31
+ self.config = Config({"devices": {}}, config_folder, "DeviceManager")
32
+ self._size = (0, 0)
33
+ self.thread = None
34
+ self.capture_method = None
35
+ self.refresh()
36
+
37
+ def refresh(self):
38
+ if self.thread is None or not self.thread.is_alive():
39
+ self.thread = threading.Thread(target=self.do_refresh, name="refresh adb")
40
+ self.thread.start()
41
+
42
+ @property
43
+ def device_dict(self):
44
+ return self.config.get("devices")
45
+
46
+ def connect_all(self):
47
+ for device in self.device_dict.values():
48
+ self.adb.connect(device['address'])
49
+ logger.debug(f'connect_all {self.adb.device_list()}')
50
+
51
+ def get_devices(self):
52
+ return self.device_dict.values()
53
+
54
+ def do_refresh(self):
55
+ if self.adb is None:
56
+ self.adb = adbutils.AdbClient(host="127.0.0.1")
57
+ logger.debug(f'connect adb')
58
+ self.connect_all()
59
+ installed_emulators = installed_emulator()
60
+ for device in self.adb.list():
61
+ logger.debug(f'adb.list() {device}')
62
+
63
+ for emulator in installed_emulators:
64
+ logger.debug(f"installed_emulator: {emulator}")
65
+ self.adb.connect(emulator.adb_address)
66
+
67
+ device_list = self.adb.device_list()
68
+
69
+ device_list = sorted(device_list, key=lambda x: x.serial)
70
+
71
+ for device in self.device_dict.values():
72
+ device['connected'] = False
73
+
74
+ for device in device_list:
75
+ imei = device.shell("settings get secure android_id") or device.shell(
76
+ "service call iphonesubinfo 4") or device.prop.model
77
+ frame = do_screencap(device)
78
+ width, height = 0, 0
79
+ if frame is not None:
80
+ height, width, _ = frame.shape
81
+ adb_device = {"address": device.serial, "imei": imei, "method": "adb",
82
+ "model": device.prop.model, "nick": device.prop.model, "connected": True, "preferred": False,
83
+ "resolution": f"{width}x{height}"}
84
+ found = False
85
+ for emulator in installed_emulators:
86
+ if emulator.adb_address == adb_device['address']:
87
+ adb_device['nick'] = emulator.description
88
+ found = True
89
+ if self.device_dict.get(imei):
90
+ if found or not self.device_dict[imei]['connected']:
91
+ self.device_dict[imei] = adb_device
92
+ else:
93
+ self.device_dict[imei] = adb_device
94
+ if self.hwnd_title:
95
+ nick = self.hwnd_title
96
+ width = 0
97
+ height = 0
98
+ if self.hwnd:
99
+ nick = self.hwnd.title_text()
100
+ width = self.hwnd.width
101
+ height = self.hwnd.height
102
+ self.device_dict[self.hwnd_title] = {"address": "", "imei": self.hwnd_title, "method": "windows",
103
+ "model": "", "nick": nick or self.hwnd_title,
104
+ "connected": self.hwnd is not None and self.hwnd.exists,
105
+ "preferred": False,
106
+ "resolution": f"{width}x{height}"}
107
+ communicate.adb_devices.emit()
108
+ self.config.save_file()
109
+ logger.debug(f'refresh {self.device_dict}')
110
+
111
+ def set_preferred_device(self, imei=None):
112
+ logger.debug(f"set_preferred_device {imei}")
113
+ preferred = None
114
+ if imei is None:
115
+ for device in self.device_dict.values():
116
+ if device.get('preferred'):
117
+ preferred = device
118
+ imei = preferred['imei']
119
+ else:
120
+ preferred = self.device_dict.get(imei)
121
+ for key in self.device_dict.keys():
122
+ if imei == key:
123
+ self.device_dict[imei]["preferred"] = False
124
+ if preferred is None and len(self.device_dict) > 0:
125
+ preferred = next(iter(self.device_dict.values()))
126
+ if preferred is not None:
127
+ preferred['preferred'] = True
128
+ if preferred['method'] == 'windows':
129
+ if not isinstance(self.capture_method, WindowsGraphicsCaptureMethod):
130
+ if self.capture_method is not None:
131
+ self.capture_method.close()
132
+ self.capture_method = WindowsGraphicsCaptureMethod(self.hwnd)
133
+ self.interaction = Win32Interaction(self.capture_method)
134
+ self.capture_method.hwnd_window = self.hwnd
135
+ else:
136
+ for adb_device in self.adb.device_list():
137
+ if adb_device.serial == preferred.get('address'):
138
+ self._device = adb_device
139
+ if not isinstance(self.capture_method, ADBCaptureMethod):
140
+ if self.capture_method is not None:
141
+ self.capture_method.close()
142
+ self.capture_method = ADBCaptureMethod(self)
143
+ self.interaction = ADBBaseInteraction(self, self.capture_method)
144
+ self.config.save_file()
145
+ logger.debug(f'preferred device: {preferred}')
146
+
147
+ def start(self):
148
+ if isinstance(self.capture_method, ADBCaptureMethod):
149
+ if self.debug:
150
+ if self.hwnd is not None:
151
+ self.hwnd.update_title_re("autohelper_debug")
152
+ self.hwnd.update_frame_size(self.capture_method.width, self.capture_method.height)
153
+ else:
154
+ self.hwnd = HwndWindow("autohelper_debug", self.exit_event, self.capture_method.width,
155
+ self.capture_method.height)
156
+ else:
157
+ self.hwnd.stop()
158
+ self.hwnd = None
159
+
160
+ @property
161
+ def device(self):
162
+ if self._device is None:
163
+ self.set_preferred_device()
164
+ return self._device
165
+
166
+ @property
167
+ def width(self):
168
+ if self._size[0] == 0:
169
+ self.screencap()
170
+ return self._size[0]
171
+
172
+ @property
173
+ def height(self):
174
+ if self._size[1] == 0:
175
+ self.screencap()
176
+ return self._size[1]
177
+
178
+ def update_device_list(self):
179
+ pass
180
+
181
+ def shell(self, *args, **kwargs):
182
+ device = self.device
183
+ if device is not None:
184
+ try:
185
+ return device.shell(*args, **kwargs)
186
+ except Exception as e:
187
+ logger.error(f"shell_wrapper error occurred: {e}")
188
+
189
+ def screencap(self):
190
+ if self.exit_event.is_set():
191
+ return None
192
+ self._size = (0, 0)
193
+ frame = do_screencap(self.device)
194
+ if frame is not None:
195
+ height, width, _ = frame.shape
196
+ self._size = (width, height)
197
+ return frame
198
+
199
+
200
+ def do_screencap(device) -> np.ndarray | None:
201
+ if device is not None:
202
+ png_bytes = device.shell("screencap -p", encoding=None)
203
+ if png_bytes is not None and len(png_bytes) > 0:
204
+ image_data = np.frombuffer(png_bytes, dtype=np.uint8)
205
+ image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
206
+ if image is not None:
207
+ return image
208
+ else:
209
+ logger.error(f"Screencap image decode error, probably disconnected")
File without changes
File without changes