Skip to content

Commit 43071cd

Browse files
authored
Term rework (Gallopsled#2242)
* term rework * towards floating cells * redraw when screen clears * term: working completion * Display non-printable characters * ui: fix options with new term * term: Clean up and fix examples/spinners * changelog and lint * Fix SSH in NOTERM mode * defer termios setup * term: py2 compat * py2: compat fixup * appease pylint * fix scroll on winch * term: fix hang on winch during readline
1 parent a8f4093 commit 43071cd

File tree

10 files changed

+473
-479
lines changed

10 files changed

+473
-479
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@ The table below shows which release corresponds to each branch, and what date th
7070

7171
## 4.13.0 (`dev`)
7272

73+
- [#2242][2242] Term module revamp: activating special handling of terminal only when necessary
7374
- [#2277][2277] elf: Resolve more relocations into GOT entries
7475
- [#2281][2281] FIX: Getting right amount of data for search fix
7576
- [#2293][2293] Add x86 CET status to checksec output
7677
- [#1763][1763] Allow to add to the existing environment in `process` instead of replacing it
7778
- [#2307][2307] Fix `pwn libcdb file` crashing if "/bin/sh" string was not found
7879

80+
[2242]: https://github.com/Gallopsled/pwntools/pull/2242
7981
[2277]: https://github.com/Gallopsled/pwntools/pull/2277
8082
[2281]: https://github.com/Gallopsled/pwntools/pull/2281
8183
[2293]: https://github.com/Gallopsled/pwntools/pull/2293

examples/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
from pwn import *
66

7-
opts = [string.letters[x] for x in range(10)]
7+
opts = [string.ascii_letters[x] for x in range(12)]
88
print('You choose "%s"' % opts[options('Pick one:', opts)])

pwnlib/log.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ def emit(self, record):
560560

561561
# we enrich the `Progress` object to keep track of the spinner
562562
if not hasattr(progress, '_spinner_handle'):
563-
spinner_handle = term.output('')
563+
spinner_handle = term.output('[x] ')
564564
msg_handle = term.output(msg)
565565
stop = threading.Event()
566566
def spin():

pwnlib/py2compat.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""
2+
Compatibility layer with python 2, allowing us to write normal code.
3+
Beware, some monkey-patching is done.
4+
"""
5+
6+
import os
7+
import shutil
8+
import sys
9+
try:
10+
import fcntl
11+
import termios
12+
except ImportError:
13+
pass
14+
15+
from collections import namedtuple
16+
from struct import Struct
17+
18+
def py2_monkey_patch(module):
19+
def decorator(f):
20+
if sys.version_info < (3,):
21+
f.__module__ = module.__name__
22+
setattr(module, f.__name__, f)
23+
return decorator
24+
25+
# python3 -c 'import shutil,inspect; print(inspect.getsource(shutil.get_terminal_size))'
26+
@py2_monkey_patch(shutil)
27+
def get_terminal_size(fallback=(80, 24)):
28+
"""Get the size of the terminal window.
29+
30+
For each of the two dimensions, the environment variable, COLUMNS
31+
and LINES respectively, is checked. If the variable is defined and
32+
the value is a positive integer, it is used.
33+
34+
When COLUMNS or LINES is not defined, which is the common case,
35+
the terminal connected to sys.__stdout__ is queried
36+
by invoking os.get_terminal_size.
37+
38+
If the terminal size cannot be successfully queried, either because
39+
the system doesn't support querying, or because we are not
40+
connected to a terminal, the value given in fallback parameter
41+
is used. Fallback defaults to (80, 24) which is the default
42+
size used by many terminal emulators.
43+
44+
The value returned is a named tuple of type os.terminal_size.
45+
"""
46+
# columns, lines are the working values
47+
try:
48+
columns = int(os.environ['COLUMNS'])
49+
except (KeyError, ValueError):
50+
columns = 0
51+
52+
try:
53+
lines = int(os.environ['LINES'])
54+
except (KeyError, ValueError):
55+
lines = 0
56+
57+
# only query if necessary
58+
if columns <= 0 or lines <= 0:
59+
try:
60+
size = os.get_terminal_size(sys.__stdout__.fileno())
61+
except (AttributeError, ValueError, IOError):
62+
# stdout is None, closed, detached, or not a terminal, or
63+
# os.get_terminal_size() is unsupported
64+
size = os.terminal_size(fallback)
65+
if columns <= 0:
66+
columns = size.columns
67+
if lines <= 0:
68+
lines = size.lines
69+
70+
return os.terminal_size((columns, lines))
71+
72+
@py2_monkey_patch(os)
73+
class terminal_size(tuple):
74+
@property
75+
def columns(self):
76+
return self[0]
77+
78+
@property
79+
def lines(self):
80+
return self[1]
81+
82+
def __repr__(self):
83+
return 'os.terminal_size(columns=%r, lines=%r)' % self
84+
85+
terminal_size = namedtuple('terminal_size', 'columns lines')
86+
87+
termsize = Struct('HHHH')
88+
89+
@py2_monkey_patch(os)
90+
def get_terminal_size(fd): # pylint: disable=function-redefined
91+
arr = b'\0' * termsize.size
92+
arr = fcntl.ioctl(fd, termios.TIOCGWINSZ, arr)
93+
lines, columns, xpixel, ypixel = termsize.unpack(arr)
94+
return os.terminal_size((columns, lines))

pwnlib/term/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
from pwnlib.term import text
1313

1414
# Re-exports (XXX: Are these needed?)
15-
output = term.output
16-
width = term.width
15+
term.update_geometry()
16+
width = term.width
1717
height = term.height
18+
output = term.output
1819
getkey = key.get
1920
Keymap = keymap.Keymap
2021

pwnlib/term/key.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from pwnlib.term import keyconsts as kc
1212
from pwnlib.term import termcap
13+
from pwnlib.term import term
1314

1415
__all__ = ['getch', 'getraw', 'get', 'unget']
1516

@@ -25,7 +26,10 @@ def getch(timeout = 0):
2526
try:
2627
rfds, _wfds, _xfds = select.select([_fd], [], [], timeout)
2728
if rfds:
28-
c = os.read(_fd, 1)
29+
with term.rlock:
30+
rfds, _wfds, _xfds = select.select([_fd], [], [], 0)
31+
if not rfds: continue
32+
c = os.read(_fd, 1)
2933
return ord(c) if c else None
3034
else:
3135
return None

pwnlib/term/readline.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import division
44
from __future__ import print_function
55

6+
import io
67
import six
78
import sys
89

@@ -406,17 +407,20 @@ def readline(_size=-1, prompt='', float=True, priority=10):
406407
history.insert(0, buffer)
407408
return force_to_bytes(buffer)
408409
except KeyboardInterrupt:
409-
control_c()
410+
do_raise = False
411+
try:
412+
control_c()
413+
except KeyboardInterrupt:
414+
do_raise = True
415+
if do_raise:
416+
raise
410417
finally:
411418
line = buffer_left + buffer_right + '\n'
412419
buffer_handle.update(line)
413-
buffer_handle.freeze()
414420
buffer_handle = None
415421
if prompt_handle:
416-
prompt_handle.freeze()
417422
prompt_handle = None
418423
if suggest_handle:
419-
suggest_handle.freeze()
420424
suggest_handle = None
421425
if shutdown_hook:
422426
shutdown_hook()
@@ -484,7 +488,10 @@ class Wrapper:
484488
def __init__(self, fd):
485489
self._fd = fd
486490
def readline(self, size = None):
487-
return readline(size)
491+
r = readline(size)
492+
if isinstance(self._fd, io.TextIOWrapper):
493+
r = r.decode(encoding=self._fd.encoding, errors=self._fd.errors)
494+
return r
488495
def __getattr__(self, k):
489496
return getattr(self._fd, k)
490497
sys.stdin = Wrapper(sys.stdin)

0 commit comments

Comments
 (0)