ok-script 0.0.12__tar.gz → 0.0.13__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 (166) hide show
  1. {ok-script-0.0.12 → ok-script-0.0.13}/PKG-INFO +2 -1
  2. {ok-script-0.0.12 → ok-script-0.0.13}/ok/OK.py +8 -7
  3. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/BaseCaptureMethod.py +3 -0
  4. ok-script-0.0.13/ok/capture/HwndWindow.py +198 -0
  5. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/adb/ADBCaptureMethod.py +19 -8
  6. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/adb/DeviceManager.py +67 -46
  7. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/adb/vbox.py +12 -11
  8. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/windows/WindowsGraphicsCaptureMethod.py +6 -1
  9. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/windows/utils.py +0 -19
  10. {ok-script-0.0.12 → ok-script-0.0.13}/ok/config/Config.py +6 -0
  11. {ok-script-0.0.12 → ok-script-0.0.13}/ok/feature/Box.py +42 -5
  12. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/App.py +20 -18
  13. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/Communicate.py +3 -2
  14. ok-script-0.0.13/ok/gui/MainWindow.py +89 -0
  15. ok-script-0.0.13/ok/gui/__init__.py +6 -0
  16. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/about/AboutTab.py +5 -6
  17. ok-script-0.0.13/ok/gui/common/OKIcon.py +13 -0
  18. ok-script-0.0.13/ok/gui/common/config.py +58 -0
  19. ok-script-0.0.13/ok/gui/common/style_sheet.py +23 -0
  20. ok-script-0.0.13/ok/gui/debug/DebugTab.py +17 -0
  21. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/debug/LoggerWidget.py +8 -6
  22. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/debug/Screenshot.py +2 -2
  23. ok-script-0.0.13/ok/gui/i18n/update.py +29 -0
  24. ok-script-0.0.13/ok/gui/resources.py +1057 -0
  25. ok-script-0.0.13/ok/gui/settings/SettingTab.py +117 -0
  26. {ok-script-0.0.12/ok/gui/loading → ok-script-0.0.13/ok/gui/start}/SelectCaptureListView.py +6 -5
  27. {ok-script-0.0.12/ok/gui/loading → ok-script-0.0.13/ok/gui/start}/SelectHwndWindow.py +20 -23
  28. ok-script-0.0.13/ok/gui/start/StartCard.py +93 -0
  29. ok-script-0.0.13/ok/gui/start/StartTab.py +140 -0
  30. ok-script-0.0.13/ok/gui/tasks/ConfigItemFactory.py +20 -0
  31. ok-script-0.0.13/ok/gui/tasks/ConfigLabelAndWidget.py +10 -0
  32. ok-script-0.0.13/ok/gui/tasks/LabelAndDoubleSpinBox.py +21 -0
  33. ok-script-0.0.13/ok/gui/tasks/LabelAndLineEdit.py +21 -0
  34. ok-script-0.0.13/ok/gui/tasks/LabelAndList.py +18 -0
  35. ok-script-0.0.13/ok/gui/tasks/LabelAndSpinBox.py +21 -0
  36. ok-script-0.0.13/ok/gui/tasks/LabelAndSwitchButton.py +21 -0
  37. ok-script-0.0.13/ok/gui/tasks/LabelAndWidget.py +21 -0
  38. ok-script-0.0.13/ok/gui/tasks/ModifyListDialog.py +97 -0
  39. ok-script-0.0.13/ok/gui/tasks/ModifyListItem.py +32 -0
  40. ok-script-0.0.13/ok/gui/tasks/OneTimeTaskTab.py +63 -0
  41. ok-script-0.0.13/ok/gui/tasks/TaskCard.py +97 -0
  42. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/tasks/TooltipTableWidget.py +4 -3
  43. ok-script-0.0.13/ok/gui/tasks/TriggerTaskTab.py +17 -0
  44. ok-script-0.0.13/ok/gui/widget/Card.py +59 -0
  45. ok-script-0.0.13/ok/gui/widget/FramelessDialog.py +13 -0
  46. ok-script-0.0.13/ok/gui/widget/StatusBar.py +131 -0
  47. ok-script-0.0.13/ok/gui/widget/Tab.py +37 -0
  48. {ok-script-0.0.12 → ok-script-0.0.13}/ok/interaction/ADBInteraction.py +7 -10
  49. {ok-script-0.0.12 → ok-script-0.0.13}/ok/interaction/BaseInteraction.py +3 -0
  50. {ok-script-0.0.12 → ok-script-0.0.13}/ok/interaction/Win32Interaction.py +37 -1
  51. {ok-script-0.0.12 → ok-script-0.0.13}/ok/ocr/OCR.py +5 -1
  52. ok-script-0.0.13/ok/stats/__init__.py +0 -0
  53. {ok-script-0.0.12 → ok-script-0.0.13}/ok/task/BaseTask.py +43 -38
  54. {ok-script-0.0.12 → ok-script-0.0.13}/ok/task/ExecutorOperation.py +21 -7
  55. ok-script-0.0.13/ok/task/OneTimeTask.py +35 -0
  56. {ok-script-0.0.12 → ok-script-0.0.13}/ok/task/TaskExecutor.py +86 -56
  57. ok-script-0.0.13/ok/task/TriggerTask.py +29 -0
  58. ok-script-0.0.13/ok/task/__init__.py +0 -0
  59. ok-script-0.0.13/ok/util/__init__.py +0 -0
  60. {ok-script-0.0.12 → ok-script-0.0.13}/ok_script.egg-info/PKG-INFO +2 -1
  61. {ok-script-0.0.12 → ok-script-0.0.13}/ok_script.egg-info/SOURCES.txt +31 -7
  62. {ok-script-0.0.12 → ok-script-0.0.13}/ok_script.egg-info/requires.txt +1 -0
  63. {ok-script-0.0.12 → ok-script-0.0.13}/setup.py +3 -2
  64. ok-script-0.0.12/ok/capture/HwndWindow.py +0 -142
  65. ok-script-0.0.12/ok/gui/MainWindow.py +0 -73
  66. ok-script-0.0.12/ok/gui/__init__.py +0 -7
  67. ok-script-0.0.12/ok/gui/debug/DebugTab.py +0 -31
  68. ok-script-0.0.12/ok/gui/loading/LoadingWindow.py +0 -182
  69. ok-script-0.0.12/ok/gui/resources.py +0 -533
  70. ok-script-0.0.12/ok/gui/tasks/ConfigItemFactory.py +0 -14
  71. ok-script-0.0.12/ok/gui/tasks/TaskTab.py +0 -121
  72. ok-script-0.0.12/ok/gui/widget/RoundCornerContainer.py +0 -60
  73. ok-script-0.0.12/ok/task/FindFeatureTask.py +0 -6
  74. {ok-script-0.0.12 → ok-script-0.0.13}/README.md +0 -0
  75. {ok-script-0.0.12 → ok-script-0.0.13}/ok/__init__.py +0 -0
  76. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/__init__.py +0 -0
  77. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/adb/__init__.py +0 -0
  78. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/adb/bin/__init__.py +0 -0
  79. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/adb/targets.py +0 -0
  80. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/windows/__init__.py +0 -0
  81. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/windows/d3d11.py +0 -0
  82. {ok-script-0.0.12 → ok-script-0.0.13}/ok/capture/windows/window.py +0 -0
  83. {ok-script-0.0.12 → ok-script-0.0.13}/ok/color/Color.py +0 -0
  84. {ok-script-0.0.12 → ok-script-0.0.13}/ok/color/__init__.py +0 -0
  85. {ok-script-0.0.12 → ok-script-0.0.13}/ok/config/InfoDict.py +0 -0
  86. {ok-script-0.0.12 → ok-script-0.0.13}/ok/config/__init__.py +0 -0
  87. {ok-script-0.0.12 → ok-script-0.0.13}/ok/feature/Feature.py +0 -0
  88. {ok-script-0.0.12 → ok-script-0.0.13}/ok/feature/FeatureSet.py +0 -0
  89. {ok-script-0.0.12 → ok-script-0.0.13}/ok/feature/FindFeature.py +0 -0
  90. {ok-script-0.0.12 → ok-script-0.0.13}/ok/feature/__init__.py +0 -0
  91. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/TabTitles.py +0 -0
  92. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/about/__init__.py +0 -0
  93. {ok-script-0.0.12/ok/gui/debug → ok-script-0.0.13/ok/gui/common}/__init__.py +0 -0
  94. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/debug/AspectRatioWidget.py +0 -0
  95. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/debug/FrameWidget.py +0 -0
  96. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/debug/InfoWidget.py +0 -0
  97. {ok-script-0.0.12/ok/gui/i18n → ok-script-0.0.13/ok/gui/debug}/__init__.py +0 -0
  98. {ok-script-0.0.12/ok/gui/icon → ok-script-0.0.13/ok/gui/i18n}/__init__.py +0 -0
  99. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/i18n/path.py +0 -0
  100. {ok-script-0.0.12/ok/gui/loading → ok-script-0.0.13/ok/gui/icon}/__init__.py +0 -0
  101. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/icon/icon.py +0 -0
  102. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/overlay/OverlayWindow.py +0 -0
  103. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/overlay/__init__.py +0 -0
  104. {ok-script-0.0.12/ok/gui/tasks → ok-script-0.0.13/ok/gui/qss}/__init__.py +0 -0
  105. {ok-script-0.0.12/ok/gui/util → ok-script-0.0.13/ok/gui/settings}/__init__.py +0 -0
  106. {ok-script-0.0.12/ok/gui/widget → ok-script-0.0.13/ok/gui/start}/__init__.py +0 -0
  107. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/tasks/StartButton.py +0 -0
  108. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/tasks/TaskOpButton.py +0 -0
  109. {ok-script-0.0.12/ok/interaction → ok-script-0.0.13/ok/gui/tasks}/__init__.py +0 -0
  110. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/util/Alert.py +0 -0
  111. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/util/InitWorker.py +0 -0
  112. {ok-script-0.0.12/ok/logging → ok-script-0.0.13/ok/gui/util}/__init__.py +0 -0
  113. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/widget/ListTableWidgetItem.py +0 -0
  114. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/widget/NumericTableWidgetItem.py +0 -0
  115. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/widget/TabWidget.py +0 -0
  116. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/widget/UpdateConfigWidgetItem.py +0 -0
  117. {ok-script-0.0.12 → ok-script-0.0.13}/ok/gui/widget/YesNonWidgetItem.py +0 -0
  118. {ok-script-0.0.12/ok/ocr → ok-script-0.0.13/ok/gui/widget}/__init__.py +0 -0
  119. {ok-script-0.0.12/ok/predict → ok-script-0.0.13/ok/interaction}/__init__.py +0 -0
  120. {ok-script-0.0.12 → ok-script-0.0.13}/ok/logging/Logger.py +0 -0
  121. {ok-script-0.0.12/ok/rotypes/Windows/Media → ok-script-0.0.13/ok/logging}/__init__.py +0 -0
  122. {ok-script-0.0.12/ok/rotypes/Windows/Security → ok-script-0.0.13/ok/ocr}/__init__.py +0 -0
  123. {ok-script-0.0.12/ok/rotypes/Windows/Storage → ok-script-0.0.13/ok/predict}/__init__.py +0 -0
  124. {ok-script-0.0.12 → ok-script-0.0.13}/ok/predict/predict.py +0 -0
  125. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Foundation/Collections.py +0 -0
  126. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Foundation/__init__.py +0 -0
  127. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Globalization/__init__.py +0 -0
  128. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Graphics/Capture/__init__.py +0 -0
  129. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +0 -0
  130. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Graphics/DirectX/__init__.py +0 -0
  131. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Graphics/Imaging/__init__.py +0 -0
  132. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Graphics/__init__.py +0 -0
  133. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Media/Ocr/__init__.py +0 -0
  134. {ok-script-0.0.12/ok/rotypes/Windows → ok-script-0.0.13/ok/rotypes/Windows/Media}/__init__.py +0 -0
  135. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Security/Cryptography/__init__.py +0 -0
  136. {ok-script-0.0.12/ok/save → ok-script-0.0.13/ok/rotypes/Windows/Security}/__init__.py +0 -0
  137. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/Windows/Storage/Streams/__init__.py +0 -0
  138. {ok-script-0.0.12/ok/scene → ok-script-0.0.13/ok/rotypes/Windows/Storage}/__init__.py +0 -0
  139. {ok-script-0.0.12/ok/stats → ok-script-0.0.13/ok/rotypes/Windows}/__init__.py +0 -0
  140. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/__init__.py +0 -0
  141. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/delegate.py +0 -0
  142. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/export.py +0 -0
  143. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/idldsl.py +0 -0
  144. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/inspectable.py +0 -0
  145. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/roapi.py +0 -0
  146. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/types.py +0 -0
  147. {ok-script-0.0.12 → ok-script-0.0.13}/ok/rotypes/winstring.py +0 -0
  148. {ok-script-0.0.12 → ok-script-0.0.13}/ok/save/BlackBarProcessor.py +0 -0
  149. {ok-script-0.0.12 → ok-script-0.0.13}/ok/save/PostProcessor.py +0 -0
  150. {ok-script-0.0.12 → ok-script-0.0.13}/ok/save/SaveByInterval.py +0 -0
  151. {ok-script-0.0.12 → ok-script-0.0.13}/ok/save/SaveByKeyPress.py +0 -0
  152. {ok-script-0.0.12 → ok-script-0.0.13}/ok/save/SaveMethodBase.py +0 -0
  153. {ok-script-0.0.12/ok/task → ok-script-0.0.13/ok/save}/__init__.py +0 -0
  154. {ok-script-0.0.12 → ok-script-0.0.13}/ok/scene/FeatureScene.py +0 -0
  155. {ok-script-0.0.12 → ok-script-0.0.13}/ok/scene/Scene.py +0 -0
  156. {ok-script-0.0.12/ok/util → ok-script-0.0.13/ok/scene}/__init__.py +0 -0
  157. {ok-script-0.0.12 → ok-script-0.0.13}/ok/stats/StreamStats.py +0 -0
  158. {ok-script-0.0.12 → ok-script-0.0.13}/ok/util/json.py +0 -0
  159. {ok-script-0.0.12 → ok-script-0.0.13}/ok/util/list.py +0 -0
  160. {ok-script-0.0.12 → ok-script-0.0.13}/ok/util/path.py +0 -0
  161. {ok-script-0.0.12 → ok-script-0.0.13}/ok/util/thread.py +0 -0
  162. {ok-script-0.0.12 → ok-script-0.0.13}/ok/util/win32_process.py +0 -0
  163. {ok-script-0.0.12 → ok-script-0.0.13}/ok/util/yaml.py +0 -0
  164. {ok-script-0.0.12 → ok-script-0.0.13}/ok_script.egg-info/dependency_links.txt +0 -0
  165. {ok-script-0.0.12 → ok-script-0.0.13}/ok_script.egg-info/top_level.txt +0 -0
  166. {ok-script-0.0.12 → ok-script-0.0.13}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ok-script
3
- Version: 0.0.12
3
+ Version: 0.0.13
4
4
  Summary: Automation with Computer Vision for Python
5
5
  Home-page: https://github.com/ok-oldking/ok-script
6
6
  Author: ok-oldking
@@ -17,6 +17,7 @@ Requires-Dist: PyDirectInput~=1.0.4
17
17
  Requires-Dist: pywin32==306
18
18
  Requires-Dist: typing-extensions~=4.10.0
19
19
  Requires-Dist: PySide6~=6.6.2
20
+ Requires-Dist: PySide6-Fluent-Widgets~=1.5.5
20
21
 
21
22
  # ok-script
22
23
 
@@ -8,7 +8,6 @@ from PySide6.QtWidgets import QApplication
8
8
  import ok
9
9
  from ok.logging.Logger import get_logger, config_logger
10
10
 
11
-
12
11
  logger = get_logger(__name__)
13
12
 
14
13
 
@@ -44,10 +43,8 @@ class OK:
44
43
 
45
44
  def start(self):
46
45
  if self.config.get("use_gui"):
47
- self.app.show_loading()
48
- from ok.gui.util.InitWorker import InitWorker
49
- self.worker = InitWorker(self.do_init)
50
- self.worker.start()
46
+ self.do_init()
47
+ self.app.show_main_window()
51
48
  self.app.exec()
52
49
  else:
53
50
  self.task_executor.start()
@@ -102,7 +99,9 @@ class OK:
102
99
  self.init_message("TaskExecutor init Start")
103
100
  from ok.task.TaskExecutor import TaskExecutor
104
101
  self.task_executor = TaskExecutor(self.device_manager, exit_event=self.exit_event,
105
- tasks=self.config['tasks'], scenes=self.config['scenes'],
102
+ onetime_tasks=self.config.get('onetime_tasks', []),
103
+ trigger_tasks=self.config.get('trigger_tasks', []),
104
+ scenes=self.config['scenes'],
106
105
  feature_set=self.feature_set,
107
106
  ocr=self.ocr, config_folder=self.config.get("config_folder") or "config")
108
107
  self.init_message("TaskExecutor init Done")
@@ -118,6 +117,8 @@ class OK:
118
117
  if self.device_manager is None:
119
118
  from ok.capture.adb.DeviceManager import DeviceManager
120
119
  self.device_manager = DeviceManager(self.config.get("config_folder") or "config",
121
- self.config.get('capture_window_title'), self.config.get("debug"),
120
+ self.config.get('capture_window_title'),
121
+ self.config.get('capture_window_exe_name'),
122
+ self.config.get("debug"),
122
123
  self.exit_event)
123
124
  ok.gui.device_manager = self.device_manager
@@ -28,3 +28,6 @@ class BaseCaptureMethod:
28
28
 
29
29
  def clickable(self):
30
30
  pass
31
+
32
+ def connected(self):
33
+ pass
@@ -0,0 +1,198 @@
1
+ # original https://github.com/dantmnf & https://github.com/hakaboom/winAuto
2
+ import ctypes
3
+ import re
4
+ import threading
5
+
6
+ import win32process
7
+ from win32 import win32gui
8
+
9
+ from ok.capture.windows.window import is_foreground_window, get_window_bounds
10
+ from ok.gui.Communicate import communicate
11
+ from ok.logging.Logger import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ class HwndWindow:
17
+ visible = True
18
+ x = 0
19
+ y = 0
20
+ width = 0
21
+ height = 0
22
+ title_height = 0
23
+ border = 0
24
+ scaling = 1
25
+ top_cut = 0
26
+ right_cut = 0
27
+ bottom_cut = 0
28
+ left_cut = 0
29
+ window_change_listeners = []
30
+ frame_aspect_ratio = 0
31
+ hwnd = None
32
+ frame_width = 0
33
+ frame_height = 0
34
+ exists = False
35
+ title = None
36
+
37
+ def __init__(self, title="", exe_name=None, exit_event=threading.Event(), frame_width=0, frame_height=0):
38
+ super().__init__()
39
+ self.app_exit_event = exit_event
40
+ self.exe_name = exe_name
41
+ self.stop_event = threading.Event()
42
+ self.update_title_re(title)
43
+ self.visible = False
44
+ self.update_frame_size(frame_width, frame_height)
45
+ self.do_update_window_size()
46
+ self.thread = threading.Thread(target=self.update_window_size)
47
+ self.thread.start()
48
+
49
+ def update_title_and_exe(self, title, exe):
50
+ self.update_title_re(title)
51
+ self.exe_name = exe
52
+ self.hwnd = None
53
+ self.visible = False
54
+ self.exists = False
55
+ self.width = 0
56
+ self.height = 0
57
+ self.do_update_window_size()
58
+
59
+ def stop(self):
60
+ self.stop_event.set()
61
+
62
+ def update_title_re(self, title):
63
+ if not title:
64
+ self.title = None
65
+ else:
66
+ self.title = re.compile(title)
67
+
68
+ def update_frame_size(self, width, height):
69
+ logger.debug(f"update_frame_size:{self.frame_width}x{self.frame_height} to {width}x{height}")
70
+ if width != self.frame_width or height != self.frame_height:
71
+ self.frame_width = width
72
+ self.frame_height = height
73
+ if width > 0 and height > 0:
74
+ self.frame_aspect_ratio = width / height
75
+ logger.debug(f"HwndWindow: frame ratio:{self.frame_aspect_ratio} width: {width}, height: {height}")
76
+
77
+ def update_window_size(self):
78
+ while not self.app_exit_event.is_set() and not self.stop_event.is_set():
79
+ self.do_update_window_size()
80
+ self.app_exit_event.wait(0.1)
81
+
82
+ def get_abs_cords(self, x, y):
83
+ return int(self.x * self.scaling + (self.border * self.scaling + x)), int(
84
+ self.y * self.scaling + (y + self.title_height * self.scaling))
85
+
86
+ def get_top_left_frame_offset(self):
87
+ return int(self.border * self.scaling), int(
88
+ self.title_height * self.scaling)
89
+
90
+ def do_update_window_size(self):
91
+ try:
92
+ 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
93
+ if self.hwnd is None:
94
+ self.hwnd = find_hwnd_by_title_and_exe(self.title, self.exe_name)
95
+ self.exists = self.hwnd is not None
96
+ if self.hwnd is not None:
97
+ self.exists = win32gui.IsWindow(self.hwnd)
98
+ if self.exists:
99
+ visible = is_foreground_window(self.hwnd)
100
+ x, y, border, title_height, width, height, scaling = get_window_bounds(
101
+ self.hwnd)
102
+ if self.frame_aspect_ratio != 0:
103
+ window_ratio = width / height
104
+ if window_ratio < self.frame_aspect_ratio:
105
+ cropped_window_height = int(width / self.frame_aspect_ratio)
106
+ title_height += height - cropped_window_height
107
+ height = cropped_window_height
108
+ height = height
109
+ width = width
110
+ title_height = title_height
111
+ else:
112
+ self.hwnd = None
113
+ changed = False
114
+ if visible != self.visible or self.scaling != scaling:
115
+ self.visible = visible
116
+ self.scaling = scaling
117
+ changed = True
118
+ if (
119
+ 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 (
120
+ (x >= 0 and y >= 0) or self.visible):
121
+ self.x, self.y, self.border, self.title_height, self.width, self.height = x, y, border, title_height, width, height
122
+ changed = True
123
+ if changed:
124
+ logger.debug(
125
+ f"do_update_window_size changed: {self.visible} {self.x} {self.y} {self.border} {self.width} {self.height} {self.scaling}")
126
+ communicate.window.emit(self.visible, self.x, self.y, self.border, self.title_height, self.width,
127
+ self.height, self.scaling)
128
+ except Exception as e:
129
+ logger.error(f"do_update_window_size exception", e)
130
+
131
+ def frame_ratio(self, size):
132
+ if self.frame_width > 0 and self.width > 0:
133
+ return int(size / self.frame_width * self.width)
134
+ else:
135
+ return size
136
+
137
+ def title_text(self):
138
+ if self.hwnd:
139
+ return win32gui.GetWindowText(self.hwnd)
140
+ return ""
141
+
142
+
143
+ def find_hwnd_by_title_and_exe(title, exe):
144
+ if not title and not exe:
145
+ return None
146
+ hwnds = find_hwnds_by_title(title)
147
+ if exe is not None:
148
+ for hwnd in hwnds:
149
+ exe_2 = get_exe_name_by_hwnd(hwnd)
150
+ if exe_2 == exe:
151
+ return hwnd
152
+ if len(hwnds) > 0:
153
+ return hwnds[0]
154
+
155
+
156
+ def find_hwnds_by_title(title):
157
+ hwnds = []
158
+ if not title:
159
+ return hwnds
160
+
161
+ def enum_windows_proc(hwnd, lParam):
162
+ text = win32gui.GetWindowText(hwnd)
163
+ if text and win32gui.IsWindowVisible(hwnd):
164
+ if isinstance(title, str) and title == text:
165
+ hwnds.append(hwnd)
166
+ elif re.search(title, text):
167
+ hwnds.append(hwnd)
168
+
169
+ win32gui.EnumWindows(enum_windows_proc, None)
170
+ if len(hwnds) > 0:
171
+ if len(hwnds) > 1:
172
+ logger.warning(f"Found multiple hwnds {len(hwnds)}")
173
+ return hwnds
174
+
175
+
176
+ OpenProcess = ctypes.windll.kernel32.OpenProcess
177
+ CloseHandle = ctypes.windll.kernel32.CloseHandle
178
+ GetModuleFileNameExW = ctypes.windll.psapi.GetModuleFileNameExW
179
+
180
+ PROCESS_QUERY_INFORMATION = 0x0400
181
+ PROCESS_VM_READ = 0x0010
182
+
183
+
184
+ def get_exe_name_by_hwnd(hwnd):
185
+ # Get the process ID of the window
186
+ _, pid = win32process.GetWindowThreadProcessId(hwnd)
187
+
188
+ # Open the process
189
+ h_process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid)
190
+ if h_process:
191
+ # Get the executable name
192
+ exe_name = ctypes.create_unicode_buffer(1024)
193
+ GetModuleFileNameExW(h_process, None, exe_name, 1024)
194
+
195
+ # Close the process handle
196
+ CloseHandle(h_process)
197
+
198
+ return exe_name.value
@@ -1,6 +1,7 @@
1
1
  # original https://github.com/Toufool/AutoSplit/blob/master/src/capture_method/WindowsGraphicsCaptureMethod.py
2
2
  import cv2
3
3
  import numpy as np
4
+ from adbutils import AdbError
4
5
  from typing_extensions import override
5
6
 
6
7
  from ok.capture.BaseCaptureMethod import BaseCaptureMethod
@@ -17,6 +18,7 @@ class ADBCaptureMethod(BaseCaptureMethod):
17
18
  super().__init__()
18
19
  self.exit_event = exit_event
19
20
  self._size = (width, height)
21
+ self._connected = (width != 0 and height != 0)
20
22
  self.device = device
21
23
 
22
24
  @property
@@ -41,17 +43,26 @@ class ADBCaptureMethod(BaseCaptureMethod):
41
43
  frame = do_screencap(self.device)
42
44
  if frame is not None:
43
45
  height, width, _ = frame.shape
46
+ self._connected = True
44
47
  self._size = (width, height)
48
+ else:
49
+ self._connected = False
45
50
  return frame
46
51
 
52
+ def connected(self):
53
+ return self._connected and self.device is not None
54
+
47
55
 
48
56
  def do_screencap(device) -> np.ndarray | None:
49
57
  if device is not None:
50
- png_bytes = device.shell("screencap -p", encoding=None)
51
- if png_bytes is not None and len(png_bytes) > 0:
52
- image_data = np.frombuffer(png_bytes, dtype=np.uint8)
53
- image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
54
- if image is not None:
55
- return image
56
- else:
57
- logger.error(f"Screencap image decode error, probably disconnected")
58
+ try:
59
+ png_bytes = device.shell("screencap -p", encoding=None)
60
+ if png_bytes is not None and len(png_bytes) > 0:
61
+ image_data = np.frombuffer(png_bytes, dtype=np.uint8)
62
+ image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
63
+ if image is not None:
64
+ return image
65
+ else:
66
+ logger.error(f"Screencap image decode error, probably disconnected")
67
+ except AdbError as e:
68
+ logger.error(f"Device {device} error", e)
@@ -1,11 +1,13 @@
1
1
  import threading
2
2
 
3
3
  import adbutils
4
+ import win32gui
4
5
 
5
- from ok.capture.HwndWindow import HwndWindow
6
+ from ok.capture.HwndWindow import HwndWindow, find_hwnd_by_title_and_exe
6
7
  from ok.capture.adb.ADBCaptureMethod import ADBCaptureMethod, do_screencap
7
8
  from ok.capture.adb.vbox import installed_emulator
8
9
  from ok.capture.windows.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod
10
+ from ok.capture.windows.window import get_window_bounds
9
11
  from ok.config.Config import Config
10
12
  from ok.gui.Communicate import communicate
11
13
  from ok.interaction.ADBInteraction import ADBBaseInteraction
@@ -17,18 +19,22 @@ logger = get_logger(__name__)
17
19
 
18
20
  class DeviceManager:
19
21
 
20
- def __init__(self, config_folder, hwnd_title=None, debug=False, exit_event=None):
22
+ def __init__(self, config_folder, hwnd_title=None, exe_name=None, debug=False, exit_event=None):
21
23
  self._device = None
22
- self.adb = None
24
+ self._connect_all = False
25
+ self.adb = self.adb = adbutils.AdbClient(host="127.0.0.1")
26
+ logger.debug(f'connect adb')
23
27
  self.hwnd_title = hwnd_title
28
+ self.exe_name = exe_name
24
29
  self.debug = debug
25
30
  self.interaction = None
26
31
  self.exit_event = exit_event
27
32
  if hwnd_title is not None:
28
- self.hwnd = HwndWindow(hwnd_title, exit_event)
29
- self.config = Config({"devices": {}}, config_folder, "DeviceManager")
33
+ self.hwnd = HwndWindow(hwnd_title, exe_name, exit_event)
34
+ self.config = Config({"devices": {}, "preferred": "none"}, config_folder, "DeviceManager")
30
35
  self.thread = None
31
36
  self.capture_method = None
37
+ self.start()
32
38
 
33
39
  def refresh(self):
34
40
  if self.thread is None or not self.thread.is_alive():
@@ -40,18 +46,40 @@ class DeviceManager:
40
46
  return self.config.get("devices")
41
47
 
42
48
  def connect_all(self):
43
- for device in self.device_dict.values():
44
- self.adb.connect(device['address'])
45
- logger.debug(f'connect_all {self.adb.device_list()}')
49
+ if not self._connect_all:
50
+ for device in self.device_dict.values():
51
+ if device["device"] == "adb":
52
+ self.adb.connect(device['address'])
53
+ self._connect_all = True
54
+ logger.debug(f'connect_all {self.adb.device_list()}')
46
55
 
47
56
  def get_devices(self):
48
- return self.device_dict.values()
57
+ return list(self.device_dict.values())
49
58
 
50
59
  def do_refresh(self):
51
- if self.adb is None:
52
- self.adb = adbutils.AdbClient(host="127.0.0.1")
53
- logger.debug(f'connect adb')
54
60
  self.connect_all()
61
+
62
+ if self.hwnd_title:
63
+ nick = ""
64
+ width = 0
65
+ height = 0
66
+ hwnd = find_hwnd_by_title_and_exe(self.hwnd_title, self.exe_name)
67
+ if hwnd is not None:
68
+ nick = win32gui.GetWindowText(hwnd)
69
+ _, _, _, _, width, height, _ = get_window_bounds(
70
+ hwnd)
71
+ if not nick:
72
+ nick = self.exe_name
73
+ self.device_dict[self.hwnd_title] = {"address": "", "imei": self.hwnd_title, "device": "windows",
74
+ "model": "", "nick": nick, "width": width,
75
+ "height": height,
76
+ "hwnd": nick, "capture": "windows",
77
+ "connected": hwnd is not None
78
+ }
79
+ if width != 0:
80
+ self.device_dict[self.hwnd_title]["resolution"] = f"{width}x{height}"
81
+ communicate.adb_devices.emit(False)
82
+
55
83
  installed_emulators = installed_emulator()
56
84
  for device in self.adb.list():
57
85
  logger.debug(f'adb.list() {device}')
@@ -75,7 +103,7 @@ class DeviceManager:
75
103
  height, width, _ = frame.shape
76
104
  adb_device = {"address": device.serial, "imei": imei, "device": "adb", "capture": "adb", "width": width,
77
105
  "height": height,
78
- "model": device.prop.model, "nick": device.prop.model, "connected": True, "preferred": False,
106
+ "model": device.prop.model, "nick": device.prop.model, "connected": True,
79
107
  "resolution": f"{width}x{height}"}
80
108
  found = False
81
109
  for emulator in installed_emulators:
@@ -95,24 +123,12 @@ class DeviceManager:
95
123
  self.device_dict[imei] = adb_device
96
124
  adb_connected.append(imei)
97
125
  for imei in self.device_dict:
98
- if imei not in adb_connected:
126
+ if imei not in adb_connected and self.device_dict[imei]['device'] == 'adb':
99
127
  self.device_dict[imei]['connected'] = False
100
- if self.hwnd_title:
101
- nick = ""
102
- width = 0
103
- height = 0
104
- if self.hwnd:
105
- nick = self.hwnd.title_text()
106
- width = self.hwnd.width
107
- height = self.hwnd.height
108
- self.device_dict[self.hwnd_title] = {"address": "", "imei": self.hwnd_title, "device": "windows",
109
- "model": "", "nick": nick or self.hwnd_title, "width": width,
110
- "height": height,
111
- "hwnd": nick, "capture": "windows",
112
- "connected": self.hwnd is not None and self.hwnd.exists,
113
- "resolution": f"{width}x{height}"}
114
- communicate.adb_devices.emit()
128
+
129
+ communicate.adb_devices.emit(True)
115
130
  self.config.save_file()
131
+ self.start()
116
132
  logger.debug(f'refresh {self.device_dict}')
117
133
 
118
134
  def set_preferred_device(self, imei=None):
@@ -127,12 +143,11 @@ class DeviceManager:
127
143
  self.config["preferred"] = imei
128
144
  self.config.save_file()
129
145
  logger.debug(f'preferred device: {preferred}')
146
+ self.start()
130
147
 
131
148
  def get_preferred_device(self):
132
149
  imei = self.config.get("preferred")
133
150
  preferred = self.device_dict.get(imei)
134
- if preferred is None:
135
- raise ValueError(f"preferred device {imei} is none")
136
151
  return preferred
137
152
 
138
153
  def set_hwnd_name(self, hwnd_name):
@@ -140,19 +155,32 @@ class DeviceManager:
140
155
  if preferred.get("hwnd") != hwnd_name:
141
156
  preferred['hwnd'] = hwnd_name
142
157
  self.config.save_file()
158
+ self.start()
143
159
 
144
160
  def set_capture(self, capture):
145
161
  preferred = self.get_preferred_device()
146
- preferred['capture'] = capture
147
- self.config.save_file()
162
+ if preferred.get("capture") != capture:
163
+ preferred['capture'] = capture
164
+ self.config.save_file()
165
+ self.start()
148
166
 
149
167
  def get_hwnd_name(self):
150
168
  preferred = self.get_preferred_device()
151
169
  return preferred.get('hwnd')
152
170
 
171
+ def ensure_hwnd(self, title, exe, frame_width=0, frame_height=0):
172
+ if self.hwnd is None:
173
+ self.hwnd = HwndWindow(title, exe, self.exit_event, frame_width, frame_height)
174
+ else:
175
+ self.hwnd.update_frame_size(frame_width, frame_height)
176
+ self.hwnd.update_title_and_exe(title, exe)
177
+
153
178
  def start(self):
154
179
  preferred = self.get_preferred_device()
180
+ if preferred is None:
181
+ return
155
182
  if preferred['device'] == 'windows':
183
+ self.ensure_hwnd(self.hwnd_title, self.exe_name)
156
184
  if not isinstance(self.capture_method, WindowsGraphicsCaptureMethod):
157
185
  if self.capture_method is not None:
158
186
  self.capture_method.close()
@@ -160,39 +188,32 @@ class DeviceManager:
160
188
  self.interaction = Win32Interaction(self.capture_method)
161
189
  self.capture_method.hwnd_window = self.hwnd
162
190
  else:
191
+ self.connect_all()
163
192
  for adb_device in self.adb.device_list():
164
193
  if adb_device.serial == preferred.get('address'):
194
+ logger.debug(f"set device {adb_device}")
165
195
  self._device = adb_device
166
196
  hwnd_name = preferred.get('hwnd')
167
197
  width = preferred.get('width', 0)
168
198
  height = preferred.get('height', 0)
169
199
  if preferred.get('capture') == "windows":
170
- if not hwnd_name:
171
- raise ValueError(f"adb device preferred hwnd is none, capture is windows {preferred}")
172
- if self.hwnd is None:
173
- self.hwnd = HwndWindow(hwnd_name, self.exit_event, width, height)
200
+ self.ensure_hwnd(hwnd_name, None, width, height)
174
201
  if not isinstance(self.capture_method, WindowsGraphicsCaptureMethod):
175
202
  if self.capture_method is not None:
176
203
  self.capture_method.close()
177
204
  self.capture_method = WindowsGraphicsCaptureMethod(self.hwnd)
178
- self.hwnd.title = hwnd_name
179
205
  self.capture_method.hwnd_window = self.hwnd
180
206
  elif not isinstance(self.capture_method, ADBCaptureMethod):
181
207
  if self.capture_method is not None:
182
208
  self.capture_method.close()
183
209
  self.capture_method = ADBCaptureMethod(self._device, self.exit_event, width=width,
184
210
  height=height)
185
- self.interaction = ADBBaseInteraction(self, self.capture_method)
186
- if self.debug:
187
- if self.hwnd is None and hwnd_name:
188
- if self.hwnd is None:
189
- self.hwnd = HwndWindow(hwnd_name, self.exit_event, width, height)
190
- self.hwnd.title = hwnd_name
211
+ self.interaction = ADBBaseInteraction(self, self.capture_method, width, height)
212
+ if self.debug and hwnd_name:
213
+ self.ensure_hwnd(hwnd_name, None, width, height)
191
214
  elif self.hwnd is not None:
192
215
  self.hwnd.stop()
193
216
  self.hwnd = None
194
- if self.hwnd:
195
- self.hwnd.update_frame_size(width, height)
196
217
 
197
218
  @property
198
219
  def device(self):
@@ -85,15 +85,16 @@ def installed_emulator():
85
85
 
86
86
  logger.debug(f'running VirtualBox servers: {running_servers}')
87
87
  results = []
88
- for server in running_servers:
89
- logger.debug(f'checking {server}')
90
- hproc = OpenProcess(PROCESS_QUERY_INFORMATION, False, server.pid)
91
- if not hproc:
92
- logger.debug(f'try OpenProcess failed for PID {server.pid}')
93
- continue
94
- CloseHandle(hproc)
95
- logger.debug(f'checking hproc end {server}')
96
- try:
88
+ try:
89
+ for server in running_servers:
90
+ logger.debug(f'checking {server}')
91
+ hproc = OpenProcess(PROCESS_QUERY_INFORMATION, False, server.pid)
92
+ if not hproc:
93
+ logger.debug(f'try OpenProcess failed for PID {server.pid}')
94
+ continue
95
+ CloseHandle(hproc)
96
+ logger.debug(f'checking hproc end {server}')
97
+
97
98
  client = win32com.client.Dispatch(server.clsid)
98
99
  for machine in client.Machines:
99
100
  machine_name = machine.Name
@@ -118,8 +119,8 @@ def installed_emulator():
118
119
  address, 2, 1, override_identifier=identifier,
119
120
  preload_device_info={'emulator_hypervisor': 'vbox'}))
120
121
  logger.debug(f'try checking {server}')
121
- except Exception as e:
122
- logger.error(f'failed to check server :{server} {e}', e)
122
+ except Exception as e:
123
+ logger.error(f'failed to check server :{server} {e}', e)
123
124
  return results
124
125
 
125
126
 
@@ -37,6 +37,8 @@ class WindowsGraphicsCaptureMethod(BaseCaptureMethod):
37
37
  item = None
38
38
  session = None
39
39
  cputex = None
40
+ rtdevice = None
41
+ dxdevice = None
40
42
 
41
43
  def __init__(self, hwnd_window: HwndWindow):
42
44
  super().__init__()
@@ -105,6 +107,9 @@ class WindowsGraphicsCaptureMethod(BaseCaptureMethod):
105
107
  return self.get_frame()
106
108
  self.last_frame = img
107
109
 
110
+ def connected(self):
111
+ return self.hwnd_window is not None and self.hwnd_window.exists
112
+
108
113
  def start_or_stop(self, capture_cursor=False):
109
114
  if self.hwnd_window.exists and self.frame_pool is None:
110
115
  logger.info('init windows capture')
@@ -205,7 +210,7 @@ class WindowsGraphicsCaptureMethod(BaseCaptureMethod):
205
210
  return self.hwnd_window.get_abs_cords(x, y)
206
211
 
207
212
  def clickable(self):
208
- return self.hwnd_window.visible
213
+ return self.hwnd_window is not None and self.hwnd_window.visible
209
214
 
210
215
 
211
216
  def crop_image(image, border, title_height):
@@ -1,6 +1,4 @@
1
1
  import asyncio
2
- import ctypes
3
- import ctypes.wintypes
4
2
  import os
5
3
  import sys
6
4
  from collections.abc import Callable, Iterable
@@ -83,23 +81,6 @@ def try_delete_dc(dc: "PyCDC"):
83
81
  pass
84
82
 
85
83
 
86
- def get_window_bounds(hwnd: int) -> tuple[int, int, int, int]:
87
- extended_frame_bounds = ctypes.wintypes.RECT()
88
- ctypes.windll.dwmapi.DwmGetWindowAttribute(
89
- hwnd,
90
- DWMWA_EXTENDED_FRAME_BOUNDS,
91
- ctypes.byref(extended_frame_bounds),
92
- ctypes.sizeof(extended_frame_bounds),
93
- )
94
-
95
- window_rect = win32gui.GetWindowRect(hwnd)
96
- window_left_bounds = extended_frame_bounds.left - window_rect[0]
97
- window_top_bounds = extended_frame_bounds.top - window_rect[1]
98
- window_width = extended_frame_bounds.right - extended_frame_bounds.left
99
- window_height = extended_frame_bounds.bottom - extended_frame_bounds.top
100
- return window_left_bounds, window_top_bounds, window_width, window_height
101
-
102
-
103
84
  def open_file(file_path: str | bytes | os.PathLike[str] | os.PathLike[bytes]):
104
85
  os.startfile(file_path) # noqa: S606
105
86
 
@@ -5,6 +5,7 @@ from ok.util.path import get_path_relative_to_exe
5
5
  class Config:
6
6
  def __init__(self, default, folder, name):
7
7
  super().__init__()
8
+ self.default = default
8
9
  self.config_file = get_path_relative_to_exe(folder, f"{name}.json")
9
10
  self.config = read_json_file(self.config_file)
10
11
  if self.config is None:
@@ -15,6 +16,11 @@ class Config:
15
16
  def save_file(self):
16
17
  write_json_file(self.config_file, self.config)
17
18
 
19
+ def reset_to_default(self):
20
+ self.config.clear()
21
+ self.update(self.default)
22
+ self.save_file()
23
+
18
24
  def update(self, *args, **kwargs):
19
25
  self.config.update(*args, **kwargs)
20
26
  self.save_file()