python-libei 0.3.0__py3-none-any.whl → 0.4.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 +2 -2
- libei/portal.py +197 -20
- {python_libei-0.3.0.dist-info → python_libei-0.4.1.dist-info}/METADATA +5 -4
- {python_libei-0.3.0.dist-info → python_libei-0.4.1.dist-info}/RECORD +7 -7
- {python_libei-0.3.0.dist-info → python_libei-0.4.1.dist-info}/WHEEL +0 -0
- {python_libei-0.3.0.dist-info → python_libei-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {python_libei-0.3.0.dist-info → python_libei-0.4.1.dist-info}/top_level.txt +0 -0
libei/__init__.py
CHANGED
|
@@ -26,9 +26,9 @@ evdev keycodes -- key positions, not characters -- though ``text_utf8()``
|
|
|
26
26
|
sends characters directly where libei 1.6 is available. See the README for
|
|
27
27
|
the full breakdown, including which features need which libei version.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Beta: the API is not frozen.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
__version__ = "0.
|
|
32
|
+
__version__ = "0.4.1"
|
|
33
33
|
|
|
34
34
|
__all__ = ["__version__"]
|
libei/portal.py
CHANGED
|
@@ -63,6 +63,7 @@ from __future__ import annotations
|
|
|
63
63
|
import enum
|
|
64
64
|
import logging
|
|
65
65
|
import os
|
|
66
|
+
import time
|
|
66
67
|
import uuid
|
|
67
68
|
from typing import Any
|
|
68
69
|
|
|
@@ -95,6 +96,23 @@ to see and answer the consent dialog `Start` raises -- but bounded, because
|
|
|
95
96
|
the alternative is a caller wedged forever if the portal dies after
|
|
96
97
|
accepting the call and before sending its `Response`."""
|
|
97
98
|
|
|
99
|
+
_GIO_DEFAULT_TIMEOUT_MSEC = 25_000
|
|
100
|
+
"""What GDBus's ``-1`` reply timeout actually is.
|
|
101
|
+
|
|
102
|
+
``call_sync(..., -1, ...)`` does not mean "wait forever" -- that is
|
|
103
|
+
``G_MAXINT`` -- it means GIO's own default, which is 25 seconds. Only used
|
|
104
|
+
to report the right number when a call left on that default runs out of
|
|
105
|
+
time; see `_call_sync`."""
|
|
106
|
+
|
|
107
|
+
_G_IO_ERROR_DOMAIN = "g-io-error-quark"
|
|
108
|
+
"""The GLib error domain GDBus reports its own reply timeout in.
|
|
109
|
+
|
|
110
|
+
Not one of the ``org.freedesktop.DBus.Error.*`` codes, as one might expect:
|
|
111
|
+
a ``call_sync()`` whose reply never arrives raises ``G_IO_ERROR_TIMED_OUT``
|
|
112
|
+
in this domain with the message "Timeout was reached" (checked against a
|
|
113
|
+
service that accepts a call and then deliberately never answers). See
|
|
114
|
+
`_is_reply_timeout`."""
|
|
115
|
+
|
|
98
116
|
_ALL_DEVICE_TYPES = DeviceType.KEYBOARD | DeviceType.POINTER | DeviceType.TOUCHSCREEN
|
|
99
117
|
"""Every bit the RemoteDesktop `types` bitmask defines.
|
|
100
118
|
|
|
@@ -189,6 +207,43 @@ def _glib_error(GLib: Any) -> Any:
|
|
|
189
207
|
return getattr(GLib, "Error", ())
|
|
190
208
|
|
|
191
209
|
|
|
210
|
+
def _is_reply_timeout(Gio: Any, exc: Exception) -> bool:
|
|
211
|
+
"""Whether a GDBus failure is its own reply timeout expiring.
|
|
212
|
+
|
|
213
|
+
Worth telling apart from every other ``GLib.Error``, because it means
|
|
214
|
+
exactly what the nested loop's own timeout means -- nobody answered in
|
|
215
|
+
the time allowed -- and so should raise the `PortalTimeoutError` a
|
|
216
|
+
caller is documented to catch for that, not a generic `PortalError`.
|
|
217
|
+
|
|
218
|
+
Defensive throughout: a test double standing in for ``Gio`` need not
|
|
219
|
+
define ``IOErrorEnum``, and one standing in for ``GLib.Error`` need not
|
|
220
|
+
carry a domain or a code. An attribute that isn't there just means
|
|
221
|
+
"not a timeout", which is the safe answer -- the failure still reaches
|
|
222
|
+
the caller, only as the more general error.
|
|
223
|
+
"""
|
|
224
|
+
timed_out = getattr(getattr(Gio, "IOErrorEnum", None), "TIMED_OUT", None)
|
|
225
|
+
if timed_out is None:
|
|
226
|
+
return False
|
|
227
|
+
return (
|
|
228
|
+
getattr(exc, "domain", None) == _G_IO_ERROR_DOMAIN
|
|
229
|
+
and getattr(exc, "code", None) == timed_out
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _msec_until(deadline: float) -> int:
|
|
234
|
+
"""Milliseconds left before ``deadline``, never negative.
|
|
235
|
+
|
|
236
|
+
Both legs of a round trip -- the call that returns the request handle,
|
|
237
|
+
then the wait for its ``Response`` signal -- draw down the same
|
|
238
|
+
deadline, so ``timeout`` bounds the round trip rather than each half of
|
|
239
|
+
it separately, which would let a slow first leg quietly double the wait
|
|
240
|
+
a caller asked for. Zero is a legitimate answer: GLib fires a 0ms
|
|
241
|
+
timeout on the next main-loop iteration, which is the right thing for a
|
|
242
|
+
deadline that has already passed.
|
|
243
|
+
"""
|
|
244
|
+
return max(0, int((deadline - time.monotonic()) * 1000))
|
|
245
|
+
|
|
246
|
+
|
|
192
247
|
def _call_sync(
|
|
193
248
|
connection: Any,
|
|
194
249
|
Gio: Any,
|
|
@@ -199,6 +254,7 @@ def _call_sync(
|
|
|
199
254
|
method: str,
|
|
200
255
|
parameters: Any,
|
|
201
256
|
reply_type: Any,
|
|
257
|
+
timeout_msec: int = -1,
|
|
202
258
|
) -> Any:
|
|
203
259
|
"""``call_sync``, with GDBus failures translated to `PortalError`.
|
|
204
260
|
|
|
@@ -206,6 +262,13 @@ def _call_sync(
|
|
|
206
262
|
no-portal-backend cases -- exactly the ones `is_available()` documents
|
|
207
263
|
as surfacing here, since it deliberately checks neither -- escape a
|
|
208
264
|
caller's `except PortalError`.
|
|
265
|
+
|
|
266
|
+
``timeout_msec`` is GDBus's own reply timeout, and defaults to its
|
|
267
|
+
``-1`` -- GIO's 25 seconds (see `_GIO_DEFAULT_TIMEOUT_MSEC`), which is
|
|
268
|
+
what `RemoteDesktopSession.close` leaves it at, having no caller-facing
|
|
269
|
+
timeout to honour. Anything reached from `negotiate` passes the
|
|
270
|
+
caller's own ``timeout`` instead, so that the bound on a round trip is
|
|
271
|
+
the documented one rather than a number this module never chose.
|
|
209
272
|
"""
|
|
210
273
|
try:
|
|
211
274
|
return connection.call_sync(
|
|
@@ -216,13 +279,47 @@ def _call_sync(
|
|
|
216
279
|
parameters,
|
|
217
280
|
reply_type,
|
|
218
281
|
Gio.DBusCallFlags.NONE,
|
|
219
|
-
|
|
282
|
+
timeout_msec,
|
|
220
283
|
None,
|
|
221
284
|
)
|
|
222
285
|
except _glib_error(GLib) as exc:
|
|
286
|
+
if _is_reply_timeout(Gio, exc):
|
|
287
|
+
expired = timeout_msec if timeout_msec >= 0 else _GIO_DEFAULT_TIMEOUT_MSEC
|
|
288
|
+
raise PortalTimeoutError(method, expired / 1000) from exc
|
|
223
289
|
raise PortalError(f"{method} failed on the D-Bus: {exc}") from exc
|
|
224
290
|
|
|
225
291
|
|
|
292
|
+
def _close_session(
|
|
293
|
+
connection: Any,
|
|
294
|
+
Gio: Any,
|
|
295
|
+
GLib: Any,
|
|
296
|
+
busname: str,
|
|
297
|
+
session_handle: str,
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Call ``Session.Close()``, logging rather than raising on failure.
|
|
300
|
+
|
|
301
|
+
Shared by `RemoteDesktopSession.close` and `negotiate`'s failure path,
|
|
302
|
+
which need the same thing of it: the session may already be gone (the
|
|
303
|
+
portal restarted, the user revoked the grant), and neither a caller
|
|
304
|
+
tidying up nor an exception already unwinding is helped by a second
|
|
305
|
+
failure thrown over the top of the first.
|
|
306
|
+
"""
|
|
307
|
+
try:
|
|
308
|
+
_call_sync(
|
|
309
|
+
connection,
|
|
310
|
+
Gio,
|
|
311
|
+
GLib,
|
|
312
|
+
busname,
|
|
313
|
+
session_handle,
|
|
314
|
+
_SESSION_INTERFACE,
|
|
315
|
+
"Close",
|
|
316
|
+
None,
|
|
317
|
+
None,
|
|
318
|
+
)
|
|
319
|
+
except PortalError as exc:
|
|
320
|
+
logger.debug("closing the portal session failed: %s", exc)
|
|
321
|
+
|
|
322
|
+
|
|
226
323
|
def _returned_handle(reply: Any) -> str | None:
|
|
227
324
|
"""The request object path a Request-returning call replied with.
|
|
228
325
|
|
|
@@ -269,11 +366,16 @@ def _request(
|
|
|
269
366
|
call at all -- the pattern xdg-desktop-portal's own documentation
|
|
270
367
|
describes.
|
|
271
368
|
|
|
272
|
-
Raises :class:`PortalTimeoutError` if
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
369
|
+
Raises :class:`PortalTimeoutError` if the whole round trip -- the call
|
|
370
|
+
that returns the request handle, then the ``Response`` that answers it
|
|
371
|
+
-- exceeds ``timeout``. The nested loop is otherwise unbounded, and a
|
|
372
|
+
portal that dies after accepting the call sends no ``Response`` and no
|
|
373
|
+
error, leaving the caller wedged with nothing to poll and no way out.
|
|
374
|
+
Both legs share one deadline (see `_msec_until`), since a caller asking
|
|
375
|
+
for 60 seconds means the answer arrives inside 60 seconds, not inside
|
|
376
|
+
however many 60-second waits the sequence happens to be built from.
|
|
276
377
|
"""
|
|
378
|
+
deadline = time.monotonic() + timeout
|
|
277
379
|
unique_name = connection.get_unique_name()
|
|
278
380
|
escaped_sender = unique_name[1:].replace(".", "_")
|
|
279
381
|
token = uuid.uuid4().hex
|
|
@@ -337,6 +439,7 @@ def _request(
|
|
|
337
439
|
method,
|
|
338
440
|
parameters,
|
|
339
441
|
None,
|
|
442
|
+
_msec_until(deadline),
|
|
340
443
|
)
|
|
341
444
|
# The spec says the handle the call returns matches the path derived
|
|
342
445
|
# from our own handle_token, but a portal is free to hand back
|
|
@@ -353,7 +456,7 @@ def _request(
|
|
|
353
456
|
# is not running yet does not stop the later run(), so running it
|
|
354
457
|
# then would block with the reply already delivered.
|
|
355
458
|
if not result:
|
|
356
|
-
timeout_source = GLib.timeout_add(
|
|
459
|
+
timeout_source = GLib.timeout_add(_msec_until(deadline), on_timeout)
|
|
357
460
|
try:
|
|
358
461
|
loop.run()
|
|
359
462
|
finally:
|
|
@@ -377,12 +480,18 @@ def _call_for_fd(
|
|
|
377
480
|
interface: str,
|
|
378
481
|
method: str,
|
|
379
482
|
session_handle: str,
|
|
483
|
+
timeout: float,
|
|
380
484
|
) -> int:
|
|
381
485
|
"""Call a method that returns a fd via a GUnixFDList index.
|
|
382
486
|
|
|
383
487
|
The fd that comes back is *owned* -- `g_unix_fd_list_get()` dups it --
|
|
384
488
|
so whoever receives it has to close it. See
|
|
385
489
|
:meth:`RemoteDesktopSession.close`.
|
|
490
|
+
|
|
491
|
+
``ConnectToEIS`` returns no `Request`, so there is no ``Response`` to
|
|
492
|
+
wait on and nothing here beyond the call itself -- but it is a call to
|
|
493
|
+
the same portal that just made the caller wait on a consent dialog, so
|
|
494
|
+
it is bounded by the same ``timeout`` rather than by GIO's default.
|
|
386
495
|
"""
|
|
387
496
|
try:
|
|
388
497
|
reply, fd_list = connection.call_with_unix_fd_list_sync(
|
|
@@ -393,17 +502,22 @@ def _call_for_fd(
|
|
|
393
502
|
GLib.Variant("(oa{sv})", (session_handle, {})),
|
|
394
503
|
GLib.VariantType.new("(h)"),
|
|
395
504
|
Gio.DBusCallFlags.NONE,
|
|
396
|
-
|
|
505
|
+
int(timeout * 1000),
|
|
397
506
|
None,
|
|
398
507
|
None,
|
|
399
508
|
)
|
|
400
509
|
except _glib_error(GLib) as exc:
|
|
510
|
+
if _is_reply_timeout(Gio, exc):
|
|
511
|
+
raise PortalTimeoutError(method, timeout) from exc
|
|
401
512
|
raise PortalError(f"{method} failed on the D-Bus: {exc}") from exc
|
|
402
513
|
(handle_index,) = reply.unpack()
|
|
403
514
|
return fd_list.get(handle_index)
|
|
404
515
|
|
|
405
516
|
|
|
406
|
-
def _remote_desktop_version(
|
|
517
|
+
def _remote_desktop_version(
|
|
518
|
+
connection: Any, Gio: Any, GLib: Any, busname: str, timeout: float
|
|
519
|
+
) -> int:
|
|
520
|
+
"""Read the RemoteDesktop portal's ``version`` property."""
|
|
407
521
|
reply = _call_sync(
|
|
408
522
|
connection,
|
|
409
523
|
Gio,
|
|
@@ -414,6 +528,7 @@ def _remote_desktop_version(connection: Any, Gio: Any, GLib: Any, busname: str)
|
|
|
414
528
|
"Get",
|
|
415
529
|
GLib.Variant("(ss)", (_REMOTE_DESKTOP, "version")),
|
|
416
530
|
None,
|
|
531
|
+
int(timeout * 1000),
|
|
417
532
|
)
|
|
418
533
|
(version,) = reply.unpack()
|
|
419
534
|
return int(version)
|
|
@@ -446,10 +561,15 @@ class RemoteDesktopSession:
|
|
|
446
561
|
eis_fd: int,
|
|
447
562
|
restore_token: str | None,
|
|
448
563
|
session_handle: str | None = None,
|
|
564
|
+
busname: str = _BUS_NAME,
|
|
449
565
|
) -> None:
|
|
450
566
|
self._connection = connection
|
|
451
567
|
self._eis_fd: int | None = eis_fd
|
|
452
568
|
self._session_handle = session_handle
|
|
569
|
+
# Whichever bus name negotiate() used, not the default: a session
|
|
570
|
+
# created on an alternate portal name has to be closed on that same
|
|
571
|
+
# name, or Session.Close() goes to a portal that never heard of it.
|
|
572
|
+
self._busname = busname
|
|
453
573
|
# Mirrors libei.oeffis.Oeffis's ownership rule: reading `eis_fd`
|
|
454
574
|
# hands the fd to the caller, so close() must not also close it once
|
|
455
575
|
# that has happened -- but nothing else will ever close it if the
|
|
@@ -516,19 +636,13 @@ class RemoteDesktopSession:
|
|
|
516
636
|
return
|
|
517
637
|
Gio, GLib = gio_modules
|
|
518
638
|
try:
|
|
519
|
-
|
|
639
|
+
_close_session(
|
|
520
640
|
self._connection,
|
|
521
641
|
Gio,
|
|
522
642
|
GLib,
|
|
523
|
-
|
|
643
|
+
self._busname,
|
|
524
644
|
self._session_handle,
|
|
525
|
-
_SESSION_INTERFACE,
|
|
526
|
-
"Close",
|
|
527
|
-
None,
|
|
528
|
-
None,
|
|
529
645
|
)
|
|
530
|
-
except PortalError as exc:
|
|
531
|
-
logger.debug("closing the portal session failed: %s", exc)
|
|
532
646
|
finally:
|
|
533
647
|
self._session_handle = None
|
|
534
648
|
self._connection = None
|
|
@@ -578,7 +692,11 @@ class RemoteDesktopSession:
|
|
|
578
692
|
:class:`PortalDeniedError` if any step is declined, and
|
|
579
693
|
:class:`PortalTimeoutError` if any one round trip exceeds
|
|
580
694
|
``timeout`` seconds (60 by default -- generous, since ``Start``
|
|
581
|
-
waits on a human answering a dialog).
|
|
695
|
+
waits on a human answering a dialog). Any of those raised after
|
|
696
|
+
``CreateSession`` has succeeded closes the session it created on
|
|
697
|
+
the way out; nothing else could, since no `RemoteDesktopSession`
|
|
698
|
+
exists yet to own it and the portal would keep it alive for the
|
|
699
|
+
life of the bus connection.
|
|
582
700
|
|
|
583
701
|
``devices`` selects what to ask for. ``DeviceType.ALL_DEVICES`` is
|
|
584
702
|
liboeffis's sentinel for "everything" and is literally ``0``, which
|
|
@@ -628,7 +746,7 @@ class RemoteDesktopSession:
|
|
|
628
746
|
except _glib_error(GLib) as exc:
|
|
629
747
|
raise PortalError(f"cannot reach the session bus: {exc}") from exc
|
|
630
748
|
|
|
631
|
-
version = _remote_desktop_version(connection, Gio, GLib, busname)
|
|
749
|
+
version = _remote_desktop_version(connection, Gio, GLib, busname, timeout)
|
|
632
750
|
if version < _MIN_REMOTE_DESKTOP_VERSION:
|
|
633
751
|
raise PortalVersionError(
|
|
634
752
|
f"RemoteDesktop version {version} is too old for "
|
|
@@ -653,8 +771,60 @@ class RemoteDesktopSession:
|
|
|
653
771
|
)
|
|
654
772
|
if code != 0:
|
|
655
773
|
raise PortalDeniedError("CreateSession")
|
|
656
|
-
session_handle = results
|
|
774
|
+
session_handle = results.get("session_handle")
|
|
775
|
+
if not isinstance(session_handle, str):
|
|
776
|
+
# Approved, but with no handle to address the rest of the
|
|
777
|
+
# sequence to. `results` is portal-supplied data like any other
|
|
778
|
+
# reply, so a malformed one fails the way the rest of this
|
|
779
|
+
# module's do -- rather than as the KeyError a direct index
|
|
780
|
+
# would raise straight past a caller's `except PortalError`.
|
|
781
|
+
raise PortalError("CreateSession returned no session_handle")
|
|
782
|
+
|
|
783
|
+
# Past this point a session exists inside xdg-desktop-portal, and
|
|
784
|
+
# nothing else can close it: no RemoteDesktopSession owns it yet,
|
|
785
|
+
# and the connection it was created on is GLib's shared session-bus
|
|
786
|
+
# singleton, which outlives this failure rather than taking the
|
|
787
|
+
# session down with it. So every exit from here closes it.
|
|
788
|
+
try:
|
|
789
|
+
return cls._connect_devices(
|
|
790
|
+
connection,
|
|
791
|
+
Gio,
|
|
792
|
+
GLib,
|
|
793
|
+
busname,
|
|
794
|
+
session_handle,
|
|
795
|
+
devices=devices,
|
|
796
|
+
persist_mode=persist_mode,
|
|
797
|
+
restore_token=restore_token,
|
|
798
|
+
timeout=timeout,
|
|
799
|
+
)
|
|
800
|
+
except BaseException:
|
|
801
|
+
# BaseException, not Exception: `Start` blocks on a human
|
|
802
|
+
# answering a consent dialog, so Ctrl-C during that wait is a
|
|
803
|
+
# routine way out of this function -- and it strands an
|
|
804
|
+
# approved session exactly as a decline does.
|
|
805
|
+
_close_session(connection, Gio, GLib, busname, session_handle)
|
|
806
|
+
raise
|
|
807
|
+
|
|
808
|
+
@classmethod
|
|
809
|
+
def _connect_devices(
|
|
810
|
+
cls,
|
|
811
|
+
connection: Any,
|
|
812
|
+
Gio: Any,
|
|
813
|
+
GLib: Any,
|
|
814
|
+
busname: str,
|
|
815
|
+
session_handle: str,
|
|
816
|
+
*,
|
|
817
|
+
devices: DeviceType,
|
|
818
|
+
persist_mode: PersistMode,
|
|
819
|
+
restore_token: str | None,
|
|
820
|
+
timeout: float,
|
|
821
|
+
) -> RemoteDesktopSession:
|
|
822
|
+
"""``SelectDevices`` -> ``Start`` -> ``ConnectToEIS``, given a session.
|
|
657
823
|
|
|
824
|
+
Split out of :meth:`negotiate` only so that the caller can wrap the
|
|
825
|
+
whole of it in one ``try`` -- every step here can fail, and every
|
|
826
|
+
one of those failures leaves the same session behind to be closed.
|
|
827
|
+
"""
|
|
658
828
|
# ALL_DEVICES is 0, which SelectDevices reads as "no device types"
|
|
659
829
|
# rather than "every device type" -- see _ALL_DEVICE_TYPES.
|
|
660
830
|
types = _ALL_DEVICE_TYPES if devices == DeviceType.ALL_DEVICES else devices
|
|
@@ -704,5 +874,12 @@ class RemoteDesktopSession:
|
|
|
704
874
|
_REMOTE_DESKTOP,
|
|
705
875
|
"ConnectToEIS",
|
|
706
876
|
session_handle,
|
|
877
|
+
timeout,
|
|
878
|
+
)
|
|
879
|
+
return cls(
|
|
880
|
+
connection,
|
|
881
|
+
eis_fd,
|
|
882
|
+
new_restore_token,
|
|
883
|
+
session_handle,
|
|
884
|
+
busname,
|
|
707
885
|
)
|
|
708
|
-
return cls(connection, eis_fd, new_restore_token, session_handle)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-libei
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Inject and receive input on Wayland from Python: ctypes bindings for libei, libeis and liboeffis
|
|
5
5
|
Author: Dennis K. Paulsen
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,7 +9,7 @@ Project-URL: repository, https://github.com/ctrondlp/python-libei
|
|
|
9
9
|
Project-URL: issues, https://github.com/ctrondlp/python-libei/issues
|
|
10
10
|
Project-URL: changelog, https://github.com/ctrondlp/python-libei/blob/main/CHANGELOG.md
|
|
11
11
|
Keywords: wayland,libei,libeis,liboeffis,input,input-emulation,emulated-input,portal,xdg-desktop-portal,remote-desktop,automation,gui-testing,accessibility,ctypes
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Operating System :: POSIX :: Linux
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -136,7 +136,7 @@ pointer.
|
|
|
136
136
|
|
|
137
137
|
## Status
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
Beta (`0.4.1`), published on [PyPI](https://pypi.org/project/python-libei/)
|
|
140
140
|
since `0.1.0`, and the API is not frozen — expect renames before 1.0. What
|
|
141
141
|
that qualifier covers, concretely:
|
|
142
142
|
|
|
@@ -151,7 +151,8 @@ that qualifier covers, concretely:
|
|
|
151
151
|
verified by hand, since they need an interactive consent dialog that
|
|
152
152
|
nothing here can drive automatically — `tests/test_portal.py` covers
|
|
153
153
|
`libei.portal`'s orchestration (raceless subscribe-before-call, the
|
|
154
|
-
`session_handle_token` crash workaround, persist_mode/restore_token
|
|
154
|
+
`session_handle_token` crash workaround, persist_mode/restore_token, and
|
|
155
|
+
closing the portal session on a negotiation that fails part-way)
|
|
155
156
|
against a fake D-Bus connection only. `libei.oeffis` was verified by hand
|
|
156
157
|
on 2026-08-25 (see [Troubleshooting](#troubleshooting)), and
|
|
157
158
|
`libei.portal` on 2026-09-01 against a real GNOME Wayland session
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
libei/__init__.py,sha256=
|
|
1
|
+
libei/__init__.py,sha256=_q8OgwIhvpHsOyrFrhjO8-ODsXMhPVUfHrpU52YRqEg,1675
|
|
2
2
|
libei/_cobject.py,sha256=msrbviABSWjc5fKsXHSCg7nQ0Y4Etcq_Dkh0fblW-sY,12845
|
|
3
3
|
libei/ei.py,sha256=Da3ezXnFPXtejlcJFH8bcE5f4pxKZXXqb6ltCh2zAdE,46704
|
|
4
4
|
libei/eis.py,sha256=aYWOCN1NlRchcWd6NjIUi1eYMFWJbp0UHMcQFEJ0ii4,43642
|
|
5
5
|
libei/oeffis.py,sha256=UcnDwLErFxB0xuktNpIzAK01ylpl6ZrmaGnWiUOcFOo,9302
|
|
6
|
-
libei/portal.py,sha256=
|
|
6
|
+
libei/portal.py,sha256=K74g5yhnlo0E1arGvoyD24zRnkKk2Th90-xEeg9wO_M,35342
|
|
7
7
|
libei/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
libei/_capi/__init__.py,sha256=3VxixYYlr_ZcuiK0GwrCpbE3VvEv_O990mJzdIJ8i74,209
|
|
9
9
|
libei/_capi/libei.py,sha256=gpp42bhqPonhVD2zmFWCl4rdk_lm4XvD_7W2Rw5RsuI,11308
|
|
10
10
|
libei/_capi/libeis.py,sha256=jQR0Z0YN9qa71mAcSJ__-UaoF8PsVsBs8OXwjfqstFk,13080
|
|
11
11
|
libei/_capi/liboeffis.py,sha256=V8jdmJm3qOQUzzNiCAXt4Jr2JY11Y4-w6NIfIxbxGI4,1218
|
|
12
12
|
libei/_capi/loader.py,sha256=k7fb_Nz0fg5QkuLJ_duKXj8bESSktLQ2ZyS5LNQ5v2I,4689
|
|
13
|
-
python_libei-0.
|
|
14
|
-
python_libei-0.
|
|
15
|
-
python_libei-0.
|
|
16
|
-
python_libei-0.
|
|
17
|
-
python_libei-0.
|
|
13
|
+
python_libei-0.4.1.dist-info/licenses/LICENSE,sha256=l6xbMU6Y-JZDzmBciBj-J5t6h6jNg_Bcyp4bHxDepqU,1074
|
|
14
|
+
python_libei-0.4.1.dist-info/METADATA,sha256=EuF32FjOc05E9MdG_TDJ-mxAjUwGgu8HOVyXO1YXy08,40102
|
|
15
|
+
python_libei-0.4.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
16
|
+
python_libei-0.4.1.dist-info/top_level.txt,sha256=_DQXzGjDsUBENI_cNkiOxPB4xi8coCbQS1lq18FMudQ,6
|
|
17
|
+
python_libei-0.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|