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,275 @@
|
|
|
1
|
+
# Urwid main loop code using ZeroMQ queues
|
|
2
|
+
# Copyright (C) 2019 Dave Jones
|
|
3
|
+
#
|
|
4
|
+
# This library is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
6
|
+
# License as published by the Free Software Foundation; either
|
|
7
|
+
# version 2.1 of the License, or (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12
|
+
# Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
|
15
|
+
# License along with this library; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
17
|
+
#
|
|
18
|
+
# Urwid web site: https://urwid.org/
|
|
19
|
+
|
|
20
|
+
"""ZeroMQ based urwid EventLoop implementation.
|
|
21
|
+
|
|
22
|
+
`ZeroMQ <https://zeromq.org>`_ library is required.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import contextlib
|
|
28
|
+
import errno
|
|
29
|
+
import heapq
|
|
30
|
+
import logging
|
|
31
|
+
import os
|
|
32
|
+
import time
|
|
33
|
+
import typing
|
|
34
|
+
from itertools import count
|
|
35
|
+
|
|
36
|
+
import zmq
|
|
37
|
+
|
|
38
|
+
from .abstract_loop import EventLoop, ExitMainLoop
|
|
39
|
+
|
|
40
|
+
if typing.TYPE_CHECKING:
|
|
41
|
+
from collections.abc import Callable
|
|
42
|
+
from concurrent.futures import Executor, Future
|
|
43
|
+
|
|
44
|
+
from typing_extensions import ParamSpec
|
|
45
|
+
|
|
46
|
+
ZMQAlarmHandle = typing.TypeVar("ZMQAlarmHandle")
|
|
47
|
+
_T = typing.TypeVar("_T")
|
|
48
|
+
_Spec = ParamSpec("_Spec")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ZMQEventLoop(EventLoop):
|
|
52
|
+
"""
|
|
53
|
+
This class is an urwid event loop for `ZeroMQ`_ applications. It is very
|
|
54
|
+
similar to :class:`SelectEventLoop`, supporting the usual :meth:`alarm`
|
|
55
|
+
events and file watching (:meth:`watch_file`) capabilities, but also
|
|
56
|
+
incorporates the ability to watch zmq queues for events
|
|
57
|
+
(:meth:`watch_queue`).
|
|
58
|
+
|
|
59
|
+
.. _ZeroMQ: https://zeromq.org/
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
_alarm_break = count()
|
|
63
|
+
|
|
64
|
+
def __init__(self):
|
|
65
|
+
super().__init__()
|
|
66
|
+
self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
|
|
67
|
+
self._did_something = True
|
|
68
|
+
self._alarms = []
|
|
69
|
+
self._poller = zmq.Poller()
|
|
70
|
+
self._queue_callbacks = {}
|
|
71
|
+
self._idle_handle = 0
|
|
72
|
+
self._idle_callbacks = {}
|
|
73
|
+
|
|
74
|
+
def run_in_executor(
|
|
75
|
+
self,
|
|
76
|
+
executor: Executor,
|
|
77
|
+
func: Callable[_Spec, _T],
|
|
78
|
+
*args: _Spec.args,
|
|
79
|
+
**kwargs: _Spec.kwargs,
|
|
80
|
+
) -> Future[_T]:
|
|
81
|
+
"""Run callable in executor.
|
|
82
|
+
|
|
83
|
+
:param executor: Executor to use for running the function
|
|
84
|
+
:type executor: concurrent.futures.Executor
|
|
85
|
+
:param func: function to call
|
|
86
|
+
:type func: Callable
|
|
87
|
+
:param args: positional arguments to function
|
|
88
|
+
:type args: object
|
|
89
|
+
:param kwargs: keyword arguments to function
|
|
90
|
+
:type kwargs: object
|
|
91
|
+
:return: future object for the function call outcome.
|
|
92
|
+
:rtype: concurrent.futures.Future
|
|
93
|
+
"""
|
|
94
|
+
return executor.submit(func, *args, **kwargs)
|
|
95
|
+
|
|
96
|
+
def alarm(self, seconds: float, callback: Callable[[], typing.Any]) -> ZMQAlarmHandle:
|
|
97
|
+
"""
|
|
98
|
+
Call *callback* a given time from now. No parameters are passed to
|
|
99
|
+
callback. Returns a handle that may be passed to :meth:`remove_alarm`.
|
|
100
|
+
|
|
101
|
+
:param float seconds:
|
|
102
|
+
floating point time to wait before calling callback.
|
|
103
|
+
|
|
104
|
+
:param callback:
|
|
105
|
+
function to call from event loop.
|
|
106
|
+
"""
|
|
107
|
+
handle = (time.time() + seconds, next(self._alarm_break), callback)
|
|
108
|
+
heapq.heappush(self._alarms, handle)
|
|
109
|
+
return handle
|
|
110
|
+
|
|
111
|
+
def remove_alarm(self, handle: ZMQAlarmHandle) -> bool:
|
|
112
|
+
"""
|
|
113
|
+
Remove an alarm. Returns ``True`` if the alarm exists, ``False``
|
|
114
|
+
otherwise.
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
self._alarms.remove(handle)
|
|
118
|
+
heapq.heapify(self._alarms)
|
|
119
|
+
|
|
120
|
+
except ValueError:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
def watch_queue(
|
|
126
|
+
self,
|
|
127
|
+
queue: zmq.Socket,
|
|
128
|
+
callback: Callable[[], typing.Any],
|
|
129
|
+
flags: int = zmq.POLLIN,
|
|
130
|
+
) -> zmq.Socket:
|
|
131
|
+
"""
|
|
132
|
+
Call *callback* when zmq *queue* has something to read (when *flags* is
|
|
133
|
+
set to ``POLLIN``, the default) or is available to write (when *flags*
|
|
134
|
+
is set to ``POLLOUT``). No parameters are passed to the callback.
|
|
135
|
+
Returns a handle that may be passed to :meth:`remove_watch_queue`.
|
|
136
|
+
|
|
137
|
+
:param queue:
|
|
138
|
+
The zmq queue to poll.
|
|
139
|
+
|
|
140
|
+
:param callback:
|
|
141
|
+
The function to call when the poll is successful.
|
|
142
|
+
|
|
143
|
+
:param int flags:
|
|
144
|
+
The condition to monitor on the queue (defaults to ``POLLIN``).
|
|
145
|
+
"""
|
|
146
|
+
if queue in self._queue_callbacks:
|
|
147
|
+
raise ValueError(f"already watching {queue!r}")
|
|
148
|
+
self._poller.register(queue, flags)
|
|
149
|
+
self._queue_callbacks[queue] = callback
|
|
150
|
+
return queue
|
|
151
|
+
|
|
152
|
+
def watch_file(
|
|
153
|
+
self,
|
|
154
|
+
fd: int,
|
|
155
|
+
callback: Callable[[], typing.Any],
|
|
156
|
+
flags: int = zmq.POLLIN,
|
|
157
|
+
) -> int:
|
|
158
|
+
"""
|
|
159
|
+
Call *callback* when *fd* has some data to read. No parameters are
|
|
160
|
+
passed to the callback. The *flags* are as for :meth:`watch_queue`.
|
|
161
|
+
Returns a handle that may be passed to :meth:`remove_watch_file`.
|
|
162
|
+
|
|
163
|
+
:param fd:
|
|
164
|
+
The file-like object, or fileno to monitor.
|
|
165
|
+
|
|
166
|
+
:param callback:
|
|
167
|
+
The function to call when the file has data available.
|
|
168
|
+
|
|
169
|
+
:param int flags:
|
|
170
|
+
The condition to monitor on the file (defaults to ``POLLIN``).
|
|
171
|
+
"""
|
|
172
|
+
if isinstance(fd, int):
|
|
173
|
+
fd = os.fdopen(fd)
|
|
174
|
+
self._poller.register(fd, flags)
|
|
175
|
+
self._queue_callbacks[fd.fileno()] = callback
|
|
176
|
+
return fd
|
|
177
|
+
|
|
178
|
+
def remove_watch_queue(self, handle: zmq.Socket) -> bool:
|
|
179
|
+
"""
|
|
180
|
+
Remove a queue from background polling. Returns ``True`` if the queue
|
|
181
|
+
was being monitored, ``False`` otherwise.
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
try:
|
|
185
|
+
self._poller.unregister(handle)
|
|
186
|
+
finally:
|
|
187
|
+
self._queue_callbacks.pop(handle, None)
|
|
188
|
+
|
|
189
|
+
except KeyError:
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
def remove_watch_file(self, handle: int) -> bool:
|
|
195
|
+
"""
|
|
196
|
+
Remove a file from background polling. Returns ``True`` if the file was
|
|
197
|
+
being monitored, ``False`` otherwise.
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
try:
|
|
201
|
+
self._poller.unregister(handle)
|
|
202
|
+
finally:
|
|
203
|
+
self._queue_callbacks.pop(handle.fileno(), None)
|
|
204
|
+
|
|
205
|
+
except KeyError:
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
|
|
211
|
+
"""
|
|
212
|
+
Add a *callback* to be executed when the event loop detects it is idle.
|
|
213
|
+
Returns a handle that may be passed to :meth:`remove_enter_idle`.
|
|
214
|
+
"""
|
|
215
|
+
self._idle_handle += 1
|
|
216
|
+
self._idle_callbacks[self._idle_handle] = callback
|
|
217
|
+
return self._idle_handle
|
|
218
|
+
|
|
219
|
+
def remove_enter_idle(self, handle: int) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Remove an idle callback. Returns ``True`` if *handle* was removed,
|
|
222
|
+
``False`` otherwise.
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
del self._idle_callbacks[handle]
|
|
226
|
+
except KeyError:
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def _entering_idle(self) -> None:
|
|
232
|
+
for callback in list(self._idle_callbacks.values()):
|
|
233
|
+
callback()
|
|
234
|
+
|
|
235
|
+
def run(self) -> None:
|
|
236
|
+
"""
|
|
237
|
+
Start the event loop. Exit the loop when any callback raises an
|
|
238
|
+
exception. If :exc:`ExitMainLoop` is raised, exit cleanly.
|
|
239
|
+
"""
|
|
240
|
+
with contextlib.suppress(ExitMainLoop):
|
|
241
|
+
while True:
|
|
242
|
+
try:
|
|
243
|
+
self._loop()
|
|
244
|
+
except zmq.error.ZMQError as exc: # noqa: PERF203
|
|
245
|
+
if exc.errno != errno.EINTR:
|
|
246
|
+
raise
|
|
247
|
+
|
|
248
|
+
def _loop(self) -> None:
|
|
249
|
+
"""
|
|
250
|
+
A single iteration of the event loop.
|
|
251
|
+
"""
|
|
252
|
+
if self._alarms or self._did_something:
|
|
253
|
+
if self._alarms:
|
|
254
|
+
state = "alarm"
|
|
255
|
+
timeout = max(0, self._alarms[0][0] - time.time())
|
|
256
|
+
if self._did_something and (not self._alarms or (self._alarms and timeout > 0)):
|
|
257
|
+
state = "idle"
|
|
258
|
+
timeout = 0
|
|
259
|
+
ready = dict(self._poller.poll(timeout * 1000))
|
|
260
|
+
else:
|
|
261
|
+
state = "wait"
|
|
262
|
+
ready = dict(self._poller.poll())
|
|
263
|
+
|
|
264
|
+
if not ready:
|
|
265
|
+
if state == "idle":
|
|
266
|
+
self._entering_idle()
|
|
267
|
+
self._did_something = False
|
|
268
|
+
elif state == "alarm":
|
|
269
|
+
_due, _tie_break, callback = heapq.heappop(self._alarms)
|
|
270
|
+
callback()
|
|
271
|
+
self._did_something = True
|
|
272
|
+
|
|
273
|
+
for queue in ready:
|
|
274
|
+
self._queue_callbacks[queue]()
|
|
275
|
+
self._did_something = True
|