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 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.get_event_loop().create_task(_pump_messages_forever())
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
- self._parser = llsd.serde_notation.LLSDNotationParser()
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.parse(await self._reader.readexactly(length))
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
@@ -48,8 +48,7 @@ async def amain():
48
48
 
49
49
 
50
50
  def agent_main():
51
- loop = asyncio.get_event_loop_policy().get_event_loop()
52
- loop.run_until_complete(amain())
51
+ asyncio.run(amain())
53
52
 
54
53
 
55
54
  if __name__ == "__main__":
@@ -44,13 +44,15 @@ def temp_file_path():
44
44
  os.remove(f.name)
45
45
 
46
46
 
47
- def _calc_clipped_rect(pix: QtGui.QPixmap, elem: outleap.UIElement) -> QtCore.QRect:
48
- base_rect = _elem_rect_to_qrect(pix, elem.rect)
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
- clipped = pix_screenshot.copy(_calc_clipped_rect(pix_screenshot, elem))
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.get_event_loop_policy().get_event_loop()
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.get_event_loop()
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.get_event_loop()
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.5.1".split(".", 3)
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.5.1"
19
+ __version__ = "0.6.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: outleap
3
- Version: 0.5.1
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
- Requires-Dist: llsd
22
+ License-File: COPYING
23
+ Requires-Dist: llsd ==1.0.0
24
+ Requires-Dist: setuptools
21
25
  Provides-Extra: tools
22
- Requires-Dist: ptpython (<4,>=3) ; extra == 'tools'
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
- loop = asyncio.get_event_loop_policy().get_event_loop()
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,