ok-script 0.0.7__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 (122) hide show
  1. autohelper/AutoHelper.py +116 -0
  2. autohelper/__init__.py +0 -0
  3. autohelper/capture/BaseCaptureMethod.py +30 -0
  4. autohelper/capture/HwndWindow.py +138 -0
  5. autohelper/capture/__init__.py +0 -0
  6. autohelper/capture/adb/ADBCaptureMethod.py +29 -0
  7. autohelper/capture/adb/DeviceManager.py +209 -0
  8. autohelper/capture/adb/__init__.py +0 -0
  9. autohelper/capture/adb/bin/__init__.py +0 -0
  10. autohelper/capture/adb/targets.py +61 -0
  11. autohelper/capture/adb/vbox.py +140 -0
  12. autohelper/capture/windows/WindowsGraphicsCaptureMethod.py +228 -0
  13. autohelper/capture/windows/__init__.py +0 -0
  14. autohelper/capture/windows/d3d11.py +74 -0
  15. autohelper/capture/windows/utils.py +149 -0
  16. autohelper/capture/windows/window.py +36 -0
  17. autohelper/color/Color.py +41 -0
  18. autohelper/color/__init__.py +0 -0
  19. autohelper/config/Config.py +79 -0
  20. autohelper/config/InfoDict.py +16 -0
  21. autohelper/config/__init__.py +0 -0
  22. autohelper/feature/Box.py +199 -0
  23. autohelper/feature/Feature.py +20 -0
  24. autohelper/feature/FeatureSet.py +207 -0
  25. autohelper/feature/FindFeature.py +52 -0
  26. autohelper/feature/__init__.py +0 -0
  27. autohelper/gui/App.py +120 -0
  28. autohelper/gui/Communicate.py +22 -0
  29. autohelper/gui/MainWindow.py +69 -0
  30. autohelper/gui/TabTitles.py +56 -0
  31. autohelper/gui/__init__.py +5 -0
  32. autohelper/gui/debug/AspectRatioWidget.py +31 -0
  33. autohelper/gui/debug/DebugTab.py +37 -0
  34. autohelper/gui/debug/FrameWidget.py +125 -0
  35. autohelper/gui/debug/InfoWidget.py +40 -0
  36. autohelper/gui/debug/LoggerWidget.py +74 -0
  37. autohelper/gui/debug/__init__.py +0 -0
  38. autohelper/gui/i18n/__init__.py +0 -0
  39. autohelper/gui/i18n/path.py +3 -0
  40. autohelper/gui/icon/__init__.py +0 -0
  41. autohelper/gui/icon/icon.py +6 -0
  42. autohelper/gui/loading/LoadingWindow.py +142 -0
  43. autohelper/gui/loading/__init__.py +0 -0
  44. autohelper/gui/overlay/OverlayWindow.py +34 -0
  45. autohelper/gui/overlay/__init__.py +0 -0
  46. autohelper/gui/resources.py +485 -0
  47. autohelper/gui/tasks/ConfigItemFactory.py +14 -0
  48. autohelper/gui/tasks/StartButton.py +47 -0
  49. autohelper/gui/tasks/TaskOpButton.py +33 -0
  50. autohelper/gui/tasks/TaskTab.py +121 -0
  51. autohelper/gui/tasks/TooltipTableWidget.py +39 -0
  52. autohelper/gui/tasks/__init__.py +0 -0
  53. autohelper/gui/util/Alert.py +20 -0
  54. autohelper/gui/util/InitWorker.py +17 -0
  55. autohelper/gui/util/__init__.py +0 -0
  56. autohelper/gui/widget/ListTableWidgetItem.py +27 -0
  57. autohelper/gui/widget/NumericTableWidgetItem.py +33 -0
  58. autohelper/gui/widget/RoundCornerContainer.py +59 -0
  59. autohelper/gui/widget/TabWidget.py +30 -0
  60. autohelper/gui/widget/UpdateConfigWidgetItem.py +16 -0
  61. autohelper/gui/widget/YesNonWidgetItem.py +23 -0
  62. autohelper/gui/widget/__init__.py +0 -0
  63. autohelper/interaction/ADBInteraction.py +33 -0
  64. autohelper/interaction/BaseInteraction.py +36 -0
  65. autohelper/interaction/Win32Interaction.py +56 -0
  66. autohelper/interaction/__init__.py +0 -0
  67. autohelper/logging/Logger.py +103 -0
  68. autohelper/logging/__init__.py +0 -0
  69. autohelper/ocr/OCR.py +50 -0
  70. autohelper/ocr/__init__.py +0 -0
  71. autohelper/predict/__init__.py +0 -0
  72. autohelper/predict/predict.py +45 -0
  73. autohelper/rotypes/Windows/Foundation/Collections.py +60 -0
  74. autohelper/rotypes/Windows/Foundation/__init__.py +153 -0
  75. autohelper/rotypes/Windows/Globalization/__init__.py +25 -0
  76. autohelper/rotypes/Windows/Graphics/Capture/__init__.py +110 -0
  77. autohelper/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +41 -0
  78. autohelper/rotypes/Windows/Graphics/DirectX/__init__.py +167 -0
  79. autohelper/rotypes/Windows/Graphics/Imaging/__init__.py +34 -0
  80. autohelper/rotypes/Windows/Graphics/__init__.py +5 -0
  81. autohelper/rotypes/Windows/Media/Ocr/__init__.py +76 -0
  82. autohelper/rotypes/Windows/Media/__init__.py +0 -0
  83. autohelper/rotypes/Windows/Security/Cryptography/__init__.py +18 -0
  84. autohelper/rotypes/Windows/Security/__init__.py +0 -0
  85. autohelper/rotypes/Windows/Storage/Streams/__init__.py +27 -0
  86. autohelper/rotypes/Windows/Storage/__init__.py +0 -0
  87. autohelper/rotypes/Windows/__init__.py +0 -0
  88. autohelper/rotypes/__init__.py +4 -0
  89. autohelper/rotypes/delegate.py +105 -0
  90. autohelper/rotypes/export.py +21 -0
  91. autohelper/rotypes/idldsl.py +272 -0
  92. autohelper/rotypes/inspectable.py +79 -0
  93. autohelper/rotypes/roapi.py +30 -0
  94. autohelper/rotypes/types.py +77 -0
  95. autohelper/rotypes/winstring.py +44 -0
  96. autohelper/save/BlackBarProcessor.py +25 -0
  97. autohelper/save/PostProcessor.py +6 -0
  98. autohelper/save/SaveByInterval.py +30 -0
  99. autohelper/save/SaveByKeyPress.py +27 -0
  100. autohelper/save/SaveMethodBase.py +46 -0
  101. autohelper/save/__init__.py +0 -0
  102. autohelper/scene/FeatureScene.py +14 -0
  103. autohelper/scene/Scene.py +21 -0
  104. autohelper/scene/__init__.py +0 -0
  105. autohelper/stats/StreamStats.py +34 -0
  106. autohelper/stats/__init__.py +0 -0
  107. autohelper/task/BaseTask.py +109 -0
  108. autohelper/task/ExecutorOperation.py +119 -0
  109. autohelper/task/FindFeatureTask.py +6 -0
  110. autohelper/task/TaskExecutor.py +249 -0
  111. autohelper/task/__init__.py +0 -0
  112. autohelper/util/__init__.py +0 -0
  113. autohelper/util/json.py +27 -0
  114. autohelper/util/list.py +2 -0
  115. autohelper/util/path.py +37 -0
  116. autohelper/util/thread.py +12 -0
  117. autohelper/util/win32_process.py +86 -0
  118. autohelper/util/yaml.py +28 -0
  119. ok_script-0.0.7.dist-info/METADATA +64 -0
  120. ok_script-0.0.7.dist-info/RECORD +122 -0
  121. ok_script-0.0.7.dist-info/WHEEL +5 -0
  122. ok_script-0.0.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,34 @@
1
+ import time
2
+
3
+ import numpy as np
4
+
5
+
6
+ class StreamStats:
7
+ last_frame_time = 0
8
+ sleep_padding = 0
9
+
10
+ def __init__(self, max_size=100):
11
+ self.max_size = max_size
12
+ self.data = []
13
+
14
+ def add_frame(self):
15
+ now = time.time()
16
+ if self.last_frame_time != 0:
17
+ if len(self.data) >= self.max_size:
18
+ self.data.pop(0)
19
+ self.data.append(int((now - self.last_frame_time - self.sleep_padding) * 1000))
20
+ self.last_frame_time = now
21
+ self.sleep_padding = 0
22
+
23
+ def add_sleep(self, seconds):
24
+ self.sleep_padding += seconds
25
+
26
+ def mean(self):
27
+ """Calculate and return the mean of the numbers in the stream."""
28
+ return round(np.mean(self.data)) if self.data else 0
29
+
30
+ def percentile(self, percentile):
31
+ """Calculate and return the specified percentile of the numbers in the stream."""
32
+ if not self.data:
33
+ return 0
34
+ return np.percentile(self.data, percentile)
File without changes
@@ -0,0 +1,109 @@
1
+ import time
2
+
3
+ from autohelper.config.Config import Config
4
+ from autohelper.config.InfoDict import InfoDict
5
+ from autohelper.gui.Communicate import communicate
6
+ from autohelper.logging.Logger import get_logger
7
+ from autohelper.task.ExecutorOperation import ExecutorOperation
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ class BaseTask(ExecutorOperation):
13
+ _done = False
14
+
15
+ def __init__(self):
16
+ self.logger = get_logger(self.__class__.__name__)
17
+ self.name = self.__class__.__name__
18
+ self.success_count = 0
19
+ self.error_count = 0
20
+ self.enabled = True
21
+ self.running = False
22
+ self.config = None
23
+ self.info = InfoDict()
24
+ self.default_config = {}
25
+ self.last_execute_time = 0
26
+
27
+ def log_info(self, message, notify=False):
28
+ self.logger.info(message)
29
+ self.info_set("Log", message)
30
+ if notify:
31
+ self.notification(message)
32
+ communicate.task_info.emit()
33
+
34
+ def log_error(self, message, exception=None, notify=False):
35
+ self.logger.error(message, exception)
36
+ if exception is not None:
37
+ message += exception.args[0]
38
+ self.info_set("Error", message)
39
+ if notify:
40
+ self.notification(message)
41
+ communicate.task_info.emit()
42
+
43
+ @staticmethod
44
+ def notification(message, title=None):
45
+ communicate.notification.emit(title, message)
46
+
47
+ def info_clear(self):
48
+ self.info.clear()
49
+
50
+ def info_incr(self, key, by=1):
51
+ # If the key is in the dictionary, get its value. If not, return 0.
52
+ value = self.info.get(key, 0)
53
+ # Increment the value
54
+ value += by
55
+ # Store the incremented value back in the dictionary
56
+ self.info[key] = value
57
+
58
+ def info_add_to_list(self, key, item):
59
+ value = self.info.get(key, [])
60
+ if isinstance(item, list):
61
+ value += item
62
+ else:
63
+ value.append(item)
64
+ self.info[key] = value
65
+
66
+ def info_set(self, key, value):
67
+ self.info[key] = value
68
+
69
+ def can_run(self):
70
+ return self.enabled and not self._done
71
+
72
+ def load_config(self, folder):
73
+ self.config = Config(self.default_config, folder, self.__class__.__name__)
74
+
75
+ def enable(self):
76
+ self.enabled = True
77
+ self.info_clear()
78
+ self.set_done(False)
79
+ communicate.tasks.emit()
80
+
81
+ def disable(self):
82
+ self.enabled = False
83
+ communicate.tasks.emit()
84
+
85
+ def get_status(self):
86
+ if not self.enabled:
87
+ return "Disabled"
88
+ elif self.done:
89
+ return "Done"
90
+ elif self.enabled and self.executor.paused:
91
+ return "Paused"
92
+ elif self.running or self.enabled and time.time() - self.last_execute_time < 3:
93
+ return "Running"
94
+ else:
95
+ return "In Queue"
96
+
97
+ def run_frame(self):
98
+ pass
99
+
100
+ def reset(self):
101
+ self._done = False
102
+ pass
103
+
104
+ @property
105
+ def done(self):
106
+ return self._done
107
+
108
+ def set_done(self, done=True):
109
+ self._done = done
@@ -0,0 +1,119 @@
1
+ from autohelper.feature.Box import Box, find_box_by_name
2
+ from autohelper.gui.Communicate import communicate
3
+ from autohelper.logging.Logger import get_logger
4
+
5
+ logger = get_logger(__name__)
6
+
7
+
8
+ class ExecutorOperation:
9
+ executor = None
10
+
11
+ def box_in_horizontal_center(self, box, off_percent=0.02):
12
+ center = self.executor.method.width / 2
13
+ left = center - box.x
14
+ right = box.x + box.width - center
15
+ if left > 0 and right > 0 and abs(left - right) / box.width < off_percent:
16
+ return True
17
+ else:
18
+ return False
19
+
20
+ def is_scene(self, the_scene):
21
+ return isinstance(self.executor.current_scene, the_scene)
22
+
23
+ def click(self, x, y):
24
+ self.executor.reset_scene()
25
+ self.executor.interaction.click(x, y)
26
+
27
+ def click_box_if_name_match(self, boxes, names, relative_x=0.5, relative_y=0.5):
28
+ """
29
+ Clicks on a box from a list of boxes if the box's name matches one of the specified names.
30
+ The box to click is selected based on the order of names provided, with priority given
31
+ to the earliest match in the names list.
32
+
33
+ Parameters:
34
+ - boxes (list): A list of box objects. Each box object must have a 'name' attribute.
35
+ - names (str or list): A string or a list of strings representing the name(s) to match against the boxes' names.
36
+ - relative_x (float, optional): The relative X coordinate within the box to click,
37
+ as a fraction of the box's width. Defaults to 0.5 (center).
38
+ - relative_y (float, optional): The relative Y coordinate within the box to click,
39
+ as a fraction of the box's height. Defaults to 0.5 (center).
40
+
41
+ Returns:
42
+ - box: the matched box
43
+
44
+ The method attempts to find and click on the highest-priority matching box. If no matches are found,
45
+ or if there are no boxes, the method returns False. This operation is case-sensitive.
46
+ """
47
+ to_click = find_box_by_name(boxes, names)
48
+ if to_click is not None:
49
+ logger.info(f"click_box_if_name_match found {to_click}")
50
+ self.click_box(to_click, relative_x, relative_y)
51
+ return to_click
52
+
53
+ def box_of_screen(self, x, y, width, height, name=None):
54
+ if name is None:
55
+ name = f"{x} {y} {width} {height}"
56
+ return Box(int(x * self.executor.method.width), int(y * self.executor.method.height),
57
+ int(width * self.executor.method.width), int(height * self.executor.method.height),
58
+ name=name)
59
+
60
+ def click_relative(self, x, y):
61
+ self.executor.reset_scene()
62
+ self.executor.interaction.click_relative(x, y)
63
+
64
+ @property
65
+ def height(self):
66
+ return self.executor.method.height
67
+
68
+ @property
69
+ def width(self):
70
+ return self.executor.method.width
71
+
72
+ def move_relative(self, x, y):
73
+ self.executor.reset_scene()
74
+ self.executor.interaction.move_relative(x, y)
75
+
76
+ def click_box(self, box, relative_x=0.5, relative_y=0.5, raise_if_not_found=True):
77
+ self.executor.reset_scene()
78
+ if isinstance(box, list):
79
+ if len(box) > 0:
80
+ box = box[0]
81
+ else:
82
+ logger.error(f"No box")
83
+ if box is None:
84
+ logger.error(f"click_box box is None")
85
+ if raise_if_not_found:
86
+ raise Exception(f"click_box box is None")
87
+ return
88
+ self.executor.interaction.click_box(box, relative_x, relative_y)
89
+
90
+ def wait_scene(self, scene_type=None, time_out=0, pre_action=None, post_action=None):
91
+ return self.executor.wait_scene(scene_type, time_out, pre_action, post_action)
92
+
93
+ def sleep(self, timeout):
94
+ self.executor.sleep(timeout)
95
+
96
+ def send_key(self, key, down_time=0.02):
97
+ self.executor.interaction.send_key(key, down_time)
98
+
99
+ def wait_until(self, condition, time_out=0, pre_action=None, post_action=None):
100
+ return self.executor.wait_condition(condition, time_out, pre_action, post_action)
101
+
102
+ def wait_click_box(self, condition, time_out=0, pre_action=None, post_action=None, raise_if_not_found=True):
103
+ target = self.wait_until(condition, time_out, pre_action, post_action)
104
+ self.click_box(target, raise_if_not_found=raise_if_not_found)
105
+
106
+ def next_frame(self):
107
+ return self.executor.next_frame()
108
+
109
+ @property
110
+ def scene(self):
111
+ return self.executor.current_scene
112
+
113
+ @property
114
+ def frame(self):
115
+ return self.executor.frame
116
+
117
+ @staticmethod
118
+ def draw_boxes(feature_name, boxes, color="red"):
119
+ communicate.draw_box.emit(feature_name, boxes, color)
@@ -0,0 +1,6 @@
1
+ from autohelper.feature.FindFeature import FindFeature
2
+ from autohelper.task.BaseTask import BaseTask
3
+
4
+
5
+ class FindFeatureTask(BaseTask, FindFeature):
6
+ pass
@@ -0,0 +1,249 @@
1
+ import threading
2
+ import time
3
+ import traceback
4
+
5
+ from autohelper.capture.adb.DeviceManager import DeviceManager
6
+ from autohelper.gui.Communicate import communicate
7
+ from autohelper.logging.Logger import get_logger
8
+ from autohelper.scene.Scene import Scene
9
+ from autohelper.stats.StreamStats import StreamStats
10
+
11
+ logger = get_logger(__name__)
12
+
13
+
14
+ class TaskDisabledException(Exception):
15
+ pass
16
+
17
+
18
+ class TaskExecutor:
19
+ current_scene: Scene | None = None
20
+ last_scene: Scene | None = None
21
+ frame_stats = StreamStats()
22
+ _frame = None
23
+ paused = True
24
+ ocr = None
25
+ pause_start = time.time()
26
+ pause_end_time = time.time()
27
+
28
+ def __init__(self, device_manager: DeviceManager,
29
+ wait_until_timeout=10, wait_until_before_delay=1, wait_until_check_delay=1,
30
+ exit_event=threading.Event(), tasks=[], scenes=[], feature_set=None, ocr=None, config_folder=None):
31
+ self.device_manager = device_manager
32
+ self.feature_set = feature_set
33
+ self.wait_until_check_delay = wait_until_check_delay
34
+ self.wait_until_before_delay = wait_until_before_delay
35
+ self.wait_scene_timeout = wait_until_timeout
36
+ self.exit_event = exit_event
37
+ self.ocr = ocr
38
+ self.current_task = None
39
+ self.tasks = tasks
40
+ self.scenes = scenes
41
+ self.config_folder = config_folder or "config"
42
+ for scene in self.scenes:
43
+ scene.executor = self
44
+ scene.feature_set = self.feature_set
45
+ for task in self.tasks:
46
+ task.executor = self
47
+ task.feature_set = self.feature_set
48
+ task.load_config(self.config_folder)
49
+ self.thread = threading.Thread(target=self.execute, name="TaskExecutor")
50
+ self.thread.start()
51
+
52
+ @property
53
+ def interaction(self):
54
+ return self.device_manager.interaction
55
+
56
+ @property
57
+ def method(self):
58
+ return self.device_manager.capture_method
59
+
60
+ def next_frame(self):
61
+ self.reset_scene()
62
+ start = time.time()
63
+ while not self.exit_event.is_set():
64
+ if self.method and self.interaction.should_capture():
65
+ self._frame = self.method.get_frame()
66
+ if self._frame is not None:
67
+ height, width = self._frame.shape[:2]
68
+ if height <= 0 or width <= 0:
69
+ logger.warning(f"captured wrong size frame: {width}x{height}")
70
+ self._frame = None
71
+ communicate.frame.emit(self._frame)
72
+ return self._frame
73
+ self.sleep(0.001)
74
+ if time.time() - start > self.wait_scene_timeout:
75
+ return None
76
+
77
+ @property
78
+ def frame(self):
79
+ if self._frame is None:
80
+ return self.next_frame()
81
+ else:
82
+ return self._frame
83
+
84
+ def sleep(self, timeout):
85
+ """
86
+ Sleeps for the specified timeout, checking for an exit event every 100ms, with adjustments to prevent oversleeping.
87
+
88
+ :param timeout: The total time to sleep in seconds.
89
+ """
90
+ self.frame_stats.add_sleep(timeout)
91
+ self.pause_end_time = time.time() + timeout
92
+ while True:
93
+ if self.exit_event.is_set():
94
+ logger.info("Exit event set. Exiting early.")
95
+ return
96
+ if self.current_task and not self.current_task.enabled:
97
+ raise TaskDisabledException()
98
+ if not (self.paused or not self.interaction.should_capture()):
99
+ to_sleep = self.pause_end_time - time.time()
100
+ if to_sleep <= 0:
101
+ return
102
+ time.sleep(0.001)
103
+
104
+ def pause(self):
105
+ self.paused = True
106
+ self.pause_start = time.time()
107
+ communicate.executor_paused.emit(self.paused)
108
+ communicate.tasks.emit()
109
+
110
+ def start(self):
111
+ can_run = False
112
+ for task in self.tasks:
113
+ if task.can_run():
114
+ can_run = True
115
+ break
116
+ if not can_run:
117
+ return False
118
+ self.pause_end_time += self.pause_start - time.time()
119
+ self.paused = False
120
+ communicate.executor_paused.emit(self.paused)
121
+ communicate.tasks.emit()
122
+ return True
123
+
124
+ def wait_scene(self, scene_type, time_out, pre_action, post_action):
125
+ return self.wait_condition(lambda: self.detect_scene(scene_type), time_out, pre_action, post_action)
126
+
127
+ def wait_condition(self, condition, time_out, pre_action, post_action):
128
+ self.sleep(self.wait_until_before_delay)
129
+ start = time.time()
130
+ if time_out == 0:
131
+ time_out = self.wait_scene_timeout
132
+ while not self.exit_event.is_set():
133
+ self.reset_scene()
134
+ if pre_action is not None:
135
+ pre_action()
136
+ self._frame = self.next_frame()
137
+ if self._frame is not None:
138
+ result = condition()
139
+ self.add_frame_stats()
140
+ result_str = list_or_obj_to_str(result)
141
+ if result:
142
+ logger.debug(f"found result {result_str}")
143
+ self.sleep(self.wait_until_check_delay)
144
+ return result
145
+ if post_action is not None:
146
+ post_action()
147
+ if time.time() - start > time_out:
148
+ logger.info(f"wait_until timeout {condition} {time_out} seconds")
149
+ break
150
+ self.sleep(0.01)
151
+ return None
152
+
153
+ def reset_scene(self):
154
+ self._frame = None
155
+ self.last_scene = self.current_scene
156
+ self.current_scene = None
157
+
158
+ def execute(self):
159
+ logger.info(f"start execute")
160
+ while not self.exit_event.is_set():
161
+ self._frame = self.next_frame()
162
+ start = time.time()
163
+ if self._frame is not None:
164
+ self.detect_scene()
165
+ task_executed = 0
166
+ for index, task in enumerate(self.tasks):
167
+ if task.done or not task.enabled:
168
+ continue
169
+ self.current_task = task
170
+ task.running = True
171
+ task.last_execute_time = start
172
+ try:
173
+ result = task.run_frame()
174
+ if result is not None:
175
+ if result:
176
+ task.success_count += 1
177
+ else:
178
+ task.error_count += 1
179
+ except TaskDisabledException:
180
+ logger.info(f"{task.name} is disabled, breaking")
181
+ except Exception as e:
182
+ traceback.print_exc()
183
+ stack_trace_str = traceback.format_exc()
184
+ logger.error(f"{task.name} exception: {e}, traceback: {stack_trace_str}")
185
+ task.error_count += 1
186
+ self.current_task = None
187
+ task.running = False
188
+ processing_time = time.time() - start
189
+ task_executed += 1
190
+ if processing_time > 0.5:
191
+ logger.debug(
192
+ f"{task.__class__.__name__} taking too long get new frame {processing_time} {task_executed} {len(self.tasks)}")
193
+ self.next_frame()
194
+ start = time.time()
195
+
196
+ # if task_executed == 0:
197
+ # self.pause()
198
+ self.add_frame_stats()
199
+ self.sleep(0.001)
200
+
201
+ def add_frame_stats(self):
202
+ self.frame_stats.add_frame()
203
+ mean = self.frame_stats.mean()
204
+ if mean > 0:
205
+ communicate.frame_time.emit(mean)
206
+ communicate.fps.emit(round(1000 / mean))
207
+ scene = "None"
208
+ if self.current_scene is not None:
209
+ scene = self.current_scene.name or self.current_scene.__class__.__name__
210
+ communicate.scene.emit(scene)
211
+
212
+ def latest_scene(self):
213
+ return self.current_scene or self.last_scene
214
+
215
+ def detect_scene(self, scene_type=None):
216
+ latest_scene = self.latest_scene()
217
+ if latest_scene is not None:
218
+ # detect the last scene optimistically
219
+ if latest_scene.detect(self._frame):
220
+ self.current_scene = latest_scene
221
+ return latest_scene
222
+ for scene in self.scenes:
223
+ if scene_type is not None and not isinstance(scene, scene_type):
224
+ continue
225
+ if scene != latest_scene:
226
+ if scene.detect(self._frame):
227
+ self.current_scene = scene
228
+ logger.debug(f"TaskExecutor: scene changed {scene.__class__.__name__}")
229
+ return scene
230
+ if self.current_scene is not None:
231
+ logger.debug(f"TaskExecutor: scene changed to None")
232
+ self.current_scene = None
233
+ return self.current_scene
234
+
235
+ def stop(self):
236
+ self.exit_event.set()
237
+
238
+ def wait_until_done(self):
239
+ self.thread.join()
240
+
241
+
242
+ def list_or_obj_to_str(val):
243
+ if val is not None:
244
+ if isinstance(val, list):
245
+ return ', '.join(str(obj) for obj in val)
246
+ else:
247
+ return str(val)
248
+ else:
249
+ return None
File without changes
File without changes
@@ -0,0 +1,27 @@
1
+ import json
2
+ import os
3
+
4
+ from autohelper.util.path import ensure_dir_for_file
5
+
6
+
7
+ def read_json_file(file_path) -> dict | None:
8
+ if not os.path.exists(file_path):
9
+ return None
10
+
11
+ try:
12
+ with open(file_path, 'r', encoding='utf-8') as f:
13
+ data = json.load(f)
14
+ return data
15
+ except json.JSONDecodeError:
16
+ return None
17
+
18
+
19
+ def write_json_file(file_path, data):
20
+ try:
21
+ ensure_dir_for_file(file_path)
22
+ with open(file_path, 'w', encoding='utf-8') as f:
23
+ json.dump(data, f, indent=4, ensure_ascii=False)
24
+ return True
25
+ except Exception as e:
26
+ print(f"Error writing to JSON file: {e}")
27
+ return False
@@ -0,0 +1,2 @@
1
+ def get_first_item(lst, default=None):
2
+ return next(iter(lst), default) if lst is not None else default
@@ -0,0 +1,37 @@
1
+ import os
2
+ import sys
3
+
4
+
5
+ def get_path_relative_to_exe(*files):
6
+ if getattr(sys, 'frozen', False):
7
+ # The application is running as a bundled executable
8
+ application_path = os.path.abspath(sys.executable)
9
+ else:
10
+ # The application is running as a Python script
11
+ application_path = os.path.abspath(sys.argv[0])
12
+ the_dir = os.path.dirname(application_path)
13
+
14
+ # Join the directory with the file paths
15
+ path = os.path.join(the_dir, *files)
16
+
17
+ # Normalize the path
18
+ normalized_path = os.path.normpath(path)
19
+
20
+ return normalized_path
21
+
22
+
23
+ def ensure_dir_for_file(file_path):
24
+ # Extract the directory from the file path
25
+ directory = os.path.dirname(file_path)
26
+
27
+ # Check if the directory exists
28
+ if not os.path.exists(directory):
29
+ # If the directory does not exist, create it (including any intermediate directories)
30
+ os.makedirs(directory)
31
+
32
+
33
+ def get_path_in_package(base, file):
34
+ the_dir = os.path.dirname(os.path.realpath(base))
35
+
36
+ # Get the path of the file relative to the script
37
+ return os.path.join(the_dir, file)
@@ -0,0 +1,12 @@
1
+ import threading
2
+
3
+
4
+ def run_in_new_thread(func):
5
+ # Create a new thread and run the function in it
6
+ thread = threading.Thread(target=func)
7
+
8
+ # Start the new thread
9
+ thread.start()
10
+
11
+ # Return the thread
12
+ return thread
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+
3
+ import ctypes
4
+ import msvcrt
5
+ import os
6
+ from typing import Union
7
+
8
+
9
+ def all_pids() -> list[int]:
10
+ pidbuffer = 512
11
+ bytes_written = ctypes.c_uint32()
12
+ while True:
13
+ pids = (ctypes.c_uint32 * pidbuffer)()
14
+ bufsize = ctypes.sizeof(pids)
15
+ if ctypes.windll.kernel32.K32EnumProcesses(pids, bufsize, ctypes.byref(bytes_written)) == 0:
16
+ return []
17
+ if bytes_written.value < bufsize:
18
+ break
19
+ pidbuffer *= 2
20
+ pidcount = bytes_written.value // 4
21
+ return list(pids[:pidcount])
22
+
23
+
24
+ NtQuerySystemInformation = ctypes.windll.ntdll.NtQuerySystemInformation
25
+ NtQuerySystemInformation.argtypes = (ctypes.c_int32, ctypes.c_void_p, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32))
26
+ SystemProcessIdInformation = 0x58
27
+
28
+
29
+ class UNICODE_STRING(ctypes.Structure):
30
+ _fields_ = [
31
+ ('Length', ctypes.c_ushort),
32
+ ('MaximumLength', ctypes.c_ushort),
33
+ ('Buffer', ctypes.c_wchar_p),
34
+ ]
35
+
36
+ @classmethod
37
+ def create(cls, init_with: Union[str, int]):
38
+ buffer = ctypes.create_unicode_buffer(init_with)
39
+ bufsize = len(buffer)
40
+ if isinstance(init_with, str):
41
+ wchlen = bufsize - 1 # exclude null
42
+ else:
43
+ wchlen = 0
44
+ return cls(wchlen * 2, bufsize * 2, ctypes.cast(buffer, ctypes.c_wchar_p))
45
+
46
+ def __str__(self):
47
+ return ctypes.wstring_at(self.Buffer, self.Length // 2)
48
+
49
+
50
+ class SYSTEM_PROCESS_ID_INFORMATION(ctypes.Structure):
51
+ _fields_ = [
52
+ ('ProcessId', ctypes.c_size_t),
53
+ ('ImageName', UNICODE_STRING),
54
+ ]
55
+
56
+
57
+ def resolve_image_name(pid: int):
58
+ """Resolves the image name of a process, in NT device path (\\Device\\HarddiskVolumeX\\...) format."""
59
+ info = SYSTEM_PROCESS_ID_INFORMATION()
60
+ info.ProcessId = pid
61
+ status = NtQuerySystemInformation(SystemProcessIdInformation, ctypes.byref(info), ctypes.sizeof(info), None)
62
+ if status == -1073741820:
63
+ info.ImageName = UNICODE_STRING.create(info.ImageName.MaximumLength // 2)
64
+ status = NtQuerySystemInformation(SystemProcessIdInformation, ctypes.byref(info), ctypes.sizeof(info), None)
65
+ return str(info.ImageName)
66
+ else:
67
+ return ''
68
+
69
+
70
+ GetFinalPathNameByHandleW = ctypes.windll.kernel32.GetFinalPathNameByHandleW
71
+ GetFinalPathNameByHandleW.argtypes = (ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_uint32, ctypes.c_uint32)
72
+
73
+
74
+ def get_final_path(path: os.PathLike) -> str:
75
+ with open(path, 'rb', buffering=0) as f:
76
+ handle = msvcrt.get_osfhandle(f.fileno())
77
+ buf = ctypes.create_unicode_buffer(32768)
78
+ pathlen = GetFinalPathNameByHandleW(handle, buf, 32768, 2)
79
+ if pathlen == 0:
80
+ raise ctypes.WinError()
81
+ return buf[:pathlen]
82
+
83
+
84
+ def _test():
85
+ for pid in all_pids():
86
+ print(pid, resolve_image_name(pid), sep='\t')