autoxkit 2.0.0__tar.gz → 2.1.0__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.
- {autoxkit-2.0.0 → autoxkit-2.1.0}/PKG-INFO +1 -1
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/android/adb.py +10 -0
- autoxkit-2.1.0/autoxkit/android/control.py +246 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/android/streams.py +55 -1
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/hook/hook_listener.py +3 -2
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit.egg-info/PKG-INFO +1 -1
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit.egg-info/SOURCES.txt +1 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/pyproject.toml +1 -1
- {autoxkit-2.0.0 → autoxkit-2.1.0}/LICENSE.txt +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/README.md +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/__init__.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/android/__init__.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/android/binary.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/android/client.py +0 -0
- /autoxkit-2.0.0/autoxkit/android/control.py → /autoxkit-2.1.0/autoxkit/android/control_backup.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/android/models.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/constants.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/hook/__init__.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/hook/event.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/hook/hotkey_listener.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/match/__init__.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/match/match.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/mousekey/__init__.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/mousekey/input.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/mousekey/keyboard.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/mousekey/mouse.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/utils.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/window/__init__.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/window/window.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/window/window_action.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit/window/window_match.py +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit.egg-info/dependency_links.txt +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit.egg-info/requires.txt +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/autoxkit.egg-info/top_level.txt +0 -0
- {autoxkit-2.0.0 → autoxkit-2.1.0}/setup.cfg +0 -0
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import os
|
|
6
7
|
import re
|
|
8
|
+
import subprocess
|
|
7
9
|
from dataclasses import dataclass
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
|
|
12
|
+
if os.name == "nt":
|
|
13
|
+
_CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
|
|
14
|
+
else:
|
|
15
|
+
_CREATE_NO_WINDOW = 0
|
|
16
|
+
|
|
10
17
|
|
|
11
18
|
class AdbError(RuntimeError):
|
|
12
19
|
pass
|
|
@@ -66,6 +73,7 @@ class AdbServerLauncher:
|
|
|
66
73
|
"devices", "-l",
|
|
67
74
|
stdout=asyncio.subprocess.PIPE,
|
|
68
75
|
stderr=asyncio.subprocess.PIPE,
|
|
76
|
+
creationflags=_CREATE_NO_WINDOW,
|
|
69
77
|
)
|
|
70
78
|
stdout, _ = await proc.communicate()
|
|
71
79
|
out = stdout.decode(errors="replace")
|
|
@@ -149,6 +157,7 @@ class AdbServerLauncher:
|
|
|
149
157
|
*args,
|
|
150
158
|
stdout=asyncio.subprocess.PIPE,
|
|
151
159
|
stderr=asyncio.subprocess.PIPE,
|
|
160
|
+
creationflags=_CREATE_NO_WINDOW,
|
|
152
161
|
)
|
|
153
162
|
|
|
154
163
|
async def _adb(self, *args: str, check: bool = True) -> asyncio.subprocess.Process:
|
|
@@ -173,6 +182,7 @@ class AdbServerLauncher:
|
|
|
173
182
|
*args,
|
|
174
183
|
stdout=asyncio.subprocess.PIPE,
|
|
175
184
|
stderr=asyncio.subprocess.PIPE,
|
|
185
|
+
creationflags=_CREATE_NO_WINDOW,
|
|
176
186
|
)
|
|
177
187
|
stdout, stderr = await proc.communicate()
|
|
178
188
|
out = stdout.decode(errors="replace").strip()
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Control and device-message serializers for scrcpy-server v4.0."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .binary import (
|
|
6
|
+
ProtocolError,
|
|
7
|
+
float_to_i16fp,
|
|
8
|
+
float_to_u16fp,
|
|
9
|
+
i16be,
|
|
10
|
+
i32be,
|
|
11
|
+
read_u16be,
|
|
12
|
+
read_u32be,
|
|
13
|
+
read_u64be,
|
|
14
|
+
string8,
|
|
15
|
+
string32,
|
|
16
|
+
u16be,
|
|
17
|
+
u32be,
|
|
18
|
+
u64be,
|
|
19
|
+
)
|
|
20
|
+
from .models import ControlMessage, ControlMessageType, DeviceMessage, DeviceMessageType, Position
|
|
21
|
+
|
|
22
|
+
MESSAGE_MAX_SIZE = 1 << 18
|
|
23
|
+
INJECT_TEXT_MAX_LENGTH = 300
|
|
24
|
+
CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 14
|
|
25
|
+
|
|
26
|
+
ACTION_DOWN = 0
|
|
27
|
+
ACTION_UP = 1
|
|
28
|
+
ACTION_MOVE = 2
|
|
29
|
+
|
|
30
|
+
POINTER_ID_MOUSE = -1 & 0xFFFFFFFFFFFFFFFF
|
|
31
|
+
POINTER_ID_GENERIC_FINGER = -2 & 0xFFFFFFFFFFFFFFFF
|
|
32
|
+
MAX_TOUCH_POINTERS = 10
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PointerManager:
|
|
36
|
+
"""Manages a pool of unique pointer IDs for multi-touch.
|
|
37
|
+
|
|
38
|
+
Allocates pointer IDs from 0 upward, up to `MAX_TOUCH_POINTERS`
|
|
39
|
+
concurrent touches. IDs are reused when released.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
self._next_id = 0
|
|
44
|
+
self._freed: list[int] = []
|
|
45
|
+
self._active: set[int] = set()
|
|
46
|
+
|
|
47
|
+
def allocate(self) -> int | None:
|
|
48
|
+
"""Allocate a unique pointer ID.
|
|
49
|
+
|
|
50
|
+
Returns an integer pointer ID, or `None` if the maximum number
|
|
51
|
+
of concurrent touches (`MAX_TOUCH_POINTERS`) has been reached.
|
|
52
|
+
"""
|
|
53
|
+
if len(self._active) >= MAX_TOUCH_POINTERS:
|
|
54
|
+
return None
|
|
55
|
+
if self._freed:
|
|
56
|
+
pid = self._freed.pop()
|
|
57
|
+
else:
|
|
58
|
+
pid = self._next_id
|
|
59
|
+
self._next_id += 1
|
|
60
|
+
self._active.add(pid)
|
|
61
|
+
return pid
|
|
62
|
+
|
|
63
|
+
def release(self, pointer_id: int) -> None:
|
|
64
|
+
"""Release a previously allocated pointer ID back to the pool."""
|
|
65
|
+
if pointer_id in self._active:
|
|
66
|
+
self._active.discard(pointer_id)
|
|
67
|
+
self._freed.append(pointer_id)
|
|
68
|
+
|
|
69
|
+
def reset(self) -> None:
|
|
70
|
+
"""Release all active pointer IDs."""
|
|
71
|
+
self._active.clear()
|
|
72
|
+
self._freed.clear()
|
|
73
|
+
self._next_id = 0
|
|
74
|
+
|
|
75
|
+
def active_count(self) -> int:
|
|
76
|
+
"""Return the number of currently active pointer IDs."""
|
|
77
|
+
return len(self._active)
|
|
78
|
+
|
|
79
|
+
def active_ids(self) -> set[int]:
|
|
80
|
+
"""Return the set of currently active pointer IDs."""
|
|
81
|
+
return set(self._active)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _position(position: Position) -> bytes:
|
|
87
|
+
return (
|
|
88
|
+
i32be(position.x)
|
|
89
|
+
+ i32be(position.y)
|
|
90
|
+
+ u16be(position.screen_width)
|
|
91
|
+
+ u16be(position.screen_height)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def inject_keycode(action: int, keycode: int, repeat: int = 0, metastate: int = 0) -> ControlMessage:
|
|
96
|
+
payload = (
|
|
97
|
+
bytes([ControlMessageType.INJECT_KEYCODE, action])
|
|
98
|
+
+ i32be(keycode)
|
|
99
|
+
+ u32be(repeat)
|
|
100
|
+
+ u32be(metastate)
|
|
101
|
+
)
|
|
102
|
+
return ControlMessage(ControlMessageType.INJECT_KEYCODE, payload)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def inject_text(text: str) -> ControlMessage:
|
|
106
|
+
payload = bytes([ControlMessageType.INJECT_TEXT]) + string32(text, INJECT_TEXT_MAX_LENGTH)
|
|
107
|
+
return ControlMessage(ControlMessageType.INJECT_TEXT, payload)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def inject_touch_event(
|
|
111
|
+
action: int,
|
|
112
|
+
position: Position,
|
|
113
|
+
pointer_id: int = POINTER_ID_GENERIC_FINGER,
|
|
114
|
+
pressure: float = 1.0,
|
|
115
|
+
action_button: int = 0,
|
|
116
|
+
buttons: int = 0,
|
|
117
|
+
) -> ControlMessage:
|
|
118
|
+
payload = (
|
|
119
|
+
bytes([ControlMessageType.INJECT_TOUCH_EVENT, action])
|
|
120
|
+
+ u64be(pointer_id)
|
|
121
|
+
+ _position(position)
|
|
122
|
+
+ u16be(float_to_u16fp(pressure))
|
|
123
|
+
+ u32be(action_button)
|
|
124
|
+
+ u32be(buttons)
|
|
125
|
+
)
|
|
126
|
+
return ControlMessage(ControlMessageType.INJECT_TOUCH_EVENT, payload)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def inject_scroll_event(position: Position, hscroll: float = 0.0, vscroll: float = 0.0, buttons: int = 0) -> ControlMessage:
|
|
130
|
+
h = float_to_i16fp(hscroll / 16)
|
|
131
|
+
v = float_to_i16fp(vscroll / 16)
|
|
132
|
+
payload = (
|
|
133
|
+
bytes([ControlMessageType.INJECT_SCROLL_EVENT])
|
|
134
|
+
+ _position(position)
|
|
135
|
+
+ i16be(h)
|
|
136
|
+
+ i16be(v)
|
|
137
|
+
+ u32be(buttons)
|
|
138
|
+
)
|
|
139
|
+
return ControlMessage(ControlMessageType.INJECT_SCROLL_EVENT, payload)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def back_or_screen_on(action: int) -> ControlMessage:
|
|
143
|
+
return ControlMessage(ControlMessageType.BACK_OR_SCREEN_ON, bytes([ControlMessageType.BACK_OR_SCREEN_ON, action]))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_clipboard(copy_key: int = 0) -> ControlMessage:
|
|
147
|
+
return ControlMessage(ControlMessageType.GET_CLIPBOARD, bytes([ControlMessageType.GET_CLIPBOARD, copy_key]))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def set_clipboard(sequence: int, text: str, paste: bool = False) -> ControlMessage:
|
|
151
|
+
payload = (
|
|
152
|
+
bytes([ControlMessageType.SET_CLIPBOARD])
|
|
153
|
+
+ u64be(sequence)
|
|
154
|
+
+ bytes([1 if paste else 0])
|
|
155
|
+
+ string32(text, CLIPBOARD_TEXT_MAX_LENGTH)
|
|
156
|
+
)
|
|
157
|
+
return ControlMessage(ControlMessageType.SET_CLIPBOARD, payload)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def set_display_power(on: bool) -> ControlMessage:
|
|
161
|
+
return ControlMessage(ControlMessageType.SET_DISPLAY_POWER, bytes([ControlMessageType.SET_DISPLAY_POWER, int(on)]))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def empty(message_type: ControlMessageType) -> ControlMessage:
|
|
165
|
+
if message_type not in {
|
|
166
|
+
ControlMessageType.EXPAND_NOTIFICATION_PANEL,
|
|
167
|
+
ControlMessageType.EXPAND_SETTINGS_PANEL,
|
|
168
|
+
ControlMessageType.COLLAPSE_PANELS,
|
|
169
|
+
ControlMessageType.ROTATE_DEVICE,
|
|
170
|
+
ControlMessageType.OPEN_HARD_KEYBOARD_SETTINGS,
|
|
171
|
+
ControlMessageType.RESET_VIDEO,
|
|
172
|
+
ControlMessageType.CAMERA_ZOOM_IN,
|
|
173
|
+
ControlMessageType.CAMERA_ZOOM_OUT,
|
|
174
|
+
}:
|
|
175
|
+
raise ValueError(f"{message_type.name} is not an empty control message")
|
|
176
|
+
return ControlMessage(message_type, bytes([message_type]))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def camera_set_torch(on: bool) -> ControlMessage:
|
|
180
|
+
return ControlMessage(ControlMessageType.CAMERA_SET_TORCH, bytes([ControlMessageType.CAMERA_SET_TORCH, int(on)]))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def resize_display(width: int, height: int) -> ControlMessage:
|
|
184
|
+
payload = bytes([ControlMessageType.RESIZE_DISPLAY]) + u16be(width) + u16be(height)
|
|
185
|
+
return ControlMessage(ControlMessageType.RESIZE_DISPLAY, payload)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def start_app(name: str) -> ControlMessage:
|
|
189
|
+
payload = bytes([ControlMessageType.START_APP]) + string8(name, 255)
|
|
190
|
+
return ControlMessage(ControlMessageType.START_APP, payload)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def uhid_create(uhid_id: int, vendor_id: int, product_id: int, name: str, report_desc: bytes) -> ControlMessage:
|
|
194
|
+
payload = (
|
|
195
|
+
bytes([ControlMessageType.UHID_CREATE])
|
|
196
|
+
+ u16be(uhid_id)
|
|
197
|
+
+ u16be(vendor_id)
|
|
198
|
+
+ u16be(product_id)
|
|
199
|
+
+ string8(name, 127)
|
|
200
|
+
+ u16be(len(report_desc))
|
|
201
|
+
+ report_desc
|
|
202
|
+
)
|
|
203
|
+
return ControlMessage(ControlMessageType.UHID_CREATE, payload)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def uhid_input(uhid_id: int, data: bytes) -> ControlMessage:
|
|
207
|
+
payload = bytes([ControlMessageType.UHID_INPUT]) + u16be(uhid_id) + u16be(len(data)) + data
|
|
208
|
+
return ControlMessage(ControlMessageType.UHID_INPUT, payload)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def uhid_destroy(uhid_id: int) -> ControlMessage:
|
|
212
|
+
return ControlMessage(ControlMessageType.UHID_DESTROY, bytes([ControlMessageType.UHID_DESTROY]) + u16be(uhid_id))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def deserialize_device_message(buffer: bytes) -> tuple[DeviceMessage | None, int]:
|
|
216
|
+
if not buffer:
|
|
217
|
+
return None, 0
|
|
218
|
+
try:
|
|
219
|
+
message_type = DeviceMessageType(buffer[0])
|
|
220
|
+
except ValueError as exc:
|
|
221
|
+
raise ProtocolError(f"unknown device message type: {buffer[0]}") from exc
|
|
222
|
+
|
|
223
|
+
if message_type is DeviceMessageType.CLIPBOARD:
|
|
224
|
+
if len(buffer) < 5:
|
|
225
|
+
return None, 0
|
|
226
|
+
size = read_u32be(buffer, 1)
|
|
227
|
+
total = 5 + size
|
|
228
|
+
if len(buffer) < total:
|
|
229
|
+
return None, 0
|
|
230
|
+
text = buffer[5:total].decode("utf-8")
|
|
231
|
+
return DeviceMessage(message_type, text=text), total
|
|
232
|
+
|
|
233
|
+
if message_type is DeviceMessageType.ACK_CLIPBOARD:
|
|
234
|
+
if len(buffer) < 9:
|
|
235
|
+
return None, 0
|
|
236
|
+
return DeviceMessage(message_type, sequence=read_u64be(buffer, 1)), 9
|
|
237
|
+
|
|
238
|
+
if len(buffer) < 5:
|
|
239
|
+
return None, 0
|
|
240
|
+
uhid_id = read_u16be(buffer, 1)
|
|
241
|
+
size = read_u16be(buffer, 3)
|
|
242
|
+
total = 5 + size
|
|
243
|
+
if len(buffer) < total:
|
|
244
|
+
return None, 0
|
|
245
|
+
return DeviceMessage(message_type, uhid_id=uhid_id, data=buffer[5:total]), total
|
|
246
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Async stream readers and writers for scrcpy-server v4.0."""
|
|
1
|
+
"""Async stream readers and writers for scrcpy-server v4.0."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -190,6 +190,7 @@ class ControlStream:
|
|
|
190
190
|
self.reader = reader
|
|
191
191
|
self.writer = writer
|
|
192
192
|
self._device_buffer = bytearray()
|
|
193
|
+
self.pointer_manager: control.PointerManager | None = None
|
|
193
194
|
|
|
194
195
|
async def send(self, message: control.ControlMessage) -> None:
|
|
195
196
|
self.writer.write(message.payload)
|
|
@@ -213,6 +214,57 @@ class ControlStream:
|
|
|
213
214
|
position = control.Position(x, y, screen_width, screen_height)
|
|
214
215
|
await self.send(control.inject_touch_event(action, position, pointer_id, pressure, action_button, buttons))
|
|
215
216
|
|
|
217
|
+
async def send_touch_managed(
|
|
218
|
+
self,
|
|
219
|
+
action: int,
|
|
220
|
+
x: int,
|
|
221
|
+
y: int,
|
|
222
|
+
screen_width: int,
|
|
223
|
+
screen_height: int,
|
|
224
|
+
pointer_id: int | None = None,
|
|
225
|
+
pressure: float = 1.0,
|
|
226
|
+
action_button: int = 0,
|
|
227
|
+
buttons: int = 0,
|
|
228
|
+
) -> int | None:
|
|
229
|
+
"""Send a touch event with automatic pointer ID management.
|
|
230
|
+
|
|
231
|
+
On ``ACTION_DOWN``: if *pointer_id* is ``None``, a new ID is
|
|
232
|
+
allocated from ``self.pointer_manager``. The final pointer_id
|
|
233
|
+
is returned (or ``None`` if allocation failed).
|
|
234
|
+
|
|
235
|
+
On ``ACTION_UP``: the given *pointer_id* is released back to
|
|
236
|
+
the pool after sending.
|
|
237
|
+
|
|
238
|
+
When ``self.pointer_manager`` is ``None``, falls back to the
|
|
239
|
+
default ``POINTER_ID_GENERIC_FINGER``.
|
|
240
|
+
"""
|
|
241
|
+
pm = self.pointer_manager
|
|
242
|
+
|
|
243
|
+
if action == control.ACTION_DOWN:
|
|
244
|
+
if pm is not None and pointer_id is None:
|
|
245
|
+
pid = pm.allocate()
|
|
246
|
+
if pid is None:
|
|
247
|
+
return None
|
|
248
|
+
elif pointer_id is not None:
|
|
249
|
+
pid = pointer_id
|
|
250
|
+
else:
|
|
251
|
+
pid = control.POINTER_ID_GENERIC_FINGER
|
|
252
|
+
await self.send_touch(action, x, y, screen_width, screen_height, pointer_id=pid, pressure=pressure, action_button=action_button, buttons=buttons)
|
|
253
|
+
return pid
|
|
254
|
+
|
|
255
|
+
elif action == control.ACTION_UP:
|
|
256
|
+
pid = pointer_id if pointer_id is not None else control.POINTER_ID_GENERIC_FINGER
|
|
257
|
+
await self.send_touch(action, x, y, screen_width, screen_height, pointer_id=pid, pressure=pressure, action_button=action_button, buttons=buttons)
|
|
258
|
+
if pm is not None and pointer_id is not None:
|
|
259
|
+
pm.release(pid)
|
|
260
|
+
return pid
|
|
261
|
+
|
|
262
|
+
# ACTION_MOVE: just forward, no lifecycle management
|
|
263
|
+
pid = pointer_id if pointer_id is not None else control.POINTER_ID_GENERIC_FINGER
|
|
264
|
+
await self.send_touch(action, x, y, screen_width, screen_height, pointer_id=pid, pressure=pressure, action_button=action_button, buttons=buttons)
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
|
|
216
268
|
async def set_clipboard(self, text: str, sequence: int = 1, paste: bool = False) -> None:
|
|
217
269
|
await self.send(control.set_clipboard(sequence, text, paste))
|
|
218
270
|
|
|
@@ -277,3 +329,5 @@ class AudioVideoCombinedStream:
|
|
|
277
329
|
finally:
|
|
278
330
|
for task in tasks:
|
|
279
331
|
task.cancel()
|
|
332
|
+
|
|
333
|
+
|
|
@@ -277,8 +277,9 @@ class HookListener:
|
|
|
277
277
|
user32.TranslateMessage(byref(msg))
|
|
278
278
|
user32.DispatchMessageW(byref(msg))
|
|
279
279
|
except Exception:
|
|
280
|
-
#
|
|
281
|
-
|
|
280
|
+
# 记录异常但不退出循环,确保钩子持续工作
|
|
281
|
+
# 回调中的异常应由上层自行处理
|
|
282
|
+
continue
|
|
282
283
|
|
|
283
284
|
# 离开循环之前确保取消钩子
|
|
284
285
|
if self.keyboard_hook:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "autoxkit"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.1.0"
|
|
8
8
|
description = "Python library for Windows automation and Android device screen casting and control"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/autoxkit-2.0.0/autoxkit/android/control.py → /autoxkit-2.1.0/autoxkit/android/control_backup.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|