urwid 2.6.0.post0__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.

Potentially problematic release.


This version of urwid might be problematic. Click here for more details.

Files changed (75) hide show
  1. urwid/__init__.py +333 -0
  2. urwid/canvas.py +1413 -0
  3. urwid/command_map.py +137 -0
  4. urwid/container.py +59 -0
  5. urwid/decoration.py +65 -0
  6. urwid/display/__init__.py +97 -0
  7. urwid/display/_posix_raw_display.py +413 -0
  8. urwid/display/_raw_display_base.py +914 -0
  9. urwid/display/_web.css +12 -0
  10. urwid/display/_web.js +462 -0
  11. urwid/display/_win32.py +171 -0
  12. urwid/display/_win32_raw_display.py +269 -0
  13. urwid/display/common.py +1219 -0
  14. urwid/display/curses.py +690 -0
  15. urwid/display/escape.py +624 -0
  16. urwid/display/html_fragment.py +251 -0
  17. urwid/display/lcd.py +518 -0
  18. urwid/display/raw.py +37 -0
  19. urwid/display/web.py +636 -0
  20. urwid/event_loop/__init__.py +55 -0
  21. urwid/event_loop/abstract_loop.py +175 -0
  22. urwid/event_loop/asyncio_loop.py +231 -0
  23. urwid/event_loop/glib_loop.py +294 -0
  24. urwid/event_loop/main_loop.py +721 -0
  25. urwid/event_loop/select_loop.py +230 -0
  26. urwid/event_loop/tornado_loop.py +206 -0
  27. urwid/event_loop/trio_loop.py +302 -0
  28. urwid/event_loop/twisted_loop.py +269 -0
  29. urwid/event_loop/zmq_loop.py +275 -0
  30. urwid/font.py +695 -0
  31. urwid/graphics.py +96 -0
  32. urwid/highlight.css +19 -0
  33. urwid/listbox.py +1899 -0
  34. urwid/monitored_list.py +522 -0
  35. urwid/numedit.py +376 -0
  36. urwid/signals.py +330 -0
  37. urwid/split_repr.py +130 -0
  38. urwid/str_util.py +358 -0
  39. urwid/text_layout.py +632 -0
  40. urwid/treetools.py +515 -0
  41. urwid/util.py +557 -0
  42. urwid/version.py +16 -0
  43. urwid/vterm.py +1806 -0
  44. urwid/widget/__init__.py +181 -0
  45. urwid/widget/attr_map.py +161 -0
  46. urwid/widget/attr_wrap.py +140 -0
  47. urwid/widget/bar_graph.py +649 -0
  48. urwid/widget/big_text.py +77 -0
  49. urwid/widget/box_adapter.py +126 -0
  50. urwid/widget/columns.py +1145 -0
  51. urwid/widget/constants.py +574 -0
  52. urwid/widget/container.py +227 -0
  53. urwid/widget/divider.py +110 -0
  54. urwid/widget/edit.py +718 -0
  55. urwid/widget/filler.py +403 -0
  56. urwid/widget/frame.py +539 -0
  57. urwid/widget/grid_flow.py +539 -0
  58. urwid/widget/line_box.py +194 -0
  59. urwid/widget/overlay.py +829 -0
  60. urwid/widget/padding.py +597 -0
  61. urwid/widget/pile.py +971 -0
  62. urwid/widget/popup.py +170 -0
  63. urwid/widget/progress_bar.py +141 -0
  64. urwid/widget/scrollable.py +597 -0
  65. urwid/widget/solid_fill.py +44 -0
  66. urwid/widget/text.py +354 -0
  67. urwid/widget/widget.py +852 -0
  68. urwid/widget/widget_decoration.py +166 -0
  69. urwid/widget/wimp.py +792 -0
  70. urwid/wimp.py +23 -0
  71. urwid-2.6.0.post0.dist-info/COPYING +504 -0
  72. urwid-2.6.0.post0.dist-info/METADATA +332 -0
  73. urwid-2.6.0.post0.dist-info/RECORD +75 -0
  74. urwid-2.6.0.post0.dist-info/WHEEL +5 -0
  75. urwid-2.6.0.post0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,175 @@
1
+ # Urwid main loop code
2
+ # Copyright (C) 2004-2012 Ian Ward
3
+ # Copyright (C) 2008 Walter Mundt
4
+ # Copyright (C) 2009 Andrew Psaltis
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ # Urwid web site: https://urwid.org/
21
+
22
+ """Abstract shared code for urwid EventLoop implementation."""
23
+
24
+ from __future__ import annotations
25
+
26
+ import abc
27
+ import logging
28
+ import signal
29
+ import typing
30
+
31
+ if typing.TYPE_CHECKING:
32
+ import asyncio
33
+ from collections.abc import Callable
34
+ from concurrent.futures import Executor, Future
35
+ from types import FrameType
36
+
37
+ from typing_extensions import ParamSpec
38
+
39
+ _T = typing.TypeVar("_T")
40
+ _Spec = ParamSpec("_Spec")
41
+
42
+ __all__ = ("EventLoop", "ExitMainLoop")
43
+
44
+
45
+ class ExitMainLoop(Exception):
46
+ """
47
+ When this exception is raised within a main loop the main loop
48
+ will exit cleanly.
49
+ """
50
+
51
+
52
+ class EventLoop(abc.ABC):
53
+ """
54
+ Abstract class representing an event loop to be used by :class:`MainLoop`.
55
+ """
56
+
57
+ __slots__ = ("logger",)
58
+
59
+ def __init__(self) -> None:
60
+ self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
61
+
62
+ def run_in_executor(
63
+ self,
64
+ executor: Executor,
65
+ func: Callable[_Spec, _T],
66
+ *args: _Spec.args,
67
+ ) -> Future[_T] | asyncio.Future[_T]:
68
+ """Run callable in executor if supported.
69
+
70
+ :param executor: Executor to use for running the function
71
+ :type executor: concurrent.futures.Executor
72
+ :param func: function to call
73
+ :type func: Callable
74
+ :param args: arguments to function (positional only)
75
+ :type args: object
76
+ :return: future object for the function call outcome.
77
+ (exact future type depends on the event loop type)
78
+ :rtype: concurrent.futures.Future | asyncio.Future
79
+ """
80
+ raise NotImplementedError
81
+
82
+ @abc.abstractmethod
83
+ def alarm(self, seconds: float, callback: Callable[[], typing.Any]) -> typing.Any:
84
+ """
85
+ Call callback() a given time from now. No parameters are
86
+ passed to callback.
87
+
88
+ This method has no default implementation.
89
+
90
+ Returns a handle that may be passed to remove_alarm()
91
+
92
+ seconds -- floating point time to wait before calling callback
93
+ callback -- function to call from event loop
94
+ """
95
+
96
+ @abc.abstractmethod
97
+ def enter_idle(self, callback):
98
+ """
99
+ Add a callback for entering idle.
100
+
101
+ This method has no default implementation.
102
+
103
+ Returns a handle that may be passed to remove_idle()
104
+ """
105
+
106
+ @abc.abstractmethod
107
+ def remove_alarm(self, handle) -> bool:
108
+ """
109
+ Remove an alarm.
110
+
111
+ This method has no default implementation.
112
+
113
+ Returns True if the alarm exists, False otherwise
114
+ """
115
+
116
+ @abc.abstractmethod
117
+ def remove_enter_idle(self, handle) -> bool:
118
+ """
119
+ Remove an idle callback.
120
+
121
+ This method has no default implementation.
122
+
123
+ Returns True if the handle was removed.
124
+ """
125
+
126
+ @abc.abstractmethod
127
+ def remove_watch_file(self, handle) -> bool:
128
+ """
129
+ Remove an input file.
130
+
131
+ This method has no default implementation.
132
+
133
+ Returns True if the input file exists, False otherwise
134
+ """
135
+
136
+ @abc.abstractmethod
137
+ def run(self) -> None:
138
+ """
139
+ Start the event loop. Exit the loop when any callback raises
140
+ an exception. If ExitMainLoop is raised, exit cleanly.
141
+
142
+ This method has no default implementation.
143
+ """
144
+
145
+ @abc.abstractmethod
146
+ def watch_file(self, fd: int, callback: Callable[[], typing.Any]):
147
+ """
148
+ Call callback() when fd has some data to read. No parameters
149
+ are passed to callback.
150
+
151
+ This method has no default implementation.
152
+
153
+ Returns a handle that may be passed to remove_watch_file()
154
+
155
+ fd -- file descriptor to watch for input
156
+ callback -- function to call when input is available
157
+ """
158
+
159
+ def set_signal_handler(
160
+ self,
161
+ signum: int,
162
+ handler: Callable[[int, FrameType | None], typing.Any] | int | signal.Handlers,
163
+ ) -> Callable[[int, FrameType | None], typing.Any] | int | signal.Handlers | None:
164
+ """
165
+ Sets the signal handler for signal signum.
166
+
167
+ The default implementation of :meth:`set_signal_handler`
168
+ is simply a proxy function that calls :func:`signal.signal()`
169
+ and returns the resulting value.
170
+
171
+ signum -- signal number
172
+ handler -- function (taking signum as its single argument),
173
+ or `signal.SIG_IGN`, or `signal.SIG_DFL`
174
+ """
175
+ return signal.signal(signum, handler)
@@ -0,0 +1,231 @@
1
+ # Urwid main loop code
2
+ # Copyright (C) 2004-2012 Ian Ward
3
+ # Copyright (C) 2008 Walter Mundt
4
+ # Copyright (C) 2009 Andrew Psaltis
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ # Urwid web site: https://urwid.org/
21
+
22
+ """Asyncio based urwid EventLoop implementation."""
23
+
24
+ from __future__ import annotations
25
+
26
+ import asyncio
27
+ import functools
28
+ import logging
29
+ import sys
30
+ import typing
31
+
32
+ from .abstract_loop import EventLoop, ExitMainLoop
33
+
34
+ if typing.TYPE_CHECKING:
35
+ from collections.abc import Callable
36
+ from concurrent.futures import Executor
37
+
38
+ from typing_extensions import ParamSpec
39
+
40
+ _Spec = ParamSpec("_Spec")
41
+ _T = typing.TypeVar("_T")
42
+
43
+ __all__ = ("AsyncioEventLoop",)
44
+ IS_WINDOWS = sys.platform == "win32"
45
+
46
+
47
+ class AsyncioEventLoop(EventLoop):
48
+ """
49
+ Event loop based on the standard library ``asyncio`` module.
50
+
51
+ .. warning::
52
+ Under Windows, AsyncioEventLoop globally enforces WindowsSelectorEventLoopPolicy
53
+ as a side-effect of creating a class instance.
54
+ Original event loop policy is restored in destructor method.
55
+
56
+ .. note::
57
+ If you make any changes to the urwid state outside of it
58
+ handling input or responding to alarms (for example, from asyncio.Task
59
+ running in background), and wish the screen to be
60
+ redrawn, you must call :meth:`MainLoop.draw_screen` method of the
61
+ main loop manually.
62
+
63
+ A good way to do this:
64
+ asyncio.get_event_loop().call_soon(main_loop.draw_screen)
65
+ """
66
+
67
+ def __init__(self, *, loop: asyncio.AbstractEventLoop | None = None, **kwargs) -> None:
68
+ super().__init__()
69
+ self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
70
+ if loop:
71
+ self._loop: asyncio.AbstractEventLoop = loop
72
+ self._event_loop_policy_altered: bool = False
73
+ self._original_event_loop_policy: asyncio.AbstractEventLoopPolicy | None = None
74
+ else:
75
+ self._original_event_loop_policy = asyncio.get_event_loop_policy()
76
+ if IS_WINDOWS and not isinstance(self._original_event_loop_policy, asyncio.WindowsSelectorEventLoopPolicy):
77
+ self.logger.debug("Set WindowsSelectorEventLoopPolicy as asyncio event loop policy")
78
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
79
+ self._event_loop_policy_altered = True
80
+ else:
81
+ self._event_loop_policy_altered = False
82
+
83
+ self._loop = asyncio.get_event_loop()
84
+
85
+ self._exc: BaseException | None = None
86
+
87
+ self._idle_asyncio_handle: asyncio.TimerHandle | None = None
88
+ self._idle_handle: int = 0
89
+ self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}
90
+
91
+ def __del__(self) -> None:
92
+ if self._event_loop_policy_altered:
93
+ asyncio.set_event_loop_policy(self._original_event_loop_policy) # Restore default event loop policy
94
+
95
+ def _also_call_idle(self, callback: Callable[_Spec, _T]) -> Callable[_Spec, _T]:
96
+ """
97
+ Wrap the callback to also call _entering_idle.
98
+ """
99
+
100
+ @functools.wraps(callback)
101
+ def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T:
102
+ if not self._idle_asyncio_handle:
103
+ self._idle_asyncio_handle = self._loop.call_later(0, self._entering_idle)
104
+ return callback(*args, **kwargs)
105
+
106
+ return wrapper
107
+
108
+ def _entering_idle(self) -> None:
109
+ """
110
+ Call all the registered idle callbacks.
111
+ """
112
+ try:
113
+ for callback in self._idle_callbacks.values():
114
+ callback()
115
+ finally:
116
+ self._idle_asyncio_handle = None
117
+
118
+ def run_in_executor(
119
+ self,
120
+ executor: Executor | None,
121
+ func: Callable[_Spec, _T],
122
+ *args: _Spec.args,
123
+ ) -> asyncio.Future[_T]:
124
+ """Run callable in executor.
125
+
126
+ :param executor: Executor to use for running the function. Default asyncio executor is used if None.
127
+ :type executor: concurrent.futures.Executor | None
128
+ :param func: function to call
129
+ :type func: Callable
130
+ :param args: arguments to function (positional only)
131
+ :type args: object
132
+ :return: future object for the function call outcome.
133
+ :rtype: asyncio.Future
134
+ """
135
+ return self._loop.run_in_executor(executor, func, *args)
136
+
137
+ def alarm(self, seconds: float, callback: Callable[[], typing.Any]) -> asyncio.TimerHandle:
138
+ """
139
+ Call callback() a given time from now. No parameters are
140
+ passed to callback.
141
+
142
+ Returns a handle that may be passed to remove_alarm()
143
+
144
+ seconds -- time in seconds to wait before calling callback
145
+ callback -- function to call from event loop
146
+ """
147
+ return self._loop.call_later(seconds, self._also_call_idle(callback))
148
+
149
+ def remove_alarm(self, handle) -> bool:
150
+ """
151
+ Remove an alarm.
152
+
153
+ Returns True if the alarm exists, False otherwise
154
+ """
155
+ existed = not handle.cancelled()
156
+ handle.cancel()
157
+ return existed
158
+
159
+ def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:
160
+ """
161
+ Call callback() when fd has some data to read. No parameters
162
+ are passed to callback.
163
+
164
+ Returns a handle that may be passed to remove_watch_file()
165
+
166
+ fd -- file descriptor to watch for input
167
+ callback -- function to call when input is available
168
+ """
169
+ self._loop.add_reader(fd, self._also_call_idle(callback))
170
+ return fd
171
+
172
+ def remove_watch_file(self, handle: int) -> bool:
173
+ """
174
+ Remove an input file.
175
+
176
+ Returns True if the input file exists, False otherwise
177
+ """
178
+ return self._loop.remove_reader(handle)
179
+
180
+ def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
181
+ """
182
+ Add a callback for entering idle.
183
+
184
+ Returns a handle that may be passed to remove_enter_idle()
185
+ """
186
+ # XXX there's no such thing as "idle" in most event loops; this fakes
187
+ # it by adding extra callback to the timer and file watch callbacks.
188
+ self._idle_handle += 1
189
+ self._idle_callbacks[self._idle_handle] = callback
190
+ return self._idle_handle
191
+
192
+ def remove_enter_idle(self, handle: int) -> bool:
193
+ """
194
+ Remove an idle callback.
195
+
196
+ Returns True if the handle was removed.
197
+ """
198
+ try:
199
+ del self._idle_callbacks[handle]
200
+ except KeyError:
201
+ return False
202
+ return True
203
+
204
+ def _exception_handler(self, loop: asyncio.AbstractEventLoop, context):
205
+ exc = context.get("exception")
206
+ if exc:
207
+ loop.stop()
208
+
209
+ if self._idle_asyncio_handle:
210
+ # clean it up to prevent old callbacks
211
+ # from messing things up if loop is restarted
212
+ self._idle_asyncio_handle.cancel()
213
+ self._idle_asyncio_handle = None
214
+
215
+ if not isinstance(exc, ExitMainLoop):
216
+ # Store the exc_info so we can re-raise after the loop stops
217
+ self._exc = exc
218
+ else:
219
+ loop.default_exception_handler(context)
220
+
221
+ def run(self) -> None:
222
+ """
223
+ Start the event loop. Exit the loop when any callback raises
224
+ an exception. If ExitMainLoop is raised, exit cleanly.
225
+ """
226
+ self._loop.set_exception_handler(self._exception_handler)
227
+ self._loop.run_forever()
228
+ if self._exc:
229
+ exc = self._exc
230
+ self._exc = None
231
+ raise exc.with_traceback(exc.__traceback__)
@@ -0,0 +1,294 @@
1
+ # Urwid main loop code
2
+ # Copyright (C) 2004-2012 Ian Ward
3
+ # Copyright (C) 2008 Walter Mundt
4
+ # Copyright (C) 2009 Andrew Psaltis
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ # Urwid web site: https://urwid.org/
21
+
22
+ """GLib based urwid EventLoop implementation.
23
+
24
+ PyGObject library is required.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import functools
30
+ import logging
31
+ import signal
32
+ import typing
33
+
34
+ from gi.repository import GLib
35
+
36
+ from .abstract_loop import EventLoop, ExitMainLoop
37
+
38
+ if typing.TYPE_CHECKING:
39
+ from collections.abc import Callable
40
+ from concurrent.futures import Executor, Future
41
+ from types import FrameType
42
+
43
+ from typing_extensions import Literal, ParamSpec
44
+
45
+ _Spec = ParamSpec("_Spec")
46
+ _T = typing.TypeVar("_T")
47
+
48
+ __all__ = ("GLibEventLoop",)
49
+
50
+
51
+ def _ignore_handler(_sig: int, _frame: FrameType | None = None) -> None:
52
+ return None
53
+
54
+
55
+ class GLibEventLoop(EventLoop):
56
+ """
57
+ Event loop based on GLib.MainLoop
58
+ """
59
+
60
+ def __init__(self) -> None:
61
+ super().__init__()
62
+ self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
63
+ self._alarms: list[int] = []
64
+ self._watch_files: dict[int, int] = {}
65
+ self._idle_handle: int = 0
66
+ self._glib_idle_enabled = False # have we called glib.idle_add?
67
+ self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}
68
+ self._loop = GLib.MainLoop()
69
+ self._exc: BaseException | None = None
70
+ self._enable_glib_idle()
71
+ self._signal_handlers: dict[int, int] = {}
72
+
73
+ def run_in_executor(
74
+ self,
75
+ executor: Executor,
76
+ func: Callable[_Spec, _T],
77
+ *args: _Spec.args,
78
+ **kwargs: _Spec.kwargs,
79
+ ) -> Future[_T]:
80
+ """Run callable in executor.
81
+
82
+ :param executor: Executor to use for running the function
83
+ :type executor: concurrent.futures.Executor
84
+ :param func: function to call
85
+ :type func: Callable
86
+ :param args: positional arguments to function
87
+ :type args: object
88
+ :param kwargs: keyword arguments to function
89
+ :type kwargs: object
90
+ :return: future object for the function call outcome.
91
+ :rtype: concurrent.futures.Future
92
+ """
93
+ return executor.submit(func, *args, **kwargs)
94
+
95
+ def alarm(
96
+ self,
97
+ seconds: float,
98
+ callback: Callable[[], typing.Any],
99
+ ) -> tuple[int, Callable[[], typing.Any]]:
100
+ """
101
+ Call callback() a given time from now. No parameters are
102
+ passed to callback.
103
+
104
+ Returns a handle that may be passed to remove_alarm()
105
+
106
+ seconds -- floating point time to wait before calling callback
107
+ callback -- function to call from event loop
108
+ """
109
+
110
+ @self.handle_exit
111
+ def ret_false() -> Literal[False]:
112
+ callback()
113
+ self._enable_glib_idle()
114
+ return False
115
+
116
+ fd = GLib.timeout_add(int(seconds * 1000), ret_false)
117
+ self._alarms.append(fd)
118
+ return (fd, callback)
119
+
120
+ def set_signal_handler(
121
+ self,
122
+ signum: int,
123
+ handler: Callable[[int, FrameType | None], typing.Any] | int | signal.Handlers,
124
+ ) -> None:
125
+ """
126
+ Sets the signal handler for signal signum.
127
+
128
+ .. WARNING::
129
+ Because this method uses the `GLib`-specific `unix_signal_add`
130
+ function, its behaviour is different than `signal.signal().`
131
+
132
+ If `signum` is not `SIGHUP`, `SIGINT`, `SIGTERM`, `SIGUSR1`,
133
+ `SIGUSR2` or `SIGWINCH`, this method performs no actions and
134
+ immediately returns None.
135
+
136
+ Returns None in all cases (unlike :func:`signal.signal()`).
137
+ ..
138
+
139
+ signum -- signal number
140
+ handler -- function (taking signum as its single argument),
141
+ or `signal.SIG_IGN`, or `signal.SIG_DFL`
142
+ """
143
+ glib_signals = [
144
+ signal.SIGHUP,
145
+ signal.SIGINT,
146
+ signal.SIGTERM,
147
+ signal.SIGUSR1,
148
+ signal.SIGUSR2,
149
+ ]
150
+
151
+ # GLib supports SIGWINCH as of version 2.54.
152
+ if not GLib.check_version(2, 54, 0):
153
+ glib_signals.append(signal.SIGWINCH)
154
+
155
+ if signum not in glib_signals:
156
+ # The GLib event loop supports only the signals listed above
157
+ return
158
+
159
+ if signum in self._signal_handlers:
160
+ GLib.source_remove(self._signal_handlers.pop(signum))
161
+
162
+ if handler == signal.Handlers.SIG_IGN:
163
+ handler = _ignore_handler
164
+ elif handler == signal.Handlers.SIG_DFL:
165
+ return
166
+
167
+ def final_handler(signal_number: int):
168
+ # MyPy False-negative: signal.Handlers casted
169
+ handler(signal_number, None) # type: ignore[operator]
170
+ return GLib.SOURCE_CONTINUE
171
+
172
+ source = GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signum, final_handler, signum)
173
+ self._signal_handlers[signum] = source
174
+
175
+ def remove_alarm(self, handle) -> bool:
176
+ """
177
+ Remove an alarm.
178
+
179
+ Returns True if the alarm exists, False otherwise
180
+ """
181
+ try:
182
+ self._alarms.remove(handle[0])
183
+ GLib.source_remove(handle[0])
184
+
185
+ except ValueError:
186
+ return False
187
+
188
+ return True
189
+
190
+ def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:
191
+ """
192
+ Call callback() when fd has some data to read. No parameters
193
+ are passed to callback.
194
+
195
+ Returns a handle that may be passed to remove_watch_file()
196
+
197
+ fd -- file descriptor to watch for input
198
+ callback -- function to call when input is available
199
+ """
200
+
201
+ @self.handle_exit
202
+ def io_callback(source, cb_condition) -> Literal[True]:
203
+ callback()
204
+ self._enable_glib_idle()
205
+ return True
206
+
207
+ self._watch_files[fd] = GLib.io_add_watch(fd, GLib.IO_IN, io_callback)
208
+ return fd
209
+
210
+ def remove_watch_file(self, handle: int) -> bool:
211
+ """
212
+ Remove an input file.
213
+
214
+ Returns True if the input file exists, False otherwise
215
+ """
216
+ if handle in self._watch_files:
217
+ GLib.source_remove(self._watch_files[handle])
218
+ del self._watch_files[handle]
219
+ return True
220
+ return False
221
+
222
+ def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
223
+ """
224
+ Add a callback for entering idle.
225
+
226
+ Returns a handle that may be passed to remove_enter_idle()
227
+ """
228
+ self._idle_handle += 1
229
+ self._idle_callbacks[self._idle_handle] = callback
230
+ return self._idle_handle
231
+
232
+ def _enable_glib_idle(self) -> None:
233
+ if self._glib_idle_enabled:
234
+ return
235
+ GLib.idle_add(self._glib_idle_callback)
236
+ self._glib_idle_enabled = True
237
+
238
+ def _glib_idle_callback(self):
239
+ for callback in self._idle_callbacks.values():
240
+ callback()
241
+ self._glib_idle_enabled = False
242
+ return False # ask glib not to call again (or we would be called
243
+
244
+ def remove_enter_idle(self, handle) -> bool:
245
+ """
246
+ Remove an idle callback.
247
+
248
+ Returns True if the handle was removed.
249
+ """
250
+ try:
251
+ del self._idle_callbacks[handle]
252
+ except KeyError:
253
+ return False
254
+ return True
255
+
256
+ def run(self) -> None:
257
+ """
258
+ Start the event loop. Exit the loop when any callback raises
259
+ an exception. If ExitMainLoop is raised, exit cleanly.
260
+ """
261
+ try:
262
+ self._loop.run()
263
+ finally:
264
+ if self._loop.is_running():
265
+ self._loop.quit()
266
+ if self._exc:
267
+ # An exception caused us to exit, raise it now
268
+ exc = self._exc
269
+ self._exc = None
270
+ raise exc.with_traceback(exc.__traceback__)
271
+
272
+ def handle_exit(self, f: Callable[_Spec, _T]) -> Callable[_Spec, _T | Literal[False]]:
273
+ """
274
+ Decorator that cleanly exits the :class:`GLibEventLoop` if
275
+ :exc:`ExitMainLoop` is thrown inside of the wrapped function. Store the
276
+ exception info if some other exception occurs, it will be reraised after
277
+ the loop quits.
278
+
279
+ *f* -- function to be wrapped
280
+ """
281
+
282
+ @functools.wraps(f)
283
+ def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T | Literal[False]:
284
+ try:
285
+ return f(*args, **kwargs)
286
+ except ExitMainLoop:
287
+ self._loop.quit()
288
+ except BaseException as exc:
289
+ self._exc = exc
290
+ if self._loop.is_running():
291
+ self._loop.quit()
292
+ return False
293
+
294
+ return wrapper