ok-script 0.0.8__tar.gz → 0.0.10__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.
- {ok-script-0.0.8 → ok-script-0.0.10}/PKG-INFO +10 -7
- {ok-script-0.0.8 → ok-script-0.0.10}/README.md +9 -6
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/OK.py +13 -6
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/HwndWindow.py +6 -2
- ok-script-0.0.10/ok/capture/adb/ADBCaptureMethod.py +57 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/adb/DeviceManager.py +90 -76
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/windows/WindowsGraphicsCaptureMethod.py +4 -2
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/feature/Box.py +24 -9
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/feature/FeatureSet.py +1 -1
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/App.py +9 -7
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/Communicate.py +6 -2
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/MainWindow.py +5 -1
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/__init__.py +2 -0
- ok-script-0.0.10/ok/gui/about/AboutTab.py +16 -0
- ok-script-0.0.10/ok/gui/debug/DebugTab.py +31 -0
- ok-script-0.0.10/ok/gui/debug/FrameWidget.py +58 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/debug/LoggerWidget.py +1 -0
- ok-script-0.0.10/ok/gui/debug/Screenshot.py +140 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/loading/LoadingWindow.py +46 -11
- ok-script-0.0.10/ok/gui/loading/SelectCaptureListView.py +31 -0
- ok-script-0.0.10/ok/gui/loading/SelectHwndWindow.py +70 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/resources.py +140 -128
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/widget/RoundCornerContainer.py +1 -0
- ok-script-0.0.10/ok/interaction/BaseInteraction.py +24 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/logging/Logger.py +0 -1
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/ocr/OCR.py +2 -2
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/scene/Scene.py +0 -5
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/task/BaseTask.py +8 -3
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/task/ExecutorOperation.py +26 -22
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/task/TaskExecutor.py +19 -14
- ok-script-0.0.10/ok/util/__init__.py +0 -0
- ok-script-0.0.10/ok/util/path.py +77 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok_script.egg-info/PKG-INFO +10 -7
- {ok-script-0.0.8 → ok-script-0.0.10}/ok_script.egg-info/SOURCES.txt +5 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/setup.py +2 -2
- ok-script-0.0.8/ok/capture/adb/ADBCaptureMethod.py +0 -29
- ok-script-0.0.8/ok/gui/debug/DebugTab.py +0 -37
- ok-script-0.0.8/ok/gui/debug/FrameWidget.py +0 -125
- ok-script-0.0.8/ok/interaction/BaseInteraction.py +0 -36
- ok-script-0.0.8/ok/util/path.py +0 -37
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/BaseCaptureMethod.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/adb/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/adb/bin/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/adb/targets.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/adb/vbox.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/windows/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/windows/d3d11.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/windows/utils.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/capture/windows/window.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/color/Color.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/color/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/config/Config.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/config/InfoDict.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/config/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/feature/Feature.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/feature/FindFeature.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/feature/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/TabTitles.py +0 -0
- {ok-script-0.0.8/ok/gui/debug → ok-script-0.0.10/ok/gui/about}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/debug/AspectRatioWidget.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/debug/InfoWidget.py +0 -0
- {ok-script-0.0.8/ok/gui/i18n → ok-script-0.0.10/ok/gui/debug}/__init__.py +0 -0
- {ok-script-0.0.8/ok/gui/icon → ok-script-0.0.10/ok/gui/i18n}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/i18n/path.py +0 -0
- {ok-script-0.0.8/ok/gui/loading → ok-script-0.0.10/ok/gui/icon}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/icon/icon.py +0 -0
- {ok-script-0.0.8/ok/gui/overlay → ok-script-0.0.10/ok/gui/loading}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/overlay/OverlayWindow.py +0 -0
- {ok-script-0.0.8/ok/gui/tasks → ok-script-0.0.10/ok/gui/overlay}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/tasks/ConfigItemFactory.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/tasks/StartButton.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/tasks/TaskOpButton.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/tasks/TaskTab.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/tasks/TooltipTableWidget.py +0 -0
- {ok-script-0.0.8/ok/gui/util → ok-script-0.0.10/ok/gui/tasks}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/util/Alert.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/util/InitWorker.py +0 -0
- {ok-script-0.0.8/ok/gui/widget → ok-script-0.0.10/ok/gui/util}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/widget/ListTableWidgetItem.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/widget/NumericTableWidgetItem.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/widget/TabWidget.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/widget/UpdateConfigWidgetItem.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/gui/widget/YesNonWidgetItem.py +0 -0
- {ok-script-0.0.8/ok/interaction → ok-script-0.0.10/ok/gui/widget}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/interaction/ADBInteraction.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/interaction/Win32Interaction.py +0 -0
- {ok-script-0.0.8/ok/logging → ok-script-0.0.10/ok/interaction}/__init__.py +0 -0
- {ok-script-0.0.8/ok/ocr → ok-script-0.0.10/ok/logging}/__init__.py +0 -0
- {ok-script-0.0.8/ok/predict → ok-script-0.0.10/ok/ocr}/__init__.py +0 -0
- {ok-script-0.0.8/ok/rotypes/Windows/Media → ok-script-0.0.10/ok/predict}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/predict/predict.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Foundation/Collections.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Foundation/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Globalization/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Graphics/Capture/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Graphics/DirectX/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Graphics/Imaging/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Graphics/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Media/Ocr/__init__.py +0 -0
- {ok-script-0.0.8/ok/rotypes/Windows/Security → ok-script-0.0.10/ok/rotypes/Windows/Media}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Security/Cryptography/__init__.py +0 -0
- {ok-script-0.0.8/ok/rotypes/Windows/Storage → ok-script-0.0.10/ok/rotypes/Windows/Security}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/Windows/Storage/Streams/__init__.py +0 -0
- {ok-script-0.0.8/ok/rotypes/Windows → ok-script-0.0.10/ok/rotypes/Windows/Storage}/__init__.py +0 -0
- {ok-script-0.0.8/ok/save → ok-script-0.0.10/ok/rotypes/Windows}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/delegate.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/export.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/idldsl.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/inspectable.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/roapi.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/types.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/rotypes/winstring.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/save/BlackBarProcessor.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/save/PostProcessor.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/save/SaveByInterval.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/save/SaveByKeyPress.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/save/SaveMethodBase.py +0 -0
- {ok-script-0.0.8/ok/scene → ok-script-0.0.10/ok/save}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/scene/FeatureScene.py +0 -0
- {ok-script-0.0.8/ok/stats → ok-script-0.0.10/ok/scene}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/stats/StreamStats.py +0 -0
- {ok-script-0.0.8/ok/task → ok-script-0.0.10/ok/stats}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/task/FindFeatureTask.py +0 -0
- {ok-script-0.0.8/ok/util → ok-script-0.0.10/ok/task}/__init__.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/util/json.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/util/list.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/util/thread.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/util/win32_process.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok/util/yaml.py +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok_script.egg-info/dependency_links.txt +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok_script.egg-info/requires.txt +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/ok_script.egg-info/top_level.txt +0 -0
- {ok-script-0.0.8 → ok-script-0.0.10}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ok-script
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
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
|
|
@@ -18,15 +18,18 @@ Requires-Dist: pywin32==306
|
|
|
18
18
|
Requires-Dist: typing-extensions~=4.10.0
|
|
19
19
|
Requires-Dist: PySide6~=6.6.2
|
|
20
20
|
|
|
21
|
-
#
|
|
21
|
+
# ok-script
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
windows,
|
|
25
|
-
emulator, or physical Android Phone. Currently still working in progress, there is a working
|
|
23
|
+
类似于AirScript, 使用Python
|
|
26
24
|
|
|
27
|
-
##
|
|
25
|
+
## 优势
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
1. 开源免费, 依赖库均为开源方案, 支持pip install任何第三方库
|
|
28
|
+
2. 代码和打包流程完全由开发者控制, 不会上传服务器, 没有安全性隐患, 不容易被侦测和识别
|
|
29
|
+
3. 一套代码即可支持Windows安卓模拟器/ADB连接的虚拟机, Windows客户端游戏, 安卓系统直接运行(开发中)
|
|
30
|
+
4. 支持GUI以及Console运行, 也可以完全自己开发GUI
|
|
31
|
+
5. 支持Python3.7以上版本
|
|
32
|
+
6. 学习成本低, 自定义函数少, 也可以直接使用opencv, RapidOCR等api
|
|
30
33
|
|
|
31
34
|
## Example Usage
|
|
32
35
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ok-script
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
windows,
|
|
5
|
-
emulator, or physical Android Phone. Currently still working in progress, there is a working
|
|
3
|
+
类似于AirScript, 使用Python
|
|
6
4
|
|
|
7
|
-
##
|
|
5
|
+
## 优势
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
1. 开源免费, 依赖库均为开源方案, 支持pip install任何第三方库
|
|
8
|
+
2. 代码和打包流程完全由开发者控制, 不会上传服务器, 没有安全性隐患, 不容易被侦测和识别
|
|
9
|
+
3. 一套代码即可支持Windows安卓模拟器/ADB连接的虚拟机, Windows客户端游戏, 安卓系统直接运行(开发中)
|
|
10
|
+
4. 支持GUI以及Console运行, 也可以完全自己开发GUI
|
|
11
|
+
5. 支持Python3.7以上版本
|
|
12
|
+
6. 学习成本低, 自定义函数少, 也可以直接使用opencv, RapidOCR等api
|
|
10
13
|
|
|
11
14
|
## Example Usage
|
|
12
15
|
|
|
@@ -9,6 +9,7 @@ import ok
|
|
|
9
9
|
from ok.feature.FeatureSet import FeatureSet
|
|
10
10
|
from ok.gui.App import App
|
|
11
11
|
from ok.gui.Communicate import communicate
|
|
12
|
+
from ok.gui.debug.Screenshot import Screenshot
|
|
12
13
|
from ok.gui.overlay.OverlayWindow import OverlayWindow
|
|
13
14
|
from ok.gui.util.InitWorker import InitWorker
|
|
14
15
|
from ok.logging.Logger import get_logger, config_logger
|
|
@@ -27,27 +28,33 @@ class OK:
|
|
|
27
28
|
ocr = None
|
|
28
29
|
overlay_window = None
|
|
29
30
|
app = None
|
|
31
|
+
screenshot = None
|
|
30
32
|
|
|
31
33
|
def __init__(self, config: Dict[str, Any]):
|
|
32
34
|
print(f"AutoHelper init, config: {config}")
|
|
35
|
+
ok.gui.ok = self
|
|
33
36
|
self.debug = config.get("debug", False)
|
|
34
37
|
self.exit_event = threading.Event()
|
|
35
38
|
self.config = config
|
|
36
39
|
self.init_device_manager()
|
|
40
|
+
self.screenshot = Screenshot()
|
|
37
41
|
if config.get("use_gui"):
|
|
38
|
-
self.app = App(
|
|
39
|
-
self.config['tasks'], self.exit_event)
|
|
42
|
+
self.app = App(config, self.exit_event)
|
|
40
43
|
ok.gui.app = self.app
|
|
44
|
+
else:
|
|
45
|
+
self.device_manager.set_preferred_device()
|
|
46
|
+
self.device_manager.start()
|
|
47
|
+
self.do_init()
|
|
48
|
+
|
|
49
|
+
def start(self):
|
|
50
|
+
if self.config.get("use_gui"):
|
|
41
51
|
self.app.show_loading()
|
|
42
52
|
self.worker = InitWorker(self.do_init)
|
|
43
53
|
self.worker.start()
|
|
44
54
|
self.app.exec()
|
|
45
55
|
else:
|
|
46
|
-
self.device_manager.set_preferred_device()
|
|
47
|
-
self.device_manager.start()
|
|
48
|
-
self.do_init()
|
|
49
56
|
self.task_executor.start()
|
|
50
|
-
if config.get("debug"):
|
|
57
|
+
if self.config.get("debug"):
|
|
51
58
|
self.app = QApplication(sys.argv)
|
|
52
59
|
self.overlay_window = OverlayWindow(ok.gui.device_manager.hwnd)
|
|
53
60
|
self.app.exec()
|
|
@@ -54,6 +54,7 @@ class HwndWindow:
|
|
|
54
54
|
self.title = re.compile(title)
|
|
55
55
|
|
|
56
56
|
def update_frame_size(self, width, height):
|
|
57
|
+
logger.debug(f"update_frame_size:{self.frame_width}x{self.frame_height} to {width}x{height}")
|
|
57
58
|
if width != self.frame_width or height != self.frame_height:
|
|
58
59
|
self.frame_width = width
|
|
59
60
|
self.frame_height = height
|
|
@@ -127,8 +128,11 @@ def find_hwnds_by_title(title):
|
|
|
127
128
|
hwnds = []
|
|
128
129
|
|
|
129
130
|
def enum_windows_proc(hwnd, lParam):
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
text = win32gui.GetWindowText(hwnd)
|
|
132
|
+
if text and win32gui.IsWindowVisible(hwnd):
|
|
133
|
+
if isinstance(title, str) and title == text:
|
|
134
|
+
hwnds.append(hwnd)
|
|
135
|
+
elif re.search(title, text):
|
|
132
136
|
hwnds.append(hwnd)
|
|
133
137
|
|
|
134
138
|
win32gui.EnumWindows(enum_windows_proc, None)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# original https://github.com/Toufool/AutoSplit/blob/master/src/capture_method/WindowsGraphicsCaptureMethod.py
|
|
2
|
+
import cv2
|
|
3
|
+
import numpy as np
|
|
4
|
+
from typing_extensions import override
|
|
5
|
+
|
|
6
|
+
from ok.capture.BaseCaptureMethod import BaseCaptureMethod
|
|
7
|
+
from ok.logging.Logger import get_logger
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ADBCaptureMethod(BaseCaptureMethod):
|
|
13
|
+
name = "ADB command line Capture"
|
|
14
|
+
description = "use the adb screencap command, slow but works when in background/minimized, takes 300ms per frame"
|
|
15
|
+
|
|
16
|
+
def __init__(self, device, exit_event, width=0, height=0):
|
|
17
|
+
super().__init__()
|
|
18
|
+
self.exit_event = exit_event
|
|
19
|
+
self._size = (width, height)
|
|
20
|
+
self.device = device
|
|
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
|
+
@override
|
|
35
|
+
def get_frame(self) -> np.ndarray | None:
|
|
36
|
+
return self.screencap()
|
|
37
|
+
|
|
38
|
+
def screencap(self):
|
|
39
|
+
if self.exit_event.is_set():
|
|
40
|
+
return None
|
|
41
|
+
frame = do_screencap(self.device)
|
|
42
|
+
if frame is not None:
|
|
43
|
+
height, width, _ = frame.shape
|
|
44
|
+
self._size = (width, height)
|
|
45
|
+
return frame
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def do_screencap(device) -> np.ndarray | None:
|
|
49
|
+
if device is not None:
|
|
50
|
+
png_bytes = device.shell("screencap -p", encoding=None)
|
|
51
|
+
if png_bytes is not None and len(png_bytes) > 0:
|
|
52
|
+
image_data = np.frombuffer(png_bytes, dtype=np.uint8)
|
|
53
|
+
image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
|
|
54
|
+
if image is not None:
|
|
55
|
+
return image
|
|
56
|
+
else:
|
|
57
|
+
logger.error(f"Screencap image decode error, probably disconnected")
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import threading
|
|
2
2
|
|
|
3
3
|
import adbutils
|
|
4
|
-
import cv2
|
|
5
|
-
import numpy as np
|
|
6
4
|
|
|
7
5
|
from ok.capture.HwndWindow import HwndWindow
|
|
8
|
-
from ok.capture.adb.ADBCaptureMethod import ADBCaptureMethod
|
|
6
|
+
from ok.capture.adb.ADBCaptureMethod import ADBCaptureMethod, do_screencap
|
|
9
7
|
from ok.capture.adb.vbox import installed_emulator
|
|
10
8
|
from ok.capture.windows.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod
|
|
11
9
|
from ok.config.Config import Config
|
|
@@ -29,7 +27,6 @@ class DeviceManager:
|
|
|
29
27
|
if hwnd_title is not None:
|
|
30
28
|
self.hwnd = HwndWindow(hwnd_title, exit_event)
|
|
31
29
|
self.config = Config({"devices": {}}, config_folder, "DeviceManager")
|
|
32
|
-
self._size = (0, 0)
|
|
33
30
|
self.thread = None
|
|
34
31
|
self.capture_method = None
|
|
35
32
|
self.refresh()
|
|
@@ -68,8 +65,7 @@ class DeviceManager:
|
|
|
68
65
|
|
|
69
66
|
device_list = sorted(device_list, key=lambda x: x.serial)
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
device['connected'] = False
|
|
68
|
+
adb_connected = []
|
|
73
69
|
|
|
74
70
|
for device in device_list:
|
|
75
71
|
imei = device.shell("settings get secure android_id") or device.shell(
|
|
@@ -78,7 +74,8 @@ class DeviceManager:
|
|
|
78
74
|
width, height = 0, 0
|
|
79
75
|
if frame is not None:
|
|
80
76
|
height, width, _ = frame.shape
|
|
81
|
-
adb_device = {"address": device.serial, "imei": imei, "
|
|
77
|
+
adb_device = {"address": device.serial, "imei": imei, "device": "adb", "capture": "adb", "width": width,
|
|
78
|
+
"height": height,
|
|
82
79
|
"model": device.prop.model, "nick": device.prop.model, "connected": True, "preferred": False,
|
|
83
80
|
"resolution": f"{width}x{height}"}
|
|
84
81
|
found = False
|
|
@@ -88,21 +85,32 @@ class DeviceManager:
|
|
|
88
85
|
found = True
|
|
89
86
|
if self.device_dict.get(imei):
|
|
90
87
|
if found or not self.device_dict[imei]['connected']:
|
|
88
|
+
old_capture = self.device_dict[imei].get("capture")
|
|
89
|
+
old_hwnd = self.device_dict[imei].get("hwnd")
|
|
91
90
|
self.device_dict[imei] = adb_device
|
|
91
|
+
if old_capture is not None:
|
|
92
|
+
self.device_dict[imei]['capture'] = old_capture
|
|
93
|
+
if old_hwnd is not None:
|
|
94
|
+
self.device_dict[imei]['hwnd'] = old_hwnd
|
|
92
95
|
else:
|
|
93
96
|
self.device_dict[imei] = adb_device
|
|
97
|
+
adb_connected.append(imei)
|
|
98
|
+
for imei in self.device_dict:
|
|
99
|
+
if imei not in adb_connected:
|
|
100
|
+
self.device_dict[imei]['connected'] = False
|
|
94
101
|
if self.hwnd_title:
|
|
95
|
-
nick =
|
|
102
|
+
nick = ""
|
|
96
103
|
width = 0
|
|
97
104
|
height = 0
|
|
98
105
|
if self.hwnd:
|
|
99
106
|
nick = self.hwnd.title_text()
|
|
100
107
|
width = self.hwnd.width
|
|
101
108
|
height = self.hwnd.height
|
|
102
|
-
self.device_dict[self.hwnd_title] = {"address": "", "imei": self.hwnd_title, "
|
|
103
|
-
"model": "", "nick": nick or self.hwnd_title,
|
|
109
|
+
self.device_dict[self.hwnd_title] = {"address": "", "imei": self.hwnd_title, "device": "windows",
|
|
110
|
+
"model": "", "nick": nick or self.hwnd_title, "width": width,
|
|
111
|
+
"height": height,
|
|
112
|
+
"hwnd": nick, "capture": "windows",
|
|
104
113
|
"connected": self.hwnd is not None and self.hwnd.exists,
|
|
105
|
-
"preferred": False,
|
|
106
114
|
"resolution": f"{width}x{height}"}
|
|
107
115
|
communicate.adb_devices.emit()
|
|
108
116
|
self.config.save_file()
|
|
@@ -110,70 +118,98 @@ class DeviceManager:
|
|
|
110
118
|
|
|
111
119
|
def set_preferred_device(self, imei=None):
|
|
112
120
|
logger.debug(f"set_preferred_device {imei}")
|
|
113
|
-
preferred = None
|
|
114
121
|
if imei is None:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
preferred = device
|
|
118
|
-
imei = preferred['imei']
|
|
119
|
-
else:
|
|
120
|
-
preferred = self.device_dict.get(imei)
|
|
121
|
-
for key in self.device_dict.keys():
|
|
122
|
-
if imei == key:
|
|
123
|
-
self.device_dict[imei]["preferred"] = False
|
|
122
|
+
imei = self.config.get("preferred")
|
|
123
|
+
preferred = self.device_dict.get(imei)
|
|
124
124
|
if preferred is None and len(self.device_dict) > 0:
|
|
125
125
|
preferred = next(iter(self.device_dict.values()))
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
imei = preferred['imei']
|
|
127
|
+
if self.config.get("preferred") != imei:
|
|
128
|
+
self.config["preferred"] = imei
|
|
129
|
+
self.config.save_file()
|
|
130
|
+
logger.debug(f'preferred device: {preferred}')
|
|
131
|
+
|
|
132
|
+
def get_preferred_device(self):
|
|
133
|
+
imei = self.config.get("preferred")
|
|
134
|
+
preferred = self.device_dict.get(imei)
|
|
135
|
+
if preferred is None:
|
|
136
|
+
raise ValueError(f"preferred device {imei} is none")
|
|
137
|
+
return preferred
|
|
138
|
+
|
|
139
|
+
def set_hwnd_name(self, hwnd_name):
|
|
140
|
+
preferred = self.get_preferred_device()
|
|
141
|
+
if preferred.get("hwnd") != hwnd_name:
|
|
142
|
+
preferred['hwnd'] = hwnd_name
|
|
143
|
+
self.config.save_file()
|
|
144
|
+
|
|
145
|
+
def set_capture(self, capture):
|
|
146
|
+
preferred = self.get_preferred_device()
|
|
147
|
+
preferred['capture'] = capture
|
|
148
|
+
self.config.save_file()
|
|
149
|
+
|
|
150
|
+
def get_hwnd_name(self):
|
|
151
|
+
preferred = self.get_preferred_device()
|
|
152
|
+
return preferred.get('hwnd')
|
|
153
|
+
|
|
154
|
+
def start(self):
|
|
155
|
+
preferred = self.get_preferred_device()
|
|
156
|
+
if preferred['device'] == 'windows':
|
|
157
|
+
if not isinstance(self.capture_method, WindowsGraphicsCaptureMethod):
|
|
158
|
+
if self.capture_method is not None:
|
|
159
|
+
self.capture_method.close()
|
|
160
|
+
self.capture_method = WindowsGraphicsCaptureMethod(self.hwnd)
|
|
161
|
+
self.interaction = Win32Interaction(self.capture_method)
|
|
162
|
+
self.capture_method.hwnd_window = self.hwnd
|
|
163
|
+
else:
|
|
164
|
+
for adb_device in self.adb.device_list():
|
|
165
|
+
if adb_device.serial == preferred.get('address'):
|
|
166
|
+
self._device = adb_device
|
|
167
|
+
hwnd_name = preferred.get('hwnd')
|
|
168
|
+
width = preferred.get('width', 0)
|
|
169
|
+
height = preferred.get('height', 0)
|
|
170
|
+
if preferred.get('capture') == "windows":
|
|
171
|
+
if not hwnd_name:
|
|
172
|
+
raise ValueError(f"adb device preferred hwnd is none, capture is windows {preferred}")
|
|
173
|
+
if self.hwnd is None:
|
|
174
|
+
self.hwnd = HwndWindow(hwnd_name, self.exit_event, width, height)
|
|
129
175
|
if not isinstance(self.capture_method, WindowsGraphicsCaptureMethod):
|
|
130
176
|
if self.capture_method is not None:
|
|
131
177
|
self.capture_method.close()
|
|
132
178
|
self.capture_method = WindowsGraphicsCaptureMethod(self.hwnd)
|
|
133
|
-
|
|
179
|
+
self.hwnd.title = hwnd_name
|
|
134
180
|
self.capture_method.hwnd_window = self.hwnd
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
self.capture_method.close()
|
|
142
|
-
self.capture_method = ADBCaptureMethod(self)
|
|
143
|
-
self.interaction = ADBBaseInteraction(self, self.capture_method)
|
|
144
|
-
self.config.save_file()
|
|
145
|
-
logger.debug(f'preferred device: {preferred}')
|
|
146
|
-
|
|
147
|
-
def start(self):
|
|
148
|
-
if isinstance(self.capture_method, ADBCaptureMethod):
|
|
181
|
+
elif not isinstance(self.capture_method, ADBCaptureMethod):
|
|
182
|
+
if self.capture_method is not None:
|
|
183
|
+
self.capture_method.close()
|
|
184
|
+
self.capture_method = ADBCaptureMethod(self._device, self.exit_event, width=width,
|
|
185
|
+
height=height)
|
|
186
|
+
self.interaction = ADBBaseInteraction(self, self.capture_method)
|
|
149
187
|
if self.debug:
|
|
150
|
-
if self.hwnd is
|
|
151
|
-
self.hwnd
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
self.capture_method.height)
|
|
156
|
-
else:
|
|
188
|
+
if self.hwnd is None and hwnd_name:
|
|
189
|
+
if self.hwnd is None:
|
|
190
|
+
self.hwnd = HwndWindow(hwnd_name, self.exit_event, width, height)
|
|
191
|
+
self.hwnd.title = hwnd_name
|
|
192
|
+
elif self.hwnd is not None:
|
|
157
193
|
self.hwnd.stop()
|
|
158
194
|
self.hwnd = None
|
|
195
|
+
if self.hwnd:
|
|
196
|
+
self.hwnd.update_frame_size(width, height)
|
|
159
197
|
|
|
160
198
|
@property
|
|
161
199
|
def device(self):
|
|
162
|
-
if self._device is None:
|
|
163
|
-
self.set_preferred_device()
|
|
164
200
|
return self._device
|
|
165
201
|
|
|
166
202
|
@property
|
|
167
203
|
def width(self):
|
|
168
|
-
if self.
|
|
169
|
-
self.
|
|
170
|
-
return
|
|
204
|
+
if self.capture_method is not None:
|
|
205
|
+
return self.capture_method.width
|
|
206
|
+
return 0
|
|
171
207
|
|
|
172
208
|
@property
|
|
173
209
|
def height(self):
|
|
174
|
-
if self.
|
|
175
|
-
self.
|
|
176
|
-
return
|
|
210
|
+
if self.capture_method is not None:
|
|
211
|
+
return self.capture_method.height
|
|
212
|
+
return 0
|
|
177
213
|
|
|
178
214
|
def update_device_list(self):
|
|
179
215
|
pass
|
|
@@ -185,25 +221,3 @@ class DeviceManager:
|
|
|
185
221
|
return device.shell(*args, **kwargs)
|
|
186
222
|
except Exception as e:
|
|
187
223
|
logger.error(f"shell_wrapper error occurred: {e}")
|
|
188
|
-
|
|
189
|
-
def screencap(self):
|
|
190
|
-
if self.exit_event.is_set():
|
|
191
|
-
return None
|
|
192
|
-
self._size = (0, 0)
|
|
193
|
-
frame = do_screencap(self.device)
|
|
194
|
-
if frame is not None:
|
|
195
|
-
height, width, _ = frame.shape
|
|
196
|
-
self._size = (width, height)
|
|
197
|
-
return frame
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def do_screencap(device) -> np.ndarray | None:
|
|
201
|
-
if device is not None:
|
|
202
|
-
png_bytes = device.shell("screencap -p", encoding=None)
|
|
203
|
-
if png_bytes is not None and len(png_bytes) > 0:
|
|
204
|
-
image_data = np.frombuffer(png_bytes, dtype=np.uint8)
|
|
205
|
-
image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
|
|
206
|
-
if image is not None:
|
|
207
|
-
return image
|
|
208
|
-
else:
|
|
209
|
-
logger.error(f"Screencap image decode error, probably disconnected")
|
|
@@ -162,8 +162,10 @@ class WindowsGraphicsCaptureMethod(BaseCaptureMethod):
|
|
|
162
162
|
self.session.Close() # E_UNEXPECTED ???
|
|
163
163
|
self.session = None
|
|
164
164
|
self.item = None
|
|
165
|
-
self.rtdevice
|
|
166
|
-
|
|
165
|
+
if self.rtdevice:
|
|
166
|
+
self.rtdevice.Release()
|
|
167
|
+
if self.dxdevice:
|
|
168
|
+
self.dxdevice.Release()
|
|
167
169
|
if self.cputex:
|
|
168
170
|
self.cputex.Release()
|
|
169
171
|
|
|
@@ -6,10 +6,10 @@ import re
|
|
|
6
6
|
class Box:
|
|
7
7
|
def __init__(self, x: int, y: int, width: int, height: int, confidence: float = 1, name=None) -> None:
|
|
8
8
|
self.name = name
|
|
9
|
-
self.x = x
|
|
10
|
-
self.y = y
|
|
11
|
-
self.width = width
|
|
12
|
-
self.height = height
|
|
9
|
+
self.x = int(x)
|
|
10
|
+
self.y = int(y)
|
|
11
|
+
self.width = int(width)
|
|
12
|
+
self.height = int(height)
|
|
13
13
|
self.confidence = confidence
|
|
14
14
|
|
|
15
15
|
def __eq__(self, other):
|
|
@@ -24,6 +24,14 @@ class Box:
|
|
|
24
24
|
self.confidence == other.confidence and
|
|
25
25
|
self.name == other.name)
|
|
26
26
|
|
|
27
|
+
def in_boundary(self, boxes):
|
|
28
|
+
in_boundary_boxes = []
|
|
29
|
+
for box in boxes:
|
|
30
|
+
if (self.x <= box.x and self.x + self.width >= box.x + box.width and
|
|
31
|
+
self.y <= box.y and self.y + self.height >= box.y + box.height):
|
|
32
|
+
in_boundary_boxes.append(box)
|
|
33
|
+
return in_boundary_boxes
|
|
34
|
+
|
|
27
35
|
def __repr__(self):
|
|
28
36
|
return self.name
|
|
29
37
|
|
|
@@ -80,7 +88,7 @@ class Box:
|
|
|
80
88
|
def center(self):
|
|
81
89
|
return self.x + self.width / 2, self.y + self.height / 2
|
|
82
90
|
|
|
83
|
-
def find_closest_box(self, direction: str, boxes: list):
|
|
91
|
+
def find_closest_box(self, direction: str, boxes: list, condition=None):
|
|
84
92
|
orig_center_x, orig_center_y = self.center()
|
|
85
93
|
|
|
86
94
|
def distance_criteria(box):
|
|
@@ -91,14 +99,21 @@ class Box:
|
|
|
91
99
|
dy = box_center_y - orig_center_y
|
|
92
100
|
distance = math.sqrt(dx ** 2 + dy ** 2)
|
|
93
101
|
if box == self:
|
|
94
|
-
|
|
102
|
+
distance = float('inf')
|
|
95
103
|
elif direction == 'up' and self.y - (box.y + box.height / 2) >= 0:
|
|
96
|
-
|
|
104
|
+
pass
|
|
97
105
|
elif direction == 'down' and box.y - (self.y + self.height / 2) >= 0:
|
|
98
|
-
|
|
106
|
+
pass
|
|
99
107
|
elif direction == 'left' and self.x - (box.x + box.width / 2) >= 0:
|
|
100
|
-
|
|
108
|
+
pass
|
|
101
109
|
elif direction == 'right' and box.x - (self.x + self.width / 2) >= 0:
|
|
110
|
+
pass
|
|
111
|
+
else:
|
|
112
|
+
distance = float('inf')
|
|
113
|
+
return check_condition(box, distance)
|
|
114
|
+
|
|
115
|
+
def check_condition(box, distance):
|
|
116
|
+
if condition is None or condition(box):
|
|
102
117
|
return distance
|
|
103
118
|
else:
|
|
104
119
|
return float('inf')
|
|
@@ -18,19 +18,21 @@ logger = get_logger(__name__)
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class App:
|
|
21
|
-
def __init__(self,
|
|
21
|
+
def __init__(self, config,
|
|
22
22
|
exit_event=None):
|
|
23
23
|
super().__init__()
|
|
24
|
+
self.config = config
|
|
24
25
|
self.app = QApplication(sys.argv)
|
|
25
26
|
self.app.setStyle(QStyleFactory.create("Fusion"))
|
|
26
|
-
self.tasks = tasks
|
|
27
|
-
self.
|
|
28
|
-
self.
|
|
27
|
+
self.tasks = self.config['tasks']
|
|
28
|
+
self.about = self.config.get('about')
|
|
29
|
+
self.title = self.config.get('gui_title')
|
|
30
|
+
self.overlay = self.config.get('debug')
|
|
29
31
|
self.loading_window = None
|
|
30
32
|
self.overlay_window = None
|
|
31
33
|
self.main_window = None
|
|
32
34
|
self.exit_event = exit_event
|
|
33
|
-
self.icon = QIcon(
|
|
35
|
+
self.icon = QIcon(self.config.get('gui_icon') or ":/icon/icon.ico")
|
|
34
36
|
self.tray = QSystemTrayIcon(self.icon)
|
|
35
37
|
|
|
36
38
|
locale = QLocale.system().name()
|
|
@@ -63,7 +65,7 @@ class App:
|
|
|
63
65
|
|
|
64
66
|
def show_loading(self):
|
|
65
67
|
self.loading_window = LoadingWindow(self, self.exit_event)
|
|
66
|
-
size = self.size_relative_to_screen(width=0.
|
|
68
|
+
size = self.size_relative_to_screen(width=0.6, height=0.4)
|
|
67
69
|
self.loading_window.resize(size)
|
|
68
70
|
self.loading_window.setMinimumSize(size)
|
|
69
71
|
self.center_window(self.loading_window)
|
|
@@ -86,7 +88,7 @@ class App:
|
|
|
86
88
|
|
|
87
89
|
def show_main_window(self):
|
|
88
90
|
self.loading_window.close()
|
|
89
|
-
self.main_window = MainWindow(self.tasks, self.overlay, exit_event=self.exit_event)
|
|
91
|
+
self.main_window = MainWindow(self.tasks, self.overlay, self.about, exit_event=self.exit_event)
|
|
90
92
|
self.main_window.setWindowTitle(self.title) # Set the window title here
|
|
91
93
|
self.main_window.setWindowIcon(self.icon)
|
|
92
94
|
if self.overlay and ok.gui.device_manager.hwnd is not None:
|
|
@@ -3,12 +3,11 @@ from PySide6.QtCore import Signal, QObject
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Communicate(QObject):
|
|
6
|
-
frame = Signal(np.ndarray)
|
|
7
6
|
log = Signal(int, str)
|
|
8
7
|
fps = Signal(int)
|
|
9
8
|
frame_time = Signal(int)
|
|
10
9
|
scene = Signal(str)
|
|
11
|
-
draw_box = Signal(str, object, str)
|
|
10
|
+
draw_box = Signal(str, object, str, np.ndarray)
|
|
12
11
|
tasks = Signal()
|
|
13
12
|
task_info = Signal()
|
|
14
13
|
window = Signal(bool, int, int, int, int, int, int, float)
|
|
@@ -16,7 +15,12 @@ class Communicate(QObject):
|
|
|
16
15
|
init = Signal(bool, str)
|
|
17
16
|
notification = Signal(str, str)
|
|
18
17
|
executor_paused: Signal = Signal(bool)
|
|
18
|
+
screenshot = Signal(np.ndarray, str)
|
|
19
|
+
update_overlay = Signal()
|
|
19
20
|
adb_devices: Signal = Signal()
|
|
20
21
|
|
|
22
|
+
def emit_draw_box(self, key: str = None, boxes=None, color=None, frame=None):
|
|
23
|
+
self.draw_box.emit(key, boxes, color, frame)
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
communicate = Communicate()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from PySide6.QtCore import QObject, Signal, Slot
|
|
2
2
|
from PySide6.QtWidgets import QMessageBox, QTabWidget
|
|
3
3
|
|
|
4
|
+
from ok.gui.about.AboutTab import AboutTab
|
|
4
5
|
from ok.gui.debug.DebugTab import DebugTab
|
|
5
6
|
from ok.gui.tasks.TaskTab import TaskTab
|
|
6
7
|
from ok.logging.Logger import get_logger
|
|
@@ -13,7 +14,7 @@ class Communicate(QObject):
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class MainWindow(QTabWidget):
|
|
16
|
-
def __init__(self, tasks, debug=False, exit_event=None):
|
|
17
|
+
def __init__(self, tasks, debug=False, about=None, exit_event=None):
|
|
17
18
|
super().__init__()
|
|
18
19
|
self.exit_event = exit_event
|
|
19
20
|
task_tab = TaskTab(tasks)
|
|
@@ -22,6 +23,9 @@ class MainWindow(QTabWidget):
|
|
|
22
23
|
debug_tab = DebugTab()
|
|
23
24
|
self.addTab(debug_tab, self.tr("Debug"))
|
|
24
25
|
# ... Add other tabs similarly
|
|
26
|
+
if about:
|
|
27
|
+
about_tab = AboutTab(about)
|
|
28
|
+
self.addTab(about_tab, self.tr("About"))
|
|
25
29
|
|
|
26
30
|
# Styling the tabs and content if needed, for example:
|
|
27
31
|
self.setStyleSheet("""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QWidget, QTextEdit, QVBoxLayout
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AboutTab(QWidget):
|
|
5
|
+
def __init__(self, text=""):
|
|
6
|
+
super().__init__()
|
|
7
|
+
layout = QVBoxLayout()
|
|
8
|
+
|
|
9
|
+
# Create a QTextEdit instance
|
|
10
|
+
text_edit = QTextEdit()
|
|
11
|
+
text_edit.setHtml(text)
|
|
12
|
+
text_edit.setReadOnly(True)
|
|
13
|
+
layout.addWidget(text_edit)
|
|
14
|
+
|
|
15
|
+
# Set the layout on the widget
|
|
16
|
+
self.setLayout(layout)
|