python-libei 0.8.0__py3-none-any.whl → 0.8.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.
libei/__init__.py CHANGED
@@ -31,6 +31,6 @@ the full breakdown, including which features need which libei version.
31
31
  Beta: the API is not frozen.
32
32
  """
33
33
 
34
- __version__ = "0.8.0"
34
+ __version__ = "0.8.1"
35
35
 
36
36
  __all__ = ["__version__"]
libei/_capi/libei.py CHANGED
@@ -219,7 +219,13 @@ region_get_mapping_id = lib.function( # libei 1.1+
219
219
  "ei_region_get_mapping_id", (c_void_p,), c_char_p
220
220
  )
221
221
  # x and y are in/out parameters, overwritten only when the point is inside.
222
- region_convert_point = lib.function( # libei 1.1+
222
+ # No version marker: this one is in libei 1.0.0's own public header and
223
+ # implemented there, so every libei at or above the declared floor exports
224
+ # it. It carried `# libei 1.1+` alongside its genuine 1.1 neighbours
225
+ # `ei_region_get_mapping_id` and `ei_device_region_at`, and the weekly
226
+ # floor job caught it -- building 1.0.0 from source found the symbol
227
+ # present where the marker promised it would be absent.
228
+ region_convert_point = lib.function(
223
229
  "ei_region_convert_point",
224
230
  (c_void_p, POINTER(c_double), POINTER(c_double)),
225
231
  c_bool,
libei/ei.py CHANGED
@@ -372,8 +372,7 @@ class Region(CObject):
372
372
 
373
373
  Returns the point with the region's offset subtracted, or ``None``
374
374
  if it falls outside the region -- which is also how you test
375
- membership without a second :meth:`contains` call. Requires
376
- libei 1.1.
375
+ membership without a second :meth:`contains` call.
377
376
  """
378
377
  # x/y are in-out parameters: libei overwrites them only when the
379
378
  # point is inside, so the return value has to gate reading them.
@@ -1127,7 +1126,10 @@ class Context(CObject):
1127
1126
  pointer = _capi.libei.get_event(self)
1128
1127
  if not pointer:
1129
1128
  break
1130
- event = Event.wrap(pointer)
1129
+ # adopt(), not wrap(): ei_get_event() transfers ownership of
1130
+ # the returned reference to the caller, and the unref in
1131
+ # release() below is the one that balances it.
1132
+ event = Event.adopt(pointer)
1131
1133
  assert event is not None
1132
1134
  # try/finally, not a bare call after yield: breaking out of a
1133
1135
  # `for event in ctx.events:` loop (or an exception propagating
libei/eis.py CHANGED
@@ -944,7 +944,18 @@ class Event(CObject):
944
944
 
945
945
  @property
946
946
  def emulating_sequence(self) -> int:
947
- """Sequence number of the start_emulating transaction."""
947
+ """Sequence number of the start_emulating transaction.
948
+
949
+ Reads 0 on libei older than 1.6, whatever the client sent. libeis
950
+ queued the event without ever copying the sequence into it, so the
951
+ getter returned a zeroed struct field; upstream added the missing
952
+ assignment in libei 1.6.0. Every version from this package's 1.0.0
953
+ floor up exports the symbol, so there is nothing to raise and no
954
+ way to tell an old library apart from a client that genuinely sent
955
+ 0 -- treat a 0 here as "unknown" unless you know both ends are on
956
+ 1.6. :attr:`libei.ei.Event.emulating_sequence`, the receiver side
957
+ of the same transaction, is unaffected: libei has always stored it.
958
+ """
948
959
  self._require("emulating_sequence", EventType.DEVICE_START_EMULATING)
949
960
  return _capi.libeis.event_emulating_get_sequence(self)
950
961
 
@@ -1150,7 +1161,10 @@ class Eis(CObject):
1150
1161
  pointer = _capi.libeis.get_event(self)
1151
1162
  if not pointer:
1152
1163
  break
1153
- event = Event.wrap(pointer)
1164
+ # adopt(), not wrap(): eis_get_event() transfers ownership of
1165
+ # the returned reference to the caller, and the unref in
1166
+ # release() below is the one that balances it.
1167
+ event = Event.adopt(pointer)
1154
1168
  assert event is not None
1155
1169
  # See ei.Context.events: try/finally so release() still runs
1156
1170
  # if the caller breaks out of the loop (GeneratorExit at the
@@ -1168,9 +1182,11 @@ class Eis(CObject):
1168
1182
  def set_flag(self, flag: Flag) -> None:
1169
1183
  """Change this context's protocol behavior. Requires libei 1.6.
1170
1184
 
1171
- Must be called before the backend is set up, so in practice
1172
- before :meth:`create_for_fd` / :meth:`create_for_socket` -- which
1173
- also means this is only reachable on a context built by hand.
1185
+ Must be called before the backend is set up, so the supported
1186
+ way to use it is the ``flags`` parameter of
1187
+ :meth:`create_for_fd` / :meth:`create_for_socket`, which apply
1188
+ each flag at exactly that point; calling it directly only works
1189
+ on a context built by hand, before its backend is set up.
1174
1190
  Takes one flag, never a bitmask; call it again for another.
1175
1191
  """
1176
1192
  result = _capi.libeis.set_flag(self, flag)
libei/portal.py CHANGED
@@ -207,6 +207,14 @@ class PortalProtocolError(PortalError):
207
207
  default message names the expected shape), a phrase for a value fault
208
208
  (naming the field). ``payload`` is whatever best shows the fault -- the
209
209
  whole signal tuple for a shape fault, the options dict for a value one.
210
+
211
+ :func:`_request` raises it the same way for a ``Request``'s
212
+ ``Response`` reply that does not match its own ``(u response, a{sv}
213
+ results)`` shape, and :meth:`InputCaptureSession.zones` for a result
214
+ value that is not the type the spec gives it -- always with a
215
+ ``detail`` phrase, since the default shape message above is specific
216
+ to the session signals. ``step`` is then the method name, matching the
217
+ sibling errors raised from the same call.
210
218
  """
211
219
 
212
220
  def __init__(self, step: str, payload: object, detail: str | None = None) -> None:
@@ -457,6 +465,18 @@ def _request(
457
465
  for 60 seconds means the answer arrives inside 60 seconds, not inside
458
466
  however many 60-second waits the sequence happens to be built from.
459
467
 
468
+ A ``Response`` whose payload does not match the spec's ``(u response,
469
+ a{sv} results)`` shape raises :class:`PortalProtocolError` rather than
470
+ being unpacked on faith -- the same hardening `_wait_for_signal`
471
+ applies to the signals it waits on, and for the same two reasons: a
472
+ wrong-arity payload used to raise inside the GLib callback, where PyGI
473
+ swallows it, degrading the step to a full-timeout
474
+ :class:`PortalTimeoutError`; a non-dict ``results`` instead slipped
475
+ through to the caller, whose ``.get()`` crashed with a raw
476
+ ``AttributeError`` past ``except PortalError``. The guarantee callers
477
+ rely on is therefore structural: what this returns is always an
478
+ ``int`` code and a ``dict`` of results.
479
+
460
480
  ``trailing_args``, appended after ``options`` in the call's parameter
461
481
  tuple, exists for ``InputCapture.SetPointerBarriers`` -- the one
462
482
  Request-returning method in either portal whose ``options`` is not its
@@ -474,12 +494,28 @@ def _request(
474
494
 
475
495
  loop = GLib.MainLoop()
476
496
  result: dict[str, Any] = {}
497
+ malformed: dict[str, Any] = {}
477
498
  subscriptions: list[Any] = []
478
499
  # Set inside the timeout callback rather than inferred from an empty
479
500
  # `result` afterwards: a Response carrying no results is legitimate
480
501
  # (SelectDevices answers with an empty dict), so "did the loop end
481
502
  # because it timed out" has to be recorded, not deduced.
482
503
  timed_out = False
504
+ # Distinct from `timed_out`: whether `on_timeout` ran at all, either
505
+ # path -- the flag the cleanup below keys "already removed?" on. See
506
+ # the matching pair in `_wait_for_signal`.
507
+ timeout_fired = False
508
+
509
+ def _settled() -> bool:
510
+ """Whether some terminal outcome has already been recorded.
511
+
512
+ Same role as in `_wait_for_signal`: GLib dispatches every source
513
+ ready in one main-context iteration regardless of an earlier
514
+ ``loop.quit()``, so a Response and the deadline can both fire in
515
+ the same poll -- the first recorded outcome wins, and a delivered
516
+ reply is never dropped for a timeout (or vice versa).
517
+ """
518
+ return bool(result) or bool(malformed) or timed_out
483
519
 
484
520
  def on_response(
485
521
  _conn: Any,
@@ -491,14 +527,42 @@ def _request(
491
527
  *_a: Any,
492
528
  ) -> None:
493
529
  """Record the first reply and quit the loop; later replies are ignored."""
494
- if result: # both subscriptions may fire; the first reply wins
530
+ if _settled(): # both subscriptions may fire; the first reply wins
495
531
  return
496
- result["code"], result["results"] = params.unpack()
532
+ args = params.unpack()
533
+ # Checked here, raised only after the loop: this runs inside a
534
+ # GLib callback, where PyGI prints an exception to stderr and
535
+ # swallows it -- a bare raise on a malformed Response would leave
536
+ # the loop running until the full timeout, misreported as
537
+ # PortalTimeoutError; a non-dict `results` let through instead
538
+ # crashed the caller's `.get()` with a raw AttributeError past
539
+ # `except PortalError`. Same hardening `_wait_for_signal` applies
540
+ # to the signals it waits on.
541
+ if (
542
+ not isinstance(args, tuple)
543
+ or len(args) != 2
544
+ or isinstance(args[0], bool)
545
+ or not isinstance(args[0], int)
546
+ or not isinstance(args[1], dict)
547
+ ):
548
+ malformed["args"] = args
549
+ loop.quit()
550
+ return
551
+ result["code"], result["results"] = args
497
552
  loop.quit()
498
553
 
499
554
  def on_timeout() -> bool:
500
555
  """Stop the loop and record that it timed out rather than finished."""
501
- nonlocal timed_out
556
+ nonlocal timed_out, timeout_fired
557
+ # Set first, before the early return: GLib removes the source the
558
+ # moment this returns False (which both paths do), so the finally
559
+ # block keys "already removed" on this, not on `timed_out` -- see
560
+ # the same dance in `_wait_for_signal`.
561
+ timeout_fired = True
562
+ if _settled():
563
+ # A Response delivered in the same poll as the deadline has
564
+ # already recorded its outcome; do not overwrite it.
565
+ return False
502
566
  timed_out = True
503
567
  loop.quit()
504
568
  return False # one-shot; GLib removes the source when this is False
@@ -542,12 +606,14 @@ def _request(
542
606
  handle = _returned_handle(reply)
543
607
  if handle is not None and handle != expected_path:
544
608
  subscribe(handle)
545
- # `if not result` because a synchronous answer (a fast
609
+ # `if not _settled()` because a synchronous answer (a fast
546
610
  # non-interactive Response, or a test double) can arrive during the
547
611
  # call above, before run() is reached -- and quit() on a loop that
548
612
  # is not running yet does not stop the later run(), so running it
549
- # then would block with the reply already delivered.
550
- if not result:
613
+ # then would block with the reply already delivered. A malformed
614
+ # synchronous reply counts: it too has already quit a loop that
615
+ # was not running yet.
616
+ if not _settled():
551
617
  timeout_source = GLib.timeout_add(_msec_until(deadline), on_timeout)
552
618
  try:
553
619
  loop.run()
@@ -559,12 +625,27 @@ def _request(
559
625
  # fixed for _wait_for_signal below. Only remove it when the
560
626
  # *Response* woke the loop instead; leaving a live source in
561
627
  # that case holds a reference to this closure and fires it
562
- # into a dead loop on some later request.
563
- if not timed_out:
628
+ # into a dead loop on some later request. Keyed on
629
+ # `timeout_fired` (did on_timeout run), not `timed_out` (did
630
+ # the wait time out): a Response landing in the same poll as
631
+ # the deadline runs on_timeout down its `_settled` early
632
+ # return, which removes the source yet leaves `timed_out`
633
+ # False.
634
+ if not timeout_fired:
564
635
  GLib.source_remove(timeout_source)
565
636
  finally:
566
637
  for subscription in subscriptions:
567
638
  connection.signal_unsubscribe(subscription)
639
+ # Mirrors `_signal_outcome`: only one of these is ever set (every
640
+ # callback checks `_settled()` first), so the order here only decides
641
+ # between genuinely simultaneous dispatches.
642
+ if malformed:
643
+ raise PortalProtocolError(
644
+ method,
645
+ malformed["args"],
646
+ "the Request's Response signal is specified as "
647
+ "(uint response, results dict)",
648
+ )
568
649
  if timed_out:
569
650
  raise PortalTimeoutError(method, timeout)
570
651
  return result["code"], result["results"]
@@ -1100,6 +1181,23 @@ def _classify_signal_payload(
1100
1181
  return "take"
1101
1182
 
1102
1183
 
1184
+ def _spec_int(value: Any) -> int:
1185
+ """``int(value)`` for a spec ``u``/``i``, refusing a silent truncation.
1186
+
1187
+ A portal integer field arrives as a Python ``int``. A malformed portal
1188
+ that put a ``d`` (double) there would still reach ``int()``, and
1189
+ ``int(0.9) == 0`` / ``int(1.9) == 1`` truncates it *silently* -- a zone
1190
+ coordinate or a refused barrier id quietly becomes a different,
1191
+ valid-looking value rather than an error. Reject any ``float``
1192
+ (``inf``/``nan`` included) with the ``ValueError`` the surrounding
1193
+ guards already turn into a :class:`PortalProtocolError`, so an off-spec
1194
+ fractional value is refused on the same footing as a non-numeric one.
1195
+ """
1196
+ if isinstance(value, float):
1197
+ raise ValueError("a fractional value where the spec gives an integer")
1198
+ return int(value)
1199
+
1200
+
1103
1201
  def _deactivation_id(options: dict[str, Any], signal: str) -> int:
1104
1202
  """The ``activation_id`` from Deactivated's options, as the spec's ``u``.
1105
1203
 
@@ -1107,13 +1205,12 @@ def _deactivation_id(options: dict[str, Any], signal: str) -> int:
1107
1205
  dict -- but not the types of the values in it. ``activation_id`` is a
1108
1206
  ``u``, and a non-conforming portal that put something else there would
1109
1207
  crash the caller with ``ValueError``/``TypeError``/``OverflowError``
1110
- (the last from ``int(float('inf'))``) past the shape check. Converting
1111
- it here turns that into the same typed :class:`PortalProtocolError` the
1112
- shape check raises, so a caller that guards against malformed signals
1113
- catches one error, not two.
1208
+ past the shape check. Converting it here turns that into the same typed
1209
+ :class:`PortalProtocolError` the shape check raises, so a caller that
1210
+ guards against malformed signals catches one error, not two.
1114
1211
  """
1115
1212
  try:
1116
- return int(options.get("activation_id", 0))
1213
+ return _spec_int(options.get("activation_id", 0))
1117
1214
  except (ValueError, TypeError, OverflowError) as exc:
1118
1215
  raise PortalProtocolError(
1119
1216
  signal, options, "activation_id is not an integer"
@@ -1134,7 +1231,7 @@ def _activation_from_options(options: dict[str, Any], signal: str) -> Activation
1134
1231
  fourth hand-rolled check nobody remembers to write.
1135
1232
  """
1136
1233
  try:
1137
- activation_id = int(options.get("activation_id", 0))
1234
+ activation_id = _spec_int(options.get("activation_id", 0))
1138
1235
  cursor = options.get("cursor_position")
1139
1236
  if cursor is None:
1140
1237
  cursor_position = None
@@ -1142,7 +1239,7 @@ def _activation_from_options(options: dict[str, Any], signal: str) -> Activation
1142
1239
  cx, cy = cursor # (dd): exactly two, or this unpacking raises
1143
1240
  cursor_position = (float(cx), float(cy))
1144
1241
  barrier = options.get("barrier_id")
1145
- barrier_id = None if barrier is None else int(barrier)
1242
+ barrier_id = None if barrier is None else _spec_int(barrier)
1146
1243
  except (ValueError, TypeError, OverflowError) as exc:
1147
1244
  raise PortalProtocolError(
1148
1245
  signal, options, "an option value is not the type the spec gives it"
@@ -1608,9 +1705,26 @@ class InputCaptureSession:
1608
1705
  )
1609
1706
  if code != 0:
1610
1707
  raise PortalDeniedError("GetZones")
1611
- zone_set = results.get("zone_set", 0)
1612
- zones = [tuple(z) for z in results.get("zones", [])]
1613
- return int(zone_set), zones
1708
+ # `results` is a dict by `_request`'s own guarantee; the *values*
1709
+ # are still the portal's to malform. The spec types them --
1710
+ # `zone_set` is a `u`, `zones` an `a(uuii)` -- so convert them
1711
+ # under one guard, the same way `_activation_from_options` covers
1712
+ # the Activated signal's values: a non-conforming value becomes
1713
+ # the typed PortalProtocolError rather than a raw
1714
+ # TypeError/ValueError past `except PortalError` in the caller.
1715
+ try:
1716
+ zone_set = _spec_int(results.get("zone_set", 0))
1717
+ zones = [
1718
+ (_spec_int(w), _spec_int(h), _spec_int(x), _spec_int(y))
1719
+ for w, h, x, y in results.get("zones", [])
1720
+ ]
1721
+ except (ValueError, TypeError, OverflowError) as exc:
1722
+ raise PortalProtocolError(
1723
+ "GetZones",
1724
+ results,
1725
+ "a result value is not the type the spec gives it",
1726
+ ) from exc
1727
+ return zone_set, zones
1614
1728
 
1615
1729
  def set_pointer_barriers(
1616
1730
  self,
@@ -1659,7 +1773,19 @@ class InputCaptureSession:
1659
1773
  )
1660
1774
  if code != 0:
1661
1775
  raise PortalDeniedError("SetPointerBarriers")
1662
- failed = list(results.get("failed_barriers", []))
1776
+ # `results` is a dict by `_request`'s own guarantee; the *value* at
1777
+ # `failed_barriers` (an `au` per the spec) is not. A non-iterable
1778
+ # there would crash the caller with a raw TypeError past
1779
+ # `except PortalError` -- the same value-level hole `zones()` guards
1780
+ # -- so a malformed one becomes the typed PortalProtocolError.
1781
+ try:
1782
+ failed = [_spec_int(b) for b in results.get("failed_barriers", [])]
1783
+ except (ValueError, TypeError, OverflowError) as exc:
1784
+ raise PortalProtocolError(
1785
+ "SetPointerBarriers",
1786
+ results,
1787
+ "a result value is not the type the spec gives it",
1788
+ ) from exc
1663
1789
  # A partially-refused set is the quiet failure here: the call
1664
1790
  # succeeds, some barriers stand, and an edge the caller believes is
1665
1791
  # armed simply never triggers. Callers see only the returned list,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-libei
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: Inject, receive and capture input on Wayland from Python: ctypes bindings for libei, libeis and liboeffis, plus the RemoteDesktop and InputCapture portals
5
5
  Author: Dennis K. Paulsen
6
6
  License-Expression: MIT
@@ -175,7 +175,7 @@ and why, is in
175
175
 
176
176
  ## Status
177
177
 
178
- Beta (`0.8.0`), published on [PyPI](https://pypi.org/project/python-libei/)
178
+ Beta (`0.8.1`), published on [PyPI](https://pypi.org/project/python-libei/)
179
179
  since `0.1.0`, and **the API is not frozen** — expect renames before 1.0.
180
180
 
181
181
  The injection path is exercised end to end against the real libraries by the
@@ -228,17 +228,27 @@ Exactly what was run, when, and against which versions:
228
228
 
229
229
  | Needs | For |
230
230
  | --- | --- |
231
- | 1.1 | `Region.mapping_id`, `Region.convert_point()`, `Device.region_at()` |
231
+ | 1.1 | `Region.mapping_id`, `Device.region_at()` |
232
232
  | 1.4 | ping/pong round trips (`Context.new_ping()`), `Context.disconnect()`, touch cancellation (`Touch.cancel()`) |
233
233
  | 1.5 | `eis.Client.pid` |
234
234
  | 1.6 | `Device.text_utf8()` / `text_keysym()` and TEXT events, `Seat.request_device()`, `Eis.set_flag()` |
235
235
 
236
236
  Nothing is resolved until it is called, so a build without one of these
237
237
  costs you only that call — it raises `LibraryNotFoundError` naming the
238
- missing symbol, while the rest of the package keeps working. The one
239
- exception is `Event.touch_up_event`, which degrades instead of raising:
240
- on libei older than 1.4 it reports `is_cancel=False`, since a library
241
- with no notion of cancellation genuinely never sends one.
238
+ missing symbol, while the rest of the package keeps working. Two
239
+ accessors degrade instead of raising, because every supported version
240
+ exports the symbol and only the answer changed — so there is nothing to
241
+ resolve and nothing to report:
242
+
243
+ - `Event.touch_up_event` reports `is_cancel=False` on libei older than
244
+ 1.4, since a library with no notion of cancellation genuinely never
245
+ sends one.
246
+ - `eis.Event.emulating_sequence` reads 0 on libei older than 1.6
247
+ whatever the client sent: libeis queued the start_emulating event
248
+ without ever copying the sequence into it, until 1.6.0 added the
249
+ missing assignment. A 0 on an older build is indistinguishable from a
250
+ client that really sent 0. The client-side
251
+ `ei.Event.emulating_sequence` is unaffected.
242
252
 
243
253
  These versions were established by building libei 1.2.1 and diffing its
244
254
  exported symbols against every binding here, not by reading `@since`
@@ -0,0 +1,17 @@
1
+ libei/__init__.py,sha256=7fJVf_RPxcEEeOM2YTQHtJPP3ilPhFREQKNmCoJqOL4,1858
2
+ libei/_cobject.py,sha256=XjbUNJpZ87jAyPWh6dmRslk6cMy831oMRFhWmfjLDFo,13344
3
+ libei/ei.py,sha256=3oaZ4dxo-ytBHtES3_1_LUca2ZbR8r3SwA50Rj0hGxo,49541
4
+ libei/eis.py,sha256=kcFi6vfw9F9uBt6Bz-blGD-0M6mLEMTYDCgC_SKK7vU,46843
5
+ libei/oeffis.py,sha256=gJHR90s9VhQrgkjWUMDx-lxZ8qKxkttnqbx_xo-7oxs,10035
6
+ libei/portal.py,sha256=95f3I5q8BF2ehGP6LEEMzRKTisrwBxkf20rY1MvD1ic,88168
7
+ libei/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ libei/_capi/__init__.py,sha256=raISSWSV9hAcE92DcLCz-vUb1IPcsmxRZaS-iAoQJBE,211
9
+ libei/_capi/libei.py,sha256=AQgVZ2ZsHH8rywwcmq1EJ-IauuNHbBMhwauMelX83yE,11754
10
+ libei/_capi/libeis.py,sha256=C_Uh7DwNncFC8A4wuMhviAj5C_TUmKHtLJYq0Qhhz1E,13142
11
+ libei/_capi/liboeffis.py,sha256=V8jdmJm3qOQUzzNiCAXt4Jr2JY11Y4-w6NIfIxbxGI4,1218
12
+ libei/_capi/loader.py,sha256=xW67uk35ckgeFaheDGrHC05ZM5w_cxBGvX5lG_GUSu8,6625
13
+ python_libei-0.8.1.dist-info/licenses/LICENSE,sha256=l6xbMU6Y-JZDzmBciBj-J5t6h6jNg_Bcyp4bHxDepqU,1074
14
+ python_libei-0.8.1.dist-info/METADATA,sha256=gGc0OayDeot7yFnA2wmURo_xZjjr1UoQxpK7mdynrWk,23364
15
+ python_libei-0.8.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
16
+ python_libei-0.8.1.dist-info/top_level.txt,sha256=_DQXzGjDsUBENI_cNkiOxPB4xi8coCbQS1lq18FMudQ,6
17
+ python_libei-0.8.1.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- libei/__init__.py,sha256=8k-acXmeIea963zVksZW5Ymby8ZYL_aO6B4o7g4_Y9w,1858
2
- libei/_cobject.py,sha256=XjbUNJpZ87jAyPWh6dmRslk6cMy831oMRFhWmfjLDFo,13344
3
- libei/ei.py,sha256=W4akKc_BNVfL0FroSXLGD1Z_L3OmYjZT5QDHNnEtMP8,49367
4
- libei/eis.py,sha256=X_umAaAAbAMC9d2RdnMReeJZI61WsnKz3cySY5YrGr0,45841
5
- libei/oeffis.py,sha256=gJHR90s9VhQrgkjWUMDx-lxZ8qKxkttnqbx_xo-7oxs,10035
6
- libei/portal.py,sha256=FdtVdUUKrq2GOcDBtunLCISw7RYO4Mwyk3ci26OH330,81621
7
- libei/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- libei/_capi/__init__.py,sha256=raISSWSV9hAcE92DcLCz-vUb1IPcsmxRZaS-iAoQJBE,211
9
- libei/_capi/libei.py,sha256=qsccHa_komCxtvPYi6AobszhFbEG2qA8QYJJVr8SaGM,11356
10
- libei/_capi/libeis.py,sha256=C_Uh7DwNncFC8A4wuMhviAj5C_TUmKHtLJYq0Qhhz1E,13142
11
- libei/_capi/liboeffis.py,sha256=V8jdmJm3qOQUzzNiCAXt4Jr2JY11Y4-w6NIfIxbxGI4,1218
12
- libei/_capi/loader.py,sha256=xW67uk35ckgeFaheDGrHC05ZM5w_cxBGvX5lG_GUSu8,6625
13
- python_libei-0.8.0.dist-info/licenses/LICENSE,sha256=l6xbMU6Y-JZDzmBciBj-J5t6h6jNg_Bcyp4bHxDepqU,1074
14
- python_libei-0.8.0.dist-info/METADATA,sha256=SNey10DqR4IKZ_T_XCKmafkR0ioMfxHbdbaSl9IWG-c,22882
15
- python_libei-0.8.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
16
- python_libei-0.8.0.dist-info/top_level.txt,sha256=_DQXzGjDsUBENI_cNkiOxPB4xi8coCbQS1lq18FMudQ,6
17
- python_libei-0.8.0.dist-info/RECORD,,