/[ttyrec]/jsttyplay/Term/Emulator.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /jsttyplay/Term/Emulator.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (show annotations)
Tue Feb 17 18:12:32 2009 UTC (15 years, 2 months ago) by dpavlin
File size: 6410 byte(s)
import upstream from http://encryptio.com/code/jsttyplay

1 package Term::Emulator;
2 use strict;
3
4 use IO::Pty;
5 use IO::Tty qw/ TIOCSWINSZ TCGETA TCSETA ICANON ISIG IEXTEN ECHO ECHOE ECHOKE ECHOCTL PENDIN ICRNL IXON IXANY IMAXBEL BRKINT OPOST ONLCR TIOCGETP TIOCSETN /;
6 use Term::Emulator::Parser;
7
8 use IO::Handle;
9 use Carp;
10 use Time::HiRes qw/ time sleep /;
11
12 our $STDIN, $STDOUT, $STDERR;
13 open $STDIN, "<&=", \*STDIN or die;
14 open $STDOUT, ">&=", \*STDOUT or die;
15 open $STDERR, ">&=", \*STDERR or die;
16
17 sub new {
18 my ($class, %args) = @_;
19 my $self = bless {}, $class;
20
21 my $width = exists $args{'width'} ? delete $args{'width'} : 80;
22 my $height = exists $args{'height'} ? delete $args{'height'} : 24;
23
24 $self->{'term'} = Term::Emulator::Parser->new(width => $width, height => $height);
25 $self->{'pty'} = IO::Pty->new;
26 $self->{'pid'} = undef;
27
28 $self->{'buffers'} = {
29 ptysend => '',
30 };
31
32 $self->set_size($width, $height);
33
34 return $self;
35 }
36
37 sub set_size {
38 my ($self, $width, $height) = @_;
39
40 # TODO - kill WINCH the subproccess
41 # TODO - update the Term::Emulator::Parser size
42
43 ioctl $self->pty->slave, TIOCSWINSZ, pack "S!S!S!S!", $height, $width, $height, $width;
44
45 return $self;
46 }
47
48 sub spawn {
49 # based on IO::Pty::Easy v0.03
50 my ($self, @program) = @_;
51 my $slave = $self->pty->slave;
52
53 croak "Can't spawn a process when one is already running!"
54 if $self->is_active;
55
56 # for returning errors from the child process
57 my ($readp, $writep);
58 unless ( pipe($readp, $writep) ) {
59 croak "Failed to create a pipe";
60 }
61 $writep->autoflush(1);
62
63 # fork the child
64 my $pid = fork;
65 croak "Couldn't fork" unless defined $pid;
66
67 unless ( $pid ) {
68 # child process
69 close $readp;
70
71 $self->pty->make_slave_controlling_terminal;
72 close $self->pty;
73 $slave->set_raw;
74
75 # reopen STD{IN,OUT,ERR} to use the pty
76 eval {
77 open($STDIN, "<&", $slave->fileno) or croak "Couldn't reopen STDIN for reading: $!";
78 open($STDOUT, ">&", $slave->fileno) or croak "Couldn't reopen STDOUT for writing: $!";
79 open($STDERR, ">&", $slave->fileno) or croak "Couldn't reopen STDERR for writing: $!";
80 };
81 warn "t: $@" if $@;
82 die "t: $@" if $@;
83
84 close $slave;
85
86 system("stty","sane");
87
88 exec(@program);
89
90 # exec failed, tell our parent what happened
91 print $writep $! + 0;
92 carp "Cannot exec(@program): $!";
93
94 exit 1;
95 }
96
97 # parent process continues
98 close $writep;
99 $self->{'pid'} = $pid;
100
101 $self->pty->close_slave;
102 $self->pty->set_raw;
103
104 # this will block until EOF (exec killed the filehandle) or we get the error (exec failed)
105 my $errno;
106 my $read_bytes = sysread($readp, $errno, 256);
107
108 unless ( defined $read_bytes ) {
109 # something went bad wrong with the sysread
110 my $err = $!;
111 kill TERM => $pid;
112 close $readp;
113 # wait up to 2 seconds for the process to die
114 my $starttime = time;
115 sleep 0.01 while time() - $starttime < 2 and $self->is_active;
116 if ( time() - $starttime >= 2 ) { # harmless race condition
117 # beat the living crap out of the process
118 kill KILL => $pid;
119 }
120 croak "Cannot sync with child: $err";
121 }
122
123 close $readp;
124
125 if ( $read_bytes > 0 ) {
126 # child couldn't exec
127 $errno = $errno + 0;
128 $self->_wait_for_inactive;
129 croak "Cannot exec(@program): $errno";
130 }
131
132 return $self;
133 }
134
135 sub userinput {
136 my ($self, $input) = @_;
137 $self->term->userinput($input);
138 $self->_move_term_sendbuf;
139 $self->work_for(0);
140 return $self;
141 }
142
143 sub key {
144 my ($self, $key) = @_;
145 $self->term->key($key);
146 $self->_move_term_sendbuf;
147 $self->work_for(0);
148 return $self;
149 }
150
151 sub work_for {
152 my ($self, $time) = @_;
153 my $start = time;
154 my $ptyfn = fileno($self->pty);
155 my $loops = 0;
156 while ( 1 ) {
157 my $readvec = '';
158 vec($readvec, $ptyfn, 1) = 1;
159
160 my $writevec = '';
161 if ( length $self->{'buffers'}->{'ptysend'} ) {
162 vec($writevec, $ptyfn, 1) = 1;
163 }
164
165 my $len = ($start + $time) - time;
166 if ( $len < 0 ) {
167 last if $loops;
168 $len = 0;
169 }
170 my $nfound = select($readvec, $writevec, undef, $len);
171 last unless $nfound; # if no handles have been written to, we've finished our time chunk
172
173 # check for reads
174 if ( vec($readvec, $ptyfn, 1) ) {
175 # pty can read
176
177 my $buf = '';
178 my $n = sysread $self->pty, $buf, 16384;
179 if ( $n == 0 ) {
180 # EOF
181 $self->kill if $self->is_active;
182 last;
183 }
184 $self->term->parse($buf); # pass data sent from the pty slave to the terminal
185 $self->_move_term_sendbuf;
186 }
187
188 # check for writes if we have outstanding buffers
189 if ( vec($writevec, $ptyfn, 1) ) {
190 my $nchars = syswrite $self->pty, $self->{'buffers'}->{'ptysend'};
191 if ( $nchars ) {
192 $self->{'buffers'}->{'ptysend'} = substr $self->{'buffers'}->{'ptysend'}, $nchars;
193 }
194 }
195
196 # TODO: check for error conditions
197 $loops++;
198 }
199
200 return $self;
201 }
202
203 sub _move_term_sendbuf {
204 my ($self) = @_;
205 my $buf = $self->term->output;
206 if ( length $buf ) {
207 $self->{'buffers'}->{'ptysend'} .= $buf;
208 $self->term->output = '';
209 }
210 }
211
212 sub is_active {
213 my ($self) = @_;
214
215 return 0 unless defined $self->{'pid'};
216 if ( kill 0, $self->{'pid'} ) {
217 return 1;
218 } else {
219 undef $self->{'pid'};
220 }
221 }
222
223 sub kill {
224 my ($self, $signal) = @_;
225 $signal = "KILL" unless defined $signal;
226
227 return 0 unless $self->is_active;
228 return kill $signal, $self->{'pid'};
229 }
230
231 sub stop_it_in {
232 my ($self, $maxtime) = @_;
233 $maxtime = 5 unless defined $maxtime;
234 return 0 unless $self->is_active;
235
236 kill KILL => $self->{'pid'};
237 my $killtime = time;
238 while ( time() - $killtime < $maxtime ) {
239 return 1 if not $self->is_active;
240 sleep 0.05;
241 }
242
243 kill TERM => $self->{'pid'};
244
245 sleep 0.01;
246 return $self->is_active;
247 }
248
249 sub _wait_for_inactive {
250 my ($self) = @_;
251 sleep 0.01 while $self->is_active;
252 }
253
254 sub pty { $_[0]->{'pty'} }
255 sub term { $_[0]->{'term'} }
256
257 1;
258

  ViewVC Help
Powered by ViewVC 1.1.26