python-libei 0.5.2__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.
- libei/__init__.py +1 -1
- libei/_capi/libei.py +5 -3
- libei/_capi/libeis.py +6 -4
- libei/_capi/loader.py +41 -1
- libei/_cobject.py +6 -0
- libei/ei.py +19 -0
- libei/eis.py +17 -0
- libei/oeffis.py +12 -1
- libei/portal.py +26 -9
- {python_libei-0.5.2.dist-info → python_libei-0.6.1.dist-info}/METADATA +58 -28
- python_libei-0.6.1.dist-info/RECORD +17 -0
- python_libei-0.5.2.dist-info/RECORD +0 -17
- {python_libei-0.5.2.dist-info → python_libei-0.6.1.dist-info}/WHEEL +0 -0
- {python_libei-0.5.2.dist-info → python_libei-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {python_libei-0.5.2.dist-info → python_libei-0.6.1.dist-info}/top_level.txt +0 -0
libei/__init__.py
CHANGED
libei/_capi/libei.py
CHANGED
|
@@ -128,7 +128,7 @@ event_scroll_get_discrete_dy = lib.function(
|
|
|
128
128
|
event_touch_get_id = lib.function("ei_event_touch_get_id", (c_void_p,), c_uint32)
|
|
129
129
|
event_touch_get_x = lib.function("ei_event_touch_get_x", (c_void_p,), c_double)
|
|
130
130
|
event_touch_get_y = lib.function("ei_event_touch_get_y", (c_void_p,), c_double)
|
|
131
|
-
event_touch_get_is_cancel = lib.function(
|
|
131
|
+
event_touch_get_is_cancel = lib.function( # libei 1.4+
|
|
132
132
|
"ei_event_touch_get_is_cancel", (c_void_p,), c_bool
|
|
133
133
|
)
|
|
134
134
|
event_text_get_utf8 = lib.function( # libei 1.6+
|
|
@@ -141,7 +141,9 @@ event_text_get_keysym_is_press = lib.function( # libei 1.6+
|
|
|
141
141
|
"ei_event_text_get_keysym_is_press", (c_void_p,), c_bool
|
|
142
142
|
)
|
|
143
143
|
# Borrowed reference -- the event still owns it, so wrap() rather than adopt().
|
|
144
|
-
event_pong_get_ping = lib.function(
|
|
144
|
+
event_pong_get_ping = lib.function( # libei 1.4+
|
|
145
|
+
"ei_event_pong_get_ping", (c_void_p,), c_void_p
|
|
146
|
+
)
|
|
145
147
|
|
|
146
148
|
device_ref = lib.function("ei_device_ref", (c_void_p,), c_void_p)
|
|
147
149
|
device_unref = lib.function("ei_device_unref", (c_void_p,), c_void_p)
|
|
@@ -236,7 +238,7 @@ touch_get_device = lib.function("ei_touch_get_device", (c_void_p,), c_void_p)
|
|
|
236
238
|
touch_down = lib.function("ei_touch_down", (c_void_p, c_double, c_double), None)
|
|
237
239
|
touch_motion = lib.function("ei_touch_motion", (c_void_p, c_double, c_double), None)
|
|
238
240
|
touch_up = lib.function("ei_touch_up", (c_void_p,), None)
|
|
239
|
-
touch_cancel = lib.function("ei_touch_cancel", (c_void_p,), None)
|
|
241
|
+
touch_cancel = lib.function("ei_touch_cancel", (c_void_p,), None) # libei 1.4+
|
|
240
242
|
|
|
241
243
|
# ei_new_ping() returns an owned reference; ei_ping() then triggers the round
|
|
242
244
|
# trip that comes back as an EI_EVENT_PONG.
|
libei/_capi/libeis.py
CHANGED
|
@@ -104,7 +104,7 @@ event_scroll_get_discrete_dy = lib.function(
|
|
|
104
104
|
event_touch_get_id = lib.function("eis_event_touch_get_id", (c_void_p,), c_uint32)
|
|
105
105
|
event_touch_get_x = lib.function("eis_event_touch_get_x", (c_void_p,), c_double)
|
|
106
106
|
event_touch_get_y = lib.function("eis_event_touch_get_y", (c_void_p,), c_double)
|
|
107
|
-
event_touch_get_is_cancel = lib.function(
|
|
107
|
+
event_touch_get_is_cancel = lib.function( # libei 1.4+
|
|
108
108
|
"eis_event_touch_get_is_cancel", (c_void_p,), c_bool
|
|
109
109
|
)
|
|
110
110
|
event_text_get_utf8 = lib.function( # libei 1.6+
|
|
@@ -117,14 +117,16 @@ event_text_get_keysym_is_press = lib.function( # libei 1.6+
|
|
|
117
117
|
"eis_event_text_get_keysym_is_press", (c_void_p,), c_bool
|
|
118
118
|
)
|
|
119
119
|
# Borrowed reference -- the event still owns it, so wrap() rather than adopt().
|
|
120
|
-
event_pong_get_ping = lib.function(
|
|
120
|
+
event_pong_get_ping = lib.function( # libei 1.4+
|
|
121
|
+
"eis_event_pong_get_ping", (c_void_p,), c_void_p
|
|
122
|
+
)
|
|
121
123
|
|
|
122
124
|
client_ref = lib.function("eis_client_ref", (c_void_p,), c_void_p)
|
|
123
125
|
client_unref = lib.function("eis_client_unref", (c_void_p,), c_void_p)
|
|
124
126
|
client_is_sender = lib.function("eis_client_is_sender", (c_void_p,), c_bool)
|
|
125
127
|
# pid_t, i.e. a 32-bit signed int on Linux. Socket backend only, and
|
|
126
128
|
# negative errno on failure.
|
|
127
|
-
backend_socket_get_client_pid = lib.function(
|
|
129
|
+
backend_socket_get_client_pid = lib.function( # libei 1.5+
|
|
128
130
|
"eis_backend_socket_get_client_pid", (c_void_p,), c_int32
|
|
129
131
|
)
|
|
130
132
|
client_get_name = lib.function("eis_client_get_name", (c_void_p,), c_char_p)
|
|
@@ -277,7 +279,7 @@ touch_get_device = lib.function("eis_touch_get_device", (c_void_p,), c_void_p)
|
|
|
277
279
|
touch_down = lib.function("eis_touch_down", (c_void_p, c_double, c_double), None)
|
|
278
280
|
touch_motion = lib.function("eis_touch_motion", (c_void_p, c_double, c_double), None)
|
|
279
281
|
touch_up = lib.function("eis_touch_up", (c_void_p,), None)
|
|
280
|
-
touch_cancel = lib.function("eis_touch_cancel", (c_void_p,), None)
|
|
282
|
+
touch_cancel = lib.function("eis_touch_cancel", (c_void_p,), None) # libei 1.4+
|
|
281
283
|
|
|
282
284
|
ping = lib.function("eis_ping", (c_void_p,), None) # libei 1.4+
|
|
283
285
|
ping_get_id = lib.function("eis_ping_get_id", (c_void_p,), c_uint64) # libei 1.4+
|
libei/_capi/loader.py
CHANGED
|
@@ -25,12 +25,19 @@ class LazyLibrary:
|
|
|
25
25
|
"""A ctypes.CDLL that only opens the library on first real use."""
|
|
26
26
|
|
|
27
27
|
def __init__(self, soname: str) -> None:
|
|
28
|
+
"""Remember the soname; nothing is opened until first use."""
|
|
28
29
|
self._soname = soname
|
|
29
30
|
self._lib: ctypes.CDLL | None = None
|
|
30
31
|
self._load_error: OSError | None = None
|
|
31
32
|
self._lock = threading.Lock()
|
|
33
|
+
self._declared: dict[str, tuple[tuple[type, ...], type | None]] = {}
|
|
32
34
|
|
|
33
35
|
def _ensure_loaded(self) -> ctypes.CDLL:
|
|
36
|
+
"""Return the opened library, opening it on first call.
|
|
37
|
+
|
|
38
|
+
Raises LibraryNotFoundError on every later call too after a failure:
|
|
39
|
+
the failed load is cached rather than retried.
|
|
40
|
+
"""
|
|
34
41
|
# Double-checked: the unlocked read is the fast path taken by every
|
|
35
42
|
# call after the first, and the repeated check inside the lock is
|
|
36
43
|
# what makes it safe -- two threads can both fall through the first
|
|
@@ -69,6 +76,21 @@ class LazyLibrary:
|
|
|
69
76
|
return False
|
|
70
77
|
return True
|
|
71
78
|
|
|
79
|
+
@property
|
|
80
|
+
def soname(self) -> str:
|
|
81
|
+
"""The shared library this binds, as it is passed to ``dlopen``."""
|
|
82
|
+
return self._soname
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def declared(self) -> dict[str, tuple[tuple[type, ...], type | None]]:
|
|
86
|
+
"""Every function bound so far: C name -> (argtypes, restype).
|
|
87
|
+
|
|
88
|
+
Read by the ABI tests, which compare it with the library's exports and
|
|
89
|
+
with the upstream headers. Declaring a binding records it here and does
|
|
90
|
+
nothing else -- the library is still not opened until a call.
|
|
91
|
+
"""
|
|
92
|
+
return dict(self._declared)
|
|
93
|
+
|
|
72
94
|
def function(
|
|
73
95
|
self,
|
|
74
96
|
name: str,
|
|
@@ -88,9 +110,11 @@ class LazyLibrary:
|
|
|
88
110
|
# the dict is chosen only because "absent from the dict" already
|
|
89
111
|
# means "not resolved yet", with no None sentinel to confuse with a
|
|
90
112
|
# legitimately-None value.
|
|
113
|
+
self._declared[name] = (tuple(argtypes), restype)
|
|
91
114
|
cache: dict[str, Any] = {}
|
|
92
115
|
|
|
93
116
|
def call(*args: Any) -> Any:
|
|
117
|
+
"""Resolve the C function on first call, then pass straight through."""
|
|
94
118
|
# Resolution happens here, on first call, not at bind time --
|
|
95
119
|
# that is the whole point of this module (see its docstring).
|
|
96
120
|
bound = cache.get("f")
|
|
@@ -106,7 +130,23 @@ class LazyLibrary:
|
|
|
106
130
|
bound.argtypes = list(argtypes)
|
|
107
131
|
bound.restype = restype
|
|
108
132
|
cache["f"] = bound
|
|
109
|
-
|
|
133
|
+
try:
|
|
134
|
+
return bound(*args)
|
|
135
|
+
except ctypes.ArgumentError:
|
|
136
|
+
# ctypes turns *any* exception raised while converting an
|
|
137
|
+
# argument into an ArgumentError carrying only its text -- no
|
|
138
|
+
# __cause__, no __context__. For a released object that loses
|
|
139
|
+
# the RuntimeError its ``_as_parameter_`` raised, so a caller
|
|
140
|
+
# catching RuntimeError (as documented) would miss it. Ask the
|
|
141
|
+
# arguments again and re-raise the real one.
|
|
142
|
+
for arg in args:
|
|
143
|
+
try:
|
|
144
|
+
arg._as_parameter_ # noqa: B018 - evaluated for its error
|
|
145
|
+
except RuntimeError as released:
|
|
146
|
+
raise released from None
|
|
147
|
+
except AttributeError:
|
|
148
|
+
continue
|
|
149
|
+
raise
|
|
110
150
|
|
|
111
151
|
call.__name__ = name
|
|
112
152
|
return call
|
libei/_cobject.py
CHANGED
|
@@ -66,6 +66,7 @@ class CObject:
|
|
|
66
66
|
_instances_lock: ClassVar[threading.RLock]
|
|
67
67
|
|
|
68
68
|
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
69
|
+
"""Give each wrapper hierarchy one identity cache, shared by subclasses."""
|
|
69
70
|
super().__init_subclass__(**kwargs)
|
|
70
71
|
# One cache per wrapper *hierarchy*, not per class. Only a root
|
|
71
72
|
# wrapper class -- one whose only CObject ancestor is CObject
|
|
@@ -98,6 +99,7 @@ class CObject:
|
|
|
98
99
|
cls._instances_lock = threading.RLock()
|
|
99
100
|
|
|
100
101
|
def __init__(self, pointer: int, *, _adopt: bool = False) -> None:
|
|
102
|
+
"""Take a reference on a non-NULL pointer and cache this wrapper for it."""
|
|
101
103
|
if not pointer:
|
|
102
104
|
raise ValueError(f"{type(self).__name__} cannot wrap a NULL pointer")
|
|
103
105
|
self._pointer = pointer
|
|
@@ -121,6 +123,7 @@ class CObject:
|
|
|
121
123
|
|
|
122
124
|
@property
|
|
123
125
|
def _as_parameter_(self) -> int:
|
|
126
|
+
"""The raw pointer ctypes passes to C -- an error once it is released."""
|
|
124
127
|
if self._pointer == 0:
|
|
125
128
|
raise RuntimeError(
|
|
126
129
|
f"{type(self).__name__} has already been released; "
|
|
@@ -159,6 +162,7 @@ class CObject:
|
|
|
159
162
|
|
|
160
163
|
@classmethod
|
|
161
164
|
def _get_or_create(cls: type[T], pointer: int | None, *, adopt: bool) -> T | None:
|
|
165
|
+
"""The wrapper already caching this pointer, or a new one; None for NULL."""
|
|
162
166
|
if not pointer:
|
|
163
167
|
return None
|
|
164
168
|
if not cls._wrappable:
|
|
@@ -232,6 +236,7 @@ class CObject:
|
|
|
232
236
|
return cls._get_or_create(pointer, adopt=True)
|
|
233
237
|
|
|
234
238
|
def __eq__(self, other: object) -> bool:
|
|
239
|
+
"""Equal when the types match and the wrapped pointers are the same."""
|
|
235
240
|
if not isinstance(other, CObject):
|
|
236
241
|
return NotImplemented
|
|
237
242
|
if type(self) is not type(other):
|
|
@@ -245,6 +250,7 @@ class CObject:
|
|
|
245
250
|
return self._pointer == other._pointer
|
|
246
251
|
|
|
247
252
|
def __hash__(self) -> int:
|
|
253
|
+
"""Stable for the object's life: the original pointer, not the live one."""
|
|
248
254
|
# Deliberately keyed on _hash_key, not _pointer: release() zeroes
|
|
249
255
|
# _pointer, and an object whose hash changes mid-life vanishes
|
|
250
256
|
# from any set or dict it was placed in. Two wrappers can share a
|
libei/ei.py
CHANGED
|
@@ -67,6 +67,7 @@ _emulating_sequence = itertools.count(1)
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
def _next_emulating_sequence() -> int:
|
|
70
|
+
"""The next emulating-event sequence number: 32-bit, and never 0."""
|
|
70
71
|
# Masked into uint32 to match the C parameter. libei asks callers to
|
|
71
72
|
# keep wraparound detection "reasonable"; skipping 0 keeps the value
|
|
72
73
|
# away from anything that might read as unset.
|
|
@@ -192,6 +193,8 @@ class KeymapType(enum.IntEnum):
|
|
|
192
193
|
|
|
193
194
|
|
|
194
195
|
class _LogPriority(enum.IntEnum):
|
|
196
|
+
"""The log levels, as the integers the C library passes to its handler."""
|
|
197
|
+
|
|
195
198
|
DEBUG = 10
|
|
196
199
|
INFO = 20
|
|
197
200
|
WARNING = 30
|
|
@@ -329,6 +332,7 @@ class Region(CObject):
|
|
|
329
332
|
_unref_func = staticmethod(_capi.libei.region_unref)
|
|
330
333
|
|
|
331
334
|
def __repr__(self) -> str:
|
|
335
|
+
"""The size and position, as WxH+X+Y."""
|
|
332
336
|
w, h = self.dimension
|
|
333
337
|
x, y = self.position
|
|
334
338
|
return f"<Region {w}x{h}+{x}+{y}>"
|
|
@@ -510,6 +514,7 @@ class Device(CObject):
|
|
|
510
514
|
_unref_func = staticmethod(_capi.libei.device_unref)
|
|
511
515
|
|
|
512
516
|
def __repr__(self) -> str:
|
|
517
|
+
"""Name, device type and capabilities -- what tells two devices apart."""
|
|
513
518
|
caps = "|".join(c.name or str(c.value) for c in self.capabilities)
|
|
514
519
|
return f"<Device {self.name!r} {self.device_type.name} {caps}>"
|
|
515
520
|
|
|
@@ -692,6 +697,7 @@ class Seat(CObject):
|
|
|
692
697
|
_unref_func = staticmethod(_capi.libei.seat_unref)
|
|
693
698
|
|
|
694
699
|
def __repr__(self) -> str:
|
|
700
|
+
"""The seat's name and capabilities."""
|
|
695
701
|
caps = "|".join(c.name or str(c.value) for c in self.capabilities)
|
|
696
702
|
return f"<Seat {self.name!r} {caps}>"
|
|
697
703
|
|
|
@@ -774,6 +780,7 @@ class Ping(CObject):
|
|
|
774
780
|
_unref_func = staticmethod(_capi.libei.ping_unref)
|
|
775
781
|
|
|
776
782
|
def __repr__(self) -> str:
|
|
783
|
+
"""The ping's id, which is all a ping carries."""
|
|
777
784
|
return f"<Ping {self.id}>"
|
|
778
785
|
|
|
779
786
|
@property
|
|
@@ -801,6 +808,7 @@ class Event(CObject):
|
|
|
801
808
|
_unref_func = staticmethod(_capi.libei.event_unref)
|
|
802
809
|
|
|
803
810
|
def __repr__(self) -> str:
|
|
811
|
+
"""The event type by name, or the raw value for one we do not model."""
|
|
804
812
|
event_type = self.event_type
|
|
805
813
|
label = event_type.name if isinstance(event_type, EventType) else event_type
|
|
806
814
|
return f"<Event {label}>"
|
|
@@ -1008,6 +1016,11 @@ class Event(CObject):
|
|
|
1008
1016
|
|
|
1009
1017
|
|
|
1010
1018
|
def _log_callback(_ei: int, priority: int, message: bytes, _context: int) -> None:
|
|
1019
|
+
"""Forward libei's log lines into the logging module.
|
|
1020
|
+
|
|
1021
|
+
Runs inside a ctypes callback, where an exception is printed to stderr
|
|
1022
|
+
and then dropped, so every lookup below falls back rather than raising.
|
|
1023
|
+
"""
|
|
1011
1024
|
# Look up the raw int, not _LogPriority(priority): constructing the
|
|
1012
1025
|
# enum from an unrecognized value raises ValueError immediately, which
|
|
1013
1026
|
# would happen *before* .get()'s default ever gets a chance to apply
|
|
@@ -1227,6 +1240,7 @@ class Sender(Context):
|
|
|
1227
1240
|
|
|
1228
1241
|
@classmethod
|
|
1229
1242
|
def _new(cls) -> int:
|
|
1243
|
+
"""A new sender from the C library, or an error if it returned NULL."""
|
|
1230
1244
|
pointer = _capi.libei.new_sender(c_void_p(None))
|
|
1231
1245
|
if not pointer:
|
|
1232
1246
|
raise Error("ei_new_sender() returned NULL")
|
|
@@ -1250,6 +1264,7 @@ class Receiver(Context):
|
|
|
1250
1264
|
|
|
1251
1265
|
@classmethod
|
|
1252
1266
|
def _new(cls) -> int:
|
|
1267
|
+
"""A new receiver from the C library, or an error if it returned NULL."""
|
|
1253
1268
|
pointer = _capi.libei.new_receiver(c_void_p(None))
|
|
1254
1269
|
if not pointer:
|
|
1255
1270
|
raise Error("ei_new_receiver() returned NULL")
|
|
@@ -1280,6 +1295,10 @@ __all__ = [
|
|
|
1280
1295
|
"KeyEvent",
|
|
1281
1296
|
"Keymap",
|
|
1282
1297
|
"KeymapType",
|
|
1298
|
+
# Imported rather than defined here: every call bound through
|
|
1299
|
+
# _capi.libei can raise it, so a caller importing from this module
|
|
1300
|
+
# catches it from here too -- see docs/troubleshooting.md.
|
|
1301
|
+
"LibraryNotFoundError",
|
|
1283
1302
|
"Ping",
|
|
1284
1303
|
"PointerAbsoluteEvent",
|
|
1285
1304
|
"PointerEvent",
|
libei/eis.py
CHANGED
|
@@ -159,6 +159,8 @@ class Flag(enum.IntEnum):
|
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
class _LogPriority(enum.IntEnum):
|
|
162
|
+
"""The log levels, as the integers libeis passes to its log handler."""
|
|
163
|
+
|
|
162
164
|
DEBUG = 10
|
|
163
165
|
INFO = 20
|
|
164
166
|
WARNING = 30
|
|
@@ -456,6 +458,7 @@ class Device(CObject):
|
|
|
456
458
|
_unref_func = staticmethod(_capi.libeis.device_unref)
|
|
457
459
|
|
|
458
460
|
def __repr__(self) -> str:
|
|
461
|
+
"""Name, device type and capabilities -- what tells two devices apart."""
|
|
459
462
|
caps = "|".join(c.name or str(c.value) for c in self.capabilities)
|
|
460
463
|
return f"<Device {self.name!r} {self.device_type.name} {caps}>"
|
|
461
464
|
|
|
@@ -709,6 +712,7 @@ class Seat(CObject):
|
|
|
709
712
|
_unref_func = staticmethod(_capi.libeis.seat_unref)
|
|
710
713
|
|
|
711
714
|
def __repr__(self) -> str:
|
|
715
|
+
"""The seat's name and capabilities."""
|
|
712
716
|
caps = "|".join(c.name or str(c.value) for c in self.capabilities)
|
|
713
717
|
return f"<Seat {self.name!r} {caps}>"
|
|
714
718
|
|
|
@@ -770,6 +774,7 @@ class Client(CObject):
|
|
|
770
774
|
_unref_func = staticmethod(_capi.libeis.client_unref)
|
|
771
775
|
|
|
772
776
|
def __repr__(self) -> str:
|
|
777
|
+
"""The client's name and which side of the protocol it is on."""
|
|
773
778
|
return f"<Client {self.name!r} sender={self.is_sender}>"
|
|
774
779
|
|
|
775
780
|
@property
|
|
@@ -840,6 +845,7 @@ class Ping(CObject):
|
|
|
840
845
|
_unref_func = staticmethod(_capi.libeis.ping_unref)
|
|
841
846
|
|
|
842
847
|
def __repr__(self) -> str:
|
|
848
|
+
"""The ping's id, which is all a ping carries."""
|
|
843
849
|
return f"<Ping {self.id}>"
|
|
844
850
|
|
|
845
851
|
@property
|
|
@@ -867,6 +873,7 @@ class Event(CObject):
|
|
|
867
873
|
_unref_func = staticmethod(_capi.libeis.event_unref)
|
|
868
874
|
|
|
869
875
|
def __repr__(self) -> str:
|
|
876
|
+
"""The event type by name, or the raw value for one we do not model."""
|
|
870
877
|
event_type = self.event_type
|
|
871
878
|
label = event_type.name if isinstance(event_type, EventType) else event_type
|
|
872
879
|
return f"<Event {label}>"
|
|
@@ -1079,6 +1086,11 @@ class Event(CObject):
|
|
|
1079
1086
|
|
|
1080
1087
|
|
|
1081
1088
|
def _log_callback(_eis: int, priority: int, message: bytes, _context: int) -> None:
|
|
1089
|
+
"""Forward libeis's log lines into the logging module.
|
|
1090
|
+
|
|
1091
|
+
The same constraint as ei.py's callback of that name: it runs inside a
|
|
1092
|
+
ctypes callback, so it falls back rather than raising.
|
|
1093
|
+
"""
|
|
1082
1094
|
# See ei.py's _log_callback: look up the raw int, not
|
|
1083
1095
|
# _LogPriority(priority), which would raise ValueError before .get()'s
|
|
1084
1096
|
# default could apply -- silently, since this runs inside a ctypes
|
|
@@ -1208,6 +1220,7 @@ class Eis(CObject):
|
|
|
1208
1220
|
|
|
1209
1221
|
@classmethod
|
|
1210
1222
|
def _new(cls) -> int:
|
|
1223
|
+
"""A new server from the C library, or an error if it returned NULL."""
|
|
1211
1224
|
pointer = _capi.libeis.new(c_void_p(None))
|
|
1212
1225
|
if not pointer:
|
|
1213
1226
|
raise Error("eis_new() returned NULL")
|
|
@@ -1265,6 +1278,10 @@ __all__ = [
|
|
|
1265
1278
|
"KeyEvent",
|
|
1266
1279
|
"Keymap",
|
|
1267
1280
|
"KeymapType",
|
|
1281
|
+
# Imported rather than defined here: every call bound through
|
|
1282
|
+
# _capi.libeis can raise it, so a caller importing from this module
|
|
1283
|
+
# catches it from here too -- see docs/troubleshooting.md.
|
|
1284
|
+
"LibraryNotFoundError",
|
|
1268
1285
|
"Ping",
|
|
1269
1286
|
"PointerAbsoluteEvent",
|
|
1270
1287
|
"PointerEvent",
|
libei/oeffis.py
CHANGED
|
@@ -6,7 +6,7 @@ Negotiates an EIS connection through the
|
|
|
6
6
|
This is the path a sandboxed or otherwise non-privileged client uses to get
|
|
7
7
|
an EI socket: it asks the portal, the user is shown a consent dialog, and on
|
|
8
8
|
approval this hands back a file descriptor to pass to
|
|
9
|
-
:meth:`libei.ei.Sender.create_for_fd
|
|
9
|
+
:meth:`libei.ei.Sender.create_for_fd`::
|
|
10
10
|
|
|
11
11
|
oeffis = Oeffis.create(devices=DeviceType.POINTER)
|
|
12
12
|
while True:
|
|
@@ -41,6 +41,7 @@ import logging
|
|
|
41
41
|
import os
|
|
42
42
|
|
|
43
43
|
from . import _capi
|
|
44
|
+
from ._capi.loader import LibraryNotFoundError
|
|
44
45
|
|
|
45
46
|
logger = logging.getLogger("libei.oeffis")
|
|
46
47
|
|
|
@@ -122,6 +123,11 @@ class Oeffis:
|
|
|
122
123
|
self._state = _EventType.NONE
|
|
123
124
|
|
|
124
125
|
def __del__(self) -> None:
|
|
126
|
+
"""Release the session, closing the EIS fd if nobody claimed it.
|
|
127
|
+
|
|
128
|
+
Safe on a half-built object: __init__ can raise before every
|
|
129
|
+
attribute exists, and this still runs.
|
|
130
|
+
"""
|
|
125
131
|
# getattr() with defaults rather than plain attribute access:
|
|
126
132
|
# __init__ raises DisconnectedError when oeffis_new() returns NULL,
|
|
127
133
|
# and Python still calls __del__ on the half-built object, where
|
|
@@ -237,6 +243,11 @@ class Oeffis:
|
|
|
237
243
|
__all__ = [
|
|
238
244
|
"DeviceType",
|
|
239
245
|
"DisconnectedError",
|
|
246
|
+
# Imported rather than defined here: every call bound through
|
|
247
|
+
# _capi.liboeffis can raise it -- Oeffis.create() on a machine with no
|
|
248
|
+
# liboeffis is the ordinary one -- so a caller importing from this
|
|
249
|
+
# module catches it from here too. See docs/troubleshooting.md.
|
|
250
|
+
"LibraryNotFoundError",
|
|
240
251
|
"Oeffis",
|
|
241
252
|
"SessionClosedError",
|
|
242
253
|
"is_available",
|
libei/portal.py
CHANGED
|
@@ -21,7 +21,7 @@ should be handled by an application talking to DBus directly"
|
|
|
21
21
|
(https://libinput.pages.freedesktop.org/libei/api/group__liboeffis.html).
|
|
22
22
|
This module is that: the ``CreateSession`` -> ``SelectDevices`` -> ``Start``
|
|
23
23
|
-> ``ConnectToEIS`` sequence driven directly, with ``persist_mode`` and
|
|
24
|
-
``restore_token`` exposed as real parameters
|
|
24
|
+
``restore_token`` exposed as real parameters::
|
|
25
25
|
|
|
26
26
|
with RemoteDesktopSession.negotiate(
|
|
27
27
|
devices=DeviceType.POINTER | DeviceType.KEYBOARD,
|
|
@@ -427,18 +427,21 @@ def _request(
|
|
|
427
427
|
params: Any,
|
|
428
428
|
*_a: Any,
|
|
429
429
|
) -> None:
|
|
430
|
+
"""Record the first reply and quit the loop; later replies are ignored."""
|
|
430
431
|
if result: # both subscriptions may fire; the first reply wins
|
|
431
432
|
return
|
|
432
433
|
result["code"], result["results"] = params.unpack()
|
|
433
434
|
loop.quit()
|
|
434
435
|
|
|
435
436
|
def on_timeout() -> bool:
|
|
437
|
+
"""Stop the loop and record that it timed out rather than finished."""
|
|
436
438
|
nonlocal timed_out
|
|
437
439
|
timed_out = True
|
|
438
440
|
loop.quit()
|
|
439
441
|
return False # one-shot; GLib removes the source when this is False
|
|
440
442
|
|
|
441
443
|
def subscribe(path: str) -> None:
|
|
444
|
+
"""Subscribe to Response on one path, keeping the handle alive."""
|
|
442
445
|
subscriptions.append(
|
|
443
446
|
connection.signal_subscribe(
|
|
444
447
|
busname,
|
|
@@ -685,12 +688,20 @@ class RemoteDesktopSession:
|
|
|
685
688
|
self._connection = None
|
|
686
689
|
|
|
687
690
|
def __enter__(self) -> RemoteDesktopSession:
|
|
691
|
+
"""Return self: entering performs no action of its own."""
|
|
688
692
|
return self
|
|
689
693
|
|
|
690
694
|
def __exit__(self, *_exc: Any) -> None:
|
|
695
|
+
"""Close the session on the way out, whatever happened."""
|
|
691
696
|
self.close()
|
|
692
697
|
|
|
693
698
|
def __del__(self) -> None:
|
|
699
|
+
"""Close the fd only: __del__ can run during interpreter shutdown.
|
|
700
|
+
|
|
701
|
+
A synchronous D-Bus round trip there may hang or fail with nothing
|
|
702
|
+
left able to report it, so the D-Bus half of close() is deliberately
|
|
703
|
+
not attempted.
|
|
704
|
+
"""
|
|
694
705
|
# Deliberately only the fd, not the D-Bus half of close(): __del__
|
|
695
706
|
# can run during interpreter shutdown, where a synchronous D-Bus
|
|
696
707
|
# round trip may hang or fail in ways nothing can report. Closing an
|
|
@@ -1037,6 +1048,7 @@ def _wait_for_signal(
|
|
|
1037
1048
|
params: Any,
|
|
1038
1049
|
*_a: Any,
|
|
1039
1050
|
) -> None:
|
|
1051
|
+
"""Record the first matching signal; anything else is logged and dropped."""
|
|
1040
1052
|
if result: # a subscription that outlives its own wait can fire twice
|
|
1041
1053
|
return
|
|
1042
1054
|
args = params.unpack()
|
|
@@ -1058,6 +1070,7 @@ def _wait_for_signal(
|
|
|
1058
1070
|
loop.quit()
|
|
1059
1071
|
|
|
1060
1072
|
def on_timeout() -> bool:
|
|
1073
|
+
"""Stop the loop and record that it timed out rather than finished."""
|
|
1061
1074
|
nonlocal timed_out
|
|
1062
1075
|
timed_out = True
|
|
1063
1076
|
loop.quit()
|
|
@@ -1141,8 +1154,8 @@ class InputCaptureSession:
|
|
|
1141
1154
|
direction: instead of injecting synthetic input, this receives real
|
|
1142
1155
|
input from the user's own devices once the compositor decides to divert
|
|
1143
1156
|
it here. That decision is the whole point of the protocol and is never
|
|
1144
|
-
this session's to make -- see :meth:`enable` and
|
|
1145
|
-
wait_for_activation`.
|
|
1157
|
+
this session's to make -- see :meth:`enable` and
|
|
1158
|
+
:meth:`wait_for_activation`.
|
|
1146
1159
|
|
|
1147
1160
|
**Capturing is exclusive.** Once the compositor activates a capture,
|
|
1148
1161
|
the events it captures stop reaching the desktop entirely and are sent
|
|
@@ -1163,12 +1176,13 @@ class InputCaptureSession:
|
|
|
1163
1176
|
about, and this cannot release on a caller's behalf during cleanup
|
|
1164
1177
|
without risking racing a capture that only just started.
|
|
1165
1178
|
|
|
1166
|
-
**
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
the
|
|
1170
|
-
|
|
1171
|
-
|
|
1179
|
+
**Only half live-tested.** The negotiation -- consent dialog, EIS fd,
|
|
1180
|
+
zones, ``Session.Close()`` -- was run against a real GNOME session on
|
|
1181
|
+
libei's own hand-verification terms (see
|
|
1182
|
+
``docs/developers/verification.md``); the *capture* half never has been,
|
|
1183
|
+
because verifying it means a developer accepting that their pointer will
|
|
1184
|
+
be diverted away from their own desktop for the length of the test -- not
|
|
1185
|
+
something to trigger without asking first, unlike everything else here. Designed
|
|
1172
1186
|
against ``/usr/share/dbus-1/interfaces/org.freedesktop.portal.
|
|
1173
1187
|
InputCapture.xml`` (the shipped portal spec, not the header alone) and
|
|
1174
1188
|
unit-tested against a fake connection reproducing that spec's documented
|
|
@@ -1487,12 +1501,15 @@ class InputCaptureSession:
|
|
|
1487
1501
|
self._connection = None
|
|
1488
1502
|
|
|
1489
1503
|
def __enter__(self) -> InputCaptureSession:
|
|
1504
|
+
"""Return self: entering performs no action of its own."""
|
|
1490
1505
|
return self
|
|
1491
1506
|
|
|
1492
1507
|
def __exit__(self, *_exc: Any) -> None:
|
|
1508
|
+
"""Close the session on the way out, whatever happened."""
|
|
1493
1509
|
self.close()
|
|
1494
1510
|
|
|
1495
1511
|
def __del__(self) -> None:
|
|
1512
|
+
"""Close the fd only, for the reason RemoteDesktopSession.__del__ gives."""
|
|
1496
1513
|
# See RemoteDesktopSession.__del__ for why this closes only the fd.
|
|
1497
1514
|
if getattr(self, "_eis_fd_claimed", True):
|
|
1498
1515
|
return
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-libei
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Inject and
|
|
3
|
+
Version: 0.6.1
|
|
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
|
|
7
7
|
Project-URL: homepage, https://github.com/ctrondlp/python-libei
|
|
8
8
|
Project-URL: repository, https://github.com/ctrondlp/python-libei
|
|
9
9
|
Project-URL: issues, https://github.com/ctrondlp/python-libei/issues
|
|
10
|
+
Project-URL: documentation, https://github.com/ctrondlp/python-libei/tree/main/docs
|
|
11
|
+
Project-URL: pyguitest, https://github.com/ctrondlp/pyguitest
|
|
10
12
|
Project-URL: changelog, https://github.com/ctrondlp/python-libei/blob/main/CHANGELOG.md
|
|
11
|
-
Keywords: wayland,libei,libeis,liboeffis,input,input-emulation,emulated-input,portal,xdg-desktop-portal,remote-desktop,automation,gui-testing,accessibility,ctypes
|
|
13
|
+
Keywords: wayland,libei,libeis,liboeffis,input,input-emulation,emulated-input,input-capture,eis,portal,xdg-desktop-portal,remote-desktop,automation,gui-testing,accessibility,ctypes
|
|
12
14
|
Classifier: Development Status :: 4 - Beta
|
|
13
15
|
Classifier: Intended Audience :: Developers
|
|
14
16
|
Classifier: Operating System :: POSIX :: BSD :: FreeBSD
|
|
15
17
|
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Operating System :: POSIX
|
|
16
19
|
Classifier: Programming Language :: Python :: 3
|
|
17
20
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -37,6 +40,10 @@ Dynamic: license-file
|
|
|
37
40
|
|
|
38
41
|
# python-libei
|
|
39
42
|
|
|
43
|
+
[](https://github.com/ctrondlp/python-libei/actions/workflows/ci.yml)
|
|
44
|
+
[](https://pypi.org/project/python-libei/)
|
|
45
|
+
[](https://github.com/ctrondlp/python-libei/blob/main/LICENSE)
|
|
46
|
+
|
|
40
47
|
Python bindings for [libei, libeis and liboeffis](https://libinput.pages.freedesktop.org/libei/) —
|
|
41
48
|
the Wayland input-emulation libraries. Use this to **move the pointer, click,
|
|
42
49
|
type, or scroll on a Wayland desktop** from Python, the way `xdotool` did on
|
|
@@ -49,8 +56,11 @@ device.start_emulating().pointer_motion(5, 0).frame().stop_emulating()
|
|
|
49
56
|
```
|
|
50
57
|
|
|
51
58
|
**The one concept:** events queue up, and **`frame()` is what sends them** as
|
|
52
|
-
one logical hardware event. Forget it and
|
|
53
|
-
warning, no movement.
|
|
59
|
+
one logical hardware event. Forget it and the motion is usually lost — no
|
|
60
|
+
exception, no warning, no movement. (Ending the chain with `stop_emulating()`
|
|
61
|
+
makes libei frame the queue for you, but it logs an error-level `Bug:` line
|
|
62
|
+
when it does; see [Things that will bite you](#things-that-will-bite-you).) Fill
|
|
63
|
+
the package, then post it.
|
|
54
64
|
|
|
55
65
|
New here? [docs/getting-started.md](docs/getting-started.md) is install
|
|
56
66
|
through a first real pointer motion, in five minutes.
|
|
@@ -92,8 +102,8 @@ than a widget-tree back door:
|
|
|
92
102
|
|
|
93
103
|
## Which API do I need?
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
to get permission, then `ei` to inject.
|
|
105
|
+
Four modules (`ei`, `eis`, `oeffis`, `portal`), and most callers need exactly
|
|
106
|
+
two of them: `oeffis` or `portal` to get permission, then `ei` to inject.
|
|
97
107
|
|
|
98
108
|
| You want to… | Use | Notes |
|
|
99
109
|
| --- | --- | --- |
|
|
@@ -101,13 +111,18 @@ to get permission, then `ei` to inject.
|
|
|
101
111
|
| **Consume input** from a compositor | `libei.ei` → `Receiver` | Compositor-side or input-capture code; same connection dance |
|
|
102
112
|
| **Get permission**, simply | `libei.oeffis` | One call, pollable fd, no dependencies. The consent dialog appears on **every** run |
|
|
103
113
|
| **Get permission**, and not be asked again | `libei.portal` | Same handshake over D-Bus directly, with `persist_mode` / `restore_token`. Needs PyGObject |
|
|
114
|
+
| **Capture the user's real input** | `libei.portal` → `InputCaptureSession` | The read direction, through the InputCapture portal: once the compositor decides to, the user's pointer and keyboard are diverted to you over EIS. Exclusive while active, so hold it briefly. Only its negotiation half has been run against a real desktop — see [verification](docs/developers/verification.md) |
|
|
104
115
|
| **Be the server**, for tests or a compositor | `libei.eis` | Drives your client code with no real compositor and no consent dialog |
|
|
105
116
|
|
|
106
|
-
Each module has `is_available()
|
|
107
|
-
`
|
|
108
|
-
`
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
Each module has `is_available()`. `ei` and `eis` share the shapes around them:
|
|
118
|
+
an `Error` exception, an `EventType` and `DeviceCapability` enum, and `Device`,
|
|
119
|
+
`Seat`, `Region`, `Keymap`, `Touch`, `Ping`, `Event` with the frozen dataclasses
|
|
120
|
+
its accessors return. `oeffis` and `portal` are smaller — `DeviceType`, no
|
|
121
|
+
`EventType` or `DeviceCapability`, `Activation` as the one frozen result a
|
|
122
|
+
portal wait hands back, and their own exception classes rather than an `Error`.
|
|
123
|
+
[docs/troubleshooting.md](docs/troubleshooting.md) names every class they raise.
|
|
124
|
+
The package ships `py.typed`, so callers type-check against real
|
|
125
|
+
annotations rather than `Any`.
|
|
111
126
|
|
|
112
127
|
## What's implemented
|
|
113
128
|
|
|
@@ -135,8 +150,9 @@ because a skimming reader could otherwise take them as supported. 1.6.0's own
|
|
|
135
150
|
`enum ei_device_capability` stops at `TEXT`, and its `enum ei_event_type`
|
|
136
151
|
stops at `EI_EVENT_TEXT_UTF8` — so the swipe/pinch/hold/stylus members of
|
|
137
152
|
`EventType` cannot arrive either. The values here match upstream `main`
|
|
138
|
-
exactly, so they are ready for whatever release adds them. The
|
|
139
|
-
|
|
153
|
+
exactly, so they are ready for whatever release adds them. The gesture
|
|
154
|
+
and stylus functions `main` adds -- 46 in libei and 51 in libeis against
|
|
155
|
+
1.6.0's headers, counted 2026-09-30 -- are deliberately not bound:
|
|
140
156
|
nothing that ships today exports them, so nothing here could be verified
|
|
141
157
|
against a real library, which is the bar every other binding in this package
|
|
142
158
|
was held to.
|
|
@@ -149,14 +165,16 @@ Beyond sending input, the wrapper also covers ping/pong round trips
|
|
|
149
165
|
(`Device.keymap`), region mapping ids and coordinate conversion,
|
|
150
166
|
`Context.disconnect()`, `Context.peek_event_type()`, and
|
|
151
167
|
`Seat.request_device()`. On the server side, `libei.eis` mirrors all of it and
|
|
152
|
-
adds `Eis.set_flag()`
|
|
168
|
+
adds `Eis.set_flag()` (with the `Flag` values it takes), `Client.pid`, and
|
|
169
|
+
`Device.configure()` with the `ConfigureRegion` descriptions it accepts.
|
|
170
|
+
Underneath, the ctypes layer binds 250
|
|
153
171
|
of the 302 functions the three libraries export as of 1.6.0; what is left out,
|
|
154
172
|
and why, is in
|
|
155
173
|
[docs/developers/architecture.md](docs/developers/architecture.md#what-is-bound-and-what-is-deliberately-not).
|
|
156
174
|
|
|
157
175
|
## Status
|
|
158
176
|
|
|
159
|
-
Beta (`0.
|
|
177
|
+
Beta (`0.6.1`), published on [PyPI](https://pypi.org/project/python-libei/)
|
|
160
178
|
since `0.1.0`, and **the API is not frozen** — expect renames before 1.0.
|
|
161
179
|
|
|
162
180
|
The injection path is exercised end to end against the real libraries by the
|
|
@@ -197,9 +215,13 @@ Exactly what was run, when, and against which versions:
|
|
|
197
215
|
- libei 1.0.0 or newer for the core: connecting, binding a seat, and
|
|
198
216
|
sending pointer, button, keyboard, scroll and touch input all use symbols
|
|
199
217
|
that have existed with a stable signature since 1.0.0, and upstream keeps
|
|
200
|
-
API/ABI back-compatible within the 1.x series.
|
|
201
|
-
|
|
202
|
-
the injection path passes the full suite with
|
|
218
|
+
API/ABI back-compatible within the 1.x series. The suite has been run
|
|
219
|
+
against 1.2.1 (Ubuntu 24.04, in CI) and 1.6.0 -- the latter on Fedora 44 and
|
|
220
|
+
45 and FreeBSD 15, where the injection path passes the full suite with
|
|
221
|
+
nothing skipped. Every binding's name, argument types and return type, and
|
|
222
|
+
every enum value, is also checked against the upstream headers of 1.0.0,
|
|
223
|
+
1.2.1, 1.4.0, 1.5.0 and 1.6.0 (`tests/test_abi.py`), which is the only
|
|
224
|
+
coverage 1.0.0, 1.4.0 and 1.5.0 have had.
|
|
203
225
|
|
|
204
226
|
Newer libei buys you more, per feature:
|
|
205
227
|
|
|
@@ -357,8 +379,12 @@ All four, with working code:
|
|
|
357
379
|
soon as the loop moves on, and using it afterwards raises `RuntimeError`.
|
|
358
380
|
Objects you pull *off* an event (`event.device`, `event.seat`) are safe to
|
|
359
381
|
keep — copy out `event.pointer_event` and friends rather than the event.
|
|
360
|
-
- **`frame()` or
|
|
361
|
-
them
|
|
382
|
+
- **`frame()` or the input is lost.** Events queue up until a `frame()` commits
|
|
383
|
+
them, and queued events that are never framed are dropped without an
|
|
384
|
+
exception or a log line. The one exception is a chain that ends in
|
|
385
|
+
`stop_emulating()`: libei frames what is queued, delivers it, and logs
|
|
386
|
+
`Bug: ei_device_stop_emulating: missing call to ei_device_frame()` at error
|
|
387
|
+
level. Measured against libei 1.2.1 and 1.6.0; don't rely on it.
|
|
362
388
|
- **`bind()` needs at least one capability.** Binding an empty set sends
|
|
363
389
|
nothing, so the device you are waiting for never arrives; this raises
|
|
364
390
|
`ValueError` rather than hanging.
|
|
@@ -367,20 +393,22 @@ All four, with working code:
|
|
|
367
393
|
- **One seat can resume several devices.** Bind both `POINTER` and
|
|
368
394
|
`POINTER_ABSOLUTE` and GNOME gives you two, relative first. Taking
|
|
369
395
|
whichever resumes first is a coin flip — select on `device.capabilities`
|
|
370
|
-
instead. Sending an event the device lacks the capability for is
|
|
371
|
-
|
|
396
|
+
instead. Sending an event the device lacks the capability for is dropped with
|
|
397
|
+
no exception — libei only logs an error-level `Bug: ... device is not a
|
|
398
|
+
keyboard` line — which makes this look like the injection simply not working.
|
|
372
399
|
- **Read the accessor that matches the event type.** `event.key_event` on a
|
|
373
400
|
`POINTER_MOTION` event raises `TypeError` naming both types. libei itself
|
|
374
401
|
would have returned `KeyEvent(key=0, is_press=False)` — a real-looking
|
|
375
|
-
value —
|
|
376
|
-
`event_type` first. `TOUCH_UP` has its own `touch_up_event`, since it
|
|
402
|
+
value — and raised nothing, only logging a `Bug:` line at error level, so
|
|
403
|
+
branch on `event_type` first. `TOUCH_UP` has its own `touch_up_event`, since it
|
|
377
404
|
carries no coordinates.
|
|
378
405
|
- **`GESTURES` and `STYLUS` are not in any released libei.** They match
|
|
379
406
|
upstream `main` and are here ready for it, but 1.6.0's capability enum
|
|
380
407
|
stops at `TEXT`. Binding them against a shipping library silently does
|
|
381
408
|
nothing — no error, no device, no events.
|
|
382
409
|
|
|
383
|
-
Nearly all of these fail
|
|
410
|
+
Nearly all of these fail without raising — some only log an error-level `Bug:`
|
|
411
|
+
line, some not even that — which is why
|
|
384
412
|
[docs/troubleshooting.md](docs/troubleshooting.md) is a checklist rather than
|
|
385
413
|
a list of error messages. Start there when nothing happens.
|
|
386
414
|
|
|
@@ -394,8 +422,10 @@ a list of error messages. Start there when nothing happens.
|
|
|
394
422
|
happens" checklist
|
|
395
423
|
- [docs/vs-snegg.md](docs/vs-snegg.md) — how this differs from the reference
|
|
396
424
|
bindings, and two signature issues found by cross-checking the C source
|
|
397
|
-
- [docs/developers/](docs/developers/)
|
|
398
|
-
|
|
425
|
+
- [docs/developers/architecture.md](docs/developers/architecture.md) and
|
|
426
|
+
[docs/developers/verification.md](docs/developers/verification.md) — the
|
|
427
|
+
four-layer architecture, and what has actually been verified against which
|
|
428
|
+
libei versions
|
|
399
429
|
- [CONTRIBUTING.md](CONTRIBUTING.md) — setup, checks, testing against an old
|
|
400
430
|
libei, releasing
|
|
401
431
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
libei/__init__.py,sha256=OP-aoBP8bXNhL8q3gXxVL4FtWLhVyaBlmoQEInBdhp4,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=XC_FsKueIpcmAPZ4TDXJgFhucJNeMEO2byTJHG3GMmI,69756
|
|
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.6.1.dist-info/licenses/LICENSE,sha256=l6xbMU6Y-JZDzmBciBj-J5t6h6jNg_Bcyp4bHxDepqU,1074
|
|
14
|
+
python_libei-0.6.1.dist-info/METADATA,sha256=nV9GTh7ArlrDPXyPTt4fGHJiPP22J9bmA2e8v-mDgp0,22728
|
|
15
|
+
python_libei-0.6.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
16
|
+
python_libei-0.6.1.dist-info/top_level.txt,sha256=_DQXzGjDsUBENI_cNkiOxPB4xi8coCbQS1lq18FMudQ,6
|
|
17
|
+
python_libei-0.6.1.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
libei/__init__.py,sha256=SwaEhWSSGzxTEOqlW6PsfGC24QWz2A2FUrktq4AztTM,1858
|
|
2
|
-
libei/_cobject.py,sha256=msrbviABSWjc5fKsXHSCg7nQ0Y4Etcq_Dkh0fblW-sY,12845
|
|
3
|
-
libei/ei.py,sha256=5LAvMDOYS0AdwEHpKKZ3Anky5yIcDvr926A7ydL_Vmk,48289
|
|
4
|
-
libei/eis.py,sha256=HepGyded_soT84GUu66iSz9IcGP6Bm_Y2750-YK9GDg,44912
|
|
5
|
-
libei/oeffis.py,sha256=MhUPKD2Sk2OzKikaztNoL5k1CresqjdY7EPWuCPCMIM,9477
|
|
6
|
-
libei/portal.py,sha256=dGOX5Rf5slbEKq-Ie0od4_ZgStd8UruwMbSnaOO2QI8,68682
|
|
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=gpp42bhqPonhVD2zmFWCl4rdk_lm4XvD_7W2Rw5RsuI,11308
|
|
10
|
-
libei/_capi/libeis.py,sha256=jQR0Z0YN9qa71mAcSJ__-UaoF8PsVsBs8OXwjfqstFk,13080
|
|
11
|
-
libei/_capi/liboeffis.py,sha256=V8jdmJm3qOQUzzNiCAXt4Jr2JY11Y4-w6NIfIxbxGI4,1218
|
|
12
|
-
libei/_capi/loader.py,sha256=k7fb_Nz0fg5QkuLJ_duKXj8bESSktLQ2ZyS5LNQ5v2I,4689
|
|
13
|
-
python_libei-0.5.2.dist-info/licenses/LICENSE,sha256=l6xbMU6Y-JZDzmBciBj-J5t6h6jNg_Bcyp4bHxDepqU,1074
|
|
14
|
-
python_libei-0.5.2.dist-info/METADATA,sha256=IIL9EKM_LBc2PoqLzGQKu5sZHQ9l4ivyH5W3uq61CW0,20086
|
|
15
|
-
python_libei-0.5.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
16
|
-
python_libei-0.5.2.dist-info/top_level.txt,sha256=_DQXzGjDsUBENI_cNkiOxPB4xi8coCbQS1lq18FMudQ,6
|
|
17
|
-
python_libei-0.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|