python-libei 0.6.1__py3-none-any.whl → 0.7.0__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.6.1"
34
+ __version__ = "0.7.0"
35
35
 
36
36
  __all__ = ["__version__"]
libei/portal.py CHANGED
@@ -185,6 +185,28 @@ class PortalDeniedError(PortalError):
185
185
  self.message = message
186
186
 
187
187
 
188
+ class PortalSessionClosedError(PortalError):
189
+ """The portal session ended while something was still waiting on it.
190
+
191
+ The ``org.freedesktop.portal.Session`` interface emits ``Closed`` when
192
+ the compositor or the portal ends a session from its side -- a
193
+ compositor restart, the portal process dying, a session revoked from a
194
+ settings panel. Distinct from :class:`PortalTimeoutError` (the session
195
+ is fine, the signal just has not come) and from
196
+ :class:`PortalDeniedError` (a request was answered, negatively): here
197
+ there is no session left to answer anything, so the wait that raised
198
+ this can never be satisfied by waiting longer.
199
+ """
200
+
201
+ def __init__(self, step: str) -> None:
202
+ """Name what was being waited for when the session went away."""
203
+ super().__init__(
204
+ f"the portal session was closed while waiting for {step}; "
205
+ "the compositor or portal ended it from its side"
206
+ )
207
+ self.step = step
208
+
209
+
188
210
  def _gio() -> tuple[Any, Any] | None:
189
211
  """Import Gio and GLib, or return None.
190
212
 
@@ -1038,6 +1060,27 @@ def _wait_for_signal(
1038
1060
  loop = GLib.MainLoop()
1039
1061
  result: dict[str, Any] = {}
1040
1062
  timed_out = False
1063
+ session_closed = False
1064
+
1065
+ def on_closed(*_args: Any) -> None:
1066
+ """End the wait: a closed session can never deliver the signal.
1067
+
1068
+ ``Closed`` is emitted on the **session object's own path** (unlike
1069
+ ``Activated``/``Deactivated``, which fire on the portal object --
1070
+ see above), so the subscription below uses ``session_handle`` as
1071
+ the path and needs no payload filtering: any ``Closed`` there is
1072
+ this session's. Without this subscription, a wait with
1073
+ ``timeout=None`` on a session the compositor has ended -- a
1074
+ compositor restart, the portal dying, capture revoked from a
1075
+ settings panel -- blocked forever, indistinguishable from a signal
1076
+ that merely had not fired yet.
1077
+ """
1078
+ nonlocal session_closed
1079
+ if result:
1080
+ return
1081
+ logger.debug("session %s closed while waiting for %s", session_handle, signal)
1082
+ session_closed = True
1083
+ loop.quit()
1041
1084
 
1042
1085
  def on_signal(
1043
1086
  _conn: Any,
@@ -1086,6 +1129,16 @@ def _wait_for_signal(
1086
1129
  on_signal,
1087
1130
  None,
1088
1131
  )
1132
+ closed_subscription = connection.signal_subscribe(
1133
+ busname,
1134
+ _SESSION_INTERFACE,
1135
+ "Closed",
1136
+ session_handle,
1137
+ None,
1138
+ Gio.DBusSignalFlags.NONE,
1139
+ on_closed,
1140
+ None,
1141
+ )
1089
1142
  logger.debug(
1090
1143
  "waiting up to %s for %s.%s on %s for session %s",
1091
1144
  "forever" if timeout is None else f"{timeout:g}s",
@@ -1095,7 +1148,7 @@ def _wait_for_signal(
1095
1148
  session_handle,
1096
1149
  )
1097
1150
  try:
1098
- if not result:
1151
+ if not result and not session_closed:
1099
1152
  timeout_source = None
1100
1153
  if timeout is not None:
1101
1154
  timeout_source = GLib.timeout_add(int(timeout * 1000), on_timeout)
@@ -1115,6 +1168,9 @@ def _wait_for_signal(
1115
1168
  GLib.source_remove(timeout_source)
1116
1169
  finally:
1117
1170
  connection.signal_unsubscribe(subscription)
1171
+ connection.signal_unsubscribe(closed_subscription)
1172
+ if session_closed:
1173
+ raise PortalSessionClosedError(signal)
1118
1174
  if timed_out:
1119
1175
  # timed_out is only ever set inside on_timeout, itself only ever
1120
1176
  # registered when timeout is not None -- so this always holds, but
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-libei
3
- Version: 0.6.1
3
+ Version: 0.7.0
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
@@ -40,6 +40,7 @@ Dynamic: license-file
40
40
 
41
41
  # python-libei
42
42
 
43
+ [![Contributing](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](https://github.com/ctrondlp/python-libei/blob/main/CONTRIBUTING.md)
43
44
  [![CI](https://github.com/ctrondlp/python-libei/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ctrondlp/python-libei/actions/workflows/ci.yml)
44
45
  [![PyPI](https://img.shields.io/pypi/v/python-libei)](https://pypi.org/project/python-libei/)
45
46
  [![License](https://img.shields.io/pypi/l/python-libei)](https://github.com/ctrondlp/python-libei/blob/main/LICENSE)
@@ -174,7 +175,7 @@ and why, is in
174
175
 
175
176
  ## Status
176
177
 
177
- Beta (`0.6.1`), published on [PyPI](https://pypi.org/project/python-libei/)
178
+ Beta (`0.7.0`), published on [PyPI](https://pypi.org/project/python-libei/)
178
179
  since `0.1.0`, and **the API is not frozen** — expect renames before 1.0.
179
180
 
180
181
  The injection path is exercised end to end against the real libraries by the
@@ -1,17 +1,17 @@
1
- libei/__init__.py,sha256=OP-aoBP8bXNhL8q3gXxVL4FtWLhVyaBlmoQEInBdhp4,1858
1
+ libei/__init__.py,sha256=3Ru2esZsveT5oIlBPJ_6DH8RsRoofVvvf27WZafcOZg,1858
2
2
  libei/_cobject.py,sha256=XjbUNJpZ87jAyPWh6dmRslk6cMy831oMRFhWmfjLDFo,13344
3
3
  libei/ei.py,sha256=W4akKc_BNVfL0FroSXLGD1Z_L3OmYjZT5QDHNnEtMP8,49367
4
4
  libei/eis.py,sha256=X_umAaAAbAMC9d2RdnMReeJZI61WsnKz3cySY5YrGr0,45841
5
5
  libei/oeffis.py,sha256=gJHR90s9VhQrgkjWUMDx-lxZ8qKxkttnqbx_xo-7oxs,10035
6
- libei/portal.py,sha256=XC_FsKueIpcmAPZ4TDXJgFhucJNeMEO2byTJHG3GMmI,69756
6
+ libei/portal.py,sha256=Q2GbZBUnKJZ90WJtDtpkcgQbvsCLY6xJwngmYRg5qQ0,72089
7
7
  libei/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  libei/_capi/__init__.py,sha256=raISSWSV9hAcE92DcLCz-vUb1IPcsmxRZaS-iAoQJBE,211
9
9
  libei/_capi/libei.py,sha256=qsccHa_komCxtvPYi6AobszhFbEG2qA8QYJJVr8SaGM,11356
10
10
  libei/_capi/libeis.py,sha256=C_Uh7DwNncFC8A4wuMhviAj5C_TUmKHtLJYq0Qhhz1E,13142
11
11
  libei/_capi/liboeffis.py,sha256=V8jdmJm3qOQUzzNiCAXt4Jr2JY11Y4-w6NIfIxbxGI4,1218
12
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,,
13
+ python_libei-0.7.0.dist-info/licenses/LICENSE,sha256=l6xbMU6Y-JZDzmBciBj-J5t6h6jNg_Bcyp4bHxDepqU,1074
14
+ python_libei-0.7.0.dist-info/METADATA,sha256=kCY_c4mpI43wAk_72MFq1yyv3VZm8r9NBaexaQzgWCg,22882
15
+ python_libei-0.7.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
16
+ python_libei-0.7.0.dist-info/top_level.txt,sha256=_DQXzGjDsUBENI_cNkiOxPB4xi8coCbQS1lq18FMudQ,6
17
+ python_libei-0.7.0.dist-info/RECORD,,