File Coverage

blib/lib/Term/VT102.pm
Criterion Covered Total %
statement 578 761 75.9
branch 223 370 60.2
condition 34 72 47.2
subroutine 61 87 70.1
pod 25 25 100.0
total 921 1315 70.0


line stmt bran cond sub pod time code
1             # Term::VT102 - module for VT102 emulation in Perl
2             #
3             # Copyright (C) Andrew Wood
4             # NO WARRANTY - see COPYING.
5             #
6              
7             package Term::VT102;
8              
9 14     14   14008 use strict;
  14         31  
  14         702  
10              
11             BEGIN {
12 14     14   76 use Exporter ();
  14         28  
  14         299  
13 14     14   73 use vars qw($VERSION @ISA);
  14         26  
  14         1052  
14              
15 14     14   36 $VERSION = '0.91';
16              
17 14         4238 @ISA = qw(Exporter);
18             }
19              
20              
21             # Return the packed version of a set of attributes fg, bg, bo, fa, st, ul,
22             # bl, rv.
23             #
24             sub attr_pack {
25 132 100   132 1 836 shift if ref($_[0]); # called in object context, ditch the object
26 132         207 my ($fg, $bg, $bo, $fa, $st, $ul, $bl, $rv) = @_;
27 132         142 my $num = 0;
28              
29 132         291 $num = ($fg & 7)
30             | (($bg & 7) << 4)
31             | ($bo << 8)
32             | ($fa << 9)
33             | ($st << 10)
34             | ($ul << 11)
35             | ($bl << 12)
36             | ($rv << 13);
37 132         184854 return pack ('S', $num);
38             }
39              
40              
41             # Return the unpacked version of a packed attribute.
42             #
43             sub attr_unpack {
44 30 50   30 1 109 shift if ref($_[0]); # called in object context, ditch the object
45 30         39 my $data = shift;
46 30         34 my ($num, $fg, $bg, $bo, $fa, $st, $ul, $bl, $rv);
47              
48 30         76 $num = unpack ('S', $data);
49              
50 30         119 ($fg, $bg, $bo, $fa, $st, $ul, $bl, $rv) = (
51             $num & 7,
52             ($num >> 4) & 7,
53             ($num >> 8) & 1,
54             ($num >> 9) & 1,
55             ($num >> 10) & 1,
56             ($num >> 11) & 1,
57             ($num >> 12) & 1,
58             ($num >> 13) & 1
59             );
60              
61 30         99 return ($fg, $bg, $bo, $fa, $st, $ul, $bl, $rv);
62             }
63              
64              
65             # Default attribute set (in both packed and unpacked flavors)
66             #
67 14     14   93 use constant DEFAULT_ATTR => (7, 0, 0, 0, 0, 0, 0, 0);
  14         27  
  14         1426  
68 14     14   73 use constant DEFAULT_ATTR_PACKED => attr_pack(&DEFAULT_ATTR);
  14         30  
  14         66  
69              
70              
71             # Constructor function.
72             #
73             sub new {
74 81     81 1 24919 my ($proto, %init) = @_;
75 81   33     411 my $class = ref ($proto) || $proto;
76 81         141 my $self = {};
77              
78 81         1180 $self->{'_ctlseq'} = { ( # control characters
79             "\000" => 'NUL', # ignored
80             "\005" => 'ENQ', # trigger answerback message
81             "\007" => 'BEL', # beep
82             "\010" => 'BS', # backspace one column
83             "\011" => 'HT', # horizontal tab to next tab stop
84             "\012" => 'LF', # line feed
85             "\013" => 'VT', # line feed
86             "\014" => 'FF', # line feed
87             "\015" => 'CR', # carriage return
88             "\016" => 'SO', # activate G1 character set & newline
89             "\017" => 'SI', # activate G0 character set
90             "\021" => 'XON', # resume transmission
91             "\023" => 'XOFF', # stop transmission, ignore characters
92             "\030" => 'CAN', # interrupt escape sequence
93             "\032" => 'SUB', # interrupt escape sequence
94             "\033" => 'ESC', # start escape sequence
95             "\177" => 'DEL', # ignored
96             "\233" => 'CSI' # equivalent to ESC [
97             ) };
98              
99 81         2134 $self->{'_escseq'} = { ( # escape sequences
100             'c' => 'RIS', # reset
101             'D' => 'IND', # line feed
102             'E' => 'NEL', # newline
103             'H' => 'HTS', # set tab stop at current column
104             'M' => 'RI', # reverse line feed
105             'Z' => 'DECID', # DEC private ID; return ESC [ ? 6 c (VT102)
106             '7' => 'DECSC', # save state (position, charset, attributes)
107             '8' => 'DECRC', # restore most recently saved state
108             '[' => 'CSI', # control sequence introducer
109             '[[' => 'IGN', # ignored control sequence
110             '%@' => 'CSDFL', # select default charset (ISO646/8859-1)
111             '%G' => 'CSUTF8', # select UTF-8
112             '%8' => 'CSUTF8', # select UTF-8 (obsolete)
113             '#8' => 'DECALN', # DEC alignment test - fill screen with E's
114             '(8' => 'G0DFL', # G0 charset = default mapping (ISO8859-1)
115             '(0' => 'G0GFX', # G0 charset = VT100 graphics mapping
116             '(U' => 'G0ROM', # G0 charset = null mapping (straight to ROM)
117             '(K' => 'G0USR', # G0 charset = user defined mapping
118             '(B' => 'G0TXT', # G0 charset = ASCII mapping
119             ')8' => 'G1DFL', # G1 charset = default mapping (ISO8859-1)
120             ')0' => 'G1GFX', # G1 charset = VT100 graphics mapping
121             ')U' => 'G1ROM', # G1 charset = null mapping (straight to ROM)
122             ')K' => 'G1USR', # G1 charset = user defined mapping
123             ')B' => 'G1TXT', # G1 charset = ASCII mapping
124             '*8' => 'G2DFL', # G2 charset = default mapping (ISO8859-1)
125             '*0' => 'G2GFX', # G2 charset = VT100 graphics mapping
126             '*U' => 'G2ROM', # G2 charset = null mapping (straight to ROM)
127             '*K' => 'G2USR', # G2 charset = user defined mapping
128             '+8' => 'G3DFL', # G3 charset = default mapping (ISO8859-1)
129             '+0' => 'G3GFX', # G3 charset = VT100 graphics mapping
130             '+U' => 'G3ROM', # G3 charset = null mapping (straight to ROM)
131             '+K' => 'G3USR', # G3 charset = user defined mapping
132             '>' => 'DECPNM', # set numeric keypad mode
133             '=' => 'DECPAM', # set application keypad mode
134             'N' => 'SS2', # select G2 charset for next char only
135             'O' => 'SS3', # select G3 charset for next char only
136             'P' => 'DCS', # device control string (ended by ST)
137             'X' => 'SOS', # start of string
138             '^' => 'PM', # privacy message (ended by ST)
139             '_' => 'APC', # application program command (ended by ST)
140             "\\" => 'ST', # string terminator
141             'n' => 'LS2', # invoke G2 charset
142             'o' => 'LS3', # invoke G3 charset
143             '|' => 'LS3R', # invoke G3 charset as GR
144             '}' => 'LS2R', # invoke G2 charset as GR
145             '~' => 'LS1R', # invoke G1 charset as GR
146             ']' => 'OSC', # operating system command
147             'g' => 'BEL', # alternate BEL
148             ) };
149              
150 81         1238 $self->{'_csiseq'} = { ( # ECMA-48 CSI sequences
151             '[' => 'IGN', # ignored control sequence
152             '@' => 'ICH', # insert blank characters
153             'A' => 'CUU', # move cursor up
154             'B' => 'CUD', # move cursor down
155             'C' => 'CUF', # move cursor right
156             'D' => 'CUB', # move cursor left
157             'E' => 'CNL', # move cursor down and to column 1
158             'F' => 'CPL', # move cursor up and to column 1
159             'G' => 'CHA', # move cursor to column in current row
160             'H' => 'CUP', # move cursor to row, column
161             'J' => 'ED', # erase display
162             'K' => 'EL', # erase line
163             'L' => 'IL', # insert blank lines
164             'M' => 'DL', # delete lines
165             'P' => 'DCH', # delete characters on current line
166             'X' => 'ECH', # erase characters on current line
167             'a' => 'HPR', # move cursor right
168             'c' => 'DA', # return ESC [ ? 6 c (VT102)
169             'd' => 'VPA', # move to row (current column)
170             'e' => 'VPR', # move cursor down
171             'f' => 'HVP', # move cursor to row, column
172             'g' => 'TBC', # clear tab stop (CSI 3 g = clear all stops)
173             'h' => 'SM', # set mode
174             'l' => 'RM', # reset mode
175             'm' => 'SGR', # set graphic rendition
176             'n' => 'DSR', # device status report
177             'q' => 'DECLL', # set keyboard LEDs
178             'r' => 'DECSTBM', # set scrolling region to (top, bottom) rows
179             's' => 'CUPSV', # save cursor position
180             'u' => 'CUPRS', # restore cursor position
181             '`' => 'HPA' # move cursor to column in current row
182             ) };
183              
184 81         1503 $self->{'_modeseq'} = { ( # ANSI/DEC specified modes for SM/RM
185             # ANSI Specified Modes
186             '0' => 'IGN', # Error (Ignored)
187             '1' => 'GATM', # guarded-area transfer mode (ignored)
188             '2' => 'KAM', # keyboard action mode (always reset)
189             '3' => 'CRM', # control representation mode (always reset)
190             '4' => 'IRM', # insertion/replacement mode (always reset)
191             '5' => 'SRTM', # status-reporting transfer mode
192             '6' => 'ERM', # erasure mode (always set)
193             '7' => 'VEM', # vertical editing mode (ignored)
194             '10' => 'HEM', # horizontal editing mode
195             '11' => 'PUM', # positioning unit mode
196             '12' => 'SRM', # send/receive mode (echo on/off)
197             '13' => 'FEAM', # format effector action mode
198             '14' => 'FETM', # format effector transfer mode
199             '15' => 'MATM', # multiple area transfer mode
200             '16' => 'TTM', # transfer termination mode
201             '17' => 'SATM', # selected area transfer mode
202             '18' => 'TSM', # tabulation stop mode
203             '19' => 'EBM', # editing boundary mode
204             '20' => 'LNM', # Line Feed / New Line Mode
205             # DEC Private Modes
206             '?0' => 'IGN', # Error (Ignored)
207             '?1' => 'DECCKM', # Cursorkeys application (set); Cursorkeys normal (reset)
208             '?2' => 'DECANM', # ANSI (set); VT52 (reset)
209             '?3' => 'DECCOLM', # 132 columns (set); 80 columns (reset)
210             '?4' => 'DECSCLM', # Jump scroll (set); Smooth scroll (reset)
211             '?5' => 'DECSCNM', # Reverse screen (set); Normal screen (reset)
212             '?6' => 'DECOM', # Sets relative coordinates (set); Sets absolute coordinates (reset)
213             '?7' => 'DECAWM', # Auto Wrap
214             '?8' => 'DECARM', # Auto Repeat
215             '?9' => 'DECINLM', # Interlace
216             '?18' => 'DECPFF', # Send FF to printer after print screen (set); No char after PS (reset)
217             '?19' => 'DECPEX', # Print screen: prints full screen (set); prints scroll region (reset)
218             '?25' => 'DECTCEM', # Cursor on (set); Cursor off (reset)
219             ) };
220              
221 81         14845 $self->{'_funcs'} = { ( # supported character sequences
222             'BS' => \&_code_BS, # backspace one column
223             'CR' => \&_code_CR, # carriage return
224             'DA' => \&_code_DA, # return ESC [ ? 6 c (VT102)
225             'DL' => \&_code_DL, # delete lines
226             'ED' => \&_code_ED, # erase display
227             'EL' => \&_code_EL, # erase line
228             'FF' => \&_code_LF, # line feed
229             'HT' => \&_code_HT, # horizontal tab to next tab stop
230             'IL' => \&_code_IL, # insert blank lines
231             'LF' => \&_code_LF, # line feed
232             'PM' => \&_code_PM, # privacy message (ended by ST)
233             'RI' => \&_code_RI, # reverse line feed
234             'RM' => \&_code_RM, # reset mode
235             'SI' => undef, # activate G0 character set
236             'SM' => \&_code_SM, # set mode
237             'SO' => undef, # activate G1 character set & CR
238             'ST' => undef, # string terminator
239             'VT' => \&_code_LF, # line feed
240             'APC' => \&_code_APC, # application program command (ended by ST)
241             'BEL' => \&_code_BEL, # beep
242             'CAN' => \&_code_CAN, # interrupt escape sequence
243             'CHA' => \&_code_CHA, # move cursor to column in current row
244             'CNL' => \&_code_CNL, # move cursor down and to column 1
245             'CPL' => \&_code_CPL, # move cursor up and to column 1
246             'CRM' => undef, # control representation mode
247             'CSI' => \&_code_CSI, # equivalent to ESC [
248             'CUB' => \&_code_CUB, # move cursor left
249             'CUD' => \&_code_CUD, # move cursor down
250             'CUF' => \&_code_CUF, # move cursor right
251             'CUP' => \&_code_CUP, # move cursor to row, column
252             'CUU' => \&_code_CUU, # move cursor up
253             'DCH' => \&_code_DCH, # delete characters on current line
254             'DCS' => \&_code_DCS, # device control string (ended by ST)
255             'DEL' => \&_code_IGN, # ignored
256             'DSR' => \&_code_DSR, # device status report
257             'EBM' => undef, # editing boundary mode
258             'ECH' => \&_code_ECH, # erase characters on current line
259             'ENQ' => undef, # trigger answerback message
260             'ERM' => undef, # erasure mode
261             'ESC' => \&_code_ESC, # start escape sequence
262             'HEM' => undef, # horizontal editing mode
263             'HPA' => \&_code_CHA, # move cursor to column in current row
264             'HPR' => \&_code_CUF, # move cursor right
265             'HTS' => \&_code_HTS, # set tab stop at current column
266             'HVP' => \&_code_CUP, # move cursor to row, column
267             'ICH' => \&_code_ICH, # insert blank characters
268             'IGN' => \&_code_IGN, # ignored control sequence
269             'IND' => \&_code_LF, # line feed
270             'IRM' => undef, # insert/replace mode
271             'KAM' => undef, # keyboard action mode
272             'LNM' => undef, # line feed / newline mode
273             'LS2' => undef, # invoke G2 charset
274             'LS3' => undef, # invoke G3 charset
275             'NEL' => \&_code_NEL, # newline
276             'NUL' => \&_code_IGN, # ignored
277             'OSC' => \&_code_OSC, # operating system command
278             'PUM' => undef, # positioning unit mode
279             'RIS' => \&_code_RIS, # reset
280             'SGR' => \&_code_SGR, # set graphic rendition
281             'SOS' => undef, # start of string
282             'SRM' => undef, # send/receive mode (echo on/off)
283             'SS2' => undef, # select G2 charset for next char only
284             'SS3' => undef, # select G3 charset for next char only
285             'SUB' => \&_code_CAN, # interrupt escape sequence
286             'TBC' => \&_code_TBC, # clear tab stop (CSI 3 g = clear all stops)
287             'TSM' => undef, # tabulation stop mode
288             'TTM' => undef, # transfer termination mode
289             'VEM' => undef, # vertical editing mode
290             'VPA' => \&_code_VPA, # move to row (current column)
291             'VPR' => \&_code_CUD, # move cursor down
292             'XON' => \&_code_XON, # resume transmission
293             'FEAM' => undef, # format effector action mode
294             'FETM' => undef, # format effector transfer mode
295             'GATM' => undef, # guarded-area transfer mode
296             'LS1R' => undef, # invoke G1 charset as GR
297             'LS2R' => undef, # invoke G2 charset as GR
298             'LS3R' => undef, # invoke G3 charset as GR
299             'MATM' => undef, # multiple area transfer mode
300             'SATM' => undef, # selected area transfer mode
301             'SRTM' => undef, # status-reporting transfer mode
302             'XOFF' => \&_code_XOFF, # stop transmission, ignore characters
303             'CSDFL' => undef, # select default charset (ISO646/8859-1)
304             'CUPRS' => \&_code_CUPRS, # restore cursor position
305             'CUPSV' => \&_code_CUPSV, # save cursor position
306             'DECID' => \&_code_DA, # DEC private ID; return ESC [ ? 6 c (VT102)
307             'DECLL' => undef, # set keyboard LEDs
308             'DECOM' => undef, # relative/absolute coordinate mode
309             'DECRC' => \&_code_DECRC, # restore most recently saved state
310             'DECSC' => \&_code_DECSC, # save state (position, charset, attributes)
311             'G0DFL' => undef, # G0 charset = default mapping (ISO8859-1)
312             'G0GFX' => undef, # G0 charset = VT100 graphics mapping
313             'G0ROM' => undef, # G0 charset = null mapping (straight to ROM)
314             'G0TXT' => undef, # G0 charset = ASCII mapping
315             'G0USR' => undef, # G0 charset = user defined mapping
316             'G1DFL' => undef, # G1 charset = default mapping (ISO8859-1)
317             'G1GFX' => undef, # G1 charset = VT100 graphics mapping
318             'G1ROM' => undef, # G1 charset = null mapping (straight to ROM)
319             'G1TXT' => undef, # G1 charset = ASCII mapping
320             'G1USR' => undef, # G1 charset = user defined mapping
321             'G2DFL' => undef, # G2 charset = default mapping (ISO8859-1)
322             'G2GFX' => undef, # G2 charset = VT100 graphics mapping
323             'G2ROM' => undef, # G2 charset = null mapping (straight to ROM)
324             'G2USR' => undef, # G2 charset = user defined mapping
325             'G3DFL' => undef, # G3 charset = default mapping (ISO8859-1)
326             'G3GFX' => undef, # G3 charset = VT100 graphics mapping
327             'G3ROM' => undef, # G3 charset = null mapping (straight to ROM)
328             'G3USR' => undef, # G3 charset = user defined mapping
329             'CSUTF8' => undef, # select UTF-8 (obsolete)
330             'DECALN' => \&_code_DECALN,# DEC alignment test - fill screen with E's
331             'DECANM' => undef, # ANSI/VT52 mode
332             'DECARM' => undef, # auto repeat mode
333             'DECAWM' => undef, # auto wrap mode
334             'DECCKM' => undef, # cursor key mode
335             'DECPAM' => undef, # set application keypad mode
336             'DECPEX' => undef, # print screen / scrolling region
337             'DECPFF' => undef, # sent FF after print screen, or not
338             'DECPNM' => undef, # set numeric keypad mode
339             'DECCOLM' => undef, # 132 column mode
340             'DECINLM' => undef, # interlace mode
341             'DECSCLM' => undef, # jump/smooth scroll mode
342             'DECSCNM' => undef, # reverse/normal screen mode
343             'DECSTBM' => \&_code_DECSTBM, # set scrolling region
344             'DECTCEM' => \&_code_DECTCEM, # Cursor on (set); Cursor off (reset)
345             ) };
346              
347 81         766 $self->{'_callbacks'} = { ( # available callbacks
348             'BELL' => undef, # bell character received
349             'CLEAR' => undef, # screen cleared
350             'OUTPUT' => undef, # data to be sent back to originator
351             'ROWCHANGE' => undef, # screen row changed
352             'SCROLL_DOWN' => undef, # text about to move up (par=top row)
353             'SCROLL_UP' => undef, # text about to move down (par=bott.)
354             'UNKNOWN' => undef, # unknown character / sequence
355             'STRING' => undef, # string received
356             'XICONNAME' => undef, # xterm icon name changed
357             'XWINTITLE' => undef, # xterm window title changed
358             'LINEFEED' => undef, # line feed about to be processed
359             ) };
360              
361 81         173 $self->{'_callbackarg'} = { () }; # stored arguments for callbacks
362              
363 81         215 $self->{'_decsc'} = [ () ]; # saved state for DECSC/DECRC
364 81         178 $self->{'_cupsv'} = [ () ]; # saved state for CUPSV/CUPRS
365 81         651 $self->{'_xon'} = 1; # state is XON (characters accepted)
366              
367 81         109 $self->{'cols'} = 80; # default: 80 columns
368 81         115 $self->{'rows'} = 24; # default: 24 rows
369              
370 81         139 $self->{'_tabstops'} = []; # tab stops
371              
372 81 100 66     571 $self->{'cols'} = $init{'cols'}
373             if ((defined $init{'cols'}) && ($init{'cols'} > 0));
374 81 100 66     463 $self->{'rows'} = $init{'rows'}
375             if ((defined $init{'rows'}) && ($init{'rows'} > 0));
376              
377 81         212 bless ($self, $class);
378              
379 81         219 $self->reset ();
380              
381 81         257 return $self;
382             }
383              
384              
385             # Call a callback function with the given parameters.
386             #
387             sub callback_call {
388 823     823 1 1600 my ($self, $callback, $par1, $par2) = (@_);
389 823         813 my ($func, $arg);
390              
391 823         1437 $func = $self->{'_callbacks'}->{$callback};
392 823 100       2225 return if (not defined $func);
393              
394 4         7 $arg = $self->{'_callbackarg'}->{$callback};
395              
396 4         5 &{$func} ($self, $callback, $par1, $par2, $arg);
  4         12  
397             }
398              
399              
400             # Set a callback function.
401             #
402             sub callback_set {
403 8     8 1 67 my ($self, $callback, $ref, $arg) = (@_);
404 8         15 $self->{'_callbacks'}->{$callback} = $ref;
405 8         83 $self->{'_callbackarg'}->{$callback} = $arg;
406             }
407              
408              
409             # Reset the terminal to "power-on" values.
410             #
411             sub reset {
412 81     81 1 104 my $self = shift;
413 81         93 my ($a, $b, $i);
414              
415 81         256 $self->{'x'} = 1; # default X position: 1
416 81         127 $self->{'y'} = 1; # default Y position: 1
417              
418 81         243 $self->{'attr'} = DEFAULT_ATTR_PACKED;
419              
420 81         132 $self->{'ti'} = ''; # default: blank window title
421 81         132 $self->{'ic'} = ''; # default: blank icon title
422              
423 81         108 $self->{'srt'} = 1; # scrolling region top: row 1
424 81         127 $self->{'srb'} = $self->{'rows'}; # scrolling region bottom
425              
426 81         138 $self->{'opts'} = {}; # blank all options
427 81         157 $self->{'opts'}->{'LINEWRAP'} = 0; # line wrapping off
428 81         128 $self->{'opts'}->{'LFTOCRLF'} = 0; # don't map LF -> CRLF
429 81         122 $self->{'opts'}->{'IGNOREXOFF'} = 1; # ignore XON/XOFF by default
430              
431 81         137 $self->{'scrt'} = [ () ]; # blank screen text
432 81         136 $self->{'scra'} = [ () ]; # blank screen attributes
433              
434 81         371 $a = "\000" x $self->{'cols'}; # set text to NUL
435 81         189 $b = $self->{'attr'} x $self->{'cols'}; # set attributes to default
436              
437 81         198 foreach $i (1 .. $self->{'rows'}) {
438 1452         7705 ($self->{'scrt'}->[$i], $self->{'scra'}->[$i]) = ($a, $b);
439             }
440              
441 81         166 $self->{'_tabstops'} = []; # reset tab stops
442 81         267 for ($i = 1; $i < $self->{'cols'}; $i += 8) {
443 342         961 $self->{'_tabstops'}->[$i] = 1;
444             }
445              
446 81         118 $self->{'_buf'} = undef; # blank the esc-sequence buffer
447 81         132 $self->{'_inesc'} = ''; # not in any escape sequence
448 81         98 $self->{'_xon'} = 1; # state is XON (chars accepted)
449              
450 81         166 $self->{'cursor'} = 1; # turn cursor on
451             }
452              
453              
454             # Resize the terminal.
455             #
456             sub resize {
457 0     0 1 0 my $self = shift;
458 0         0 my $cols = shift;
459 0         0 my $rows = shift;
460              
461 0         0 $self->callback_call ('CLEAR', 0, 0);
462              
463 0         0 $self->{'cols'} = $cols;
464 0         0 $self->{'rows'} = $rows;
465              
466 0         0 $self->reset ();
467             }
468              
469              
470             # Return the package version.
471             #
472             sub version {
473 1     1 1 22 return $VERSION;
474             }
475              
476              
477             # Return the current number of columns.
478             #
479             sub cols {
480 0     0 1 0 my $self = shift;
481 0         0 return $self->{'cols'};
482             }
483              
484              
485             # Return the current number of rows.
486             #
487             sub rows {
488 0     0 1 0 my $self = shift;
489 0         0 return $self->{'rows'};
490             }
491              
492              
493             # Return the current terminal size.
494             #
495             sub size {
496 79     79 1 270 my $self = shift;
497 79         261 return ( $self->{'cols'}, $self->{'rows'} );
498             }
499              
500              
501             # Return the current cursor X co-ordinate.
502             #
503             sub x {
504 0     0 1 0 my $self = shift;
505 0         0 return $self->{'x'};
506             }
507              
508              
509             # Return the current cursor Y co-ordinate.
510             #
511             sub y {
512 0     0 1 0 my $self = shift;
513 0         0 return $self->{'y'};
514             }
515              
516              
517             # Return the current cursor state (1=on, 0=off).
518             #
519             sub cursor {
520 0     0 1 0 my $self = shift;
521 0         0 return $self->{'cursor'};
522             }
523              
524              
525             # Return the current xterm title text.
526             #
527             sub xtitle {
528 0     0 1 0 my $self = shift;
529 0         0 return $self->{'ti'};
530             }
531              
532              
533             # Return the current xterm icon text.
534             #
535             sub xicon {
536 0     0 1 0 my $self = shift;
537 0         0 return $self->{'ic'};
538             }
539              
540              
541             # Return the current terminal status.
542             #
543             sub status {
544 0     0 1 0 my $self = shift;
545              
546             return (
547 0         0 $self->{'x'}, # cursor X position
548             $self->{'y'}, # cursor Y position
549             $self->{'attr'}, # packed attributes
550             $self->{'ti'}, # xterm title text
551             $self->{'ic'} # xterm icon text
552             );
553             }
554              
555              
556             # Process the given string, updating the terminal object and calling any
557             # necessary callbacks on the way.
558             #
559             sub process {
560 77     77 1 495 my $self = shift;
561 77         111 my ($string) = @_;
562              
563 77 50       173 return if (not defined $string);
564              
565 77         189 while (length $string > 0) {
566 1914 100       3538 if (defined $self->{'_buf'}) { # in escape sequence
567 835 50       2848 if ($string =~ s/^(.)//s) {
568 835         1315 my $ch = $1;
569 835 100       1596 if ($ch =~ /[\x00-\x1F]/s) {
570 3         5 $self->_process_ctl ($ch);
571             } else {
572 832         1123 $self->{'_buf'} .= $ch;
573 832         1550 $self->_process_escseq ();
574             }
575             }
576             } else { # not in escape sequence
577 1079 100       4851 if ($string =~
    50          
578             s/^([^\x00-\x1F\x7F\x9B]+)//s) {
579 410         864 $self->_process_text ($1);
580             } elsif ($string =~ s/^(.)//s) {
581 669         1236 $self->_process_ctl ($1);
582             }
583             }
584             }
585             }
586              
587              
588             # Return the current value of the given option, or undef if it doesn't exist.
589             #
590             sub option_read {
591 0     0 1 0 my $self = shift;
592 0         0 my ($option) = @_;
593              
594 0 0       0 return undef if (not defined $option);
595 0         0 return $self->{'opts'}->{$option};
596             }
597              
598              
599             # Set the value of the given option to the given value, returning the old
600             # value or undef if an invalid option was given.
601             #
602             sub option_set {
603 4     4 1 34 my $self = shift;
604 4         5 my ($option, $value) = @_;
605 4         4 my $prev;
606              
607 4 50       10 return undef if (not defined $option);
608 4 50       8 return undef if (not defined $value);
609 4 50       13 return undef if (not defined $self->{'opts'}->{$option});
610              
611 4         6 $prev = $self->{'opts'}->{$option};
612 4         6 $self->{'opts'}->{$option} = $value;
613 4         10 return $prev;
614             }
615              
616              
617             # Return the attributes of the given row, or undef if out of range.
618             #
619             sub row_attr {
620 15     15 1 82 my $self = shift;
621 15         32 my ($row, $startcol, $endcol) = @_;
622 15         23 my ($data);
623              
624 15 50       34 return undef if ($row < 1);
625 15 50       38 return undef if ($row > $self->{'rows'});
626              
627 15         32 $data = $self->{'scra'}->[$row];
628              
629 15 50 33     40 if (defined $startcol && defined $endcol) {
630 0         0 $data = substr (
631             $data,
632             ($startcol - 1) * 2,
633             (($endcol - $startcol) + 1) * 2
634             );
635             }
636              
637 15         149 return $data;
638             }
639              
640              
641             # Return the textual contents of the given row, or undef if out of range.
642             #
643             sub row_text {
644 212     212 1 1672 my $self = shift;
645 212         267 my ($row, $startcol, $endcol) = @_;
646 212         245 my $text;
647              
648 212 50       391 return undef if ($row < 1);
649 212 50       426 return undef if ($row > $self->{'rows'});
650              
651 212         334 $text = $self->{'scrt'}->[$row];
652              
653 212 50 33     471 if (defined $startcol && defined $endcol) {
654 0         0 $text = substr (
655             $text,
656             $startcol - 1,
657             ($endcol - $startcol) + 1
658             );
659             }
660              
661 212         530 return $text;
662             }
663              
664              
665             # Return the textual contents of the given row, or undef if out of range,
666             # with unused characters represented as a space instead of \0.
667             #
668             sub row_plaintext {
669 0     0 1 0 my $self = shift;
670 0         0 my ($row, $startcol, $endcol) = @_;
671 0         0 my $text;
672              
673 0 0       0 return undef if ($row < 1);
674 0 0       0 return undef if ($row > $self->{'rows'});
675              
676 0         0 $text = $self->{'scrt'}->[$row];
677 0         0 $text =~ s/\0/ /g;
678              
679 0 0 0     0 if (defined $startcol && defined $endcol) {
680 0         0 $text = substr (
681             $text,
682             $startcol - 1,
683             ($endcol - $startcol) + 1
684             );
685             }
686              
687 0         0 return $text;
688             }
689              
690              
691             # Return a set of SGR escape sequences that will change colours and
692             # attributes from "source" to "dest" (packed attributes).
693             #
694             sub sgr_change {
695 0 0   0 1 0 shift if ref($_[0]);
696 0         0 my ($source, $dest) = @_;
697 0         0 my ($out, %off, %on) = ('', (), ());
698              
699 0 0       0 $source = DEFAULT_ATTR_PACKED if (not defined $source);
700 0 0       0 $dest = DEFAULT_ATTR_PACKED if (not defined $dest);
701              
702 0 0       0 return '' if ($source eq $dest);
703 0 0       0 return "\e[m" if ($dest eq DEFAULT_ATTR_PACKED);
704              
705 0         0 my ($sfg, $sbg, $sbo, $sfa, $sst, $sul, $sbl, $srv) = attr_unpack ($source);
706 0         0 my ($dfg, $dbg, $dbo, $dfa, $dst, $dul, $dbl, $drv) = attr_unpack ($dest);
707              
708 0 0 0     0 if (($sfg != $dfg) || ($sbg != $dbg)) {
709 0         0 $out .= sprintf ("\e[m\e[3%d;4%dm", $dfg, $dbg);
710 0         0 ($sbo, $sfa, $sst, $sul, $sbl, $srv) = (0, 0, 0, 0, 0, 0);
711             }
712              
713 0 0 0     0 if (($sbo > $dbo) || ($sfa > $dfa)) {
714 0         0 $off{'22'} = 1;
715 0         0 ($sbo, $sfa) = (0, 0);
716             }
717 0 0       0 $off{'24'} = 1 if ($sul > $dul);
718 0 0       0 $off{'25'} = 1 if ($sbl > $dbl);
719 0 0       0 $off{'27'} = 1 if ($srv > $drv);
720              
721 0 0       0 if (scalar keys %off > 2) {
    0          
722 0         0 $out .= "\e[m";
723 0         0 ($sbo, $sfa, $sst, $sul, $sbl, $srv) = (0, 0, 0, 0, 0, 0);
724             } elsif (scalar keys %off > 0) {
725 0         0 $out .= "\e[" . join (';', keys %off) . "m";
726             }
727              
728 0 0       0 $on{'1'} = 1 if ($dbo > $sbo);
729 0 0 0     0 $on{'2'} = 1 if (($dfa > $sfa) && !($dbo > $sbo));
730 0 0       0 $on{'4'} = 1 if ($dul > $sul);
731 0 0       0 $on{'5'} = 1 if ($dbl > $sbl);
732 0 0       0 $on{'7'} = 1 if ($drv > $srv);
733              
734 0 0       0 $out .= "\e[" . join (';', keys %on) . "m" if (scalar keys %on > 0);
735              
736 0         0 return $out;
737             }
738              
739              
740             # Return the textual contents of the given row, or undef if out of range,
741             # with unused characters represented as a space instead of \0, and any
742             # colour or attribute changes expressed by the relevant SGR escape
743             # sequences.
744             #
745             sub row_sgrtext {
746 0     0 1 0 my ($self, $row, $startcol, $endcol) = @_;
747 0         0 my ($row_text, $row_attr, $text, $char, $attr_cur, $attr_next);
748              
749 0 0       0 return undef if ($row < 1);
750 0 0       0 return undef if ($row > $self->{'rows'});
751              
752 0 0       0 $startcol = 1 if (not defined $startcol);
753 0 0       0 $endcol = $self->{'cols'} if (not defined $endcol);
754              
755 0 0 0     0 return undef if (($startcol < 1) || ($startcol > $self->{'cols'}));
756 0 0 0     0 return undef if (($endcol < 1) || ($endcol > $self->{'cols'}));
757 0 0       0 return undef if ($endcol < $startcol);
758              
759 0         0 $row_text = $self->{'scrt'}->[$row];
760 0         0 $row_attr = $self->{'scra'}->[$row];
761              
762 0         0 $text = '';
763 0         0 $attr_cur = DEFAULT_ATTR_PACKED;
764              
765 0         0 for (; $startcol <= $endcol; $startcol++) {
766 0         0 $char = substr ($row_text, $startcol - 1, 1);
767 0         0 $char =~ s/\0/ /g;
768 0 0       0 $char = ' ' if ($char !~ /./);
769 0         0 $attr_next = substr ($row_attr, ($startcol - 1) * 2, 2);
770 0         0 $text .= $self->sgr_change ($attr_cur, $attr_next) . $char;
771 0         0 $attr_cur = $attr_next;
772             }
773              
774 0         0 $attr_next = DEFAULT_ATTR_PACKED;
775 0         0 $text .= $self->sgr_change ($attr_cur, $attr_next);
776              
777 0         0 return $text;
778             }
779              
780              
781             # Process a string of plain text, with no special characters in it.
782             #
783             sub _process_text {
784 410     410   453 my $self = shift;
785 410         681 my ($text) = @_;
786 410         423 my ($width, $segment);
787              
788 410 100       1092 return if ($self->{'_xon'} == 0);
789              
790 408         620 $width = ($self->{'cols'} + 1) - $self->{'x'};
791              
792 408 100       918 if ($self->{'opts'}->{'LINEWRAP'} == 0) { # no line wrap - truncate
793 407 50       891 return if ($width < 1);
794 407         641 $text = substr ($text, 0, $width);
795 407         886 substr (
796             $self->{'scrt'}->[$self->{'y'}], $self->{'x'} - 1,
797             length $text
798             ) = $text;
799 407         1280 substr (
800             $self->{'scra'}->[$self->{'y'}], 2 * ($self->{'x'} - 1),
801             2 * (length $text)
802             ) = $self->{'attr'} x (length $text);
803 407         527 $self->{'x'} += length $text;
804 407 100       1031 $self->{'x'} = $self->{'cols'}
805             if ($self->{'x'} > $self->{'cols'});
806 407         1037 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
807 407         1532 return;
808             }
809              
810 1         3 while (length $text > 0) { # line wrapping enabled
811 5 100       10 if ($width > 0) {
812 3         7 $segment = substr ($text, 0, $width, '');
813 3         6 substr (
814             $self->{'scrt'}->[$self->{'y'}], $self->{'x'} - 1,
815             length $segment
816             ) = $segment;
817 3         12 substr (
818             $self->{'scra'}->[$self->{'y'}],
819             2 * ($self->{'x'} - 1),
820             2 * (length $segment)
821             ) = $self->{'attr'} x (length $segment);
822 3         6 $self->{'x'} += length $segment;
823             } else {
824 2 50       5 if ($self->{'x'} > $self->{'cols'}) { # wrap to next line
825 2         5 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
826 2         19 $self->callback_call ('LINEFEED', $self->{'y'}, 0);
827 2         4 $self->{'x'} = 1;
828 2         14 $self->_move_down;
829             }
830             }
831 5         14 $width = ($self->{'cols'} + 1) - $self->{'x'};
832             }
833              
834 1         4 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
835             }
836              
837              
838             # Process a control character.
839             #
840             sub _process_ctl {
841 672     672   721 my $self = shift;
842 672         1053 my $ctl = shift;
843 672         642 my ($name, $func);
844              
845 672         1116 $name = $self->{'_ctlseq'}->{$ctl};
846 672 50       1194 return if (not defined $name); # ignore unknown characters
847              
848             # If we're in XOFF mode, ignore anything other than XON
849             #
850 672 100       1259 if ($self->{'_xon'} == 0) {
851 2 100       7 return if ($name ne 'XON');
852             }
853              
854 671         987 $func = $self->{'_funcs'}->{$name};
855 671 50       1125 if (not defined $func) { # do nothing if unsupported
856 0         0 $self->callback_call ('UNKNOWN', $name, $ctl);
857             } else { # call handler function
858 671         703 &{$func} ($self, $name);
  671         1326  
859             }
860             }
861              
862              
863             # Check the escape-sequence buffer, and process it if necessary.
864             #
865             sub _process_escseq {
866 835     835   994 my $self = shift;
867 835         847 my ($prefix, $suffix, $func, $name, $dat);
868 0         0 my @params;
869              
870 835 50       7185 return if (not defined $self->{'_buf'});
871 835 50       1626 return if (length $self->{'_buf'} < 1);
872 835 50       1676 return if ($self->{'_xon'} == 0);
873              
874 835 100       2508 if ($self->{'_inesc'} eq 'OSC') { # in OSC sequence
    100          
    100          
875 32 50       293 if (
    100          
    100          
    50          
    50          
876             $self->{'_buf'} =~ /^0;([^\007]*)(?:\007|\033\\)/
877             ) { # icon & window
878 0         0 $dat = $1;
879 0         0 $self->callback_call ('XWINTITLE', $dat, 0);
880 0         0 $self->callback_call ('XICONNAME', $dat, 0);
881 0         0 $self->{'ic'} = $dat;
882 0         0 $self->{'ti'} = $dat;
883 0         0 $self->{'_buf'} = undef;
884 0         0 $self->{'_inesc'} = '';
885             } elsif (
886             $self->{'_buf'} =~ /^1;([^\007]*)(?:\007|\033\\)/
887             ) { # set icon name
888 1         3 $dat = $1;
889 1         3 $self->callback_call ('XICONNAME', $dat, 0);
890 1         6 $self->{'ic'} = $dat;
891 1         2 $self->{'_buf'} = undef;
892 1         5 $self->{'_inesc'} = '';
893             } elsif (
894             $self->{'_buf'} =~ /^2;([^\007]*)(?:\007|\033\\)/
895             ) { # set window title
896 1         2 $dat = $1;
897 1         12 $self->callback_call ('XWINTITLE', $dat, 0);
898 1         7 $self->{'ti'} = $dat;
899 1         2 $self->{'_buf'} = undef;
900 1         4 $self->{'_inesc'} = '';
901             } elsif (
902             $self->{'_buf'} =~ /^\d+;([^\007]*)(?:\007|\033\\)/
903             ) { # unknown OSC
904 0         0 $self->callback_call (
905             'UNKNOWN', 'OSC', "\033]" . $self->{'_buf'}
906             );
907 0         0 $self->{'_buf'} = undef;
908 0         0 $self->{'_inesc'} = '';
909             } elsif (
910             length $self->{'_buf'} > 1024
911             ) { # OSC too long
912 0         0 $self->callback_call (
913             'UNKNOWN', 'OSC', "\033]" . $self->{'_buf'}
914             );
915 0         0 $self->{'_buf'} = undef;
916 0         0 $self->{'_inesc'} = '';
917             }
918             } elsif ($self->{'_inesc'} eq 'CSI') { # in CSI sequence
919 534         540 foreach $suffix (keys %{$self->{'_csiseq'}}) {
  534         2956  
920 13700 50       24559 next if (length $self->{'_buf'} < length $suffix);
921             next if (
922 13700 100       32054 substr (
923             $self->{'_buf'},
924             (length $self->{'_buf'}) - (length $suffix),
925             length $suffix
926             ) ne $suffix
927             );
928 227         649 $self->{'_buf'} = substr (
929             $self->{'_buf'},
930             0,
931             (length $self->{'_buf'}) - (length $suffix)
932             );
933 227         348 $name = $self->{'_csiseq'}->{$suffix};
934 227         386 $func = $self->{'_funcs'}->{$name};
935 227 50       439 if (not defined $func) { # unsupported sequence
936 0         0 $self->callback_call (
937             'UNKNOWN',
938             $name,
939             "\033[" . $self->{'_buf'} . $suffix
940             );
941 0         0 $self->{'_buf'} = undef;
942 0         0 $self->{'_inesc'} = '';
943 0         0 return;
944             }
945 227         664 @params = split (';', $self->{'_buf'});
946 227         329 $self->{'_buf'} = undef;
947 227         312 $self->{'_inesc'} = '';
948 227         258 &{$func} ($self, @params);
  227         551  
949 227         1298 return;
950             }
951 307 50       2091 if (
952             length $self->{'_buf'} > 64
953             ) { # abort CSI sequence if too long
954 0         0 $self->callback_call (
955             'UNKNOWN', 'CSI', "\033[" . $self->{'_buf'}
956             );
957 0         0 $self->{'_buf'} = undef;
958 0         0 $self->{'_inesc'} = '';
959             }
960             } elsif ($self->{'_inesc'} =~ /_ST$/) {
961 13 100       70 if ($self->{'_buf'} =~ s/\033\\$//) {
    50          
962 1         4 $self->{'_inesc'} =~ s/_ST$//;
963 1         5 $self->callback_call (
964             'STRING', $self->{'_inesc'}, $self->{'_buf'}
965             );
966 1         6 $self->{'_buf'} = undef;
967 1         2 $self->{'_inesc'} = '';
968 1         2 $self->{'_buf'} = undef;
969 1         4 $self->{'_inesc'} = '';
970             } elsif (
971             length $self->{'_buf'} > 1024
972             ) { # string too long
973 0         0 $self->{'_inesc'} =~ s/_ST$//;
974 0         0 $self->callback_call (
975             'STRING', $self->{'_inesc'}, $self->{'_buf'}
976             );
977 0         0 $self->{'_buf'} = undef;
978 0         0 $self->{'_inesc'} = '';
979             }
980             } else { # in ESC sequence
981 256         265 foreach $prefix (
  256         2101  
982             keys %{$self->{'_escseq'}}
983             ) {
984             next if (
985 5725 100       16070 substr ($self->{'_buf'}, 0, length $prefix)
986             ne $prefix
987             );
988 255         527 $name = $self->{'_escseq'}->{$prefix};
989 255         389 $func = $self->{'_funcs'}->{$name};
990 255 50       563 if (not defined $func) { # unsupported sequence
991 0         0 $self->callback_call (
992             'UNKNOWN',
993             $name,
994             "\033" . $self->{'_buf'}
995             );
996 0         0 $self->{'_buf'} = undef;
997 0         0 $self->{'_inesc'} = '';
998 0         0 return;
999             }
1000 255         367 $self->{'_buf'} = undef;
1001 255         343 $self->{'_inesc'} = '';
1002 255         273 &{$func} ($self);
  255         508  
1003 255         1529 return;
1004             }
1005 1 50       10 if (
1006             length $self->{'_buf'} > 8
1007             ) { # abort ESC sequence if too long
1008 0         0 $self->callback_call (
1009             'UNKNOWN',
1010             'ESC',
1011             "\033" . $self->{'_buf'}
1012             );
1013 0         0 $self->{'_buf'} = undef;
1014 0         0 $self->{'_inesc'} = '';
1015             }
1016             }
1017             }
1018              
1019              
1020             # Scroll the scrolling region up such that the text in the scrolling region
1021             # moves down, by the given number of lines.
1022             #
1023             sub _scroll_up {
1024 7     7   7 my $self = shift;
1025 7         9 my $lines = shift;
1026 7         10 my ($attr, $a, $b, $i);
1027              
1028 7 50       22 return if ($lines < 1);
1029              
1030 7         16 $self->callback_call ('SCROLL_UP', $self->{'srb'}, $lines);
1031              
1032 7         35 for ($i = $self->{'srb'}; $i >= ($self->{'srt'} + $lines); $i --) {
1033 8         17 $self->{'scrt'}->[$i] = $self->{'scrt'}->[$i - $lines];
1034 8         26 $self->{'scra'}->[$i] = $self->{'scra'}->[$i - $lines];
1035             }
1036              
1037 7         17 $a = "\000" x $self->{'cols'}; # set text to NUL
1038 7         9 $attr = DEFAULT_ATTR_PACKED;
1039 7         15 $b = $attr x $self->{'cols'}; # set attributes to default
1040              
1041 7   100     51 for (
1042             $i = $self->{'srt'};
1043             ($i <= $self->{'srb'}) && ($i < ($self->{'srt'} + $lines));
1044             $i ++
1045             ) {
1046 14         18 $self->{'scrt'}->[$i] = $a; # blank new lines
1047 14         66 $self->{'scra'}->[$i] = $b; # wipe attributes of new lines
1048             }
1049             }
1050              
1051              
1052             # Scroll the scrolling region down such that the text in the scrolling region
1053             # moves up, by the given number of lines.
1054             #
1055             sub _scroll_down {
1056 9     9   11 my $self = shift;
1057 9         11 my $lines = shift;
1058 9         51 my ($a, $b, $i, $attr);
1059              
1060 9         23 $self->callback_call ('SCROLL_DOWN', $self->{'srt'}, $lines);
1061              
1062 9         28 for ($i = $self->{'srt'}; $i <= ($self->{'srb'} - $lines); $i ++) {
1063 14         31 $self->{'scrt'}->[$i] = $self->{'scrt'}->[$i + $lines];
1064 14         44 $self->{'scra'}->[$i] = $self->{'scra'}->[$i + $lines];
1065             }
1066              
1067 9         22 $a = "\000" x $self->{'cols'}; # set text to NUL
1068 9         10 $attr = DEFAULT_ATTR_PACKED;
1069 9         19 $b = $attr x $self->{'cols'}; # set attributes to default
1070              
1071 9   100     50 for (
1072             $i = $self->{'srb'};
1073             ($i >= $self->{'srt'}) && ($i > ($self->{'srb'} - $lines));
1074             $i --
1075             ) {
1076 16         37 $self->{'scrt'}->[$i] = $a; # blank new lines
1077 16         83 $self->{'scra'}->[$i] = $b; # wipe attributes of new lines
1078             }
1079             }
1080              
1081              
1082             # Move the cursor up the given number of lines, without triggering a GOTO callback, taking scrolling into account.
1083             #
1084             sub _move_up {
1085 11     11   66 my $self = shift;
1086 11         16 my $num = shift;
1087 11 100       24 $num = 1 if (not defined $num);
1088 11 50       21 $num = 1 if ($num < 1);
1089 11         16 $self->{'y'} -= $num;
1090 11 100       30 return if ($self->{'y'} >= $self->{'srt'});
1091 7         23 $self->_scroll_up ($self->{'srt'} - $self->{'y'}); # scroll
1092 7         17 $self->{'y'} = $self->{'srt'};
1093             }
1094              
1095              
1096             # Move the cursor down the given number of lines, without triggering a GOTO
1097             # callback, taking scrolling into account.
1098             #
1099             sub _move_down {
1100 195     195   209 my $self = shift;
1101 195         196 my $num = shift;
1102 195 100       382 $num = 1 if (not defined $num);
1103 195 50       393 $num = 1 if ($num < 1);
1104 195         268 $self->{'y'} += $num;
1105 195 100       914 return if ($self->{'y'} <= $self->{'srb'});
1106 9         25 $self->_scroll_down ($self->{'y'} - $self->{'srb'}); # scroll
1107 9         23 $self->{'y'} = $self->{'srb'};
1108             }
1109              
1110              
1111             sub _code_BEL { # beep
1112 0     0   0 my $self = shift;
1113 0 0 0     0 if ((defined $self->{'_buf'}) && ($self->{'_inesc'} eq 'OSC')) {
1114             # CSI OSC can be terminated with a BEL
1115 0         0 $self->{'_buf'} .= "\007";
1116 0         0 $self->_process_escseq ();
1117             } else {
1118 0         0 $self->callback_call ('BELL', 0, 0);
1119             }
1120             }
1121              
1122             sub _code_BS { # move left 1 character
1123 0     0   0 my $self = shift;
1124 0         0 $self->{'x'} --;
1125 0 0       0 $self->{'x'} = 1 if ($self->{'x'} < 1);
1126             }
1127              
1128             sub _code_CAN { # cancel escape sequence
1129 0     0   0 my $self = shift;
1130 0         0 $self->{'_inesc'} = '';
1131 0         0 $self->{'_buf'} = undef;
1132             }
1133              
1134             sub _code_TBC { # clear tab stop (CSI 3 g = clear all stops)
1135 4     4   6 my $self = shift;
1136 4         5 my $num = shift;
1137 4 100 66     16 if ((defined $num) && ($num eq '3')) {
1138 3         7 $self->{'_tabstops'} = [];
1139             } else {
1140 1         5 $self->{'_tabstops'}->[$self->{'x'}] = undef;
1141             }
1142             }
1143              
1144             sub _code_CHA { # move to column in current row
1145 18     18   21 my $self = shift;
1146 18         23 my $col = shift;
1147 18 100       34 $col = 1 if (not defined $col);
1148 18 50       55 return if ($self->{'x'} == $col);
1149 18         43 $self->callback_call ('GOTO', $col, $self->{'y'});
1150 18         23 $self->{'x'} = $col;
1151 18 50       70 $self->{'x'} = 1 if ($self->{'x'} < 1);
1152 18 100       46 $self->{'x'} = $self->{'cols'} if ($self->{'x'} > $self->{'cols'});
1153             }
1154              
1155             sub _code_CNL { # move cursor down and to column 1
1156 6     6   10 my $self = shift;
1157 6         8 my $num = shift;
1158 6 100       17 $num = 1 if (not defined $num);
1159 6         23 $self->callback_call ('GOTO', 1, $self->{'y'} + $num);
1160 6         11 $self->{'x'} = 1;
1161 6         16 $self->_move_down ($num);
1162             }
1163              
1164             sub _code_CPL { # move cursor up and to column 1
1165 5     5   10 my $self = shift;
1166 5         8 my $num = shift;
1167 5 100       13 $num = 1 if (not defined $num);
1168 5         20 $self->callback_call ('GOTO', $self->{'x'}, $self->{'y'} - $num);
1169 5         9 $self->{'x'} = 1;
1170 5         35 $self->_move_up ($num);
1171             }
1172              
1173             sub _code_CR { # carriage return
1174 181     181   289 my $self = shift;
1175 181         645 $self->{'x'} = 1;
1176             }
1177              
1178             sub _code_CSI { # ESC [
1179 227     227   256 my $self = shift;
1180 227         287 $self->{'_buf'} = ''; # restart ESC buffering
1181 227         379 $self->{'_inesc'} = 'CSI'; # ...for a CSI, not an ESC
1182             }
1183              
1184             sub _code_CUB { # move cursor left
1185 1     1   2 my $self = shift;
1186 1         3 my $num = shift;
1187 1 50       4 $num = 1 if (not defined $num);
1188 1 50       7 $num = 1 if ($num < 1);
1189 1         29 $self->callback_call ('GOTO', $self->{'x'} - $num, $self->{'y'});
1190 1         2 $self->{'x'} -= $num;
1191 1 50       10 $self->{'x'} = 1 if ($self->{'x'} < 1);
1192             }
1193              
1194             sub _code_CUD { # move cursor down
1195 3     3   5 my $self = shift;
1196 3         5 my $num = shift;
1197 3 100       11 $num = 1 if (not defined $num);
1198 3 50       9 $num = 1 if ($num < 1);
1199 3         11 $self->callback_call ('GOTO', $self->{'x'}, $self->{'y'} + $num);
1200 3         8 $self->_move_down ($num);
1201             }
1202              
1203             sub _code_CUF { # move cursor right
1204 4     4   5 my $self = shift;
1205 4         4 my $num = shift;
1206 4 100       11 $num = 1 if (not defined $num);
1207 4 50       9 $num = 1 if ($num < 1);
1208 4         13 $self->callback_call ('GOTO', $self->{'x'} + $num, $self->{'y'});
1209 4         6 $self->{'x'} += $num;
1210 4 50       17 $self->{'x'} = $self->{'cols'} if ($self->{'x'} > $self->{'cols'});
1211             }
1212              
1213             sub _code_CUP { # move cursor to row, column
1214 102     102   121 my $self = shift;
1215 102         152 my ($row, $col) = (@_);
1216 102 100       197 $row = 1 if (not defined $row);
1217 102 100       217 $col = 1 if (not defined $col);
1218 102 50       215 $row = 1 if ($row < 1);
1219 102 50       194 $col = 1 if ($col < 1);
1220 102 100       218 $row = $self->{'rows'} if ($row > $self->{'rows'});
1221 102 100       212 $col = $self->{'cols'} if ($col > $self->{'cols'});
1222 102         225 $self->callback_call ('GOTO', $col, $row);
1223 102         163 $self->{'x'} = $col;
1224 102         198 $self->{'y'} = $row;
1225             }
1226              
1227             sub _code_RI { # reverse line feed
1228 3     3   5 my $self = shift;
1229 3         11 $self->callback_call ('GOTO', $self->{'x'}, $self->{'y'} - 1);
1230 3         13 $self->_move_up;
1231             }
1232              
1233             sub _code_CUU { # move cursor up
1234 3     3   4 my $self = shift;
1235 3         5 my $num = shift;
1236 3 100       10 $num = 1 if (not defined $num);
1237 3 50       8 $num = 1 if ($num < 1);
1238 3         10 $self->callback_call ('GOTO', $self->{'x'}, $self->{'y'} - $num);
1239 3         9 $self->_move_up ($num);
1240             }
1241              
1242             sub _code_DA { # return ESC [ ? 6 c (VT102)
1243 0     0   0 my $self = shift;
1244 0         0 $self->callback_call ('OUTPUT', "\033[?6c", 0);
1245             }
1246              
1247             sub _code_DCH { # delete characters on current line
1248 3     3   5 my $self = shift;
1249 3         3 my $num = shift;
1250 3         3 my ($width, $todel, $line, $lsub, $rsub, $attr);
1251              
1252 3 100       8 $num = 1 if (not defined $num);
1253 3 50       35 $num = 1 if ($num < 1);
1254              
1255 3         5 $width = $self->{'cols'} + 1 - $self->{'x'};
1256 3         4 $todel = $num;
1257 3 100       5 $todel = $width if ($todel > $width);
1258              
1259 3         5 $line = $self->{'scrt'}->[$self->{'y'}];
1260 3         4 ($lsub, $rsub) = ("", "");
1261 3 100       9 $lsub = substr ($line, 0, $self->{'x'} - 1) if ($self->{'x'} > 1);
1262 3         5 $rsub = substr ($line, $self->{'x'} - 1 + $todel);
1263 3         7 $self->{'scrt'}->[$self->{'y'}] = $lsub . $rsub . ("\0" x $todel);
1264              
1265 3         3 $attr = DEFAULT_ATTR_PACKED;
1266 3         4 $line = $self->{'scra'}->[$self->{'y'}];
1267 3         4 ($lsub, $rsub) = ("", "");
1268 3 100       9 $lsub = substr ($line, 0, 2 * ($self->{'x'} - 1)) if ($self->{'x'} > 1);
1269 3         5 $rsub = substr ($line, 2 * ($self->{'x'} - 1 + $todel));
1270 3         6 $self->{'scra'}->[$self->{'y'}] = $lsub . $rsub . ($attr x $todel);
1271              
1272 3         6 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
1273             }
1274              
1275             sub _code_DCS { # device control string (ignored)
1276 0     0   0 my $self = shift;
1277 0         0 $self->{'_buf'} = '';
1278 0         0 $self->{'_inesc'} = 'DCS_ST';
1279             }
1280              
1281             sub _code_DECSTBM { # set scrolling region
1282 15     15   22 my $self = shift;
1283 15         23 my ($top, $bottom) = (@_);
1284 15 50       33 $top = 1 if (not defined $top);
1285 15 50       27 $bottom = $self->{'rows'} if (not defined $bottom);
1286 15 50       38 $top = 1 if ($top < 1);
1287 15 50       30 $bottom = 1 if ($bottom < 1);
1288 15 50       36 $top = $self->{'rows'} if ($top > $self->{'rows'});
1289 15 50       33 $bottom = $self->{'rows'} if ($bottom > $self->{'rows'});
1290 15 50       27 if ($bottom < $top) {
1291 0         0 my $a = $bottom;
1292 0         0 $bottom = $top;
1293 0         0 $top = $a;
1294             }
1295 15         27 $self->{'srt'} = $top;
1296 15         34 $self->{'srb'} = $bottom;
1297             }
1298              
1299             sub _code_DECTCEM { # Cursor on (set); Cursor off (reset)
1300 0     0   0 my $self = shift;
1301 0         0 $self->{'cursor'} = shift;
1302             }
1303              
1304 0     0   0 sub _code_IGN { # ignored control sequence
1305             }
1306              
1307             sub _code_DL { # delete lines
1308 8     8   11 my $self = shift;
1309 8         10 my $lines = shift;
1310 8         9 my ($attr, $scrb, $row);
1311              
1312 8 100       19 $lines = 1 if (not defined $lines);
1313 8 50       19 $lines = 1 if ($lines < 1);
1314              
1315 8         13 $attr = DEFAULT_ATTR_PACKED;
1316              
1317 8         12 $scrb = $self->{'srb'};
1318 8 50       18 $scrb = $self->{'rows'} if ($self->{'y'} > $self->{'srb'});
1319 8 100       20 $scrb = $self->{'srt'} - 1 if ($self->{'y'} < $self->{'srt'});
1320              
1321 8         24 for ($row = $self->{'y'}; $row <= ($scrb - $lines); $row ++) {
1322 6         19 $self->{'scrt'}->[$row] = $self->{'scrt'}->[$row + $lines];
1323 6         14 $self->{'scra'}->[$row] = $self->{'scra'}->[$row + $lines];
1324 6         25 $self->callback_call ('ROWCHANGE', $row, 0);
1325             }
1326              
1327 8   100     51 for (
1328             $row = $scrb;
1329             ($row > ($scrb - $lines)) && ($row >= ($self->{'y'}));
1330             $row --
1331             ) {
1332 14         33 $self->{'scrt'}->[$row] = "\000" x $self->{'cols'};
1333 14         31 $self->{'scra'}->[$row] = $attr x $self->{'cols'};
1334 14         26 $self->callback_call ('ROWCHANGE', $row, 0);
1335             }
1336             }
1337              
1338             sub _code_DSR { # device status report
1339 0     0   0 my $self = shift;
1340 0         0 my $num = shift;
1341 0 0       0 $num = 5 if (not defined $num);
1342 0 0       0 if ($num == 6) { # CPR - cursor position report
    0          
1343 0         0 $self->callback_call (
1344             'OUTPUT', "\e[" . $self->{'y'} . ";" . $self->{'x'} . "R", 0
1345             );
1346             } elsif ($num == 5) { # DSR - reply ESC [ 0 n
1347 0         0 $self->callback_call ('OUTPUT', "\e[0n", 0);
1348             }
1349             }
1350              
1351             sub _code_ECH { # erase characters on current line
1352 3     3   4 my $self = shift;
1353 3         3 my $num = shift;
1354 3         4 my ($width, $todel, $line, $lsub, $rsub, $attr);
1355              
1356 3 100       8 $num = 1 if (not defined $num);
1357 3 50       16 $num = 1 if ($num < 1);
1358              
1359 3         5 $width = $self->{'cols'} + 1 - $self->{'x'};
1360 3         4 $todel = $num;
1361 3 100       11 $todel = $width if ($todel > $width);
1362              
1363 3         7 $line = $self->{'scrt'}->[$self->{'y'}];
1364 3         4 ($lsub, $rsub) = ("", "");
1365 3 100       9 $lsub = substr ($line, 0, $self->{'x'} - 1) if ($self->{'x'} > 1);
1366 3         6 $rsub = substr ($line, $self->{'x'} - 1 + $todel);
1367 3         10 $self->{'scrt'}->[$self->{'y'}] = $lsub . ("\0" x $todel) . $rsub;
1368              
1369 3         3 $attr = DEFAULT_ATTR_PACKED;
1370              
1371              
1372 3         7 $line = $self->{'scra'}->[$self->{'y'}];
1373 3         4 ($lsub, $rsub) = ("", "");
1374 3 100       11 $lsub = substr ($line, 0, 2 * ($self->{'x'} - 1)) if ($self->{'x'} > 1);
1375 3         5 $rsub = substr ($line, 2 * ($self->{'x'} - 1 + $todel));
1376 3         8 $self->{'scra'}->[$self->{'y'}] = $lsub . ($attr x $todel) . $rsub;
1377              
1378 3         14 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
1379             }
1380              
1381             sub _code_ED { # erase display
1382 3     3   3 my $self = shift;
1383 3         4 my $num = shift;
1384 3         4 my ($row, $attr);
1385              
1386 3 100       8 $num = 0 if (not defined $num);
1387              
1388 3         5 $attr = DEFAULT_ATTR_PACKED;
1389              
1390             # Wipe-cursor-to-end is the same as clear-whole-screen if cursor at top left
1391             #
1392 3 50 66     24 $num = 2 if (($num == 0) && ($self->{'x'} == 1) && ($self->{'y'} == 1));
      33        
1393              
1394 3 100       10 if ($num == 0) { # 0 = cursor to end
    100          
1395 1         8 $self->{'scrt'}->[$self->{'y'}] =
1396             substr (
1397             $self->{'scrt'}->[$self->{'y'}],
1398             0,
1399             $self->{'x'} - 1
1400             ) . ("\0" x ($self->{'cols'} + 1 - $self->{'x'}));
1401 1         7 $self->{'scra'}->[$self->{'y'}] =
1402             substr (
1403             $self->{'scra'}->[$self->{'y'}],
1404             0,
1405             2 * ($self->{'x'} - 1)
1406             ) . ($attr x ($self->{'cols'} + 1 - $self->{'x'}));
1407 1         3 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
1408 1         4 for (
1409             $row = $self->{'y'} + 1;
1410             $row <= $self->{'rows'};
1411             $row ++
1412             ) {
1413 2         5 $self->{'scrt'}->[$row] = "\0" x $self->{'cols'};
1414 2         6 $self->{'scra'}->[$row] = $attr x $self->{'cols'};
1415 2         5 $self->callback_call ('ROWCHANGE', $row, 0);
1416             }
1417             } elsif ($num == 1) { # 1 = start to cursor
1418 1         4 for (
1419             $row = 1;
1420             $row < $self->{'y'};
1421             $row ++
1422             ) {
1423 1         3 $self->{'scrt'}->[$row] = "\0" x $self->{'cols'};
1424 1         3 $self->{'scra'}->[$row] = $attr x $self->{'cols'};
1425 1         3 $self->callback_call ('ROWCHANGE', $row, 0);
1426             }
1427 1         5 $self->{'scrt'}->[$self->{'y'}] =
1428             ("\0" x $self->{'x'}) .
1429             substr ($self->{'scrt'}->[$self->{'y'}], $self->{'x'});
1430 1         5 $self->{'scra'}->[$self->{'y'}] =
1431             ($attr x $self->{'x'}) .
1432             substr ($self->{'scra'}->[$self->{'y'}], 2 * $self->{'x'});
1433 1         4 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
1434             } else { # 2 = whole display
1435 1         5 $self->callback_call ('CLEAR', 0, 0);
1436 1         5 for ($row = 1; $row <= $self->{'rows'}; $row ++) {
1437 4         12 $self->{'scrt'}->[$row] = "\0" x $self->{'cols'};
1438 4         15 $self->{'scra'}->[$row] = $attr x $self->{'cols'};
1439             }
1440             }
1441             }
1442              
1443             sub _code_EL { # erase line
1444 3     3   4 my $self = shift;
1445 3         4 my $num = shift;
1446 3         4 my $attr;
1447              
1448 3 100       7 $num = 0 if (not defined $num);
1449              
1450 3         5 $attr = DEFAULT_ATTR_PACKED;
1451              
1452 3 100       12 if ($num == 0) { # 0 = cursor to end of line
    100          
1453 1         9 $self->{'scrt'}->[$self->{'y'}] =
1454             substr (
1455             $self->{'scrt'}->[$self->{'y'}],
1456             0,
1457             $self->{'x'} - 1
1458             ) . ("\0" x ($self->{'cols'} + 1 - $self->{'x'}));
1459 1         7 $self->{'scra'}->[$self->{'y'}] =
1460             substr (
1461             $self->{'scra'}->[$self->{'y'}],
1462             0,
1463             2 * ($self->{'x'} - 1)
1464             ) . ($attr x ($self->{'cols'} + 1 - $self->{'x'}));
1465 1         3 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
1466             } elsif ($num == 1) { # 1 = start of line to cursor
1467 1         8 $self->{'scrt'}->[$self->{'y'}] =
1468             ("\0" x $self->{'x'}) .
1469             substr ($self->{'scrt'}->[$self->{'y'}], $self->{'x'});
1470 1         23 $self->{'scra'}->[$self->{'y'}] =
1471             ($attr x $self->{'x'}) .
1472             substr ($self->{'scra'}->[$self->{'y'}], 2 * $self->{'x'});
1473 1         5 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
1474             } else { # 2 = whole line
1475 1         5 $self->{'scrt'}->[$self->{'y'}] = "\0" x $self->{'cols'};
1476 1         10 $self->{'scra'}->[$self->{'y'}] = $attr x $self->{'cols'};
1477 1         4 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
1478             }
1479             }
1480              
1481             sub _code_ESC { # start escape sequence
1482 258     258   370 my $self = shift;
1483 258 100 66     652 if ((defined $self->{'_buf'}) && ($self->{'_inesc'} =~ /OSC|_ST/)) {
1484             # Some sequences are terminated with an ST
1485 3         6 $self->{'_buf'} .= "\033";
1486 3         7 $self->_process_escseq ();
1487 3         12 return;
1488             }
1489 255         366 $self->{'_buf'} = ''; # set ESC buffer
1490 255         966 $self->{'_inesc'} = 'ESC'; # ...for ESC, not CSI
1491             }
1492              
1493             sub _code_LF { # line feed
1494 184     184   244 my $self = shift;
1495 184 100       417 $self->_code_CR () # cursor to start of line
1496             if ($self->{'opts'}->{'LFTOCRLF'} != 0);
1497 184         473 $self->callback_call ('LINEFEED', $self->{'y'}, 0);
1498 184         346 $self->_move_down ();
1499             }
1500              
1501             sub _code_NEL { # newline
1502 2     2   3 my $self = shift;
1503 2         9 $self->_code_CR (); # cursor always to start
1504 2         17 $self->_code_LF (); # standard line feed
1505             }
1506              
1507             sub _code_HT { # horizontal tab to next tab stop
1508 53     53   52 my $self = shift;
1509 53         57 my ($newx, $spaces, $width);
1510              
1511 53 50 33     129 if (
1512             ($self->{'opts'}->{'LINEWRAP'} != 0)
1513             && ($self->{'x'} >= $self->{'cols'})
1514             ) {
1515 0         0 $self->callback_call ('LINEFEED', $self->{'y'}, 0);
1516 0         0 $self->{'x'} = 1;
1517 0         0 $self->_move_down;
1518             }
1519              
1520 53         62 $newx = $self->{'x'} + 1;
1521 53   100     190 while ($newx < $self->{'cols'} && not $self->{'_tabstops'}->[$newx]) {
1522 335         1119 $newx++;
1523             }
1524              
1525 53         71 $width = ($self->{'cols'} + 1) - $self->{'x'};
1526 53         56 $spaces = $newx - $self->{'x'};
1527 53 50       95 $spaces = $width + 1 if ($spaces > $width);
1528              
1529 53 50       6089 if ($spaces > 0) {
1530 53         64 $self->{'x'} += $spaces;
1531 53 100       402 $self->{'x'} = $self->{'cols'} if ($self->{'x'} > $self->{'cols'});
1532             }
1533             }
1534              
1535             sub _code_HTS { # set tab stop at current column
1536 13     13   14 my $self = shift;
1537 13         33 $self->{'_tabstops'}->[$self->{'x'}] = 1;
1538             }
1539              
1540             sub _code_ICH { # insert blank characters
1541 3     3   5 my $self = shift;
1542 3         5 my $num = shift;
1543 3         5 my ($width, $toins, $line, $lsub, $rsub, $attr);
1544              
1545 3 100       8 $num = 1 if (not defined $num);
1546 3 50       7 $num = 1 if ($num < 1);
1547              
1548 3         6 $width = $self->{'cols'} + 1 - $self->{'x'};
1549 3         6 $toins = $num;
1550 3 100       7 $toins = $width if ($toins > $width);
1551              
1552 3         8 $line = $self->{'scrt'}->[$self->{'y'}];
1553 3         4 ($lsub, $rsub) = ("", "");
1554 3 100       11 $lsub = substr ($line, 0, $self->{'x'} - 1) if ($self->{'x'} > 1);
1555 3         8 $rsub = substr ($line, $self->{'x'} - 1, $width - $toins);
1556 3         10 $self->{'scrt'}->[$self->{'y'}] = $lsub . ("\0" x $toins) . $rsub;
1557              
1558 3         7 $attr = DEFAULT_ATTR_PACKED;
1559 3         5 $line = $self->{'scra'}->[$self->{'y'}];
1560 3         6 ($lsub, $rsub) = ("", "");
1561 3 100       18 $lsub = substr ($line, 0, 2 * ($self->{'x'} - 1)) if ($self->{'x'} > 1);
1562 3         10 $rsub = substr ($line, 2 * ($self->{'x'} - 1), 2 * ($width - $toins));
1563 3         8 $self->{'scra'}->[$self->{'y'}] = $lsub . ($attr x $toins) . $rsub;
1564              
1565 3         22 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
1566             }
1567              
1568             sub _code_IL { # insert blank lines
1569 8     8   13 my $self = shift;
1570 8         11 my $lines = shift;
1571 8         10 my ($attr, $scrb, $row);
1572              
1573 8 100       22 $lines = 1 if (not defined $lines);
1574 8 50       19 $lines = 1 if ($lines < 1);
1575              
1576 8         12 $attr = DEFAULT_ATTR_PACKED;
1577              
1578 8         12 $scrb = $self->{'srb'};
1579 8 50       21 $scrb = $self->{'rows'} if ($self->{'y'} > $self->{'srb'});
1580 8 100       45 $scrb = $self->{'srt'} - 1 if ($self->{'y'} < $self->{'srt'});
1581              
1582 8         27 for ($row = $scrb; $row >= ($self->{'y'} + $lines); $row --) {
1583 6         19 $self->{'scrt'}->[$row] = $self->{'scrt'}->[$row - $lines];
1584 6         14 $self->{'scra'}->[$row] = $self->{'scra'}->[$row - $lines];
1585 6         16 $self->callback_call ('ROWCHANGE', $row, 0);
1586             }
1587              
1588 8   100     55 for (
1589             $row = $self->{'y'};
1590             ($row <= $scrb) && ($row < ($self->{'y'} + $lines));
1591             $row ++
1592             ) {
1593 14         37 $self->{'scrt'}->[$row] = "\000" x $self->{'cols'};
1594 14         29 $self->{'scra'}->[$row] = $attr x $self->{'cols'};
1595 14         32 $self->callback_call ('ROWCHANGE', $row, 0);
1596             }
1597             }
1598              
1599             sub _code_PM { # privacy message (ignored)
1600 0     0   0 my $self = shift;
1601 0         0 $self->{'_buf'} = '';
1602 0         0 $self->{'_inesc'} = 'PM_ST';
1603             }
1604              
1605             sub _code_APC { # application program command (ignored)
1606 1     1   3 my $self = shift;
1607 1         2 $self->{'_buf'} = '';
1608 1         2 $self->{'_inesc'} = 'APC_ST';
1609             }
1610              
1611             sub _code_OSC { # operating system command
1612 2     2   3 my $self = shift;
1613 2         3 $self->{'_buf'} = ''; # restart buffering
1614 2         4 $self->{'_inesc'} = 'OSC'; # ...for OSC, not ESC or CSI
1615             }
1616              
1617             sub _code_RIS { # reset
1618 0     0   0 my $self = shift;
1619 0         0 $self->reset ();
1620             }
1621              
1622             sub _toggle_mode { # set/reset modes
1623 0     0   0 my $self = shift;
1624 0         0 my ($flag, @modes) = @_;
1625              
1626 0         0 foreach my $mode (@modes) {
1627 0         0 my $name = $self->{'_modeseq'}->{$mode};
1628 0         0 my $func = undef;
1629 0 0       0 $func = $self->{'_funcs'}->{$name} if (defined $name);
1630 0 0       0 if (not defined $func) { # unsupported seq.
1631 0 0       0 $self->callback_call (
1632             'UNKNOWN',
1633             $name,
1634             "\033[${mode}" . ($flag ? "h" : "l")
1635             );
1636 0         0 $self->{'_buf'} = undef;
1637 0         0 $self->{'_inesc'} = '';
1638 0         0 return;
1639             }
1640 0         0 $self->{'_buf'} = undef;
1641 0         0 $self->{'_inesc'} = '';
1642 0         0 &{$func} ($self, $flag);
  0         0  
1643 0         0 return;
1644             }
1645             }
1646              
1647             sub _code_RM { # reset mode
1648 0     0   0 my $self = shift;
1649 0         0 $self->_toggle_mode(0, @_);
1650             }
1651              
1652             sub _code_SM { # set mode
1653 0     0   0 my $self = shift;
1654 0         0 $self->_toggle_mode(1, @_);
1655             }
1656              
1657             sub _code_SGR { # set graphic rendition
1658 30     30   38 my $self = shift;
1659 30         58 my (@parms) = (@_);
1660 30         49 my ($val, $fg, $bg, $bo, $fa, $st, $ul, $bl, $rv);
1661              
1662 30         79 ($fg, $bg, $bo, $fa, $st, $ul, $bl, $rv) =
1663             $self->attr_unpack ($self->{'attr'});
1664              
1665 30 100       94 @parms = (0) if ($#parms < 0); # ESC [ m = ESC [ 0 m
1666              
1667 30         75 while (defined ($val = shift @parms)) {
1668 37 100 66     473 if ($val == 0) { # reset all attributes
    100 66        
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    50          
    50          
    100          
    100          
    100          
    100          
    50          
1669 5         23 ($fg, $bg, $bo, $fa, $st, $ul, $bl, $rv) = DEFAULT_ATTR;
1670             } elsif ($val == 1) { # bold ON
1671 4         14 ($bo, $fa) = (1, 0);
1672             } elsif ($val == 2) { # faint ON
1673 3         9 ($bo, $fa) = (0, 1);
1674             } elsif ($val == 4) { # underline ON
1675 2         6 $ul = 1;
1676             } elsif ($val == 5) { # blink ON
1677 2         6 $bl = 1;
1678             } elsif ($val == 7) { # reverse video ON
1679 2         5 $rv = 1;
1680             } elsif ($val == 21) { # normal intensity
1681 1         4 ($bo, $fa) = (0, 0);
1682             } elsif ($val == 22) { # normal intensity
1683 1         5 ($bo, $fa) = (0, 0);
1684             } elsif ($val == 24) { # underline OFF
1685 0         0 $ul = 0;
1686             } elsif ($val == 25) { # blink OFF
1687 0         0 $bl = 0;
1688             } elsif ($val == 27) { # reverse video OFF
1689 0         0 $rv = 0;
1690             } elsif (($val >= 30) && ($val <= 37)) {# set foreground colour
1691 7         20 $fg = $val - 30;
1692             } elsif ($val == 38) { # underline on, default fg
1693 1         4 ($ul, $fg) = (1, 7);
1694             } elsif ($val == 39) { # underline off, default fg
1695 1         3 ($ul, $fg) = (0, 7);
1696             } elsif (($val >= 40) && ($val <= 47)) {# set background colour
1697 7         25 $bg = $val - 40;
1698             } elsif ($val == 49) { # default background
1699 1         4 $bg = 0;
1700             }
1701             }
1702              
1703 30         77 $self->{'attr'} = $self->attr_pack ($fg, $bg, $bo, $fa, $st, $ul, $bl, $rv);
1704             }
1705              
1706             sub _code_VPA { # move to row (current column)
1707 1     1   4 my $self = shift;
1708 1         3 my $row = shift;
1709 1 50       5 $row = 1 if (not defined $row);
1710 1 50       6 return if ($self->{'y'} == $row);
1711 1         3 $self->{'y'} = $row;
1712 1 50       4 $self->{'y'} = 1 if ($self->{'y'} < 1);
1713 1 50       5 $self->{'y'} = $self->{'rows'} if ($self->{'y'} > $self->{'rows'});
1714             }
1715              
1716             sub _code_DECALN { # fill screen with E's
1717 1     1   2 my $self = shift;
1718 1         2 my ($row, $attr);
1719              
1720 1         2 $attr = DEFAULT_ATTR_PACKED;
1721              
1722 1         4 for ($row = 1; $row <= $self->{'rows'}; $row ++) {
1723 3         8 $self->{'scrt'}->[$row] = 'E' x $self->{'cols'};
1724 3         44 $self->{'scra'}->[$row] = $attr x $self->{'cols'};
1725 3         9 $self->callback_call ('ROWCHANGE', $self->{'y'}, 0);
1726             }
1727              
1728 1         2 $self->{'x'} = 1;
1729 1         2 $self->{'y'} = 1;
1730             }
1731              
1732             sub _code_DECSC { # save state
1733 2     2   4 my $self = shift;
1734 2         2 my @state;
1735              
1736 2         3 @state = @{$self->{'_decsc'}};
  2         7  
1737 2         13 push (
1738             @state,
1739             [
1740             $self->{'x'},
1741             $self->{'y'},
1742             $self->{'attr'},
1743             $self->{'ti'},
1744             $self->{'ic'},
1745             $self->{'cursor'}
1746             ]
1747             );
1748 2         6 $self->{'_decsc'} = [ @state ];
1749             }
1750              
1751             sub _code_DECRC { # restore most recently saved state
1752 2     2   50 my $self = shift;
1753 2         4 my @state;
1754             my $ref;
1755              
1756 2         3 @state = @{$self->{'_decsc'}};
  2         6  
1757 2 50       8 return if ($#state < 0);
1758              
1759 2         3 $ref = pop @state;
1760              
1761             (
1762 2         10 $self->{'x'},
1763             $self->{'y'},
1764             $self->{'attr'},
1765             $self->{'ti'},
1766             $self->{'ic'},
1767             $self->{'cursor'}
1768             ) = @$ref;
1769              
1770 2         7 $self->{'_decsc'} = [ @state ];
1771             }
1772              
1773             sub _code_CUPSV { # save cursor position
1774 2     2   4 my $self = shift;
1775 2         2 my @state;
1776              
1777 2         3 @state = @{$self->{'_cupsv'}};
  2         5  
1778 2         7 push (
1779             @state,
1780             [
1781             $self->{'x'},
1782             $self->{'y'}
1783             ]
1784             );
1785 2         6 $self->{'_cupsv'} = [ @state ];
1786             }
1787              
1788             sub _code_CUPRS { # restore cursor position
1789 2     2   38 my $self = shift;
1790 2         4 my @state;
1791             my $ref;
1792              
1793 2         3 @state = @{$self->{'_cupsv'}};
  2         6  
1794 2 50       6 return if ($#state < 0);
1795              
1796 2         2 $ref = pop @state;
1797              
1798             (
1799 2         6 $self->{'x'},
1800             $self->{'y'}
1801             ) = @$ref;
1802              
1803 2         5 $self->{'_cupsv'} = [ @state ];
1804             }
1805              
1806             sub _code_XON { # resume character processing
1807 2     2   3 my $self = shift;
1808 2         8 $self->{'_xon'} = 1;
1809             }
1810              
1811             sub _code_XOFF { # stop character processing
1812 2     2   3 my $self = shift;
1813 2 100       10 return if ($self->{'opts'}->{'IGNOREXOFF'});
1814 1         5 $self->{'_xon'} = 0;
1815             }
1816              
1817             1;
1818             __END__