File Coverage

blib/lib/Net/OpenVPN/TrayIcon.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Net::OpenVPN::TrayIcon;
2              
3 1     1   24992 use 5.010;
  1         6  
  1         43  
4 1     1   6 use warnings;
  1         2  
  1         32  
5 1     1   1113 use Moo;
  1         25459  
  1         7  
6 1     1   2887 use Gtk2 '-init';
  0            
  0            
7             use Gtk2::TrayIcon;
8             use MIME::Base64;
9             use Time::HiRes 'usleep';
10             use Data::Section -setup;
11             use POSIX ':sys_wait_h';
12              
13             has icons => ( is => 'rw' );
14             has config => ( is => 'rw' );
15             has dispatch => ( is => 'rw' );
16             has gtk_icon => ( is => 'rw' );
17             has gtk_menu => ( is => 'rw' );
18             has gtk_tray_icon => ( is => 'rw' );
19             has gtk_tooltip => ( is => 'rw' );
20              
21             our $this;
22              
23             sub _sig_usr1_handler {
24             my ($sig) = @_;
25             $this->dispatch->{set_icon}->('active') if $sig ~~ 'USR1';
26             return 1;
27             }
28              
29             sub _sig_usr2_handler {
30             my ($sig) = @_;
31             $this->dispatch->{set_icon}->('inactive') if $sig ~~ 'USR2';
32             return 1;
33             }
34              
35             sub BUILD {
36             my ($self) = @_;
37              
38             if ($self->_first_run){
39             my %icons;
40             $icons{default} = ${$self->section_data('icon_default')};
41             $icons{active} = ${$self->section_data('icon_active')};
42             $icons{inactive} = ${$self->section_data('icon_inactive')};
43              
44             my $default_config = ${$self->section_data('default_config')};
45              
46             mkdir $ENV{HOME} . '/.ovpntray' or die "Could not create config directory: $!";
47              
48             open my $fh, '>', $ENV{HOME} . '/.ovpntray/config';
49             print $fh $default_config;
50             close $fh;
51              
52             for (keys %icons){
53             open my $bfh, '>', $ENV{HOME} . '/.ovpntray/icon_' . $_ . '.png';
54             binmode $bfh;
55             print $bfh decode_base64($icons{$_});
56             close $bfh;
57             }
58             }
59              
60             $self->_build_config;
61             $self->_build_dispatch_table;
62             $self->_build_icons;
63             $self->_build_tray;
64             $self->_build_this;
65              
66             $SIG{USR1} = \&_sig_usr1_handler;
67             $SIG{USR2} = \&_sig_usr2_handler;
68              
69             return 1;
70             }
71              
72             sub _build_this {
73             my ($self) = @_;
74             $this = $self;
75              
76             return 1;
77             }
78              
79             sub run {
80             my ($self) = @_;
81              
82             Gtk2->main;
83             }
84              
85             sub click {
86             my ($self, $gtk_evnt_box, $gtk_evnt_button) = @_;
87              
88             given ($gtk_evnt_button->button){
89             when (1){ # left click
90             $self->menu;
91             }
92             when (2){ # middle click
93             $self->menu;
94             }
95             when (3){ # right click
96             $self->menu;
97             }
98             default {
99             }
100             }
101              
102             return 1;
103             }
104              
105             sub config_view {
106             my ($self) = @_;
107              
108             # main config window
109             my $gtk_window = Gtk2::Window->new('toplevel');
110             $gtk_window->set_title('Config');
111             $gtk_window->set_position('center');
112              
113             # vbox container (rows, columns, homogeneous)
114             my $gtk_table = Gtk2::Table->new( 3, 1, 0 );
115              
116             # notebook
117             my $gtk_notebook = Gtk2::Notebook->new;
118             $gtk_notebook->set_tab_pos('top');
119              
120             # first page
121             my $gtk_vbox_page_1 = Gtk2::VBox->new( 0, 1 );
122             }
123              
124             sub menu {
125             my ($self) = @_;
126              
127             my $gtk_menu = Gtk2::Menu->new();
128             my $gtk_menu_start = Gtk2::ImageMenuItem->new_with_label('start VPN');
129             $gtk_menu_start->signal_connect(
130             activate => sub {
131             system 'sudo ' . $self->config->{start_cmnd};
132             $self->dispatch->{set_icon}->('default');
133             $self->dispatch->{set_tip}->('OpenVPN checking connection..');
134             $self->dispatch->{active_if_running}->();
135             },
136             );
137              
138             my $gtk_menu_stop = Gtk2::ImageMenuItem->new_with_label('stop VPN');
139             $gtk_menu_stop->signal_connect(
140             activate => sub {
141             system 'sudo ' . $self->config->{stop_cmnd};
142             $self->dispatch->{set_icon}->('default');
143             $self->dispatch->{set_tip}->('OpenVPN checking connection..');
144             $self->dispatch->{inactive_if_not_running}->();
145             },
146             );
147              
148             $gtk_menu->add($gtk_menu_start);
149             $gtk_menu->add($gtk_menu_stop);
150             $gtk_menu->show_all;
151              
152             $gtk_menu->popup( undef, undef, undef, undef, 0, 0 );
153             $self->gtk_menu($gtk_menu);
154              
155             return 1;
156             }
157              
158             sub _build_dispatch_table {
159             my ($self) = @_;
160              
161             my %table = (
162             set_icon => sub {
163             my ($type) = @_;
164             $self->gtk_icon->set_from_pixbuf($self->icons->{$type});
165             return 1;
166             },
167             set_tip => sub {
168             my ($tip) = @_;
169             $self->gtk_tooltip->set_tip( $self->gtk_tray_icon, $tip);
170             return 1;
171             },
172             active_if_running => sub {
173             my $parent_pid = $$;
174             my $child_pid = fork;
175              
176             if ($child_pid == 0){ # the child
177             if ($self->_vpn_is_running(200)){
178             if ($self->_check_if_connected(10000)){
179             kill USR1 => $parent_pid;
180             }
181             }
182             else {
183             kill USR2 => $parent_pid;
184             }
185              
186             POSIX::_exit(0);
187             }
188             else {
189             # keep going
190             }
191             },
192             inactive_if_not_running => sub {
193             my $parent_pid = $$;
194             my $child_pid = fork;
195              
196             if ($child_pid == 0){ # the child
197             if ($self->_vpn_is_not_running(200)){
198             kill USR2 => $parent_pid;
199             }
200             else {
201             kill USR1 => $parent_pid;
202             }
203              
204             POSIX::_exit(0);
205             }
206             else {
207             # keep going
208             }
209             },
210              
211             );
212              
213             $self->dispatch({ %table });
214              
215             return 1;
216             }
217              
218             sub _build_tray {
219             my ($self) = @_;
220              
221             my $gtk_tooltip = Gtk2::Tooltips->new;
222             my $gtk_trayicon = Gtk2::TrayIcon->new('VPN Tray');
223             my $gtk_icon;
224             if ($self->_vpn_is_running(10)){
225             $gtk_icon = Gtk2::Image->new_from_pixbuf($self->icons->{'active'});
226             $gtk_tooltip->set_tip( $gtk_trayicon, 'OpenVPN started');
227             }
228             else {
229             $gtk_icon = Gtk2::Image->new_from_pixbuf($self->icons->{'inactive'});
230             $gtk_tooltip->set_tip( $gtk_trayicon, 'OpenVPN stopped');
231             }
232             my $gtk_eventbox = Gtk2::EventBox->new;
233             $gtk_eventbox->add($gtk_icon);
234              
235              
236             $gtk_trayicon->add($gtk_eventbox);
237              
238             $self->gtk_icon($gtk_icon);
239             $self->gtk_tooltip($gtk_tooltip);
240             $self->gtk_tray_icon($gtk_trayicon);
241              
242             my $click_callback = sub {
243             $self->click(@_);
244             };
245              
246             $gtk_eventbox->signal_connect( 'button_press_event', $click_callback );
247              
248             $gtk_trayicon->show_all;
249              
250             return 1;
251             }
252              
253             sub _vpn_is_running {
254             my ($self, $timeout) = @_;
255             return $self->_check_if_running(1, $timeout);
256             }
257              
258             sub _vpn_is_not_running {
259             my ($self, $timeout) = @_;
260             return $self->_check_if_running(0, $timeout);
261             }
262              
263             sub _check_if_running {
264             my ($self, $condition, $timeout) = @_;
265              
266             while ($timeout){
267             my $ovpn_pid = qx[pgrep openvpn];
268             if ($condition){
269             if ($ovpn_pid){
270             return 1;
271             }
272             }
273             else {
274             if (!$ovpn_pid){
275             return 1;
276             }
277             }
278             Time::HiRes::usleep 100;
279             $timeout--;
280             }
281              
282             return 0;
283             }
284              
285             sub _check_if_connected { # check if tunnel_interface appears in routes
286             my ($self, $timeout) = @_;
287              
288             my $tunnel = $self->config->{tunnel_interface};
289             while ($timeout){
290             my ($routes) = qx[/sbin/ip r]; # first line -> default route
291              
292             return 1 if $routes ~~ /default\svia\s\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3}\sdev\s$tunnel/;
293              
294             Time::HiRes::usleep 100;
295             $timeout--;
296             }
297              
298             return 0;
299             }
300              
301             sub _first_run {
302             my ($self) = @_;
303             return -f $ENV{HOME} . '/.ovpntray/config' ? 0 : 1;
304             }
305              
306             sub _build_config {
307             my ($self) = @_;
308              
309             my %config;
310             open my $fh, '<', $ENV{HOME} . '/.ovpntray/config';
311             while (<$fh>){
312             my ($key, $value) = split ':', $_;
313             chomp $key;
314             chomp $value;
315             $config{$key} = $value;
316             }
317             close $fh;
318              
319             $self->config({%config});
320              
321             return 1;
322             }
323              
324             sub _build_icons {
325             my ($self) = @_;
326              
327             my %icons;
328             $icons{'default'} = Gtk2::Gdk::Pixbuf->new_from_file_at_scale(
329             $ENV{HOME} . '/.ovpntray/' . $self->config->{icon_file},
330             $self->config->{icon_size}, # width
331             $self->config->{icon_size}, # height
332             1, # keep aspect
333             );
334             $icons{'active'} = Gtk2::Gdk::Pixbuf->new_from_file_at_scale(
335             $ENV{HOME} . '/.ovpntray/' . $self->config->{icon_file_active},
336             $self->config->{icon_size}, # width
337             $self->config->{icon_size}, # height
338             1, # keep aspect
339             );
340             $icons{'inactive'} = Gtk2::Gdk::Pixbuf->new_from_file_at_scale(
341             $ENV{HOME} . '/.ovpntray/' . $self->config->{icon_file_inactive},
342             $self->config->{icon_size}, # width
343             $self->config->{icon_size}, # height
344             1, # keep aspect
345             );
346              
347             $self->icons({%icons});
348              
349             return 1;
350             }
351              
352             1;
353              
354             __DATA__