Skip to content

Instantly share code, notes, and snippets.

@j4james
Last active October 30, 2022 09:02
Show Gist options
  • Save j4james/f8f3fb5ecd066dfc722dd98fec7a5742 to your computer and use it in GitHub Desktop.
Save j4james/f8f3fb5ecd066dfc722dd98fec7a5742 to your computer and use it in GitHub Desktop.
Testing rectangular area operations
'''
Rectangular Area Operations
This is a collection of test cases for the DEC rectangular area operations.
For each test, the top half of the screen shows the expected output, while the
bottom half shows the actual output.
NB: The expected output is based on the documentation in the DEC STD-070 manual
but has not yet been confirmed on any of the actual DEC terminals.
'''
import sys
selected_test = sys.argv[1] if len(sys.argv) > 1 else None
test_attrs = '1;4;7;33;41'
def write(s):
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.write(s.encode('latin1'))
else:
sys.stdout.write(s)
def fill(x,y,w,h,c,attrs = ''):
if attrs != None: write('\033[%sm' % attrs)
for i in range(h):
write('\033[%d;%dH' % (y+i,x))
write(c * w)
def decaln():
write('\033[m\033[H\033#8')
class TestGroup:
group_number = 0
def __init__(self, name, test_setup = None):
TestGroup.group_number += 1
self.group_number = TestGroup.group_number
self.group_name = name
self.test_setup = test_setup
self.test_number = 0
self.active_test = None
self.double_width_lines = None
def __del__(self):
self.end_last_test()
write('\033[m\033[H\033[J')
def new_test(self, name):
self.test_number += 1
self.test_id = '%d.%d' % (self.group_number, self.test_number)
if not self.is_selected(): return False
self.end_last_test()
self.test_setup()
self.active_test = '%s %s: %s' % (self.test_id, self.group_name, name)
return True
def reset_double_width(self, *lines):
self.double_width_lines = lines
def is_selected(self):
if selected_test == None: return True
if selected_test == self.test_id: return True
return self.test_id.startswith(selected_test+'.')
def end_last_test(self):
if self.active_test:
write('\033[m\033[H')
write('%s\033[K' % self.active_test)
write('\033[999C\033[7DEXPECTED')
write('\033[13H')
write('%s\033[K' % self.active_test)
write('\033[999C\033[5DACTUAL')
write('\r')
sys.stdout.flush()
sys.stdin.readline()
if self.double_width_lines:
for row in self.double_width_lines:
write('\033[%dH\033#5' % row)
self.double_width_lines = None
self.active_test = None
def test_decfra():
def test_setup():
decaln()
write('\033[%sm' % test_attrs)
g = TestGroup('DECFRA', test_setup)
if g.new_test('Basic functionality'):
'''
STD070: When this control is received, the terminal fills rectangular area
of character positions defined by Pt, Pl, Pb, and Pr, replacing both the
character and the rendition present in those character positions with the
character defined by the decimal value of Pch and the current renditions
set through the SGR command.
'''
write('\033[42;16;27;22;54$x')
# Expected output
fill(27, 4, 28, 7, '*', test_attrs)
if g.new_test('Default parameters'):
'''
STD070: Default values are Pch = 32, Pt = 1, Pb = last-line-of-page, PI = 1,
and Pr = last-column-of-page.
'''
write('\033[;16;1;19;80$x')
write('\033[32;20$x')
# Expected output
fill(1, 4, 80, 9, ' ', test_attrs)
if g.new_test('Invalid parameters'):
'''
STD070: The Pch parameter may be a decimal value from 32 to 126, or from 160
160 to 255. If the value of Pch is not in the above range, the command is
ignored. Pt must be less or equal to Pb, and Pl must be less than or equal
to Pr, otherwise the entire control function is ignored.
'''
write('\033[42;16;80;22;71$x') # Ignored, Pl > Pr
write('\033[42;22;1;16;10$x') # Ignored, Pt > Pb
test_chars = [31,32,126,0,160,255,145]
for i,ch in enumerate(test_chars):
write('\033[%d;%d;27;%d;54$x' % (ch,i+16,i+16))
# Expected output
for i,ch in enumerate(test_chars):
if ch not in [31,145]: # 31 and 145 ignored
if ch == 0: ch = 32 # 0 is the same as default (32)
fill(27, 4+i, 28, 1, chr(ch), test_attrs)
if g.new_test('Origin mode'):
'''
STD070: The coordinates of the rectangular area are affected by the setting
of Origin Mode. This control is not otherwise affected by the margins.
'''
write('\033[17;20r') # Margins 17 to 20
write('\033[?6h') # Origin Mode enabled
write('\033[65;6;33;8;48$x') # Clamped
write('\033[66;2;36;6;45$x') # Clipped
write('\033[?6l') # Origin Mode reset
write('\033[67;16;39;22;42$x') # Not Clipped
write('\033[r') # Margins reset
# Expected output
fill(39, 4, 4, 2, 'C', test_attrs)
fill(36, 6, 1, 2, 'BBBCCCCBBB', test_attrs)
fill(33, 8, 1, 1, 'AAABBBCCCCBBBAAA', test_attrs)
fill(39, 9, 4, 2, 'C', test_attrs)
if g.new_test('Double-width/height lines'):
'''
STD070: If the rectangular area contains both single and double width lines,
the area affected may not appear rectangular on the display.
'''
write('\033[16H\033#3')
write('\033[17H\033#4')
write('\033[18H\033#6')
write('\033[42;16;27;22;54$x')
# Expected output
fill(27, 4, 28, 7, '*', test_attrs)
write('\033[4H\033#3')
write('\033[5H\033#4')
write('\033[6H\033#6')
g.reset_double_width(4,5,6,16,17,18)
if g.new_test('Overflowing page boundary'):
'''
STD070: If a value exceeds the width or height of the active page, it is
treated as the width or height of the active page.
'''
write('\033[42;19;41;9999;9999$x')
# Expected output
fill(41, 7, 40, 6, '*', test_attrs)
if g.new_test('Character set translation'):
'''
STD070: The decimal value refers to the character in the current GL and GR
"in-use" character table, which is used to fill the specified rectangular
area.
'''
write('\033(0')
write('\033[110;16;27;22;54$x')
# Expected output
fill(27, 4, 28, 7, 'n', test_attrs)
write('\033(B')
pass
def test_decera():
erase_attrs = '0;45'
def test_setup():
fill(1, 1, 80, 24, 'E', test_attrs)
write('\033[0;4;7;36;45m')
g = TestGroup('DECERA', test_setup)
if g.new_test('Basic functionality'):
'''
STD070: When this control is received, the terminal erases the rectangular
area of character positions defined by Pt, Pl, Pb, and Pr, clearing all
character attributes. When an area is erased, all characters are replaced
with the Space character (2/0).
'''
write('\033[16;27;22;54$z')
# Expected output
fill(27, 4, 28, 7, ' ', erase_attrs)
if g.new_test('Default parameters'):
'''
STD070: Default values are Pt = 1, Pb = last-line-of-page, Pl = 1, and Pr =
last-column-of-page.
'''
write('\033[16$z')
# Expected output
fill(1, 4, 80, 9, ' ', erase_attrs)
if g.new_test('Invalid parameters'):
'''
STD070: Pt must be less or equal to Pb, and Pl must be less than or equal
to Pr, otherwise the entire control function is ignored.
'''
write('\033[16;80;22;71$z') # Ignored, Pl > Pr
write('\033[22;1;16;10$z') # Ignored, Pt > Pb
write('\033[18;38;20;43$z')
# Expected output
fill(38, 6, 6, 3, ' ', erase_attrs)
if g.new_test('Origin mode'):
'''
STD070: The coordinates of the rectangular area are affected by the setting
of Origin Mode. This control is not otherwise affected by the margins.
'''
write('\033[17;20r') # Margins 17 to 20
write('\033[?6h') # Origin Mode enabled
write('\033[6;33;8;48$z') # Clamped
write('\033[2;36;6;45$z') # Clipped
write('\033[?6l') # Origin Mode reset
write('\033[16;39;22;42$z') # Not Clipped
write('\033[r') # Margins reset
# Expected output
fill(39, 4, 4, 2, ' ', erase_attrs)
fill(36, 6, 10, 2, ' ', None)
fill(33, 8, 16, 1, ' ', None)
fill(39, 9, 4, 2, ' ', None)
if g.new_test('Double-width/height lines'):
'''
STD070: If the rectangular area contains both single and double width lines,
the area affected may not appear rectangular on the display.
'''
write('\033[16H\033#3')
write('\033[17H\033#4')
write('\033[18H\033#6')
write('\033[16;27;22;54$z')
# Expected output
fill(27, 4, 28, 7, ' ', erase_attrs)
write('\033[4H\033#3')
write('\033[5H\033#4')
write('\033[6H\033#6')
g.reset_double_width(4,5,6,16,17,18)
if g.new_test('Overflowing page boundary'):
'''
STD070: If a value exceeds the width or height of the active page, it is
treated as the width or height of the active page.
'''
write('\033[19;41;9999;9999$z')
# Expected output
fill(41, 7, 40, 6, ' ', erase_attrs)
def test_decsera():
def diagonal(y):
for i in range(9):
write('\033[%d;%dHEE' % (y+i,34+i*2))
def test_setup():
fill(1, 1, 80, 24, 'E', test_attrs)
write('\033[1"q')
diagonal(16)
write('\033[0"q')
write('\033[m')
g = TestGroup('DECSERA', test_setup)
if g.new_test('Basic functionality'):
'''
STD070: When a character is erased, it is replaced with the Space character
(2/0). This sequence does not change or clear any video character attributes
(SGR or DECSCA)
'''
write('\033[16;27;22;54${')
# Expected output
fill(27, 4, 28, 7, ' ', test_attrs)
diagonal(4)
if g.new_test('Default parameters'):
'''
STD070: Default values are Pt = 1, Pb = last-line-of-page, Pl = 1, and Pr =
last-column-of-page.
'''
write('\033[16${')
# Expected output
fill(1, 4, 80, 9, ' ', test_attrs)
diagonal(4)
if g.new_test('Invalid parameters'):
'''
STD070: Pt must be less or equal to Pb, and Pl must be less than or equal
to Pr, otherwise the entire control function is ignored.
'''
write('\033[16;80;22;71${') # Ignored, Pl > Pr
write('\033[22;1;16;10${') # Ignored, Pt > Pb
write('\033[18;38;20;43${')
# Expected output
fill(38, 6, 6, 3, ' ', test_attrs)
diagonal(4)
if g.new_test('Origin mode'):
'''
STD070: The coordinates of the rectangular area are affected by the setting
of Origin Mode. This control is not otherwise affected by the margins.
'''
write('\033[17;20r') # Margins 17 to 20
write('\033[?6h') # Origin Mode enabled
write('\033[6;33;8;48${') # Clamped
write('\033[2;36;6;45${') # Clipped
write('\033[?6l') # Origin Mode reset
write('\033[16;39;22;42${') # Not Clipped
write('\033[r') # Margins reset
# Expected output
fill(39, 4, 4, 2, ' ', test_attrs)
fill(36, 6, 10, 2, ' ', None)
fill(33, 8, 16, 1, ' ', None)
fill(39, 9, 4, 2, ' ', None)
diagonal(4)
if g.new_test('Double-width/height lines'):
'''
STD070: If the rectangular area contains both single and double width lines,
the area affected may not appear rectangular on the display.
'''
write('\033[16H\033#3')
write('\033[17H\033#4')
write('\033[18H\033#6')
write('\033[16;27;22;54${')
# Expected output
fill(27, 4, 28, 7, ' ', test_attrs)
diagonal(4)
write('\033[4H\033#3')
write('\033[5H\033#4')
write('\033[6H\033#6')
g.reset_double_width(4,5,6,16,17,18)
if g.new_test('Overflowing page boundary'):
'''
STD070: If a value exceeds the width or height of the active page, it is
treated as the width or height of the active page.
'''
write('\033[19;41;9999;9999${')
# Expected output
fill(41, 7, 40, 6, ' ', test_attrs)
diagonal(4)
def test_deccra():
def pattern(x, y, h = 3, clipped = False):
for i in range(h):
write('\033[%d;%dH***' % (y+i,x))
if not clipped or i == 1:
write('\033[22mooo\033[1m')
def test_setup():
decaln()
write('\033[%sm' % test_attrs)
pattern(38, 18)
g = TestGroup('DECCRA', test_setup)
if g.new_test('Basic functionality'):
'''
STD070: the terminal copies the character values and attributes in the
specified rectangular area of character positions defined by Pts, Pls, Pbs,
and Prs on the specified page Pps, to the area specified by the coordinate
Ptd, Pld on the specified page Ppd.
'''
write('\033[18;38;20;43;1;20;42;1$v')
write('\033[20;42;22;47;1;16;34;1$v')
# Expected output
pattern(38, 6)
pattern(42, 8)
pattern(34, 4)
if g.new_test('Default parameters'):
'''
STD070: Default values are Pts = 1, Pls = 1, Pbs = last-line-of-page, Prs =
last-column-of-page, Pps = 1, Ptd = 1, Pld = 1, and Ppd = 1.
'''
write('\033[;;;;;2;3$v')
write('\033[3;5;21;45$v')
# Expected output
pattern(40, 7)
pattern(36, 5)
if g.new_test('Invalid parameters'):
'''
STD070: Pts must be less or equal to Pbs, and Pls must be less than or equal
to Prs, otherwise the entire control function is ignored.
'''
write('\033[18;38;20;43;1;20;42;1$v')
write('\033[20;42;22;47;1;16;34;1$v')
write('\033[16;47;23;34;1;16;1;1$v') # Ignored, Pls > Prs
write('\033[23;34;16;47;1;16;67;1$v') # Ignored, Pbs > Pts
# Expected output
pattern(38, 6)
pattern(42, 8)
pattern(34, 4)
if g.new_test('Origin mode'):
'''
STD070: The coordinates of the rectangular area are affected by the setting
of Origin Mode. This control is not otherwise affected by the margins.
'''
write('\033[17;19r') # Margins 17 to 19
write('\033[?6h') # Origin Mode enabled
write('\033[6;38;8;43;1;1;28;1$v') # Clamped Src
write('\033[3;38;5;43;1;2;29;1$v') # Clipped
write('\033[2;38;4;43;1;5;30;1$v') # Clamped Dst
write('\033[?6l') # Origin Mode reset
write('\033[18;38;20;43;1;19;47;1$v') # Not Clipped
write('\033[r') # Margins reset
# Expected output
pattern(38, 6)
pattern(47, 7)
pattern(28, 5, h=1)
pattern(29, 6, h=1)
pattern(30, 7, h=1)
if g.new_test('Double-width/height lines'):
'''
STD070: If the rectangular area contains both single and double width lines,
the area copied FROM may not appear rectangular on the display. Text copied
to the destination area take on the line attributes of that area.
'''
write('\033[18H\033#6')
write('\033[20H\033#6')
write('\033[18;38;20;43;1;20;44;1$v')
write('\033[18;38;20;43;1;16;32;1$v')
# Expected output
pattern(38, 6)
pattern(44, 8, clipped=True)
pattern(32, 4, clipped=True)
write('\033[6H\033#6')
write('\033[8H\033#6')
g.reset_double_width(6,8,18,20)
if g.new_test('Overflowing page boundary'):
'''
STD070: If a value exceeds the width or height of the active page, it is
treated as the width or height of the active page. If the destination
rectangle specified is partially off of the Page, clipping of information
will occur.
'''
write('\033[18;38;999;999;1;17;36;1$v')
write('\033[17;36;999;999;1;19;40;1$v')
# Expected output
pattern(36, 5)
pattern(40, 7)
if g.new_test('Copying between pages'):
'''
STD070: the terminal copies the character values and attributes in the
specified rectangular area ... on the specified page Pps, to the area ...
on the specified page Ppd.
'''
write('\033[2 P\033[14H\033[m\033[J\033[1 P')
write('\033[18;38;20;43;1;18;38;2$v')
write('\033[16;34;22;47;2;17;39;1$v')
write('\033[16;34;22;47;2;15;29;1$v')
# Expected output
write('\033[%sm' % test_attrs)
fill(29, 3, 14, 7, ' ')
fill(39, 5, 14, 7, ' ')
write('\033[%sm' % test_attrs)
pattern(33, 5)
pattern(43, 7)
def test_deccara():
attrs = '1;4;7'
def test_setup():
decaln()
write('\033[2*x')
g = TestGroup('DECCARA', test_setup)
if g.new_test('Basic functionality'):
'''
STD070: This sequence changes the video attributes for all of the character
positions in a rectangular area without altering any characters in those
positions.
'''
write('\033[16;27;22;54;%s$r' % attrs)
# Expected output
fill(27, 4, 28, 7, 'E', attrs)
if g.new_test('Default parameters'):
'''
STD070: Default values are Pt = 1, Pb = last-line-of-page, Pl = 1, and Pr =
last-column-of-page. The default for the video attribute parameters, if no
valid subsequent parameter is received, is 0, which will clear all video
attributes in the specified area.
'''
write('\033[16;;;;%s$r' % attrs)
write('\033[18;27;22;54$r')
# Expected output
fill(1, 4, 80, 9, 'E', attrs)
fill(27, 6, 28, 5, 'E')
if g.new_test('Invalid parameters'):
'''
STD070: Pt must be less or equal to Pb, and Pl must be less than or equal
to Pr, otherwise the entire control function is ignored.
'''
write('\033[16;80;22;71;%s$r' % attrs) # Ignored, Pl > Pr
write('\033[22;1;16;10;%s$r' % attrs) # Ignored, Pt > Pb
write('\033[18;38;20;43;%s$r' % attrs)
# Expected output
fill(38, 6, 6, 3, 'E', attrs)
if g.new_test('Origin mode'):
'''
STD070: The coordinates of the rectangular area are affected by the current
setting of Origin Mode. This control is not otherwise affected by the
margins.
'''
write('\033[17;20r') # Margins 17 to 20
write('\033[?6h') # Origin Mode enabled
write('\033[6;33;8;48;%s$r' % attrs) # Clamped
write('\033[2;36;6;45;%s$r' % attrs) # Clipped
write('\033[?6l') # Origin Mode reset
write('\033[16;39;22;42;%s$r' % attrs) # Not Clipped
write('\033[r') # Margins reset
# Expected output
fill(39, 4, 4, 2, 'E', attrs)
fill(36, 6, 10, 2, 'E', None)
fill(33, 8, 16, 1, 'E', None)
fill(39, 9, 4, 2, 'E', None)
if g.new_test('Double-width/height lines'):
'''
STD070: If the rectangular area contains both single and double width lines,
the area affected may not appear rectangular on the display.
'''
write('\033[16H\033#3')
write('\033[17H\033#4')
write('\033[18H\033#6')
write('\033[16;27;22;54;%s$r' % attrs)
# Expected output
fill(27, 4, 28, 7, 'E', attrs)
write('\033[4H\033#3')
write('\033[5H\033#4')
write('\033[6H\033#6')
g.reset_double_width(4,5,6,16,17,18)
if g.new_test('Overflowing page boundary'):
'''
STD070: If a value exceeds the width or height of the active page, it is
treated as the width or height of the active page.
'''
write('\033[19;41;9999;9999;%s$r' % attrs)
# Expected output
fill(41, 7, 40, 6, 'E', attrs)
def test_decrara():
def test_setup():
decaln()
write('\033[2*x')
g = TestGroup('DECRARA', test_setup)
if g.new_test('Basic functionality'):
'''
STD070: This sequence reverses one or more video attributes for all of the
character positions in a rectangular area without altering any characters in
those positions.
'''
write('\033[16;27;22;54;1;4;7$t')
# Expected output
fill(27, 4, 28, 7, 'E', '1;4;7')
if g.new_test('Default parameters'):
'''
STD070: Default values are Pt = 1, Pb = last-line-of-page, Pl = 1, and Pr =
last-column-of-page.
'''
write('\033[16;;;;1;4;7$t')
# Expected output
fill(1, 4, 80, 9, 'E', '1;4;7')
if g.new_test('Invalid parameters'):
'''
STD070: Pt must be less or equal to Pb, and Pl must be less than or equal
to Pr, otherwise the entire control function is ignored. There is no default
for the video attribute parameters, if no valid subsequent parameter is
received, the command is ignored.
'''
write('\033[16;80;22;71;1;4;7$t') # Ignored, Pl > Pr
write('\033[22;1;16;10;1;4;7$t') # Ignored, Pt > Pb
write('\033[16;27;17;54$t') # Ignored, no attribute parameters
write('\033[18;38;20;43;1;4;7$t')
# Expected output
fill(38, 6, 6, 3, 'E', '1;4;7')
if g.new_test('Origin mode'):
'''
STD070: The coordinates of the rectangular area are affected by the current
setting of Origin Mode. This control is not otherwise affected by the
margins.
'''
write('\033[17;20r') # Margins 17 to 20
write('\033[?6h') # Origin Mode enabled
write('\033[6;33;8;48;1;4;7$t') # Clamped
write('\033[2;36;6;45;1;4;7$t') # Clipped
write('\033[?6l') # Origin Mode reset
write('\033[16;39;22;42;1;4;7$t') # Not Clipped
write('\033[r') # Margins reset
# Expected output
fill(39, 4, 4, 2, 'E', '1;4;7')
fill(36, 6, 3, 2, 'E', None)
fill(43, 6, 3, 2, 'E', None)
fill(33, 8, 3, 1, 'E', None)
fill(46, 8, 3, 1, 'E', None)
fill(39, 8, 4, 3, 'E', None)
if g.new_test('Double-width/height lines'):
'''
STD070: If the rectangular area contains both single and double width lines,
the area affected may not appear rectangular on the display.
'''
write('\033[16H\033#3')
write('\033[17H\033#4')
write('\033[18H\033#6')
write('\033[16;27;22;54;1;4;7$t')
# Expected output
fill(27, 4, 28, 7, 'E', '1;4;7')
write('\033[4H\033#3')
write('\033[5H\033#4')
write('\033[6H\033#6')
g.reset_double_width(4,5,6,16,17,18)
if g.new_test('Overflowing page boundary'):
'''
STD070: If a value exceeds the width or height of the active page, it is
treated as the width or height of the active page.
'''
write('\033[19;41;9999;9999;1;4;7$t')
# Expected output
fill(41, 7, 40, 6, 'E', '1;4;7')
def test_attributes():
color = ';33;41'
all = '0;1;4;7'+color
def test_setup():
decaln()
write('\033[2*x') # Rectangle mode
fill(31,18,6,3,'*',all)
fill(45,18,6,3,'*',None)
g = TestGroup('Attributes', test_setup)
'''
DECCARA: The default, if no valid subsequent parameter is received, is 0.
DECRARA: There is no default. If no valid subsequent parameter is received,
the command is ignored.
Both: When multiple parameters are used, they are cumulative.
'''
test_cases = [
('', 'Default'),
('0', 'All Off / Reverse All (0)'),
('1', 'Increased Intensity (1)'),
('4', 'Underscore (4)'),
('7', 'Negative Image (7)'),
('22', 'Normal Intensity (22)'),
('24', 'No Underline (24)'),
('27', 'Positive Image (27)'),
('4;7', 'Underscore + Negative (4;7)'),
('0;7', 'All Off + Negative (0;7)'),
(';7', 'Default + Negative (0;7)'),
('7;27','Negative + Positive (7;27)'),
('44', 'Blue Background (44)'),
('32', 'Green Foreground (32)'),
]
def apply(src, add):
src = set([int(n) for n in src.split(';')])
if add == '': add = '0'
for n in add.split(';'):
n = int(n) if n else 0
if n == 0:
src.clear()
elif n == 22:
src.discard(1)
elif n in [24,27]:
src.discard(n-20)
else:
if n//10 == 3: src = set(i for i in src if i//10 != 3)
if n//10 == 4: src = set(i for i in src if i//10 != 4)
src.add(n)
return ';'.join(['0'] + [str(n) for n in src if n])
def reverse(src, rev):
src = set([int(n) for n in src.split(';') if n != '0'])
if rev:
all_rendition = [1,2,3,4,5,6,7,8,9,21,53]
for n in rev.split(';'):
if n in ['','0']:
expanded = all_rendition
else:
expanded = [int(n)]
for n in expanded:
if n in src:
src.discard(n)
elif n in all_rendition:
src.add(n)
return ';'.join(['0'] + [str(n) for n in src if n])
for attrs,description in test_cases:
if g.new_test(description):
semiattrs = ';'+attrs if attrs else attrs
write('\033[16;27;22;40%s$r' % semiattrs)
write('\033[16;41;22;54%s$t' % semiattrs)
# Expected output
a1 = apply('0', attrs)
a2 = apply(all, attrs)
fill(27,4,14,7,'E',a1)
fill(31,6,6,3,'*',a2)
a1 = reverse('0', attrs)
a2 = reverse(all, attrs)
fill(41,4,14,7,'E',a1)
fill(45,6,6,3,'*',a2)
def test_decsace():
attrs = '1;4;7'
def test_setup():
decaln()
g = TestGroup('DECSACE', test_setup)
if g.new_test('Basic functionality'):
write('\033[1*x') # Stream
write('\033[16;34;17;47;%s$r' % attrs);
write('\033[2*x') # Rectangle
write('\033[18;34;20;47;%s$r' % attrs);
write('\033[0*x') # Stream
write('\033[21;34;22;47;%s$r' % attrs);
# Expected output
fill(34,4,47,1,'E','0;'+attrs)
fill(1,5,47,1,'E',None)
fill(34,6,14,3,'E',None)
fill(34,9,47,1,'E',None)
fill(1,10,47,1,'E',None)
if g.new_test('Default parameter'):
write('\033[2*x') # Start with 2 (rectangle) and see if
write('\033[*x') # the default (stream) overrides that.
write('\033[16;34;22;47;%s$r' % attrs);
# Expected output
fill(34,4,47,1,'E','0;'+attrs)
fill(1,5,80,5,'E',None)
fill(1,10,47,1,'E',None)
if g.new_test('Invalid parameters'):
write('\033[1*x')
write('\033[15;34;17;33;%s$r' % attrs); # Ignored, Pl > Pr
write('\033[1*x')
write('\033[3*x') # Ignored, Ps > 2
write('\033[17;34;19;47;%s$r' % attrs);
write('\033[2*x')
write('\033[3*x') # Ignored, Ps > 2
write('\033[20;34;21;47;%s$r' % attrs);
# Expected output
fill(34,5,47,1,'E','0;'+attrs)
fill(1,6,80,1,'E',None)
fill(1,7,47,1,'E',None)
fill(34,8,14,2,'E',None)
if g.new_test('Unoccupied positions (stream)'):
write('\033[18;39H\033[4@') # ECH unoccupied
write('\033[19;39H\033[2K ') # EL unoccupied + occupied spaces
write('\033[20;39H\033[4@') # ECH unoccupied
write('\033[1*x')
write('\033[16;34;19;40;%s$r' % attrs);
write('\033[19;41;22;47;%s$t' % attrs);
# Expected output
fill(1,7,80,1,' ')
fill(34,4,47,1,'E','0;'+attrs)
fill(1,5,80,2,'E',None)
fill(1,8,80,2,'E',None)
fill(1,10,47,1,'E',None)
fill(39,7,4,1,' ',None)
fill(39,6,4,1,' ','0')
fill(39,8,4,1,' ',None)
if g.new_test('Unoccupied positions (rectangle)'):
write('\033[18;39H\033[4@') # ECH unoccupied
write('\033[19;39H\033[2K ') # EL unoccupied + occupied spaces
write('\033[20;39H\033[4@') # ECH unoccupied
write('\033[2*x')
write('\033[16;34;22;40;%s$r' % attrs);
write('\033[16;41;22;47;%s$t' % attrs);
# Expected output
fill(1,7,80,1,' ')
fill(34,4,14,3,'E','0;'+attrs)
fill(34,7,14,3,' ',None)
fill(34,8,14,3,'E',None)
fill(39,6,4,1,' ',None)
fill(39,8,4,1,' ',None)
# Try and make sure we're using ISO 2022 encoding.
write('\033%@')
sys.stdout.flush()
# Make sure Erase Color Mode is reset.
write('\033[?117l')
test_decfra()
test_decera()
test_decsera()
test_deccra()
test_deccara()
test_decrara()
test_attributes()
test_decsace()
@KalleOlaviNiemitalo
Copy link

Oscilloscope attempt on flow control

I configured 9600 bps, no flow control in the host, Modem Control and Unlimited Transmit in the VT420. Connected the second null modem cable, ran yes in the host, and viewed the signals of the host-side connector using a Sinclair SC110 oscilloscope. The following voltages are not exact.

VT420 host behaviour
2 RXD (To VT420) 3 TXD ±5 V data
3 TXD (From VT420) 2 RXD -5 V stable
4 DTR (From VT420) 6 DSR and 1 DCD +5 V stable
5 GND 5 GND ground
6 DSR and 1 DCD (To VT420) 4 DTR +5 V fuzzy
7 RTS (From VT420) 8 CTS +5 V stable
8 CTS (To VT420) 7 RTS +5 V fuzzy

Something like half of the characters transmitted by the host were displayed as reverse question marks, which I think means the input buffer of the VT420 could not keep up. The indicator status line of the VT420 was displaying "Modem: DSR".

Alas, neither RTS nor DTR appears suitable for flow control. I think the best way to get flow control without sacrificing ^Q and ^S would be to reverse engineer the SSU protocol and implement that in the host.

@j4james
Copy link
Author

j4james commented Oct 29, 2022

I think the best way to get flow control without sacrificing ^Q and ^S would be to reverse engineer the SSU protocol and implement that in the host.

If you want to attempt that, there is some discussion of the the protocol here:
https://paperlined.org/apps/terminals/control_characters/TDSMP.html

They say the protocol was patented, and in theory you should be able to work it out from those patents, but the links they gave appear to be dead now. I'm sure there must be somewhere you can look them up though.

@KalleOlaviNiemitalo
Copy link

One can still search for the patent numbers 4791566 and 5165020 at Patent Public Search | USPTO.

IIUC, https://ppubs.uspto.gov/pubwebapp/external.html?q=(%225165020%22).pn.%20OR%20(%224791566%22).pn. should be a link to such a search, but I couldn't get that to work.

@j4james
Copy link
Author

j4james commented Oct 29, 2022

@KalleOlaviNiemitalo
Copy link

KalleOlaviNiemitalo commented Oct 29, 2022

These patents do not describe how the commands and their parameters are encoded. Perhaps it can be figured out by trial and error. (A log of the communication with the official SSU software would be easier to analyze, but my AlphaStation 500 is not in good condition and lacks OpenVMS. Community licenses are available but Community License Agreement §2.e. forbids reverse engineering.)

@j4james
Copy link
Author

j4james commented Oct 29, 2022

A log of the communication with the official SSU software would be easier to analyze

Yeah, the more I think about it having a log of both sides of the communication is probably essential. There's really not that much to go on in those patents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment