ok-script 2.0.7b1__tar.gz → 2.0.8__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 (362) hide show
  1. {ok_script-2.0.7b1/ok_script.egg-info → ok_script-2.0.8}/PKG-INFO +1 -1
  2. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/__init__.py +36 -9
  3. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/start_controller.py +6 -0
  4. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/windows_graphics.py +16 -1
  5. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/task/TaskExecutor.py +13 -0
  6. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/logger.py +3 -0
  7. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/windows_schedule.py +1 -1
  8. {ok_script-2.0.7b1 → ok_script-2.0.8/ok_script.egg-info}/PKG-INFO +1 -1
  9. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok_script.egg-info/SOURCES.txt +3 -1
  10. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_logger.py +21 -0
  11. ok_script-2.0.8/tests/test_overlay_lifecycle.py +105 -0
  12. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_start_controller.py +87 -0
  13. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_task_executor_queue.py +54 -0
  14. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_windows_graphics_capture.py +103 -1
  15. ok_script-2.0.8/tests/test_windows_schedule_xml.py +53 -0
  16. {ok_script-2.0.7b1 → ok_script-2.0.8}/LICENSE.txt +0 -0
  17. {ok_script-2.0.7b1 → ok_script-2.0.8}/MANIFEST.in +0 -0
  18. {ok_script-2.0.7b1 → ok_script-2.0.8}/README.md +0 -0
  19. {ok_script-2.0.7b1 → ok_script-2.0.8}/diagnostics/qq_trace.py +0 -0
  20. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/__main__.py +0 -0
  21. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/alas/__init__.py +0 -0
  22. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/alas/emulator_base.py +0 -0
  23. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/alas/emulator_windows.py +0 -0
  24. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/alas/platform_windows.py +0 -0
  25. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/alas/utils.py +0 -0
  26. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/__init__.py +0 -0
  27. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/adb/__init__.py +0 -0
  28. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/adb/deep.py +0 -0
  29. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/adb/minitouch.py +0 -0
  30. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/adb/nemu_ipc.py +0 -0
  31. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/adb/nemu_utils.py +0 -0
  32. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/adb/pool.py +0 -0
  33. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/adb/timer.py +0 -0
  34. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/adb/util.py +0 -0
  35. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/windows/__init__.py +0 -0
  36. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/windows/d3d11.py +0 -0
  37. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/capture/windows/dump.py +0 -0
  38. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/cli.py +0 -0
  39. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/__init__.py +0 -0
  40. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/config_schema.py +0 -0
  41. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/downloads.py +0 -0
  42. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/events.py +0 -0
  43. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/icons.py +0 -0
  44. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/notifications.py +0 -0
  45. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/screenshot.py +0 -0
  46. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/script_editing.py +0 -0
  47. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/script_packager.py +0 -0
  48. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/script_templates.py +0 -0
  49. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/task_manager.py +0 -0
  50. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/template_store.py +0 -0
  51. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/translation.py +0 -0
  52. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/core/ui_config.py +0 -0
  53. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/DeviceManager.py +0 -0
  54. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/__init__.py +0 -0
  55. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture.py +0 -0
  56. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/__init__.py +0 -0
  57. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/adb.py +0 -0
  58. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/base.py +0 -0
  59. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/bitblt.py +0 -0
  60. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/bitblt_utils.py +0 -0
  61. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/browser.py +0 -0
  62. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/desktop_duplication.py +0 -0
  63. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/hwnd_window.py +0 -0
  64. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/image.py +0 -0
  65. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/nemu_ipc.py +0 -0
  66. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/types.py +0 -0
  67. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/capture_methods/update.py +0 -0
  68. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction.py +0 -0
  69. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/__init__.py +0 -0
  70. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/adb.py +0 -0
  71. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/base.py +0 -0
  72. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/browser.py +0 -0
  73. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/do_nothing.py +0 -0
  74. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/foreground_post_message.py +0 -0
  75. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/genshin.py +0 -0
  76. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/keys.py +0 -0
  77. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/post_message.py +0 -0
  78. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/pydirect.py +0 -0
  79. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/pynput.py +0 -0
  80. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/interaction_methods/swipe.py +0 -0
  81. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/device/intercation.py +0 -0
  82. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/feature/Box.py +0 -0
  83. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/feature/Feature.py +0 -0
  84. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/feature/FeatureSet.py +0 -0
  85. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/feature/__init__.py +0 -0
  86. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/gui/__init__.py +0 -0
  87. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/gui/util/pyappify_startup.py +0 -0
  88. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/notification/__init__.py +0 -0
  89. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/notification/manager.py +0 -0
  90. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/notification/messenger_images.py +0 -0
  91. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/notification/pipeline.py +0 -0
  92. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/notification/ppocr.py +0 -0
  93. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/notification/providers.py +0 -0
  94. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/notification/system.py +0 -0
  95. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/notification/windows_messenger.py +0 -0
  96. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ocr/__init__.py +0 -0
  97. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ocr/download_paddle_model.py +0 -0
  98. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Foundation/Collections.py +0 -0
  99. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Foundation/__init__.py +0 -0
  100. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Globalization/__init__.py +0 -0
  101. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Graphics/Capture/__init__.py +0 -0
  102. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +0 -0
  103. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Graphics/DirectX/__init__.py +0 -0
  104. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Graphics/Imaging/__init__.py +0 -0
  105. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Graphics/__init__.py +0 -0
  106. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Media/Ocr/__init__.py +0 -0
  107. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Media/__init__.py +0 -0
  108. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Security/Cryptography/__init__.py +0 -0
  109. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Security/__init__.py +0 -0
  110. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Storage/Streams/__init__.py +0 -0
  111. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/Storage/__init__.py +0 -0
  112. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/UI/ViewManagement/__init__.py +0 -0
  113. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/UI/__init__.py +0 -0
  114. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/Windows/__init__.py +0 -0
  115. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/__init__.py +0 -0
  116. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/delegate.py +0 -0
  117. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/export.py +0 -0
  118. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/idldsl.py +0 -0
  119. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/inspectable.py +0 -0
  120. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/roapi.py +0 -0
  121. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/types.py +0 -0
  122. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/rotypes/winstring.py +0 -0
  123. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/task/DiagnosisTask.py +0 -0
  124. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/task/__init__.py +0 -0
  125. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/task/exceptions.py +0 -0
  126. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/task/task.py +0 -0
  127. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/task/web.py +0 -0
  128. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/test/OKTestRunner.py +0 -0
  129. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/test/RunTests.py +0 -0
  130. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/test/TaskTestCase.py +0 -0
  131. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/test/__init__.py +0 -0
  132. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/third_party/__init__.py +0 -0
  133. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/third_party/paperclip.py +0 -0
  134. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/__init__.py +0 -0
  135. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/overlay/__init__.py +0 -0
  136. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/overlay/win32_gdi.py +0 -0
  137. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/Communicate.py +0 -0
  138. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/MainWindow.py +0 -0
  139. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/MessageWindow.py +0 -0
  140. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/StartController.py +0 -0
  141. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/TabTitles.py +0 -0
  142. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/__init__.py +0 -0
  143. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/about/AboutTab.py +0 -0
  144. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/about/HttpTextEdit.py +0 -0
  145. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/about/LinksBar.py +0 -0
  146. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/about/ProjectCard.py +0 -0
  147. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/about/UpdateCard.py +0 -0
  148. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/about/VersionCard.py +0 -0
  149. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/about/__init__.py +0 -0
  150. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/common/OKIcon.py +0 -0
  151. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/common/__init__.py +0 -0
  152. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/common/accent_color.py +0 -0
  153. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/common/config.py +0 -0
  154. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/common/design_system.py +0 -0
  155. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/common/icons.py +0 -0
  156. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/common/style_sheet.py +0 -0
  157. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/debug/DebugTab.py +0 -0
  158. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/debug/OverlayWidget.py +0 -0
  159. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/debug/RunCodeTab.py +0 -0
  160. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/debug/Screenshot.py +0 -0
  161. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/debug/__init__.py +0 -0
  162. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/events.py +0 -0
  163. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/GettextTranslator.py +0 -0
  164. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/__init__.py +0 -0
  165. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/en_US.qm +0 -0
  166. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/en_US.ts +0 -0
  167. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/es_ES.qm +0 -0
  168. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/es_ES.ts +0 -0
  169. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/ja_JP.qm +0 -0
  170. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/ja_JP.ts +0 -0
  171. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/ko_KR.qm +0 -0
  172. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/ko_KR.ts +0 -0
  173. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/path.py +0 -0
  174. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/update.py +0 -0
  175. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/zh_CN.qm +0 -0
  176. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/zh_CN.ts +0 -0
  177. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/zh_TW.qm +0 -0
  178. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/i18n/zh_TW.ts +0 -0
  179. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/overlay/OverlayWindow.py +0 -0
  180. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/overlay/__init__.py +0 -0
  181. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/__init__.py +0 -0
  182. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/dark/card.qss +0 -0
  183. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/dark/discord.svg +0 -0
  184. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/dark/heart.svg +0 -0
  185. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/dark/message_window.qss +0 -0
  186. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/dark/setting_interface.qss +0 -0
  187. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/dark/status_bar.qss +0 -0
  188. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/dark/stop.svg +0 -0
  189. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/dark/tab.qss +0 -0
  190. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/light/card.qss +0 -0
  191. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/light/discord.svg +0 -0
  192. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/light/heart.svg +0 -0
  193. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/light/message_window.qss +0 -0
  194. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/light/setting_interface.qss +0 -0
  195. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/light/status_bar.qss +0 -0
  196. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/light/stop.svg +0 -0
  197. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qss/light/tab.qss +0 -0
  198. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/qt.qrc +0 -0
  199. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/resources.py +0 -0
  200. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/settings/GlobalConfigCard.py +0 -0
  201. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/settings/GlobalConfigTab.py +0 -0
  202. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/settings/SettingTab.py +0 -0
  203. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/settings/__init__.py +0 -0
  204. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/start/LogWindow.py +0 -0
  205. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/start/SelectCaptureListView.py +0 -0
  206. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/start/SelectInteractionListView.py +0 -0
  207. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/start/StartCard.py +0 -0
  208. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/start/StartTab.py +0 -0
  209. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/start/__init__.py +0 -0
  210. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/ConfigCard.py +0 -0
  211. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/ConfigItemFactory.py +0 -0
  212. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/ConfigLabelAndWidget.py +0 -0
  213. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/EditTaskTab.py +0 -0
  214. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndButtons.py +0 -0
  215. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndDoubleSpinBox.py +0 -0
  216. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndDropDown.py +0 -0
  217. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndFileSelector.py +0 -0
  218. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndGlobal.py +0 -0
  219. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndLineEdit.py +0 -0
  220. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndList.py +0 -0
  221. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndMultiSelection.py +0 -0
  222. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndSpinBox.py +0 -0
  223. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndSwitchButton.py +0 -0
  224. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndTextEdit.py +0 -0
  225. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/LabelAndWidget.py +0 -0
  226. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/MarkUpWindow.py +0 -0
  227. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/ModifyListDialog.py +0 -0
  228. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/ModifyListItem.py +0 -0
  229. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/OneTimeTaskTab.py +0 -0
  230. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/PythonHighlighter.py +0 -0
  231. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/RecordScript.py +0 -0
  232. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/ScheduleTaskTab.py +0 -0
  233. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/ScriptPackager.py +0 -0
  234. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/StartButton.py +0 -0
  235. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/TaskCard.py +0 -0
  236. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/TaskManger.py +0 -0
  237. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/TaskOpButton.py +0 -0
  238. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/TaskTab.py +0 -0
  239. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/TemplateFactory.py +0 -0
  240. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/TemplateTab.py +0 -0
  241. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/TooltipTableWidget.py +0 -0
  242. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/TriggerTaskTab.py +0 -0
  243. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/__init__.py +0 -0
  244. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/tasks/schedule_index_sync.py +0 -0
  245. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/util/Alert.py +0 -0
  246. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/util/InitWorker.py +0 -0
  247. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/util/__init__.py +0 -0
  248. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/util/app.py +0 -0
  249. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/util/download.py +0 -0
  250. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/util/pip_util.py +0 -0
  251. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/util/pyappify_startup.py +0 -0
  252. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/util/touch_scroll.py +0 -0
  253. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/util/windows_thumbnail.py +0 -0
  254. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/BaseLoading.py +0 -0
  255. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/BaseWindow.py +0 -0
  256. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/Card.py +0 -0
  257. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/CustomTab.py +0 -0
  258. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/EmptyCard.py +0 -0
  259. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/ExpandCardLayout.py +0 -0
  260. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/FlowLayout.py +0 -0
  261. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/FramelessDialog.py +0 -0
  262. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/ImageWidget.py +0 -0
  263. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/ListTableWidgetItem.py +0 -0
  264. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/NumericTableWidgetItem.py +0 -0
  265. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/OkCompactSpinBox.py +0 -0
  266. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/SortingTableWidgetItem.py +0 -0
  267. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/StartLoadingDialog.py +0 -0
  268. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/StatusBar.py +0 -0
  269. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/Tab.py +0 -0
  270. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/TabWidget.py +0 -0
  271. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/UpdateConfigWidgetItem.py +0 -0
  272. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/WidgetWithVLabel.py +0 -0
  273. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/YesNonWidgetItem.py +0 -0
  274. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/qt/widget/__init__.py +0 -0
  275. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/web/__init__.py +0 -0
  276. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/web/app.py +0 -0
  277. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/web/requirements.py +0 -0
  278. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/web/server.py +0 -0
  279. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/web/static/app.js +0 -0
  280. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/web/static/chunk-fluent.js +0 -0
  281. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/web/static/chunk-react.js +0 -0
  282. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/web/static/index.css +0 -0
  283. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/ui/web/static/index.html +0 -0
  284. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/update/__init__.py +0 -0
  285. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/update/copy_ok_folder.py +0 -0
  286. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/update/inline_ok_requirements.py +0 -0
  287. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/Analytics.py +0 -0
  288. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/GlobalConfig.py +0 -0
  289. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/__init__.py +0 -0
  290. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/blur.py +0 -0
  291. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/clazz.py +0 -0
  292. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/collection.py +0 -0
  293. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/color.py +0 -0
  294. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/config.py +0 -0
  295. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/explorer.py +0 -0
  296. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/file.py +0 -0
  297. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/gpu_driver_settings.py +0 -0
  298. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/handler.py +0 -0
  299. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/i18n.py +0 -0
  300. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/partial_sync_repo.py +0 -0
  301. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/print_hwnd.py +0 -0
  302. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/process.py +0 -0
  303. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok/util/window.py +0 -0
  304. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok_script.egg-info/dependency_links.txt +0 -0
  305. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok_script.egg-info/entry_points.txt +0 -0
  306. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok_script.egg-info/not-zip-safe +0 -0
  307. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok_script.egg-info/requires.txt +0 -0
  308. {ok_script-2.0.7b1 → ok_script-2.0.8}/ok_script.egg-info/top_level.txt +0 -0
  309. {ok_script-2.0.7b1 → ok_script-2.0.8}/pyproject.toml +0 -0
  310. {ok_script-2.0.7b1 → ok_script-2.0.8}/scripts/generate_web_i18n.py +0 -0
  311. {ok_script-2.0.7b1 → ok_script-2.0.8}/setup.cfg +0 -0
  312. {ok_script-2.0.7b1 → ok_script-2.0.8}/setup.py +0 -0
  313. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_accent_color.py +0 -0
  314. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_app.py +0 -0
  315. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_base_interaction.py +0 -0
  316. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_blur.py +0 -0
  317. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_box.py +0 -0
  318. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_capture_update.py +0 -0
  319. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_cli.py +0 -0
  320. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_config_card.py +0 -0
  321. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_core_events.py +0 -0
  322. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_core_ui_services.py +0 -0
  323. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_device_manager.py +0 -0
  324. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_expand_card_layout.py +0 -0
  325. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_explorer.py +0 -0
  326. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_global_config.py +0 -0
  327. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_gpu_driver_settings.py +0 -0
  328. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_handler.py +0 -0
  329. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_headless_imports.py +0 -0
  330. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_inline_ok_requirements.py +0 -0
  331. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_main_window_notifications.py +0 -0
  332. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_main_window_startup_tab.py +0 -0
  333. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_main_window_system_theme.py +0 -0
  334. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_missing_frame.py +0 -0
  335. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_navigation_state.py +0 -0
  336. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_nemu_ipc_capture.py +0 -0
  337. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_notifications.py +0 -0
  338. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_ok_mutex.py +0 -0
  339. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_overlay_custom_draw.py +0 -0
  340. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_package_metadata.py +0 -0
  341. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_process.py +0 -0
  342. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_process_mutex.py +0 -0
  343. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_pyappify_startup_update.py +0 -0
  344. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_qt_events.py +0 -0
  345. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_qt_legacy_imports.py +0 -0
  346. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_qt_page_layout.py +0 -0
  347. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_runtime_startup.py +0 -0
  348. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_schedule_startup.py +0 -0
  349. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_schedule_task_index_sync.py +0 -0
  350. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_start_tab_overlay.py +0 -0
  351. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_task_ui.py +0 -0
  352. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_template_tab.py +0 -0
  353. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_touch_scroll.py +0 -0
  354. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_translation_fallback.py +0 -0
  355. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_ui_config.py +0 -0
  356. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_web_app.py +0 -0
  357. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_web_i18n.py +0 -0
  358. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_web_server.py +0 -0
  359. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_web_task_tabs.py +0 -0
  360. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_web_ui_pages.py +0 -0
  361. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_web_ui_tasks.py +0 -0
  362. {ok_script-2.0.7b1 → ok_script-2.0.8}/tests/test_windows_schedule_target_parse.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ok-script
3
- Version: 2.0.7b1
3
+ Version: 2.0.8
4
4
  Summary: Automation with Computer Vision for Python
5
5
  Author-email: ok-oldking <firedcto@gmail.com>
6
6
  License: ok-script License
@@ -231,15 +231,35 @@ def _create_ok_config(config):
231
231
 
232
232
 
233
233
  class _OverlayConfigMixin:
234
+ def _blur_enabled(self):
235
+ executor = getattr(og, 'executor', None)
236
+ return (callable(self.config.get('blur_area'))
237
+ and bool(getattr(executor, 'basic_options', {}).get('Enable Blur', False)))
238
+
239
+ def _overlay_needed(self):
240
+ return self.ok_config.get('use_overlay', False) or self._blur_enabled()
241
+
234
242
  def overlay_state(self):
235
243
  return {'boxes': bool(self.ok_config.get('use_overlay', False))}
236
244
 
237
245
  def initialize_overlay(self):
238
246
  """Apply persisted overlay state independently of the active UI."""
239
- if self.ok_config.get('use_overlay', False):
247
+ if self._overlay_needed():
240
248
  overlay = self.get_overlay_view()
241
249
  if overlay is not None:
242
- overlay.set_boxes_enabled(True)
250
+ overlay.set_boxes_enabled(self.ok_config.get('use_overlay', False))
251
+
252
+ def _on_blur_patches(self, patches):
253
+ # A live setting change can enable blur without a new window geometry event.
254
+ # The new overlay does not receive the signal currently being dispatched.
255
+ if self._blur_enabled() and self.overlay_window is None:
256
+ overlay = self.get_overlay_view()
257
+ if overlay is not None:
258
+ overlay.update_blur_patches(patches)
259
+
260
+ def _on_clear_blur_patches(self):
261
+ if not self._overlay_needed() and self.overlay_window is not None:
262
+ self._close_overlay(wait=False)
243
263
 
244
264
  def _close_overlay(self, wait=True):
245
265
  overlay = self.overlay_window
@@ -269,7 +289,7 @@ class _OverlayConfigMixin:
269
289
  overlay = self.overlay_window
270
290
  if name == 'boxes' and value:
271
291
  overlay = self.get_overlay_view()
272
- elif name == 'boxes' and not value and overlay is not None:
292
+ elif name == 'boxes' and not value and overlay is not None and not self._blur_enabled():
273
293
  self._close_overlay()
274
294
  return self.overlay_state()
275
295
  if overlay is not None:
@@ -301,6 +321,8 @@ class App(_OverlayConfigMixin):
301
321
  task_executor.load_tr()
302
322
  self.ok_config = _create_ok_config(config)
303
323
  communicate.quit.connect(self.quit)
324
+ communicate.blur_overlay.connect(self._on_blur_patches)
325
+ communicate.clear_blur_overlay.connect(self._on_clear_blur_patches)
304
326
 
305
327
  self.about = self.config.get('about')
306
328
  self.title = self.config.get('gui_title')
@@ -341,6 +363,9 @@ class App(_OverlayConfigMixin):
341
363
  logger.debug('init app end')
342
364
 
343
365
  def quit(self):
366
+ from ok.core.events import communicate
367
+ communicate.blur_overlay.disconnect(self._on_blur_patches)
368
+ communicate.clear_blur_overlay.disconnect(self._on_clear_blur_patches)
344
369
  self.exit_event.set()
345
370
  self._close_overlay(wait=False)
346
371
  self._close_device_manager()
@@ -412,10 +437,8 @@ class App(_OverlayConfigMixin):
412
437
  self.show_message_window(title, content)
413
438
 
414
439
  def update_overlay(self, visible, x, y, window_width, window_height, width, height, scaling):
415
- if not self.ok_config.get('use_overlay', False):
416
- # Treat the setting as an absolute lifecycle gate. In particular,
417
- # a configured blur callback must not resurrect a disabled native
418
- # window (and its input/expiry workers).
440
+ if not self._overlay_needed():
441
+ # Keep the native window only while debug boxes or UID blur need it.
419
442
  if self.overlay_window is not None:
420
443
  self._close_overlay(wait=False)
421
444
  return
@@ -427,7 +450,7 @@ class App(_OverlayConfigMixin):
427
450
 
428
451
  def get_overlay_view(self):
429
452
  """Return the overlay widget exposed to tasks, custom tabs, and my_app."""
430
- if not self.ok_config.get('use_overlay', False):
453
+ if not self._overlay_needed():
431
454
  return None
432
455
  if self.overlay_window is None:
433
456
  from ok.core.events import communicate
@@ -515,6 +538,8 @@ class HeadlessApp(_OverlayConfigMixin):
515
538
  self.to_translate = None
516
539
  communicate.quit.connect(self.quit)
517
540
  communicate.adb_devices.connect(self.sync_overlay_source)
541
+ communicate.blur_overlay.connect(self._on_blur_patches)
542
+ communicate.clear_blur_overlay.connect(self._on_clear_blur_patches)
518
543
 
519
544
  self.locale = LocaleName(config.get("locale", "en_US"))
520
545
  from ok.core.start_controller import StartController
@@ -562,6 +587,8 @@ class HeadlessApp(_OverlayConfigMixin):
562
587
  from ok.core.events import communicate
563
588
  communicate.adb_devices.disconnect(self.sync_overlay_source)
564
589
  communicate.notification.disconnect(self.show_notification)
590
+ communicate.blur_overlay.disconnect(self._on_blur_patches)
591
+ communicate.clear_blur_overlay.disconnect(self._on_clear_blur_patches)
565
592
  if self.notification_manager is not None:
566
593
  self.notification_manager.stop()
567
594
  if self.exit_event:
@@ -581,7 +608,7 @@ class HeadlessApp(_OverlayConfigMixin):
581
608
  self.notification_manager.submit(translated_title, translated_message, images)
582
609
 
583
610
  def get_overlay_view(self):
584
- if not self.ok_config.get('use_overlay', False):
611
+ if not self._overlay_needed():
585
612
  return None
586
613
  if self.overlay_window is None:
587
614
  from ok.core.events import communicate
@@ -193,6 +193,12 @@ class StartController:
193
193
  dx11_config = og.global_config.get_config('Launch with DX11')
194
194
  if dx11_config and dx11_config.get('Launch with DX11'):
195
195
  args = "-dx11 -d3d11 -force-d3d11"
196
+ if device['device'] == "windows":
197
+ launch_arguments = (self.config.get('windows') or {}).get('launch_arguments')
198
+ if callable(launch_arguments):
199
+ launch_arguments = launch_arguments()
200
+ if launch_arguments:
201
+ args = f'{args or ""} {launch_arguments}'.strip()
196
202
  if not execute(path, arguments=args, start_method=self.start_method):
197
203
  communicate.starting_emulator.emit(True, self.tr("Start game failed, please start game first"), 0)
198
204
  return False
@@ -157,6 +157,17 @@ class WindowsGraphicsCaptureMethod(BaseWindowsCaptureMethod):
157
157
  return 0
158
158
 
159
159
  def start_or_stop(self, capture_cursor=False):
160
+ # Lifecycle changes may call close(), which needs get_frame_lock.
161
+ # Always acquire it before lock, just as do_get_frame() does, so a
162
+ # concurrent startup refresh cannot deadlock a frame request.
163
+ if not self.get_frame_lock.acquire(timeout=WGC_FRAME_WAIT_TIMEOUT):
164
+ raise TimeoutError('Windows Graphics Capture is busy, please retry starting the game')
165
+ try:
166
+ return self._start_or_stop(capture_cursor)
167
+ finally:
168
+ self.get_frame_lock.release()
169
+
170
+ def _start_or_stop(self, capture_cursor=False):
160
171
  with self.lock:
161
172
  if self.exit_event.is_set():
162
173
  logger.warning('start_or_stop exit_event.is_set() return')
@@ -297,8 +308,12 @@ class WindowsGraphicsCaptureMethod(BaseWindowsCaptureMethod):
297
308
  def do_get_frame(self):
298
309
  # frame_requested and last_frame represent one in-flight request. Keep
299
310
  # concurrent task/UI callers from consuming each other's response.
300
- with self.get_frame_lock:
311
+ if not self.get_frame_lock.acquire(timeout=WGC_FRAME_WAIT_TIMEOUT):
312
+ raise TimeoutError('Windows Graphics Capture is busy, please retry starting the game')
313
+ try:
301
314
  return self._do_get_frame()
315
+ finally:
316
+ self.get_frame_lock.release()
302
317
 
303
318
  def _do_get_frame(self):
304
319
  if self.exit_event.is_set():
@@ -532,6 +532,18 @@ class TaskExecutor:
532
532
  ]
533
533
  return min(delays, default=default)
534
534
 
535
+ def _prepare_task_for_run(self, is_trigger_task):
536
+ """Prepare a task's input backend before the first frame is requested.
537
+
538
+ Foreground interaction backends such as Pynput only report a capture
539
+ frame while the target window is active. One-time tasks used to
540
+ request that frame first and only bring the target window forward from
541
+ inside ``task.run()``, which is too late and can leave the task waiting
542
+ indefinitely.
543
+ """
544
+ if not is_trigger_task and self.interaction is not None:
545
+ self.interaction.on_run()
546
+
535
547
  def execute(self):
536
548
  logger.info(f"start execute")
537
549
  while not self.exit_event.is_set():
@@ -553,6 +565,7 @@ class TaskExecutor:
553
565
  self.current_task = task
554
566
  if not is_trigger_task:
555
567
  communicate.task.emit(task)
568
+ self._prepare_task_for_run(is_trigger_task)
556
569
  if cycled or self._frame is None:
557
570
  if self.next_frame(time_out=4) is None and is_trigger_task:
558
571
  logger.info("no frame available, skip remaining trigger tasks")
@@ -49,6 +49,9 @@ class Logger:
49
49
  stack_trace_str = self.exception_to_str(exception)
50
50
  self.logger.error(f"{self.name}:{message} {stack_trace_str}")
51
51
 
52
+ def exception(self, message, *args, **kwargs):
53
+ self.logger.exception(f"{self.name}:{message}", *args, **kwargs)
54
+
52
55
  def critical(self, message):
53
56
  self.logger.critical(f"{self.name}:{message}")
54
57
 
@@ -1252,7 +1252,7 @@ class WindowsScheduleManager:
1252
1252
  <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
1253
1253
  <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
1254
1254
  <AllowHardTerminate>true</AllowHardTerminate>
1255
- <StartWhenAvailable>false</StartWhenAvailable>
1255
+ <StartWhenAvailable>true</StartWhenAvailable>
1256
1256
  <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
1257
1257
  <AllowStartOnDemand>true</AllowStartOnDemand>
1258
1258
  <Enabled>true</Enabled>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ok-script
3
- Version: 2.0.7b1
3
+ Version: 2.0.8
4
4
  Summary: Automation with Computer Vision for Python
5
5
  Author-email: ok-oldking <firedcto@gmail.com>
6
6
  License: ok-script License
@@ -330,6 +330,7 @@ tests/test_nemu_ipc_capture.py
330
330
  tests/test_notifications.py
331
331
  tests/test_ok_mutex.py
332
332
  tests/test_overlay_custom_draw.py
333
+ tests/test_overlay_lifecycle.py
333
334
  tests/test_package_metadata.py
334
335
  tests/test_process.py
335
336
  tests/test_process_mutex.py
@@ -355,4 +356,5 @@ tests/test_web_task_tabs.py
355
356
  tests/test_web_ui_pages.py
356
357
  tests/test_web_ui_tasks.py
357
358
  tests/test_windows_graphics_capture.py
358
- tests/test_windows_schedule_target_parse.py
359
+ tests/test_windows_schedule_target_parse.py
360
+ tests/test_windows_schedule_xml.py
@@ -1,10 +1,31 @@
1
1
  import logging
2
2
  from unittest.mock import Mock
3
3
 
4
+ import pytest
5
+
4
6
  import ok.util.logger as logger_module
5
7
  from ok.util.logger import SafeFileHandler, config_logger
6
8
 
7
9
 
10
+ @pytest.mark.parametrize("use_exception_message", [False, True])
11
+ def test_logger_exception_preserves_original_traceback(caplog, use_exception_message):
12
+ logger = logger_module.Logger.get_logger("ok.ui.qt.MainWindow")
13
+ logger.logger = logging.getLogger("test.logger_exception")
14
+
15
+ try:
16
+ raise RuntimeError("schedule resolution failed")
17
+ except RuntimeError as error:
18
+ logger.exception(error if use_exception_message else "schedule task index sync failed in __init__")
19
+
20
+ record = caplog.records[-1]
21
+ assert record.levelno == logging.ERROR
22
+ assert record.getMessage().startswith("MainWindow:")
23
+ assert record.exc_info[0] is RuntimeError
24
+ assert str(record.exc_info[1]) == "schedule resolution failed"
25
+ assert "Traceback (most recent call last)" in caplog.text
26
+ assert "RuntimeError: schedule resolution failed" in caplog.text
27
+
28
+
8
29
  def test_config_logger_does_not_create_file_log_during_pytest(tmp_path, monkeypatch):
9
30
  monkeypatch.chdir(tmp_path)
10
31
 
@@ -0,0 +1,105 @@
1
+ from types import SimpleNamespace
2
+ from unittest.mock import Mock
3
+
4
+ import pytest
5
+
6
+ from ok import App, HeadlessApp, og
7
+ from ok.core.events import communicate
8
+
9
+
10
+ @pytest.mark.parametrize("app_type", [App, HeadlessApp])
11
+ def test_blur_initializes_overlay_without_debug_boxes(app_type, monkeypatch):
12
+ monkeypatch.setattr(og, "executor", SimpleNamespace(basic_options={"Enable Blur": True}), raising=False)
13
+ app = object.__new__(app_type)
14
+ app.config = {"blur_area": lambda *_: None}
15
+ app.ok_config = {"use_overlay": False}
16
+ overlay = Mock()
17
+ app.get_overlay_view = Mock(return_value=overlay)
18
+
19
+ app.initialize_overlay()
20
+
21
+ app.get_overlay_view.assert_called_once_with()
22
+ overlay.set_boxes_enabled.assert_called_once_with(False)
23
+
24
+
25
+ @pytest.mark.parametrize("app_type", [App, HeadlessApp])
26
+ def test_blur_only_getter_creates_overlay_with_boxes_disabled(app_type, monkeypatch):
27
+ monkeypatch.setattr(og, "executor", SimpleNamespace(basic_options={"Enable Blur": True}), raising=False)
28
+ monkeypatch.setattr(og, "device_manager", SimpleNamespace(hwnd_window=None), raising=False)
29
+ overlay = Mock()
30
+ monkeypatch.setattr("ok.ui.overlay.Win32GdiOverlay", Mock(return_value=overlay))
31
+ app = object.__new__(app_type)
32
+ app.config = {"blur_area": lambda *_: None}
33
+ app.ok_config = {"use_overlay": False}
34
+ app.overlay_window = None
35
+ app.exit_event = None
36
+
37
+ try:
38
+ assert app.get_overlay_view() is overlay
39
+ overlay.set_boxes_enabled.assert_called_once_with(False)
40
+ finally:
41
+ communicate.window.disconnect(overlay.update_overlay)
42
+
43
+
44
+ @pytest.mark.parametrize("app_type", [App, HeadlessApp])
45
+ def test_blur_event_creates_overlay_after_live_setting_change(app_type, monkeypatch):
46
+ options = {"Enable Blur": False}
47
+ monkeypatch.setattr(og, "executor", SimpleNamespace(basic_options=options), raising=False)
48
+ app = object.__new__(app_type)
49
+ app.config = {"blur_area": lambda *_: None}
50
+ app.ok_config = {"use_overlay": False}
51
+ app.overlay_window = None
52
+ overlay = Mock()
53
+ app.get_overlay_view = Mock(return_value=overlay)
54
+ patches = [object()]
55
+
56
+ app._on_blur_patches(patches)
57
+ app.get_overlay_view.assert_not_called()
58
+ options["Enable Blur"] = True
59
+ app._on_blur_patches(patches)
60
+
61
+ app.get_overlay_view.assert_called_once_with()
62
+ overlay.update_blur_patches.assert_called_once_with(patches)
63
+
64
+
65
+ def test_window_update_keeps_blur_only_overlay(monkeypatch):
66
+ monkeypatch.setattr(og, "executor", SimpleNamespace(basic_options={"Enable Blur": True}), raising=False)
67
+ app = object.__new__(App)
68
+ app.config = {"blur_area": lambda *_: None}
69
+ app.ok_config = {"use_overlay": False}
70
+ overlay = Mock()
71
+ app.overlay_window = overlay
72
+
73
+ app.update_overlay(True, 1, 2, 100, 80, 100, 80, 1)
74
+
75
+ overlay.update_overlay.assert_called_once_with(True, 1, 2, 100, 80, 100, 80, 1)
76
+
77
+
78
+ def test_disabling_debug_boxes_preserves_active_blur(monkeypatch):
79
+ monkeypatch.setattr(og, "executor", SimpleNamespace(basic_options={"Enable Blur": True}), raising=False)
80
+ app = object.__new__(HeadlessApp)
81
+ app.config = {"blur_area": lambda *_: None}
82
+ app.ok_config = {"use_overlay": True}
83
+ overlay = Mock()
84
+ app.overlay_window = overlay
85
+ app._close_overlay = Mock()
86
+
87
+ app.set_overlay_setting("boxes", False)
88
+
89
+ app._close_overlay.assert_not_called()
90
+ overlay.set_boxes_enabled.assert_called_once_with(False)
91
+
92
+
93
+ def test_disabling_last_overlay_feature_releases_window(monkeypatch):
94
+ options = {"Enable Blur": True}
95
+ monkeypatch.setattr(og, "executor", SimpleNamespace(basic_options=options), raising=False)
96
+ app = object.__new__(HeadlessApp)
97
+ app.config = {"blur_area": lambda *_: None}
98
+ app.ok_config = {"use_overlay": False}
99
+ app.overlay_window = Mock()
100
+ app._close_overlay = Mock()
101
+
102
+ options["Enable Blur"] = False
103
+ app._on_clear_blur_patches()
104
+
105
+ app._close_overlay.assert_called_once_with(wait=False)
@@ -38,6 +38,7 @@ class TestStartController(unittest.TestCase):
38
38
  controller = StartController.__new__(StartController)
39
39
  controller.exit_event = threading.Event()
40
40
  controller.start_timeout = 20
41
+ controller.config = {}
41
42
  controller.start_method = 'start'
42
43
  controller.STARTED_WINDOW_STABLE_SECONDS = 2
43
44
  controller.STARTED_WINDOW_POLL_INTERVAL = 1
@@ -61,6 +62,23 @@ class TestStartController(unittest.TestCase):
61
62
 
62
63
  self.assertFalse(controller.starting)
63
64
 
65
+ def test_capture_refresh_timeout_closes_loading_and_reports_error(self):
66
+ controller = self.make_controller()
67
+ controller.start_exe = True
68
+ controller.tr = lambda message: message
69
+ message = 'Windows Graphics Capture is busy, please retry starting the game'
70
+ device_manager = SimpleNamespace(do_refresh=Mock(side_effect=TimeoutError(message)))
71
+ emit = Mock()
72
+ fake_communicate = SimpleNamespace(starting_emulator=SimpleNamespace(emit=emit))
73
+
74
+ with patch.object(start_controller_module, 'og', SimpleNamespace(device_manager=device_manager)), \
75
+ patch.object(start_controller_module, 'communicate', fake_communicate):
76
+ self.assertFalse(controller.do_start())
77
+
78
+ self.assertFalse(controller.starting)
79
+ self.assertEqual((False, None, controller.start_timeout), emit.call_args_list[0].args)
80
+ self.assertEqual((True, message, 0), emit.call_args_list[-1].args)
81
+
64
82
  def test_started_window_must_be_usable_and_stable_before_continuing(self):
65
83
  controller = self.make_controller()
66
84
  window = FakeWindow([(80, 80), (120, 120), (140, 120), (140, 120), (140, 120)])
@@ -122,6 +140,75 @@ class TestStartController(unittest.TestCase):
122
140
  self.assertEqual(['execute', 'stable', 'ready'], call_order)
123
141
  execute.assert_called_once_with(r'C:\game.exe', arguments=None, start_method='os.startfile')
124
142
 
143
+ def make_launch_context(self, device_type='windows', connected=False, dx11=False):
144
+ controller = self.make_controller()
145
+ controller._wait_until_started_window_stable = Mock(return_value=True)
146
+ controller._wait_until_device_ready = Mock(return_value=True)
147
+ device_manager = Mock()
148
+ device_manager.get_preferred_device.return_value = {
149
+ 'connected': connected,
150
+ 'device': device_type,
151
+ }
152
+ device_manager.get_exe_path.return_value = r'C:\game.exe'
153
+ fake_og = SimpleNamespace(device_manager=device_manager, global_config=Mock())
154
+ fake_og.global_config.get_config.return_value = {'Launch with DX11': dx11}
155
+ return controller, fake_og
156
+
157
+ def test_launch_arguments_preserve_dx11_and_launch_method(self):
158
+ for start_method in ('start', 'os.startfile'):
159
+ for dx11 in (False, True):
160
+ for extra_args in ('-custom="value with spaces"', None, '', lambda: '-custom', lambda: None):
161
+ with self.subTest(start_method=start_method, dx11=dx11, extra_args=extra_args):
162
+ controller, fake_og = self.make_launch_context(dx11=dx11)
163
+ controller.start_method = start_method
164
+ controller.config = {'windows': {'launch_arguments': extra_args}}
165
+ expected_args = '-dx11 -d3d11 -force-d3d11' if dx11 else None
166
+ extra = extra_args() if callable(extra_args) else extra_args
167
+ if extra:
168
+ expected_args = f'{expected_args or ""} {extra}'.strip()
169
+
170
+ with patch.object(start_controller_module, 'og', fake_og), \
171
+ patch.object(start_controller_module, 'is_admin', return_value=True), \
172
+ patch.object(start_controller_module, 'execute', return_value=True) as execute:
173
+ self.assertTrue(controller.start_device())
174
+
175
+ execute.assert_called_once_with(
176
+ r'C:\game.exe', arguments=expected_args, start_method=start_method)
177
+
178
+ def test_launch_arguments_callback_reads_current_settings_on_each_launch(self):
179
+ controller, fake_og = self.make_launch_context()
180
+ settings = {'package': 'hd'}
181
+ callback = Mock(side_effect=lambda: f'-krqlv={settings["package"]}')
182
+ controller.config = {'windows': {'launch_arguments': callback}}
183
+
184
+ with patch.object(start_controller_module, 'og', fake_og), \
185
+ patch.object(start_controller_module, 'is_admin', return_value=True), \
186
+ patch.object(start_controller_module, 'execute', return_value=True) as execute:
187
+ self.assertTrue(controller.start_device())
188
+ self.assertEqual('-krqlv=hd', execute.call_args.kwargs['arguments'])
189
+ settings['package'] = 'uhd'
190
+ self.assertTrue(controller.start_device())
191
+ self.assertEqual('-krqlv=uhd', execute.call_args.kwargs['arguments'])
192
+
193
+ self.assertEqual([unittest.mock.call(), unittest.mock.call()], callback.call_args_list)
194
+
195
+ def test_launch_arguments_callback_is_skipped_for_connected_games_and_adb(self):
196
+ for device_type, connected in (('windows', True), ('adb', False)):
197
+ with self.subTest(device_type=device_type, connected=connected):
198
+ controller, fake_og = self.make_launch_context(device_type, connected)
199
+ callback = Mock(return_value='-windows-only')
200
+ controller.config = {'windows': {'launch_arguments': callback}}
201
+
202
+ with patch.object(start_controller_module, 'og', fake_og), \
203
+ patch.object(start_controller_module, 'execute', return_value=True) as execute:
204
+ self.assertTrue(controller.start_device())
205
+
206
+ callback.assert_not_called()
207
+ if connected:
208
+ execute.assert_not_called()
209
+ else:
210
+ execute.assert_called_once_with(r'C:\game.exe', arguments=None, start_method='start')
211
+
125
212
  def test_gpu_driver_warning_identifies_each_enabled_vendor_feature(self):
126
213
  controller = self.make_controller()
127
214
  emit = Mock()
@@ -131,6 +131,60 @@ class TestTaskExecutorQueue(unittest.TestCase):
131
131
  task.on_destroy.assert_called_once_with()
132
132
  interaction.on_destroy.assert_called_once_with()
133
133
 
134
+ def test_prepare_one_time_task_before_first_frame(self):
135
+ executor = self.make_executor([])
136
+ interaction = SimpleNamespace(on_run=Mock(), on_destroy=Mock())
137
+ executor.device_manager = SimpleNamespace(interaction=interaction)
138
+
139
+ executor._prepare_task_for_run(is_trigger_task=False)
140
+
141
+ interaction.on_run.assert_called_once_with()
142
+
143
+ def test_prepare_trigger_task_does_not_force_foreground(self):
144
+ executor = self.make_executor([])
145
+ interaction = SimpleNamespace(on_run=Mock())
146
+ executor.device_manager = SimpleNamespace(interaction=interaction)
147
+
148
+ executor._prepare_task_for_run(is_trigger_task=True)
149
+
150
+ interaction.on_run.assert_not_called()
151
+
152
+ def test_one_time_task_prepares_interaction_before_first_frame(self):
153
+ executor = self.make_executor([])
154
+ executor.paused = False
155
+ executor._frame = None
156
+ executor._last_frame_time = time.time()
157
+ executor.reset_scene = lambda check_enabled=True: None
158
+ interaction = SimpleNamespace(on_run=Mock())
159
+ executor.device_manager = SimpleNamespace(interaction=interaction)
160
+
161
+ task = SimpleNamespace(
162
+ name="FishingOnce",
163
+ start_time=None,
164
+ running=False,
165
+ exit_after_task=False,
166
+ config={},
167
+ run=Mock(),
168
+ disable=Mock(),
169
+ on_destroy=Mock(),
170
+ )
171
+ events = []
172
+ interaction.on_run.side_effect = lambda: events.append("on_run")
173
+ executor.next_frame = Mock(
174
+ side_effect=lambda time_out=6: events.append("next_frame"))
175
+ task.run.side_effect = lambda: (
176
+ events.append("run"),
177
+ executor.exit_event.set(),
178
+ )
179
+ executor.next_task = lambda: (task, False, False)
180
+
181
+ with patch.object(task_executor_module.communicate.task, "emit"), \
182
+ patch.object(task_executor_module.communicate.task_done, "emit"), \
183
+ patch.object(task_executor_module, "prevent_sleeping"):
184
+ executor.execute()
185
+
186
+ self.assertEqual(["on_run", "next_frame", "run"], events)
187
+
134
188
 
135
189
  if __name__ == '__main__':
136
190
  unittest.main()
@@ -2,7 +2,7 @@ import unittest
2
2
  import threading
3
3
  import time
4
4
  from types import SimpleNamespace
5
- from unittest.mock import patch
5
+ from unittest.mock import Mock, patch
6
6
 
7
7
  import numpy as np
8
8
 
@@ -28,6 +28,108 @@ class _FakeFramePool:
28
28
  return self.frame
29
29
 
30
30
 
31
+ class _ObservedRLock:
32
+ def __init__(self):
33
+ self.lock = threading.RLock()
34
+ self.attempted = threading.Event()
35
+
36
+ def acquire(self, **kwargs):
37
+ self.attempted.set()
38
+ return self.lock.acquire(**kwargs)
39
+
40
+ def release(self):
41
+ self.lock.release()
42
+
43
+ def __enter__(self):
44
+ self.acquire()
45
+ return self
46
+
47
+ def __exit__(self, *_args):
48
+ self.release()
49
+
50
+
51
+ class TestWindowsGraphicsCaptureLifecycle(unittest.TestCase):
52
+ def _method(self):
53
+ method = object.__new__(WindowsGraphicsCaptureMethod)
54
+ method.lock = threading.RLock()
55
+ method.get_frame_lock = _ObservedRLock()
56
+ method.exit_event = threading.Event()
57
+ method.frame_requested = threading.Event()
58
+ method.frame_event = threading.Event()
59
+ method._frame_cancel_generation = 0
60
+ method._hwnd_window = SimpleNamespace(exists=True, capture_target_signature='new')
61
+ method.get_capture_hwnd = lambda: 123
62
+ method.capture_target_signature = 'old'
63
+ method.frame_pool = Mock()
64
+ method.session = None
65
+ method.rtdevice = method.dxdevice = method.immediatedc = method.cputex = None
66
+ method.contexts = {}
67
+ method.last_start_failure_key = 123
68
+ method.last_start_failure_time = time.time()
69
+ return method
70
+
71
+ def test_target_change_does_not_hold_capture_lock_while_waiting_for_frame_request(self):
72
+ method = self._method()
73
+ pool = method.frame_pool
74
+ results = []
75
+ errors = []
76
+
77
+ def refresh():
78
+ try:
79
+ results.append(method.start_or_stop())
80
+ except Exception as error:
81
+ errors.append(error)
82
+
83
+ # Simulate a frame consumer already owning the request lock while the
84
+ # startup thread discovers a changed window target.
85
+ with method.get_frame_lock:
86
+ method.get_frame_lock.attempted.clear()
87
+ worker = threading.Thread(target=refresh, daemon=True)
88
+ worker.start()
89
+ self.assertTrue(method.get_frame_lock.attempted.wait(1))
90
+ acquired = method.lock.acquire(timeout=.2)
91
+ if acquired:
92
+ method.lock.release()
93
+ worker.join(1)
94
+
95
+ self.assertTrue(acquired, 'startup held the capture lock while waiting for a frame request')
96
+ self.assertFalse(worker.is_alive())
97
+ self.assertEqual([], errors)
98
+ self.assertEqual([False], results)
99
+ pool.Close.assert_called_once()
100
+ self.assertIsNone(method.frame_pool)
101
+
102
+ def test_busy_capture_reports_error_instead_of_waiting_indefinitely(self):
103
+ for operation in ('start_or_stop', 'do_get_frame'):
104
+ with self.subTest(operation=operation):
105
+ method = self._method()
106
+ errors = []
107
+
108
+ def call():
109
+ try:
110
+ getattr(method, operation)()
111
+ except Exception as error:
112
+ errors.append(error)
113
+
114
+ with method.get_frame_lock, patch.object(windows_graphics_module, 'WGC_FRAME_WAIT_TIMEOUT', .05):
115
+ worker = threading.Thread(target=call, daemon=True)
116
+ worker.start()
117
+ worker.join(.5)
118
+ self.assertFalse(worker.is_alive())
119
+ self.assertEqual(1, len(errors))
120
+ self.assertIsInstance(errors[0], TimeoutError)
121
+ self.assertIn('Windows Graphics Capture is busy', str(errors[0]))
122
+
123
+ def test_frame_request_can_restart_changed_target_without_deadlocking_itself(self):
124
+ method = self._method()
125
+ pool = method.frame_pool
126
+
127
+ self.assertIsNone(method.do_get_frame())
128
+
129
+ pool.Close.assert_called_once()
130
+ self.assertIsNone(method.frame_pool)
131
+
132
+
31
133
  class TestCaptureTargetSignature(unittest.TestCase):
32
134
  def test_capture_origin_uses_fallback_crop_for_embedded_title_bar(self):
33
135
  window = object.__new__(HwndWindow)