1 |
#!/usr/bin/perl |
2 |
# $Id$ |
3 |
|
4 |
# This program manages a child process started with POE::Wheel::Run. |
5 |
# Wheel::Run is a lot like IPC::Run but event driven. Because it |
6 |
# doesn't block, though, it can be instantiated several times to drive |
7 |
# many child processes. |
8 |
# |
9 |
# This sample runs a single process, nethack, as its test program. It |
10 |
# adds two "features" to stock nethack: |
11 |
# |
12 |
# 1. It changes all "@" characters to random colors. It's annoying, |
13 |
# but it lets you know the program's working. |
14 |
# |
15 |
# 2. Ctrl+C sends a string of random movement commands to the child |
16 |
# process. This causes your player to go a little berzerk. |
17 |
|
18 |
use warnings; |
19 |
use strict; |
20 |
|
21 |
sub PROGRAM () { "nethack" } |
22 |
|
23 |
use POSIX; |
24 |
use POE qw( Wheel::ReadWrite Wheel::Run Filter::Stream ); |
25 |
|
26 |
### Handle the _start event. This sets things in motion. |
27 |
|
28 |
sub handle_start { |
29 |
my ( $kernel, $heap ) = @_[ KERNEL, HEAP ]; |
30 |
|
31 |
# Set a signal handler. |
32 |
|
33 |
$kernel->sig( CHLD => "got_sigchld" ); |
34 |
|
35 |
# Save the original terminal settings so they can be restored later. |
36 |
|
37 |
$heap->{stdin_tio} = POSIX::Termios->new(); |
38 |
$heap->{stdin_tio}->getattr(0); |
39 |
$heap->{stdout_tio} = POSIX::Termios->new(); |
40 |
$heap->{stdout_tio}->getattr(1); |
41 |
$heap->{stderr_tio} = POSIX::Termios->new(); |
42 |
$heap->{stderr_tio}->getattr(2); |
43 |
|
44 |
# Put the terminal into raw input mode. Otherwise discrete |
45 |
# keystrokes will not be read immediately. |
46 |
|
47 |
my $tio = POSIX::Termios->new(); |
48 |
$tio->getattr(0); |
49 |
my $lflag = $tio->getlflag; |
50 |
$lflag &= ~( ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN | ISIG ); |
51 |
$tio->setlflag($lflag); |
52 |
my $iflag = $tio->getiflag; |
53 |
$iflag &= ~( BRKINT | INPCK | ISTRIP | IXON ); |
54 |
$tio->setiflag($iflag); |
55 |
my $cflag = $tio->getcflag; |
56 |
$cflag &= ~( CSIZE | PARENB ); |
57 |
$tio->setcflag($cflag); |
58 |
$tio->setattr( 0, TCSANOW ); |
59 |
|
60 |
# Start the terminal reader/writer. |
61 |
|
62 |
$heap->{stdio} = POE::Wheel::ReadWrite->new |
63 |
( InputHandle => \*STDIN, |
64 |
OutputHandle => \*STDOUT, |
65 |
InputEvent => "got_terminal_stdin", |
66 |
Filter => POE::Filter::Stream->new(), |
67 |
); |
68 |
|
69 |
# Start the asynchronous child process. |
70 |
|
71 |
$heap->{program} = POE::Wheel::Run->new |
72 |
( Program => PROGRAM, |
73 |
Conduit => "pty", |
74 |
StdoutEvent => "got_child_stdout", |
75 |
Filter => POE::Filter::Stream->new(), |
76 |
); |
77 |
} |
78 |
|
79 |
### Handle the _stop event. This restores the original terminal |
80 |
### settings when we're done. That's very important. |
81 |
|
82 |
sub handle_stop { |
83 |
my $heap = $_[HEAP]; |
84 |
$heap->{stdin_tio}->setattr( 0, TCSANOW ); |
85 |
$heap->{stdout_tio}->setattr( 1, TCSANOW ); |
86 |
$heap->{stderr_tio}->setattr( 2, TCSANOW ); |
87 |
} |
88 |
|
89 |
### Handle terminal STDIN. Send it to the background program's STDIN. |
90 |
### If the user presses ^C, then also go berserk a little. |
91 |
|
92 |
sub handle_terminal_stdin { |
93 |
my ( $heap, $input ) = @_[ HEAP, ARG0 ]; |
94 |
|
95 |
while ( $input =~ m/\003/g ) { |
96 |
my $count = int( 5 + rand(10) ); |
97 |
my $random_walk = ''; |
98 |
$random_walk .= int( rand(9) + 1 ) while $count--; |
99 |
$random_walk =~ tr[5][]d; |
100 |
substr( $input, pos($input) - 1, 1 ) = $random_walk; |
101 |
} |
102 |
|
103 |
$heap->{program}->put($input); |
104 |
} |
105 |
|
106 |
### Handle STDOUT from the child program. Send it to the terminal's |
107 |
### STDOUT. Oh, and do something silly like change the foreground |
108 |
### color of "@" characters to something random. :) |
109 |
|
110 |
sub handle_child_stdout { |
111 |
my ( $heap, $input ) = @_[ HEAP, ARG0 ]; |
112 |
my $color = int rand 8; |
113 |
$input =~ s/\@/\e[3${color}m\@\e[0m/g; |
114 |
$heap->{stdio}->put($input); |
115 |
} |
116 |
|
117 |
### Handle SIGCHLD. Shut down if the exiting child process was the |
118 |
### one we've been managing. |
119 |
|
120 |
sub handle_sigchld { |
121 |
my ( $heap, $child_pid ) = @_[ HEAP, ARG1 ]; |
122 |
if ( $child_pid == $heap->{program}->PID ) { |
123 |
delete $heap->{program}; |
124 |
delete $heap->{stdio}; |
125 |
} |
126 |
return 0; |
127 |
} |
128 |
|
129 |
### Start a session to encapsulate the previous features. |
130 |
|
131 |
POE::Session->create |
132 |
( inline_states => |
133 |
{ _start => \&handle_start, |
134 |
_stop => \&handle_stop, |
135 |
got_terminal_stdin => \&handle_terminal_stdin, |
136 |
got_child_stdout => \&handle_child_stdout, |
137 |
got_sigchld => \&handle_sigchld, |
138 |
}, |
139 |
); |
140 |
|
141 |
### Start POE's main loop, which runs the session until it's done. |
142 |
|
143 |
$poe_kernel->run(); |
144 |
exit 0; |