libTerm 0.0.1__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.
- libTerm/__init__.py +2 -0
- libTerm/term/__init__.py +6 -0
- libTerm/term/cursor.py +123 -0
- libTerm/term/posix.py +183 -0
- libTerm/term/types.py +193 -0
- libTerm/term/winnt.py +90 -0
- libterm-0.0.1.dist-info/METADATA +3 -0
- libterm-0.0.1.dist-info/RECORD +10 -0
- libterm-0.0.1.dist-info/WHEEL +5 -0
- libterm-0.0.1.dist-info/top_level.txt +1 -0
libTerm/__init__.py
ADDED
libTerm/term/__init__.py
ADDED
libTerm/term/cursor.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from collections import namedtuple
|
|
5
|
+
import re
|
|
6
|
+
from libTerm.term.types import Coord
|
|
7
|
+
from time import time_ns
|
|
8
|
+
import sys
|
|
9
|
+
@dataclass()
|
|
10
|
+
class ANSI_Cursor(str, Enum):
|
|
11
|
+
esc = '\x1b'
|
|
12
|
+
q = '6n'
|
|
13
|
+
save = 's'
|
|
14
|
+
load = 'u'
|
|
15
|
+
show = '?25h'
|
|
16
|
+
hide = '?25l'
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return '{ESC}[{CODE}'.format(ESC=self.ESC,CODE=self.value)
|
|
20
|
+
def __repr__(self):
|
|
21
|
+
return repr(self.value)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Cursor():
|
|
25
|
+
def __init__(__s, term):
|
|
26
|
+
super().__init__()
|
|
27
|
+
__s.term = term
|
|
28
|
+
__s.ansi = ANSI_Cursor
|
|
29
|
+
|
|
30
|
+
__s.re = re.compile(r"^.?\x1b\[(?P<Y>\d*);(?P<X>\d*)R", re.VERBOSE)
|
|
31
|
+
__s.position = __s.__update__
|
|
32
|
+
__s._xy=Coord(0,0)
|
|
33
|
+
__s.XY=Coord(0,0)
|
|
34
|
+
__s.history = [*(None,) * 64]
|
|
35
|
+
__s.init = __s.__update__()
|
|
36
|
+
@property
|
|
37
|
+
def xy(__s):
|
|
38
|
+
__s._xy=__s.__update__()
|
|
39
|
+
return __s._xy
|
|
40
|
+
|
|
41
|
+
def __update__(__s, get='XY'):
|
|
42
|
+
def Parser():
|
|
43
|
+
buf = ' '
|
|
44
|
+
while buf[-1] != "R":
|
|
45
|
+
buf += sys.stdin.read(1)
|
|
46
|
+
# reading the actual values, but what if a keystroke appears while reading
|
|
47
|
+
# from stdin? As dirty work around, getpos() returns if this fails: None
|
|
48
|
+
try:
|
|
49
|
+
groups = __s.re.search(buf).groupdict()
|
|
50
|
+
result = Coord(int(groups['X']), int(groups['Y']))
|
|
51
|
+
except AttributeError:
|
|
52
|
+
result = None
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
result = None
|
|
56
|
+
timeout = {}
|
|
57
|
+
timeout['limit'] = 500
|
|
58
|
+
timeout['start'] = time_ns() // 1e6
|
|
59
|
+
timeout['running'] = 0
|
|
60
|
+
while not result:
|
|
61
|
+
result = __s.term.ansi(''.join([__s.ansi.esc,'[', __s.ansi.q]), Parser)
|
|
62
|
+
__s.XY =result
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
def show(__s, state=True):
|
|
66
|
+
if state:
|
|
67
|
+
print('\x1b[?25h', end='', flush=True)
|
|
68
|
+
else:
|
|
69
|
+
__s.hide()
|
|
70
|
+
|
|
71
|
+
def hide(__s, state=True):
|
|
72
|
+
if state:
|
|
73
|
+
print('\x1b[?25l', end='', flush=True)
|
|
74
|
+
atexit.register(__s.show)
|
|
75
|
+
else:
|
|
76
|
+
__s.show()
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def x(__s):
|
|
80
|
+
__s.X=__s.__update__('X')
|
|
81
|
+
return __s.X
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def y(__s):
|
|
85
|
+
__s.Y=__s.__update__('Y')
|
|
86
|
+
return __s.Y
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class vCursor(Cursor):
|
|
90
|
+
def __init__(__s, term,cursor):
|
|
91
|
+
__s.term = term
|
|
92
|
+
__s.realcursor=cursor
|
|
93
|
+
__s.position = Coord(__s.realcursor.x,__s.realcursor.y)
|
|
94
|
+
__s.history = [*(None,) * 64]
|
|
95
|
+
__s.controled = False
|
|
96
|
+
__s.bound = True
|
|
97
|
+
__s.frozen = False
|
|
98
|
+
__s.init = __s.__update__()
|
|
99
|
+
|
|
100
|
+
def freeze(__s, state=True):
|
|
101
|
+
if state:
|
|
102
|
+
__s.frozen = True
|
|
103
|
+
__s.bind(False)
|
|
104
|
+
__s.control(False)
|
|
105
|
+
else:
|
|
106
|
+
__s.frozen = False
|
|
107
|
+
|
|
108
|
+
def __update__(__s, get='XY'):
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
def show(__s, state=True):
|
|
112
|
+
if state:
|
|
113
|
+
print('\x1b[?25h', end='', flush=True)
|
|
114
|
+
else:
|
|
115
|
+
__s.hide()
|
|
116
|
+
|
|
117
|
+
def hide(__s, state=True):
|
|
118
|
+
if state:
|
|
119
|
+
print('\x1b[?25l', end='', flush=True)
|
|
120
|
+
atexit.register(__s.show)
|
|
121
|
+
else:
|
|
122
|
+
__s.show()
|
|
123
|
+
|
libTerm/term/posix.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
import os
|
|
3
|
+
import termios
|
|
4
|
+
import tty
|
|
5
|
+
import atexit
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from time import time_ns
|
|
9
|
+
from shutil import get_terminal_size
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from collections import namedtuple
|
|
13
|
+
from libTerm.term.types import Coord,color, Size,Colors
|
|
14
|
+
from libTerm.term.cursor import Cursor, vCursor
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Indices for termios list.
|
|
19
|
+
IFLAG = 0
|
|
20
|
+
OFLAG = 1
|
|
21
|
+
CFLAG = 2
|
|
22
|
+
LFLAG = 3
|
|
23
|
+
ISPEED = 4
|
|
24
|
+
OSPEED = 5
|
|
25
|
+
CC = 6
|
|
26
|
+
TCSAFLUSH = termios.TCSAFLUSH
|
|
27
|
+
ECHO = termios.ECHO
|
|
28
|
+
ICANON = termios.ICANON
|
|
29
|
+
|
|
30
|
+
VMIN = 6
|
|
31
|
+
VTIME = 5
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TermAttrs():
|
|
35
|
+
def __init__(s):
|
|
36
|
+
s.stack=[]
|
|
37
|
+
s.init=None
|
|
38
|
+
s.staged=None
|
|
39
|
+
s.active=None
|
|
40
|
+
def stage(s):
|
|
41
|
+
s.staged=list(s.active)
|
|
42
|
+
def update(s,new=None):
|
|
43
|
+
if new is None:
|
|
44
|
+
new=s.staged
|
|
45
|
+
s.stack+=[list(s.active)]
|
|
46
|
+
s.active=new
|
|
47
|
+
s.staged=None
|
|
48
|
+
def restore(s):
|
|
49
|
+
if s.stack:
|
|
50
|
+
s.staged=s.stack.pop()
|
|
51
|
+
return s.staged
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Term():
|
|
56
|
+
def __init__(s,*a,**k):
|
|
57
|
+
# super().__init__()
|
|
58
|
+
s.pid = os.getpid()
|
|
59
|
+
s.ppid = os.getpid()
|
|
60
|
+
|
|
61
|
+
s.fd = sys.stdin.fileno()
|
|
62
|
+
s.tty = os.ttyname(s.fd)
|
|
63
|
+
|
|
64
|
+
s.TCSAFLUSH = termios.TCSAFLUSH
|
|
65
|
+
s.ECHO = termios.ECHO
|
|
66
|
+
s.ICANON = termios.ICANON
|
|
67
|
+
s.TCSANOW = termios.TCSANOW
|
|
68
|
+
|
|
69
|
+
s.attrs = TermAttrs()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
s.attrs.active = s.tcgetattr()
|
|
73
|
+
s.attrs.init = list([*s.attrs.active])
|
|
74
|
+
s.attrs.stack += [list(s.attrs.active)]
|
|
75
|
+
|
|
76
|
+
s._mode = 0
|
|
77
|
+
s.mode = s.__mode__
|
|
78
|
+
atexit.register(s.mode,'normal')
|
|
79
|
+
s.cursor = Cursor(s)
|
|
80
|
+
s.vcursors = {0:vCursor(s,s.cursor)}
|
|
81
|
+
s.size = Size(parent=s)
|
|
82
|
+
s.color = Colors(parent=s)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def tcgetattr(s):
|
|
86
|
+
return termios.tcgetattr(s.fd)
|
|
87
|
+
|
|
88
|
+
def tcsetattr(s,attr,when=TCSAFLUSH):
|
|
89
|
+
termios.tcsetattr(s.fd,when,attr)
|
|
90
|
+
|
|
91
|
+
def setraw(s, when=TCSAFLUSH):
|
|
92
|
+
"""Put terminal into raw mode."""
|
|
93
|
+
from termios import IGNBRK,BRKINT,IGNPAR,PARMRK,INPCK,ISTRIP,INLCR,IGNCR,ICRNL,IXON,IXANY,IXOFF,OPOST,PARENB,CSIZE,CS8,ECHO,ECHOE,ECHOK,ECHONL,ICANON,IEXTEN,ISIG,NOFLSH,TOSTOP
|
|
94
|
+
s.attrs.stage()
|
|
95
|
+
# Clear all POSIX.1-2017 input mode flags.
|
|
96
|
+
# See chapter 11 "General Terminal Interface"
|
|
97
|
+
# of POSIX.1-2017 Base Definitions.
|
|
98
|
+
s.attrs.staged[IFLAG] &= ~( IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON
|
|
99
|
+
| IXANY | IXOFF)
|
|
100
|
+
# Do not post-process output.
|
|
101
|
+
s.attrs.staged[OFLAG] &= ~OPOST
|
|
102
|
+
# Disable parity generation and detection; clear character size mask;
|
|
103
|
+
# let character size be 8 bits.
|
|
104
|
+
s.attrs.staged[CFLAG] &= ~(PARENB | CSIZE)
|
|
105
|
+
s.attrs.staged[CFLAG] |= CS8
|
|
106
|
+
# Clear all POSIX.1-2017 local mode flags.
|
|
107
|
+
s.attrs.staged[LFLAG] &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN | ISIG | NOFLSH | TOSTOP)
|
|
108
|
+
# POSIX.1-2017, 11.1.7 Non-Canonical Mode Input Processing,
|
|
109
|
+
# Case B: MIN>0, TIME=0
|
|
110
|
+
# A pending read shall block until MIN (here 1) bytes are received,
|
|
111
|
+
# or a signal is received.
|
|
112
|
+
s.attrs.staged[CC] = list(s.attr.staged[CC])
|
|
113
|
+
s.attrs.staged[CC][VMIN] = 1
|
|
114
|
+
s.attrs.staged[CC][VTIME] = 0
|
|
115
|
+
s.update(when)
|
|
116
|
+
|
|
117
|
+
def setcbreak(s,when=TCSAFLUSH):
|
|
118
|
+
"""Put terminal into cbreak mode."""
|
|
119
|
+
# this code was lifted from the tty module and adapted for being a method
|
|
120
|
+
s.attrs.stage()
|
|
121
|
+
# Do not echo characters; disable canonical input.
|
|
122
|
+
s.attrs.staged[LFLAG] &= ~(ECHO | ICANON)
|
|
123
|
+
# POSIX.1-2017, 11.1.7 Non-Canonical Mode Input Processing,
|
|
124
|
+
# Case B: MIN>0, TIME=0
|
|
125
|
+
# A pending read shall block until MIN (here 1) bytes are received,
|
|
126
|
+
# or a signal is received.
|
|
127
|
+
s.attrs.staged[CC] = list(s.attrs.staged[CC])
|
|
128
|
+
s.attrs.staged[CC][VMIN] = 1
|
|
129
|
+
s.attrs.staged[CC][VTIME] = 0
|
|
130
|
+
s.update(when)
|
|
131
|
+
|
|
132
|
+
def echo(s,enable=False):
|
|
133
|
+
s.attrs.stage()
|
|
134
|
+
s.attrs.staged[3] &= ~s.ECHO
|
|
135
|
+
if enable:
|
|
136
|
+
s.attrs.staged[3] |= s.ECHO
|
|
137
|
+
s.update()
|
|
138
|
+
|
|
139
|
+
def canonical(s,enable):
|
|
140
|
+
s.attrs.stage()
|
|
141
|
+
s.attrs.staged[3] &= ~s.ICANON
|
|
142
|
+
if enable:
|
|
143
|
+
s.attrs.staged[3] |= s.ICANON
|
|
144
|
+
s.update()
|
|
145
|
+
|
|
146
|
+
def __mode__(s,mode=None):
|
|
147
|
+
def Normal():
|
|
148
|
+
# s.cursor.show(True)
|
|
149
|
+
s.echo(True)
|
|
150
|
+
s.canonical(True)
|
|
151
|
+
s.tcsetattr(s.attrs.init)
|
|
152
|
+
s._mode = nmodi.get('normal')
|
|
153
|
+
|
|
154
|
+
def Ctl():
|
|
155
|
+
# s.cursor.show(False)
|
|
156
|
+
s.echo(False)
|
|
157
|
+
s.canonical(False)
|
|
158
|
+
s._mode = nmodi.get('ctl')
|
|
159
|
+
|
|
160
|
+
nmodi={'normal' : 1,'ctl': 2 }
|
|
161
|
+
fmodi = {
|
|
162
|
+
1 : Normal,
|
|
163
|
+
2 : Ctl,
|
|
164
|
+
}
|
|
165
|
+
if mode is not None and mode != s._mode:
|
|
166
|
+
nmode=nmodi.get(mode)
|
|
167
|
+
fmodi.get(nmode)()
|
|
168
|
+
return s._mode
|
|
169
|
+
|
|
170
|
+
def update(s,when=TCSAFLUSH):
|
|
171
|
+
s.tcsetattr( s.attrs.staged,when)
|
|
172
|
+
s.attrs.update(s.tcgetattr())
|
|
173
|
+
|
|
174
|
+
def ansi(s, ansi, parser):
|
|
175
|
+
s.setcbreak()
|
|
176
|
+
try:
|
|
177
|
+
sys.stdout.write(ansi)
|
|
178
|
+
sys.stdout.flush()
|
|
179
|
+
result = parser()
|
|
180
|
+
finally:
|
|
181
|
+
s.tcsetattr(s.attrs.restore())
|
|
182
|
+
return result
|
|
183
|
+
#
|
libTerm/term/types.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from dataclasses import dataclass,field
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
from os import get_terminal_size
|
|
4
|
+
from time import sleep, time_ns
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass()
|
|
9
|
+
class Coord(namedtuple('Coord', ['x', 'y'])):
|
|
10
|
+
__module__ = None
|
|
11
|
+
__qualname__='Coord'
|
|
12
|
+
_x: int = field(default=0)
|
|
13
|
+
_y: int = field(default=0)
|
|
14
|
+
|
|
15
|
+
def __str__(__s):
|
|
16
|
+
return f'\x1b[{__s.y + 1};{__s.x + 1}H'
|
|
17
|
+
|
|
18
|
+
def __repr__(s):
|
|
19
|
+
return f"{s.__class__.__name__}({s.x}, {s.y})"
|
|
20
|
+
|
|
21
|
+
def __len__(self):
|
|
22
|
+
return 2
|
|
23
|
+
def __iter__(self):
|
|
24
|
+
yield self.x
|
|
25
|
+
yield self.y
|
|
26
|
+
def __getitem__(s, index):
|
|
27
|
+
if 0 > index > 2:
|
|
28
|
+
raise IndexError("numberpair index out of range")
|
|
29
|
+
return (
|
|
30
|
+
((index == 0)*s.x)+
|
|
31
|
+
((index == 1)*s.y))
|
|
32
|
+
def __add__(s, other):
|
|
33
|
+
if isinstance(other,Coord2D):
|
|
34
|
+
x=s.x+other.x
|
|
35
|
+
y=s.y+other.y
|
|
36
|
+
return Coord2D(x,y)
|
|
37
|
+
elif isinstance(other,complex):
|
|
38
|
+
x=s.x+other.real
|
|
39
|
+
y=s.y+other.imag
|
|
40
|
+
return Coord2D(x,y)
|
|
41
|
+
elif isinstance(other,str):
|
|
42
|
+
return f'{s.__str__()}{other}'
|
|
43
|
+
else:
|
|
44
|
+
raise TypeError(f"cannot add {type(s)} to {type(other)}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def xy(s) -> tuple[int, int]:
|
|
50
|
+
return (s.x, s.y)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def y(s):
|
|
54
|
+
return s._y
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def x(s):
|
|
58
|
+
return s._x
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class color:
|
|
63
|
+
R: int = field(default=0, metadata={"range": (0, 65535)})
|
|
64
|
+
G: int = field(default=0, metadata={"range": (0, 65535)})
|
|
65
|
+
B: int = field(default=0, metadata={"range": (0, 65535)})
|
|
66
|
+
BIT: int = field(default=8, metadata={"set": (4, 8, 16, 32)})
|
|
67
|
+
|
|
68
|
+
def __post_init__(self):
|
|
69
|
+
for attr_name in ("R", "G", "B"):
|
|
70
|
+
value = getattr(self, attr_name)
|
|
71
|
+
if not isinstance(value, int):
|
|
72
|
+
raise ValueError(f"{attr_name.upper()} must be an integer between 0 and 65535. Got {value}.")
|
|
73
|
+
if not isinstance(getattr(self, "BIT"), int):
|
|
74
|
+
raise ValueError(f"{attr_name.upper()} must be one of 4,8,16,32. Got {value}.")
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def RGB(self) -> tuple[int, int, int]:
|
|
78
|
+
return (self.R, self.G, self.B)
|
|
79
|
+
|
|
80
|
+
# @dataclass()
|
|
81
|
+
# class TermCo(namedtuple('Co',['x','y'])):
|
|
82
|
+
# x:int=field(default=0)
|
|
83
|
+
# y:int=field(default=0)
|
|
84
|
+
# class Line(namedtuple('Line',['a','b'])):
|
|
85
|
+
# a:Co=field(default_factory=Co)
|
|
86
|
+
# b:Co=field(default_factory=Co)
|
|
87
|
+
# @classmethod
|
|
88
|
+
# def __add__(s, o):
|
|
89
|
+
# if isinstance(o,Line):
|
|
90
|
+
# if len(s.a)!=0 and len(s.b)!=0:
|
|
91
|
+
# lenx=abs(s.b.x-s.a.x)+abs(o.b.x-o.a.x)
|
|
92
|
+
# leny=abs(s.b.y-s.a.y)+abs(o.b.y-o.a.y)
|
|
93
|
+
# L=Line(s.a,Co(s.a.x+lenx,s.a.y+leny))
|
|
94
|
+
# else:
|
|
95
|
+
# return 0
|
|
96
|
+
#
|
|
97
|
+
# def __len__(s):
|
|
98
|
+
# if len(s.a) != 0 and len(s.b) != 0:
|
|
99
|
+
# lenx = abs(s.b.x - s.a.x) + abs(o.b.x - o.a.x)
|
|
100
|
+
# leny = abs(s.b.y - s.a.y) + abs(o.b.y - o.a.y)
|
|
101
|
+
# return ((lenx**2+leny**2)**(1/2))
|
|
102
|
+
|
|
103
|
+
class Size():
|
|
104
|
+
def __init__(__s, **k):
|
|
105
|
+
__s.parent = k.get('parent')
|
|
106
|
+
__s.getsize = get_terminal_size
|
|
107
|
+
__s.time = None
|
|
108
|
+
__s.last = None
|
|
109
|
+
__s.xy = Coord(1, 1)
|
|
110
|
+
__s._tmp = Coord(1, 1)
|
|
111
|
+
__s.rows = 1
|
|
112
|
+
__s.cols = 1
|
|
113
|
+
|
|
114
|
+
__s.history = []
|
|
115
|
+
__s.changed = False
|
|
116
|
+
__s.changing = False
|
|
117
|
+
|
|
118
|
+
__s.__kwargs__(**k)
|
|
119
|
+
__s.__update__()
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def width(__s):
|
|
123
|
+
__s.__update__()
|
|
124
|
+
return __s.cols
|
|
125
|
+
@property
|
|
126
|
+
def height(__s):
|
|
127
|
+
__s.__update__()
|
|
128
|
+
return __s.rows
|
|
129
|
+
@property
|
|
130
|
+
def rc(__s):
|
|
131
|
+
__s.__update__()
|
|
132
|
+
return (__s.cols, __s.rows)
|
|
133
|
+
|
|
134
|
+
def __kwargs__(__s, **k):
|
|
135
|
+
__s.term = k.get('parent')
|
|
136
|
+
|
|
137
|
+
def __update__(__s):
|
|
138
|
+
if __s.time is None:
|
|
139
|
+
__s.last = time_ns()
|
|
140
|
+
size = Coord(*__s.getsize())
|
|
141
|
+
if size != __s.xy:
|
|
142
|
+
if size != __s._tmp:
|
|
143
|
+
__s.changing = True
|
|
144
|
+
__s._tmp = size
|
|
145
|
+
__s._tmptime = time_ns()
|
|
146
|
+
if size == __s._tmp:
|
|
147
|
+
if (time_ns() - __s._tmptime) * 1e6 > 500:
|
|
148
|
+
__s.changing = False
|
|
149
|
+
__s.changed = True
|
|
150
|
+
__s.history += [__s.xy]
|
|
151
|
+
__s.xy = size
|
|
152
|
+
__s.rows = __s.xy.y
|
|
153
|
+
__s.cols = __s.xy.x
|
|
154
|
+
else:
|
|
155
|
+
__s._tmp = size
|
|
156
|
+
if size == __s.xy:
|
|
157
|
+
__s.changed = False
|
|
158
|
+
|
|
159
|
+
class Colors():
|
|
160
|
+
def __init__(__s, **k):
|
|
161
|
+
__s.parent = None
|
|
162
|
+
__s.specs = {'fg': 10, 'bg': 11}
|
|
163
|
+
__s._ansi = '\x1b]{spec};?\a'
|
|
164
|
+
__s.__kwargs__(**k)
|
|
165
|
+
__s.fg = color(255, 255, 255)
|
|
166
|
+
__s.bg = color(0, 0, 0)
|
|
167
|
+
__s.init = __s.__update__()
|
|
168
|
+
|
|
169
|
+
def __kwargs__(__s, **k):
|
|
170
|
+
__s.term = k.get('parent')
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _ansiparser_():
|
|
174
|
+
buf = ''
|
|
175
|
+
try:
|
|
176
|
+
for i in range(23):
|
|
177
|
+
buf += sys.stdin.read(1)
|
|
178
|
+
rgb = buf.split(':')[1].split('/')
|
|
179
|
+
rgb = [int(i, base=16) for i in rgb]
|
|
180
|
+
rgb = color(*rgb, 16)
|
|
181
|
+
except Exception as E:
|
|
182
|
+
# print(E)
|
|
183
|
+
rgb = None
|
|
184
|
+
return rgb
|
|
185
|
+
|
|
186
|
+
def __update__(__s):
|
|
187
|
+
for ground in __s.specs:
|
|
188
|
+
result = None
|
|
189
|
+
while not result:
|
|
190
|
+
result = __s.term.ansi(__s._ansi.format(spec=__s.specs[ground]), __s._ansiparser_)
|
|
191
|
+
__s.__setattr__(ground, result)
|
|
192
|
+
|
|
193
|
+
return {'fg': __s.fg, 'bg': __s.bg}
|
libTerm/term/winnt.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import ctypes
|
|
3
|
+
import sys
|
|
4
|
+
import msvcrt
|
|
5
|
+
import atexit
|
|
6
|
+
import struct
|
|
7
|
+
from libTerm.term.types import color
|
|
8
|
+
from libTerm.term.types import Size
|
|
9
|
+
|
|
10
|
+
class Colors:
|
|
11
|
+
def __init__(s,**k):
|
|
12
|
+
s.parent = k.get('parent')
|
|
13
|
+
s.fg = color(255, 255, 255)
|
|
14
|
+
s.bg = color(0, 0, 0)
|
|
15
|
+
s.refresh()
|
|
16
|
+
def refresh(s):
|
|
17
|
+
# Windows API constants
|
|
18
|
+
STD_OUTPUT_HANDLE = -11
|
|
19
|
+
handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
|
|
20
|
+
csbi = ctypes.create_string_buffer(22)
|
|
21
|
+
res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(handle, csbi)
|
|
22
|
+
if res:
|
|
23
|
+
# Unpack the color attribute as a WORD (2 bytes) at offset 4
|
|
24
|
+
attr = struct.unpack('<H', csbi.raw[4:6])[0]
|
|
25
|
+
s.fg = attr & 0x0F
|
|
26
|
+
s.bg = (attr & 0xF0) >> 4
|
|
27
|
+
s.foreground = s.fg
|
|
28
|
+
s.background = s.bg
|
|
29
|
+
else:
|
|
30
|
+
s.fg = None
|
|
31
|
+
s.bg = None
|
|
32
|
+
s.foreground = None
|
|
33
|
+
s.background = None
|
|
34
|
+
|
|
35
|
+
class Term:
|
|
36
|
+
def __init__(s, *a, **k):
|
|
37
|
+
s.pid = None # Not relevant for Windows terminal
|
|
38
|
+
s.ppid = None
|
|
39
|
+
s.fd = None
|
|
40
|
+
s.tty = None
|
|
41
|
+
s.attrs = None
|
|
42
|
+
s._mode = 0
|
|
43
|
+
s.mode = s.__mode__
|
|
44
|
+
atexit.register(s.mode, 'normal')
|
|
45
|
+
s.cursor = None
|
|
46
|
+
s.vcursors = None
|
|
47
|
+
s.size = Size(parent=s) # Reflects current terminal size
|
|
48
|
+
s.color = Colors(parent=s) # Reflects current terminal colors
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def getch(s):
|
|
52
|
+
"""Read a single character from the terminal."""
|
|
53
|
+
return msvcrt.getch().decode('utf-8', errors='ignore')
|
|
54
|
+
|
|
55
|
+
def kbhit(s):
|
|
56
|
+
"""Check if a keypress is available."""
|
|
57
|
+
return msvcrt.kbhit()
|
|
58
|
+
|
|
59
|
+
def __mode__(s, mode=None):
|
|
60
|
+
nmodi = {'normal': 1, 'ctl': 2}
|
|
61
|
+
if mode is not None and mode != s._mode:
|
|
62
|
+
s._mode = nmodi.get(mode)
|
|
63
|
+
return s._mode
|
|
64
|
+
|
|
65
|
+
def ansi(s, ansi, parser):
|
|
66
|
+
# Parse ANSI color code for foreground (e.g., \x1b[32m)
|
|
67
|
+
import re
|
|
68
|
+
match = re.search(r'\x1b\[(3[0-7])m', ansi)
|
|
69
|
+
if match:
|
|
70
|
+
s.last_ansi_fg = int(match.group(1)[1:]) # 30-37 -> 0-7
|
|
71
|
+
sys.stdout.write(ansi)
|
|
72
|
+
sys.stdout.flush()
|
|
73
|
+
return parser()
|
|
74
|
+
|
|
75
|
+
def refresh(s):
|
|
76
|
+
"""Refresh the size and color settings."""
|
|
77
|
+
s.size.refresh()
|
|
78
|
+
s.color.refresh()
|
|
79
|
+
|
|
80
|
+
# Stub methods for compatibility
|
|
81
|
+
def setraw(s, when=None):
|
|
82
|
+
pass
|
|
83
|
+
def setcbreak(s, when=None):
|
|
84
|
+
pass
|
|
85
|
+
def echo(s, enable=False):
|
|
86
|
+
pass
|
|
87
|
+
def canonical(s, enable):
|
|
88
|
+
pass
|
|
89
|
+
def update(s, when=None):
|
|
90
|
+
pass
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
libTerm/__init__.py,sha256=XykOeLAxMgxFieSsHqzMB0Qa4izGjCqXPZYubTXPvtc,33
|
|
2
|
+
libTerm/term/__init__.py,sha256=bstJSmazmFJ5ZGDzx35UyeWfLwcEN9pmHtN42c4MeNI,131
|
|
3
|
+
libTerm/term/cursor.py,sha256=qIkS2Nk4Ky6Ab-RInZ9Rqd0h22VJH_brKQscCVwhq_g,2691
|
|
4
|
+
libTerm/term/posix.py,sha256=c4k3FycHxbKCK-PtGzylUDX8qTtffx3qHE9_Zd_UTFc,4544
|
|
5
|
+
libTerm/term/types.py,sha256=-IfgTCCRQxchS6IN7iBmFAoNKHny48YWxDhmyVvVBJk,4694
|
|
6
|
+
libTerm/term/winnt.py,sha256=pMAUKr1BAqFWm8Dct_mp1cvbxISQQi3JBlRfbjEzF48,2528
|
|
7
|
+
libterm-0.0.1.dist-info/METADATA,sha256=7NuroGFhL3AWgyBlj7ojoMG5u4R-1LITMmNnOuVUolY,51
|
|
8
|
+
libterm-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
libterm-0.0.1.dist-info/top_level.txt,sha256=pKN_iVl3hsXjz-Dqvni3ur7y7mdgHZrJ3OI7fBbH-lE,8
|
|
10
|
+
libterm-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
libTerm
|