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,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()
|