diff options
Diffstat (limited to 'bin/net_applet')
-rwxr-xr-x | bin/net_applet | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/bin/net_applet b/bin/net_applet new file mode 100755 index 0000000..fd2b79b --- /dev/null +++ b/bin/net_applet @@ -0,0 +1,585 @@ +#!/usr/bin/perl + +use strict; + +use POSIX ":sys_wait_h"; +use lib qw(/usr/lib/libDrakX); +# i18n: IMPORTANT: to get correct namespace (drakx-net instead of libDrakX) +BEGIN { unshift @::textdomains, 'drakx-net' } +use c; +use common; +use standalone; +use network::network; +use network::tools; +use network::connection; +use network::connection::ethernet; +use network::vpn; +use run_program; +use mygtk2 qw(gtknew gtkset); +use dbus_object; +use network::monitor; +use network::signal_strength; +use detect_devices; +use Gtk2::Notify -init, 'notify'; + +use ugtk2 qw(:create :helpers :wrappers :dialogs); + +my $onstartupfile = "$ENV{HOME}/.net_applet"; +my $system_file = '/etc/sysconfig/drakx-net'; +shouldStart() or die "$onstartupfile should be set to TRUE or use net_applet --force\n"; + +#- Allow multiple instances, but only one per user: +is_running('net_applet') and die "net_applet already running\n"; + +package network::net_applet; + +use mygtk2 qw(gtknew gtkset); +use common; + +our ($current_state, $current_interface); +our ($icon, $notification_queue); +our $dbus; +our ($interactive_cb, $ifw, $ifw_alert); + +our %wireless_networks; + +our %pixbufs = + ( + firewall => gtknew('Pixbuf', file => 'drakfirewall'), + firewall_icon => gtknew('Pixbuf', file => 'drakfirewall')->scale_simple(24, 24, 'hyper'), + state => { map { $_ => gtknew('Pixbuf', file => $_) } qw(connected disconnected unconfigured connecting) }, + link_level => { map { + $_ => gtknew('Pixbuf', file => 'wifi-' . sprintf('%03d', $_))->scale_simple(24, 24, 'hyper'); + } qw(20 40 60 80 100) }, + encryption => { map { + $_ => gtknew('Pixbuf', file => "encryption-$_-24"); + } qw(open weak strong) }, + ); + +sub get_current_network() { + detect_devices::is_wireless_interface($current_interface) && find { $_->{current} } values %wireless_networks; +} + +sub get_state_pixbuf() { + my $wnet = $current_state eq 'connected' && get_current_network(); + $wnet ? + network::signal_strength::get_strength_icon($wnet) : + $pixbufs{state}{$current_state}; +} + +sub update_tray_icon() { + if (!$ifw_alert || $icon->get_storage_type ne 'pixbuf') { + $icon->set_from_pixbuf(get_state_pixbuf()); + } else { + $icon->set_from_stock('gtk-dialog-warning'); + } +} + +1; + +package main; + +my ($current_description, $simple_menu, $menu, $wireless_device, $timeout, $update_timeout); +add_icon_path("/usr/share/libDrakX/pixmaps/"); + +my $net = {}; +my $watched_interface; + +my %global_settings = getVarsFromSh($system_file); + +sub get_state_message { + my ($o_interface) = @_; + my $interface = $o_interface || $current_interface; + my $network = network::net_applet::get_current_network(); + formatAlaTeX( + $current_state eq 'connected' ? + N("Network is up on interface %s.", get_interface_name($interface)) . + "\n\n" . N("IP address: %s", network::tools::get_interface_ip_address($net, $interface)) . + "\n\n" . N("Gateway: %s", [ network::tools::get_interface_status($interface) ]->[1]) . + "\n\n" . N("DNS: %s", $net->{resolv}{dnsServer}) . + ($network && "\n\n" . N("Connected to %s (link level: %d %%)", $network->{name}, $network->{signal_strength})) + : $current_state eq 'disconnected' ? + N("Network is down on interface %s.", get_interface_name($interface)) + : $current_state eq 'unconfigured' ? + N("You do not have any configured Internet connection. +Run the \"%s\" assistant from the Mageia Linux Control Center", N("Set up a new network interface (LAN, ISDN, ADSL, ...)")) + : + N("Connecting...") + ); +} + +sub get_interface_type { + my ($interface) = @_; + my $ifcfg = $net->{ifcfg}{$interface}; + require network::connection; + $ifcfg && network::connection->find_ifcfg_type($ifcfg); +} + +sub get_interface_icon { + my ($interface) = @_; + my $type = get_interface_type($interface); + $type && $type->get_type_icon; +} + +sub get_interface_name { + my ($interface) = @_; + my $type = get_interface_type($interface); + my $type_name = $type && $type->get_type_description; + $type_name ? "$type_name ($interface)" : $interface; +} + +my %actions = ( + 'upNetwork' => { + name => sub { N("Connect %s", get_interface_name($_[0])) }, + launch => sub { network::tools::start_interface($_[0], 1) } + }, + 'downNetwork' => { + name => sub { N("Disconnect %s", get_interface_name($_[0])) }, + launch => sub { network::tools::stop_interface($_[0], 1) } + }, + 'monitorNetwork' => { + name => N("Monitor Network"), + launch => \&run_net_monitor + }, + 'monitorIFW' => { + name => N("Interactive Firewall"), + launch => \&run_drakids + }, + 'wireless' => { + name => N("Manage wireless networks"), + launch => sub { run_drakroam() } + }, + 'drakvpn' => { + name => N("Manage VPN connections"), + launch => sub { run_program::raw({ detach => 1 }, '/usr/sbin/drakvpn'); }, + }, + 'confNetwork' => { + name => N("Configure Network"), + launch => sub { system("/usr/sbin/drakconnect &") } + }, + 'chooseInterface' => { + name => N("Watched interface"), + choices => sub { N("Auto-detect"), sort keys %{$net->{ifcfg}} }, + choice_selected => sub { + $watched_interface ? $_[0] eq $watched_interface : + $_[0] eq N("Auto-detect") + }, + launch => sub { + $watched_interface = $_[0] eq N("Auto-detect") ? undef : $_[0]; + checkNetworkForce(); + } + }, + 'setInterface' => { + name => N("Active interfaces"), + use_checkbox => 1, + choices => sub { sort keys %{$net->{ifcfg}} }, + choice_selected => sub { + my ($is_up, $_gw) = network::tools::get_interface_status($_[0]); + $is_up; + }, + format_choice => \&get_interface_name, + get_icon => \&get_interface_icon, + launch => sub { + my ($is_up, $_gw) = network::tools::get_interface_status($_[0]); + if ($is_up) { + network::tools::stop_interface($_[0], 1); + } else { + network::tools::start_interface($_[0], 1); + } + checkNetworkForce(); + } + }, + 'chooseProfile' => { + name => N("Profiles"), + choices => sub { network::network::netprofile_list() }, + choice_selected => sub { $_[0] eq $net->{PROFILE} }, + launch => sub { + require run_program; + $net->{PROFILE} = $_[0]; + run_program::raw({ detach => 1 }, common::wrap_command_for_root('/sbin/set-netprofile', $net->{PROFILE})); + } + }, + 'chooseVPN' => { + name => N("VPN connection"), + header => "drakvpn", + choices => sub { + map { $_->get_configured_connections } network::vpn::list_types + }, + allow_single_choice => 1, + format_choice => \&network::vpn::get_label, + choice_selected => sub { $_[0]->is_started }, + launch => sub { + require interactive; $_[0]->is_started ? + $_[0]->stop : $_[0]->start(interactive->vnew) + }, + }, + 'help' => { + name => N("Help"), + launch => sub { system("drakhelp --id internet-connection &") } + }, + 'quit' => { + name => N("Quit"), + launch => \&mainQuit + }, +); + + +$icon = Gtk2::StatusIcon->new; + +eval { $dbus = dbus_object::system_bus() } if !defined($global_settings{NET_APPLET_DBUS}) || text2bool($global_settings{NET_APPLET_DBUS}); +eval { $net->{monitor} = network::monitor->new($dbus) } if $dbus; +if ($dbus) { + require network::net_applet::ifw; + network::net_applet::ifw::init(); +} +if ($dbus) { + $dbus->{connection}->add_filter(sub { + my ($_con, $msg) = @_; + if ($msg->get_interface eq 'org.mageia.network' && $msg->get_member eq 'status') { + my ($status, $interface) = $msg->get_args_list; + print "got connection status event: $status $interface\n"; + if ($status eq "add") { + checkNetworkForce(); + } + } + }); + $dbus->{connection}->add_match("type='signal',interface='org.mageia.network'"); + dbus_object::set_gtk2_watch_helper($dbus); +} + +$notification_queue = Gtk2::Notify::Queue->new($icon); + +$icon->signal_connect(activate => sub { + my ($_icon, $button, $time) = @_; + if ($ifw_alert) { + run_drakids(); + } elsif ($simple_menu) { + $simple_menu->popup(undef, undef, undef, undef, $button, $time); + } else { + run_netcenter(); + } + }); +$icon->signal_connect(popup_menu => sub { + my ($_icon, $button, $time) = @_; + $menu->popup(undef, undef, undef, undef, $button, $time) if $menu; + }); + +checkNetworkForce(); +cronNetwork(); +gtkflush(); #- for notifications to appear on the status icon position +network::net_applet::ifw::get_unprocessed_ifw_messages() if $ifw; + +$SIG{HUP} = sub { + print "received SIGHUP, reloading network configuration\n"; + checkNetworkForce(); +}; +$SIG{USR1} = sub { + # clear all ifw notifications + my @packets = eval { $network::net_applet::ifw->get_reports }; +}; + +# do not create zombies (#20552) +$SIG{CHLD} = \&harvester; +sub harvester { + my $pid; + do { + # we don't care about our child processes + $pid = waitpid(-1, &WNOHANG); + } while $pid > 0; +} + +Gtk2->main; + +ugtk2::exit(0); + +sub is_running { + my ($name, $o_user) = @_; + my $user = $o_user || $ENV{USER}; + any { + my ($ppid, $pid, $n) = /^\s*(\d+)\s+(\d+)\s+(.*)/; + $ppid != 1 && $pid != $$ && $n eq $name; + } `ps -o '%P %p %c' -u $user`; +} + +sub is_running_match { + # (eugeni) this matches part of a running command. + # Right now it is only used to detect if ifup script is running. + my ($name, $o_user) = @_; + my $user = $o_user || $ENV{USER}; + any { + my ($ppid, $pid, $n) = /^\s*(\d+)\s+(\d+)\s+(.*)/; + $ppid != 1 && $pid != $$ && $n =~ $name; + } `ps -o '%P %p %c' -u $user`; +} + +sub shouldStart() { + my ($opt) = @ARGV; + if ($opt eq '--force' || $opt eq '-f') { + return 1; + } + return getAutoStart(); +} +sub run_net_monitor() { + run_program::raw({ detach => 1 }, '/usr/bin/net_monitor', '--defaultintf', $current_interface) unless is_running('net_monitor'); +} +sub run_netcenter() { + run_program::raw({ detach => 1 }, '/usr/bin/draknetcenter') unless is_running('draknetcenter', 'root'); +} +sub run_drakroam { + my ($o_ap) = @_; + run_program::raw({ detach => 1 }, '/usr/sbin/drakroam', if_($o_ap, "--ap=$o_ap")) unless is_running('drakroam', 'root'); +} +sub run_drakids() { + $ifw_alert = 0; + if (is_running('drakids')) { + eval { $ifw->send_manage_request }; + } else { + run_program::raw({ detach => 1 }, '/usr/sbin/drakids'); + } +} +sub generate_wireless_menuitem { + my ($wnet) = @_; + my $menuitem = {}; + $menuitem->{widget} = Gtk2::CheckMenuItem->new; + $menuitem->{widget}->set_draw_as_radio(1); + $menuitem->{widget}->add(gtkpack_(gtkshow(gtknew('HBox')), + 1, gtkset_alignment($menuitem->{label} = gtknew('Label'), 0, 0.5), + 0, $menuitem->{strength} = Gtk2::Image->new, + 0, $menuitem->{security} = Gtk2::Image->new, + )); + $menuitem->{activate} = $menuitem->{widget}->signal_connect('activate' => sub { + if ($net->{monitor} && exists $wnet->{id}) { + eval { $net->{monitor}->select_network($wnet->{id}) }; + $@ and err_dialog(N("Interactive Firewall"), N("Unable to contact daemon")); + } else { + run_drakroam($wnet->{ap}); + } + checkNetworkForce(); + }); + update_wireless_item($menuitem, $wnet); + push @{$wnet->{menuitems}}, $menuitem; + return $menuitem->{widget}; +} +sub update_wireless_item { + my ($menuitem, $wnet) = @_; + $menuitem->{label}->set_text($wnet->{name}); + $menuitem->{security}->set_from_pixbuf($pixbufs{encryption}{$wnet->{flags} =~ /WPA/i ? 'strong' : $wnet->{flags} =~ /WEP/i ? 'weak' : 'open'}); + $menuitem->{strength}->set_from_pixbuf(network::signal_strength::get_strength_icon($wnet)); + + $menuitem->{widget}->signal_handler_block($menuitem->{activate}); + $menuitem->{widget}->set_active($wnet->{current}); + $menuitem->{widget}->signal_handler_unblock($menuitem->{activate}); +} +sub checkWireless() { + $wireless_device or return; + my ($networks) = network::monitor::list_wireless($net->{monitor}); + my $force_applet_update; + foreach (keys %$networks) { + exists $wireless_networks{$_} or $force_applet_update = 1; + put_in_hash($wireless_networks{$_} ||= {}, $networks->{$_}); + } + if ($force_applet_update) { + undef $current_state; + } else { + foreach my $wnet (values %wireless_networks) { + my $is_valuable = exists $networks->{$wnet->{ap}}; + foreach (@{$wnet->{menuitems}}) { + update_wireless_item($_, $wnet) if $is_valuable; + $_->{widget}->visible($is_valuable); + } + } + } +} +sub checkNetwork() { + my ($gw_intf, $_is_up, $gw_address) = $watched_interface ? + ($watched_interface, network::tools::get_interface_status($watched_interface)) : + network::tools::get_default_connection($net); + my $connecting = is_running_match('ifup', 'root'); + go2State($gw_address ? 'connected' : $connecting ? 'connecting' : $gw_intf ? 'disconnected' : 'unconfigured', $gw_intf); +} +sub checkNetworkForce() { + $net = {}; + network::network::read_net_conf($net); + undef $current_state; + $wireless_device = detect_devices::get_wireless_interface(); + checkWireless(); + checkNetwork(); +} +sub cronNetwork() { + my $i; + $timeout = Glib::Timeout->add(2000, sub { + checkWireless() if !($i++%30); + checkNetwork(); + 1; + }); +} +sub go2State { + my ($state_type, $interface) = @_; + my $need_update; + my ($old_interface, $old_description); + if ($current_interface ne $interface) { + my $card = find { $_->[0] eq $interface } network::connection::ethernet::get_eth_cards(); + if ($state_type eq 'disconnected') { + $old_interface = $current_interface; + $old_description = $current_description; + } + $current_description = $card && $card->[2]; + $current_interface = $interface; + $need_update = 1; + } + if ($current_state ne $state_type) { + my $show = defined $current_state && $state_type ne 'connecting'; # don't show notification at applet startup and when establishing a connection + $current_state = $state_type; + $notification_queue->add({ + title => $old_description || $current_description || N("Network connection"), + pixbuf => network::net_applet::get_state_pixbuf(), + message => get_state_message($old_interface || $current_interface), + }) if $show; + $need_update = 1; + } + + update_applet() if $need_update; +} + +sub update_applet() { + $wireless_device = detect_devices::get_wireless_interface(); + + # Re-checking wireless networks (#40912) + checkWireless(); + + generate_menu(); + + network::net_applet::update_tray_icon(); + $icon->set_tooltip(get_state_message()); +} + +sub create_menu_choices { + my ($action, $o_allow_single_choice) = @_; + my @choices = $actions{$action}{choices}->(); + #- don't add submenu if only zero or one choice exists + my $allow_single_choice = $actions{$action}{allow_single_choice} || $o_allow_single_choice; + @choices > ($allow_single_choice ? 0 : 1) or return (); + my $selected = $actions{$action}{choice_selected}; + my $format = $actions{$action}{format_choice}; + my $get_icon = $actions{$action}{get_icon}; + map { + my $choice = $_; + my $label = $format ? $format->($choice) : $choice; + my $w = gtkshow(gtkset_active(gtkadd( + Gtk2::CheckMenuItem->new, + gtknew('HBox', children => [ + 1, gtkset_alignment(gtknew('Label', text => $label), 0, 0.5), + $get_icon ? + (0, gtknew('Image', file => $get_icon->($_))) : + (), + ])), $selected->($choice))); + gtksignal_connect($w, activate => sub { $actions{$action}{launch}->($choice) }); + $w->set_draw_as_radio(!$actions{$action}{use_checkbox}); + $w; + } $actions{$action}{choices}->(); +} + +sub create_action_item { + my ($action) = @_; + my $name = ref($actions{$action}{name}) eq 'CODE' ? $actions{$action}{name}->($current_interface) : $actions{$action}{name}; + if (exists $actions{$action}{choices}) { + my @menu = create_menu_choices($action); + @menu || $actions{$action}{header} or return (); + gtkshow(create_menu($name, + $actions{$action}{header} ? ( + create_action_item($actions{$action}{header}), + gtkshow(Gtk2::SeparatorMenuItem->new), + ) : (), + @menu, + )); + } else { + gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label($name)), activate => sub { $actions{$action}{launch}->($current_interface) }); + } +} + +sub empty_menu { + my ($menu) = @_; + delete $_->{menuitems} foreach values %wireless_networks; + $menu->destroy if $menu; + Gtk2::Menu->new; +} + +sub get_wireless_networks_sorted() { + sort { + $b->{current} <=> $a->{current} || $b->{signal_strength} <=> $a->{signal_strength} || $a->{name} cmp $b->{name}; + } values %wireless_networks; +} + +sub generate_simple_menu() { + $simple_menu = empty_menu($simple_menu); + + if ($wireless_device) { + my @networks = get_wireless_networks_sorted(); + my @valuable_networks = splice @networks, 0, 7; + gtkappend($simple_menu, + (map { generate_wireless_menuitem($_) } @valuable_networks), + (@networks ? create_menu(N("More networks"), map { generate_wireless_menuitem($_) } @networks) : ()), + Gtk2::SeparatorMenuItem->new, + ); + } + gtkappend($simple_menu, create_menu_choices('setInterface', 'allow_single_choice')); +} + +sub generate_menu() { + $menu = empty_menu($menu); + + my (@settings); + my $interactive; + eval { $interactive = $ifw->get_interactive }; + + if ($current_state eq 'connected') { + $menu->append(create_action_item($_)) foreach qw(downNetwork monitorNetwork); + } elsif ($current_state eq 'disconnected') { + $menu->append(create_action_item('upNetwork')); + } + $menu->append(create_action_item('monitorIFW')) if $current_state ne 'unconfigured' && defined $interactive; + + $menu->append(create_action_item('confNetwork')); + + push @settings, create_action_item('chooseInterface') if $current_state ne 'unconfigured'; + + push @settings, create_action_item('chooseProfile'); + if (defined $interactive) { + $interactive_cb = gtkshow(gtksignal_connect(gtkset_active(Gtk2::CheckMenuItem->new_with_label(N("Interactive Firewall automatic mode")), + !$interactive), + toggled => sub { eval { $ifw->set_interactive(to_bool(!$_[0]->get_active)) } })); + push @settings, $interactive_cb; + } + push @settings, gtkshow(gtksignal_connect(gtkset_active(Gtk2::CheckMenuItem->new_with_label(N("Always launch on startup")), getAutoStart()), + toggled => sub { setAutoStart(uc(bool2text($_[0]->get_active))) })); + + $menu->append(gtkshow(Gtk2::SeparatorMenuItem->new)); + if ($current_state ne 'unconfigured' && $wireless_device) { + $menu->append(gtkshow(create_menu(N("Wireless networks"), + create_action_item('wireless'), + gtkshow(Gtk2::SeparatorMenuItem->new), + map { generate_wireless_menuitem($_) } get_wireless_networks_sorted()))); + } + if (my $vpn = create_action_item('chooseVPN')) { $menu->append($vpn) } + if (my $set = $current_state ne 'unconfigured' && create_action_item('setInterface')) { $menu->append($set) } + $menu->append(gtkshow(create_menu(N("Settings"), @settings))); + $menu->append(gtkshow(Gtk2::SeparatorMenuItem->new)); + $menu->append(create_action_item('help')); + $menu->append(create_action_item('quit')); + $menu; +} +sub mainQuit() { + Glib::Source->remove($timeout) if $timeout; + Glib::Source->remove($update_timeout) if $update_timeout; + Gtk2->main_quit; +} +sub getAutoStart() { + my %p = getVarsFromSh($onstartupfile); + return to_bool($p{AUTOSTART} ne 'FALSE'); +} +sub setAutoStart { + my $state = shift; + output_p $onstartupfile, + qq(AUTOSTART=$state +); +} |