Started paring down urwid.
This commit is contained in:
parent
d8f1f211d1
commit
23a0fe2ab4
5 changed files with 13 additions and 1755 deletions
2
fill3.py
2
fill3.py
|
|
@ -457,6 +457,6 @@ def main(loop, appearance_changed_event, screen_widget, exit_loop=None):
|
||||||
loop.add_signal_handler(signal.SIGTERM, exit_loop)
|
loop.add_signal_handler(signal.SIGTERM, exit_loop)
|
||||||
asyncio.ensure_future(_update_screen(screen_widget,
|
asyncio.ensure_future(_update_screen(screen_widget,
|
||||||
appearance_changed_event))
|
appearance_changed_event))
|
||||||
with terminal.hidden_cursor(), _urwid_screen() as urwid_screen:
|
with terminal.hidden_cursor(), terminal.fullscreen(), _urwid_screen() as urwid_screen:
|
||||||
loop.add_reader(sys.stdin, on_input, urwid_screen, screen_widget)
|
loop.add_reader(sys.stdin, on_input, urwid_screen, screen_widget)
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,6 @@ def move(x, y): # cup
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def fullscreen():
|
def fullscreen():
|
||||||
if enter_fullscreen is None:
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
sys.stdout.write(clear)
|
|
||||||
else:
|
|
||||||
sys.stdout.write(enter_fullscreen)
|
sys.stdout.write(enter_fullscreen)
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
|
|
|
||||||
|
|
@ -20,639 +20,9 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import termios
|
||||||
|
|
||||||
try:
|
from urwid.util import StoppingContext
|
||||||
import termios
|
|
||||||
except ImportError:
|
|
||||||
pass # windows
|
|
||||||
|
|
||||||
from urwid.util import StoppingContext, int_scale
|
|
||||||
from urwid import signals
|
|
||||||
from urwid.compat import B, bytes3
|
|
||||||
|
|
||||||
# for replacing unprintable bytes with '?'
|
|
||||||
UNPRINTABLE_TRANS_TABLE = B("?") * 32 + bytes3(list(range(32,256)))
|
|
||||||
|
|
||||||
|
|
||||||
# signals sent by BaseScreen
|
|
||||||
UPDATE_PALETTE_ENTRY = "update palette entry"
|
|
||||||
INPUT_DESCRIPTORS_CHANGED = "input descriptors changed"
|
|
||||||
|
|
||||||
|
|
||||||
# AttrSpec internal values
|
|
||||||
_BASIC_START = 0 # first index of basic color aliases
|
|
||||||
_CUBE_START = 16 # first index of color cube
|
|
||||||
_CUBE_SIZE_256 = 6 # one side of the color cube
|
|
||||||
_GRAY_SIZE_256 = 24
|
|
||||||
_GRAY_START_256 = _CUBE_SIZE_256 ** 3 + _CUBE_START
|
|
||||||
_CUBE_WHITE_256 = _GRAY_START_256 -1
|
|
||||||
_CUBE_SIZE_88 = 4
|
|
||||||
_GRAY_SIZE_88 = 8
|
|
||||||
_GRAY_START_88 = _CUBE_SIZE_88 ** 3 + _CUBE_START
|
|
||||||
_CUBE_WHITE_88 = _GRAY_START_88 -1
|
|
||||||
_CUBE_BLACK = _CUBE_START
|
|
||||||
|
|
||||||
# values copied from xterm 256colres.h:
|
|
||||||
_CUBE_STEPS_256 = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
|
|
||||||
_GRAY_STEPS_256 = [0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62,
|
|
||||||
0x6c, 0x76, 0x80, 0x84, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0,
|
|
||||||
0xda, 0xe4, 0xee]
|
|
||||||
# values copied from xterm 88colres.h:
|
|
||||||
_CUBE_STEPS_88 = [0x00, 0x8b, 0xcd, 0xff]
|
|
||||||
_GRAY_STEPS_88 = [0x2e, 0x5c, 0x73, 0x8b, 0xa2, 0xb9, 0xd0, 0xe7]
|
|
||||||
# values copied from X11/rgb.txt and XTerm-col.ad:
|
|
||||||
_BASIC_COLOR_VALUES = [(0,0,0), (205, 0, 0), (0, 205, 0), (205, 205, 0),
|
|
||||||
(0, 0, 238), (205, 0, 205), (0, 205, 205), (229, 229, 229),
|
|
||||||
(127, 127, 127), (255, 0, 0), (0, 255, 0), (255, 255, 0),
|
|
||||||
(0x5c, 0x5c, 0xff), (255, 0, 255), (0, 255, 255), (255, 255, 255)]
|
|
||||||
|
|
||||||
_COLOR_VALUES_256 = (_BASIC_COLOR_VALUES +
|
|
||||||
[(r, g, b) for r in _CUBE_STEPS_256 for g in _CUBE_STEPS_256
|
|
||||||
for b in _CUBE_STEPS_256] +
|
|
||||||
[(gr, gr, gr) for gr in _GRAY_STEPS_256])
|
|
||||||
_COLOR_VALUES_88 = (_BASIC_COLOR_VALUES +
|
|
||||||
[(r, g, b) for r in _CUBE_STEPS_88 for g in _CUBE_STEPS_88
|
|
||||||
for b in _CUBE_STEPS_88] +
|
|
||||||
[(gr, gr, gr) for gr in _GRAY_STEPS_88])
|
|
||||||
|
|
||||||
assert len(_COLOR_VALUES_256) == 256
|
|
||||||
assert len(_COLOR_VALUES_88) == 88
|
|
||||||
|
|
||||||
_FG_COLOR_MASK = 0x000000ff
|
|
||||||
_BG_COLOR_MASK = 0x0000ff00
|
|
||||||
_FG_BASIC_COLOR = 0x00010000
|
|
||||||
_FG_HIGH_COLOR = 0x00020000
|
|
||||||
_BG_BASIC_COLOR = 0x00040000
|
|
||||||
_BG_HIGH_COLOR = 0x00080000
|
|
||||||
_BG_SHIFT = 8
|
|
||||||
_HIGH_88_COLOR = 0x00100000
|
|
||||||
_STANDOUT = 0x02000000
|
|
||||||
_UNDERLINE = 0x04000000
|
|
||||||
_BOLD = 0x08000000
|
|
||||||
_BLINK = 0x10000000
|
|
||||||
_FG_MASK = (_FG_COLOR_MASK | _FG_BASIC_COLOR | _FG_HIGH_COLOR |
|
|
||||||
_STANDOUT | _UNDERLINE | _BLINK | _BOLD)
|
|
||||||
_BG_MASK = _BG_COLOR_MASK | _BG_BASIC_COLOR | _BG_HIGH_COLOR
|
|
||||||
|
|
||||||
DEFAULT = 'default'
|
|
||||||
BLACK = 'black'
|
|
||||||
DARK_RED = 'dark red'
|
|
||||||
DARK_GREEN = 'dark green'
|
|
||||||
BROWN = 'brown'
|
|
||||||
DARK_BLUE = 'dark blue'
|
|
||||||
DARK_MAGENTA = 'dark magenta'
|
|
||||||
DARK_CYAN = 'dark cyan'
|
|
||||||
LIGHT_GRAY = 'light gray'
|
|
||||||
DARK_GRAY = 'dark gray'
|
|
||||||
LIGHT_RED = 'light red'
|
|
||||||
LIGHT_GREEN = 'light green'
|
|
||||||
YELLOW = 'yellow'
|
|
||||||
LIGHT_BLUE = 'light blue'
|
|
||||||
LIGHT_MAGENTA = 'light magenta'
|
|
||||||
LIGHT_CYAN = 'light cyan'
|
|
||||||
WHITE = 'white'
|
|
||||||
|
|
||||||
_BASIC_COLORS = [
|
|
||||||
BLACK,
|
|
||||||
DARK_RED,
|
|
||||||
DARK_GREEN,
|
|
||||||
BROWN,
|
|
||||||
DARK_BLUE,
|
|
||||||
DARK_MAGENTA,
|
|
||||||
DARK_CYAN,
|
|
||||||
LIGHT_GRAY,
|
|
||||||
DARK_GRAY,
|
|
||||||
LIGHT_RED,
|
|
||||||
LIGHT_GREEN,
|
|
||||||
YELLOW,
|
|
||||||
LIGHT_BLUE,
|
|
||||||
LIGHT_MAGENTA,
|
|
||||||
LIGHT_CYAN,
|
|
||||||
WHITE,
|
|
||||||
]
|
|
||||||
|
|
||||||
_ATTRIBUTES = {
|
|
||||||
'bold': _BOLD,
|
|
||||||
'underline': _UNDERLINE,
|
|
||||||
'blink': _BLINK,
|
|
||||||
'standout': _STANDOUT,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _value_lookup_table(values, size):
|
|
||||||
"""
|
|
||||||
Generate a lookup table for finding the closest item in values.
|
|
||||||
Lookup returns (index into values)+1
|
|
||||||
|
|
||||||
values -- list of values in ascending order, all < size
|
|
||||||
size -- size of lookup table and maximum value
|
|
||||||
|
|
||||||
>>> _value_lookup_table([0, 7, 9], 10)
|
|
||||||
[0, 0, 0, 0, 1, 1, 1, 1, 2, 2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
middle_values = [0] + [(values[i] + values[i + 1] + 1) // 2
|
|
||||||
for i in range(len(values) - 1)] + [size]
|
|
||||||
lookup_table = []
|
|
||||||
for i in range(len(middle_values)-1):
|
|
||||||
count = middle_values[i + 1] - middle_values[i]
|
|
||||||
lookup_table.extend([i] * count)
|
|
||||||
return lookup_table
|
|
||||||
|
|
||||||
_CUBE_256_LOOKUP = _value_lookup_table(_CUBE_STEPS_256, 256)
|
|
||||||
_GRAY_256_LOOKUP = _value_lookup_table([0] + _GRAY_STEPS_256 + [0xff], 256)
|
|
||||||
_CUBE_88_LOOKUP = _value_lookup_table(_CUBE_STEPS_88, 256)
|
|
||||||
_GRAY_88_LOOKUP = _value_lookup_table([0] + _GRAY_STEPS_88 + [0xff], 256)
|
|
||||||
|
|
||||||
# convert steps to values that will be used by string versions of the colors
|
|
||||||
# 1 hex digit for rgb and 0..100 for grayscale
|
|
||||||
_CUBE_STEPS_256_16 = [int_scale(n, 0x100, 0x10) for n in _CUBE_STEPS_256]
|
|
||||||
_GRAY_STEPS_256_101 = [int_scale(n, 0x100, 101) for n in _GRAY_STEPS_256]
|
|
||||||
_CUBE_STEPS_88_16 = [int_scale(n, 0x100, 0x10) for n in _CUBE_STEPS_88]
|
|
||||||
_GRAY_STEPS_88_101 = [int_scale(n, 0x100, 101) for n in _GRAY_STEPS_88]
|
|
||||||
|
|
||||||
# create lookup tables for 1 hex digit rgb and 0..100 for grayscale values
|
|
||||||
_CUBE_256_LOOKUP_16 = [_CUBE_256_LOOKUP[int_scale(n, 16, 0x100)]
|
|
||||||
for n in range(16)]
|
|
||||||
_GRAY_256_LOOKUP_101 = [_GRAY_256_LOOKUP[int_scale(n, 101, 0x100)]
|
|
||||||
for n in range(101)]
|
|
||||||
_CUBE_88_LOOKUP_16 = [_CUBE_88_LOOKUP[int_scale(n, 16, 0x100)]
|
|
||||||
for n in range(16)]
|
|
||||||
_GRAY_88_LOOKUP_101 = [_GRAY_88_LOOKUP[int_scale(n, 101, 0x100)]
|
|
||||||
for n in range(101)]
|
|
||||||
|
|
||||||
|
|
||||||
# The functions _gray_num_256() and _gray_num_88() do not include the gray
|
|
||||||
# values from the color cube so that the gray steps are an even width.
|
|
||||||
# The color cube grays are available by using the rgb functions. Pure
|
|
||||||
# white and black are taken from the color cube, since the gray range does
|
|
||||||
# not include them, and the basic colors are more likely to have been
|
|
||||||
# customized by an end-user.
|
|
||||||
|
|
||||||
|
|
||||||
def _gray_num_256(gnum):
|
|
||||||
"""Return ths color number for gray number gnum.
|
|
||||||
|
|
||||||
Color cube black and white are returned for 0 and 25 respectively
|
|
||||||
since those values aren't included in the gray scale.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# grays start from index 1
|
|
||||||
gnum -= 1
|
|
||||||
|
|
||||||
if gnum < 0:
|
|
||||||
return _CUBE_BLACK
|
|
||||||
if gnum >= _GRAY_SIZE_256:
|
|
||||||
return _CUBE_WHITE_256
|
|
||||||
return _GRAY_START_256 + gnum
|
|
||||||
|
|
||||||
|
|
||||||
def _gray_num_88(gnum):
|
|
||||||
"""Return ths color number for gray number gnum.
|
|
||||||
|
|
||||||
Color cube black and white are returned for 0 and 9 respectively
|
|
||||||
since those values aren't included in the gray scale.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# gnums start from index 1
|
|
||||||
gnum -= 1
|
|
||||||
|
|
||||||
if gnum < 0:
|
|
||||||
return _CUBE_BLACK
|
|
||||||
if gnum >= _GRAY_SIZE_88:
|
|
||||||
return _CUBE_WHITE_88
|
|
||||||
return _GRAY_START_88 + gnum
|
|
||||||
|
|
||||||
|
|
||||||
def _color_desc_256(num):
|
|
||||||
"""
|
|
||||||
Return a string description of color number num.
|
|
||||||
0..15 -> 'h0'..'h15' basic colors (as high-colors)
|
|
||||||
16..231 -> '#000'..'#fff' color cube colors
|
|
||||||
232..255 -> 'g3'..'g93' grays
|
|
||||||
|
|
||||||
>>> _color_desc_256(15)
|
|
||||||
'h15'
|
|
||||||
>>> _color_desc_256(16)
|
|
||||||
'#000'
|
|
||||||
>>> _color_desc_256(17)
|
|
||||||
'#006'
|
|
||||||
>>> _color_desc_256(230)
|
|
||||||
'#ffd'
|
|
||||||
>>> _color_desc_256(233)
|
|
||||||
'g7'
|
|
||||||
>>> _color_desc_256(234)
|
|
||||||
'g11'
|
|
||||||
|
|
||||||
"""
|
|
||||||
assert num >= 0 and num < 256, num
|
|
||||||
if num < _CUBE_START:
|
|
||||||
return 'h%d' % num
|
|
||||||
if num < _GRAY_START_256:
|
|
||||||
num -= _CUBE_START
|
|
||||||
b, num = num % _CUBE_SIZE_256, num // _CUBE_SIZE_256
|
|
||||||
g, num = num % _CUBE_SIZE_256, num // _CUBE_SIZE_256
|
|
||||||
r = num % _CUBE_SIZE_256
|
|
||||||
return '#%x%x%x' % (_CUBE_STEPS_256_16[r], _CUBE_STEPS_256_16[g],
|
|
||||||
_CUBE_STEPS_256_16[b])
|
|
||||||
return 'g%d' % _GRAY_STEPS_256_101[num - _GRAY_START_256]
|
|
||||||
|
|
||||||
def _color_desc_88(num):
|
|
||||||
"""
|
|
||||||
Return a string description of color number num.
|
|
||||||
0..15 -> 'h0'..'h15' basic colors (as high-colors)
|
|
||||||
16..79 -> '#000'..'#fff' color cube colors
|
|
||||||
80..87 -> 'g18'..'g90' grays
|
|
||||||
|
|
||||||
>>> _color_desc_88(15)
|
|
||||||
'h15'
|
|
||||||
>>> _color_desc_88(16)
|
|
||||||
'#000'
|
|
||||||
>>> _color_desc_88(17)
|
|
||||||
'#008'
|
|
||||||
>>> _color_desc_88(78)
|
|
||||||
'#ffc'
|
|
||||||
>>> _color_desc_88(81)
|
|
||||||
'g36'
|
|
||||||
>>> _color_desc_88(82)
|
|
||||||
'g45'
|
|
||||||
|
|
||||||
"""
|
|
||||||
assert num > 0 and num < 88
|
|
||||||
if num < _CUBE_START:
|
|
||||||
return 'h%d' % num
|
|
||||||
if num < _GRAY_START_88:
|
|
||||||
num -= _CUBE_START
|
|
||||||
b, num = num % _CUBE_SIZE_88, num // _CUBE_SIZE_88
|
|
||||||
g, r= num % _CUBE_SIZE_88, num // _CUBE_SIZE_88
|
|
||||||
return '#%x%x%x' % (_CUBE_STEPS_88_16[r], _CUBE_STEPS_88_16[g],
|
|
||||||
_CUBE_STEPS_88_16[b])
|
|
||||||
return 'g%d' % _GRAY_STEPS_88_101[num - _GRAY_START_88]
|
|
||||||
|
|
||||||
def _parse_color_256(desc):
|
|
||||||
"""
|
|
||||||
Return a color number for the description desc.
|
|
||||||
'h0'..'h255' -> 0..255 actual color number
|
|
||||||
'#000'..'#fff' -> 16..231 color cube colors
|
|
||||||
'g0'..'g100' -> 16, 232..255, 231 grays and color cube black/white
|
|
||||||
'g#00'..'g#ff' -> 16, 232...255, 231 gray and color cube black/white
|
|
||||||
|
|
||||||
Returns None if desc is invalid.
|
|
||||||
|
|
||||||
>>> _parse_color_256('h142')
|
|
||||||
142
|
|
||||||
>>> _parse_color_256('#f00')
|
|
||||||
196
|
|
||||||
>>> _parse_color_256('g100')
|
|
||||||
231
|
|
||||||
>>> _parse_color_256('g#80')
|
|
||||||
244
|
|
||||||
"""
|
|
||||||
if len(desc) > 4:
|
|
||||||
# keep the length within reason before parsing
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
if desc.startswith('h'):
|
|
||||||
# high-color number
|
|
||||||
num = int(desc[1:], 10)
|
|
||||||
if num < 0 or num > 255:
|
|
||||||
return None
|
|
||||||
return num
|
|
||||||
|
|
||||||
if desc.startswith('#') and len(desc) == 4:
|
|
||||||
# color-cube coordinates
|
|
||||||
rgb = int(desc[1:], 16)
|
|
||||||
if rgb < 0:
|
|
||||||
return None
|
|
||||||
b, rgb = rgb % 16, rgb // 16
|
|
||||||
g, r = rgb % 16, rgb // 16
|
|
||||||
# find the closest rgb values
|
|
||||||
r = _CUBE_256_LOOKUP_16[r]
|
|
||||||
g = _CUBE_256_LOOKUP_16[g]
|
|
||||||
b = _CUBE_256_LOOKUP_16[b]
|
|
||||||
return _CUBE_START + (r * _CUBE_SIZE_256 + g) * _CUBE_SIZE_256 + b
|
|
||||||
|
|
||||||
# Only remaining possibility is gray value
|
|
||||||
if desc.startswith('g#'):
|
|
||||||
# hex value 00..ff
|
|
||||||
gray = int(desc[2:], 16)
|
|
||||||
if gray < 0 or gray > 255:
|
|
||||||
return None
|
|
||||||
gray = _GRAY_256_LOOKUP[gray]
|
|
||||||
elif desc.startswith('g'):
|
|
||||||
# decimal value 0..100
|
|
||||||
gray = int(desc[1:], 10)
|
|
||||||
if gray < 0 or gray > 100:
|
|
||||||
return None
|
|
||||||
gray = _GRAY_256_LOOKUP_101[gray]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
if gray == 0:
|
|
||||||
return _CUBE_BLACK
|
|
||||||
gray -= 1
|
|
||||||
if gray == _GRAY_SIZE_256:
|
|
||||||
return _CUBE_WHITE_256
|
|
||||||
return _GRAY_START_256 + gray
|
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _parse_color_88(desc):
|
|
||||||
"""
|
|
||||||
Return a color number for the description desc.
|
|
||||||
'h0'..'h87' -> 0..87 actual color number
|
|
||||||
'#000'..'#fff' -> 16..79 color cube colors
|
|
||||||
'g0'..'g100' -> 16, 80..87, 79 grays and color cube black/white
|
|
||||||
'g#00'..'g#ff' -> 16, 80...87, 79 gray and color cube black/white
|
|
||||||
|
|
||||||
Returns None if desc is invalid.
|
|
||||||
|
|
||||||
>>> _parse_color_88('h142')
|
|
||||||
>>> _parse_color_88('h42')
|
|
||||||
42
|
|
||||||
>>> _parse_color_88('#f00')
|
|
||||||
64
|
|
||||||
>>> _parse_color_88('g100')
|
|
||||||
79
|
|
||||||
>>> _parse_color_88('g#80')
|
|
||||||
83
|
|
||||||
"""
|
|
||||||
if len(desc) > 4:
|
|
||||||
# keep the length within reason before parsing
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
if desc.startswith('h'):
|
|
||||||
# high-color number
|
|
||||||
num = int(desc[1:], 10)
|
|
||||||
if num < 0 or num > 87:
|
|
||||||
return None
|
|
||||||
return num
|
|
||||||
|
|
||||||
if desc.startswith('#') and len(desc) == 4:
|
|
||||||
# color-cube coordinates
|
|
||||||
rgb = int(desc[1:], 16)
|
|
||||||
if rgb < 0:
|
|
||||||
return None
|
|
||||||
b, rgb = rgb % 16, rgb // 16
|
|
||||||
g, r = rgb % 16, rgb // 16
|
|
||||||
# find the closest rgb values
|
|
||||||
r = _CUBE_88_LOOKUP_16[r]
|
|
||||||
g = _CUBE_88_LOOKUP_16[g]
|
|
||||||
b = _CUBE_88_LOOKUP_16[b]
|
|
||||||
return _CUBE_START + (r * _CUBE_SIZE_88 + g) * _CUBE_SIZE_88 + b
|
|
||||||
|
|
||||||
# Only remaining possibility is gray value
|
|
||||||
if desc.startswith('g#'):
|
|
||||||
# hex value 00..ff
|
|
||||||
gray = int(desc[2:], 16)
|
|
||||||
if gray < 0 or gray > 255:
|
|
||||||
return None
|
|
||||||
gray = _GRAY_88_LOOKUP[gray]
|
|
||||||
elif desc.startswith('g'):
|
|
||||||
# decimal value 0..100
|
|
||||||
gray = int(desc[1:], 10)
|
|
||||||
if gray < 0 or gray > 100:
|
|
||||||
return None
|
|
||||||
gray = _GRAY_88_LOOKUP_101[gray]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
if gray == 0:
|
|
||||||
return _CUBE_BLACK
|
|
||||||
gray -= 1
|
|
||||||
if gray == _GRAY_SIZE_88:
|
|
||||||
return _CUBE_WHITE_88
|
|
||||||
return _GRAY_START_88 + gray
|
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
class AttrSpecError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class AttrSpec(object):
|
|
||||||
def __init__(self, fg, bg, colors=256):
|
|
||||||
"""
|
|
||||||
fg -- a string containing a comma-separated foreground color
|
|
||||||
and settings
|
|
||||||
|
|
||||||
Color values:
|
|
||||||
'default' (use the terminal's default foreground),
|
|
||||||
'black', 'dark red', 'dark green', 'brown', 'dark blue',
|
|
||||||
'dark magenta', 'dark cyan', 'light gray', 'dark gray',
|
|
||||||
'light red', 'light green', 'yellow', 'light blue',
|
|
||||||
'light magenta', 'light cyan', 'white'
|
|
||||||
|
|
||||||
High-color example values:
|
|
||||||
'#009' (0% red, 0% green, 60% red, like HTML colors)
|
|
||||||
'#fcc' (100% red, 80% green, 80% blue)
|
|
||||||
'g40' (40% gray, decimal), 'g#cc' (80% gray, hex),
|
|
||||||
'#000', 'g0', 'g#00' (black),
|
|
||||||
'#fff', 'g100', 'g#ff' (white)
|
|
||||||
'h8' (color number 8), 'h255' (color number 255)
|
|
||||||
|
|
||||||
Setting:
|
|
||||||
'bold', 'underline', 'blink', 'standout'
|
|
||||||
|
|
||||||
Some terminals use 'bold' for bright colors. Most terminals
|
|
||||||
ignore the 'blink' setting. If the color is not given then
|
|
||||||
'default' will be assumed.
|
|
||||||
|
|
||||||
bg -- a string containing the background color
|
|
||||||
|
|
||||||
Color values:
|
|
||||||
'default' (use the terminal's default background),
|
|
||||||
'black', 'dark red', 'dark green', 'brown', 'dark blue',
|
|
||||||
'dark magenta', 'dark cyan', 'light gray'
|
|
||||||
|
|
||||||
High-color exaples:
|
|
||||||
see fg examples above
|
|
||||||
|
|
||||||
An empty string will be treated the same as 'default'.
|
|
||||||
|
|
||||||
colors -- the maximum colors available for the specification
|
|
||||||
|
|
||||||
Valid values include: 1, 16, 88 and 256. High-color
|
|
||||||
values are only usable with 88 or 256 colors. With
|
|
||||||
1 color only the foreground settings may be used.
|
|
||||||
|
|
||||||
>>> AttrSpec('dark red', 'light gray', 16)
|
|
||||||
AttrSpec('dark red', 'light gray')
|
|
||||||
>>> AttrSpec('yellow, underline, bold', 'dark blue')
|
|
||||||
AttrSpec('yellow,bold,underline', 'dark blue')
|
|
||||||
>>> AttrSpec('#ddb', '#004', 256) # closest colors will be found
|
|
||||||
AttrSpec('#dda', '#006')
|
|
||||||
>>> AttrSpec('#ddb', '#004', 88)
|
|
||||||
AttrSpec('#ccc', '#000', colors=88)
|
|
||||||
"""
|
|
||||||
if colors not in (1, 16, 88, 256):
|
|
||||||
raise AttrSpecError('invalid number of colors (%d).' % colors)
|
|
||||||
self._value = 0 | _HIGH_88_COLOR * (colors == 88)
|
|
||||||
self.foreground = fg
|
|
||||||
self.background = bg
|
|
||||||
if self.colors > colors:
|
|
||||||
raise AttrSpecError(('foreground/background (%s/%s) require ' +
|
|
||||||
'more colors than have been specified (%d).') %
|
|
||||||
(repr(fg), repr(bg), colors))
|
|
||||||
|
|
||||||
foreground_basic = property(lambda s: s._value & _FG_BASIC_COLOR != 0)
|
|
||||||
foreground_high = property(lambda s: s._value & _FG_HIGH_COLOR != 0)
|
|
||||||
foreground_number = property(lambda s: s._value & _FG_COLOR_MASK)
|
|
||||||
background_basic = property(lambda s: s._value & _BG_BASIC_COLOR != 0)
|
|
||||||
background_high = property(lambda s: s._value & _BG_HIGH_COLOR != 0)
|
|
||||||
background_number = property(lambda s: (s._value & _BG_COLOR_MASK)
|
|
||||||
>> _BG_SHIFT)
|
|
||||||
bold = property(lambda s: s._value & _BOLD != 0)
|
|
||||||
underline = property(lambda s: s._value & _UNDERLINE != 0)
|
|
||||||
blink = property(lambda s: s._value & _BLINK != 0)
|
|
||||||
standout = property(lambda s: s._value & _STANDOUT != 0)
|
|
||||||
|
|
||||||
def _colors(self):
|
|
||||||
"""
|
|
||||||
Return the maximum colors required for this object.
|
|
||||||
|
|
||||||
Returns 256, 88, 16 or 1.
|
|
||||||
"""
|
|
||||||
if self._value & _HIGH_88_COLOR:
|
|
||||||
return 88
|
|
||||||
if self._value & (_BG_HIGH_COLOR | _FG_HIGH_COLOR):
|
|
||||||
return 256
|
|
||||||
if self._value & (_BG_BASIC_COLOR | _BG_BASIC_COLOR):
|
|
||||||
return 16
|
|
||||||
return 1
|
|
||||||
colors = property(_colors)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""
|
|
||||||
Return an executable python representation of the AttrSpec
|
|
||||||
object.
|
|
||||||
"""
|
|
||||||
args = "%r, %r" % (self.foreground, self.background)
|
|
||||||
if self.colors == 88:
|
|
||||||
# 88-color mode is the only one that is handled differently
|
|
||||||
args = args + ", colors=88"
|
|
||||||
return "%s(%s)" % (self.__class__.__name__, args)
|
|
||||||
|
|
||||||
def _foreground_color(self):
|
|
||||||
"""Return only the color component of the foreground."""
|
|
||||||
if not (self.foreground_basic or self.foreground_high):
|
|
||||||
return 'default'
|
|
||||||
if self.foreground_basic:
|
|
||||||
return _BASIC_COLORS[self.foreground_number]
|
|
||||||
if self.colors == 88:
|
|
||||||
return _color_desc_88(self.foreground_number)
|
|
||||||
return _color_desc_256(self.foreground_number)
|
|
||||||
|
|
||||||
def _foreground(self):
|
|
||||||
return (self._foreground_color() +
|
|
||||||
',bold' * self.bold + ',standout' * self.standout +
|
|
||||||
',blink' * self.blink + ',underline' * self.underline)
|
|
||||||
|
|
||||||
def _set_foreground(self, foreground):
|
|
||||||
color = None
|
|
||||||
flags = 0
|
|
||||||
# handle comma-separated foreground
|
|
||||||
for part in foreground.split(','):
|
|
||||||
part = part.strip()
|
|
||||||
if part in _ATTRIBUTES:
|
|
||||||
# parse and store "settings"/attributes in flags
|
|
||||||
if flags & _ATTRIBUTES[part]:
|
|
||||||
raise AttrSpecError(("Setting %s specified more than" +
|
|
||||||
"once in foreground (%s)") % (repr(part),
|
|
||||||
repr(foreground)))
|
|
||||||
flags |= _ATTRIBUTES[part]
|
|
||||||
continue
|
|
||||||
# past this point we must be specifying a color
|
|
||||||
if part in ('', 'default'):
|
|
||||||
scolor = 0
|
|
||||||
elif part in _BASIC_COLORS:
|
|
||||||
scolor = _BASIC_COLORS.index(part)
|
|
||||||
flags |= _FG_BASIC_COLOR
|
|
||||||
elif self._value & _HIGH_88_COLOR:
|
|
||||||
scolor = _parse_color_88(part)
|
|
||||||
flags |= _FG_HIGH_COLOR
|
|
||||||
else:
|
|
||||||
scolor = _parse_color_256(part)
|
|
||||||
flags |= _FG_HIGH_COLOR
|
|
||||||
# _parse_color_*() return None for unrecognised colors
|
|
||||||
if scolor is None:
|
|
||||||
raise AttrSpecError(("Unrecognised color specification %s " +
|
|
||||||
"in foreground (%s)") % (repr(part), repr(foreground)))
|
|
||||||
if color is not None:
|
|
||||||
raise AttrSpecError(("More than one color given for " +
|
|
||||||
"foreground (%s)") % (repr(foreground),))
|
|
||||||
color = scolor
|
|
||||||
if color is None:
|
|
||||||
color = 0
|
|
||||||
self._value = (self._value & ~_FG_MASK) | color | flags
|
|
||||||
|
|
||||||
foreground = property(_foreground, _set_foreground)
|
|
||||||
|
|
||||||
def _background(self):
|
|
||||||
"""Return the background color."""
|
|
||||||
if not (self.background_basic or self.background_high):
|
|
||||||
return 'default'
|
|
||||||
if self.background_basic:
|
|
||||||
return _BASIC_COLORS[self.background_number]
|
|
||||||
if self._value & _HIGH_88_COLOR:
|
|
||||||
return _color_desc_88(self.background_number)
|
|
||||||
return _color_desc_256(self.background_number)
|
|
||||||
|
|
||||||
def _set_background(self, background):
|
|
||||||
flags = 0
|
|
||||||
if background in ('', 'default'):
|
|
||||||
color = 0
|
|
||||||
elif background in _BASIC_COLORS:
|
|
||||||
color = _BASIC_COLORS.index(background)
|
|
||||||
flags |= _BG_BASIC_COLOR
|
|
||||||
elif self._value & _HIGH_88_COLOR:
|
|
||||||
color = _parse_color_88(background)
|
|
||||||
flags |= _BG_HIGH_COLOR
|
|
||||||
else:
|
|
||||||
color = _parse_color_256(background)
|
|
||||||
flags |= _BG_HIGH_COLOR
|
|
||||||
if color is None:
|
|
||||||
raise AttrSpecError(("Unrecognised color specification " +
|
|
||||||
"in background (%s)") % (repr(background),))
|
|
||||||
self._value = (self._value & ~_BG_MASK) | (color << _BG_SHIFT) | flags
|
|
||||||
|
|
||||||
background = property(_background, _set_background)
|
|
||||||
|
|
||||||
def get_rgb_values(self):
|
|
||||||
"""
|
|
||||||
Return (fg_red, fg_green, fg_blue, bg_red, bg_green, bg_blue) color
|
|
||||||
components. Each component is in the range 0-255. Values are taken
|
|
||||||
from the XTerm defaults and may not exactly match the user's terminal.
|
|
||||||
|
|
||||||
If the foreground or background is 'default' then all their compenents
|
|
||||||
will be returned as None.
|
|
||||||
|
|
||||||
>>> AttrSpec('yellow', '#ccf', colors=88).get_rgb_values()
|
|
||||||
(255, 255, 0, 205, 205, 255)
|
|
||||||
>>> AttrSpec('default', 'g92').get_rgb_values()
|
|
||||||
(None, None, None, 238, 238, 238)
|
|
||||||
"""
|
|
||||||
if not (self.foreground_basic or self.foreground_high):
|
|
||||||
vals = (None, None, None)
|
|
||||||
elif self.colors == 88:
|
|
||||||
assert self.foreground_number < 88, "Invalid AttrSpec _value"
|
|
||||||
vals = _COLOR_VALUES_88[self.foreground_number]
|
|
||||||
else:
|
|
||||||
vals = _COLOR_VALUES_256[self.foreground_number]
|
|
||||||
|
|
||||||
if not (self.background_basic or self.background_high):
|
|
||||||
return vals + (None, None, None)
|
|
||||||
elif self.colors == 88:
|
|
||||||
assert self.background_number < 88, "Invalid AttrSpec _value"
|
|
||||||
return vals + _COLOR_VALUES_88[self.background_number]
|
|
||||||
else:
|
|
||||||
return vals + _COLOR_VALUES_256[self.background_number]
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return isinstance(other, AttrSpec) and self._value == other._value
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self == other
|
|
||||||
|
|
||||||
__hash__ = object.__hash__
|
|
||||||
|
|
||||||
|
|
||||||
class RealTerminal(object):
|
class RealTerminal(object):
|
||||||
|
|
@ -711,11 +81,10 @@ class RealTerminal(object):
|
||||||
class ScreenError(Exception):
|
class ScreenError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class BaseScreen(object, metaclass=signals.MetaSignals):
|
class BaseScreen():
|
||||||
"""
|
"""
|
||||||
Base class for Screen classes (raw_display.Screen, .. etc)
|
Base class for Screen classes (raw_display.Screen, .. etc)
|
||||||
"""
|
"""
|
||||||
signals = [UPDATE_PALETTE_ENTRY, INPUT_DESCRIPTORS_CHANGED]
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(BaseScreen,self).__init__()
|
super(BaseScreen,self).__init__()
|
||||||
|
|
@ -753,137 +122,6 @@ class BaseScreen(object, metaclass=signals.MetaSignals):
|
||||||
def _stop(self):
|
def _stop(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def run_wrapper(self, fn, *args, **kwargs):
|
|
||||||
"""Start the screen, call a function, then stop the screen. Extra
|
|
||||||
arguments are passed to `start`.
|
|
||||||
|
|
||||||
Deprecated in favor of calling `start` as a context manager.
|
|
||||||
"""
|
|
||||||
with self.start(*args, **kwargs):
|
|
||||||
return fn()
|
|
||||||
|
|
||||||
|
|
||||||
def register_palette(self, palette):
|
|
||||||
"""Register a set of palette entries.
|
|
||||||
|
|
||||||
palette -- a list of (name, like_other_name) or
|
|
||||||
(name, foreground, background, mono, foreground_high,
|
|
||||||
background_high) tuples
|
|
||||||
|
|
||||||
The (name, like_other_name) format will copy the settings
|
|
||||||
from the palette entry like_other_name, which must appear
|
|
||||||
before this tuple in the list.
|
|
||||||
|
|
||||||
The mono and foreground/background_high values are
|
|
||||||
optional ie. the second tuple format may have 3, 4 or 6
|
|
||||||
values. See register_palette_entry() for a description
|
|
||||||
of the tuple values.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for item in palette:
|
|
||||||
if len(item) in (3,4,6):
|
|
||||||
self.register_palette_entry(*item)
|
|
||||||
continue
|
|
||||||
if len(item) != 2:
|
|
||||||
raise ScreenError("Invalid register_palette entry: %s" %
|
|
||||||
repr(item))
|
|
||||||
name, like_name = item
|
|
||||||
if like_name not in self._palette:
|
|
||||||
raise ScreenError("palette entry '%s' doesn't exist"%like_name)
|
|
||||||
self._palette[name] = self._palette[like_name]
|
|
||||||
|
|
||||||
def register_palette_entry(self, name, foreground, background,
|
|
||||||
mono=None, foreground_high=None, background_high=None):
|
|
||||||
"""Register a single palette entry.
|
|
||||||
|
|
||||||
name -- new entry/attribute name
|
|
||||||
|
|
||||||
foreground -- a string containing a comma-separated foreground
|
|
||||||
color and settings
|
|
||||||
|
|
||||||
Color values:
|
|
||||||
'default' (use the terminal's default foreground),
|
|
||||||
'black', 'dark red', 'dark green', 'brown', 'dark blue',
|
|
||||||
'dark magenta', 'dark cyan', 'light gray', 'dark gray',
|
|
||||||
'light red', 'light green', 'yellow', 'light blue',
|
|
||||||
'light magenta', 'light cyan', 'white'
|
|
||||||
|
|
||||||
Settings:
|
|
||||||
'bold', 'underline', 'blink', 'standout'
|
|
||||||
|
|
||||||
Some terminals use 'bold' for bright colors. Most terminals
|
|
||||||
ignore the 'blink' setting. If the color is not given then
|
|
||||||
'default' will be assumed.
|
|
||||||
|
|
||||||
background -- a string containing the background color
|
|
||||||
|
|
||||||
Background color values:
|
|
||||||
'default' (use the terminal's default background),
|
|
||||||
'black', 'dark red', 'dark green', 'brown', 'dark blue',
|
|
||||||
'dark magenta', 'dark cyan', 'light gray'
|
|
||||||
|
|
||||||
mono -- a comma-separated string containing monochrome terminal
|
|
||||||
settings (see "Settings" above.)
|
|
||||||
|
|
||||||
None = no terminal settings (same as 'default')
|
|
||||||
|
|
||||||
foreground_high -- a string containing a comma-separated
|
|
||||||
foreground color and settings, standard foreground
|
|
||||||
colors (see "Color values" above) or high-colors may
|
|
||||||
be used
|
|
||||||
|
|
||||||
High-color example values:
|
|
||||||
'#009' (0% red, 0% green, 60% red, like HTML colors)
|
|
||||||
'#fcc' (100% red, 80% green, 80% blue)
|
|
||||||
'g40' (40% gray, decimal), 'g#cc' (80% gray, hex),
|
|
||||||
'#000', 'g0', 'g#00' (black),
|
|
||||||
'#fff', 'g100', 'g#ff' (white)
|
|
||||||
'h8' (color number 8), 'h255' (color number 255)
|
|
||||||
|
|
||||||
None = use foreground parameter value
|
|
||||||
|
|
||||||
background_high -- a string containing the background color,
|
|
||||||
standard background colors (see "Background colors" above)
|
|
||||||
or high-colors (see "High-color example values" above)
|
|
||||||
may be used
|
|
||||||
|
|
||||||
None = use background parameter value
|
|
||||||
"""
|
|
||||||
basic = AttrSpec(foreground, background, 16)
|
|
||||||
|
|
||||||
if type(mono) == tuple:
|
|
||||||
# old style of specifying mono attributes was to put them
|
|
||||||
# in a tuple. convert to comma-separated string
|
|
||||||
mono = ",".join(mono)
|
|
||||||
if mono is None:
|
|
||||||
mono = DEFAULT
|
|
||||||
mono = AttrSpec(mono, DEFAULT, 1)
|
|
||||||
|
|
||||||
if foreground_high is None:
|
|
||||||
foreground_high = foreground
|
|
||||||
if background_high is None:
|
|
||||||
background_high = background
|
|
||||||
high_256 = AttrSpec(foreground_high, background_high, 256)
|
|
||||||
|
|
||||||
# 'hX' where X > 15 are different in 88/256 color, use
|
|
||||||
# basic colors for 88-color mode if high colors are specified
|
|
||||||
# in this way (also avoids crash when X > 87)
|
|
||||||
def large_h(desc):
|
|
||||||
if not desc.startswith('h'):
|
|
||||||
return False
|
|
||||||
if ',' in desc:
|
|
||||||
desc = desc.split(',',1)[0]
|
|
||||||
num = int(desc[1:], 10)
|
|
||||||
return num > 15
|
|
||||||
if large_h(foreground_high) or large_h(background_high):
|
|
||||||
high_88 = basic
|
|
||||||
else:
|
|
||||||
high_88 = AttrSpec(foreground_high, background_high, 88)
|
|
||||||
|
|
||||||
signals.emit_signal(self, UPDATE_PALETTE_ENTRY,
|
|
||||||
name, basic, mono, high_88, high_256)
|
|
||||||
self._palette[name] = (basic, mono, high_88, high_256)
|
|
||||||
|
|
||||||
|
|
||||||
def _test():
|
def _test():
|
||||||
import doctest
|
import doctest
|
||||||
|
|
|
||||||
|
|
@ -25,23 +25,13 @@ Direct terminal UI implementation
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
import struct
|
|
||||||
import sys
|
import sys
|
||||||
import signal
|
|
||||||
|
|
||||||
import fcntl
|
|
||||||
import termios
|
import termios
|
||||||
import tty
|
import tty
|
||||||
|
|
||||||
from urwid import util
|
|
||||||
from urwid import escape
|
from urwid import escape
|
||||||
from urwid.display_common import BaseScreen, RealTerminal, \
|
|
||||||
UPDATE_PALETTE_ENTRY, AttrSpec, UNPRINTABLE_TRANS_TABLE, \
|
|
||||||
INPUT_DESCRIPTORS_CHANGED
|
|
||||||
from urwid import signals
|
|
||||||
from urwid.compat import PYTHON3, bytes, B
|
|
||||||
|
|
||||||
from subprocess import Popen, PIPE
|
from urwid.display_common import BaseScreen, RealTerminal
|
||||||
|
|
||||||
|
|
||||||
class Screen(BaseScreen, RealTerminal):
|
class Screen(BaseScreen, RealTerminal):
|
||||||
|
|
@ -50,47 +40,16 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
terminal.
|
terminal.
|
||||||
"""
|
"""
|
||||||
super(Screen, self).__init__()
|
super(Screen, self).__init__()
|
||||||
self._pal_escape = {}
|
|
||||||
self._pal_attrspec = {}
|
|
||||||
signals.connect_signal(self, UPDATE_PALETTE_ENTRY,
|
|
||||||
self._on_update_palette_entry)
|
|
||||||
self.colors = 16 # FIXME: detect this
|
|
||||||
self.has_underline = True # FIXME: detect this
|
|
||||||
self.register_palette_entry( None, 'default','default')
|
|
||||||
self._keyqueue = []
|
self._keyqueue = []
|
||||||
self.prev_input_resize = 0
|
self.prev_input_resize = 0
|
||||||
self.set_input_timeouts()
|
self.set_input_timeouts()
|
||||||
self.screen_buf = None
|
|
||||||
self._screen_buf_canvas = None
|
|
||||||
self._resized = False
|
|
||||||
self.maxrow = None
|
|
||||||
self.gpm_mev = None
|
|
||||||
self.gpm_event_pending = False
|
|
||||||
self._mouse_tracking_enabled = False
|
self._mouse_tracking_enabled = False
|
||||||
self.last_bstate = 0
|
|
||||||
self._setup_G1_done = False
|
|
||||||
self._rows_used = None
|
|
||||||
self._cy = 0
|
|
||||||
term = os.environ.get('TERM', '')
|
|
||||||
self.fg_bright_is_bold = not term.startswith("xterm")
|
|
||||||
self.bg_bright_is_blink = (term == "linux")
|
|
||||||
self.back_color_erase = not term.startswith("screen")
|
|
||||||
self._next_timeout = None
|
self._next_timeout = None
|
||||||
|
|
||||||
# Our connections to the world
|
# Our connections to the world
|
||||||
self._term_output_file = output
|
self._term_output_file = output
|
||||||
self._term_input_file = input
|
self._term_input_file = input
|
||||||
|
|
||||||
# pipe for signalling external event loops about resize events
|
|
||||||
self._resize_pipe_rd, self._resize_pipe_wr = os.pipe()
|
|
||||||
fcntl.fcntl(self._resize_pipe_rd, fcntl.F_SETFL, os.O_NONBLOCK)
|
|
||||||
|
|
||||||
def _on_update_palette_entry(self, name, *attrspecs):
|
|
||||||
# copy the attribute to a dictionary containing the escape seqences
|
|
||||||
a = attrspecs[{16:0,1:1,88:2,256:3}[self.colors]]
|
|
||||||
self._pal_attrspec[name] = a
|
|
||||||
self._pal_escape[name] = self._attrspec_to_escape(a)
|
|
||||||
|
|
||||||
def set_input_timeouts(self, max_wait=None, complete_wait=0.125,
|
def set_input_timeouts(self, max_wait=None, complete_wait=0.125,
|
||||||
resize_wait=0.125):
|
resize_wait=0.125):
|
||||||
"""
|
"""
|
||||||
|
|
@ -116,39 +75,6 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
self.complete_wait = complete_wait
|
self.complete_wait = complete_wait
|
||||||
self.resize_wait = resize_wait
|
self.resize_wait = resize_wait
|
||||||
|
|
||||||
def _sigwinch_handler(self, signum, frame):
|
|
||||||
if not self._resized:
|
|
||||||
os.write(self._resize_pipe_wr, B('R'))
|
|
||||||
self._resized = True
|
|
||||||
self.screen_buf = None
|
|
||||||
|
|
||||||
def _sigcont_handler(self, signum, frame):
|
|
||||||
self.stop()
|
|
||||||
self.start()
|
|
||||||
self._sigwinch_handler(None, None)
|
|
||||||
|
|
||||||
def signal_init(self):
|
|
||||||
"""
|
|
||||||
Called in the startup of run wrapper to set the SIGWINCH
|
|
||||||
and SIGCONT signal handlers.
|
|
||||||
|
|
||||||
Override this function to call from main thread in threaded
|
|
||||||
applications.
|
|
||||||
"""
|
|
||||||
signal.signal(signal.SIGWINCH, self._sigwinch_handler)
|
|
||||||
signal.signal(signal.SIGCONT, self._sigcont_handler)
|
|
||||||
|
|
||||||
def signal_restore(self):
|
|
||||||
"""
|
|
||||||
Called in the finally block of run wrapper to restore the
|
|
||||||
SIGWINCH and SIGCONT signal handlers.
|
|
||||||
|
|
||||||
Override this function to call from main thread in threaded
|
|
||||||
applications.
|
|
||||||
"""
|
|
||||||
signal.signal(signal.SIGCONT, signal.SIG_DFL)
|
|
||||||
signal.signal(signal.SIGWINCH, signal.SIG_DFL)
|
|
||||||
|
|
||||||
def set_mouse_tracking(self, enable=True):
|
def set_mouse_tracking(self, enable=True):
|
||||||
"""
|
"""
|
||||||
Enable (or disable) mouse tracking.
|
Enable (or disable) mouse tracking.
|
||||||
|
|
@ -166,29 +92,8 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
def _mouse_tracking(self, enable):
|
def _mouse_tracking(self, enable):
|
||||||
if enable:
|
if enable:
|
||||||
self.write(escape.MOUSE_TRACKING_ON)
|
self.write(escape.MOUSE_TRACKING_ON)
|
||||||
self._start_gpm_tracking()
|
|
||||||
else:
|
else:
|
||||||
self.write(escape.MOUSE_TRACKING_OFF)
|
self.write(escape.MOUSE_TRACKING_OFF)
|
||||||
self._stop_gpm_tracking()
|
|
||||||
|
|
||||||
def _start_gpm_tracking(self):
|
|
||||||
if not os.path.isfile("/usr/bin/mev"):
|
|
||||||
return
|
|
||||||
if not os.environ.get('TERM',"").lower().startswith("linux"):
|
|
||||||
return
|
|
||||||
if not Popen:
|
|
||||||
return
|
|
||||||
m = Popen(["/usr/bin/mev","-e","158"], stdin=PIPE, stdout=PIPE,
|
|
||||||
close_fds=True)
|
|
||||||
fcntl.fcntl(m.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
|
|
||||||
self.gpm_mev = m
|
|
||||||
|
|
||||||
def _stop_gpm_tracking(self):
|
|
||||||
if not self.gpm_mev:
|
|
||||||
return
|
|
||||||
os.kill(self.gpm_mev.pid, signal.SIGINT)
|
|
||||||
os.waitpid(self.gpm_mev.pid, 0)
|
|
||||||
self.gpm_mev = None
|
|
||||||
|
|
||||||
def _start(self, alternate_buffer=True):
|
def _start(self, alternate_buffer=True):
|
||||||
"""
|
"""
|
||||||
|
|
@ -207,15 +112,12 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
self._old_termios_settings = termios.tcgetattr(fd)
|
self._old_termios_settings = termios.tcgetattr(fd)
|
||||||
tty.setcbreak(fd)
|
tty.setcbreak(fd)
|
||||||
|
|
||||||
self.signal_init()
|
|
||||||
self._alternate_buffer = alternate_buffer
|
self._alternate_buffer = alternate_buffer
|
||||||
self._next_timeout = self.max_wait
|
self._next_timeout = self.max_wait
|
||||||
|
|
||||||
if not self._signal_keys_set:
|
if not self._signal_keys_set:
|
||||||
self._old_signal_keys = self.tty_signal_keys(fileno=fd)
|
self._old_signal_keys = self.tty_signal_keys(fileno=fd)
|
||||||
|
|
||||||
signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED)
|
|
||||||
# restore mouse tracking to previous state
|
|
||||||
self._mouse_tracking(self._mouse_tracking_enabled)
|
self._mouse_tracking(self._mouse_tracking_enabled)
|
||||||
|
|
||||||
return super(Screen, self)._start()
|
return super(Screen, self)._start()
|
||||||
|
|
@ -224,11 +126,6 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
"""
|
"""
|
||||||
Restore the screen.
|
Restore the screen.
|
||||||
"""
|
"""
|
||||||
self.clear()
|
|
||||||
|
|
||||||
signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED)
|
|
||||||
|
|
||||||
self.signal_restore()
|
|
||||||
|
|
||||||
fd = self._term_input_file.fileno()
|
fd = self._term_input_file.fileno()
|
||||||
if os.isatty(fd):
|
if os.isatty(fd):
|
||||||
|
|
@ -240,12 +137,9 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
move_cursor = ""
|
move_cursor = ""
|
||||||
if self._alternate_buffer:
|
if self._alternate_buffer:
|
||||||
move_cursor = escape.RESTORE_NORMAL_BUFFER
|
move_cursor = escape.RESTORE_NORMAL_BUFFER
|
||||||
elif self.maxrow is not None:
|
|
||||||
move_cursor = escape.set_cursor_position(
|
|
||||||
0, self.maxrow)
|
|
||||||
self.write(
|
self.write(
|
||||||
self._attrspec_to_escape(AttrSpec('',''))
|
escape.SI
|
||||||
+ escape.SI
|
|
||||||
+ move_cursor
|
+ move_cursor
|
||||||
+ escape.SHOW_CURSOR)
|
+ escape.SHOW_CURSOR)
|
||||||
self.flush()
|
self.flush()
|
||||||
|
|
@ -347,73 +241,10 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
return keys, raw
|
return keys, raw
|
||||||
return keys
|
return keys
|
||||||
|
|
||||||
def get_input_descriptors(self):
|
|
||||||
"""
|
|
||||||
Return a list of integer file descriptors that should be
|
|
||||||
polled in external event loops to check for user input.
|
|
||||||
|
|
||||||
Use this method if you are implementing your own event loop.
|
|
||||||
"""
|
|
||||||
if not self._started:
|
|
||||||
return []
|
|
||||||
|
|
||||||
fd_list = [self._term_input_file.fileno(), self._resize_pipe_rd]
|
|
||||||
if self.gpm_mev is not None:
|
|
||||||
fd_list.append(self.gpm_mev.stdout.fileno())
|
|
||||||
return fd_list
|
|
||||||
|
|
||||||
_current_event_loop_handles = ()
|
|
||||||
|
|
||||||
def unhook_event_loop(self, event_loop):
|
|
||||||
"""
|
|
||||||
Remove any hooks added by hook_event_loop.
|
|
||||||
"""
|
|
||||||
for handle in self._current_event_loop_handles:
|
|
||||||
event_loop.remove_watch_file(handle)
|
|
||||||
|
|
||||||
if self._input_timeout:
|
|
||||||
event_loop.remove_alarm(self._input_timeout)
|
|
||||||
self._input_timeout = None
|
|
||||||
|
|
||||||
def hook_event_loop(self, event_loop, callback):
|
|
||||||
"""
|
|
||||||
Register the given callback with the event loop, to be called with new
|
|
||||||
input whenever it's available. The callback should be passed a list of
|
|
||||||
processed keys and a list of unprocessed keycodes.
|
|
||||||
|
|
||||||
Subclasses may wish to use parse_input to wrap the callback.
|
|
||||||
"""
|
|
||||||
if hasattr(self, 'get_input_nonblocking'):
|
|
||||||
wrapper = self._make_legacy_input_wrapper(event_loop, callback)
|
|
||||||
else:
|
|
||||||
wrapper = lambda: self.parse_input(
|
|
||||||
event_loop, callback, self.get_available_raw_input())
|
|
||||||
fds = self.get_input_descriptors()
|
|
||||||
handles = []
|
|
||||||
for fd in fds:
|
|
||||||
event_loop.watch_file(fd, wrapper)
|
|
||||||
self._current_event_loop_handles = handles
|
|
||||||
|
|
||||||
_input_timeout = None
|
_input_timeout = None
|
||||||
_partial_codes = None
|
_partial_codes = None
|
||||||
|
|
||||||
def _make_legacy_input_wrapper(self, event_loop, callback):
|
|
||||||
"""
|
|
||||||
Support old Screen classes that still have a get_input_nonblocking and
|
|
||||||
expect it to work.
|
|
||||||
"""
|
|
||||||
def wrapper():
|
|
||||||
if self._input_timeout:
|
|
||||||
event_loop.remove_alarm(self._input_timeout)
|
|
||||||
self._input_timeout = None
|
|
||||||
timeout, keys, raw = self.get_input_nonblocking()
|
|
||||||
if timeout is not None:
|
|
||||||
self._input_timeout = event_loop.alarm(timeout, wrapper)
|
|
||||||
|
|
||||||
callback(keys, raw)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
def get_available_raw_input(self):
|
def get_available_raw_input(self):
|
||||||
"""
|
"""
|
||||||
Return any currently-available input. Does not block.
|
Return any currently-available input. Does not block.
|
||||||
|
|
@ -421,19 +252,12 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
This method is only used by the default `hook_event_loop`
|
This method is only used by the default `hook_event_loop`
|
||||||
implementation; you can safely ignore it if you implement your own.
|
implementation; you can safely ignore it if you implement your own.
|
||||||
"""
|
"""
|
||||||
codes = self._get_gpm_codes() + self._get_keyboard_codes()
|
codes = self._get_keyboard_codes()
|
||||||
|
|
||||||
if self._partial_codes:
|
if self._partial_codes:
|
||||||
codes = self._partial_codes + codes
|
codes = self._partial_codes + codes
|
||||||
self._partial_codes = None
|
self._partial_codes = None
|
||||||
|
|
||||||
# clean out the pipe used to signal external event loops
|
|
||||||
# that a resize has occurred
|
|
||||||
try:
|
|
||||||
while True: os.read(self._resize_pipe_rd, 1)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return codes
|
return codes
|
||||||
|
|
||||||
def parse_input(self, event_loop, callback, codes, wait_for_more=True):
|
def parse_input(self, event_loop, callback, codes, wait_for_more=True):
|
||||||
|
|
@ -482,10 +306,6 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
processed_codes = original_codes
|
processed_codes = original_codes
|
||||||
self._partial_codes = None
|
self._partial_codes = None
|
||||||
|
|
||||||
if self._resized:
|
|
||||||
processed.append('window resize')
|
|
||||||
self._resized = False
|
|
||||||
|
|
||||||
if callback:
|
if callback:
|
||||||
callback(processed, processed_codes)
|
callback(processed, processed_codes)
|
||||||
else:
|
else:
|
||||||
|
|
@ -501,21 +321,9 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
codes.append(code)
|
codes.append(code)
|
||||||
return codes
|
return codes
|
||||||
|
|
||||||
def _get_gpm_codes(self):
|
|
||||||
codes = []
|
|
||||||
try:
|
|
||||||
while self.gpm_mev is not None and self.gpm_event_pending:
|
|
||||||
codes.extend(self._encode_gpm_event())
|
|
||||||
except IOError as e:
|
|
||||||
if e.args[0] != 11:
|
|
||||||
raise
|
|
||||||
return codes
|
|
||||||
|
|
||||||
def _wait_for_input_ready(self, timeout):
|
def _wait_for_input_ready(self, timeout):
|
||||||
ready = None
|
ready = None
|
||||||
fd_list = [self._term_input_file.fileno()]
|
fd_list = [self._term_input_file.fileno()]
|
||||||
if self.gpm_mev is not None:
|
|
||||||
fd_list.append(self.gpm_mev.stdout.fileno())
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
|
|
@ -528,498 +336,18 @@ class Screen(BaseScreen, RealTerminal):
|
||||||
except select.error as e:
|
except select.error as e:
|
||||||
if e.args[0] != 4:
|
if e.args[0] != 4:
|
||||||
raise
|
raise
|
||||||
if self._resized:
|
|
||||||
ready = []
|
|
||||||
break
|
|
||||||
return ready
|
return ready
|
||||||
|
|
||||||
def _getch(self, timeout):
|
def _getch(self, timeout):
|
||||||
ready = self._wait_for_input_ready(timeout)
|
ready = self._wait_for_input_ready(timeout)
|
||||||
if self.gpm_mev is not None:
|
|
||||||
if self.gpm_mev.stdout.fileno() in ready:
|
|
||||||
self.gpm_event_pending = True
|
|
||||||
if self._term_input_file.fileno() in ready:
|
if self._term_input_file.fileno() in ready:
|
||||||
return ord(os.read(self._term_input_file.fileno(), 1))
|
return ord(os.read(self._term_input_file.fileno(), 1))
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
def _encode_gpm_event( self ):
|
|
||||||
self.gpm_event_pending = False
|
|
||||||
s = self.gpm_mev.stdout.readline().decode('ascii')
|
|
||||||
l = s.split(",")
|
|
||||||
if len(l) != 6:
|
|
||||||
# unexpected output, stop tracking
|
|
||||||
self._stop_gpm_tracking()
|
|
||||||
signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED)
|
|
||||||
return []
|
|
||||||
ev, x, y, ign, b, m = s.split(",")
|
|
||||||
ev = int( ev.split("x")[-1], 16)
|
|
||||||
x = int( x.split(" ")[-1] )
|
|
||||||
y = int( y.lstrip().split(" ")[0] )
|
|
||||||
b = int( b.split(" ")[-1] )
|
|
||||||
m = int( m.split("x")[-1].rstrip(), 16 )
|
|
||||||
|
|
||||||
# convert to xterm-like escape sequence
|
|
||||||
|
|
||||||
last = next = self.last_bstate
|
|
||||||
l = []
|
|
||||||
|
|
||||||
mod = 0
|
|
||||||
if m & 1: mod |= 4 # shift
|
|
||||||
if m & 10: mod |= 8 # alt
|
|
||||||
if m & 4: mod |= 16 # ctrl
|
|
||||||
|
|
||||||
def append_button( b ):
|
|
||||||
b |= mod
|
|
||||||
l.extend([ 27, ord('['), ord('M'), b+32, x+32, y+32 ])
|
|
||||||
|
|
||||||
def determine_button_release( flag ):
|
|
||||||
if b & 4 and last & 1:
|
|
||||||
append_button( 0 + flag )
|
|
||||||
next |= 1
|
|
||||||
if b & 2 and last & 2:
|
|
||||||
append_button( 1 + flag )
|
|
||||||
next |= 2
|
|
||||||
if b & 1 and last & 4:
|
|
||||||
append_button( 2 + flag )
|
|
||||||
next |= 4
|
|
||||||
|
|
||||||
if ev == 20 or ev == 36 or ev == 52: # press
|
|
||||||
if b & 4 and last & 1 == 0:
|
|
||||||
append_button( 0 )
|
|
||||||
next |= 1
|
|
||||||
if b & 2 and last & 2 == 0:
|
|
||||||
append_button( 1 )
|
|
||||||
next |= 2
|
|
||||||
if b & 1 and last & 4 == 0:
|
|
||||||
append_button( 2 )
|
|
||||||
next |= 4
|
|
||||||
elif ev == 146: # drag
|
|
||||||
if b & 4:
|
|
||||||
append_button( 0 + escape.MOUSE_DRAG_FLAG )
|
|
||||||
elif b & 2:
|
|
||||||
append_button( 1 + escape.MOUSE_DRAG_FLAG )
|
|
||||||
elif b & 1:
|
|
||||||
append_button( 2 + escape.MOUSE_DRAG_FLAG )
|
|
||||||
else: # release
|
|
||||||
if b & 4 and last & 1:
|
|
||||||
append_button( 0 + escape.MOUSE_RELEASE_FLAG )
|
|
||||||
next &= ~ 1
|
|
||||||
if b & 2 and last & 2:
|
|
||||||
append_button( 1 + escape.MOUSE_RELEASE_FLAG )
|
|
||||||
next &= ~ 2
|
|
||||||
if b & 1 and last & 4:
|
|
||||||
append_button( 2 + escape.MOUSE_RELEASE_FLAG )
|
|
||||||
next &= ~ 4
|
|
||||||
if ev == 40: # double click (release)
|
|
||||||
if b & 4 and last & 1:
|
|
||||||
append_button( 0 + escape.MOUSE_MULTIPLE_CLICK_FLAG )
|
|
||||||
if b & 2 and last & 2:
|
|
||||||
append_button( 1 + escape.MOUSE_MULTIPLE_CLICK_FLAG )
|
|
||||||
if b & 1 and last & 4:
|
|
||||||
append_button( 2 + escape.MOUSE_MULTIPLE_CLICK_FLAG )
|
|
||||||
elif ev == 52:
|
|
||||||
if b & 4 and last & 1:
|
|
||||||
append_button( 0 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 )
|
|
||||||
if b & 2 and last & 2:
|
|
||||||
append_button( 1 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 )
|
|
||||||
if b & 1 and last & 4:
|
|
||||||
append_button( 2 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 )
|
|
||||||
|
|
||||||
self.last_bstate = next
|
|
||||||
return l
|
|
||||||
|
|
||||||
def _getch_nodelay(self):
|
def _getch_nodelay(self):
|
||||||
return self._getch(0)
|
return self._getch(0)
|
||||||
|
|
||||||
|
|
||||||
def get_cols_rows(self):
|
|
||||||
"""Return the terminal dimensions (num columns, num rows)."""
|
|
||||||
y, x = 80, 24
|
|
||||||
try:
|
|
||||||
buf = fcntl.ioctl(self._term_output_file.fileno(),
|
|
||||||
termios.TIOCGWINSZ, ' '*4)
|
|
||||||
y, x = struct.unpack('hh', buf)
|
|
||||||
except IOError:
|
|
||||||
# Term size could not be determined
|
|
||||||
pass
|
|
||||||
self.maxrow = y
|
|
||||||
return x, y
|
|
||||||
|
|
||||||
def _setup_G1(self):
|
|
||||||
"""
|
|
||||||
Initialize the G1 character set to graphics mode if required.
|
|
||||||
"""
|
|
||||||
if self._setup_G1_done:
|
|
||||||
return
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
self.write(escape.DESIGNATE_G1_SPECIAL)
|
|
||||||
self.flush()
|
|
||||||
break
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
self._setup_G1_done = True
|
|
||||||
|
|
||||||
|
|
||||||
def draw_screen(self, xxx_todo_changeme, r ):
|
|
||||||
"""Paint screen with rendered canvas."""
|
|
||||||
(maxcol, maxrow) = xxx_todo_changeme
|
|
||||||
assert self._started
|
|
||||||
|
|
||||||
assert maxrow == r.rows()
|
|
||||||
|
|
||||||
# quick return if nothing has changed
|
|
||||||
if self.screen_buf and r is self._screen_buf_canvas:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._setup_G1()
|
|
||||||
|
|
||||||
if self._resized:
|
|
||||||
# handle resize before trying to draw screen
|
|
||||||
return
|
|
||||||
|
|
||||||
o = [escape.HIDE_CURSOR, self._attrspec_to_escape(AttrSpec('',''))]
|
|
||||||
|
|
||||||
def partial_display():
|
|
||||||
# returns True if the screen is in partial display mode
|
|
||||||
# ie. only some rows belong to the display
|
|
||||||
return self._rows_used is not None
|
|
||||||
|
|
||||||
if not partial_display():
|
|
||||||
o.append(escape.CURSOR_HOME)
|
|
||||||
|
|
||||||
if self.screen_buf:
|
|
||||||
osb = self.screen_buf
|
|
||||||
else:
|
|
||||||
osb = []
|
|
||||||
sb = []
|
|
||||||
cy = self._cy
|
|
||||||
y = -1
|
|
||||||
|
|
||||||
def set_cursor_home():
|
|
||||||
if not partial_display():
|
|
||||||
return escape.set_cursor_position(0, 0)
|
|
||||||
return (escape.CURSOR_HOME_COL +
|
|
||||||
escape.move_cursor_up(cy))
|
|
||||||
|
|
||||||
def set_cursor_row(y):
|
|
||||||
if not partial_display():
|
|
||||||
return escape.set_cursor_position(0, y)
|
|
||||||
return escape.move_cursor_down(y - cy)
|
|
||||||
|
|
||||||
def set_cursor_position(x, y):
|
|
||||||
if not partial_display():
|
|
||||||
return escape.set_cursor_position(x, y)
|
|
||||||
if cy > y:
|
|
||||||
return ('\b' + escape.CURSOR_HOME_COL +
|
|
||||||
escape.move_cursor_up(cy - y) +
|
|
||||||
escape.move_cursor_right(x))
|
|
||||||
return ('\b' + escape.CURSOR_HOME_COL +
|
|
||||||
escape.move_cursor_down(y - cy) +
|
|
||||||
escape.move_cursor_right(x))
|
|
||||||
|
|
||||||
def is_blank_row(row):
|
|
||||||
if len(row) > 1:
|
|
||||||
return False
|
|
||||||
if row[0][2].strip():
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def attr_to_escape(a):
|
|
||||||
if a in self._pal_escape:
|
|
||||||
return self._pal_escape[a]
|
|
||||||
elif isinstance(a, AttrSpec):
|
|
||||||
return self._attrspec_to_escape(a)
|
|
||||||
# undefined attributes use default/default
|
|
||||||
# TODO: track and report these
|
|
||||||
return self._attrspec_to_escape(
|
|
||||||
AttrSpec('default','default'))
|
|
||||||
|
|
||||||
def using_standout(a):
|
|
||||||
a = self._pal_attrspec.get(a, a)
|
|
||||||
return isinstance(a, AttrSpec) and a.standout
|
|
||||||
|
|
||||||
ins = None
|
|
||||||
o.append(set_cursor_home())
|
|
||||||
cy = 0
|
|
||||||
for row in r.content():
|
|
||||||
y += 1
|
|
||||||
if osb and y < len(osb) and osb[y] == row:
|
|
||||||
# this row of the screen buffer matches what is
|
|
||||||
# currently displayed, so we can skip this line
|
|
||||||
sb.append( osb[y] )
|
|
||||||
continue
|
|
||||||
|
|
||||||
sb.append(row)
|
|
||||||
|
|
||||||
# leave blank lines off display when we are using
|
|
||||||
# the default screen buffer (allows partial screen)
|
|
||||||
if partial_display() and y > self._rows_used:
|
|
||||||
if is_blank_row(row):
|
|
||||||
continue
|
|
||||||
self._rows_used = y
|
|
||||||
|
|
||||||
if y or partial_display():
|
|
||||||
o.append(set_cursor_position(0, y))
|
|
||||||
# after updating the line we will be just over the
|
|
||||||
# edge, but terminals still treat this as being
|
|
||||||
# on the same line
|
|
||||||
cy = y
|
|
||||||
|
|
||||||
whitespace_at_end = False
|
|
||||||
if row:
|
|
||||||
a, cs, run = row[-1]
|
|
||||||
if (run[-1:] == B(' ') and self.back_color_erase
|
|
||||||
and not using_standout(a)):
|
|
||||||
whitespace_at_end = True
|
|
||||||
row = row[:-1] + [(a, cs, run.rstrip(B(' ')))]
|
|
||||||
elif y == maxrow-1 and maxcol > 1:
|
|
||||||
row, back, ins = self._last_row(row)
|
|
||||||
|
|
||||||
first = True
|
|
||||||
lasta = lastcs = None
|
|
||||||
for (a,cs, run) in row:
|
|
||||||
assert isinstance(run, bytes) # canvases should render with bytes
|
|
||||||
if cs != 'U':
|
|
||||||
run = run.translate(UNPRINTABLE_TRANS_TABLE)
|
|
||||||
if first or lasta != a:
|
|
||||||
o.append(attr_to_escape(a))
|
|
||||||
lasta = a
|
|
||||||
if first or lastcs != cs:
|
|
||||||
assert cs in [None, "0", "U"], repr(cs)
|
|
||||||
if lastcs == "U":
|
|
||||||
o.append( escape.IBMPC_OFF )
|
|
||||||
|
|
||||||
if cs is None:
|
|
||||||
o.append( escape.SI )
|
|
||||||
elif cs == "U":
|
|
||||||
o.append( escape.IBMPC_ON )
|
|
||||||
else:
|
|
||||||
o.append( escape.SO )
|
|
||||||
lastcs = cs
|
|
||||||
o.append( run )
|
|
||||||
first = False
|
|
||||||
if ins:
|
|
||||||
(inserta, insertcs, inserttext) = ins
|
|
||||||
ias = attr_to_escape(inserta)
|
|
||||||
assert insertcs in [None, "0", "U"], repr(insertcs)
|
|
||||||
if cs is None:
|
|
||||||
icss = escape.SI
|
|
||||||
elif cs == "U":
|
|
||||||
icss = escape.IBMPC_ON
|
|
||||||
else:
|
|
||||||
icss = escape.SO
|
|
||||||
o += [ "\x08"*back,
|
|
||||||
ias, icss,
|
|
||||||
escape.INSERT_ON, inserttext,
|
|
||||||
escape.INSERT_OFF ]
|
|
||||||
|
|
||||||
if cs == "U":
|
|
||||||
o.append(escape.IBMPC_OFF)
|
|
||||||
if whitespace_at_end:
|
|
||||||
o.append(escape.ERASE_IN_LINE_RIGHT)
|
|
||||||
|
|
||||||
if r.cursor is not None:
|
|
||||||
x,y = r.cursor
|
|
||||||
o += [set_cursor_position(x, y),
|
|
||||||
escape.SHOW_CURSOR ]
|
|
||||||
self._cy = y
|
|
||||||
|
|
||||||
if self._resized:
|
|
||||||
# handle resize before trying to draw screen
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
for l in o:
|
|
||||||
if isinstance(l, bytes) and PYTHON3:
|
|
||||||
l = l.decode('utf-8')
|
|
||||||
self.write(l)
|
|
||||||
self.flush()
|
|
||||||
except IOError as e:
|
|
||||||
# ignore interrupted syscall
|
|
||||||
if e.args[0] != 4:
|
|
||||||
raise
|
|
||||||
|
|
||||||
self.screen_buf = sb
|
|
||||||
self._screen_buf_canvas = r
|
|
||||||
|
|
||||||
|
|
||||||
def _last_row(self, row):
|
|
||||||
"""On the last row we need to slide the bottom right character
|
|
||||||
into place. Calculate the new line, attr and an insert sequence
|
|
||||||
to do that.
|
|
||||||
|
|
||||||
eg. last row:
|
|
||||||
XXXXXXXXXXXXXXXXXXXXYZ
|
|
||||||
|
|
||||||
Y will be drawn after Z, shifting Z into position.
|
|
||||||
"""
|
|
||||||
|
|
||||||
new_row = row[:-1]
|
|
||||||
z_attr, z_cs, last_text = row[-1]
|
|
||||||
last_cols = util.calc_width(last_text, 0, len(last_text))
|
|
||||||
last_offs, z_col = util.calc_text_pos(last_text, 0,
|
|
||||||
len(last_text), last_cols-1)
|
|
||||||
if last_offs == 0:
|
|
||||||
z_text = last_text
|
|
||||||
del new_row[-1]
|
|
||||||
# we need another segment
|
|
||||||
y_attr, y_cs, nlast_text = row[-2]
|
|
||||||
nlast_cols = util.calc_width(nlast_text, 0,
|
|
||||||
len(nlast_text))
|
|
||||||
z_col += nlast_cols
|
|
||||||
nlast_offs, y_col = util.calc_text_pos(nlast_text, 0,
|
|
||||||
len(nlast_text), nlast_cols-1)
|
|
||||||
y_text = nlast_text[nlast_offs:]
|
|
||||||
if nlast_offs:
|
|
||||||
new_row.append((y_attr, y_cs,
|
|
||||||
nlast_text[:nlast_offs]))
|
|
||||||
else:
|
|
||||||
z_text = last_text[last_offs:]
|
|
||||||
y_attr, y_cs = z_attr, z_cs
|
|
||||||
nlast_cols = util.calc_width(last_text, 0,
|
|
||||||
last_offs)
|
|
||||||
nlast_offs, y_col = util.calc_text_pos(last_text, 0,
|
|
||||||
last_offs, nlast_cols-1)
|
|
||||||
y_text = last_text[nlast_offs:last_offs]
|
|
||||||
if nlast_offs:
|
|
||||||
new_row.append((y_attr, y_cs,
|
|
||||||
last_text[:nlast_offs]))
|
|
||||||
|
|
||||||
new_row.append((z_attr, z_cs, z_text))
|
|
||||||
return new_row, z_col-y_col, (y_attr, y_cs, y_text)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
"""
|
|
||||||
Force the screen to be completely repainted on the next
|
|
||||||
call to draw_screen().
|
|
||||||
"""
|
|
||||||
self.screen_buf = None
|
|
||||||
self.setup_G1 = True
|
|
||||||
|
|
||||||
|
|
||||||
def _attrspec_to_escape(self, a):
|
|
||||||
"""
|
|
||||||
Convert AttrSpec instance a to an escape sequence for the terminal
|
|
||||||
|
|
||||||
>>> s = Screen()
|
|
||||||
>>> s.set_terminal_properties(colors=256)
|
|
||||||
>>> a2e = s._attrspec_to_escape
|
|
||||||
>>> a2e(s.AttrSpec('brown', 'dark green'))
|
|
||||||
'\\x1b[0;33;42m'
|
|
||||||
>>> a2e(s.AttrSpec('#fea,underline', '#d0d'))
|
|
||||||
'\\x1b[0;38;5;229;4;48;5;164m'
|
|
||||||
"""
|
|
||||||
if a.foreground_high:
|
|
||||||
fg = "38;5;%d" % a.foreground_number
|
|
||||||
elif a.foreground_basic:
|
|
||||||
if a.foreground_number > 7:
|
|
||||||
if self.fg_bright_is_bold:
|
|
||||||
fg = "1;%d" % (a.foreground_number - 8 + 30)
|
|
||||||
else:
|
|
||||||
fg = "%d" % (a.foreground_number - 8 + 90)
|
|
||||||
else:
|
|
||||||
fg = "%d" % (a.foreground_number + 30)
|
|
||||||
else:
|
|
||||||
fg = "39"
|
|
||||||
st = ("1;" * a.bold + "4;" * a.underline +
|
|
||||||
"5;" * a.blink + "7;" * a.standout)
|
|
||||||
if a.background_high:
|
|
||||||
bg = "48;5;%d" % a.background_number
|
|
||||||
elif a.background_basic:
|
|
||||||
if a.background_number > 7:
|
|
||||||
if self.bg_bright_is_blink:
|
|
||||||
bg = "5;%d" % (a.background_number - 8 + 40)
|
|
||||||
else:
|
|
||||||
# this doesn't work on most terminals
|
|
||||||
bg = "%d" % (a.background_number - 8 + 100)
|
|
||||||
else:
|
|
||||||
bg = "%d" % (a.background_number + 40)
|
|
||||||
else:
|
|
||||||
bg = "49"
|
|
||||||
return escape.ESC + "[0;%s;%s%sm" % (fg, st, bg)
|
|
||||||
|
|
||||||
|
|
||||||
def set_terminal_properties(self, colors=None, bright_is_bold=None,
|
|
||||||
has_underline=None):
|
|
||||||
"""
|
|
||||||
colors -- number of colors terminal supports (1, 16, 88 or 256)
|
|
||||||
or None to leave unchanged
|
|
||||||
bright_is_bold -- set to True if this terminal uses the bold
|
|
||||||
setting to create bright colors (numbers 8-15), set to False
|
|
||||||
if this Terminal can create bright colors without bold or
|
|
||||||
None to leave unchanged
|
|
||||||
has_underline -- set to True if this terminal can use the
|
|
||||||
underline setting, False if it cannot or None to leave
|
|
||||||
unchanged
|
|
||||||
"""
|
|
||||||
if colors is None:
|
|
||||||
colors = self.colors
|
|
||||||
if bright_is_bold is None:
|
|
||||||
bright_is_bold = self.fg_bright_is_bold
|
|
||||||
if has_underline is None:
|
|
||||||
has_underline = self.has_underline
|
|
||||||
|
|
||||||
if colors == self.colors and bright_is_bold == self.fg_bright_is_bold \
|
|
||||||
and has_underline == self.has_underline:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.colors = colors
|
|
||||||
self.fg_bright_is_bold = bright_is_bold
|
|
||||||
self.has_underline = has_underline
|
|
||||||
|
|
||||||
self.clear()
|
|
||||||
self._pal_escape = {}
|
|
||||||
for p,v in list(self._palette.items()):
|
|
||||||
self._on_update_palette_entry(p, *v)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def reset_default_terminal_palette(self):
|
|
||||||
"""
|
|
||||||
Attempt to set the terminal palette to default values as taken
|
|
||||||
from xterm. Uses number of colors from current
|
|
||||||
set_terminal_properties() screen setting.
|
|
||||||
"""
|
|
||||||
if self.colors == 1:
|
|
||||||
return
|
|
||||||
|
|
||||||
def rgb_values(n):
|
|
||||||
if self.colors == 16:
|
|
||||||
aspec = AttrSpec("h%d"%n, "", 256)
|
|
||||||
else:
|
|
||||||
aspec = AttrSpec("h%d"%n, "", self.colors)
|
|
||||||
return aspec.get_rgb_values()[:3]
|
|
||||||
|
|
||||||
entries = [(n,) + rgb_values(n) for n in range(self.colors)]
|
|
||||||
self.modify_terminal_palette(entries)
|
|
||||||
|
|
||||||
|
|
||||||
def modify_terminal_palette(self, entries):
|
|
||||||
"""
|
|
||||||
entries - list of (index, red, green, blue) tuples.
|
|
||||||
|
|
||||||
Attempt to set part of the terminal palette (this does not work
|
|
||||||
on all terminals.) The changes are sent as a single escape
|
|
||||||
sequence so they should all take effect at the same time.
|
|
||||||
|
|
||||||
0 <= index < 256 (some terminals will only have 16 or 88 colors)
|
|
||||||
0 <= red, green, blue < 256
|
|
||||||
"""
|
|
||||||
|
|
||||||
modify = ["%d;rgb:%02x/%02x/%02x" % (index, red, green, blue)
|
|
||||||
for index, red, green, blue in entries]
|
|
||||||
self.write("\x1b]4;"+";".join(modify)+"\x1b\\")
|
|
||||||
self.flush()
|
|
||||||
|
|
||||||
|
|
||||||
# shortcut for creating an AttrSpec with this screen object's
|
|
||||||
# number of colors
|
|
||||||
AttrSpec = lambda self, fg, bg: AttrSpec(fg, bg, self.colors)
|
|
||||||
|
|
||||||
|
|
||||||
def _test():
|
def _test():
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
|
|
|
||||||
302
urwid/signals.py
302
urwid/signals.py
|
|
@ -1,302 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# Urwid signal dispatching
|
|
||||||
# Copyright (C) 2004-2012 Ian Ward
|
|
||||||
#
|
|
||||||
# This library is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
|
||||||
# License as published by the Free Software Foundation; either
|
|
||||||
# version 2.1 of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This library is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with this library; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
# Urwid web site: http://excess.org/urwid/
|
|
||||||
|
|
||||||
|
|
||||||
import itertools
|
|
||||||
import weakref
|
|
||||||
|
|
||||||
|
|
||||||
class MetaSignals(type):
|
|
||||||
"""
|
|
||||||
register the list of signals in the class varable signals,
|
|
||||||
including signals in superclasses.
|
|
||||||
"""
|
|
||||||
def __init__(cls, name, bases, d):
|
|
||||||
signals = d.get("signals", [])
|
|
||||||
for superclass in cls.__bases__:
|
|
||||||
signals.extend(getattr(superclass, 'signals', []))
|
|
||||||
signals = list(dict([(x,None) for x in signals]).keys())
|
|
||||||
d["signals"] = signals
|
|
||||||
register_signal(cls, signals)
|
|
||||||
super(MetaSignals, cls).__init__(name, bases, d)
|
|
||||||
|
|
||||||
def setdefaultattr(obj, name, value):
|
|
||||||
# like dict.setdefault() for object attributes
|
|
||||||
if hasattr(obj, name):
|
|
||||||
return getattr(obj, name)
|
|
||||||
setattr(obj, name, value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
class Key(object):
|
|
||||||
"""
|
|
||||||
Minimal class, whose only purpose is to produce objects with a
|
|
||||||
unique hash
|
|
||||||
"""
|
|
||||||
__slots__ = []
|
|
||||||
|
|
||||||
class Signals(object):
|
|
||||||
_signal_attr = '_urwid_signals' # attribute to attach to signal senders
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._supported = {}
|
|
||||||
|
|
||||||
def register(self, sig_cls, signals):
|
|
||||||
"""
|
|
||||||
:param sig_class: the class of an object that will be sending signals
|
|
||||||
:type sig_class: class
|
|
||||||
:param signals: a list of signals that may be sent, typically each
|
|
||||||
signal is represented by a string
|
|
||||||
:type signals: signal names
|
|
||||||
|
|
||||||
This function must be called for a class before connecting any
|
|
||||||
signal callbacks or emiting any signals from that class' objects
|
|
||||||
"""
|
|
||||||
self._supported[sig_cls] = signals
|
|
||||||
|
|
||||||
def connect(self, obj, name, callback, user_arg=None, weak_args=None, user_args=None):
|
|
||||||
"""
|
|
||||||
:param obj: the object sending a signal
|
|
||||||
:type obj: object
|
|
||||||
:param name: the signal to listen for, typically a string
|
|
||||||
:type name: signal name
|
|
||||||
:param callback: the function to call when that signal is sent
|
|
||||||
:type callback: function
|
|
||||||
:param user_arg: deprecated additional argument to callback (appended
|
|
||||||
after the arguments passed when the signal is
|
|
||||||
emitted). If None no arguments will be added.
|
|
||||||
Don't use this argument, use user_args instead.
|
|
||||||
:param weak_args: additional arguments passed to the callback
|
|
||||||
(before any arguments passed when the signal
|
|
||||||
is emitted and before any user_args).
|
|
||||||
|
|
||||||
These arguments are stored as weak references
|
|
||||||
(but converted back into their original value
|
|
||||||
before passing them to callback) to prevent
|
|
||||||
any objects referenced (indirectly) from
|
|
||||||
weak_args from being kept alive just because
|
|
||||||
they are referenced by this signal handler.
|
|
||||||
|
|
||||||
Use this argument only as a keyword argument,
|
|
||||||
since user_arg might be removed in the future.
|
|
||||||
:type weak_args: iterable
|
|
||||||
:param user_args: additional arguments to pass to the callback,
|
|
||||||
(before any arguments passed when the signal
|
|
||||||
is emitted but after any weak_args).
|
|
||||||
|
|
||||||
Use this argument only as a keyword argument,
|
|
||||||
since user_arg might be removed in the future.
|
|
||||||
:type user_args: iterable
|
|
||||||
|
|
||||||
When a matching signal is sent, callback will be called. The
|
|
||||||
arguments it receives will be the user_args passed at connect
|
|
||||||
time (as individual arguments) followed by all the positional
|
|
||||||
parameters sent with the signal.
|
|
||||||
|
|
||||||
As an example of using weak_args, consider the following snippet:
|
|
||||||
|
|
||||||
>>> import urwid
|
|
||||||
>>> debug = urwid.Text('')
|
|
||||||
>>> def handler(widget, newtext):
|
|
||||||
... debug.set_text("Edit widget changed to %s" % newtext)
|
|
||||||
>>> edit = urwid.Edit('')
|
|
||||||
>>> key = urwid.connect_signal(edit, 'change', handler)
|
|
||||||
|
|
||||||
If you now build some interface using "edit" and "debug", the
|
|
||||||
"debug" widget will show whatever you type in the "edit" widget.
|
|
||||||
However, if you remove all references to the "debug" widget, it
|
|
||||||
will still be kept alive by the signal handler. This because the
|
|
||||||
signal handler is a closure that (implicitly) references the
|
|
||||||
"edit" widget. If you want to allow the "debug" widget to be
|
|
||||||
garbage collected, you can create a "fake" or "weak" closure
|
|
||||||
(it's not really a closure, since it doesn't reference any
|
|
||||||
outside variables, so it's just a dynamic function):
|
|
||||||
|
|
||||||
>>> debug = urwid.Text('')
|
|
||||||
>>> def handler(weak_debug, widget, newtext):
|
|
||||||
... weak_debug.set_text("Edit widget changed to %s" % newtext)
|
|
||||||
>>> edit = urwid.Edit('')
|
|
||||||
>>> key = urwid.connect_signal(edit, 'change', handler, weak_args=[debug])
|
|
||||||
|
|
||||||
Here the weak_debug parameter in print_debug is the value passed
|
|
||||||
in the weak_args list to connect_signal. Note that the
|
|
||||||
weak_debug value passed is not a weak reference anymore, the
|
|
||||||
signals code transparently dereferences the weakref parameter
|
|
||||||
before passing it to print_debug.
|
|
||||||
|
|
||||||
Returns a key associated by this signal handler, which can be
|
|
||||||
used to disconnect the signal later on using
|
|
||||||
urwid.disconnect_signal_by_key. Alternatively, the signal
|
|
||||||
handler can also be disconnected by calling
|
|
||||||
urwid.disconnect_signal, which doesn't need this key.
|
|
||||||
"""
|
|
||||||
|
|
||||||
sig_cls = obj.__class__
|
|
||||||
if not name in self._supported.get(sig_cls, []):
|
|
||||||
raise NameError("No such signal %r for object %r" %
|
|
||||||
(name, obj))
|
|
||||||
|
|
||||||
# Just generate an arbitrary (but unique) key
|
|
||||||
key = Key()
|
|
||||||
|
|
||||||
signals = setdefaultattr(obj, self._signal_attr, {})
|
|
||||||
handlers = signals.setdefault(name, [])
|
|
||||||
|
|
||||||
# Remove the signal handler when any of the weakref'd arguments
|
|
||||||
# are garbage collected. Note that this means that the handlers
|
|
||||||
# dictionary can be modified _at any time_, so it should never
|
|
||||||
# be iterated directly (e.g. iterate only over .keys() and
|
|
||||||
# .items(), never over .iterkeys(), .iteritems() or the object
|
|
||||||
# itself).
|
|
||||||
# We let the callback keep a weakref to the object as well, to
|
|
||||||
# prevent a circular reference between the handler and the
|
|
||||||
# object (via the weakrefs, which keep strong references to
|
|
||||||
# their callbacks) from existing.
|
|
||||||
obj_weak = weakref.ref(obj)
|
|
||||||
def weakref_callback(weakref):
|
|
||||||
o = obj_weak()
|
|
||||||
if o:
|
|
||||||
try:
|
|
||||||
del getattr(o, self._signal_attr, {})[name][key]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
user_args = self._prepare_user_args(weak_args, user_args, weakref_callback)
|
|
||||||
handlers.append((key, callback, user_arg, user_args))
|
|
||||||
|
|
||||||
return key
|
|
||||||
|
|
||||||
def _prepare_user_args(self, weak_args, user_args, callback = None):
|
|
||||||
# Turn weak_args into weakrefs and prepend them to user_args
|
|
||||||
return [weakref.ref(a, callback) for a in (weak_args or [])] + (user_args or [])
|
|
||||||
|
|
||||||
|
|
||||||
def disconnect(self, obj, name, callback, user_arg=None, weak_args=None, user_args=None):
|
|
||||||
"""
|
|
||||||
:param obj: the object to disconnect the signal from
|
|
||||||
:type obj: object
|
|
||||||
:param name: the signal to disconnect, typically a string
|
|
||||||
:type name: signal name
|
|
||||||
:param callback: the callback function passed to connect_signal
|
|
||||||
:type callback: function
|
|
||||||
:param user_arg: the user_arg parameter passed to connect_signal
|
|
||||||
:param weak_args: the weak_args parameter passed to connect_signal
|
|
||||||
:param user_args: the weak_args parameter passed to connect_signal
|
|
||||||
|
|
||||||
This function will remove a callback from the list connected
|
|
||||||
to a signal with connect_signal(). The arguments passed should
|
|
||||||
be exactly the same as those passed to connect_signal().
|
|
||||||
|
|
||||||
If the callback is not connected or already disconnected, this
|
|
||||||
function will simply do nothing.
|
|
||||||
"""
|
|
||||||
signals = setdefaultattr(obj, self._signal_attr, {})
|
|
||||||
if name not in signals:
|
|
||||||
return
|
|
||||||
|
|
||||||
handlers = signals[name]
|
|
||||||
|
|
||||||
# Do the same processing as in connect, so we can compare the
|
|
||||||
# resulting tuple.
|
|
||||||
user_args = self._prepare_user_args(weak_args, user_args)
|
|
||||||
|
|
||||||
# Remove the given handler
|
|
||||||
for h in handlers:
|
|
||||||
if h[1:] == (callback, user_arg, user_args):
|
|
||||||
return self.disconnect_by_key(obj, name, h[0])
|
|
||||||
|
|
||||||
def disconnect_by_key(self, obj, name, key):
|
|
||||||
"""
|
|
||||||
:param obj: the object to disconnect the signal from
|
|
||||||
:type obj: object
|
|
||||||
:param name: the signal to disconnect, typically a string
|
|
||||||
:type name: signal name
|
|
||||||
:param key: the key for this signal handler, as returned by
|
|
||||||
connect_signal().
|
|
||||||
:type key: Key
|
|
||||||
|
|
||||||
This function will remove a callback from the list connected
|
|
||||||
to a signal with connect_signal(). The key passed should be the
|
|
||||||
value returned by connect_signal().
|
|
||||||
|
|
||||||
If the callback is not connected or already disconnected, this
|
|
||||||
function will simply do nothing.
|
|
||||||
"""
|
|
||||||
signals = setdefaultattr(obj, self._signal_attr, {})
|
|
||||||
handlers = signals.get(name, [])
|
|
||||||
handlers[:] = [h for h in handlers if h[0] is not key]
|
|
||||||
|
|
||||||
def emit(self, obj, name, *args):
|
|
||||||
"""
|
|
||||||
:param obj: the object sending a signal
|
|
||||||
:type obj: object
|
|
||||||
:param name: the signal to send, typically a string
|
|
||||||
:type name: signal name
|
|
||||||
:param \*args: zero or more positional arguments to pass to the signal
|
|
||||||
callback functions
|
|
||||||
|
|
||||||
This function calls each of the callbacks connected to this signal
|
|
||||||
with the args arguments as positional parameters.
|
|
||||||
|
|
||||||
This function returns True if any of the callbacks returned True.
|
|
||||||
"""
|
|
||||||
result = False
|
|
||||||
signals = getattr(obj, self._signal_attr, {})
|
|
||||||
handlers = signals.get(name, [])
|
|
||||||
for key, callback, user_arg, user_args in handlers:
|
|
||||||
result |= self._call_callback(callback, user_arg, user_args, args)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _call_callback(self, callback, user_arg, user_args, emit_args):
|
|
||||||
if user_args:
|
|
||||||
args_to_pass = []
|
|
||||||
for arg in user_args:
|
|
||||||
if isinstance(arg, weakref.ReferenceType):
|
|
||||||
arg = arg()
|
|
||||||
if arg is None:
|
|
||||||
# If the weakref is None, the referenced object
|
|
||||||
# was cleaned up. We just skip the entire
|
|
||||||
# callback in this case. The weakref cleanup
|
|
||||||
# handler will have removed the callback when
|
|
||||||
# this happens, so no need to actually remove
|
|
||||||
# the callback here.
|
|
||||||
return False
|
|
||||||
args_to_pass.append(arg)
|
|
||||||
|
|
||||||
args_to_pass.extend(emit_args)
|
|
||||||
else:
|
|
||||||
# Optimization: Don't create a new list when there are
|
|
||||||
# no user_args
|
|
||||||
args_to_pass = emit_args
|
|
||||||
|
|
||||||
# The deprecated user_arg argument was added to the end
|
|
||||||
# instead of the beginning.
|
|
||||||
if user_arg is not None:
|
|
||||||
args_to_pass = itertools.chain(args_to_pass, (user_arg,))
|
|
||||||
|
|
||||||
return bool(callback(*args_to_pass))
|
|
||||||
|
|
||||||
_signals = Signals()
|
|
||||||
emit_signal = _signals.emit
|
|
||||||
register_signal = _signals.register
|
|
||||||
connect_signal = _signals.connect
|
|
||||||
disconnect_signal = _signals.disconnect
|
|
||||||
disconnect_signal_by_key = _signals.disconnect_by_key
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue