ok-script 0.0.42__tar.gz → 0.0.44__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 (163) hide show
  1. {ok-script-0.0.42 → ok-script-0.0.44}/PKG-INFO +11 -9
  2. {ok-script-0.0.42 → ok-script-0.0.44}/ok/OK.py +2 -2
  3. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/BaseCaptureMethod.py +18 -1
  4. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/HwndWindow.py +33 -50
  5. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/adb/ADBCaptureMethod.py +5 -17
  6. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/adb/DeviceManager.py +89 -48
  7. ok-script-0.0.44/ok/capture/adb/WindowsCaptureFactory.py +39 -0
  8. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/adb/vbox.py +0 -18
  9. ok-script-0.0.44/ok/capture/windows/BaseWindowsCaptureMethod.py +36 -0
  10. ok-script-0.0.44/ok/capture/windows/BitBltCaptureMethod.py +97 -0
  11. ok-script-0.0.44/ok/capture/windows/DesktopDuplicationCaptureMethod.py +64 -0
  12. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/windows/WindowsGraphicsCaptureMethod.py +52 -46
  13. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/windows/window.py +6 -6
  14. {ok-script-0.0.42 → ok-script-0.0.44}/ok/config/Config.py +1 -1
  15. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/overlay/OverlayWindow.py +1 -1
  16. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/resources.py +394 -358
  17. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/start/StartCard.py +64 -7
  18. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/start/StartTab.py +2 -0
  19. {ok-script-0.0.42 → ok-script-0.0.44}/ok/logging/Logger.py +6 -4
  20. {ok-script-0.0.42 → ok-script-0.0.44}/ok/task/TaskExecutor.py +1 -1
  21. {ok-script-0.0.42 → ok-script-0.0.44}/ok_script.egg-info/PKG-INFO +11 -9
  22. {ok-script-0.0.42 → ok-script-0.0.44}/ok_script.egg-info/SOURCES.txt +4 -0
  23. ok-script-0.0.44/ok_script.egg-info/requires.txt +10 -0
  24. {ok-script-0.0.42 → ok-script-0.0.44}/setup.py +11 -9
  25. ok-script-0.0.42/ok_script.egg-info/requires.txt +0 -8
  26. {ok-script-0.0.42 → ok-script-0.0.44}/README.md +0 -0
  27. {ok-script-0.0.42 → ok-script-0.0.44}/ok/__init__.py +0 -0
  28. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/__init__.py +0 -0
  29. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/adb/__init__.py +0 -0
  30. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/adb/bin/__init__.py +0 -0
  31. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/adb/targets.py +0 -0
  32. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/windows/__init__.py +0 -0
  33. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/windows/d3d11.py +0 -0
  34. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/windows/dump.py +0 -0
  35. {ok-script-0.0.42 → ok-script-0.0.44}/ok/capture/windows/utils.py +0 -0
  36. {ok-script-0.0.42 → ok-script-0.0.44}/ok/color/Color.py +0 -0
  37. {ok-script-0.0.42 → ok-script-0.0.44}/ok/color/__init__.py +0 -0
  38. {ok-script-0.0.42 → ok-script-0.0.44}/ok/config/InfoDict.py +0 -0
  39. {ok-script-0.0.42 → ok-script-0.0.44}/ok/config/__init__.py +0 -0
  40. {ok-script-0.0.42 → ok-script-0.0.44}/ok/feature/Box.py +0 -0
  41. {ok-script-0.0.42 → ok-script-0.0.44}/ok/feature/Feature.py +0 -0
  42. {ok-script-0.0.42 → ok-script-0.0.44}/ok/feature/FeatureSet.py +0 -0
  43. {ok-script-0.0.42 → ok-script-0.0.44}/ok/feature/FindFeature.py +0 -0
  44. {ok-script-0.0.42 → ok-script-0.0.44}/ok/feature/__init__.py +0 -0
  45. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/App.py +0 -0
  46. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/Communicate.py +0 -0
  47. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/MainWindow.py +0 -0
  48. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/MessageWindow.py +0 -0
  49. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/TabTitles.py +0 -0
  50. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/__init__.py +0 -0
  51. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/about/AboutTab.py +0 -0
  52. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/about/__init__.py +0 -0
  53. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/common/OKIcon.py +0 -0
  54. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/common/__init__.py +0 -0
  55. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/common/config.py +0 -0
  56. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/common/style_sheet.py +0 -0
  57. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/debug/AspectRatioWidget.py +0 -0
  58. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/debug/DebugTab.py +0 -0
  59. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/debug/FrameWidget.py +0 -0
  60. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/debug/InfoWidget.py +0 -0
  61. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/debug/LoggerWidget.py +0 -0
  62. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/debug/Screenshot.py +0 -0
  63. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/debug/__init__.py +0 -0
  64. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/i18n/__init__.py +0 -0
  65. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/i18n/path.py +0 -0
  66. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/i18n/update.py +0 -0
  67. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/icon/__init__.py +0 -0
  68. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/icon/icon.py +0 -0
  69. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/overlay/__init__.py +0 -0
  70. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/qss/__init__.py +0 -0
  71. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/settings/SettingTab.py +0 -0
  72. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/settings/__init__.py +0 -0
  73. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/start/SelectCaptureListView.py +0 -0
  74. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/start/SelectHwndWindow.py +0 -0
  75. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/start/__init__.py +0 -0
  76. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/ConfigItemFactory.py +0 -0
  77. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/ConfigLabelAndWidget.py +0 -0
  78. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/LabelAndDoubleSpinBox.py +0 -0
  79. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/LabelAndDropDown.py +0 -0
  80. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/LabelAndLineEdit.py +0 -0
  81. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/LabelAndList.py +0 -0
  82. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/LabelAndSpinBox.py +0 -0
  83. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/LabelAndSwitchButton.py +0 -0
  84. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/LabelAndWidget.py +0 -0
  85. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/ModifyListDialog.py +0 -0
  86. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/ModifyListItem.py +0 -0
  87. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/OneTimeTaskTab.py +0 -0
  88. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/StartButton.py +0 -0
  89. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/TaskCard.py +0 -0
  90. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/TaskOpButton.py +0 -0
  91. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/TooltipTableWidget.py +0 -0
  92. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/TriggerTaskTab.py +0 -0
  93. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/tasks/__init__.py +0 -0
  94. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/util/Alert.py +0 -0
  95. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/util/InitWorker.py +0 -0
  96. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/util/__init__.py +0 -0
  97. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/Card.py +0 -0
  98. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/FramelessDialog.py +0 -0
  99. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/ListTableWidgetItem.py +0 -0
  100. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/NumericTableWidgetItem.py +0 -0
  101. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/StatusBar.py +0 -0
  102. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/Tab.py +0 -0
  103. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/TabWidget.py +0 -0
  104. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/UpdateConfigWidgetItem.py +0 -0
  105. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/YesNonWidgetItem.py +0 -0
  106. {ok-script-0.0.42 → ok-script-0.0.44}/ok/gui/widget/__init__.py +0 -0
  107. {ok-script-0.0.42 → ok-script-0.0.44}/ok/interaction/ADBInteraction.py +0 -0
  108. {ok-script-0.0.42 → ok-script-0.0.44}/ok/interaction/BaseInteraction.py +0 -0
  109. {ok-script-0.0.42 → ok-script-0.0.44}/ok/interaction/Win32Interaction.py +0 -0
  110. {ok-script-0.0.42 → ok-script-0.0.44}/ok/interaction/__init__.py +0 -0
  111. {ok-script-0.0.42 → ok-script-0.0.44}/ok/logging/__init__.py +0 -0
  112. {ok-script-0.0.42 → ok-script-0.0.44}/ok/ocr/OCR.py +0 -0
  113. {ok-script-0.0.42 → ok-script-0.0.44}/ok/ocr/__init__.py +0 -0
  114. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Foundation/Collections.py +0 -0
  115. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Foundation/__init__.py +0 -0
  116. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Globalization/__init__.py +0 -0
  117. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Graphics/Capture/__init__.py +0 -0
  118. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +0 -0
  119. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Graphics/DirectX/__init__.py +0 -0
  120. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Graphics/Imaging/__init__.py +0 -0
  121. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Graphics/__init__.py +0 -0
  122. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Media/Ocr/__init__.py +0 -0
  123. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Media/__init__.py +0 -0
  124. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Security/Cryptography/__init__.py +0 -0
  125. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Security/__init__.py +0 -0
  126. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Storage/Streams/__init__.py +0 -0
  127. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/Storage/__init__.py +0 -0
  128. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/Windows/__init__.py +0 -0
  129. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/__init__.py +0 -0
  130. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/delegate.py +0 -0
  131. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/export.py +0 -0
  132. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/idldsl.py +0 -0
  133. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/inspectable.py +0 -0
  134. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/roapi.py +0 -0
  135. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/types.py +0 -0
  136. {ok-script-0.0.42 → ok-script-0.0.44}/ok/rotypes/winstring.py +0 -0
  137. {ok-script-0.0.42 → ok-script-0.0.44}/ok/save/BlackBarProcessor.py +0 -0
  138. {ok-script-0.0.42 → ok-script-0.0.44}/ok/save/PostProcessor.py +0 -0
  139. {ok-script-0.0.42 → ok-script-0.0.44}/ok/save/SaveByInterval.py +0 -0
  140. {ok-script-0.0.42 → ok-script-0.0.44}/ok/save/SaveByKeyPress.py +0 -0
  141. {ok-script-0.0.42 → ok-script-0.0.44}/ok/save/SaveMethodBase.py +0 -0
  142. {ok-script-0.0.42 → ok-script-0.0.44}/ok/save/__init__.py +0 -0
  143. {ok-script-0.0.42 → ok-script-0.0.44}/ok/scene/FeatureScene.py +0 -0
  144. {ok-script-0.0.42 → ok-script-0.0.44}/ok/scene/Scene.py +0 -0
  145. {ok-script-0.0.42 → ok-script-0.0.44}/ok/scene/__init__.py +0 -0
  146. {ok-script-0.0.42 → ok-script-0.0.44}/ok/stats/StreamStats.py +0 -0
  147. {ok-script-0.0.42 → ok-script-0.0.44}/ok/stats/__init__.py +0 -0
  148. {ok-script-0.0.42 → ok-script-0.0.44}/ok/task/BaseTask.py +0 -0
  149. {ok-script-0.0.42 → ok-script-0.0.44}/ok/task/ExecutorOperation.py +0 -0
  150. {ok-script-0.0.42 → ok-script-0.0.44}/ok/task/OneTimeTask.py +0 -0
  151. {ok-script-0.0.42 → ok-script-0.0.44}/ok/task/TriggerTask.py +0 -0
  152. {ok-script-0.0.42 → ok-script-0.0.44}/ok/task/__init__.py +0 -0
  153. {ok-script-0.0.42 → ok-script-0.0.44}/ok/util/__init__.py +0 -0
  154. {ok-script-0.0.42 → ok-script-0.0.44}/ok/util/exit_event.py +0 -0
  155. {ok-script-0.0.42 → ok-script-0.0.44}/ok/util/json.py +0 -0
  156. {ok-script-0.0.42 → ok-script-0.0.44}/ok/util/list.py +0 -0
  157. {ok-script-0.0.42 → ok-script-0.0.44}/ok/util/path.py +0 -0
  158. {ok-script-0.0.42 → ok-script-0.0.44}/ok/util/thread.py +0 -0
  159. {ok-script-0.0.42 → ok-script-0.0.44}/ok/util/win32_process.py +0 -0
  160. {ok-script-0.0.42 → ok-script-0.0.44}/ok/util/yaml.py +0 -0
  161. {ok-script-0.0.42 → ok-script-0.0.44}/ok_script.egg-info/dependency_links.txt +0 -0
  162. {ok-script-0.0.42 → ok-script-0.0.44}/ok_script.egg-info/top_level.txt +0 -0
  163. {ok-script-0.0.42 → ok-script-0.0.44}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ok-script
3
- Version: 0.0.42
3
+ Version: 0.0.44
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
@@ -11,14 +11,16 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: Microsoft :: Windows
12
12
  Requires-Python: >=3.6
13
13
  Description-Content-Type: text/markdown
14
- Requires-Dist: adbutils==2.7.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.11.0
19
- Requires-Dist: PySide6==6.7.0
20
- Requires-Dist: PySide6-Fluent-Widgets==1.5.6
21
- Requires-Dist: opencv-python==4.9.0.80
14
+ Requires-Dist: adbutils>=2.7.2
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.11.0
19
+ Requires-Dist: PySide6>=6.7.0
20
+ Requires-Dist: PySide6-Fluent-Widgets>=1.5.6
21
+ Requires-Dist: psutil>=5.9.8
22
+ Requires-Dist: opencv-python>=4.9.0.80
23
+ Requires-Dist: ok-d3dshot>=0.1.5
22
24
 
23
25
  # ok-script
24
26
 
@@ -132,8 +132,8 @@ class OK:
132
132
  if self.device_manager is None:
133
133
  from ok.capture.adb.DeviceManager import DeviceManager
134
134
  self.device_manager = DeviceManager(self.config.get("config_folder") or "config",
135
- self.config.get('capture_window_title'),
136
- self.config.get('capture_window_exe_name'),
135
+ self.config.get('windows_capture'),
136
+ self.config.get('adb_capture'),
137
137
  self.config.get("debug"),
138
138
  self.exit_event)
139
139
  ok.gui.device_manager = self.device_manager
@@ -18,9 +18,26 @@ class BaseCaptureMethod:
18
18
  # Some capture methods don't need an initialization process
19
19
  pass
20
20
 
21
+ @property
22
+ def width(self):
23
+ if self._size[0] == 0:
24
+ self.get_frame()
25
+ return self._size[0]
26
+
27
+ @property
28
+ def height(self):
29
+ if self._size[1] == 0:
30
+ self.get_frame()
31
+ return self._size[1]
32
+
21
33
  def get_frame(self) -> np.ndarray | None:
22
34
  try:
23
- return self.do_get_frame()
35
+ frame = self.do_get_frame()
36
+ if frame is not None:
37
+ self._size = (frame.shape[1], frame.shape[0])
38
+ if frame.shape[2] == 4:
39
+ frame = frame[:, :, :3]
40
+ return frame
24
41
  except Exception as e:
25
42
  raise CaptureException() from e
26
43
 
@@ -1,10 +1,9 @@
1
1
  # original https://github.com/dantmnf & https://github.com/hakaboom/winAuto
2
- import ctypes
3
- import os
4
2
  import re
5
3
  import threading
6
4
  import time
7
5
 
6
+ import psutil
8
7
  import win32process
9
8
  from win32 import win32gui
10
9
 
@@ -28,6 +27,8 @@ class HwndWindow:
28
27
  right_cut = 0
29
28
  bottom_cut = 0
30
29
  left_cut = 0
30
+ ext_left_bounds = 0 # for BitBlt
31
+ ext_top_bounds = 0 # for BitBlt
31
32
  window_change_listeners = []
32
33
  frame_aspect_ratio = 0
33
34
  hwnd = None
@@ -35,13 +36,14 @@ class HwndWindow:
35
36
  frame_height = 0
36
37
  exists = False
37
38
  title = None
39
+ exe_full_path = None
38
40
 
39
- def __init__(self, exit_event, title="", exe_name=None, frame_width=0, frame_height=0):
41
+ def __init__(self, exit_event, title, exe_name=None, frame_width=0, frame_height=0):
40
42
  super().__init__()
41
43
  self.app_exit_event = exit_event
42
44
  self.exe_name = exe_name
45
+ self.title = title
43
46
  self.stop_event = threading.Event()
44
- self.update_title_re(title)
45
47
  self.visible = False
46
48
  self.update_frame_size(frame_width, frame_height)
47
49
  self.do_update_window_size()
@@ -49,7 +51,7 @@ class HwndWindow:
49
51
  self.thread.start()
50
52
 
51
53
  def update_title_and_exe(self, title, exe):
52
- self.update_title_re(title)
54
+ self.title = title
53
55
  self.exe_name = exe
54
56
  self.hwnd = None
55
57
  self.visible = False
@@ -61,12 +63,6 @@ class HwndWindow:
61
63
  def stop(self):
62
64
  self.stop_event.set()
63
65
 
64
- def update_title_re(self, title):
65
- if not title:
66
- self.title = None
67
- else:
68
- self.title = re.compile(title)
69
-
70
66
  def update_frame_size(self, width, height):
71
67
  logger.debug(f"update_frame_size:{self.frame_width}x{self.frame_height} to {width}x{height}")
72
68
  if width != self.frame_width or height != self.frame_height:
@@ -82,24 +78,22 @@ class HwndWindow:
82
78
  time.sleep(0.2)
83
79
 
84
80
  def get_abs_cords(self, x, y):
85
- return int(self.x * self.scaling + (self.border * self.scaling + x)), int(
86
- self.y * self.scaling + (y + self.title_height * self.scaling))
81
+ return self.x + self.border + x, self.y + y + self.title_height
87
82
 
88
83
  def get_top_left_frame_offset(self):
89
- return int(self.border * self.scaling), int(
90
- self.title_height * self.scaling)
84
+ return self.border, self.title_height
91
85
 
92
86
  def do_update_window_size(self):
93
87
  try:
94
- 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
88
+ visible, x, y, border, title_height, width, height, scaling, ext_left_bounds, ext_top_bounds = self.visible, self.x, self.y, self.border, self.title_height, self.width, self.height, self.scaling, self.ext_left_bounds, self.ext_top_bounds
95
89
  if self.hwnd is None:
96
- self.hwnd = find_hwnd_by_title_and_exe(self.title, self.exe_name)
90
+ self.hwnd, self.exe_full_path = find_hwnd_by_title_and_exe(self.title, self.exe_name)
97
91
  self.exists = self.hwnd is not None
98
92
  if self.hwnd is not None:
99
93
  self.exists = win32gui.IsWindow(self.hwnd)
100
94
  if self.exists:
101
95
  visible = is_foreground_window(self.hwnd)
102
- x, y, border, title_height, width, height, scaling = get_window_bounds(
96
+ x, y, border, title_height, width, height, scaling, ext_left_bounds, ext_top_bounds = get_window_bounds(
103
97
  self.hwnd)
104
98
  if self.frame_aspect_ratio != 0 and height != 0:
105
99
  window_ratio = width / height
@@ -117,10 +111,10 @@ class HwndWindow:
117
111
  self.visible = visible
118
112
  self.scaling = scaling
119
113
  changed = True
120
- if (
121
- 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 (
122
- (x >= 0 and y >= 0) or self.visible):
123
- self.x, self.y, self.border, self.title_height, self.width, self.height = x, y, border, title_height, width, height
114
+ if (ext_left_bounds != self.ext_left_bounds or ext_top_bounds != self.ext_top_bounds or
115
+ 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 (
116
+ (x >= -1 and y >= -1) or self.visible):
117
+ self.x, self.y, self.border, self.title_height, self.width, self.height, self.ext_left_bounds, self.ext_top_bounds = x, y, border, title_height, width, height, ext_left_bounds, ext_top_bounds
124
118
  changed = True
125
119
  if changed:
126
120
  logger.debug(
@@ -141,9 +135,14 @@ class HwndWindow:
141
135
  return win32gui.GetWindowText(self.hwnd)
142
136
  return ""
143
137
 
138
+ def __str__(self) -> str:
139
+ return f"title:{self.title},exe:{self.exe_name},width:{self.width},height:{self.height},exists:{self.exists},visible:{self.visible}"
140
+
144
141
 
145
142
  def find_hwnd_by_title_and_exe(title, exe_name):
146
143
  result = []
144
+ if title is None and exe_name is None:
145
+ return None, None
147
146
 
148
147
  def callback(hwnd, lParam):
149
148
  if len(result) == 0 and win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
@@ -154,42 +153,26 @@ def find_hwnd_by_title_and_exe(title, exe_name):
154
153
  return True
155
154
  elif not re.search(title, text):
156
155
  return True
156
+ name, full_path = get_exe_by_hwnd(hwnd)
157
157
  if exe_name:
158
- name = get_exe_name_by_hwnd(hwnd)
159
158
  if name == exe_name:
160
- result.append(hwnd)
159
+ result.append((hwnd, full_path))
161
160
  return True # Stop enumeration as we found the first match
162
161
  else:
163
- result.append(hwnd)
162
+ result.append((hwnd, full_path))
164
163
  return True
165
164
 
166
165
  win32gui.EnumWindows(callback, None)
167
- return result[0] if result else None
168
-
169
-
170
- OpenProcess = ctypes.windll.kernel32.OpenProcess
171
- CloseHandle = ctypes.windll.kernel32.CloseHandle
172
- GetModuleFileNameExW = ctypes.windll.psapi.GetModuleFileNameExW
166
+ if result:
167
+ return result[0][0], result[0][1]
168
+ else:
169
+ return None, None
173
170
 
174
- PROCESS_QUERY_INFORMATION = 0x0400
175
- PROCESS_VM_READ = 0x0010
176
171
 
177
-
178
- def get_exe_name_by_hwnd(hwnd):
179
- # Get the process ID of the window
172
+ def get_exe_by_hwnd(hwnd):
173
+ # Get the process ID associated with the window
180
174
  _, pid = win32process.GetWindowThreadProcessId(hwnd)
181
175
 
182
- # Open the process
183
- h_process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid)
184
- if h_process:
185
- # Get the executable name
186
- exe_name = ctypes.create_unicode_buffer(1024)
187
- GetModuleFileNameExW(h_process, None, exe_name, 1024)
188
-
189
- # Extract the executable name from the full path
190
- exe_only_name = os.path.basename(exe_name.value)
191
-
192
- # Close the process handle
193
- CloseHandle(h_process)
194
-
195
- return exe_only_name
176
+ # Get the process name and executable path
177
+ process = psutil.Process(pid)
178
+ return process.name(), process.exe()
@@ -19,18 +19,6 @@ class ADBCaptureMethod(BaseCaptureMethod):
19
19
  self._connected = (width != 0 and height != 0)
20
20
  self.device_manager = device_manager
21
21
 
22
- @property
23
- def width(self):
24
- if self._size[0] == 0:
25
- self.screencap()
26
- return self._size[0]
27
-
28
- @property
29
- def height(self):
30
- if self._size[1] == 0:
31
- self.screencap()
32
- return self._size[1]
33
-
34
22
  @override
35
23
  def do_get_frame(self) -> np.ndarray | None:
36
24
  return self.screencap()
@@ -38,11 +26,9 @@ class ADBCaptureMethod(BaseCaptureMethod):
38
26
  def screencap(self):
39
27
  if self.exit_event.is_set():
40
28
  return None
41
- frame = do_screencap(self.device_manager)
29
+ frame = do_screencap(self.device_manager.device)
42
30
  if frame is not None:
43
- height, width, _ = frame.shape
44
31
  self._connected = True
45
- self._size = (width, height)
46
32
  else:
47
33
  self._connected = False
48
34
  return frame
@@ -53,8 +39,10 @@ class ADBCaptureMethod(BaseCaptureMethod):
53
39
  return self._connected and self.device_manager.device is not None
54
40
 
55
41
 
56
- def do_screencap(device_manager) -> np.ndarray | None:
57
- png_bytes = device_manager.shell("screencap -p", encoding=None)
42
+ def do_screencap(device) -> np.ndarray | None:
43
+ if device is None:
44
+ return None
45
+ png_bytes = device.shell("screencap -p", encoding=None)
58
46
  if png_bytes is not None and len(png_bytes) > 0:
59
47
  image_data = np.frombuffer(png_bytes, dtype=np.uint8)
60
48
  image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
@@ -1,13 +1,12 @@
1
1
  import threading
2
2
 
3
- import adbutils
4
3
  import win32gui
5
4
  from adbutils import AdbError
6
5
 
7
6
  from ok.capture.HwndWindow import HwndWindow, find_hwnd_by_title_and_exe
8
7
  from ok.capture.adb.ADBCaptureMethod import ADBCaptureMethod, do_screencap
8
+ from ok.capture.adb.WindowsCaptureFactory import update_capture_method
9
9
  from ok.capture.adb.vbox import installed_emulator
10
- from ok.capture.windows.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod
11
10
  from ok.capture.windows.window import get_window_bounds
12
11
  from ok.config.Config import Config
13
12
  from ok.gui.Communicate import communicate
@@ -20,22 +19,24 @@ logger = get_logger(__name__)
20
19
 
21
20
  class DeviceManager:
22
21
 
23
- def __init__(self, config_folder, hwnd_title=None, exe_name=None, debug=False, exit_event=None):
22
+ def __init__(self, config_folder, windows_capture_config, adb_capture_config, debug=False, exit_event=None):
24
23
  self._device = None
25
24
  self._connect_all = False
26
- self.adb = self.adb = adbutils.AdbClient(host="127.0.0.1")
27
- logger.debug(f'connect adb')
28
- self.hwnd_title = hwnd_title
25
+ self._adb = None
26
+ self.windows_capture_config = windows_capture_config
27
+ self.adb_capture_config = adb_capture_config
29
28
  self.pc_device = None
30
- self.exe_name = exe_name
31
29
  self.debug = debug
32
30
  self.interaction = None
33
31
  self.exit_event = exit_event
34
- if hwnd_title is not None:
35
- self.hwnd = HwndWindow(exit_event, hwnd_title, exe_name)
36
- self.config = Config({"devices": {}, "preferred": "none"}, config_folder, "DeviceManager")
32
+ if windows_capture_config is not None:
33
+ self.hwnd = HwndWindow(exit_event, windows_capture_config.get('title'),
34
+ windows_capture_config.get('exe'))
35
+ self.config = Config({"devices": {}, "preferred": "none", "pc_full_path": None}, config_folder,
36
+ "DeviceManager")
37
37
  self.thread = None
38
38
  self.capture_method = None
39
+ self.update_pc_device()
39
40
  self.start()
40
41
 
41
42
  def refresh(self):
@@ -46,6 +47,8 @@ class DeviceManager:
46
47
  @property
47
48
  def device_dict(self):
48
49
  device_dict = self.config.get("devices")
50
+ if device_dict is None:
51
+ device_dict = {}
49
52
  if self.pc_device is not None:
50
53
  full = {'pc': self.pc_device}
51
54
  full.update(device_dict)
@@ -53,6 +56,14 @@ class DeviceManager:
53
56
  else:
54
57
  return device_dict
55
58
 
59
+ @property
60
+ def adb(self):
61
+ if self._adb is None:
62
+ import adbutils
63
+ self._adb = adbutils.AdbClient(host="127.0.0.1")
64
+ logger.debug(f'connect adb')
65
+ return self._adb
66
+
56
67
  def connect_all(self):
57
68
  if not self._connect_all:
58
69
  for device in self.device_dict.values():
@@ -67,49 +78,62 @@ class DeviceManager:
67
78
  def get_devices(self):
68
79
  return list(self.device_dict.values())
69
80
 
70
- def do_refresh(self):
71
-
72
- if self.hwnd_title:
81
+ def update_pc_device(self):
82
+ if self.windows_capture_config is not None:
73
83
  nick = ""
74
84
  width = 0
75
85
  height = 0
76
- hwnd = find_hwnd_by_title_and_exe(self.hwnd_title, self.exe_name)
86
+ hwnd, full_path = find_hwnd_by_title_and_exe(self.windows_capture_config.get('title'),
87
+ self.windows_capture_config.get('exe'))
77
88
  if hwnd is not None:
78
89
  nick = win32gui.GetWindowText(hwnd)
79
- _, _, _, _, width, height, _ = get_window_bounds(
90
+ _, _, _, _, width, height, _, _, _ = get_window_bounds(
80
91
  hwnd)
81
92
  if not nick:
82
- nick = self.exe_name
93
+ nick = self.windows_capture_config.get('exe')
83
94
  self.pc_device = {"address": "", "imei": 'pc', "device": "windows",
84
95
  "model": "", "nick": nick, "width": width,
85
96
  "height": height,
86
97
  "hwnd": nick, "capture": "windows",
87
- "connected": hwnd is not None
98
+ "connected": hwnd is not None,
99
+ "full_path": full_path or self.config.get('pc_full_path')
88
100
  }
101
+ if full_path and full_path != self.config.get('pc_full_path'):
102
+ self.config['pc_full_path'] = full_path
103
+
89
104
  if width != 0:
90
105
  self.pc_device["resolution"] = f"{width}x{height}"
91
106
  communicate.adb_devices.emit(False)
92
107
 
93
- self.connect_all()
108
+ def do_refresh(self, fast=False):
109
+ self.update_pc_device()
110
+
111
+ self.refresh_adb_device(fast)
112
+
113
+ if self.exit_event.is_set():
114
+ return
115
+ self.start()
116
+ communicate.adb_devices.emit(True)
117
+ logger.debug(f'refresh {self.device_dict}')
94
118
 
95
- installed_emulators = installed_emulator()
119
+ def refresh_adb_device(self, fast):
120
+ if self.adb_capture_config is None:
121
+ return
122
+ if not fast:
123
+ self.connect_all()
124
+ installed_emulators = [] if fast else installed_emulator()
96
125
  for device in self.adb.list():
97
126
  logger.debug(f'adb.list() {device}')
98
-
99
127
  for emulator in installed_emulators:
100
128
  logger.debug(f"installed_emulator: {emulator}")
101
- self.adb.connect(emulator.adb_address)
102
-
129
+ self.adb.connect(emulator.adb_address, timeout=1)
103
130
  device_list = self.adb.device_list()
104
-
105
131
  device_list = sorted(device_list, key=lambda x: x.serial)
106
-
107
132
  adb_connected = []
108
-
109
133
  for device in device_list:
110
134
  imei = device.shell("settings get secure android_id") or device.shell(
111
135
  "service call iphonesubinfo 4") or device.prop.model
112
- frame = do_screencap(device)
136
+ frame = None if fast else do_screencap(device)
113
137
  width, height = 0, 0
114
138
  if frame is not None:
115
139
  height, width, _ = frame.shape
@@ -128,23 +152,27 @@ class DeviceManager:
128
152
  old_hwnd = self.device_dict[imei].get("hwnd")
129
153
  self.device_dict[imei] = adb_device
130
154
  if old_capture is not None:
131
- self.device_dict[imei]['capture'] = old_capture
155
+ self.update_device_value(imei, 'capture', old_capture)
132
156
  if old_hwnd is not None:
133
- self.device_dict[imei]['hwnd'] = old_hwnd
157
+ self.update_device_value(imei, 'hwnd', old_hwnd)
134
158
  else:
135
- self.device_dict[imei] = adb_device
159
+ self.update_device(imei, adb_device)
136
160
  adb_connected.append(imei)
137
161
  for imei in self.device_dict:
138
- if imei not in adb_connected and self.device_dict[imei]['device'] == 'adb':
139
- self.device_dict[imei]['connected'] = False
140
-
162
+ if self.device_dict[imei]['device'] == 'adb':
163
+ self.update_device_value(imei, 'connected', imei in adb_connected)
141
164
  self.config.save_file()
142
165
 
143
- if self.exit_event.is_set():
144
- return
145
- self.start()
146
- communicate.adb_devices.emit(True)
147
- logger.debug(f'refresh {self.device_dict}')
166
+ def update_device(self, imei, device=None):
167
+ devices = self.config.get("devices", {})
168
+ devices[imei] = device
169
+ self.config["devices"] = devices
170
+
171
+ def update_device_value(self, imei, key, value):
172
+ devices = self.config.get("devices", {})
173
+ if imei in devices:
174
+ devices[imei][key] = value
175
+ self.config["devices"] = devices
148
176
 
149
177
  def set_preferred_device(self, imei=None):
150
178
  logger.debug(f"set_preferred_device {imei}")
@@ -191,6 +219,16 @@ class DeviceManager:
191
219
  self.hwnd.update_frame_size(frame_width, frame_height)
192
220
  self.hwnd.update_title_and_exe(title, exe)
193
221
 
222
+ def use_windows_capture(self, override_config=None, require_bg=False):
223
+ if not override_config:
224
+ override_config = self.windows_capture_config
225
+ self.capture_method = update_capture_method(override_config, self.capture_method, self.hwnd,
226
+ require_bg)
227
+ if self.capture_method is None:
228
+ logger.error(f'can find a usable windows capture')
229
+ else:
230
+ logger.info(f'capture method {type(self.capture_method)}')
231
+
194
232
  def start(self):
195
233
  preferred = self.get_preferred_device()
196
234
  if preferred is None:
@@ -199,14 +237,11 @@ class DeviceManager:
199
237
  return
200
238
 
201
239
  if preferred['device'] == 'windows':
202
- self.ensure_hwnd(self.hwnd_title, self.exe_name)
203
- if not isinstance(self.capture_method, WindowsGraphicsCaptureMethod):
204
- if self.capture_method is not None:
205
- self.capture_method.close()
206
- self.capture_method = WindowsGraphicsCaptureMethod(self.hwnd)
240
+ self.ensure_hwnd(self.windows_capture_config.get('title'), self.windows_capture_config.get('exe'))
241
+ self.use_windows_capture()
207
242
  if not isinstance(self.interaction, Win32Interaction):
208
243
  self.interaction = Win32Interaction(self.capture_method)
209
- self.capture_method.hwnd_window = self.hwnd
244
+ preferred['connected'] = self.capture_method.connected()
210
245
  else:
211
246
  self.connect_all()
212
247
  for adb_device in self.adb.device_list():
@@ -218,11 +253,9 @@ class DeviceManager:
218
253
  height = preferred.get('height', 0)
219
254
  if preferred.get('capture') == "windows":
220
255
  self.ensure_hwnd(hwnd_name, None, width, height)
221
- if not isinstance(self.capture_method, WindowsGraphicsCaptureMethod):
222
- if self.capture_method is not None:
223
- self.capture_method.close()
224
- self.capture_method = WindowsGraphicsCaptureMethod(self.hwnd)
225
- self.capture_method.hwnd_window = self.hwnd
256
+ if self.hwnd.exe_full_path:
257
+ self.update_device_value(preferred['imei'], 'full_path', self.hwnd.exe_full_path)
258
+ self.use_windows_capture({'can_bit_blt': True}, require_bg=True)
226
259
  elif not isinstance(self.capture_method, ADBCaptureMethod):
227
260
  if self.capture_method is not None:
228
261
  self.capture_method.close()
@@ -271,3 +304,11 @@ class DeviceManager:
271
304
  return device.shell(*args, **kwargs)
272
305
  else:
273
306
  raise Exception('Device is none')
307
+
308
+ def get_exe_path(self, device):
309
+ path = device.get('full_path')
310
+ if device.get('device') == 'windows' and self.windows_capture_config and self.windows_capture_config.get(
311
+ 'calculate_pc_exe_path'):
312
+ return self.windows_capture_config.get('calculate_pc_exe_path')(path)
313
+ else:
314
+ return path
@@ -0,0 +1,39 @@
1
+ from ok.logging.Logger import get_logger
2
+
3
+ logger = get_logger(__name__)
4
+
5
+
6
+ def update_capture_method(config, capture_method, hwnd, require_bg=False):
7
+ if config.get('can_bit_blt'):
8
+ logger.debug(
9
+ f"try BitBlt method {config} current_type:{type(capture_method)}")
10
+ from ok.capture.windows.BitBltCaptureMethod import BitBltCaptureMethod
11
+ target_method = BitBltCaptureMethod
12
+ capture_method = get_capture(capture_method, target_method, hwnd)
13
+ if capture_method.test_exclusive_full_screen():
14
+ return capture_method
15
+ else:
16
+ logger.info("test_exclusive_full_screen failed, can't use BitBlt")
17
+ from ok.capture.windows.WindowsGraphicsCaptureMethod import windows_graphics_available
18
+ if windows_graphics_available():
19
+ from ok.capture.windows.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod
20
+ target_method = WindowsGraphicsCaptureMethod
21
+ capture_method = get_capture(capture_method, target_method, hwnd)
22
+ return capture_method
23
+
24
+ if not require_bg:
25
+ from ok.capture.windows.DesktopDuplicationCaptureMethod import DesktopDuplicationCaptureMethod
26
+ target_method = DesktopDuplicationCaptureMethod
27
+ capture_method = get_capture(capture_method, target_method, hwnd)
28
+ return capture_method
29
+
30
+ return None
31
+
32
+
33
+ def get_capture(capture_method, target_method, hwnd):
34
+ if not isinstance(capture_method, target_method):
35
+ if capture_method is not None:
36
+ capture_method.close()
37
+ capture_method = target_method(hwnd)
38
+ capture_method.hwnd_window = hwnd
39
+ return capture_method
@@ -3,7 +3,6 @@
3
3
  import winreg
4
4
  from dataclasses import dataclass
5
5
 
6
- import adbutils
7
6
  import win32com.client
8
7
 
9
8
  from ok.capture.adb.targets import ADBControllerTarget
@@ -122,20 +121,3 @@ def installed_emulator():
122
121
  except Exception as e:
123
122
  logger.error(f'failed to check server :{server} {e}', e)
124
123
  return results
125
-
126
-
127
- def _main():
128
- logger.config({'debug': True})
129
- adb = adbutils.AdbClient(host="127.0.0.1", port=5037)
130
- for x in installed_emulator():
131
- logger.debug(f'installed emulators {x}')
132
- adb.connect(x.adb_address)
133
-
134
- logger.debug(adb.device_list())
135
- for device in adb.device_list():
136
- logger.debug(device.shell('settings get secure android_id'))
137
- logger.debug(f'{device.info}')
138
-
139
-
140
- if __name__ == '__main__':
141
- _main()
@@ -0,0 +1,36 @@
1
+ # original https://github.com/dantmnf & https://github.com/hakaboom/winAuto
2
+ import ctypes.wintypes
3
+
4
+ from ok.capture.BaseCaptureMethod import BaseCaptureMethod
5
+ from ok.capture.HwndWindow import HwndWindow
6
+ from ok.logging.Logger import get_logger
7
+
8
+ PBYTE = ctypes.POINTER(ctypes.c_ubyte)
9
+ WGC_NO_BORDER_MIN_BUILD = 20348
10
+
11
+ logger = get_logger(__name__)
12
+
13
+
14
+ class BaseWindowsCaptureMethod(BaseCaptureMethod):
15
+ _hwnd_window: HwndWindow = None
16
+
17
+ def __init__(self, hwnd_window: HwndWindow):
18
+ super().__init__()
19
+ self._hwnd_window = hwnd_window
20
+
21
+ @property
22
+ def hwnd_window(self):
23
+ return self._hwnd_window
24
+
25
+ @hwnd_window.setter
26
+ def hwnd_window(self, hwnd_window):
27
+ self._hwnd_window = hwnd_window
28
+
29
+ def connected(self):
30
+ return self.hwnd_window is not None and self.hwnd_window.exists
31
+
32
+ def get_abs_cords(self, x, y):
33
+ return self.hwnd_window.get_abs_cords(x, y)
34
+
35
+ def clickable(self):
36
+ return self.hwnd_window is not None and self.hwnd_window.visible