forked from gosella/autopython
/
console.py
206 lines (170 loc) · 6.22 KB
/
console.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Mostly refactored from:
# URL: https://bitbucket.org/techtonik/python-pager
# Author: anatoly techtonik <techtonik@gmail.com>
# License: Public Domain (use MIT if the former doesn't work for you)
import os
import sys
# Dealing with the terminal in different OSs
if os.name == 'nt':
# Windows constants
# http://msdn.microsoft.com/en-us/library/ms683231%28v=VS.85%29.aspx
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
# get console handle
from ctypes import windll, Structure, byref
try:
from ctypes.wintypes import SHORT, WORD, DWORD
except ImportError:
from ctypes import c_short as SHORT, c_ushort as WORD, c_ulong as DWORD
console_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
# CONSOLE_SCREEN_BUFFER_INFO Structure
class COORD(Structure):
_fields_ = [("X", SHORT), ("Y", SHORT)]
class SMALL_RECT(Structure):
_fields_ = [("Left", SHORT), ("Top", SHORT),
("Right", SHORT), ("Bottom", SHORT)]
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
_fields_ = [("dwSize", COORD),
("dwCursorPosition", COORD),
("wAttributes", WORD),
("srWindow", SMALL_RECT),
("dwMaximumWindowSize", DWORD)]
def _get_window_size():
"""Return (width, height) of available window area on Windows.
(0, 0) if no console is allocated.
"""
sbi = CONSOLE_SCREEN_BUFFER_INFO()
ret = windll.kernel32.GetConsoleScreenBufferInfo(console_handle, byref(sbi))
if ret == 0:
return (0, 0)
return (sbi.srWindow.Right - sbi.srWindow.Left + 1,
sbi.srWindow.Bottom - sbi.srWindow.Top + 1)
import msvcrt
def _getch():
a = msvcrt.getwch()
if a == '\x00' or a == '\xe0':
b = msvcrt.getwch()
return [a, b]
else:
return a
def enable_echo():
pass
def disable_echo():
pass
ESC = '\x1b'
ENTER = '\x0d'
LEFT = ['\xe0', 'K']
UP = ['\xe0', 'H']
RIGHT = ['\xe0', 'M']
DOWN = ['\xe0', 'P']
PGUP = ['\xe0', 'I']
PGDN = ['\xe0', 'Q']
elif os.name == 'posix':
from fcntl import ioctl
from termios import TIOCGWINSZ
from array import array
def _get_window_size():
"""Return (width, height) of console terminal on POSIX system.
(0, 0) on IOError, i.e. when no console is allocated.
"""
# see README.txt for reference information
# http://www.kernel.org/doc/man-pages/online/pages/man4/tty_ioctl.4.html
"""
struct winsize {
unsigned short ws_row;
unsigned short ws_col;
unsigned short ws_xpixel; /* unused */
unsigned short ws_ypixel; /* unused */
};
"""
winsize = array("H", [0] * 4)
try:
ioctl(sys.stdout.fileno(), TIOCGWINSZ, winsize)
except IOError:
# for example IOError: [Errno 25] Inappropriate ioctl for device
# when output is redirected
# [ ] TODO: check fd with os.isatty
pass
return (winsize[1], winsize[0])
import tty
import termios
def _getch():
fd = sys.stdin.fileno()
# save old terminal settings, because we are changing them
old_settings = termios.tcgetattr(fd)
try:
# set terminal to "cbreak" mode, in which driver returns
# one char at a time instead of one line at a time
#
# tty.setcbreak() is just a helper for tcsetattr() call, see
# http://hg.python.org/cpython/file/c6880edaf6f3/Lib/tty.py
tty.setcbreak(fd)
ch = sys.stdin.read(1)
# clear input buffer placing all available chars into morech
newattr = termios.tcgetattr(fd) # change terminal settings
# to allow non-blocking read
newattr[6][termios.VMIN] = 0 # CC structure
newattr[6][termios.VTIME] = 0
termios.tcsetattr(fd, termios.TCSANOW, newattr)
morech = []
while True:
ch2 = sys.stdin.read(1)
if ch2 == '':
break
morech.append(ch2)
finally:
# restore terminal settings. Do this when all output is
# finished - TCSADRAIN flag
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
if morech:
morech.insert(0, ch)
ch = morech
return ch
def enable_echo():
fd = sys.stdin.fileno()
attrs = termios.tcgetattr(fd)
attrs[3] = attrs[3] | termios.ECHO
termios.tcsetattr(fd, termios.TCSADRAIN, attrs)
def disable_echo():
fd = sys.stdin.fileno()
attrs = termios.tcgetattr(fd)
attrs[3] = attrs[3] & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSADRAIN, attrs)
ESC = '\x1b'
ENTER = '\n'
LEFT = [ESC, '[', 'D']
UP = [ESC, '[', 'A']
RIGHT = [ESC, '[', 'C']
DOWN = [ESC, '[', 'B']
PGUP = [ESC, '[', '5', '~']
PGDN = [ESC, '[', '6', '~']
else:
# 'mac', 'os2', 'ce', 'java' need implementations
raise ImportError("platform not supported")
def getwidth():
"""
Return width of available window in characters. If detection fails,
return value of standard width 80. Coordinate of the last character
on a line is -1 from returned value.
Windows part uses console API through ctypes module.
*nix part uses termios ioctl TIOCGWINSZ call.
"""
return _get_window_size()[0] or 80
def getheight():
"""
Return available window height in characters or 25 if detection fails.
Coordinate of the last line is -1 from returned value.
Windows part uses console API through ctypes module.
*nix part uses termios ioctl TIOCGWINSZ call.
"""
return _get_window_size()[1] or 25
def getch():
"""
Wait for keypress, return character or a list of characters.
Arrows and special keys generate a sequence of characters, so if there are
extra symbols in input buffer, this function returns list.
"""
return _getch()