1 | =head1 NAME |
1 | =head1 NAME |
2 | |
2 | |
3 | Mozilla::Plugin - embed perl into mozilla/netscape. |
3 | Browser::Plugin - embed perl into mozilla/netscape/ie/opera. |
4 | |
4 | |
5 | =head1 SYNOPSIS |
5 | =head1 SYNOPSIS |
6 | |
6 | |
7 | use Mozilla::Plugin; |
7 | use Browser::Plugin; |
8 | |
8 | |
9 | =head1 DESCRIPTION |
9 | =head1 DESCRIPTION |
10 | |
10 | |
11 | sorry... |
11 | sorry... |
12 | |
12 | |
13 | =over 4 |
13 | =over 4 |
14 | |
14 | |
15 | =cut |
15 | =cut |
16 | |
16 | |
17 | package Mozilla::Plugin; |
17 | package Browser::Plugin; |
18 | |
18 | |
19 | use base Exporter; |
19 | use base Exporter; |
20 | use Storable; |
20 | use Storable; |
21 | #use XSLoader; |
21 | #use XSLoader; |
22 | |
22 | |
23 | $VERSION = 0.01; |
23 | $VERSION = 0.01; |
24 | @EXPORT = qw(); |
24 | @EXPORT = qw(); |
25 | |
25 | |
26 | #XSLoader::load __PACKAGE__, $VERSION; |
26 | #XSLoader::load __PACKAGE__, $VERSION; |
27 | use Data::Dumper;#d# |
27 | |
|
|
28 | my $interface; # Browser::Plugin::xxx (design weakness) |
|
|
29 | |
|
|
30 | my $debug = 1; |
|
|
31 | |
|
|
32 | sub NP_VERSION_MAJOR (){ 0 } |
|
|
33 | sub NP_VERSION_MINOR (){ 9 } |
|
|
34 | sub NP_EMBED (){ 1 } |
|
|
35 | sub NP_FULL (){ 2 } |
|
|
36 | sub NP_NORMAL (){ 1 } |
|
|
37 | sub NP_SEEK (){ 2 } |
|
|
38 | sub NP_ASFILE (){ 3 } |
|
|
39 | sub NP_ASFILEONLY (){ 4 } |
|
|
40 | sub NP_MAXREADY (){ 2147483647 } |
|
|
41 | sub NPERR_NO_ERROR (){ 0 } |
|
|
42 | sub NPERR_GENERIC_ERROR (){ 1 } |
|
|
43 | sub NPERR_INVALID_INSTANCE_ERROR (){ 2 } |
|
|
44 | sub NPERR_INVALID_FUNCTABLE_ERROR (){ 3 } |
|
|
45 | sub NPERR_MODULE_LOAD_FAILED_ERROR (){ 4 } |
|
|
46 | sub NPERR_OUT_OF_MEMORY_ERROR (){ 5 } |
|
|
47 | sub NPERR_INVALID_PLUGIN_ERROR (){ 6 } |
|
|
48 | sub NPERR_INVALID_PLUGIN_DIR_ERROR (){ 7 } |
|
|
49 | sub NPERR_INCOMPATIBLE_VERSION_ERROR (){ 8 } |
|
|
50 | sub NPERR_INVALID_PARAM (){ 9 } |
|
|
51 | sub NPERR_INVALID_URL (){ 10 } |
|
|
52 | sub NPERR_FILE_NOT_FOUND (){ 11 } |
|
|
53 | sub NPERR_NO_DATA (){ 12 } |
|
|
54 | sub NPERR_STREAM_NOT_SEEKABLE (){ 13 } |
|
|
55 | sub NPVERS_HAS_STREAMOUTPUT (){ 8 } |
|
|
56 | sub NPVERS_HAS_NOTIFICATION (){ 9 } |
|
|
57 | sub NPVERS_HAS_LIVECONNECT (){ 9 } |
|
|
58 | sub NPVERS_WIN16_HAS_LIVECONNECT (){ 10 } |
28 | |
59 | |
29 | my $data; |
60 | my $data; |
30 | |
61 | |
31 | sub BIAS() { -2000000000 } # ugliest hack ever seen |
62 | sub BIAS() { -2147483647 } # ugliest hack ever seen |
32 | |
63 | |
33 | sub snd_cmd($) { |
64 | sub snd_cmd { |
34 | $data = pack "NN", 0, ord($_[0]); |
65 | $data = pack "NN", 0, ord($_[-1]); |
35 | } |
66 | } |
36 | |
67 | |
37 | sub snd_ptr($) { |
68 | sub snd_ptr { |
38 | $data .= $_[0]; |
69 | $data .= $_[-1]; |
39 | } |
70 | } |
40 | |
71 | |
41 | sub snd_u32($) { |
72 | sub snd_u32 { |
42 | $data .= pack "N", $_[0]; |
73 | $data .= pack "N", $_[-1]; |
43 | } |
74 | } |
44 | |
75 | |
45 | sub snd_i32($) { |
76 | sub snd_i32 { |
46 | $data .= pack "N", $_[0] - BIAS; |
77 | $data .= pack "N", $_[-1] - BIAS; |
47 | } |
78 | } |
48 | |
79 | |
49 | sub snd_blk($) { |
80 | sub snd_blk { |
50 | $data .= pack "NA*", length($_[0]), $_[0]; |
81 | $data .= pack "NA*", length($_[-1]), $_[-1]; |
51 | } |
82 | } |
52 | |
83 | |
53 | sub snd_snd() { |
84 | sub snd_snd { |
54 | substr ($data, 0, 4) = pack "N", length $data; |
85 | substr ($data, 0, 4) = pack "N", length $data; |
55 | length ($data) == syswrite FD, $data; |
86 | length ($data) == syswrite FD, $data; |
56 | } |
87 | } |
57 | |
88 | |
58 | sub snd_dyn($) { |
89 | sub snd_dyn { |
59 | length ($_[0]) == syswrite FD, $_[0]; |
90 | length ($_[-1]) == syswrite FD, $_[-1]; |
60 | } |
91 | } |
61 | |
92 | |
62 | sub rcv_cmd() { |
93 | sub rcv_cmd { |
63 | my $buf; |
94 | shift if ref $_[0]; |
|
|
95 | my $cmd = shift; |
|
|
96 | my ($buf, $rcmd, $len); |
|
|
97 | |
|
|
98 | do { |
64 | 4 == sysread FD, $buf, 4 or exit; |
99 | 4 == sysread FD, $buf, 4 or exit; |
65 | my $len = unpack "N", $buf; |
100 | $len = unpack "N", $buf; |
66 | $len -= 4; |
101 | $len -= 4; |
67 | $len == sysread FD, $buf, $len or die; |
102 | $len == sysread FD, $buf, $len or die; |
68 | |
103 | |
69 | $data = substr $buf, 4; |
104 | $data = substr $buf, 4; |
70 | |
105 | |
71 | substr $buf, 3, 1; |
106 | $rcmd = substr $buf, 3, 1; |
|
|
107 | if ($rcmd =~ /^[a-z]$/) { |
|
|
108 | die "rcv_cmd: sync error, expected '$cmd' but got '$rcmd'\n" if $cmd && $rcmd ne $cmd; |
|
|
109 | return $rcmd; |
|
|
110 | } else { |
|
|
111 | &_handle_event($rcmd); |
|
|
112 | } |
|
|
113 | } while ($cmd); |
72 | } |
114 | } |
73 | |
115 | |
74 | sub rcv_ptr() { |
116 | sub rcv_ptr { |
75 | my $ptr = substr $data, 0, 8; |
117 | my $ptr = substr $data, 0, 8; |
76 | $data = substr $data, 8; |
118 | $data = substr $data, 8; |
77 | $ptr; |
119 | $ptr; |
78 | } |
120 | } |
79 | |
121 | |
80 | sub rcv_u32() { |
122 | sub rcv_u32 { |
81 | my $u32 = unpack "N", $data; |
123 | my $u32 = unpack "N", $data; |
82 | $data = substr $data, 4; |
124 | $data = substr $data, 4; |
83 | $u32; |
125 | $u32; |
84 | } |
126 | } |
85 | |
127 | |
86 | sub rcv_i32() { |
128 | sub rcv_i32 { |
87 | my $i32 = BIAS + unpack "N", $data; |
129 | my $i32 = BIAS + unpack "N", $data; |
88 | $data = substr $data, 4; |
130 | $data = substr $data, 4; |
89 | $i32; |
131 | $i32; |
90 | } |
132 | } |
91 | |
133 | |
92 | sub rcv_blk() { |
134 | sub rcv_blk { |
93 | my $len = unpack "N", $data; |
135 | my $len = unpack "N", $data; |
94 | my $str = substr $data, 4, $len; |
136 | my $str = substr $data, 4, $len; |
95 | $data = substr $data, 4 + $len; |
137 | $data = substr $data, 4 + $len; |
96 | $str; |
138 | $str; |
|
|
139 | } |
|
|
140 | |
|
|
141 | sub rcv_dyn { |
|
|
142 | my $buf; |
|
|
143 | $_[-1] == sysread FD, $buf, $_[-1] or die "rcv_dyn: $!"; |
|
|
144 | $buf; |
|
|
145 | } |
|
|
146 | |
|
|
147 | # command from browser to plugin usually have capital letters while |
|
|
148 | # commands from perl to the browser always use lowercase letters |
|
|
149 | sub _handle_event($) { |
|
|
150 | my $cmd = shift; |
|
|
151 | |
|
|
152 | warn "handle_event($cmd)\n" if $debug; |
|
|
153 | if ($cmd eq 'I') { |
|
|
154 | rcv_u32 == 1 or die "protocol version mismatch\n"; |
|
|
155 | ($IFACE, $OSNAME, $OSVERS) = (rcv_blk, rcv_blk, rcv_blk); |
|
|
156 | |
|
|
157 | } elsif ($cmd eq '+') { # New |
|
|
158 | my ($objid, $type, $mode, $save, $argc) |
|
|
159 | = (rcv_ptr, rcv_blk, rcv_u32, rcv_blk, rcv_u32); |
|
|
160 | my %args; |
|
|
161 | while ($argc--) { |
|
|
162 | my ($argn, $argv) = (rcv_blk, rcv_blk); |
|
|
163 | $args{$argn} = $argv; |
|
|
164 | } |
|
|
165 | |
|
|
166 | $_OBJ{$objid} = $interface->new( |
|
|
167 | instance => $objid, |
|
|
168 | type => $type, |
|
|
169 | mode => $mode, |
|
|
170 | save => $save, |
|
|
171 | args => \%args, |
|
|
172 | ); |
|
|
173 | |
|
|
174 | } elsif ($cmd eq '-') { # Destroy |
|
|
175 | my $objid = rcv_ptr; |
|
|
176 | my $save = (delete $_OBJ{$objid})->save; |
|
|
177 | snd_cmd "-"; |
|
|
178 | snd_u32 length $save; |
|
|
179 | snd_snd and snd_dyn $save; |
|
|
180 | |
|
|
181 | } elsif ($cmd eq 'X') { # SetWindow |
|
|
182 | my $objid = rcv_ptr; |
|
|
183 | my %args = ( |
|
|
184 | id => rcv_ptr, |
|
|
185 | x => rcv_i32, |
|
|
186 | y => rcv_i32, |
|
|
187 | w => rcv_i32, |
|
|
188 | h => rcv_i32, |
|
|
189 | ); |
|
|
190 | if ($IFACE eq "UNIX") { |
|
|
191 | $args{id} = unpack "xxxxN", $args{id}; |
|
|
192 | $args{ws_type} = rcv_i32; |
|
|
193 | $args{ws_depth} = rcv_u32; |
|
|
194 | } |
|
|
195 | |
|
|
196 | snd_cmd 'X'; |
|
|
197 | snd_u32 $_OBJ{$objid}->set_window(\%args); |
|
|
198 | snd_snd; |
|
|
199 | |
|
|
200 | } elsif ($cmd eq 'N') { # NewStream |
|
|
201 | my $obj = $_OBJ{+rcv_ptr}; |
|
|
202 | my %args = ( |
|
|
203 | instance => $obj, |
|
|
204 | mimetype => rcv_blk, |
|
|
205 | id => rcv_ptr, |
|
|
206 | url => rcv_blk, |
|
|
207 | end => rcv_u32, |
|
|
208 | lastmodified => rcv_u32, |
|
|
209 | notifydata => rcv_u32, |
|
|
210 | seekable => rcv_u32, |
|
|
211 | push_stream => 1, |
|
|
212 | ); |
|
|
213 | my $str = $_OBJ{$args{id}} = new Browser::Stream::Receive \%args; |
|
|
214 | |
|
|
215 | my ($stype, $err) = $obj->push_stream($str); |
|
|
216 | |
|
|
217 | snd_cmd 'N'; |
|
|
218 | snd_u32 $err; |
|
|
219 | snd_u32 $stype || NP_NORMAL; |
|
|
220 | snd_snd; |
|
|
221 | |
|
|
222 | } elsif ($cmd eq '/') { # StreamAsFile |
|
|
223 | my $obj = $_OBJ{+rcv_ptr}; |
|
|
224 | my $str = $_OBJ{+rcv_ptr}; |
|
|
225 | my $path = rcv_blk; |
|
|
226 | |
|
|
227 | $obj->stream_as_file($obj, $stream, $path); |
|
|
228 | |
|
|
229 | } elsif ($cmd eq 'R') { # WriteReady |
|
|
230 | my $obj = $_OBJ{+rcv_ptr}; |
|
|
231 | my $str = $_OBJ{+rcv_ptr}; |
|
|
232 | |
|
|
233 | snd_cmd 'R'; |
|
|
234 | snd_u32 $obj->write_ready($obj, $str); |
|
|
235 | snd_snd; |
|
|
236 | |
|
|
237 | } elsif ($cmd eq 'W') { # Write |
|
|
238 | my $obj = $_OBJ{+rcv_ptr}; |
|
|
239 | my $str = $_OBJ{+rcv_ptr}; |
|
|
240 | my $ofs = rcv_i32; |
|
|
241 | my $len = rcv_i32; |
|
|
242 | my $dta = rcv_dyn $len; |
|
|
243 | |
|
|
244 | snd_cmd 'W'; |
|
|
245 | snd_i32 $obj->write($str, $ofs, $len, $dta); |
|
|
246 | snd_snd; |
|
|
247 | |
|
|
248 | } elsif ($cmd eq 'D') { # DestroyStream |
|
|
249 | my $obj = $_OBJ{+rcv_ptr}; |
|
|
250 | my $str = delete $_OBJ{+rcv_ptr}; |
|
|
251 | my $why = rcv_u32; |
|
|
252 | |
|
|
253 | $obj->destroy_stream($obj, $str, $why); |
|
|
254 | |
|
|
255 | } else { |
|
|
256 | die "unknown command '$cmd' received"; |
|
|
257 | } |
97 | } |
258 | } |
98 | |
259 | |
99 | =item my $fh = server_fh |
260 | =item my $fh = server_fh |
100 | |
261 | |
101 | The design of this module is event-based. When the plug-in starts (there |
262 | The design of this module is event-based. When the plug-in starts (there |
… | |
… | |
116 | plug-in is also not very viable, so in the future one might be able to |
277 | plug-in is also not very viable, so in the future one might be able to |
117 | specify a group on the embed statement (i.e. EMBED GROUP="gtk+"). |
278 | specify a group on the embed statement (i.e. EMBED GROUP="gtk+"). |
118 | |
279 | |
119 | =cut |
280 | =cut |
120 | |
281 | |
121 | sub server_fh() { *FD } |
282 | sub server_fh { \*FD } |
122 | |
283 | |
123 | sub server_event() { |
284 | sub server_event { rcv_cmd } |
124 | my $cmd = rcv_cmd; |
|
|
125 | |
|
|
126 | warn "cmd<$cmd>\n";#d# |
|
|
127 | |
|
|
128 | if ($cmd eq "I") { |
|
|
129 | rcv_u32 == 1 or die "protocol version mismatch\n"; |
|
|
130 | ($IFACE, $OSNAME, $OSVERS) = (rcv_blk, rcv_blk, rcv_blk); |
|
|
131 | |
|
|
132 | } elsif ($cmd eq "+") { |
|
|
133 | my ($objid, $type, $mode, $save, $argc) |
|
|
134 | = (rcv_ptr, rcv_blk, rcv_u32, rcv_blk, rcv_u32); |
|
|
135 | my %args; |
|
|
136 | while ($argc--) { |
|
|
137 | my ($argn, $argv) = (rcv_blk, rcv_blk); |
|
|
138 | $args{$argn} = $argv; |
|
|
139 | } |
|
|
140 | |
|
|
141 | warn "new obj $objid\n";#d# |
|
|
142 | $_OBJ{$objid} = new Mozilla::Plugin |
|
|
143 | objid => $objid, |
|
|
144 | type => $type, |
|
|
145 | mode => $mode, |
|
|
146 | save => $save, |
|
|
147 | args => \%args; |
|
|
148 | |
|
|
149 | } elsif ($cmd eq "-") { |
|
|
150 | my $objid = rcv_ptr; |
|
|
151 | my $save = (delete $_OBJ{$objid})->save; |
|
|
152 | snd_cmd "-"; |
|
|
153 | snd_u32 length $save; |
|
|
154 | snd_snd and snd_dyn $save; |
|
|
155 | |
|
|
156 | } elsif ($cmd eq "W") { |
|
|
157 | my $objid = rcv_ptr; |
|
|
158 | my %args = ( |
|
|
159 | window => rcv_ptr, |
|
|
160 | x => rcv_i32, |
|
|
161 | y => rcv_i32, |
|
|
162 | w => rcv_i32, |
|
|
163 | h => rcv_i32, |
|
|
164 | ); |
|
|
165 | if ($IFACE eq "UNIX") { |
|
|
166 | $args{window} = unpack "xxxxN", $args{window}; |
|
|
167 | $args{ws_type} = rcv_i32; |
|
|
168 | $args{ws_depth} = rcv_u32; |
|
|
169 | } |
|
|
170 | |
|
|
171 | $_OBJ{$objid}->set_window(\%args); |
|
|
172 | |
|
|
173 | } else { |
|
|
174 | die "unknown command '$cmd' received"; |
|
|
175 | } |
|
|
176 | } |
|
|
177 | |
285 | |
178 | sub mainloop { |
286 | sub mainloop { |
179 | server_event while 1; |
287 | server_event while 1; |
180 | } |
288 | } |
181 | |
289 | |
182 | sub init { |
290 | sub init { |
183 | $IN_MOZILLA = 1; |
291 | $interface = shift; |
184 | |
292 | |
185 | open FD, "+<&=$_[1]"; binmode $FD; |
293 | open FD, "+<&=$_[0]"; binmode FD; |
186 | |
294 | |
187 | warn "init: ".Dumper(@_); |
295 | $interface->mainloop; |
188 | mainloop; |
|
|
189 | } |
296 | } |
190 | |
297 | |
191 | sub new { |
298 | sub new { |
192 | my $class = shift; |
299 | my $class = shift; |
193 | my $self = bless { @_ }, $class; |
300 | my $self = bless { @_ }, $class; |
194 | $self->{save} = $self->{save} ne "" |
301 | $self->{save} = $self->{save} ne "" |
195 | ? Storable::thaw $self->{save} |
302 | ? Storable::thaw $self->{save} |
196 | : {}; |
303 | : {}; |
197 | warn "new: ".Dumper($self); |
|
|
198 | $self; |
304 | $self; |
199 | } |
305 | } |
200 | |
306 | |
201 | sub set_window { |
307 | sub set_window { |
202 | my $self = shift; |
308 | my $self = shift; |
203 | my $new = shift; |
309 | my $new = shift; |
|
|
310 | |
204 | if ($self->{window}) { |
311 | if ($self->{wininfo}) { |
205 | if ($self->{window}{window} ne $new->{window}) { |
312 | if ($self->{wininfo}{id} ne $new->{id}) { |
206 | $self->window_delete($self->{window}); |
313 | $self->window_delete($self->{wininfo}); |
207 | } else { |
314 | } elsif ($self->{wininfo}{w} != $new->{w} |
|
|
315 | or $self->{wininfo}{h} != $new->{h}) { |
208 | $self->window_resize($new->{window}, $new->{w}, $new->{h}); |
316 | $self->window_resize($new->{w}, $new->{h}); |
209 | } |
317 | } |
210 | $self->{window} = $new; |
318 | $self->{wininfo} = $new; |
211 | } else { |
|
|
212 | $self->{window} = $new; |
|
|
213 | $self->window_new($new->{window}, $new->{w}, $new->{h}); |
|
|
214 | } |
319 | } |
215 | warn "set_window: ".Dumper($self); |
|
|
216 | } |
|
|
217 | |
320 | |
|
|
321 | unless ($self->{wininfo}) { |
|
|
322 | $self->{window} = $self->window_new($new) and $self->{wininfo} = $new; |
|
|
323 | } |
|
|
324 | |
|
|
325 | (); |
|
|
326 | } |
|
|
327 | |
218 | sub window_new {} |
328 | sub window_new { () } |
219 | sub window_resize {} |
329 | sub window_resize {} |
|
|
330 | |
220 | sub window_delete {} |
331 | sub window_delete { |
|
|
332 | my $self = shift; |
|
|
333 | delete $self->{wininfo}; |
|
|
334 | delete $self->{window}; |
|
|
335 | } |
221 | |
336 | |
222 | sub save { |
337 | sub save { |
223 | $_[0]->{save}{test} = ['t1',5,7]; |
338 | my $self = shift; |
|
|
339 | $self->destroy; |
224 | Storable::nfreeze $_[0]->{save}; |
340 | Storable::nfreeze $self->{save}; |
225 | } |
341 | } |
226 | |
342 | |
|
|
343 | sub destroy { |
|
|
344 | my $self = shift; |
|
|
345 | $self->set_window(undef); |
|
|
346 | } |
|
|
347 | |
|
|
348 | sub write_ready { 0xfffff } |
|
|
349 | sub push_stream {} |
|
|
350 | sub stream_as_file {} |
|
|
351 | sub destroy_stream {} |
|
|
352 | |
227 | sub DESTROY { |
353 | sub DESTROY {} |
228 | warn "DESTROY"; |
354 | |
|
|
355 | sub write { |
|
|
356 | my ($self) = shift; |
|
|
357 | shift->write(@_); |
|
|
358 | } |
|
|
359 | |
|
|
360 | =item ($error, $stream) = new_stream $mimetype, $target |
|
|
361 | |
|
|
362 | Requests the creation of a new data stream produced by the plug-in and |
|
|
363 | consumed by the browser. |
|
|
364 | |
|
|
365 | =cut |
|
|
366 | |
|
|
367 | sub new_stream { |
|
|
368 | my ($self, $type, $target) = @_; |
|
|
369 | my ($error, $stream); |
|
|
370 | |
|
|
371 | snd_cmd 'n'; |
|
|
372 | snd_blk $mimetype; |
|
|
373 | snd_blk $target; |
|
|
374 | if (snd_snd) { |
|
|
375 | rcv_cmd 'n'; |
|
|
376 | $error = rcv_u32; |
|
|
377 | unless ($error) { |
|
|
378 | $stream = new Browser::Stream::Send ( |
|
|
379 | instance => $self, |
|
|
380 | mimetype => $type, |
|
|
381 | id => rcv_ptr, |
|
|
382 | url => $target, |
|
|
383 | ); |
|
|
384 | } |
|
|
385 | } |
|
|
386 | ($error, $stream); |
|
|
387 | } |
|
|
388 | |
|
|
389 | =item $error = $obj->get_url $url, $target |
|
|
390 | |
|
|
391 | =cut |
|
|
392 | |
|
|
393 | sub get_url { |
|
|
394 | my ($self, $url, $target) = @_; |
|
|
395 | my $error; |
|
|
396 | |
|
|
397 | snd_cmd 'u'; |
|
|
398 | snd_blk $url; |
|
|
399 | snd_blk $target; |
|
|
400 | if (snd_snd) { |
|
|
401 | rcv_cmd 'u'; |
|
|
402 | $error = rcv_i32; |
|
|
403 | } |
|
|
404 | $error; |
|
|
405 | } |
|
|
406 | |
|
|
407 | sub Browser::Stream::new { |
|
|
408 | bless $_[1], $_[0]; |
|
|
409 | } |
|
|
410 | |
|
|
411 | sub Browser::Stream::destroy { |
|
|
412 | my $str = shift; |
|
|
413 | my $error = shift; |
|
|
414 | unless ($str->{destroyed}) { |
|
|
415 | $str->{destroyed}++; |
|
|
416 | |
|
|
417 | my $obj = $str->{instance}; |
|
|
418 | snd_cmd 'd'; |
|
|
419 | snd_ptr $obj->{id}; |
|
|
420 | snd_ptr $str->{id}; |
|
|
421 | snd_u32 $error || NPERR_NO_ERROR; |
|
|
422 | snd_snd; |
|
|
423 | } |
|
|
424 | } |
|
|
425 | |
|
|
426 | *Browser::Stream::DESTROY = \&Browser::Stream::destroy; |
|
|
427 | |
|
|
428 | BEGIN { |
|
|
429 | @Browser::Stream::Receive::ISA = Browser::Stream; |
|
|
430 | } |
|
|
431 | |
|
|
432 | sub Browser::Stream::Receive::write { |
|
|
433 | # ... |
|
|
434 | } |
|
|
435 | |
|
|
436 | BEGIN { |
|
|
437 | @Browser::Stream::Send::ISA = Browser::Stream; |
|
|
438 | } |
|
|
439 | |
|
|
440 | =item $bytes = $stream->write($data) |
|
|
441 | |
|
|
442 | =cut |
|
|
443 | |
|
|
444 | sub Browser::Stream::Send::write { |
|
|
445 | my $str = shift; |
|
|
446 | my $obj = $self->{instance}; |
|
|
447 | |
|
|
448 | snd_cmd 'w'; |
|
|
449 | snd_ptr $obj->{id}; |
|
|
450 | snd_ptr $str->{id}; |
|
|
451 | snd_u32 length $_[0]; |
|
|
452 | if (snd_snd) { |
|
|
453 | if (snd_dyn $_[0]) { |
|
|
454 | rcv_cmd 'w'; |
|
|
455 | return rcv_i32 (); |
|
|
456 | } |
|
|
457 | } |
|
|
458 | return 0; |
229 | } |
459 | } |
230 | |
460 | |
231 | 1; |
461 | 1; |
232 | |
462 | |
233 | =back |
463 | =back |