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.
- urwid/__init__.py +333 -0
- urwid/canvas.py +1413 -0
- urwid/command_map.py +137 -0
- urwid/container.py +59 -0
- urwid/decoration.py +65 -0
- urwid/display/__init__.py +97 -0
- urwid/display/_posix_raw_display.py +413 -0
- urwid/display/_raw_display_base.py +914 -0
- urwid/display/_web.css +12 -0
- urwid/display/_web.js +462 -0
- urwid/display/_win32.py +171 -0
- urwid/display/_win32_raw_display.py +269 -0
- urwid/display/common.py +1219 -0
- urwid/display/curses.py +690 -0
- urwid/display/escape.py +624 -0
- urwid/display/html_fragment.py +251 -0
- urwid/display/lcd.py +518 -0
- urwid/display/raw.py +37 -0
- urwid/display/web.py +636 -0
- urwid/event_loop/__init__.py +55 -0
- urwid/event_loop/abstract_loop.py +175 -0
- urwid/event_loop/asyncio_loop.py +231 -0
- urwid/event_loop/glib_loop.py +294 -0
- urwid/event_loop/main_loop.py +721 -0
- urwid/event_loop/select_loop.py +230 -0
- urwid/event_loop/tornado_loop.py +206 -0
- urwid/event_loop/trio_loop.py +302 -0
- urwid/event_loop/twisted_loop.py +269 -0
- urwid/event_loop/zmq_loop.py +275 -0
- urwid/font.py +695 -0
- urwid/graphics.py +96 -0
- urwid/highlight.css +19 -0
- urwid/listbox.py +1899 -0
- urwid/monitored_list.py +522 -0
- urwid/numedit.py +376 -0
- urwid/signals.py +330 -0
- urwid/split_repr.py +130 -0
- urwid/str_util.py +358 -0
- urwid/text_layout.py +632 -0
- urwid/treetools.py +515 -0
- urwid/util.py +557 -0
- urwid/version.py +16 -0
- urwid/vterm.py +1806 -0
- urwid/widget/__init__.py +181 -0
- urwid/widget/attr_map.py +161 -0
- urwid/widget/attr_wrap.py +140 -0
- urwid/widget/bar_graph.py +649 -0
- urwid/widget/big_text.py +77 -0
- urwid/widget/box_adapter.py +126 -0
- urwid/widget/columns.py +1145 -0
- urwid/widget/constants.py +574 -0
- urwid/widget/container.py +227 -0
- urwid/widget/divider.py +110 -0
- urwid/widget/edit.py +718 -0
- urwid/widget/filler.py +403 -0
- urwid/widget/frame.py +539 -0
- urwid/widget/grid_flow.py +539 -0
- urwid/widget/line_box.py +194 -0
- urwid/widget/overlay.py +829 -0
- urwid/widget/padding.py +597 -0
- urwid/widget/pile.py +971 -0
- urwid/widget/popup.py +170 -0
- urwid/widget/progress_bar.py +141 -0
- urwid/widget/scrollable.py +597 -0
- urwid/widget/solid_fill.py +44 -0
- urwid/widget/text.py +354 -0
- urwid/widget/widget.py +852 -0
- urwid/widget/widget_decoration.py +166 -0
- urwid/widget/wimp.py +792 -0
- urwid/wimp.py +23 -0
- urwid-2.6.0.post0.dist-info/COPYING +504 -0
- urwid-2.6.0.post0.dist-info/METADATA +332 -0
- urwid-2.6.0.post0.dist-info/RECORD +75 -0
- urwid-2.6.0.post0.dist-info/WHEEL +5 -0
- 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
|