outleap 0.5.1__py3-none-any.whl → 0.6.1__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.
- outleap/client.py +1 -1
- outleap/protocol.py +28 -2
- outleap/scripts/agent.py +1 -2
- outleap/scripts/inspector.py +13 -6
- outleap/scripts/repl.py +1 -1
- outleap/ui_elems.py +5 -0
- outleap/utils.py +2 -2
- outleap/version.py +2 -2
- {outleap-0.5.1.dist-info → outleap-0.6.1.dist-info}/METADATA +8 -5
- outleap-0.6.1.dist-info/RECORD +20 -0
- {outleap-0.5.1.dist-info → outleap-0.6.1.dist-info}/WHEEL +1 -1
- outleap-0.5.1.dist-info/RECORD +0 -20
- {outleap-0.5.1.dist-info → outleap-0.6.1.dist-info}/COPYING +0 -0
- {outleap-0.5.1.dist-info → outleap-0.6.1.dist-info}/entry_points.txt +0 -0
- {outleap-0.5.1.dist-info → outleap-0.6.1.dist-info}/top_level.txt +0 -0
outleap/client.py
CHANGED
|
@@ -105,7 +105,7 @@ class LEAPClient:
|
|
|
105
105
|
|
|
106
106
|
# Should naturally stop on its own when disconnect is called by virtue of
|
|
107
107
|
# the incomplete read.
|
|
108
|
-
self._msg_pump_task = asyncio.
|
|
108
|
+
self._msg_pump_task = asyncio.create_task(_pump_messages_forever())
|
|
109
109
|
|
|
110
110
|
def disconnect(self) -> None:
|
|
111
111
|
"""Close the connection and clean up any pending request futures"""
|
outleap/protocol.py
CHANGED
|
@@ -23,6 +23,30 @@ class AbstractLEAPProtocol(abc.ABC):
|
|
|
23
23
|
pass
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
LLSD_PARSE_FUNC = Callable[[bytes], Any]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Python uses one, C++ uses the other, and everyone's unhappy.
|
|
30
|
+
_BINARY_HEADERS = (b"<? LLSD/Binary ?>", b"<?llsd/binary?>")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_llsd(something: bytes) -> Any:
|
|
34
|
+
# We need a special parser to remove indra's binary LLSD prefix.
|
|
35
|
+
try:
|
|
36
|
+
something = something.lstrip() # remove any pre-trailing whitespace
|
|
37
|
+
if any(something.startswith(x) for x in _BINARY_HEADERS):
|
|
38
|
+
return llsd.parse_binary(something.split(b"\n", 1)[1])
|
|
39
|
+
# This should be better.
|
|
40
|
+
elif llsd.starts_with(b"<", something):
|
|
41
|
+
return llsd.parse_xml(something)
|
|
42
|
+
else:
|
|
43
|
+
return llsd.parse_notation(something)
|
|
44
|
+
except KeyError as e:
|
|
45
|
+
raise llsd.LLSDParseError("LLSD could not be parsed: %s" % (e,))
|
|
46
|
+
except TypeError as e:
|
|
47
|
+
raise llsd.LLSDParseError("Input stream not of type bytes. %s" % (e,))
|
|
48
|
+
|
|
49
|
+
|
|
26
50
|
class LEAPProtocol(AbstractLEAPProtocol):
|
|
27
51
|
"""Wrapper for communication with a LEAP peer over an asyncio reader/writer pair"""
|
|
28
52
|
|
|
@@ -31,7 +55,9 @@ class LEAPProtocol(AbstractLEAPProtocol):
|
|
|
31
55
|
def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
|
|
32
56
|
self._reader = reader
|
|
33
57
|
self._writer = writer
|
|
34
|
-
|
|
58
|
+
# We could receive any kind of LLSD, so we have to use a parser that
|
|
59
|
+
# can handle anything via content type sniffing.
|
|
60
|
+
self._parser: LLSD_PARSE_FUNC = parse_llsd
|
|
35
61
|
self._formatter = llsd.serde_notation.LLSDNotationFormatter()
|
|
36
62
|
self._drain_task = None
|
|
37
63
|
|
|
@@ -74,7 +100,7 @@ class LEAPProtocol(AbstractLEAPProtocol):
|
|
|
74
100
|
if length > self.PAYLOAD_LIMIT:
|
|
75
101
|
raise ValueError(f"Unreasonable LEAP payload length of {length}")
|
|
76
102
|
# Everything after the colon is LLSD
|
|
77
|
-
parsed = self._parser
|
|
103
|
+
parsed = self._parser(await self._reader.readexactly(length))
|
|
78
104
|
if not isinstance(parsed, dict):
|
|
79
105
|
raise ValueError(f"Expected LEAP message to be a dict, got {parsed!r}")
|
|
80
106
|
return parsed
|
outleap/scripts/agent.py
CHANGED
outleap/scripts/inspector.py
CHANGED
|
@@ -44,13 +44,15 @@ def temp_file_path():
|
|
|
44
44
|
os.remove(f.name)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def _calc_clipped_rect(
|
|
48
|
-
|
|
47
|
+
def _calc_clipped_rect(
|
|
48
|
+
pix: QtGui.QPixmap, elem: outleap.UIElement, scale_factor: float = 1.0
|
|
49
|
+
) -> QtCore.QRect:
|
|
50
|
+
base_rect = _elem_rect_to_qrect(pix, elem.rect * scale_factor)
|
|
49
51
|
while elem := elem.parent:
|
|
50
52
|
# Should also be clipped to the intersection of all parent rects,
|
|
51
53
|
# some scrollers have offscreen rects that can't really be shown
|
|
52
54
|
# in a screenshot.
|
|
53
|
-
parent_rect = _elem_rect_to_qrect(pix, elem.rect)
|
|
55
|
+
parent_rect = _elem_rect_to_qrect(pix, elem.rect * scale_factor)
|
|
54
56
|
base_rect = base_rect.intersected(parent_rect)
|
|
55
57
|
return base_rect
|
|
56
58
|
|
|
@@ -86,8 +88,9 @@ class LEAPInspectorGUI(QtWidgets.QMainWindow):
|
|
|
86
88
|
self._filter = ""
|
|
87
89
|
|
|
88
90
|
self.interaction_manager = GUIInteractionManager(self)
|
|
89
|
-
self.window_api = outleap.LLWindowAPI(client)
|
|
91
|
+
self.window_api = outleap.LLWindowAPI(self.client)
|
|
90
92
|
self.viewer_window_api = outleap.LLViewerWindowAPI(self.client)
|
|
93
|
+
self.viewer_control_api = outleap.LLViewerControlAPI(self.client)
|
|
91
94
|
self._element_tree = outleap.UIElementTree(self.window_api)
|
|
92
95
|
self._items_by_path: Dict[UIPath, QtWidgets.QTreeWidgetItem] = weakref.WeakValueDictionary() # noqa
|
|
93
96
|
|
|
@@ -192,6 +195,9 @@ class LEAPInspectorGUI(QtWidgets.QMainWindow):
|
|
|
192
195
|
self._addChildren(node.children, item)
|
|
193
196
|
parent.addChildren(items)
|
|
194
197
|
|
|
198
|
+
async def _getUIScaleFactor(self) -> float:
|
|
199
|
+
return (await self.viewer_control_api.get("Global", "UIScaleFactor"))["value"]
|
|
200
|
+
|
|
195
201
|
@asyncSlot()
|
|
196
202
|
async def updateSelectedElemInfo(self, *args):
|
|
197
203
|
self.textElemProperties.setPlainText("")
|
|
@@ -228,7 +234,7 @@ class LEAPInspectorGUI(QtWidgets.QMainWindow):
|
|
|
228
234
|
pix_screenshot.load(path)
|
|
229
235
|
|
|
230
236
|
# Clip scene and view to elem rect
|
|
231
|
-
scene_rect = _calc_clipped_rect(pix_screenshot, elem)
|
|
237
|
+
scene_rect = _calc_clipped_rect(pix_screenshot, elem, await self._getUIScaleFactor())
|
|
232
238
|
self.sceneElemScreenshot.addPixmap(pix_screenshot)
|
|
233
239
|
self.sceneElemScreenshot.setSceneRect(scene_rect)
|
|
234
240
|
self.graphicsElemScreenshot.fitInView(scene_rect, QtCore.Qt.KeepAspectRatio)
|
|
@@ -251,7 +257,8 @@ class LEAPInspectorGUI(QtWidgets.QMainWindow):
|
|
|
251
257
|
await self.viewer_window_api.save_snapshot(path)
|
|
252
258
|
pix_screenshot.load(path)
|
|
253
259
|
# Make a clipped copy of the screenshot
|
|
254
|
-
|
|
260
|
+
scale_factor = await self._getUIScaleFactor()
|
|
261
|
+
clipped = pix_screenshot.copy(_calc_clipped_rect(pix_screenshot, elem, scale_factor))
|
|
255
262
|
|
|
256
263
|
file_name = await self.interaction_manager.save_file(
|
|
257
264
|
caption="Save Rendered Element", filter_str="PNG Images (*.png)", default_suffix="png"
|
outleap/scripts/repl.py
CHANGED
|
@@ -50,7 +50,7 @@ class REPLServer:
|
|
|
50
50
|
|
|
51
51
|
def repl_main():
|
|
52
52
|
logging.basicConfig()
|
|
53
|
-
loop = asyncio.
|
|
53
|
+
loop = asyncio.new_event_loop()
|
|
54
54
|
repl_server = REPLServer()
|
|
55
55
|
server = outleap.LEAPBridgeServer(repl_server.client_connected)
|
|
56
56
|
coro = asyncio.start_server(server.handle_connection, "127.0.0.1", 9063)
|
outleap/ui_elems.py
CHANGED
|
@@ -102,6 +102,11 @@ class UIRect(NamedTuple):
|
|
|
102
102
|
right: int
|
|
103
103
|
top: int
|
|
104
104
|
|
|
105
|
+
def __mul__(self, factor: float) -> UIRect:
|
|
106
|
+
return UIRect(
|
|
107
|
+
*map(round, (self.bottom * factor, self.left * factor, self.right * factor, self.top * factor))
|
|
108
|
+
)
|
|
109
|
+
|
|
105
110
|
|
|
106
111
|
@dataclasses.dataclass
|
|
107
112
|
class UIElementInfo:
|
outleap/utils.py
CHANGED
|
@@ -82,7 +82,7 @@ async def _make_hacky_threaded_stdio_rw() -> Tuple[asyncio.StreamReader, asyncio
|
|
|
82
82
|
#
|
|
83
83
|
# TODO: Currently we also use this if we're reading from a non-pipe file descriptor on POSIX
|
|
84
84
|
# platforms, but that's probably unnecessary.
|
|
85
|
-
loop = asyncio.
|
|
85
|
+
loop = asyncio.get_running_loop()
|
|
86
86
|
reader = asyncio.StreamReader(limit=_READER_BUFFER_LIMIT)
|
|
87
87
|
protocol = HackySTDIOProtocol()
|
|
88
88
|
transport = HackySTDIOTransport()
|
|
@@ -157,7 +157,7 @@ async def connect_stdin_stdout() -> Tuple[asyncio.StreamReader, asyncio.StreamWr
|
|
|
157
157
|
need_stdin_hack = True
|
|
158
158
|
if need_stdin_hack:
|
|
159
159
|
return await _make_hacky_threaded_stdio_rw()
|
|
160
|
-
loop = asyncio.
|
|
160
|
+
loop = asyncio.get_running_loop()
|
|
161
161
|
reader = asyncio.StreamReader(limit=_READER_BUFFER_LIMIT)
|
|
162
162
|
protocol = asyncio.StreamReaderProtocol(reader)
|
|
163
163
|
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
|
outleap/version.py
CHANGED
|
@@ -5,7 +5,7 @@ from collections import namedtuple
|
|
|
5
5
|
#: A namedtuple of the version info for the current release.
|
|
6
6
|
_version_info = namedtuple("_version_info", "major minor micro status")
|
|
7
7
|
|
|
8
|
-
parts = "0.
|
|
8
|
+
parts = "0.6.1".split(".", 3)
|
|
9
9
|
version_info = _version_info(
|
|
10
10
|
int(parts[0]),
|
|
11
11
|
int(parts[1]),
|
|
@@ -16,4 +16,4 @@ version_info = _version_info(
|
|
|
16
16
|
# Remove everything but the 'version_info' from this module.
|
|
17
17
|
del namedtuple, _version_info, parts
|
|
18
18
|
|
|
19
|
-
__version__ = "0.
|
|
19
|
+
__version__ = "0.6.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: outleap
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Tools for using asyncio to control a Second Life viewer over the LEAP protocol
|
|
5
5
|
Author-email: Salad Dais <83434023+SaladDais@users.noreply.github.com>
|
|
6
6
|
License: MIT License
|
|
@@ -15,11 +15,15 @@ Classifier: Programming Language :: Python :: 3
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.8
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
20
|
Requires-Python: >=3.8
|
|
19
21
|
Description-Content-Type: text/markdown
|
|
20
|
-
|
|
22
|
+
License-File: COPYING
|
|
23
|
+
Requires-Dist: llsd ==1.0.0
|
|
24
|
+
Requires-Dist: setuptools
|
|
21
25
|
Provides-Extra: tools
|
|
22
|
-
Requires-Dist: ptpython
|
|
26
|
+
Requires-Dist: ptpython <4,>=3 ; extra == 'tools'
|
|
23
27
|
Requires-Dist: qasync ; extra == 'tools'
|
|
24
28
|
Requires-Dist: pyside6-essentials ; extra == 'tools'
|
|
25
29
|
|
|
@@ -59,8 +63,7 @@ async def amain():
|
|
|
59
63
|
print(await viewer_control_api.get("Global", "StatsPilotFile"), file=sys.stderr)
|
|
60
64
|
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
loop.run_until_complete(amain())
|
|
66
|
+
asyncio.run(amain())
|
|
64
67
|
```
|
|
65
68
|
|
|
66
69
|
If you just want to play around with the LEAP APIs:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
outleap/__init__.py,sha256=h0VTP95rggZ-iFH1Ke4rh85fiU3BTvfj_jkLW27Ln1k,689
|
|
2
|
+
outleap/api_wrappers.py,sha256=Ch3igDgvgjft2NcMbFlp9fwt3rsGenqsPyI6Kme6W58,17434
|
|
3
|
+
outleap/bridge.py,sha256=Q5tku2JP-K0wT9zVA8OyJpFEpumgbUgHaC1YeRQR8vE,1016
|
|
4
|
+
outleap/client.py,sha256=I-hVicIEBKcfZBRuG3hQUO-N-pbmfLECQfpLl7323IQ,14404
|
|
5
|
+
outleap/protocol.py,sha256=HeCnlzszGp6Yxgq__34KYYuii6tpxnfUi5La9SFxw0k,3725
|
|
6
|
+
outleap/qt_helpers.py,sha256=qpdEx7y60T3nAJ4GFDvpcefKEgpgBBnt8dQ-c2IB-Zc,7766
|
|
7
|
+
outleap/ui_elems.py,sha256=ndi3FDp_fu1rVD3rNpFp6hs8eU53U4D_dCtuwFmums0,11577
|
|
8
|
+
outleap/utils.py,sha256=Ad1-mAU0ButNp8n4KeXQxxNa7AtFRuZWD5L2RVS5vLM,5863
|
|
9
|
+
outleap/version.py,sha256=QgdOd1nYQiSrjWXxPqJWSY5Vb7JsM1XNJYCbm7I4OE4,517
|
|
10
|
+
outleap/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
outleap/scripts/agent.py,sha256=Z_blVpeZ8JH6gv1M3_Wq8iCXxgdikTgAu7JEpx-Uxfk,1983
|
|
12
|
+
outleap/scripts/inspector.py,sha256=dNBk9i6kKuueZ7Jk5jEc_1cQ5vy9EY8ZL80oZHA8HMo,10991
|
|
13
|
+
outleap/scripts/repl.py,sha256=BC49hGBkshTRdKtzHM7Hos6nHSFq-cLV_Sw7swjqXTA,1720
|
|
14
|
+
outleap/scripts/ui/inspector.ui,sha256=CY-Tczsrpmc2groE6_7s_fIc7de6Oy2TuU2a4ZGzl7o,7269
|
|
15
|
+
outleap-0.6.1.dist-info/COPYING,sha256=OPIPYHZUdL5ylRYaujIVg05CSvX4-AHW15qBGYz_fyY,1093
|
|
16
|
+
outleap-0.6.1.dist-info/METADATA,sha256=gKfDJWk4bSqjbMFbqA1NExKtTgtJn1eks17_b1RhJJQ,4802
|
|
17
|
+
outleap-0.6.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
18
|
+
outleap-0.6.1.dist-info/entry_points.txt,sha256=gi7UECBDlCTzn0jUU8xPvyMm_8lOQMZUJuNOe266I48,174
|
|
19
|
+
outleap-0.6.1.dist-info/top_level.txt,sha256=dIQJmMy2IulmIzvBWuz8dup0dm9MV0b4p_hCtBRewfE,8
|
|
20
|
+
outleap-0.6.1.dist-info/RECORD,,
|
outleap-0.5.1.dist-info/RECORD
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
outleap/__init__.py,sha256=h0VTP95rggZ-iFH1Ke4rh85fiU3BTvfj_jkLW27Ln1k,689
|
|
2
|
-
outleap/api_wrappers.py,sha256=Ch3igDgvgjft2NcMbFlp9fwt3rsGenqsPyI6Kme6W58,17434
|
|
3
|
-
outleap/bridge.py,sha256=Q5tku2JP-K0wT9zVA8OyJpFEpumgbUgHaC1YeRQR8vE,1016
|
|
4
|
-
outleap/client.py,sha256=tM6SrGSSMuZ2bS7Py_3Tn-KbLx-JZB7zKFB5C3f3umQ,14421
|
|
5
|
-
outleap/protocol.py,sha256=dFIE-tnHMUPlr5u8AnSm-WA55e8QIsWeQ5MJSACgVA4,2715
|
|
6
|
-
outleap/qt_helpers.py,sha256=qpdEx7y60T3nAJ4GFDvpcefKEgpgBBnt8dQ-c2IB-Zc,7766
|
|
7
|
-
outleap/ui_elems.py,sha256=Jq3KkpnvLH_Hh0-fzHkVbmauHoVguwV4ySHa-EsroAE,11387
|
|
8
|
-
outleap/utils.py,sha256=RkovmLsFsmdOSI3Cw_uGVhve58nh9TABFfiwKDhnFvQ,5859
|
|
9
|
-
outleap/version.py,sha256=MvOGqIQegsePzZXtW6-YCEVXqWZLgy7240EJScwKvDc,517
|
|
10
|
-
outleap/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
outleap/scripts/agent.py,sha256=9wJM9pSjKihsbX3C0B07n9T5BuU9aiivOXMVItY95eg,2055
|
|
12
|
-
outleap/scripts/inspector.py,sha256=87sEi8Wt1QLyaxCEQR98ZknQupbi1uMYgY5f9Vfou04,10613
|
|
13
|
-
outleap/scripts/repl.py,sha256=TAvemMlhdJSqDDL6mqDvSlAerBk25deKYx-s-W0T8z0,1744
|
|
14
|
-
outleap/scripts/ui/inspector.ui,sha256=CY-Tczsrpmc2groE6_7s_fIc7de6Oy2TuU2a4ZGzl7o,7269
|
|
15
|
-
outleap-0.5.1.dist-info/COPYING,sha256=OPIPYHZUdL5ylRYaujIVg05CSvX4-AHW15qBGYz_fyY,1093
|
|
16
|
-
outleap-0.5.1.dist-info/METADATA,sha256=EjHcAq3BAuNXzmO0DaMXZz2N5b3p3c6-ZdZgLdxZ74s,4714
|
|
17
|
-
outleap-0.5.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
18
|
-
outleap-0.5.1.dist-info/entry_points.txt,sha256=gi7UECBDlCTzn0jUU8xPvyMm_8lOQMZUJuNOe266I48,174
|
|
19
|
-
outleap-0.5.1.dist-info/top_level.txt,sha256=dIQJmMy2IulmIzvBWuz8dup0dm9MV0b4p_hCtBRewfE,8
|
|
20
|
-
outleap-0.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|