line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojolicious::Plugin::PNGCast; |
2
|
1
|
|
|
1
|
|
674
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
26
|
|
3
|
1
|
|
|
1
|
|
18
|
use 5.014; |
|
1
|
|
|
|
|
4
|
|
4
|
1
|
|
|
1
|
|
524
|
use Mojo::Base 'Mojolicious::Plugin'; |
|
1
|
|
|
|
|
124077
|
|
|
1
|
|
|
|
|
7
|
|
5
|
1
|
|
|
1
|
|
1562
|
use Filter::signatures; |
|
1
|
|
|
|
|
16598
|
|
|
1
|
|
|
|
|
7
|
|
6
|
1
|
|
|
1
|
|
36
|
use feature 'signatures'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
36
|
|
7
|
1
|
|
|
1
|
|
6
|
no warnings 'experimental::signatures'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
32
|
|
8
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
468
|
use Future::Mojo; |
|
1
|
|
|
|
|
146867
|
|
|
1
|
|
|
|
|
839
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
our $VERSION = '0.22'; |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 NAME |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
Mojolicious::Plugin::PNGCast - in-process server to display a screencast |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 DESCRIPTION |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
Use this web application to display the screencast of a (headless) web browser |
20
|
|
|
|
|
|
|
or other arbitrary PNG data sent to it via websocket. |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
The synopsis shows how to use this plugin to display |
23
|
|
|
|
|
|
|
a Chrome screencast using L. |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 SYNOPSIS |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
use Mojolicious::Lite; |
28
|
|
|
|
|
|
|
use Mojo::Server::Daemon; |
29
|
|
|
|
|
|
|
use WWW::Mechanize::Chrome; |
30
|
|
|
|
|
|
|
plugin 'PNGCast'; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
my $daemon_url = 'http://localhost:3000'; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
my $ws_monitor = Mojo::Server::Daemon->new(app => app()); |
35
|
|
|
|
|
|
|
$ws_monitor->listen([$daemon_url]); |
36
|
|
|
|
|
|
|
$ws_monitor->start; |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $mech = WWW::Mechanize::Chrome->new( headless => 1 ); |
39
|
|
|
|
|
|
|
$mech->setScreenFrameCallback( sub { |
40
|
|
|
|
|
|
|
app->send_frame( $_[1]->{data} )} |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
print "Watch progress at $daemon_url\n"; |
44
|
|
|
|
|
|
|
sleep 5; |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
$mech->get('https://example.com'); |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
=cut |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
has 'clients' => sub { {} }; |
51
|
|
|
|
|
|
|
has 'last_frame' => undef; |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=head1 HELPERS |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
=head2 C<< app->send_frame >> |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
app->send_frame( $png_data ); |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
Sends a frame to all connected clients. If a fresh client connects, it will |
60
|
|
|
|
|
|
|
receive the last frame handed to C. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=head1 INTERNAL METHODS |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head2 C<< $plugin->notify_clients >> |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
$plugin->notify_clients( $PNGframe ) |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
Notify all connected clients that they should display the new frame. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=cut |
71
|
|
|
|
|
|
|
|
72
|
0
|
|
|
0
|
1
|
|
sub notify_clients( $self, @frames ) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
73
|
0
|
|
|
|
|
|
my $clients = $self->clients; |
74
|
0
|
|
|
|
|
|
for my $client_id (sort keys %$clients ) { |
75
|
0
|
|
|
|
|
|
my $client = $clients->{ $client_id }; |
76
|
0
|
|
|
|
|
|
for my $frame (@frames) { |
77
|
0
|
|
|
|
|
|
eval { |
78
|
0
|
|
|
|
|
|
$client->send({ binary => $frame }); |
79
|
|
|
|
|
|
|
}; |
80
|
|
|
|
|
|
|
}; |
81
|
|
|
|
|
|
|
}; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
0
|
|
|
0
|
1
|
|
sub register( $self, $app, $config ) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
$app->routes->get('/' => sub { |
87
|
0
|
|
|
0
|
|
|
my( $c ) = @_; |
88
|
0
|
|
|
|
|
|
$c->res->headers->content_type('text/html'); |
89
|
0
|
|
|
|
|
|
$c->res->headers->connection('close'); |
90
|
0
|
|
|
|
|
|
$c->render('index') |
91
|
0
|
|
|
|
|
|
}); |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
$app->routes->websocket( '/ws' => sub { |
94
|
0
|
|
|
0
|
|
|
my( $c ) = @_; |
95
|
0
|
|
|
|
|
|
$c->inactivity_timeout(300); |
96
|
|
|
|
|
|
|
|
97
|
0
|
|
0
|
|
|
|
my $client_id = join ":", $c->tx->original_remote_address || $c->tx->remote_address, |
98
|
|
|
|
|
|
|
$c->tx->remote_port(); |
99
|
|
|
|
|
|
|
|
100
|
0
|
|
|
|
|
|
$self->clients->{ $client_id } = $c; |
101
|
|
|
|
|
|
|
$c->tx->on( json => sub { |
102
|
0
|
|
|
|
|
|
my( $c, $data ) = @_; |
103
|
|
|
|
|
|
|
#warn Dumper $data ; |
104
|
0
|
|
|
|
|
|
warn "Click received (and ignored)"; |
105
|
|
|
|
|
|
|
#$mech->click( { selector => '//body', single => 1 }, $data->{x}, $data->{y} ); |
106
|
|
|
|
|
|
|
#$mech->click( { selector => '//body', single => 1 }, $data->{x}, $data->{y} ); |
107
|
|
|
|
|
|
|
|
108
|
0
|
|
|
|
|
|
}); |
109
|
|
|
|
|
|
|
#warn("Client connected"); |
110
|
0
|
0
|
|
|
|
|
if( $self->last_frame ) { |
111
|
|
|
|
|
|
|
# send current frame |
112
|
0
|
|
|
|
|
|
$c->send({ binary => $self->last_frame }); |
113
|
|
|
|
|
|
|
} else { |
114
|
|
|
|
|
|
|
# send a standby frame ?? |
115
|
|
|
|
|
|
|
}; |
116
|
|
|
|
|
|
|
$c->tx->on( finish => sub { |
117
|
0
|
|
|
|
|
|
my( $c,$code,$reason ) = @_; |
118
|
0
|
|
|
|
|
|
warn "Client gone ($code,$reason)" ; |
119
|
0
|
|
|
|
|
|
delete $self->clients->{ $client_id }; |
120
|
0
|
|
|
|
|
|
}); |
121
|
0
|
|
|
|
|
|
}); |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# Stop our program |
124
|
|
|
|
|
|
|
$app->routes->get( '/stop' => sub { |
125
|
0
|
|
|
0
|
|
|
my( $c ) = @_; |
126
|
0
|
|
|
|
|
|
$c->res->headers->content_type('text/html'); |
127
|
0
|
|
|
|
|
|
$c->res->headers->connection('close'); |
128
|
0
|
|
|
|
|
|
$c->render('stop'); |
129
|
0
|
|
|
|
|
|
Mojo::IOLoop->stop; |
130
|
0
|
|
|
|
|
|
}); |
131
|
|
|
|
|
|
|
|
132
|
0
|
|
|
0
|
|
|
$app->helper( 'send_frame' => sub ( $c, $framePNG ) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# send this frame to all connected clients |
134
|
0
|
0
|
|
|
|
|
if( scalar keys %{ $self->clients } ) { |
|
0
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
Future::Mojo->new->done_next_tick( 1 ) |
136
|
|
|
|
|
|
|
->then( sub { |
137
|
0
|
|
|
|
|
|
$self->notify_clients( $framePNG ); |
138
|
0
|
|
|
|
|
|
})->retain; |
139
|
|
|
|
|
|
|
}; |
140
|
0
|
|
|
|
|
|
$self->last_frame( $framePNG ); |
141
|
0
|
|
|
|
|
|
}); |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# Install our templates |
144
|
0
|
|
|
|
|
|
push @{$app->renderer->classes}, __PACKAGE__; |
|
0
|
|
|
|
|
|
|
145
|
0
|
|
|
|
|
|
push @{$app->static->classes}, __PACKAGE__; |
|
0
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=head1 EXPORTED HTTP ENDPOINTS |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
This plugin makes the following endpoints available |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=over 4 |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=item * |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
C> - the index page |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
This is an HTML page that opens a websocket to the webserver and listens for |
159
|
|
|
|
|
|
|
PNG images coming in over that websocket |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=item * |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
C - the websocket |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
This is a websocket |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=item * |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
C - stop the application |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
This stops the complete Mojolicious application |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=back |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=head1 REPOSITORY |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
The public repository of this module is |
178
|
|
|
|
|
|
|
L. |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head1 SUPPORT |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
The public support forum of this module is L. |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head1 BUG TRACKER |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Please report bugs in this module via the RT CPAN bug queue at |
187
|
|
|
|
|
|
|
L |
188
|
|
|
|
|
|
|
or via mail to L. |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=head1 AUTHOR |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
Max Maischein C |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head1 COPYRIGHT (c) |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Copyright 2010-2018 by Max Maischein C. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head1 LICENSE |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
This module is released under the same terms as Perl itself. |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=cut |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
1 |
205
|
|
|
|
|
|
|
__DATA__ |