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,413 @@
1
+ # Urwid raw display module
2
+ # Copyright (C) 2004-2009 Ian Ward
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
+
21
+ """
22
+ Direct terminal UI implementation
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import contextlib
28
+ import fcntl
29
+ import functools
30
+ import os
31
+ import selectors
32
+ import signal
33
+ import struct
34
+ import sys
35
+ import termios
36
+ import tty
37
+ import typing
38
+ from subprocess import PIPE, Popen
39
+
40
+ from urwid import signals
41
+
42
+ from . import _raw_display_base, escape
43
+ from .common import INPUT_DESCRIPTORS_CHANGED
44
+
45
+ if typing.TYPE_CHECKING:
46
+ import io
47
+ import socket
48
+ from collections.abc import Callable
49
+ from types import FrameType
50
+
51
+ from urwid.event_loop import EventLoop
52
+
53
+
54
+ class Screen(_raw_display_base.Screen):
55
+ def __init__(
56
+ self,
57
+ input: io.TextIOBase = sys.stdin, # noqa: A002 # pylint: disable=redefined-builtin
58
+ output: io.TextIOBase = sys.stdout,
59
+ bracketed_paste_mode=False,
60
+ ) -> None:
61
+ """Initialize a screen that directly prints escape codes to an output
62
+ terminal.
63
+
64
+ bracketed_paste_mode -- enable bracketed paste mode in the host terminal.
65
+ If the host terminal supports it, the application will receive `begin paste`
66
+ and `end paste` keystrokes when the user pastes text.
67
+ """
68
+ super().__init__(input, output)
69
+ self.gpm_mev: Popen | None = None
70
+ self.gpm_event_pending: bool = False
71
+ self.bracketed_paste_mode = bracketed_paste_mode
72
+
73
+ # These store the previous signal handlers after setting ours
74
+ self._prev_sigcont_handler = None
75
+ self._prev_sigtstp_handler = None
76
+ self._prev_sigwinch_handler = None
77
+
78
+ def __repr__(self) -> str:
79
+ return (
80
+ f"<{self.__class__.__name__}("
81
+ f"input={self._term_input_file}, "
82
+ f"output={self._term_output_file}, "
83
+ f"bracketed_paste_mode={self.bracketed_paste_mode})>"
84
+ )
85
+
86
+ def _sigwinch_handler(self, signum: int = 28, frame: FrameType | None = None) -> None:
87
+ """
88
+ frame -- will always be None when the GLib event loop is being used.
89
+ """
90
+ super()._sigwinch_handler(signum, frame)
91
+
92
+ if callable(self._prev_sigwinch_handler):
93
+ self._prev_sigwinch_handler(signum, frame)
94
+
95
+ def _sigtstp_handler(self, signum: int, frame: FrameType | None = None) -> None:
96
+ self.stop() # Restores the previous signal handlers
97
+ self._prev_sigcont_handler = self.signal_handler_setter(signal.SIGCONT, self._sigcont_handler)
98
+ # Handled by the previous handler.
99
+ # If non-default, it may set its own SIGCONT handler which should hopefully call our own.
100
+ os.kill(os.getpid(), signal.SIGTSTP)
101
+
102
+ def _sigcont_handler(self, signum: int, frame: FrameType | None = None) -> None:
103
+ """
104
+ frame -- will always be None when the GLib event loop is being used.
105
+ """
106
+ self.signal_restore()
107
+
108
+ if callable(self._prev_sigcont_handler):
109
+ # May set its own SIGTSTP handler which would be stored and replaced in
110
+ # `signal_init()` (via `start()`).
111
+ self._prev_sigcont_handler(signum, frame)
112
+
113
+ self.start()
114
+ self._sigwinch_handler(28, None)
115
+
116
+ def signal_init(self) -> None:
117
+ """
118
+ Called in the startup of run wrapper to set the SIGWINCH
119
+ and SIGTSTP signal handlers.
120
+
121
+ Override this function to call from main thread in threaded
122
+ applications.
123
+ """
124
+ self._prev_sigwinch_handler = self.signal_handler_setter(signal.SIGWINCH, self._sigwinch_handler)
125
+ self._prev_sigtstp_handler = self.signal_handler_setter(signal.SIGTSTP, self._sigtstp_handler)
126
+
127
+ def signal_restore(self) -> None:
128
+ """
129
+ Called in the finally block of run wrapper to restore the
130
+ SIGTSTP, SIGCONT and SIGWINCH signal handlers.
131
+
132
+ Override this function to call from main thread in threaded
133
+ applications.
134
+ """
135
+ self.signal_handler_setter(signal.SIGTSTP, self._prev_sigtstp_handler or signal.SIG_DFL)
136
+ self.signal_handler_setter(signal.SIGCONT, self._prev_sigcont_handler or signal.SIG_DFL)
137
+ self.signal_handler_setter(signal.SIGWINCH, self._prev_sigwinch_handler or signal.SIG_DFL)
138
+
139
+ def _mouse_tracking(self, enable: bool) -> None:
140
+ super()._mouse_tracking(enable)
141
+ if enable:
142
+ self._start_gpm_tracking()
143
+ else:
144
+ self._stop_gpm_tracking()
145
+
146
+ def _start_gpm_tracking(self) -> None:
147
+ if not os.path.isfile("/usr/bin/mev"):
148
+ return
149
+ if not os.environ.get("TERM", "").lower().startswith("linux"):
150
+ return
151
+
152
+ m = Popen( # pylint: disable=consider-using-with
153
+ ["/usr/bin/mev", "-e", "158"], # noqa: S603
154
+ stdin=PIPE,
155
+ stdout=PIPE,
156
+ close_fds=True,
157
+ encoding="ascii",
158
+ )
159
+ fcntl.fcntl(m.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
160
+ self.gpm_mev = m
161
+
162
+ def _stop_gpm_tracking(self) -> None:
163
+ if not self.gpm_mev:
164
+ return
165
+ os.kill(self.gpm_mev.pid, signal.SIGINT)
166
+ os.waitpid(self.gpm_mev.pid, 0)
167
+ self.gpm_mev = None
168
+
169
+ def _start(self, alternate_buffer: bool = True) -> None:
170
+ """
171
+ Initialize the screen and input mode.
172
+
173
+ alternate_buffer -- use alternate screen buffer
174
+ """
175
+ if alternate_buffer:
176
+ self.write(escape.SWITCH_TO_ALTERNATE_BUFFER)
177
+ self._rows_used = None
178
+ else:
179
+ self._rows_used = 0
180
+
181
+ if self.bracketed_paste_mode:
182
+ self.write(escape.ENABLE_BRACKETED_PASTE_MODE)
183
+
184
+ fd = self._input_fileno()
185
+ if fd is not None and os.isatty(fd):
186
+ self._old_termios_settings = termios.tcgetattr(fd)
187
+ tty.setcbreak(fd)
188
+
189
+ self.signal_init()
190
+ self._alternate_buffer = alternate_buffer
191
+ self._next_timeout = self.max_wait
192
+
193
+ if not self._signal_keys_set:
194
+ self._old_signal_keys = self.tty_signal_keys(fileno=fd)
195
+
196
+ signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED)
197
+ # restore mouse tracking to previous state
198
+ self._mouse_tracking(self._mouse_tracking_enabled)
199
+
200
+ return super()._start()
201
+
202
+ def _stop(self) -> None:
203
+ """
204
+ Restore the screen.
205
+ """
206
+ self.clear()
207
+
208
+ if self.bracketed_paste_mode:
209
+ self.write(escape.DISABLE_BRACKETED_PASTE_MODE)
210
+
211
+ signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED)
212
+
213
+ self.signal_restore()
214
+
215
+ fd = self._input_fileno()
216
+ if fd is not None and os.isatty(fd):
217
+ termios.tcsetattr(fd, termios.TCSADRAIN, self._old_termios_settings)
218
+
219
+ self._stop_mouse_restore_buffer()
220
+
221
+ if self._old_signal_keys:
222
+ self.tty_signal_keys(*self._old_signal_keys, fd)
223
+
224
+ super()._stop()
225
+
226
+ def get_input_descriptors(self) -> list[socket.socket | io.IOBase | typing.IO | int]:
227
+ """
228
+ Return a list of integer file descriptors that should be
229
+ polled in external event loops to check for user input.
230
+
231
+ Use this method if you are implementing your own event loop.
232
+
233
+ This method is only called by `hook_event_loop`, so if you override
234
+ that, you can safely ignore this.
235
+ """
236
+ if not self._started:
237
+ return []
238
+
239
+ fd_list = super().get_input_descriptors()
240
+ if self.gpm_mev is not None and self.gpm_mev.stdout is not None:
241
+ fd_list.append(self.gpm_mev.stdout)
242
+ return fd_list
243
+
244
+ def unhook_event_loop(self, event_loop: EventLoop) -> None:
245
+ """
246
+ Remove any hooks added by hook_event_loop.
247
+ """
248
+ for handle in self._current_event_loop_handles:
249
+ event_loop.remove_watch_file(handle)
250
+
251
+ if self._input_timeout:
252
+ event_loop.remove_alarm(self._input_timeout)
253
+ self._input_timeout = None
254
+
255
+ def hook_event_loop(
256
+ self,
257
+ event_loop: EventLoop,
258
+ callback: Callable[[list[str], list[int]], typing.Any],
259
+ ) -> None:
260
+ """
261
+ Register the given callback with the event loop, to be called with new
262
+ input whenever it's available. The callback should be passed a list of
263
+ processed keys and a list of unprocessed keycodes.
264
+
265
+ Subclasses may wish to use parse_input to wrap the callback.
266
+ """
267
+ if hasattr(self, "get_input_nonblocking"):
268
+ wrapper = self._make_legacy_input_wrapper(event_loop, callback)
269
+ else:
270
+
271
+ @functools.wraps(callback)
272
+ def wrapper() -> tuple[list[str], typing.Any] | None:
273
+ self.logger.debug('Calling callback for "watch file"')
274
+ return self.parse_input(event_loop, callback, self.get_available_raw_input())
275
+
276
+ fds = self.get_input_descriptors()
277
+ handles = [event_loop.watch_file(fd if isinstance(fd, int) else fd.fileno(), wrapper) for fd in fds]
278
+ self._current_event_loop_handles = handles
279
+
280
+ def _get_input_codes(self) -> list[int]:
281
+ return super()._get_input_codes() + self._get_gpm_codes()
282
+
283
+ def _get_gpm_codes(self) -> list[int]:
284
+ codes = []
285
+ try:
286
+ while self.gpm_mev is not None and self.gpm_event_pending:
287
+ codes.extend(self._encode_gpm_event())
288
+ except OSError as e:
289
+ if e.args[0] != 11:
290
+ raise
291
+ return codes
292
+
293
+ def _read_raw_input(self, timeout: int) -> bytearray:
294
+ ready = self._wait_for_input_ready(timeout)
295
+ if self.gpm_mev is not None and self.gpm_mev.stdout.fileno() in ready:
296
+ self.gpm_event_pending = True
297
+ fd = self._input_fileno()
298
+ chars = bytearray()
299
+
300
+ if fd is None or fd not in ready:
301
+ return chars
302
+
303
+ with selectors.DefaultSelector() as selector:
304
+ selector.register(fd, selectors.EVENT_READ)
305
+ input_ready = selector.select(0)
306
+ while input_ready:
307
+ chars.extend(os.read(fd, 1024))
308
+ input_ready = selector.select(0)
309
+
310
+ return chars
311
+
312
+ def _encode_gpm_event(self) -> list[int]:
313
+ self.gpm_event_pending = False
314
+ s = self.gpm_mev.stdout.readline()
315
+ result = s.split(", ")
316
+ if len(result) != 6:
317
+ # unexpected output, stop tracking
318
+ self._stop_gpm_tracking()
319
+ signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED)
320
+ return []
321
+ ev, x, y, _ign, b, m = s.split(",")
322
+ ev = int(ev.split("x")[-1], 16)
323
+ x = int(x.split(" ")[-1])
324
+ y = int(y.lstrip().split(" ")[0])
325
+ b = int(b.split(" ")[-1])
326
+ m = int(m.split("x")[-1].rstrip(), 16)
327
+
328
+ # convert to xterm-like escape sequence
329
+
330
+ last_state = next_state = self.last_bstate
331
+ result = []
332
+
333
+ mod = 0
334
+ if m & 1:
335
+ mod |= 4 # shift
336
+ if m & 10:
337
+ mod |= 8 # alt
338
+ if m & 4:
339
+ mod |= 16 # ctrl
340
+
341
+ def append_button(b: int) -> None:
342
+ b |= mod
343
+ result.extend([27, ord("["), ord("M"), b + 32, x + 32, y + 32])
344
+
345
+ if ev in {20, 36, 52}: # press
346
+ if b & 4 and last_state & 1 == 0:
347
+ append_button(0)
348
+ next_state |= 1
349
+ if b & 2 and last_state & 2 == 0:
350
+ append_button(1)
351
+ next_state |= 2
352
+ if b & 1 and last_state & 4 == 0:
353
+ append_button(2)
354
+ next_state |= 4
355
+ elif ev == 146: # drag
356
+ if b & 4:
357
+ append_button(0 + escape.MOUSE_DRAG_FLAG)
358
+ elif b & 2:
359
+ append_button(1 + escape.MOUSE_DRAG_FLAG)
360
+ elif b & 1:
361
+ append_button(2 + escape.MOUSE_DRAG_FLAG)
362
+ else: # release
363
+ if b & 4 and last_state & 1:
364
+ append_button(0 + escape.MOUSE_RELEASE_FLAG)
365
+ next_state &= ~1
366
+ if b & 2 and last_state & 2:
367
+ append_button(1 + escape.MOUSE_RELEASE_FLAG)
368
+ next_state &= ~2
369
+ if b & 1 and last_state & 4:
370
+ append_button(2 + escape.MOUSE_RELEASE_FLAG)
371
+ next_state &= ~4
372
+ if ev == 40: # double click (release)
373
+ if b & 4 and last_state & 1:
374
+ append_button(0 + escape.MOUSE_MULTIPLE_CLICK_FLAG)
375
+ if b & 2 and last_state & 2:
376
+ append_button(1 + escape.MOUSE_MULTIPLE_CLICK_FLAG)
377
+ if b & 1 and last_state & 4:
378
+ append_button(2 + escape.MOUSE_MULTIPLE_CLICK_FLAG)
379
+ elif ev == 52:
380
+ if b & 4 and last_state & 1:
381
+ append_button(0 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2)
382
+ if b & 2 and last_state & 2:
383
+ append_button(1 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2)
384
+ if b & 1 and last_state & 4:
385
+ append_button(2 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2)
386
+
387
+ self.last_bstate = next_state
388
+ return result
389
+
390
+ def get_cols_rows(self) -> tuple[int, int]:
391
+ """Return the terminal dimensions (num columns, num rows)."""
392
+ y, x = super().get_cols_rows()
393
+ with contextlib.suppress(OSError): # Term size could not be determined
394
+ if hasattr(self._term_output_file, "fileno"):
395
+ buf = fcntl.ioctl(self._term_output_file.fileno(), termios.TIOCGWINSZ, b" " * 4)
396
+ y, x = struct.unpack("hh", buf)
397
+
398
+ # Provide some lightweight fallbacks in case the TIOCWINSZ doesn't
399
+ # give sane answers
400
+ if (x <= 0 or y <= 0) and self.term in {"ansi", "vt100"}:
401
+ y, x = 24, 80
402
+ self.maxrow = y
403
+ return x, y
404
+
405
+
406
+ def _test():
407
+ import doctest
408
+
409
+ doctest.testmod()
410
+
411
+
412
+ if __name__ == "__main__":
413
+ _test()