#!/usr/bin/perl # drakroam: wireless network roaming GUI # beta version uses wlandetect as backend # Austin Acton, 2004 # # Licensed under the GPL # problems # - deletes comments in config file # - expects an ifcfg file for static IP configurations (not uncommon) # - roaming status fails (no idea why) # maybe same reason bash-completion killall can't see wlandetect? # todo # - make known and available lists have more rows by default (why so small?) # - refresh status every x seconds # - find a good way to drop the access point and resume roaming # - make 'key' column wider by default use lib qw(/usr/lib/libDrakX); require ugtk2; ugtk2->import(qw(:wrappers :create)); use Gtk2::SimpleList; use common; require_root_capability(); # global settings my $config = '/etc/wlandetect.conf'; my $route = '/sbin/route'; my $AboutFile = 'ABOUT'; my $HelpFile = 'README'; my $WLanDetect = '/usr/sbin/wlandetect'; my $IWList = '/sbin/iwlist'; my $IWConfig = '/sbin/iwconfig'; my $IFConfig = '/sbin/ifconfig'; my $IFUp = '/sbin/ifup'; my $IFDown = '/sbin/ifdown'; my $DHClient = '/sbin/dhclient'; my $device; # initialize variables my $ScanInterval = 60; # tell deamon to search for new nets every x seconds my $ScanEntry = Gtk2::Entry->new; $ScanEntry->set_width_chars(4); $ScanEntry->set_text($ScanInterval); my $KnownList = Gtk2::SimpleList->new( "ESSID" => "text", "Mode" => "text", "Channel" => "int", "Key" => "text", "DHCP" => "bool" ); $KnownList->get_selection->set_mode('single'); $KnownList->set_reorderable(1); $KnownList->set_column_editable(1, TRUE); # allow to change mode $KnownList->set_column_editable(2, TRUE); # allow to change channel $KnownList->set_column_editable(3, TRUE); # allow to change key my $AvailableList = Gtk2::SimpleList->new( "ESSID" => "text", "Type" => "text", "Encryption" => "text", "Signal (%)" => "int" ); $AvailableList->get_selection->set_mode('single'); my $NetLabel = Gtk2::Label->new; my $IpLabel = Gtk2::Label->new; my $GwLabel = Gtk2::Label->new; my $ModeLabel = Gtk2::Label->new; my $WepLabel = Gtk2::Label->new; my $SignalLabel = Gtk2::Label->new; my $w = ugtk2->new('Drakroam'); gtkadd(gtkset_border_width($w->{window}, 2), gtkpack_(Gtk2::VBox->new, 0, gtkadd(gtkset_border_width(Gtk2::Frame->new("Status"), 2), gtkpack(Gtk2::VBox->new, create_packtable({ col_spacings => 5, row_spacings => 5, homogenous => 1 }, [ $NetLabel, $IpLabel, $GwLabel ], [ $ModeLabel, $WepLabel, $SignalLabel ], ), gtkpack(create_hbox(), gtksignal_connect(Gtk2::Button->new("Disconnect"), clicked => sub { &Disconnect }), gtksignal_connect(Gtk2::Button->new("Refresh"), clicked => sub { &UpdateStatus })))), 0, gtkadd(gtkset_border_width(Gtk2::Frame->new("Roaming"), 2), gtkpack(create_hbox(), gtkpack(Gtk2::VBox->new, my $RoamStatus = Gtk2::Label->new("Roaming: off"), gtkpack(create_hbox(), gtksignal_connect(Gtk2::Button->new("Start"), clicked => sub { &StartRoam }), gtksignal_connect(Gtk2::Button->new("Stop"), clicked => sub { &StopRoam }))), gtkpack(Gtk2::VBox->new, Gtk2::Label->new("Scan interval (sec): "), gtkpack(Gtk2::HBox->new, $ScanEntry, gtksignal_connect(Gtk2::Button->new("Set"), clicked => sub { &SetInterval }))))), 1, gtkadd(gtkset_border_width(Gtk2::Frame->new("Known Networks (Drag up/down or edit)"), 2), gtkpack_(Gtk2::VBox->new, 1, create_scrolled_window($KnownList), 0, gtkpack(create_hbox(), gtksignal_connect(Gtk2::Button->new("Remove"), clicked => sub { my @selected = $KnownList->get_selected_indices; splice @{$KnownList->{data}}, "@selected", 1; }), gtksignal_connect(Gtk2::Button->new("Connect"), clicked => sub { my @selected = $KnownList->get_selected_indices; &ConnectNow(@selected); }), gtksignal_connect(Gtk2::Button->new("Save"), clicked => sub { &WriteConfig })))), 1, gtkadd(gtkset_border_width(Gtk2::Frame->new("Available Networks"), 2), gtkpack_(Gtk2::VBox->new, 1, create_scrolled_window($AvailableList), 0, gtkpack(create_hbox(), gtksignal_connect(Gtk2::Button->new("Add"), clicked => sub { my @selected = $KnownList->get_selected_indices; my ($mode, $channel, $key); my $essid = $AvailableList->{data}[@selected][0]; my $dhcp = 1; # assume dhcp for new networks &AddNet($essid, $mode, $channel, $key, $dhcp); }), gtksignal_connect(Gtk2::Button->new("Rescan"), clicked => sub { &UpdateAvailable })))), 0, gtkpack(create_hbox(), gtksignal_connect(Gtk2::Button->new("Help"), clicked => sub { &Dialog($HelpFile) }), gtksignal_connect(Gtk2::Button->new("About"), clicked => sub { &Dialog($AboutFile) }), gtksignal_connect(Gtk2::Button->new("Save and close"), clicked => sub { &WriteConfig; Gtk2->main_quit; })))); # fill the GUI &ReadConfig; &UpdateAll; sub UpdateAll { &UpdateAvailable; #must go first as it defines the device name &UpdateStatus; &UpdateRoaming; } sub UpdateRoaming() { my $status = "off"; open(my $PS, "ps -A |") or die("Can't open ps\n"); any { /wlandetect$/ } <$PS> and $status = "on"; close $PS; $RoamStatus->set_text("Roaming: $status"); } sub UpdateStatus() { my $CurrentNet = "-"; my $CurrentIP = "---.---.---.---"; my $CurrentGW = "---.---.---.---"; my $CurrentMode = ""; my $CurrentWEP = ""; my $CurrentSignal = "-"; print("Updating\n"); open(my $IWSTATUS, "$IWConfig 2>/dev/null |") or die("Can't run iwconfig\n"); local $_; while (<$IWSTATUS>) { if (/ESSID:"(\S*)"/) { $CurrentNet = $1 } if (/Mode:(\S*)\s/) { $CurrentMode = $1 } if (/key:(\S*)\s/) { $CurrentWEP = $1 } if (m!Quality:(\S*)/!) { $CurrentSignal = $1 } } close $IWSTATUS; open(my $IFSTATUS, "$IFConfig $device |") or die("Can't run ifconfig\n"); while (<$IFSTATUS>) { if (/inet addr:(\S*)\s/) { $CurrentIP = $1 } } close $IFSTATUS; open(my $ROUTE, "$route |") or die("Can't run route\n"); while (<$ROUTE>) { if (/default\s*(\S*)\s/) { $CurrentGW = $1 } else { $CurrentGW = "---.---.---.---" } } close $ROUTE; $NetLabel->set_text("Net: $CurrentNet"); $ModeLabel->set_text("Mode: $CurrentMode"); $IpLabel->set_text("IP: $CurrentIP"); $WepLabel->set_text("Encryption: $CurrentWEP"); $GwLabel->set_text("Gateway: $CurrentGW"); $SignalLabel->set_text("Signal: $CurrentSignal"); } sub UpdateAvailable() { print("Running iwlist\n"); open(my $LIST, "$IWList scan 2>/dev/null |") or die("Can't run iwlist"); @{$AvailableList->{data}} = (); my ($mode, $wep, $signal); local $_; while (<$LIST>) { if (/([^ ]+)([ \t]+)Scan completed :/) { $device = $1 } elsif (/([^ ]+)([ \t]+)No scan results/) { $device = $1 } if (/ESSID:"(\S*)"/) { $essid = $1; print("Found $essid.\n"); } if (/Mode:(\S*)/) { $mode = $1 } if (m!Quality:(\S*)/!) { $signal = $1 } if (/key:(\S*)\s/) { $wep = $1; print("Mode: $mode, WEP: $wep, Signal: $signal\n"); push @{$AvailableList->{data}}, [$essid, $mode, $wep, $signal]; } } close $LIST; } sub AddNet { my $essid = shift @_; my $mode = shift @_; my $channel = shift @_; my $key = shift @_; my $dhcp = shift @_; print "Adding net $essid\n"; push @{$KnownList->{data}}, [ $essid, $mode, $channel, $key, $dhcp ]; } sub ReadConfig() { open(my $CONFIG, $config) or die "Configuration file not found.\n"; print "Reading config file.\n\n"; my $line = 0; local $_; while (<$CONFIG>) { $line++; if (/^#/) {} #ignore comments elsif (/^\n/) {} #ignore blank lines elsif (/([^ \t]+)([ \t]+)(.*)/) { my $essid = $1; my $command = $3; # setup new network entry my $mode; my $channel; my $key; my $dhcp; if ($command =~ /mode\s(\S*);/) { $mode = $1 } elsif ($command =~ /mode\s(\S*)\s/) { $mode = $1 } if ($command =~ /channel\s(\S*);/) { $channel = $1 } elsif ($command =~ /channel\s(\S*)\s/) { $channel = $1 } if ($command =~ /key\s(\S*);/) { $key = $1 } elsif ($command =~ /key\s(\S*)\s/) { $key = $1 } if ($command =~ /dhclient/) { $dhcp = 1 } else { $dhcp = 0 } &AddNet($essid, $mode, $channel, $key, $dhcp); } else { die "Line $line of configuration file is not parseable.\n" } } close $CONFIG; } sub WriteConfig() { open(my $CONFIG, "> $config") or die("Can't open configuration file\n"); print $CONFIG "#wlandetect configuration file\n#format: essidcommands\n#use \@DEV\@ for device name\n"; foreach my $row (@{$KnownList->{data}}) { # again, lame print $CONFIG "$row->[0]\t\t$IWConfig essid $row->[0]"; if ($row->[1]) { print $CONFIG "mode $row->[1] "; } if ($row->[2]) { print $CONFIG "channel $row->[2] "; } if ($row->[3]) { print $CONFIG "key $row->[3] "; } print $CONFIG "; "; if ($row->[4]) { print $CONFIG "$IFConfig \@DEV\@ up; $DHClient \@DEV\@"; } else { print $CONFIG "$IFUp \@DEV\@" } print $CONFIG "\n"; } close $CONFIG; } sub StartRoam { system("killall wlandetect; $WLanDetect -d -t $ScanInterval &"); &UpdateRoaming } sub StopRoam { system("killall wlandetect"); &UpdateRoaming; } sub SetInterval() { $ScanInterval = $ScanEntry->get_text; system("killall wlandetect; $WLanDetect -d -t $ScanInterval &"); } sub ConnectNow { my @command = ""; push @command, "$IWConfig essid $KnownList->{data}[$row][0] "; if ($KnownList->{data}[$row][1]) { push @command, "mode $KnownList->{data}[$row][1] "; } if ($KnownList->{data}[$row][2]) { push @command, "channel $KnownList->{data}[$row][2] "; } if ($KnownList->{data}[$row][3]) { push @command, "key $KnownList->{data}[$row][3] "; } push @command, "; "; if ($KnownList->{data}[$row][4]) { push @command, "$IFConfig $device up; $DHClient $device"; } else { push @command, "$IFUp $device" } my $ToBash = join("", @command); print("Sending $ToBash\n"); system($ToBash); &UpdateStatus; } sub Disconnect { system("$IFDown $device"); &UpdateStatus; } sub Dialog { # read file my $FilePointer = pop @_; my $content = join('', cat_($FilePointer)); # dump into a dialog my $AboutWindow = Gtk2::Dialog->new("Drakroam Info", $w->{window}, 'destroy-with-parent', 'gtk-ok' => 'none'); my $AboutLabel = Gtk2::Label->new($content); my $DialogScroll = Gtk2::ScrolledWindow->new; #$DialogScroll->set_policy('automatic', 'automatic'); $DialogScroll->add_with_viewport($AboutLabel); $AboutWindow->vbox->add($DialogScroll); $AboutWindow->signal_connect(response => sub { $_[0]->destroy }); $AboutWindow->show_all; } # start GUI print "Stating GUI\n"; $w->main;