outleap 0.6.1__tar.gz → 0.7.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.
Files changed (44) hide show
  1. {outleap-0.6.1 → outleap-0.7.0}/PKG-INFO +3 -2
  2. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/api_wrappers.py +275 -36
  3. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/scripts/agent.py +1 -0
  4. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/scripts/inspector.py +1 -2
  5. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/scripts/repl.py +1 -0
  6. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/version.py +2 -2
  7. {outleap-0.6.1 → outleap-0.7.0}/src/outleap.egg-info/PKG-INFO +3 -2
  8. {outleap-0.6.1 → outleap-0.7.0}/.coveragerc +0 -0
  9. {outleap-0.6.1 → outleap-0.7.0}/.github/workflows/cis.yml +0 -0
  10. {outleap-0.6.1 → outleap-0.7.0}/.github/workflows/pypi_publish.yml +0 -0
  11. {outleap-0.6.1 → outleap-0.7.0}/.gitignore +0 -0
  12. {outleap-0.6.1 → outleap-0.7.0}/COPYING +0 -0
  13. {outleap-0.6.1 → outleap-0.7.0}/MANIFEST.in +0 -0
  14. {outleap-0.6.1 → outleap-0.7.0}/README.md +0 -0
  15. {outleap-0.6.1 → outleap-0.7.0}/codecov.yml +0 -0
  16. {outleap-0.6.1 → outleap-0.7.0}/examples/README.md +0 -0
  17. {outleap-0.6.1 → outleap-0.7.0}/examples/complex_leap_script.py +0 -0
  18. {outleap-0.6.1 → outleap-0.7.0}/examples/simple_leap_script.py +0 -0
  19. {outleap-0.6.1 → outleap-0.7.0}/pyproject.toml +0 -0
  20. {outleap-0.6.1 → outleap-0.7.0}/setup.cfg +0 -0
  21. {outleap-0.6.1 → outleap-0.7.0}/setup.py +0 -0
  22. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/__init__.py +0 -0
  23. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/bridge.py +0 -0
  24. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/client.py +0 -0
  25. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/protocol.py +0 -0
  26. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/qt_helpers.py +0 -0
  27. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/scripts/__init__.py +0 -0
  28. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/scripts/ui/inspector.ui +0 -0
  29. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/ui_elems.py +0 -0
  30. {outleap-0.6.1 → outleap-0.7.0}/src/outleap/utils.py +0 -0
  31. {outleap-0.6.1 → outleap-0.7.0}/src/outleap.egg-info/SOURCES.txt +0 -0
  32. {outleap-0.6.1 → outleap-0.7.0}/src/outleap.egg-info/dependency_links.txt +0 -0
  33. {outleap-0.6.1 → outleap-0.7.0}/src/outleap.egg-info/entry_points.txt +0 -0
  34. {outleap-0.6.1 → outleap-0.7.0}/src/outleap.egg-info/requires.txt +0 -0
  35. {outleap-0.6.1 → outleap-0.7.0}/src/outleap.egg-info/top_level.txt +0 -0
  36. {outleap-0.6.1 → outleap-0.7.0}/static/inspector_screenshot.png +0 -0
  37. {outleap-0.6.1 → outleap-0.7.0}/tests/__init__.py +0 -0
  38. {outleap-0.6.1 → outleap-0.7.0}/tests/test_client.py +0 -0
  39. {outleap-0.6.1 → outleap-0.7.0}/tests/test_protocol.py +0 -0
  40. {outleap-0.6.1 → outleap-0.7.0}/tests/test_stdio.py +0 -0
  41. {outleap-0.6.1 → outleap-0.7.0}/tests/test_ui_elems.py +0 -0
  42. {outleap-0.6.1 → outleap-0.7.0}/tests/test_wrappers.py +0 -0
  43. {outleap-0.6.1 → outleap-0.7.0}/tests/ui_paths.txt +0 -0
  44. {outleap-0.6.1 → outleap-0.7.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: outleap
3
- Version: 0.6.1
3
+ Version: 0.7.0
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
@@ -26,6 +26,7 @@ Provides-Extra: tools
26
26
  Requires-Dist: ptpython<4,>=3; extra == "tools"
27
27
  Requires-Dist: qasync; extra == "tools"
28
28
  Requires-Dist: pyside6-essentials; extra == "tools"
29
+ Dynamic: license-file
29
30
 
30
31
  # outleap
31
32
 
@@ -97,11 +97,11 @@ class LLWindowAPI(LEAPAPIWrapper):
97
97
  path: UI_PATH_TYPE,
98
98
  ) -> Dict:
99
99
  if keycode is not None:
100
- payload = {"keycode": keycode}
100
+ payload: Dict = {"keycode": keycode}
101
101
  elif keysym is not None:
102
- payload = {"keysym": keysym}
102
+ payload: Dict = {"keysym": keysym}
103
103
  elif char is not None:
104
- payload = {"char": char}
104
+ payload: Dict = {"char": char}
105
105
  else:
106
106
  raise ValueError("Didn't have one of keycode, keysym or char")
107
107
 
@@ -163,9 +163,7 @@ class LLWindowAPI(LEAPAPIWrapper):
163
163
 
164
164
  async def get_paths(self, under: Optional[UI_PATH_TYPE] = None) -> List[UIPath]:
165
165
  """Get all UI paths under the root, or under a path if specified"""
166
- if not under:
167
- under = ""
168
- resp = await self._client.command(self._pump_name, "getPaths", {"under": str(under)})
166
+ resp = await self._client.command(self._pump_name, "getPaths", {"under": str(under or "")})
169
167
  if error := resp.get("error"):
170
168
  raise ValueError(error)
171
169
  return [UIPath(path) for path in resp.get("paths", [])]
@@ -185,9 +183,9 @@ class LLWindowAPI(LEAPAPIWrapper):
185
183
  button: str = None,
186
184
  ) -> Dict:
187
185
  if path is not None:
188
- payload = {"path": str(path)}
186
+ payload: Dict = {"path": str(path)}
189
187
  elif x is not None and y is not None:
190
- payload = {"x": x, "y": y}
188
+ payload: Dict = {"x": x, "y": y}
191
189
  else:
192
190
  raise ValueError("Didn't have one of x + y or path")
193
191
 
@@ -381,7 +379,7 @@ class LLAgentAPI(LEAPAPIWrapper):
381
379
  lookat_type: int = 8,
382
380
  ):
383
381
  """Look at either a specific `obj_uuid` or the closest object to `position`"""
384
- payload = {"type": lookat_type}
382
+ payload: Dict = {"type": lookat_type}
385
383
  if obj_uuid:
386
384
  payload["obj_uuid"] = obj_uuid
387
385
  elif position:
@@ -391,9 +389,124 @@ class LLAgentAPI(LEAPAPIWrapper):
391
389
  self._client.void_command(self._pump_name, "lookAt", payload)
392
390
 
393
391
  def get_auto_pilot(self) -> Awaitable[Dict]:
394
- """Get information about current state of the autopilot system"""
392
+ """Get information about the current state of the autopilot system"""
395
393
  return self._client.command(self._pump_name, "getAutoPilot", {})
396
394
 
395
+ def get_agent_screen_pos(self, avatar_id: Optional[uuid.UUID] = None) -> Awaitable[Dict]:
396
+ """Get where the specified agent is on the screen, uses current agent's ID if none provided"""
397
+ payload = {}
398
+ if avatar_id:
399
+ payload["avatar_id"] = avatar_id
400
+ return self._client.command(self._pump_name, "getAgentScreenPos", payload)
401
+
402
+ def get_nearby_avatars(self, dist: Optional[float] = None) -> Awaitable[List[Dict]]:
403
+ payload = {}
404
+ if dist is not None:
405
+ payload["dist"] = dist
406
+ fut = self._client.command(self._pump_name, "getNearbyAvatarsList", payload)
407
+ return _data_unwrapper(fut, "result")
408
+
409
+ def get_nearby_objects(self, dist: Optional[float] = None) -> Awaitable[List[Dict]]:
410
+ payload = {}
411
+ if dist is not None:
412
+ payload["dist"] = dist
413
+ fut = self._client.command(self._pump_name, "getNearbyObjectsList", payload)
414
+ return _data_unwrapper(fut, "result")
415
+
416
+ def get_position(self) -> Awaitable[Dict]:
417
+ """Get details about the agent's position"""
418
+ return self._client.command(self._pump_name, "getPosition", {})
419
+
420
+ def request_sit(
421
+ self,
422
+ obj_uuid: Optional[uuid.UUID] = None,
423
+ position: Optional[Sequence[float]] = None,
424
+ ) -> Awaitable:
425
+ """Request to sit on obj_id, or object closest to position. Sits on ground if no args provided"""
426
+ if position and obj_uuid is not None:
427
+ raise ValueError("obj_uuid and position are mutually exclusive")
428
+
429
+ params = {}
430
+ if obj_uuid:
431
+ params["obj_uuid"] = obj_uuid
432
+ if position:
433
+ params["position"] = list(position)
434
+ return self._client.command(self._pump_name, "requestSit", params)
435
+
436
+ def request_stand(self) -> None:
437
+ self._client.void_command(self._pump_name, "requestStand", {})
438
+
439
+ def request_teleport(
440
+ self,
441
+ region_name: Optional[str] = None,
442
+ x: Optional[int] = None,
443
+ y: Optional[int] = None,
444
+ z: Optional[int] = None,
445
+ skip_confirmation: bool = True,
446
+ ) -> None:
447
+ """Request a teleport from the system, (x,y,z) are global if region_name unspecified"""
448
+ have_coords = all(_ is not None for _ in (x, y, z))
449
+ if not region_name and not have_coords:
450
+ raise ValueError("region_name or (x,y,z) is required")
451
+
452
+ params: Dict = {"skip_confirmation": skip_confirmation}
453
+ if region_name:
454
+ params["regionname"] = region_name
455
+ if have_coords:
456
+ params["x"] = x
457
+ params["y"] = y
458
+ params["z"] = z
459
+
460
+ self._client.void_command(self._pump_name, "requestTeleport", params)
461
+
462
+ def get_id(self) -> Awaitable[uuid.UUID]:
463
+ """Get the current agent's ID"""
464
+ return _data_unwrapper(self._client.command(self._pump_name, "getId", {}), "id")
465
+
466
+ def get_groups(self) -> Awaitable[List[Dict]]:
467
+ return _data_unwrapper(self._client.command(self._pump_name, "getGroups", {}), "groups")
468
+
469
+ def play_animation(self, item_id: uuid.UUID, inworld: bool = True) -> Awaitable:
470
+ """Play an animation by item id"""
471
+ return self._client.command(
472
+ self._pump_name, "playAnimation", {"item_id": item_id, "inworld": inworld}
473
+ )
474
+
475
+ def stop_animation(self, item_id: uuid.UUID) -> Awaitable:
476
+ """Stop an animation by item id"""
477
+ return self._client.command(self._pump_name, "stopAnimation", {"item_id": item_id})
478
+
479
+ def get_animation_info(self, item_id: uuid.UUID) -> Awaitable[Dict]:
480
+ """Get information about an animation by item id"""
481
+ return _data_unwrapper(
482
+ self._client.command(self._pump_name, "getAnimationInfo", {"item_id": item_id}), "anim_info"
483
+ )
484
+
485
+ def set_camera_params(self, params: Dict) -> None:
486
+ """Set camera parameters using LSL-like semantics"""
487
+ self._client.void_command(self._pump_name, "setCameraParams", params)
488
+
489
+ def set_follow_cam_active(self, active: bool) -> None:
490
+ self._client.void_command(self._pump_name, "setFollowCamActive", {"active": active})
491
+
492
+ def remove_camera_params(self) -> None:
493
+ self._client.void_command(self._pump_name, "removeCameraParams", {})
494
+
495
+ def request_touch(
496
+ self,
497
+ obj_uuid: Optional[uuid.UUID] = None,
498
+ position: Optional[Sequence[float]] = None,
499
+ face: int = 0,
500
+ ) -> None:
501
+ if not obj_uuid and not position:
502
+ raise ValueError("Must specify either obj_uuid or position")
503
+ params: Dict = {"face": face}
504
+ if obj_uuid:
505
+ params["obj_uuid"] = obj_uuid
506
+ if position:
507
+ params["position"] = list(position)
508
+ self._client.void_command(self._pump_name, "requestTouch", params)
509
+
397
510
 
398
511
  class LLFloaterRegAPI(LEAPAPIWrapper):
399
512
  PUMP_NAME = "LLFloaterReg"
@@ -466,40 +579,159 @@ class LLFloaterRegAPI(LEAPAPIWrapper):
466
579
  )
467
580
 
468
581
 
469
- class LLPuppetryAPI(LEAPAPIWrapper):
470
- PUMP_NAME = "puppetry"
471
- OP_KEY: str = "command"
582
+ class LLURLDispatcherAPI(LEAPAPIWrapper):
583
+ PUMP_NAME = "LLUrlDispatcher"
584
+
585
+ def dispatch(self, url: str, trusted: bool = True):
586
+ """At startup time or on clicks in internal web browsers, teleport, open map, or run requested command."""
587
+ self._client.void_command(self._pump_name, "dispatch", {"url": url, "trusted": trusted})
588
+
589
+ def dispatch_right_click(self, url: str):
590
+ """Dispatch ["url"] as if from a right-click on a hot link."""
591
+ self._client.void_command(self._pump_name, "dispatchRightClick", {"url": url})
472
592
 
473
- def get_camera(self) -> Awaitable[int]:
474
- """Request camera number: returns ["camera_id"]"""
475
- fut = self._client.command(self._pump_name, "get_camera", {}, op_key=self.OP_KEY)
476
- return _data_unwrapper(fut, "camera_id")
593
+ def dispatch_from_text_editor(self, url: str):
594
+ """Dispatch ["url"] as if from an edit field"""
595
+ self._client.void_command(self._pump_name, "dispatchFromTextEditor", {"url": url})
477
596
 
478
- def set_camera(self, camera_id: int) -> None:
479
- """Request camera number: returns ["camera_id"]"""
480
- payload = {"camera_id": camera_id}
481
- self._client.void_command(self._pump_name, "set_camera", payload, op_key=self.OP_KEY)
482
597
 
483
- def send_skeleton(self) -> None:
598
+ class LLFloaterAboutAPI(LEAPAPIWrapper):
599
+ PUMP_NAME = "LLFloaterAbout"
600
+
601
+ def get_info(self) -> Awaitable[dict]:
602
+ """Request an LLSD::Map containing information used to populate About box"""
603
+ return self._client.command(self._pump_name, "getInfo")
604
+
605
+
606
+ class LLGestureAPI(LEAPAPIWrapper):
607
+ PUMP_NAME = "LLGesture"
608
+
609
+ def get_active_gestures(self) -> Awaitable[list]:
484
610
  """
485
- Request skeleton data
611
+ Return information about the agent's available gestures
486
612
 
487
- Response will be sent over the "puppetry.command" listener as a "set_skeleton"
613
+ Returns a list of dicts with the following dict values for each entry:
614
+ ["name"]: name of the gesture, may be empty
615
+ ["trigger"]: trigger string used to invoke via user chat, may be empty
616
+ ["playing"]: true or false indicating the playing state
488
617
  """
489
- self._client.void_command(self._pump_name, "send_skeleton", {}, op_key=self.OP_KEY)
618
+ fut = self._client.command(self._pump_name, "getActiveGestures")
619
+ return _data_unwrapper(fut, "gestures")
620
+
621
+ def is_gesture_playing(self, gesture_id: uuid.UUID) -> Awaitable[bool]:
622
+ fut = self._client.command(self._pump_name, "isGesturePlaying", {"id": gesture_id})
623
+ return _data_unwrapper(fut, "playing")
624
+
625
+ def start_gesture(self, gesture_id: uuid):
626
+ self._client.void_command(self._pump_name, "startGesture", {"id": gesture_id})
627
+
628
+ def stop_gesture(self, gesture_id: uuid):
629
+ self._client.void_command(self._pump_name, "stopGesture", {"id": gesture_id})
630
+
490
631
 
491
- def move(self, joint_data: Dict[str, Dict]) -> None:
632
+ class GroupChatAPI(LEAPAPIWrapper):
633
+ PUMP_NAME = "GroupChat"
634
+
635
+ def start_im(self, group_id: uuid.UUID) -> Awaitable:
636
+ """Start an IM session for the specified group"""
637
+ return self._client.command(self._pump_name, "startGroupChat", {"group_id": group_id})
638
+
639
+ def end_im(self, group_id: uuid.UUID):
640
+ """End an IM session with the specified group"""
641
+ return self._client.command(self._pump_name, "leaveGroupChat", {"group_id": group_id})
642
+
643
+ def send_im(self, group_id: uuid.UUID, message: str) -> Awaitable:
644
+ """Send an IM to the specified group"""
645
+ return self._client.command(
646
+ self._pump_name, "sendGroupIM", {"group_id": group_id, "message": message}
647
+ )
648
+
649
+
650
+ class LLChatBarAPI(LEAPAPIWrapper):
651
+ PUMP_NAME = "LLChatBar"
652
+
653
+ def send_chat(self, message: str, channel: int = 0, chat_type: str = "normal"):
492
654
  """
493
- Send puppet movement data
494
-
495
- Expected data format:
496
- {'joint_name':{'param_name':[r1.23,r4.56,r7.89]}, ...}
497
- Where:
498
- joint_name = e.g. mWristLeft
499
- param_name = rot | pos | scale | eff
500
- param value = array of 3 floats [x,y,z]
655
+ Send chat to the simulator
656
+
657
+ :param message: chat message text [required]
658
+ :param channel: chat channel number [default = 0]
659
+ :param chat_type: "whisper", "normal", "shout" [default = "normal"]
501
660
  """
502
- self._client.void_command(self._pump_name, "move", joint_data, op_key=self.OP_KEY)
661
+ self._client.void_command(
662
+ self._pump_name, "sendChat", {"message": message, "channel": channel, "chat_type": chat_type}
663
+ )
664
+
665
+
666
+ class LLAppViewerAPI(LEAPAPIWrapper):
667
+ PUMP_NAME = "LLAppViewer"
668
+
669
+ def request_quit(self):
670
+ self._client.void_command(self._pump_name, "requestQuit")
671
+
672
+ def force_quit(self):
673
+ self._client.void_command(self._pump_name, "forceQuit")
674
+
675
+
676
+ class LLTeleportHandlerAPI(LEAPAPIWrapper):
677
+ PUMP_NAME = "LLTeleportHandler"
678
+
679
+ def teleport(
680
+ self,
681
+ region_name: Optional[str] = None,
682
+ x: Optional[int] = None,
683
+ y: Optional[int] = None,
684
+ z: Optional[int] = None,
685
+ ) -> None:
686
+ have_coords = all(_ is not None for _ in (x, y, z))
687
+ if not region_name and not have_coords:
688
+ raise ValueError("region_name or (x,y,z) is required")
689
+
690
+ params = {}
691
+ if region_name:
692
+ params["regionname"] = region_name
693
+ if have_coords:
694
+ params["x"] = x
695
+ params["y"] = y
696
+ params["z"] = z
697
+
698
+ self._client.void_command(self._pump_name, "teleport", params)
699
+
700
+
701
+ class LLAppearanceAPI(LEAPAPIWrapper):
702
+ PUMP_NAME = "LLAppearance"
703
+
704
+ def wear_outfit(
705
+ self,
706
+ folder_id: Optional[uuid.UUID] = None,
707
+ folder_name: Optional[str] = None,
708
+ append: bool = False,
709
+ ) -> Awaitable:
710
+ params: Dict = {"append": append}
711
+ if folder_id:
712
+ params["folder_id"] = folder_id
713
+ if folder_name:
714
+ params["folder_name"] = folder_name
715
+ return self._client.command(self._pump_name, "wearOutfit", params)
716
+
717
+ def wear_items(self, item_ids: Sequence[uuid.UUID], replace: bool = False) -> None:
718
+ self._client.void_command(
719
+ self._pump_name, "wearItems", {"items_id": list(item_ids), "replace": replace}
720
+ )
721
+
722
+ def detach_items(self, item_ids: Sequence[uuid.UUID], replace: bool = False) -> None:
723
+ self._client.void_command(
724
+ self._pump_name, "detachItems", {"items_id": list(item_ids), "replace": replace}
725
+ )
726
+
727
+ def get_outfits_list(self) -> Awaitable[Dict[str, str]]:
728
+ return _data_unwrapper(self._client.command(self._pump_name, "getOutfitsList", {}), "outfits")
729
+
730
+ def get_outfit_items(self, outfit_id: uuid.UUID) -> Awaitable[Dict[str, dict]]:
731
+ return _data_unwrapper(
732
+ self._client.command(self._pump_name, "getOutfitItems", {"outfit_id": outfit_id}),
733
+ "items",
734
+ )
503
735
 
504
736
 
505
737
  __all__ = [
@@ -511,6 +743,13 @@ __all__ = [
511
743
  "LLViewerWindowAPI",
512
744
  "LLCommandDispatcherAPI",
513
745
  "LLFloaterRegAPI",
514
- "LLPuppetryAPI",
746
+ "LLURLDispatcherAPI",
747
+ "LLFloaterAboutAPI",
748
+ "LLGestureAPI",
749
+ "GroupChatAPI",
750
+ "LLChatBarAPI",
751
+ "LLAppViewerAPI",
752
+ "LLTeleportHandlerAPI",
753
+ "LLAppearanceAPI",
515
754
  "LEAPAPIWrapper",
516
755
  ]
@@ -7,6 +7,7 @@ Hint: uncomment https://vcs.firestormviewer.org/phoenix-firestorm/files/cf85e854
7
7
  Usage: While an outleap TCP receiver is running
8
8
  ./firestorm --leap outleap-agent
9
9
  """
10
+
10
11
  import asyncio
11
12
  import multiprocessing
12
13
  import os
@@ -285,8 +285,7 @@ def inspector_main():
285
285
  print(
286
286
  "Running in direct LEAP execution mode.\n"
287
287
  "If you're seeing this anywhere other than the viewer logs, "
288
- "you probably messed up, the viewer should be executing this!\n"
289
- "Try adding a '--tcp' argument!",
288
+ "you probably messed up, the viewer should be executing this!\n",
290
289
  file=sys.stderr,
291
290
  )
292
291
 
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Interactive REPL that handles connect-back connections from outleap-agent
3
3
  """
4
+
4
5
  import asyncio
5
6
  import logging
6
7
  import multiprocessing
@@ -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.6.1".split(".", 3)
8
+ parts = "0.7.0".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.6.1"
19
+ __version__ = "0.7.0"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: outleap
3
- Version: 0.6.1
3
+ Version: 0.7.0
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
@@ -26,6 +26,7 @@ Provides-Extra: tools
26
26
  Requires-Dist: ptpython<4,>=3; extra == "tools"
27
27
  Requires-Dist: qasync; extra == "tools"
28
28
  Requires-Dist: pyside6-essentials; extra == "tools"
29
+ Dynamic: license-file
29
30
 
30
31
  # outleap
31
32
 
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
File without changes
File without changes
File without changes