kotonebot 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kotonebot/__init__.py +39 -39
- kotonebot/backend/bot.py +312 -312
- kotonebot/backend/color.py +525 -525
- kotonebot/backend/context/__init__.py +3 -3
- kotonebot/backend/context/task_action.py +183 -183
- kotonebot/backend/core.py +129 -129
- kotonebot/backend/debug/entry.py +89 -89
- kotonebot/backend/debug/mock.py +78 -78
- kotonebot/backend/debug/server.py +222 -222
- kotonebot/backend/debug/vars.py +351 -351
- kotonebot/backend/dispatch.py +227 -227
- kotonebot/backend/flow_controller.py +196 -196
- kotonebot/backend/ocr.py +535 -529
- kotonebot/backend/preprocessor.py +103 -103
- kotonebot/client/__init__.py +9 -9
- kotonebot/client/device.py +528 -503
- kotonebot/client/fast_screenshot.py +377 -377
- kotonebot/client/host/__init__.py +43 -12
- kotonebot/client/host/adb_common.py +107 -103
- kotonebot/client/host/custom.py +118 -114
- kotonebot/client/host/leidian_host.py +196 -201
- kotonebot/client/host/mumu12_host.py +353 -358
- kotonebot/client/host/protocol.py +214 -213
- kotonebot/client/host/windows_common.py +58 -58
- kotonebot/client/implements/__init__.py +71 -15
- kotonebot/client/implements/adb.py +89 -85
- kotonebot/client/implements/adb_raw.py +162 -158
- kotonebot/client/implements/nemu_ipc/__init__.py +11 -7
- kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
- kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
- kotonebot/client/implements/remote_windows.py +188 -188
- kotonebot/client/implements/uiautomator2.py +85 -81
- kotonebot/client/implements/windows.py +176 -172
- kotonebot/client/protocol.py +69 -69
- kotonebot/client/registration.py +24 -24
- kotonebot/config/base_config.py +96 -96
- kotonebot/config/manager.py +36 -36
- kotonebot/errors.py +76 -71
- kotonebot/interop/win/__init__.py +10 -3
- kotonebot/interop/win/_mouse.py +311 -0
- kotonebot/interop/win/message_box.py +313 -313
- kotonebot/interop/win/reg.py +37 -37
- kotonebot/interop/win/shortcut.py +43 -43
- kotonebot/interop/win/task_dialog.py +513 -513
- kotonebot/logging/__init__.py +2 -2
- kotonebot/logging/log.py +17 -17
- kotonebot/primitives/__init__.py +17 -17
- kotonebot/primitives/geometry.py +862 -290
- kotonebot/primitives/visual.py +63 -63
- kotonebot/tools/mirror.py +354 -354
- kotonebot/ui/file_host/sensio.py +36 -36
- kotonebot/ui/file_host/tmp_send.py +54 -54
- kotonebot/ui/pushkit/__init__.py +3 -3
- kotonebot/ui/pushkit/image_host.py +88 -87
- kotonebot/ui/pushkit/protocol.py +13 -13
- kotonebot/ui/pushkit/wxpusher.py +54 -53
- kotonebot/ui/user.py +148 -148
- kotonebot/util.py +436 -436
- {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/METADATA +82 -81
- kotonebot-0.5.0.dist-info/RECORD +71 -0
- {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/licenses/LICENSE +673 -673
- kotonebot-0.4.0.dist-info/RECORD +0 -70
- {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/WHEEL +0 -0
- {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,85 +1,89 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import cast
|
|
3
|
-
from typing_extensions import override
|
|
4
|
-
|
|
5
|
-
import cv2
|
|
6
|
-
import numpy as np
|
|
7
|
-
from cv2.typing import MatLike
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def
|
|
32
|
-
self.adb
|
|
33
|
-
|
|
34
|
-
@override
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if result_text
|
|
44
|
-
logger.error("
|
|
45
|
-
return None
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
spiltted = tuple(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
1
|
+
import logging
|
|
2
|
+
from typing import cast
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
from cv2.typing import MatLike
|
|
8
|
+
try:
|
|
9
|
+
from adbutils._device import AdbDevice as AdbUtilsDevice
|
|
10
|
+
except ImportError as _e:
|
|
11
|
+
from kotonebot.errors import MissingDependencyError
|
|
12
|
+
raise MissingDependencyError(_e, 'android')
|
|
13
|
+
|
|
14
|
+
from ..device import AndroidDevice
|
|
15
|
+
from ..protocol import AndroidCommandable, Touchable, Screenshotable
|
|
16
|
+
from ..registration import ImplConfig
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# 定义配置模型
|
|
22
|
+
@dataclass
|
|
23
|
+
class AdbImplConfig(ImplConfig):
|
|
24
|
+
addr: str
|
|
25
|
+
connect: bool = True
|
|
26
|
+
disconnect: bool = True
|
|
27
|
+
device_serial: str | None = None
|
|
28
|
+
timeout: float = 180
|
|
29
|
+
|
|
30
|
+
class AdbImpl(AndroidCommandable, Touchable, Screenshotable):
|
|
31
|
+
def __init__(self, adb_connection: AdbUtilsDevice):
|
|
32
|
+
self.adb = adb_connection
|
|
33
|
+
|
|
34
|
+
@override
|
|
35
|
+
def launch_app(self, package_name: str) -> None:
|
|
36
|
+
self.adb.shell(f"monkey -p {package_name} 1")
|
|
37
|
+
|
|
38
|
+
@override
|
|
39
|
+
def current_package(self) -> str | None:
|
|
40
|
+
# https://blog.csdn.net/guangdeshishe/article/details/117154406
|
|
41
|
+
result_text = self.adb.shell('dumpsys activity top | grep ACTIVITY | tail -n 1')
|
|
42
|
+
logger.debug(f"adb returned: {result_text}")
|
|
43
|
+
if not isinstance(result_text, str):
|
|
44
|
+
logger.error(f"Invalid result_text: {result_text}")
|
|
45
|
+
return None
|
|
46
|
+
result_text = result_text.strip()
|
|
47
|
+
if result_text == '':
|
|
48
|
+
logger.error("No current package found")
|
|
49
|
+
return None
|
|
50
|
+
_, activity, *_ = result_text.split(' ')
|
|
51
|
+
package = activity.split('/')[0]
|
|
52
|
+
return package
|
|
53
|
+
|
|
54
|
+
def adb_shell(self, cmd: str) -> str:
|
|
55
|
+
"""执行 ADB shell 命令"""
|
|
56
|
+
return cast(str, self.adb.shell(cmd))
|
|
57
|
+
|
|
58
|
+
@override
|
|
59
|
+
def detect_orientation(self):
|
|
60
|
+
# 判断方向:https://stackoverflow.com/questions/10040624/check-if-device-is-landscape-via-adb
|
|
61
|
+
# 但是上面这种方法不准确
|
|
62
|
+
# 因此这里直接通过截图判断方向
|
|
63
|
+
img = self.screenshot()
|
|
64
|
+
if img.shape[0] > img.shape[1]:
|
|
65
|
+
return 'portrait'
|
|
66
|
+
return 'landscape'
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def screen_size(self) -> tuple[int, int]:
|
|
70
|
+
ret = cast(str, self.adb.shell("wm size")).strip('Physical size: ')
|
|
71
|
+
spiltted = tuple(map(int, ret.split("x")))
|
|
72
|
+
# 检测当前方向
|
|
73
|
+
orientation = self.detect_orientation()
|
|
74
|
+
landscape = orientation == 'landscape'
|
|
75
|
+
spiltted = tuple(sorted(spiltted, reverse=landscape))
|
|
76
|
+
if len(spiltted) != 2:
|
|
77
|
+
raise ValueError(f"Invalid screen size: {ret}")
|
|
78
|
+
return spiltted
|
|
79
|
+
|
|
80
|
+
def screenshot(self) -> MatLike:
|
|
81
|
+
return cv2.cvtColor(np.array(self.adb.screenshot()), cv2.COLOR_RGB2BGR)
|
|
82
|
+
|
|
83
|
+
def click(self, x: int, y: int) -> None:
|
|
84
|
+
self.adb.shell(f"input tap {x} {y}")
|
|
85
|
+
|
|
86
|
+
def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float | None = None) -> None:
|
|
87
|
+
if duration is not None:
|
|
88
|
+
logger.warning("Swipe duration is not supported with AdbDevice. Ignoring duration.")
|
|
89
|
+
self.adb.shell(f"input touchscreen swipe {x1} {y1} {x2} {y2}")
|
|
@@ -1,159 +1,163 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import time
|
|
3
|
-
import subprocess
|
|
4
|
-
import struct
|
|
5
|
-
from threading import Thread, Lock
|
|
6
|
-
from functools import cached_property
|
|
7
|
-
from typing_extensions import override
|
|
8
|
-
|
|
9
|
-
import cv2
|
|
10
|
-
import numpy as np
|
|
11
|
-
from cv2.typing import MatLike
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
from .
|
|
15
|
-
|
|
16
|
-
from kotonebot import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
self.
|
|
37
|
-
self.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
self.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
w, h, p
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
raise ValueError(f"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import subprocess
|
|
4
|
+
import struct
|
|
5
|
+
from threading import Thread, Lock
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from typing_extensions import override
|
|
8
|
+
|
|
9
|
+
import cv2
|
|
10
|
+
import numpy as np
|
|
11
|
+
from cv2.typing import MatLike
|
|
12
|
+
try:
|
|
13
|
+
from adbutils._utils import adb_path
|
|
14
|
+
from adbutils._device import AdbDevice as AdbUtilsDevice
|
|
15
|
+
except ImportError as _e:
|
|
16
|
+
from kotonebot.errors import MissingDependencyError
|
|
17
|
+
raise MissingDependencyError(_e, 'android')
|
|
18
|
+
|
|
19
|
+
from .adb import AdbImpl
|
|
20
|
+
from kotonebot import logging
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
WAIT_TIMEOUT = 10
|
|
25
|
+
MAX_RETRY_COUNT = 5
|
|
26
|
+
SCRIPT: str = """#!/bin/sh
|
|
27
|
+
while true; do
|
|
28
|
+
screencap
|
|
29
|
+
sleep 0.3
|
|
30
|
+
done
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
class AdbRawImpl(AdbImpl):
|
|
34
|
+
def __init__(self, adb_connection: AdbUtilsDevice):
|
|
35
|
+
super().__init__(adb_connection)
|
|
36
|
+
self.__worker: Thread | None = None
|
|
37
|
+
self.__process: subprocess.Popen | None = None
|
|
38
|
+
self.__data: MatLike | None = None
|
|
39
|
+
self.__retry_count = 0
|
|
40
|
+
self.__lock = Lock()
|
|
41
|
+
self.__stopping = False
|
|
42
|
+
|
|
43
|
+
def __cleanup_worker(self) -> None:
|
|
44
|
+
if self.__process:
|
|
45
|
+
try:
|
|
46
|
+
self.__process.kill()
|
|
47
|
+
except:
|
|
48
|
+
pass
|
|
49
|
+
self.__process = None
|
|
50
|
+
if self.__worker:
|
|
51
|
+
try:
|
|
52
|
+
self.__worker.join()
|
|
53
|
+
except:
|
|
54
|
+
pass
|
|
55
|
+
self.__worker = None
|
|
56
|
+
self.__data = None
|
|
57
|
+
|
|
58
|
+
def __start_worker(self) -> None:
|
|
59
|
+
self.__stopping = True
|
|
60
|
+
self.__cleanup_worker()
|
|
61
|
+
self.__stopping = False
|
|
62
|
+
self.__worker = Thread(target=self.__worker_thread_with_retry, daemon=True)
|
|
63
|
+
self.__worker.start()
|
|
64
|
+
|
|
65
|
+
def __worker_thread_with_retry(self) -> None:
|
|
66
|
+
try:
|
|
67
|
+
self.__worker_thread()
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.error(f"Worker thread failed: {e}")
|
|
70
|
+
with self.__lock:
|
|
71
|
+
self.__retry_count += 1
|
|
72
|
+
raise
|
|
73
|
+
|
|
74
|
+
def __worker_thread(self) -> None:
|
|
75
|
+
with open('screenshot.sh', 'w', encoding='utf-8', newline='\n') as f:
|
|
76
|
+
f.write(SCRIPT)
|
|
77
|
+
self.adb.push('screenshot.sh', '/data/local/tmp/screenshot.sh')
|
|
78
|
+
self.adb.shell(f'chmod 755 /data/local/tmp/screenshot.sh')
|
|
79
|
+
os.remove('screenshot.sh')
|
|
80
|
+
|
|
81
|
+
cmd = fr'{adb_path()} -s {self.adb.serial} exec-out "sh /data/local/tmp/screenshot.sh"'
|
|
82
|
+
self.__process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
|
|
83
|
+
|
|
84
|
+
while not self.__stopping and self.__process.poll() is None:
|
|
85
|
+
if self.__process.stdout is None:
|
|
86
|
+
logger.error("Failed to get stdout from process")
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# 解析 header
|
|
90
|
+
# https://stackoverflow.com/questions/22034959/what-format-does-adb-screencap-sdcard-screenshot-raw-produce-without-p-f
|
|
91
|
+
if self.__api_level >= 26:
|
|
92
|
+
metadata = self.__process.stdout.read(16)
|
|
93
|
+
w, h, p, c = struct.unpack('<IIII', metadata)
|
|
94
|
+
# w=width, h=height, p=pixel_format, c=color_space
|
|
95
|
+
# 详见:https://android.googlesource.com/platform/frameworks/base/+/26a2b97dbe48ee45e9ae70110714048f2f360f97/cmds/screencap/screencap.cpp#209
|
|
96
|
+
else:
|
|
97
|
+
metadata = self.__process.stdout.read(12)
|
|
98
|
+
w, h, p = struct.unpack('<III', metadata)
|
|
99
|
+
if p == 1: # PixelFormat.RGBA_8888
|
|
100
|
+
channel = 4
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError(f"Unsupported pixel format: {p}")
|
|
103
|
+
data_size = w * h * channel
|
|
104
|
+
|
|
105
|
+
if (data_size < 100 * 100 * 4) or (data_size > 3000 * 3000 * 4):
|
|
106
|
+
raise ValueError(f"Invaild data_size: {w}x{h}.")
|
|
107
|
+
|
|
108
|
+
# 读取图像数据
|
|
109
|
+
# logger.verbose(f"receiving image data: {w}x{h} {data_size} bytes")
|
|
110
|
+
image_data = self.__process.stdout.read(data_size)
|
|
111
|
+
if not isinstance(image_data, bytes) or len(image_data) != data_size:
|
|
112
|
+
logger.error(f"Failed to read image data, expected {data_size} bytes but got {len(image_data) if isinstance(image_data, bytes) else 'non-bytes'}")
|
|
113
|
+
raise RuntimeError("Failed to read image data")
|
|
114
|
+
|
|
115
|
+
np_data = np.frombuffer(image_data, np.uint8)
|
|
116
|
+
np_data = np_data.reshape(h, w, channel)
|
|
117
|
+
self.__data = cv2.cvtColor(np_data, cv2.COLOR_RGBA2BGR)
|
|
118
|
+
|
|
119
|
+
@cached_property
|
|
120
|
+
def __api_level(self) -> int:
|
|
121
|
+
try:
|
|
122
|
+
output = self.adb.shell("getprop ro.build.version.sdk")
|
|
123
|
+
assert isinstance(output, str)
|
|
124
|
+
return int(output.strip())
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Failed to get API level: {e}")
|
|
127
|
+
return 0
|
|
128
|
+
|
|
129
|
+
@override
|
|
130
|
+
def screenshot(self) -> MatLike:
|
|
131
|
+
with self.__lock:
|
|
132
|
+
if self.__retry_count >= MAX_RETRY_COUNT:
|
|
133
|
+
raise RuntimeError(f"Maximum retry count ({MAX_RETRY_COUNT}) exceeded")
|
|
134
|
+
|
|
135
|
+
if not self.__worker or (self.__worker and not self.__worker.is_alive()):
|
|
136
|
+
self.__start_worker()
|
|
137
|
+
|
|
138
|
+
start_time = time.time()
|
|
139
|
+
while self.__data is None:
|
|
140
|
+
time.sleep(0.01)
|
|
141
|
+
if time.time() - start_time > WAIT_TIMEOUT:
|
|
142
|
+
logger.warning("Screenshot timeout, cleaning up and restarting worker...")
|
|
143
|
+
with self.__lock:
|
|
144
|
+
if self.__retry_count < MAX_RETRY_COUNT:
|
|
145
|
+
self.__start_worker()
|
|
146
|
+
start_time = time.time() # 重置超时计时器
|
|
147
|
+
continue
|
|
148
|
+
else:
|
|
149
|
+
raise RuntimeError(f"Maximum retry count ({MAX_RETRY_COUNT}) exceeded")
|
|
150
|
+
|
|
151
|
+
# 检查 worker 是否还活着
|
|
152
|
+
if self.__worker and not self.__worker.is_alive():
|
|
153
|
+
with self.__lock:
|
|
154
|
+
if self.__retry_count < MAX_RETRY_COUNT:
|
|
155
|
+
logger.warning("Worker thread died, restarting...")
|
|
156
|
+
self.__start_worker()
|
|
157
|
+
else:
|
|
158
|
+
raise RuntimeError(f"Maximum retry count ({MAX_RETRY_COUNT}) exceeded")
|
|
159
|
+
|
|
160
|
+
logger.verbose(f"adb raw screenshot wait time: {time.time() - start_time:.4f}s")
|
|
161
|
+
data = self.__data
|
|
162
|
+
self.__data = None
|
|
159
163
|
return data
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
from .
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
# ruff: noqa: E402
|
|
2
|
+
from kotonebot.util import require_windows
|
|
3
|
+
require_windows('"RemoteWindowsImpl" implementation')
|
|
4
|
+
|
|
5
|
+
from .external_renderer_ipc import ExternalRendererIpc
|
|
6
|
+
from .nemu_ipc import NemuIpcImpl, NemuIpcImplConfig
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ExternalRendererIpc",
|
|
10
|
+
"NemuIpcImpl",
|
|
11
|
+
"NemuIpcImplConfig",
|
|
8
12
|
]
|