gomyck-tools 1.3.1__py3-none-any.whl → 1.3.2__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.
- ctools/__init__.py +0 -0
- ctools/aes_tools.py +35 -0
- ctools/api_result.py +55 -0
- ctools/application.py +386 -0
- ctools/b64.py +7 -0
- ctools/bashPath.py +13 -0
- ctools/bottle_web_base.py +169 -0
- ctools/bottle_webserver.py +143 -0
- ctools/bottle_websocket.py +75 -0
- ctools/browser_element_tools.py +314 -0
- ctools/call.py +71 -0
- ctools/cftp.py +74 -0
- ctools/cjson.py +54 -0
- ctools/ckafka.py +159 -0
- ctools/compile_tools.py +18 -0
- ctools/console.py +55 -0
- ctools/coord_trans.py +127 -0
- ctools/credis.py +111 -0
- ctools/cron_lite.py +252 -0
- ctools/ctoken.py +34 -0
- ctools/cword.py +30 -0
- ctools/czip.py +130 -0
- ctools/database.py +185 -0
- ctools/date_utils.py +43 -0
- ctools/dict_wrapper.py +20 -0
- ctools/douglas_rarefy.py +136 -0
- ctools/download_tools.py +57 -0
- ctools/enums.py +4 -0
- ctools/ex.py +31 -0
- ctools/excelOpt.py +36 -0
- ctools/html_soup.py +35 -0
- ctools/http_utils.py +24 -0
- ctools/images_tools.py +27 -0
- ctools/imgDialog.py +44 -0
- ctools/metrics.py +131 -0
- ctools/mqtt_utils.py +289 -0
- ctools/obj.py +20 -0
- ctools/pacth.py +74 -0
- ctools/plan_area_tools.py +97 -0
- ctools/process_pool.py +36 -0
- ctools/pty_tools.py +72 -0
- ctools/resource_bundle_tools.py +121 -0
- ctools/rsa.py +70 -0
- ctools/screenshot_tools.py +127 -0
- ctools/sign.py +20 -0
- ctools/sm_tools.py +49 -0
- ctools/snow_id.py +76 -0
- ctools/str_diff.py +20 -0
- ctools/string_tools.py +85 -0
- ctools/sys_info.py +157 -0
- ctools/sys_log.py +89 -0
- ctools/thread_pool.py +35 -0
- ctools/upload_tools.py +40 -0
- ctools/win_canvas.py +83 -0
- ctools/win_control.py +106 -0
- ctools/word_fill.py +562 -0
- ctools/word_fill_entity.py +46 -0
- ctools/work_path.py +69 -0
- {gomyck_tools-1.3.1.dist-info → gomyck_tools-1.3.2.dist-info}/METADATA +1 -1
- gomyck_tools-1.3.2.dist-info/RECORD +62 -0
- gomyck_tools-1.3.2.dist-info/top_level.txt +1 -0
- gomyck_tools-1.3.1.dist-info/RECORD +0 -4
- gomyck_tools-1.3.1.dist-info/top_level.txt +0 -1
- {gomyck_tools-1.3.1.dist-info → gomyck_tools-1.3.2.dist-info}/WHEEL +0 -0
ctools/mqtt_utils.py
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
import time
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Dict
|
4
|
+
|
5
|
+
from ctools.dict_wrapper import DictWrapper as DictToObj
|
6
|
+
from paho.mqtt import client as mqtt
|
7
|
+
from paho.mqtt.enums import CallbackAPIVersion
|
8
|
+
|
9
|
+
from ctools import sys_log, cjson, string_tools, sys_info, date_utils, sm_tools, thread_pool
|
10
|
+
|
11
|
+
class MQTTEvent(Enum):
|
12
|
+
|
13
|
+
pass
|
14
|
+
|
15
|
+
'''
|
16
|
+
MQTT服务使用示例:
|
17
|
+
|
18
|
+
mqtt_utils = MQTTUtils("192.168.3.100")
|
19
|
+
mqtt_utils.connect(username="rpa", password="123")
|
20
|
+
|
21
|
+
# 订阅
|
22
|
+
mqtt_utils.subscribe("test/topic1", "test/topic2")
|
23
|
+
|
24
|
+
# 异步发送
|
25
|
+
msg = {"name": "测试消息"}
|
26
|
+
mqtt_utils.publish_async(msg, event=MQTTEvent.END_TASK)
|
27
|
+
|
28
|
+
# 同步发送
|
29
|
+
mqtt_resp = mqtt_utils.publish_sync(msg, event=MQTTEvent.UPLOAD_PROCESS, timeout=60)
|
30
|
+
status = mqtt_resp.status
|
31
|
+
body = mqtt_resp.body
|
32
|
+
print("%s, %s" % (status, body))
|
33
|
+
'''
|
34
|
+
|
35
|
+
log = sys_log.flog
|
36
|
+
|
37
|
+
NO_LOG_EVENT = [MQTTEvent.HEARTBEAT]
|
38
|
+
|
39
|
+
|
40
|
+
class MQTTRequest:
|
41
|
+
|
42
|
+
def __init__(self, **kwargs):
|
43
|
+
self.event: str = kwargs.get('event')
|
44
|
+
self.trace_id: str = kwargs.get('trace_id') if kwargs.get('trace_id') else string_tools.get_uuid()
|
45
|
+
self.client_id: str = kwargs.get('client_id')
|
46
|
+
self.time: str = kwargs.get('time') if kwargs.get('time') else date_utils.get_date_time()
|
47
|
+
self.body = kwargs.get('body')
|
48
|
+
|
49
|
+
def __str__(self):
|
50
|
+
res_str = ""
|
51
|
+
for k, v in self.__dict__.items():
|
52
|
+
res_str += "%s=%s," % (k, v)
|
53
|
+
return res_str[:-1]
|
54
|
+
|
55
|
+
|
56
|
+
class MQTTResponse:
|
57
|
+
|
58
|
+
def __init__(self, **kwargs):
|
59
|
+
self.event: str = kwargs.get('event')
|
60
|
+
self.trace_id: str = kwargs.get('trace_id')
|
61
|
+
self.status: int = kwargs.get('status')
|
62
|
+
self.time: str = kwargs.get('time') if kwargs.get('time') else date_utils.get_date_time()
|
63
|
+
self.msg: str = kwargs.get('msg')
|
64
|
+
self.body = kwargs.get('body')
|
65
|
+
|
66
|
+
def __str__(self):
|
67
|
+
res_str = ""
|
68
|
+
for k, v in self.__dict__.items():
|
69
|
+
res_str += "%s=%s," % (k, v)
|
70
|
+
return res_str[:-1]
|
71
|
+
|
72
|
+
|
73
|
+
class MQTTUtils:
|
74
|
+
def __init__(self, broker_address, port=1883, publish_topic=None, client_id=None, keepalive=60, clean_session=False,
|
75
|
+
userdata=None, protocol=mqtt.MQTTv311, message_handle=None, broker_encrypt_key=None):
|
76
|
+
self.publish_topic = publish_topic
|
77
|
+
self.broker_address = broker_address
|
78
|
+
self.port = port
|
79
|
+
self.client_id = client_id if client_id else sys_info.get_machine_code()
|
80
|
+
self.keepalive = keepalive
|
81
|
+
self.clean_session = clean_session
|
82
|
+
self.userdata = userdata
|
83
|
+
self.protocol = protocol
|
84
|
+
self.message_handle = message_handle
|
85
|
+
self.broker_encrypt_key = broker_encrypt_key
|
86
|
+
|
87
|
+
self.client = mqtt.Client(CallbackAPIVersion.VERSION2, client_id=self.client_id, clean_session=self.clean_session,
|
88
|
+
userdata=self.userdata, protocol=self.protocol)
|
89
|
+
self.client.on_connect = self._on_connect
|
90
|
+
self.client.on_message = self._on_message_thread
|
91
|
+
self.client.on_disconnect = self._on_disconnect
|
92
|
+
if self.publish_topic:
|
93
|
+
will_msg = {
|
94
|
+
"event": "offline",
|
95
|
+
"client_id": self.client_id,
|
96
|
+
"trace_id": string_tools.get_uuid(),
|
97
|
+
"time": date_utils.get_date_time(),
|
98
|
+
"body": {"type": "will_msg"}
|
99
|
+
}
|
100
|
+
self.client.will_set(self.publish_topic, cjson.dumps(will_msg), 2, False)
|
101
|
+
self.connected = False
|
102
|
+
self._publish_trace_id = []
|
103
|
+
self._received_message: Dict[str: MQTTResponse] = {}
|
104
|
+
|
105
|
+
def _on_connect(self, client, userdata, flags, rc, properties):
|
106
|
+
if rc == 0:
|
107
|
+
log.info("mqtt服务连接成功")
|
108
|
+
self.connected = True
|
109
|
+
else:
|
110
|
+
log.info("mqtt服务连接失败, broker地址: %s:%s, rc: %s" % (self.broker_address, self.port, rc))
|
111
|
+
|
112
|
+
def _on_message_thread(self, client, userdata, message):
|
113
|
+
thread_pool.submit(self._on_message, client=client, userdata=userdata, message=message)
|
114
|
+
|
115
|
+
def _on_message(self, client, userdata, message):
|
116
|
+
# try:
|
117
|
+
if message.topic != self.publish_topic and message.payload:
|
118
|
+
mqtt_msg = cjson.loads(message.payload.decode())
|
119
|
+
trace_id = mqtt_msg.get('trace_id')
|
120
|
+
if trace_id in self._publish_trace_id:
|
121
|
+
mqtt_resp = MQTTResponse(**mqtt_msg)
|
122
|
+
self._received_message[trace_id] = mqtt_resp
|
123
|
+
self._publish_trace_id.remove(trace_id)
|
124
|
+
else:
|
125
|
+
mqtt_req = MQTTRequest(**mqtt_msg)
|
126
|
+
|
127
|
+
try:
|
128
|
+
if mqtt_req.event not in NO_LOG_EVENT:
|
129
|
+
log.debug("异步接收mqtt消息: %s" % str(mqtt_req))
|
130
|
+
if mqtt_req.body:
|
131
|
+
try:
|
132
|
+
mqtt_req.body = self.decrypt_body(mqtt_req.body)
|
133
|
+
except Exception:
|
134
|
+
if mqtt_req.event != MQTTEvent.HAND_SHARK:
|
135
|
+
log.error('解密消息失败: {}'.format(mqtt_req))
|
136
|
+
return
|
137
|
+
if isinstance(mqtt_req.body, dict):
|
138
|
+
mqtt_req.body = DictToObj(mqtt_req.body)
|
139
|
+
except Exception as e:
|
140
|
+
log.info("异步接收mqtt消息异常: %s" % e)
|
141
|
+
self.message_handle(mqtt_req, message.topic)
|
142
|
+
# except Exception as e:
|
143
|
+
# log.error("接收mqtt消息异常: %s" % e)
|
144
|
+
|
145
|
+
def _on_disconnect(self, client, userdata, flags, rc, properties):
|
146
|
+
self.connected = False
|
147
|
+
log.info("mqtt服务已断开连接, rc: %s" % rc)
|
148
|
+
|
149
|
+
def encrypt_body(self, body):
|
150
|
+
if self.broker_encrypt_key:
|
151
|
+
try:
|
152
|
+
return sm_tools.encrypt_with_sm4(self.broker_encrypt_key, cjson.dumps(body))
|
153
|
+
except Exception:
|
154
|
+
return sm_tools.encrypt_with_sm4(self.broker_encrypt_key, body)
|
155
|
+
|
156
|
+
def decrypt_body(self, body):
|
157
|
+
if self.broker_encrypt_key:
|
158
|
+
try:
|
159
|
+
return cjson.loads(sm_tools.decrypt_with_sm4(self.broker_encrypt_key, body))
|
160
|
+
except Exception:
|
161
|
+
sm_tools.decrypt_with_sm4(self.broker_encrypt_key, body)
|
162
|
+
|
163
|
+
def connect(self, username=None, password=None, timeout=10):
|
164
|
+
if username and password:
|
165
|
+
self.client.username_pw_set(username, password)
|
166
|
+
self.client.connect_async(host=self.broker_address, port=self.port, keepalive=self.keepalive)
|
167
|
+
self.client.loop_start()
|
168
|
+
|
169
|
+
start_time = time.time()
|
170
|
+
while not self.connected and time.time() - start_time < timeout:
|
171
|
+
time.sleep(0.1)
|
172
|
+
|
173
|
+
if not self.connected:
|
174
|
+
log.info("mqtt服务连接超时, broker地址: %s:%s" % (self.broker_address, self.port))
|
175
|
+
|
176
|
+
def publish_resp(self, mqtt_resp: MQTTResponse, topic=None, encrypt=True, qos=2, retain=False):
|
177
|
+
"""
|
178
|
+
发送mqtt响应信息
|
179
|
+
:param mqtt_resp: 响应信息, 使用 mqtt_service.init_resp('ok', req)
|
180
|
+
:param topic: 主题, 默认用订阅的主题
|
181
|
+
:param encrypt: 是否加密
|
182
|
+
:param qos: 消息质量
|
183
|
+
:param retain: 是否保留
|
184
|
+
"""
|
185
|
+
topic = topic if topic else self.publish_topic
|
186
|
+
if self.connected:
|
187
|
+
if mqtt_resp:
|
188
|
+
try:
|
189
|
+
if encrypt and mqtt_resp.body:
|
190
|
+
mqtt_resp.body = self.encrypt_body(mqtt_resp.body)
|
191
|
+
mqtt_resp.client_id = self.client_id
|
192
|
+
mqtt_data = cjson.dumps(mqtt_resp)
|
193
|
+
self.client.publish(topic, mqtt_data, qos=qos, retain=retain)
|
194
|
+
log.debug("异步发送mqtt响应消息: topic: %s message: %s" % (topic, mqtt_data))
|
195
|
+
except Exception as e:
|
196
|
+
log.error('异步发送mqtt响应消息异常: %s' % e)
|
197
|
+
else:
|
198
|
+
log.info("异步发送mqtt响应-mqtt服务未连接, 无法发送消息")
|
199
|
+
|
200
|
+
def publish_async(self, message, event: MQTTEvent = None, encrypt=True, topic=None, qos=2, retain=False):
|
201
|
+
topic = topic if topic else self.publish_topic
|
202
|
+
if self.connected:
|
203
|
+
try:
|
204
|
+
if encrypt and message:
|
205
|
+
message = self.encrypt_body(message)
|
206
|
+
|
207
|
+
mqtt_req = MQTTRequest()
|
208
|
+
mqtt_req.client_id = self.client_id
|
209
|
+
mqtt_req.body = message
|
210
|
+
if event:
|
211
|
+
mqtt_req.event = event.value
|
212
|
+
mqtt_data = cjson.dumps(mqtt_req)
|
213
|
+
self.client.publish(topic, mqtt_data, qos=qos, retain=retain)
|
214
|
+
if event not in NO_LOG_EVENT:
|
215
|
+
log.debug("异步发送mqtt消息: topic: %s message: %s" % (topic, mqtt_data))
|
216
|
+
except Exception as e:
|
217
|
+
log.error('异步发送mqtt消息异常: %s' % e)
|
218
|
+
else:
|
219
|
+
log.info("异步发送mqtt消息-mqtt服务未连接, 无法发送消息")
|
220
|
+
|
221
|
+
def publish_sync(self, message, event: MQTTEvent, topic=None, encrypt=True,
|
222
|
+
qos=2, retain=False, timeout: int = 15, send_time: str = None) -> MQTTResponse:
|
223
|
+
if not event: raise Exception("事件不能为空")
|
224
|
+
mqtt_resp = MQTTResponse()
|
225
|
+
mqtt_resp.status = 500
|
226
|
+
mqtt_resp.msg = "服务端未响应, 请联系管理员!"
|
227
|
+
topic = topic if topic else self.publish_topic
|
228
|
+
if self.connected:
|
229
|
+
try:
|
230
|
+
if encrypt and message:
|
231
|
+
message = self.encrypt_body(message)
|
232
|
+
|
233
|
+
mqtt_req = MQTTRequest()
|
234
|
+
mqtt_req.source = "client"
|
235
|
+
mqtt_req.client_id = self.client_id
|
236
|
+
mqtt_req.body = message
|
237
|
+
mqtt_req.time = send_time if send_time else mqtt_req.time
|
238
|
+
mqtt_req.event = event.value
|
239
|
+
mqtt_data = cjson.dumps(mqtt_req)
|
240
|
+
self._publish_trace_id.append(mqtt_req.trace_id)
|
241
|
+
self.client.publish(topic, mqtt_data, qos=qos, retain=retain)
|
242
|
+
if event not in NO_LOG_EVENT:
|
243
|
+
log.debug("同步发送mqtt消息: topic: %s message: %s" % (topic, mqtt_data))
|
244
|
+
is_timeout = False
|
245
|
+
start_time = time.time()
|
246
|
+
while True:
|
247
|
+
if mqtt_req.trace_id in self._received_message:
|
248
|
+
mqtt_resp = self._received_message.pop(mqtt_req.trace_id)
|
249
|
+
if mqtt_resp.body and encrypt:
|
250
|
+
mqtt_resp.body = self.decrypt_body(mqtt_resp.body)
|
251
|
+
break
|
252
|
+
if (time.time() - start_time) >= timeout:
|
253
|
+
is_timeout = True
|
254
|
+
break
|
255
|
+
time.sleep(0.5)
|
256
|
+
if not is_timeout:
|
257
|
+
if event not in NO_LOG_EVENT:
|
258
|
+
log.debug("同步接收mqtt消息: topic: %s message: %s" % (topic, mqtt_resp))
|
259
|
+
else:
|
260
|
+
log.info("同步发送mqtt消息, 等待返回信息超时: %s" % mqtt_req)
|
261
|
+
except Exception as e:
|
262
|
+
log.error('同步发送mqtt消息异常: %s-%s' % (event.value, e))
|
263
|
+
else:
|
264
|
+
log.info("同步接收mqtt消息-mqtt服务未连接, 无法发送消息")
|
265
|
+
return mqtt_resp
|
266
|
+
|
267
|
+
def subscribe(self, *topic, qos=0):
|
268
|
+
if self.connected:
|
269
|
+
if topic and len(topic) > 0:
|
270
|
+
for t in topic:
|
271
|
+
self.client.subscribe(t, qos=qos)
|
272
|
+
log.info("已订阅mqtt主题[%s]" % t)
|
273
|
+
else:
|
274
|
+
log.info("mqtt服务未连接, 无法订阅主题")
|
275
|
+
|
276
|
+
def unsubscribe(self, *topic):
|
277
|
+
if self.connected:
|
278
|
+
if topic and len(topic) > 0:
|
279
|
+
for t in topic:
|
280
|
+
self.client.unsubscribe(t)
|
281
|
+
log.info("已解除订阅mqtt主题[%s]" % t)
|
282
|
+
else:
|
283
|
+
log.info("mqtt服务未连接, 无法解除订阅主题")
|
284
|
+
|
285
|
+
def disconnect(self):
|
286
|
+
if self.connected:
|
287
|
+
self.client.disconnect()
|
288
|
+
self.client.loop_stop()
|
289
|
+
log.info("已关闭mqtt服务连接")
|
ctools/obj.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
def isNull(param) -> bool:
|
3
|
+
if type(param) == str:
|
4
|
+
return param == ''
|
5
|
+
elif type(param) == list:
|
6
|
+
return len(param) == 0
|
7
|
+
elif type(param) == dict:
|
8
|
+
return len(param) == 0
|
9
|
+
elif type(param) == int:
|
10
|
+
return param == 0
|
11
|
+
elif type(param) == float:
|
12
|
+
return param == 0.0
|
13
|
+
elif type(param) == bool:
|
14
|
+
return param == False
|
15
|
+
else:
|
16
|
+
return param is None
|
17
|
+
|
18
|
+
def isNotNull(param) -> bool:
|
19
|
+
return not isNull(param)
|
20
|
+
|
ctools/pacth.py
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
|
4
|
+
from sqlalchemy.sql import text
|
5
|
+
|
6
|
+
from ctools import database
|
7
|
+
|
8
|
+
|
9
|
+
class Patch:
|
10
|
+
|
11
|
+
def __init__(self, oldVersion, newVersion, pythonPath, playwrightPath, driverPath) -> None:
|
12
|
+
super().__init__()
|
13
|
+
self.oldV = oldVersion[len('V'):].replace('.', '').replace('-snapshot', '')
|
14
|
+
self.currentV = newVersion[len('V'):].replace('.', '').replace('-snapshot', '')
|
15
|
+
self.snapshot = '-snapshot' in newVersion or '-snapshot' in oldVersion
|
16
|
+
self.pythonPath = pythonPath
|
17
|
+
self.playwrightPath = playwrightPath
|
18
|
+
self.driverPath = driverPath
|
19
|
+
self.deleteSDK, self.deleteHPL, self.deleteDriver = False, False, False
|
20
|
+
|
21
|
+
def apply_patch(self):
|
22
|
+
patch_methods = [method for method in dir(self) if callable(getattr(self, method)) and method.startswith('patch_V')]
|
23
|
+
patch_methods.sort(key=lambda x: int(x[len('patch_V'):]))
|
24
|
+
max_method_name = patch_methods[-1]
|
25
|
+
exec_max_method = False
|
26
|
+
for method_name in patch_methods:
|
27
|
+
slVersion = method_name[len('patch_V'):]
|
28
|
+
if int(self.currentV) > int(slVersion) >= int(self.oldV):
|
29
|
+
if max_method_name == method_name: exec_max_method = True
|
30
|
+
method = getattr(self, method_name)
|
31
|
+
print('start exec patch {}'.format(method_name))
|
32
|
+
method()
|
33
|
+
print('patch {} update success'.format(method_name))
|
34
|
+
if self.snapshot and not exec_max_method:
|
35
|
+
print('start exec snapshot patch {}'.format(max_method_name))
|
36
|
+
method = getattr(self, max_method_name)
|
37
|
+
method()
|
38
|
+
print('snapshot patch {} update success'.format(max_method_name))
|
39
|
+
|
40
|
+
def patch_V220(self):
|
41
|
+
pass
|
42
|
+
|
43
|
+
def run_sqls(self, sqls):
|
44
|
+
with database.get_session() as s:
|
45
|
+
for sql in sqls.split(";"):
|
46
|
+
try:
|
47
|
+
s.execute(text(sql.strip()))
|
48
|
+
s.commit()
|
49
|
+
except Exception as e:
|
50
|
+
print('结构升级错误, 请检查!!! {}'.format(e.__cause__))
|
51
|
+
|
52
|
+
def remove_sdk(self):
|
53
|
+
if self.deleteSDK: return
|
54
|
+
try:
|
55
|
+
self.deleteSDK = True
|
56
|
+
if os.path.exists(self.pythonPath): shutil.rmtree(self.pythonPath)
|
57
|
+
except Exception as e:
|
58
|
+
print('删除SDK错误: {}'.format(e))
|
59
|
+
|
60
|
+
def remove_hpl(self):
|
61
|
+
if self.deleteHPL: return
|
62
|
+
try:
|
63
|
+
self.deleteHPL = True
|
64
|
+
if os.path.exists(self.playwrightPath): shutil.rmtree(self.playwrightPath)
|
65
|
+
except Exception as e:
|
66
|
+
print('删除HPL错误: {}'.format(e))
|
67
|
+
|
68
|
+
def remove_driver(self):
|
69
|
+
if self.deleteDriver: return
|
70
|
+
try:
|
71
|
+
self.deleteDriver = True
|
72
|
+
if os.path.exists(self.driverPath): shutil.rmtree(self.driverPath)
|
73
|
+
except Exception as e:
|
74
|
+
print('删除Driver错误: {}'.format(e))
|
@@ -0,0 +1,97 @@
|
|
1
|
+
import tkinter as tk
|
2
|
+
|
3
|
+
"""
|
4
|
+
规划区域工具类, 按回车获取区域坐标
|
5
|
+
image_path=screenshot_tools.screenshot()
|
6
|
+
"""
|
7
|
+
|
8
|
+
|
9
|
+
class PlanAreaTools:
|
10
|
+
def __init__(self):
|
11
|
+
self.root = tk.Tk()
|
12
|
+
self.root.overrideredirect(True)
|
13
|
+
self.root.attributes("-alpha", 0.1)
|
14
|
+
self.root.attributes('-topmost', 'true')
|
15
|
+
self.root.geometry("{0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
|
16
|
+
self.root.configure(bg="black")
|
17
|
+
|
18
|
+
self.canvas = tk.Canvas(self.root)
|
19
|
+
|
20
|
+
self.x, self.y = 0, 0
|
21
|
+
self.xstart, self.ystart = 0, 0
|
22
|
+
self.xend, self.yend = 0, 0
|
23
|
+
self.xcenter, self.ycenter = 0, 0
|
24
|
+
|
25
|
+
self.select_area = self.canvas.create_rectangle(0, 0, 0, 0, outline='red', width=0, dash=(4, 4))
|
26
|
+
self.center_offset = (0, 0)
|
27
|
+
|
28
|
+
self.pos_div = tk.Toplevel(self.root)
|
29
|
+
self.pos_div.overrideredirect(True)
|
30
|
+
self.pos_div.attributes('-topmost', 'true')
|
31
|
+
self.pos_div.configure(bg="grey")
|
32
|
+
self.pos_div.geometry("270x25")
|
33
|
+
|
34
|
+
self.pos_div_text = "POS: (%s, %s), Enter键-获取区域, Esc键-退出"
|
35
|
+
self.pos_label = tk.Label(self.pos_div, text=self.pos_div_text % (0, 0), bg="black", fg="white")
|
36
|
+
self.pos_label.pack(fill="both", expand=True)
|
37
|
+
|
38
|
+
self.root.bind('<Escape>', self.sys_out) # 键盘Esc键->退出
|
39
|
+
self.root.bind('<Motion>', self.print_pos)
|
40
|
+
self.root.bind("<Button-1>", self.mouse_left_down) # 鼠标左键点击->显示子窗口
|
41
|
+
self.root.bind("<B1-Motion>", self.mouse_left_move) # 鼠标左键移动->改变子窗口大小
|
42
|
+
self.root.bind("<ButtonRelease-1>", self.mouse_left_up) # 鼠标左键释放->记录最后光标的位置
|
43
|
+
self.root.bind("<Return>", self.save_area) # 回车键->获取区域信息
|
44
|
+
self.root.focus()
|
45
|
+
|
46
|
+
self.area = [0, 0, 0, 0]
|
47
|
+
|
48
|
+
def start(self):
|
49
|
+
self.root.mainloop()
|
50
|
+
|
51
|
+
def print_pos(self, event):
|
52
|
+
self.x, self.y = event.widget.winfo_pointerxy()
|
53
|
+
self.pos_label.config(text=self.pos_div_text % (self.x, self.y))
|
54
|
+
self.pos_div.geometry(f"+{self.x + 10}+{self.y + 10}")
|
55
|
+
|
56
|
+
def mouse_left_down(self, event):
|
57
|
+
self.x, self.y = event.x, event.y
|
58
|
+
self.xstart, self.ystart = event.x, event.y
|
59
|
+
self.canvas.configure(height=1)
|
60
|
+
self.canvas.configure(width=1)
|
61
|
+
self.canvas.config(highlightthickness=0)
|
62
|
+
self.canvas.place(x=event.x, y=event.y)
|
63
|
+
|
64
|
+
def mouse_left_move(self, event):
|
65
|
+
self.x, self.y = event.x, event.y
|
66
|
+
self.xcenter = self.xstart + int((self.x-self.xstart)/2)-2
|
67
|
+
self.ycenter = self.ystart + int((self.y-self.ystart)/2)-2
|
68
|
+
|
69
|
+
self.pos_label.config(text=self.pos_div_text % (self.x, self.y))
|
70
|
+
self.pos_div.geometry(f"+{self.x + 10}+{self.y + 10}")
|
71
|
+
|
72
|
+
self.canvas.configure(height=event.y - self.ystart)
|
73
|
+
self.canvas.configure(width=event.x - self.xstart)
|
74
|
+
self.canvas.coords(self.select_area, 0, 0, event.x - self.xstart, event.y - self.ystart)
|
75
|
+
|
76
|
+
def mouse_left_up(self, event):
|
77
|
+
self.xend, self.yend = event.x, event.y
|
78
|
+
|
79
|
+
def save_area(self, event):
|
80
|
+
try:
|
81
|
+
self.area = [self.xstart, self.ystart, self.xend, self.yend]
|
82
|
+
self.canvas.delete(self.select_area)
|
83
|
+
self.canvas.place_forget()
|
84
|
+
self.sys_out()
|
85
|
+
|
86
|
+
except Exception:
|
87
|
+
pass
|
88
|
+
|
89
|
+
def sys_out(self, event=None):
|
90
|
+
self.pos_div.destroy()
|
91
|
+
self.root.destroy()
|
92
|
+
|
93
|
+
|
94
|
+
def get_area():
|
95
|
+
pat = PlanAreaTools()
|
96
|
+
pat.start()
|
97
|
+
return tuple(pat.area)
|
ctools/process_pool.py
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: UTF-8 -*-
|
3
|
+
__author__ = 'haoyang'
|
4
|
+
__date__ = '2024/9/20 12:00'
|
5
|
+
import os
|
6
|
+
import time
|
7
|
+
from concurrent.futures import ProcessPoolExecutor
|
8
|
+
|
9
|
+
from ctools import call
|
10
|
+
|
11
|
+
_process_pool: ProcessPoolExecutor = None
|
12
|
+
|
13
|
+
@call.init
|
14
|
+
def init():
|
15
|
+
global _process_pool
|
16
|
+
max_workers = min(32, os.cpu_count()) # 最多 32 个
|
17
|
+
_process_pool = ProcessPoolExecutor(max_workers=max_workers)
|
18
|
+
|
19
|
+
def cb(f, callback):
|
20
|
+
exc = f.exception()
|
21
|
+
if exc:
|
22
|
+
if callback: callback(exc)
|
23
|
+
raise exc
|
24
|
+
else:
|
25
|
+
if callback: callback(f.result())
|
26
|
+
|
27
|
+
def submit(func, *args, callback=None, **kwargs):
|
28
|
+
if _process_pool is None: raise Exception('process pool is not init')
|
29
|
+
future = _process_pool.submit(func, *args, **kwargs)
|
30
|
+
future.add_done_callback(lambda f: cb(f, callback))
|
31
|
+
time.sleep(0.01)
|
32
|
+
return future
|
33
|
+
|
34
|
+
def shutdown(wait=True):
|
35
|
+
if _process_pool is None: raise Exception('process pool is not init')
|
36
|
+
_process_pool.shutdown(wait=wait)
|
ctools/pty_tools.py
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
import _thread
|
2
|
+
import queue
|
3
|
+
import time
|
4
|
+
|
5
|
+
# 伪终端交互
|
6
|
+
|
7
|
+
from winpty import PtyProcess
|
8
|
+
|
9
|
+
|
10
|
+
class Code:
|
11
|
+
SUCCESS = 200
|
12
|
+
FAIL = 201
|
13
|
+
PART = 206
|
14
|
+
|
15
|
+
|
16
|
+
class PtyTools:
|
17
|
+
def __init__(self, cmd, prefix: str = ""):
|
18
|
+
self._process = PtyProcess.spawn(cmd)
|
19
|
+
self._read_queue = queue.Queue()
|
20
|
+
self._prefix = prefix
|
21
|
+
self._is_enable = False
|
22
|
+
|
23
|
+
def _read(self):
|
24
|
+
try:
|
25
|
+
while True:
|
26
|
+
msg = self._process.readline()
|
27
|
+
self._is_enable = True
|
28
|
+
if msg and self._prefix not in msg[:len(self._prefix)]:
|
29
|
+
self._read_queue.put(msg)
|
30
|
+
elif self._is_exit():
|
31
|
+
break
|
32
|
+
except Exception:
|
33
|
+
pass
|
34
|
+
|
35
|
+
def _is_exit(self):
|
36
|
+
return self._process.exitstatus is not None
|
37
|
+
|
38
|
+
def run(self):
|
39
|
+
_thread.start_new_thread(self._read, ())
|
40
|
+
while not self._is_enable:
|
41
|
+
time.sleep(1)
|
42
|
+
|
43
|
+
def read(self, end_str: str = None, is_async=False, timeout: int = 1):
|
44
|
+
code = Code.SUCCESS
|
45
|
+
content = ""
|
46
|
+
start_time = time.time()
|
47
|
+
while True:
|
48
|
+
if not self._read_queue.empty():
|
49
|
+
line = self._read_queue.get()
|
50
|
+
content += line
|
51
|
+
start_time = time.time()
|
52
|
+
if end_str is not None and end_str in line:
|
53
|
+
break
|
54
|
+
if is_async:
|
55
|
+
code = Code.PART
|
56
|
+
break
|
57
|
+
else:
|
58
|
+
if time.time() - start_time >= timeout:
|
59
|
+
break
|
60
|
+
if self._is_exit():
|
61
|
+
code = Code.FAIL
|
62
|
+
break
|
63
|
+
time.sleep(0.1)
|
64
|
+
return code, content
|
65
|
+
|
66
|
+
def send(self, msg, timeout: int = 0.5):
|
67
|
+
self._process.write('%s\r\n' % msg)
|
68
|
+
return self.read(timeout=timeout)
|
69
|
+
|
70
|
+
def close(self):
|
71
|
+
self._process.close(force=True)
|
72
|
+
|