#!/usr/bin/perl # # mdh.pl-v1.0.31: # mdh (MailDooHicky) is a configurable Gtk-Perl toolbar that can display # the current date/time, mail read/total, overall cpu and memory usage and # network interface statistics (see notes). It also features user-definable # buttons, a run window, simple calendar and a scratch-pad. # # http://www.gozer.org/perl-gtk/mdh.php # # Toolbar Layout: # Menu | Hide | Lock | Run | Pad | ... | Time | Mail | CPU | Mem | Net | Temp # # Requirements: # Perl - http://www.perl.org/ --- tested with 5.6.1 # Gtk-Perl - http://www.gtkperl.org/ --- tested with 0.7008 # # Files Created: # ~/.mdh/rc : configuration file - if configuration is manually saved # ~/.mdh/data : cached temperature data - if Temperature panel is used # # Usage: # mdh.pl # # Examples: # mdh.pl # mdh.pl ~/.mdh/rc.`hostname` # # Thanks: # Paolo Molaro for Gtk-Perl. # Stephen Wilhelm for the Gtk-Perl tutorial. # # ChangeLog: # See the bottom of the script. # # Notes: # Currently, only linux (2.4+ ?) has full support for CPU/Mem/Network # utilization. A fallback function for CPU exists for non-linux systems, # executing uptime. Patches for both all three welcome. # # The configuration file can be specified on the command line, so # multiple toolbars can be used with different settings. # # Right clicking on a user defined button (Custom Buttons) will bring # up the settings for that button. # # If you hide the menu button and save your config, the only way to # get it back is to edit ~/.mdh/rc (or specified config) and change # 'button_menu disable = 1' to 0 or remove the line. # # Temperature data is retrieved from noaa.gov, to find your station-id, # head to http://www.nws.noaa.gov/tg/siteloc.shtml # # Due to numerous problems, I've had to revert back to the non-forking # method for grabbing temperature data. Should weather.noaa.gov become # unavailable or slow, the toolbar will hang until $http_timeout has # passed or the http process has completed. That's why it's disabled by # default and labeled 'Experimental'. :) # # Todo: # Auto-completion for Run command history? Tried, but had some trouble. # Anyone have any experience with this? Some good (small) examples? See # gui_button_run_autocomplete() for current implementation. # # A small dialog for failures from mdh_run_command() would be nice, but # again I'm having difficulty. once _exit() is called within the fork(), # the dialog window will close... Ideas? # # Copyright (c) 2002 - 2003 Mike Hokenson # This application is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. use Socket; use POSIX qw(strftime); use Gtk; use Gtk::Keysyms; use strict; # Swiss Army Shoe was tempting... :) my $mdh = "MailDooHicky"; my $mdh_ver = "1.0.31"; # contains all data related to the panel; vboxes, buttons, config, etc. my %panel; # total number of user buttons. if you need more, this is what to change. my $mdh_total_user_buttons = 5; # contains the list of user customizable buttons my @mdh_user_buttons = (); # contains the list of panels for config_save, panel_toggle, etc. my @mdh_panels = (); # contains the run history :P my @run_history = (); my $false = 0; my $true = 1; # timeout for http connection to weather.noaa.gov; suggested 15-30 seconds. my $http_timeout = 15; # default button relief style: none, half, normal my $button_relief_style = "half"; # default shadow type for frames: none, in, out, etched_in, etched_out my $frame_shadow_type = "in"; # $HOME is not set, configuration cannot be saved. although mdh can function # without a saved config, it could be confusing. unless($ENV{'HOME'} && -d $ENV{'HOME'}) { print "$0: \$HOME is not set or is not a directory. Exiting...\n"; exit(1); } # all default configuration will be stored in ~/.mdh $panel{'mdh_home'} = $ENV{'HOME'} . "/.mdh"; # this can be changed, or just specify an alternate config on the command line. # the config file will be saved ONLY when the Save button is pressed inside # the configuration window. $panel{'mdh_config'} = $panel{'mdh_home'} . "/rc"; # location of mdh datafile, used for caching temperature data $panel{'mdh_data'} = $panel{'mdh_home'} . "/data"; ### ### Begin work ### mkdir($panel{'mdh_home'}, 0700) unless(-d $panel{'mdh_home'}); # specified config file doesn't have to exist $panel{'mdh_config'} = $ARGV[0] if($ARGV[0]); # read config file, setup user button defaults if it failed setup_user_button_defaults() unless(config_read()); # initialize Gtk init Gtk; set_locale Gtk; ### signal handlers # quit $SIG{TERM} = $SIG{QUIT} = $SIG{INT} = sub { mdh_exit(); }; # refresh panel data $SIG{USR1} = sub { foreach my $p (@mdh_panels) { gui_panel_refresh($p); } }; # re-read configuration file $SIG{HUP} = sub { config_read(); }; # setup the main window $panel{'w'} = new Gtk::Window("popup"); $panel{'w'}->signal_connect("destroy", sub { mdh_exit(); }); $panel{'w'}->set_uposition($panel{'geom_x'}, $panel{'geom_y'}); $panel{'w'}->set_policy($true, $true, $true); $panel{'h'} = new Gtk::HBox($false, 2); $panel{'w'}->add($panel{'h'}); $panel{'w'}->border_width(2); $panel{'t'} = new Gtk::Tooltips(); init_button_menu("menu", "+", "$mdh Menu"); init_button("hide", "<", "Toggle Toolbar", \&gui_button_hide); init_button("lock", "Lock", "Lock Workstation", \&gui_button_lock); init_button("run", "Run", "Run...", \&gui_button_run); init_button("pad", "Pad", "Scratch-Pad", \&gui_button_pad); init_button_user(); init_panel("time", "Time", \&gui_get_time); init_panel("mail", "Mail", \&gui_get_mail); init_panel("cpu", "Cpu", \&gui_get_cpu); init_panel("mem", "Mem", \&gui_get_mem); init_panel("net", "Net", \&gui_get_net); init_panel("temp", "Temp", \&gui_get_temp); $panel{'h'}->show(); $panel{'w'}->show(); $panel{'t'}->disable() if($panel{'no_tooltip'} == $true); # make sure at least one panel is shown check_panels(); main Gtk; exit(0); ### ### Panel/Button Initialization Functions ### sub init_panel { my ($p, $p_name, $p_func) = @_; $panel{$p}{'sb'} = new Gtk::Statusbar(); $panel{$p}{'cID'} = $panel{$p}{'sb'}->get_context_id($p); $panel{$p}{'name'} = $p_name; $panel{'h'}->pack_start($panel{$p}{'sb'}, $false, $false, 0); $panel{$p}{'func'} = $p_func; if($panel{$p}{'disable'} == $false) { my $interval = $panel{$p}{'interval'}; my $i = ($interval) ? $interval : 1; $panel{$p}{'sb'}->show(); panel_push($p); $panel{$p}{'interval'} = $i; timeout_add($p); } push(@mdh_panels, $p); } sub panel_push { my $p = $_[0]; return unless($panel{$p}{'disable'} == $false); return unless($panel{$p}{'cID'} && $panel{$p}{'func'}); # remove the previous message; here was a good deal of lost memory if($panel{$p}{'mID'}) { $panel{$p}{'sb'}->remove($panel{$p}{'cID'}, $panel{$p}{'mID'}); } $panel{$p}{'mID'} = $panel{$p}{'sb'}->push($panel{$p}{'cID'}, " " . $panel{$p}{'func'}->() . " "); } sub init_button { my ($b, $b_name, $b_tooltip, $b_func) = @_; my $p = "button_" . $b; $panel{$p}{'sb'} = make_button($b_name); $panel{$p}{'sb'}->set_relief($button_relief_style); $panel{$p}{'sb'}->signal_connect("clicked", \&$b_func); $panel{'t'}->set_tip($panel{$p}{'sb'}, $b_tooltip, ""); $panel{$p}{'name'} = $b_name; $panel{'h'}->pack_start($panel{$p}{'sb'}, $false, $false, -1); if($panel{$p}{'disable'} == $false) { $panel{$p}{'sb'}->show(); } push(@mdh_panels, $p); } sub init_button_user { foreach my $p (@mdh_user_buttons) { my $name = $panel{$p}{'name'} || $p; my $tt = $panel{$p}{'tooltip'} || ""; my $cmd = $panel{$p}{'item'} || ""; my $dis = $panel{$p}{'disable'}; my $x = $1 if($p =~ m/^user(\d+)$/); $panel{$p}{'sb'} = make_button($name); $panel{$p}{'sb'}->set_relief($button_relief_style); $panel{$p}{'sb'}->signal_connect("event", \&gui_button_user_event, $x); $panel{'t'}->set_tip($panel{$p}{'sb'}, $tt, ""); $panel{$p}{'name'} = $name; $panel{'h'}->pack_start($panel{$p}{'sb'}, $false, $false, -1); if(defined($dis) && $dis == $false) { $panel{$p}{'sb'}->show(); } push(@mdh_panels, $p); } $panel{'user'}{'row'} = 0; } sub init_button_menu { my ($b, $b_name, $b_tooltip) = @_; my $menu; my $menu_item; my $mdh_menu; my $p = "button_$b"; $menu = new Gtk::Menu(); $mdh_menu = new Gtk::Menu(); $menu_item = new Gtk::MenuItem($mdh); $menu_item->set_submenu($mdh_menu); $menu->append($menu_item); $menu->append((new Gtk::MenuItem())); # MailDooHicky submenu $menu_item = new Gtk::MenuItem("About"); $mdh_menu->append($menu_item); $menu_item->signal_connect("activate", \&mdh_about_window); $mdh_menu->append((new Gtk::MenuItem())); $menu_item = new Gtk::MenuItem("Configure"); $mdh_menu->append($menu_item); $menu_item->signal_connect("activate", \&gui_button_conf); $menu_item = new Gtk::MenuItem("Custom Buttons"); $mdh_menu->append($menu_item); $menu_item->signal_connect("activate", \&gui_button_user); $menu_item = new Gtk::MenuItem("Toggle Panel"); $mdh_menu->append($menu_item); $menu_item->signal_connect("activate", \&gui_button_hide); $menu_item = new Gtk::MenuItem("Lock Workstation"); $menu->append($menu_item); $menu_item->signal_connect("activate", \&gui_button_lock); $menu_item = new Gtk::MenuItem("Calendar"); $menu->append($menu_item); $menu_item->signal_connect("activate", \&gui_button_cal); $menu_item = new Gtk::MenuItem("Scratch-Pad"); $menu->append($menu_item); $menu_item->signal_connect("activate", \&gui_button_pad); $menu_item = new Gtk::MenuItem("Run..."); $menu->append($menu_item); $menu_item->signal_connect("activate", \&gui_button_run); $menu->append((new Gtk::MenuItem())); $menu_item = new Gtk::MenuItem("Quit"); $menu->append($menu_item); $menu_item->signal_connect("activate", \&gui_button_quit); $menu->show_all(); $panel{$p}{'sb'} = make_button($b_name); $panel{$p}{'sb'}->set_relief($button_relief_style); $panel{$p}{'sb'}->signal_connect("clicked", sub { $menu->popup(undef, undef, 0, 0, undef) }); $panel{'t'}->set_tip($panel{$p}{'sb'}, $b_tooltip, ""); $panel{$p}{'name'} = $b_name; $panel{'h'}->pack_start($panel{$p}{'sb'}, $false, $false, -1); if($panel{$p}{'disable'} == $false) { $panel{$p}{'sb'}->show(); } push(@mdh_panels, $p); } sub gui_button_user_event { my ($button, $row, $event) = @_; my $b = $event->{'button'}; if($event->{'type'} eq "button_press") { gui_button_user_command($row) if($b == 1); # left click gui_button_user(undef, $row) if($b == 3); # right click } return(1); } sub gui_button_user_command { my $p = "user" . $_[0]; mdh_run_command($panel{$p}{'item'}); } ### ### Panel Functions ### sub gui_get_time { return(strftime($panel{'time'}{'item'}, localtime())); } sub gui_get_mail { return($panel{'mail'}{'m_func'}->()); } sub gui_get_mail_box { my $m = $panel{'mail'}{'item'}; my @s_time; my $m_time; my $m_c_time; my $total = 0; my $total_read = 0; my $not_read; $panel{'mail'}{'data'} = " 0/ 0" if(!defined($panel{'mail'}{'data'})); $panel{'mail'}{'time'} = (0, 0) if(!defined($panel{'mail'}{'time'})); @s_time = $panel{'mail'}{'time'}; # mbox does not exist, return no data return(($panel{'mail'}{'data'} = "---/---")) if(!$m || !-f $m); # mbox is empty return(($panel{'mail'}{'data'} = " 0/ 0")) if(-z $m); # previous ctime $m_time = $s_time[0]; # current ctime $m_c_time = (stat($m))[10]; # ctime is unchanged, return the previous data return($panel{'mail'}{'data'}) if($m_c_time <= $m_time); $s_time[0] = $m_c_time; $panel{'mail'}{'time'} = @s_time; if(defined(fileno(MAIL))) { seek(MAIL, 0, 0); } else { open(MAIL, $m) || return($panel{'mail'}{'data'}); # Grr. WHAT IS THIS MONSTROSITY?! :/ $panel{'mail'}{'fh'} = "MAIL"; } while() { $total++ if(/^From /); $total_read++ if(/^Status: .*RO.*/); } $not_read = ($total - $total_read); $not_read = 0 if($not_read < 0); $panel{'mail'}{'data'} = sprintf("%3d/%3d", $not_read, $total); return($panel{'mail'}{'data'}); } sub gui_get_mail_dir { my $m = $panel{'mail'}{'item'}; my @s_time; my @m_time = (0, 0); my @m_c_time = (0, 0); my $total = 0; my $total_read = 0; my $not_read; $panel{'mail'}{'data'} = " 0/ 0" if(!defined($panel{'mail'}{'data'})); $panel{'mail'}{'time'} = (0, 0) if(!defined($panel{'mail'}{'time'})); # maildir does not exist, return no data if(!$m || !-d $m || !(-d "$m/cur" && -d "$m/new")) { return(($panel{'mail'}{'data'} = "---/---")); } @s_time = $panel{'mail'}{'time'}; # previous mtime $m_time[0] = $s_time[0]; $m_time[1] = $s_time[1]; # current ctime $m_c_time[0] = (stat("$m/cur"))[10]; $m_c_time[1] = (stat("$m/new"))[10]; # ctime is unchanged, return the previous data if($m_c_time[0] <= $m_time[0] && $m_c_time[1] <= $m_time[1]) { return($panel{'mail'}{'data'}); } $panel{'mail'}{'time'} = @m_c_time; $total_read = get_maildir_count("$m/cur"); $not_read = get_maildir_count("$m/new"); $not_read = 0 if($not_read < 0); $total = $total_read + $not_read; $panel{'mail'}{'data'} = sprintf("%3d/%3d", $not_read, $total); return($panel{'mail'}{'data'}); } sub get_maildir_count { my $dir = $_[0]; my $file; my @files = (); my $count = 0; opendir(DIR, $dir) || return(0); @files = readdir(DIR); closedir(DIR); foreach $file (@files) { next if($file =~ /^\.{1,2}$/); next unless(-f "$dir/$file"); $count++; } return($count); } sub gui_get_cpu { return($panel{'cpu'}{'m_func'}->()); } sub gui_get_cpu_linux { my $cpu_dev = "/proc/loadavg"; my $cpu_load; if(defined(fileno(CPU))) { seek(CPU, 0, 0); } else { open(CPU, $cpu_dev) || return(gui_get_cpu_generic()); $panel{'cpu'}{'fh'} = "CPU"; } while() { $cpu_load = $1 if(m/^(\d+.\d+)\s+/); last; } return(gui_get_cpu_generic()) unless(defined($cpu_load)); $cpu_load = 1 if($cpu_load > 1); return(sprintf("%3d%%", $cpu_load * 100)); } sub gui_get_cpu_generic { my $uptime; my $cpu_load; $uptime = `uptime` || return("N/A"); $cpu_load = $1 if($uptime =~ m/average.*:\s+(\d+\.\d+),/); return("N/A") unless(defined($cpu_load)); $cpu_load = 1 if($cpu_load > 1); return(sprintf("%3d%%", $cpu_load * 100)); } sub gui_get_mem { return($panel{'mem'}{'m_func'}->()); } sub gui_get_mem_linux { my $mem_dev = "/proc/meminfo"; my $mem_total; my $mem_free; my $swap_total; my $swap_free; my $mem_used; my $mem_avail; if(defined(fileno(MEM))) { seek(MEM, 0, 0); } else { open(MEM, $mem_dev) || return("N/A"); $panel{'mem'}{'fh'} = "MEM"; } while() { # linux 2.2, untested if(m/^Mem:\s+(\d+)\s+\d+\s+(\d+)\s+/) { $mem_total = $1 / 1024; $mem_free = $2 / 1024; } if(m/^Swap:\s+(\d+)\s+(\d+)$/) { $swap_total = $1 / 1024; $swap_free = ($swap_total - $2) / 1024; } # linux 2.4 $mem_total = $1 if(m/^MemTotal:\s+(\d+)\s+/); $mem_free = $1 if(m/^MemFree:\s+(\d+)\s+/); $swap_total = $1 if(m/^SwapTotal:\s+(\d+)\s+/); $swap_free = $1 if(m/^SwapFree:\s+(\d+)\s+/); } return("N/A") if(!defined($mem_total) || !defined($mem_free) || !defined($swap_total) || !defined($swap_free)); $mem_total += $swap_total; $mem_used = $mem_total - ($mem_free + $swap_free); $mem_avail = $mem_free + $swap_free; $panel{'mem'}{'data'} = sprintf("%2.0f (%2.0f%%)", ($mem_used / 1024), (($mem_used / $mem_total * 100.0) + 0.5)); return($panel{'mem'}{'data'}); } sub gui_get_net { return($panel{'net'}{'m_func'}->()); } sub gui_get_net_linux { my $net_dev = "/proc/net/dev"; my $n_rx; my $n_tx; my $n_rx_l = $panel{'net'}{'last'}{'rx'}; my $n_tx_l = $panel{'net'}{'last'}{'tx'}; if(defined(fileno(NET))) { seek(NET, 0, 0); } else { open(NET, $net_dev) || return("N/A"); $panel{'net'}{'fh'} = "NET"; } while() { next unless(m/(\w+):(.*)/); next unless($1 eq $panel{'net'}{'item'}); my $n; my @n_data = (); $n = $2; $n =~ s/^\s+//; @n_data = split(/\s+/, $n); $n_rx = $n_data[0]; $n_tx = $n_data[8]; # have everything we need last; } # interface wasn't found return(" ---/ ---") unless($n_rx); $panel{'net'}{'last'}{'rx'} = $n_rx; $panel{'net'}{'last'}{'tx'} = $n_tx; # no data yet return(" 0.0/ 0.0") unless($n_rx_l); # counter reset/wrap return($panel{'net'}{'data'}) if($n_rx < $n_rx_l || $n_tx < $n_tx_l); $n_rx = (($n_rx - $n_rx_l) / 1024) / $panel{'net'}{'interval'}; $n_tx = (($n_tx - $n_tx_l) / 1024) / $panel{'net'}{'interval'}; return(($panel{'net'}{'data'} = sprintf("%5.1f/%5.1f", $n_rx, $n_tx))); } sub gui_get_temp { my $temp = gui_get_temp_data(); # N/A is returned if the METAR station-id was not found return($temp) unless($temp =~ /^-?\d+$/); $panel{'temp'}{'data'} = $temp; if($panel{'temp'}{'flag'} == 1) { return(sprintf("%1.0fC", $temp)); # Celcius } else { # -18C = -0.4F return("0F") if($temp == -18); return(sprintf("%1.0fF", ($temp * 1.8) + 32)); # Ferenheight } } sub gui_get_temp_data { my $temp = gui_get_temp_cache(); return($temp) if(defined($temp)); $panel{'temp'}{'last'} = time(); # XXX: see corresponding XXX in gui_get_temp_cache() $temp = $panel{'temp'}{'data'}; $SIG{ALRM} = sub { close(FD); }; my $temp_host = "weather.noaa.gov"; my $temp_url = "/cgi-bin/mgetmetar.pl?cccc="; my $temp_id = $panel{'temp'}{'item'}; my $http_req; $http_req = "GET " . $temp_url . $temp_id . " HTTP/1.0\r\n"; $http_req .= "Connection: close\r\n"; $http_req .= "User-Agent: Mozilla/4.7 [en] (Win98; U)\r\n"; $http_req .= "Host: " . $temp_host . "\r\n"; $http_req .= "Accept: image/gif, image/x-xbitmap, image/jpeg, "; $http_req .= "image/pjpeg, image/png, */*\r\n"; $http_req .= "Accept-Language: en\r\n"; $http_req .= "Accept-Charset: iso-8859-1,*,utf-8\r\n"; $http_req .= "\r\n"; alarm($http_timeout); my $pos; my $iaddr = inet_aton($temp_host) || return($temp); my $paddr = sockaddr_in(80, $iaddr); my $proto = getprotobyname('tcp'); socket(FD, PF_INET, SOCK_STREAM, $proto) || return($temp); connect(FD, $paddr) || return($temp); send(FD, $http_req, 0) || return($temp); while() { # receiving content, reset alarm alarm(0); alarm($http_timeout); if(/No METAR observation from /) { $temp = "N/A"; last; } # http://weather.noaa.gov/cgi-bin/mgetmetar.pl?cccc=.... # KLAM 051950Z 14003KT 40SM SKC 05/M07 A3024 # KEWR 271851Z 34018G28KT 10SM CLR M08/M23 A3032 ... # LSZH 271920Z 0 BR FEW003 SCT007 BKN009 04/04 Q1029 # KGRB 252153Z ...27010KT 10SM OVC022 M02/M07 A2996 ... # KGRB 271939Z 18014KT 2SM -SN OVC014 M12/M16 A3013 ... # KGRB 271914Z ... 1 1/4SM -SN OVC024 M12/M17 A3015 ... next unless(/^$temp_id\s/); # slightly modified match from Geo::METAR.pm. # if (A|Q)3013 always followed the temp/dew, that could # be included as well, but I'm not so sure... if(/\s(M?\d\d)\/M?\d\d\s/) { $temp = $1; $temp =~ s/^M/-/; last; } } close(FD); alarm(0); $panel{'temp'}{'time'} = time(); $panel{'temp'}{'data'} = $temp; gui_get_temp_cache_update(); return($temp); } sub gui_get_temp_cache { my $temp = $panel{'temp'}{'data'} || "---"; my $id = $panel{'temp'}{'item'}; my $time = $panel{'temp'}{'time'} || 0; my $int = $panel{'temp'}{'interval'} || 900; my $last = $panel{'temp'}{'last'}; my $curr = time(); # forced update if(defined($panel{'temp'}{'force'})) { my $min = ($http_timeout < 60) ? 60 : $http_timeout; # refreshed < 60 (or http_timeout) seconds ago return($temp) if(defined($last) && ($curr - $last) <= $min); if($panel{'temp'}{'th'}) { timeout_del("temp"); timeout_add("temp"); } return(($panel{'temp'}{'force'} = undef)); } # don't want to update the data every time the config window is closed, # time will be reset if changing station ids, in which case this check # will be skipped and the data will update. return($temp) if(($time + $int) > $curr); my $file = $panel{'mdh_data'}; return unless($file); # no cache file found, refresh data from source return(undef) if(!-s $file); $time = undef; open(FILE, $file) || return($temp); while() { chomp; if(/^$id:(\d+):(.*)$/) { $time = $1; $temp = $2; } } close(FILE); # XXX: is the stored data is better than no data, regardless of age? $panel{'temp'}{'data'} = $temp; # nothing found in file, refresh data from source return(undef) unless(defined($time)); # current data found in file, return existing return($temp) if(($time + $int) > $curr); # data is old, refresh data from source return(undef); } sub gui_get_temp_cache_update { my $temp = $panel{'temp'}{'data'}; my $id = $panel{'temp'}{'item'}; my $time = $panel{'temp'}{'time'}; my $file = $panel{'mdh_data'}; return unless($file); my @contents = (); my $id_found = $false; if(-s $file) { open(FILE, $file) || return; while() { if(/^$id:\d+:.*/) { $id_found = $true; push(@contents, "$id:$time:$temp\n"); } else { push(@contents, $_); } } close(FILE); } push(@contents, "$id:$time:$temp\n") unless($id_found == $true); open(FILE, "> $file") || return; print FILE @contents; close(FILE); } ### ### Button And Supporing Functions ### sub gui_button_conf { my $window; my $main_vbox; my $vbox; my $vbox2; my $hbox; my $button; my $spinner; my $notebook; my $table; my $frame; my $tt; my $label; my $tab_name; my $panel_desc; my $adj; my %entries; my %buttons; my $max_x = Gtk::Gdk->screen_width() - 5; my $max_y = Gtk::Gdk->screen_height() - 5; # make sure the panels are displayed if config window is opened gui_button_hide() if($panel{'hidden'} == $true); return if(gui_window_open("conf")); $window = new Gtk::Window("toplevel"); $window->set_title("$mdh Configuration"); $window->signal_connect("destroy", sub { gui_window_close("conf"); }); $window->signal_connect("event", \&gui_window_event, "conf"); $window->border_width(5); $window->set_usize(395, 0); $window->set_policy($false, $false, $false); $table = new Gtk::Table(3, 6, $false); $window->add($table); $notebook = new Gtk::Notebook(); $notebook->set_tab_pos("top"); $notebook->set_scrollable($true); $notebook->set_homogeneous_tabs($true); $table->attach_defaults($notebook, 0, 6, 0, 1); $tt = new Gtk::Tooltips(); ### General Tab $main_vbox = new Gtk::VBox($false, 5); $main_vbox->border_width(5); $tab_name = "General"; $frame = new Gtk::Frame("Toolbar Location"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $true, $true, 0); $vbox = new Gtk::VBox($false, 0); $frame->add($vbox); $hbox = new Gtk::HBox($false, 0); $vbox->pack_start($hbox, $true, $true, 5); $vbox2 = new Gtk::VBox($true, 0); $hbox->pack_start($vbox2, $true, $true, 5); $adj = new Gtk::Adjustment($panel{'geom_x'}, 0, $max_x, 1, 50, 0); $spinner = new Gtk::SpinButton($adj, 0, 0); $spinner->set_wrap($true); $spinner->set_shadow_type('in'); $tt->set_tip($spinner, "Adjust toolbars horizontal location", ""); $adj->signal_connect("value_changed", \&gui_button_conf_geometry, $spinner, "geom_x"); $vbox2->pack_start($spinner, $false, $true, 0); $vbox2 = new Gtk::VBox($true, 0); $hbox->pack_start($vbox2, $true, $true, 5); $adj = new Gtk::Adjustment($panel{'geom_y'}, 0, $max_y, 1, 50, 0); $spinner = new Gtk::SpinButton($adj, 0, 0); $spinner->set_wrap($true); $spinner->set_shadow_type('in'); $tt->set_tip($spinner, "Adjust toolbars vertical location", ""); $vbox2->pack_start($spinner, $false, $true, 0); $adj->signal_connect("value_changed", \&gui_button_conf_geometry, $spinner, "geom_y"); ### Labels, Button Tooltips, Lock command, Run xterm $hbox = new Gtk::HBox($true, 0); $main_vbox->pack_start($hbox, $true, $true, 0); ### Tooltips $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame = new Gtk::Frame("Button Tooltips"); $frame->set_shadow_type($frame_shadow_type); $frame->add($vbox); $hbox->pack_start($frame, $true, $true, 2); $button = new Gtk::CheckButton("Enable"); $button->set_active(($panel{'no_tooltip'} == $false)); $button->signal_connect("clicked", \&tooltip_toggle); $tt->set_tip($button, "Enable tooltips for main toolbar buttons", ""); $vbox->pack_start($button, $true, $true, 0); ### Lock command $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame = new Gtk::Frame("Lock command"); $frame->set_shadow_type($frame_shadow_type); $frame->add($vbox); $hbox->pack_start($frame, $true, $true, 2); $entries{'lock'} = new Gtk::Entry(255); $entries{'lock'}->set_text($panel{'lock'}{'item'}); $tt->set_tip($entries{'lock'}, "Command to lock workstation (xlock, xscreensaver)", ""); $vbox->pack_start($entries{'lock'}, $true, $true, 0); ### Run xterm $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame = new Gtk::Frame("Run xterm"); $frame->set_shadow_type($frame_shadow_type); $frame->add($vbox); $hbox->pack_start($frame, $true, $true, 2); $entries{'run'} = new Gtk::Entry(25); $entries{'run'}->set_text($panel{'run'}{'item'}); $tt->set_tip($entries{'run'}, "XTerm to use for Run... (-e assumed)", ""); $vbox->pack_start($entries{'run'}, $true, $true, 0); ### Menu, Hide, Run, Pad and Lock Buttons $frame = new Gtk::Frame("Buttons"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $true, $true, 0); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $hbox = new Gtk::HBox($true, 0); $vbox->pack_start($hbox, $true, $true, 0); $button = new Gtk::CheckButton("Menu"); $button->set_active(($panel{'button_menu'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "button_menu", undef, undef, undef); $tt->set_tip($button, "Display the Menu button (recommended)", ""); $hbox->pack_start($button, $true, $true, 1); $button = new Gtk::CheckButton("Hide"); $button->set_active(($panel{'button_hide'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "button_hide", undef, undef, undef); $tt->set_tip($button, "Display the Hide button", ""); $hbox->pack_start($button, $true, $true, 1); $button = new Gtk::CheckButton("Lock"); $button->set_active(($panel{'button_lock'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "button_lock", undef, undef, undef); $tt->set_tip($button, "Display the Lock Workstation button", ""); $hbox->pack_start($button, $true, $true, 1); $button = new Gtk::CheckButton("Run"); $button->set_active(($panel{'button_run'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "button_run", undef, undef, undef); $tt->set_tip($button, "Display the Run button", ""); $hbox->pack_start($button, $true, $true, 1); $button = new Gtk::CheckButton("Pad"); $button->set_active(($panel{'button_pad'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "button_pad", undef, undef, undef); $tt->set_tip($button, "Display the Scratch-Pad button", ""); $hbox->pack_start($button, $true, $true, 1); $notebook->append_page($main_vbox, (new Gtk::Label($tab_name))); ### Time panel $main_vbox = new Gtk::VBox($false, 5); $main_vbox->border_width(5); $tab_name = $panel{'time'}{'name'}; $frame = new Gtk::Frame("Panel: $tab_name"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $true, $true, 0); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $entries{'time'} = new Gtk::Entry(30); set_sensitive($entries{'time'}, opp($panel{'time'}{'disable'})); $button = new Gtk::CheckButton("Enable"); $button->set_active(($panel{'time'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "time", $entries{'time'}, undef, undef); $tt->set_tip($button, "Display the $tab_name panel", ""); $vbox->pack_start($button, $false, $true, 10); $label = new Gtk::Label("strftime format:"); $label->set_alignment(0, 0.5); $vbox->pack_start($label, $false, $true, 0); $entries{'time'}->set_text($panel{'time'}{'item'}); $tt->set_tip($entries{'time'}, "strftime format to use (default: \%m/\%d - \%l:\%M \%p)", ""); $vbox->pack_start($entries{'time'}, $false, $true, 0); $panel_desc = "Displays time as defined by above strftime(3) format."; $label = gui_button_conf_notebook_label($panel_desc); $main_vbox->pack_start($label, $false, $false, 0); $notebook->append_page($main_vbox, (new Gtk::Label($tab_name))); ### Mail panel $main_vbox = new Gtk::VBox($false, 5); $main_vbox->border_width(5); $tab_name = $panel{'mail'}{'name'}; $frame = new Gtk::Frame("Panel: $tab_name"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $true, $true, 0); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $hbox = new Gtk::HBox($false, 0); $vbox->pack_start($hbox, $false, $false, 0); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 0); $adj = new Gtk::Adjustment($panel{'mail'}{'interval'}, 5, 300, 5, 15,0); $spinner = new Gtk::SpinButton($adj, 0, 0); set_sensitive($spinner, opp($panel{'mail'}{'disable'})); $entries{'mail'} = new Gtk::Entry(255); set_sensitive($entries{'mail'}, opp($panel{'mail'}{'disable'})); $button = new Gtk::CheckButton("Enable"); $button->set_active(($panel{'mail'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "mail", $entries{'mail'}, $spinner, undef); $tt->set_tip($button, "Display the $tab_name panel", ""); $vbox2->pack_start($button, $false, $true, 10); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 5); $label = new Gtk::Label("Update Interval (seconds):"); $label->set_alignment(0, 0.5); $vbox2->pack_start($label, $false, $true, 0); $spinner->set_wrap($true); $spinner->set_shadow_type('in'); $tt->set_tip($spinner, "Set the update interval (default: 30)", ""); $vbox2->pack_start($spinner, $false, $true, 0); $adj->signal_connect("value_changed", \&gui_button_conf_interval, $spinner, "mail"); $hbox = new Gtk::HBox($false, 0); $vbox->pack_start($hbox, $false, $false, 0); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 0); $label = new Gtk::Label("Mailbox / Maildir:"); $label->set_alignment(0, 0.5); $vbox2->pack_start($label, $false, $true, 0); $entries{'mail'}->set_text($panel{'mail'}{'item'}); $tt->set_tip($entries{'mail'}, "Mailbox/dir to process (default: \$MAIL, \$MAILDIR)", ""); $vbox2->pack_start($entries{'mail'}, $false, $true, 0); $panel_desc = "Displays read/total count of above mailbox/dir."; $label = gui_button_conf_notebook_label($panel_desc); $main_vbox->pack_start($label, $false, $false, 0); $notebook->append_page($main_vbox, (new Gtk::Label($tab_name))); ### Cpu panel $main_vbox = new Gtk::VBox($false, 5); $main_vbox->border_width(5); $tab_name = $panel{'cpu'}{'name'}; $frame = new Gtk::Frame("Panel: $tab_name"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $true, $true, 0); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $hbox = new Gtk::HBox($false, 0); $vbox->pack_start($hbox, $false, $false, 0); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 0); $adj = new Gtk::Adjustment($panel{'cpu'}{'interval'}, 1, 30, 1, 5, 0); $spinner = new Gtk::SpinButton($adj, 0, 0); set_sensitive($spinner, opp($panel{'cpu'}{'disable'})); $button = new Gtk::CheckButton("Enable"); $button->set_active(($panel{'cpu'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "cpu", undef, $spinner, undef); $tt->set_tip($button, "Display the $tab_name panel", ""); $vbox2->pack_start($button, $false, $true, 10); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 5); $label = new Gtk::Label("Update Interval (seconds):"); $label->set_alignment(0, 0.5); $vbox2->pack_start($label, $false, $true, 0); $spinner->set_wrap($true); $spinner->set_shadow_type('in'); $tt->set_tip($spinner, "Set the update interval (default: 3)", ""); $vbox2->pack_start($spinner, $false, $true, 0); $adj->signal_connect("value_changed", \&gui_button_conf_interval, $spinner, "cpu"); $panel_desc = "Displays cpu load percentage over last 60 seconds."; $label = gui_button_conf_notebook_label($panel_desc); $main_vbox->pack_start($label, $false, $false, 0); $notebook->append_page($main_vbox, (new Gtk::Label($tab_name))); ### Mem panel $main_vbox = new Gtk::VBox($false, 5); $main_vbox->border_width(5); $tab_name = $panel{'mem'}{'name'}; $frame = new Gtk::Frame("Panel: $tab_name"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $true, $true, 0); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $hbox = new Gtk::HBox($false, 0); $vbox->pack_start($hbox, $false, $false, 0); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 0); $adj = new Gtk::Adjustment($panel{'mem'}{'interval'}, 1, 30, 1, 5, 0); $spinner = new Gtk::SpinButton($adj, 0, 0); set_sensitive($spinner, opp($panel{'mem'}{'disable'})); $button = new Gtk::CheckButton("Enable"); $button->set_active(($panel{'mem'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "mem", undef, $spinner, undef); $tt->set_tip($button, "Display the $tab_name panel", ""); $vbox2->pack_start($button, $false, $true, 10); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 5); $label = new Gtk::Label("Update Interval (seconds):"); $label->set_alignment(0, 0.5); $vbox2->pack_start($label, $false, $true, 0); $spinner->set_wrap($true); $spinner->set_shadow_type('in'); $tt->set_tip($spinner, "Set the update interval (default: 3)", ""); $vbox2->pack_start($spinner, $false, $true, 0); $adj->signal_connect("value_changed", \&gui_button_conf_interval, $spinner, "mem"); $panel_desc = "Displays amount (MB) of system memory used."; $label = gui_button_conf_notebook_label($panel_desc); $main_vbox->pack_start($label, $false, $false, 0); $notebook->append_page($main_vbox, (new Gtk::Label($tab_name))); ### Net panel $main_vbox = new Gtk::VBox($false, 5); $main_vbox->border_width(5); $tab_name = $panel{'net'}{'name'}; $frame = new Gtk::Frame("Panel: $tab_name"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $true, $true, 0); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $hbox = new Gtk::HBox($false, 0); $vbox->pack_start($hbox, $false, $false, 0); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 0); $adj = new Gtk::Adjustment($panel{'net'}{'interval'}, 1, 30, 1, 5, 0); $spinner = new Gtk::SpinButton($adj, 0, 0); set_sensitive($spinner, opp($panel{'net'}{'disable'})); $entries{'net'} = new Gtk::Entry(5); set_sensitive($entries{'net'}, opp($panel{'net'}{'disable'})); $button = new Gtk::CheckButton("Enable"); $button->set_active(($panel{'net'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "net", $entries{'net'}, $spinner, undef); $tt->set_tip($button, "Display the $tab_name panel", ""); $vbox2->pack_start($button, $false, $true, 10); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 5); $label = new Gtk::Label("Update Interval (seconds):"); $label->set_alignment(0, 0.5); $vbox2->pack_start($label, $false, $true, 0); $spinner->set_wrap($true); $spinner->set_shadow_type('in'); $tt->set_tip($spinner, "Set the update interval (default: 3)", ""); $vbox2->pack_start($spinner, $false, $true, 0); $adj->signal_connect("value_changed", \&gui_button_conf_interval, $spinner, "net"); $hbox = new Gtk::HBox($false, 0); $vbox->pack_start($hbox, $true, $true, 0); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 0); $label = new Gtk::Label("Network Interface:"); $label->set_alignment(0, 0.5); $vbox2->pack_start($label, $false, $true, 0); $entries{'net'}->set_text($panel{'net'}{'item'}); $tt->set_tip($entries{'net'}, "Network interface to monitor (default: eth0)", ""); $vbox2->pack_start($entries{'net'}, $false, $true, 0); $panel_desc = "Displays utilization (down/up) for above interface."; $label = gui_button_conf_notebook_label($panel_desc); $main_vbox->pack_start($label, $false, $false, 0); $notebook->append_page($main_vbox, (new Gtk::Label($tab_name))); ### Temperature panel $main_vbox = new Gtk::VBox($false, 5); $main_vbox->border_width(5); $tab_name = $panel{'temp'}{'name'}; $frame = new Gtk::Frame("Panel: $tab_name"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $true, $true, 0); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $hbox = new Gtk::HBox($false, 0); $vbox->pack_start($hbox, $false, $false, 0); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 0); # 5 minute increments $adj = new Gtk::Adjustment($panel{'temp'}{'interval'}, 900, 3600, 300, 900, 0); $spinner = new Gtk::SpinButton($adj, 0, 0); set_sensitive($spinner, opp($panel{'temp'}{'disable'})); $entries{'temp'} = new Gtk::Entry(4); set_sensitive($entries{'temp'}, opp($panel{'temp'}{'disable'})); $buttons{'celcius'} = new Gtk::CheckButton("Display in Celcius"); set_sensitive($buttons{'celcius'}, opp($panel{'temp'}{'disable'})); $buttons{'force'} = make_button("Force Update"); set_sensitive($buttons{'force'}, opp($panel{'temp'}{'disable'})); $button = new Gtk::CheckButton("Enable"); $button->set_active(($panel{'temp'}{'disable'} == $false)); $button->signal_connect("clicked", \&gui_button_conf_toggle, "temp", $entries{'temp'}, $spinner, %buttons); $tt->set_tip($button, "Display the $tab_name panel", ""); $vbox2->pack_start($button, $false, $true, 10); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 5); $label = new Gtk::Label("Update Interval (seconds):"); $label->set_alignment(0, 0.5); $vbox2->pack_start($label, $false, $true, 0); $spinner->set_wrap($true); $spinner->set_shadow_type('in'); $tt->set_tip($spinner, "Set the update interval (default: 900)", ""); $vbox2->pack_start($spinner, $false, $true, 0); $adj->signal_connect("value_changed", \&gui_button_conf_interval, $spinner, "temp"); $hbox = new Gtk::HBox($false, 0); $vbox->pack_start($hbox, $true, $true, 0); $vbox2 = new Gtk::VBox($false, 0); $hbox->pack_start($vbox2, $true, $true, 0); # an url would be nice here... :I $label = new Gtk::Label("Station-ID " . "(http://www.nws.noaa.gov/tg/siteloc.shtml):"); $label->set_alignment(0, 0.5); $vbox2->pack_start($label, $false, $true, 0); $entries{'temp'}->set_text($panel{'temp'}{'item'}); $tt->set_tip($entries{'temp'}, "Station-ID to use (default: KGRB -- Green Bay, WI)", ""); $vbox2->pack_start($entries{'temp'}, $false, $true, 0); $buttons{'celcius'}->set_active(($panel{'temp'}{'flag'} == $true)); $buttons{'celcius'}->signal_connect("clicked", sub { $panel{'temp'}{'flag'} = opp($panel{'temp'}{'flag'}); panel_push("temp"); }); $tt->set_tip($buttons{'celcius'}, "Display in Celcius instead of Ferenheight", ""); $hbox = new Gtk::HBox($false, 5); $hbox->add($buttons{'celcius'}); my $min_refresh = ($http_timeout < 60) ? 60 : $http_timeout; $tt->set_tip($buttons{'force'}, "Force Update (valid every $min_refresh seconds)", ""); $buttons{'force'}->signal_connect("clicked", sub { $panel{'temp'}{'force'} = $true; panel_push("temp"); }); $hbox->add($buttons{'force'}); $vbox2->pack_start($hbox, $false, $false, 5); $panel_desc = "Displays temperature for above station-id. Experimental."; $label = gui_button_conf_notebook_label($panel_desc); $main_vbox->pack_start($label, $false, $false, 0); $notebook->append_page($main_vbox, (new Gtk::Label($tab_name))); ### Save/Close buttons $button = make_button("Save"); $button->signal_connect("clicked", sub { gui_button_conf_close($true, %entries); $window->destroy; }); $tt->set_tip($button, "Save and Apply settings", ""); $table->attach_defaults($button, 0, 1, 1, 2); $button = make_button("Close"); $button->signal_connect("clicked", sub { gui_button_conf_close($false, %entries); $window->destroy; }); $tt->set_tip($button, "Apply settings", ""); $table->attach_defaults($button, 1, 2, 1, 2); $window->show_all(); } sub gui_button_conf_notebook_label { my $desc = $_[0]; my $label; $label = new Gtk::Label($desc); $label->set_alignment(0, 0); $label->set_line_wrap($true); $label->set_usize(305, 0); return($label); } sub gui_button_conf_toggle { my ($b, $p, $entry, $spinner, %buttons) = @_; _panel_toggle($p); $panel{$p}{'disable'} = opp($b->active); # make sure at least one panel is shown check_panels(); # no timers for buttons return(1) unless($p !~ /^button_/); set_sensitive($entry, $b->active); set_sensitive($spinner, $b->active); foreach my $key (keys(%buttons)) { set_sensitive($buttons{$key}, $b->active); } if($panel{$p}{'disable'} == $false) { my $interval = $panel{$p}{'interval'}; my $i = ($interval) ? $interval : 1; $panel{$p}{'interval'} = $i; timeout_del($p); timeout_add($p); # don't wait for the timer, update immediately panel_push($p) if($panel{$p}{'interval'} > 1); } else { timeout_del($p); # panel has been disabled, close any open file handle if(defined($panel{$p}{'fh'})) { close($panel{$p}{'fh'}); $panel{$p}{'fh'} = undef; } } return(1); } sub set_sensitive { my ($widget, $value) = @_; return unless(defined($widget) && defined($value)); $widget->set_sensitive($value); } sub gui_button_conf_interval { my ($widget, $spinner, $p) = @_; $panel{$p}{'interval'} = $spinner->get_value_as_int(); timeout_del($p); timeout_add($p); } sub gui_button_conf_geometry { my ($widget, $spinner, $p) = @_; $panel{$p} = $spinner->get_value_as_int(); $panel{'w'}->set_uposition($panel{'geom_x'}, $panel{'geom_y'}); } sub gui_button_conf_close { my ($save_config, %entries) = @_; my $key; gui_window_close("conf"); if(%entries) { foreach $key (keys(%entries)) { my $item; next unless($entries{$key}); $item = $entries{$key}->get_text(); next unless(length($item)); $panel{$key}{'item'} = clean_string($item); } } # re-check mail config, possible change to maildir from mailbox, etc. setup_mail(); setup_temp(); # perhaps all panel data should be updated... check_common(); config_save() if($save_config == $true); } sub mdh_about_window { my $window; my $main_vbox; my $vbox; my $frame; my $label; my $button; return if(gui_window_open("about")); $window = new Gtk::Window("toplevel"); $window->set_title("About $mdh"); $window->set_policy($false, $false, $false); $window->border_width(5); $window->signal_connect("destroy", sub { gui_window_close("about"); }); $window->signal_connect("event", \&gui_window_event, "about"); $main_vbox = new Gtk::VBox($false, 0); $window->add($main_vbox); $frame = new Gtk::Frame("About $mdh"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $false, $false, 0); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $label = new Gtk::Label($mdh . " v" . $mdh_ver); $vbox->pack_start($label, $true, $true, 0); $label = new Gtk::Label("http://www.gozer.org/perl-gtk/mdh.php\n"); $vbox->pack_start($label, $true, $true, 0); $label = new Gtk::Label("(c) 2002 - 2003 Mike Hokenson "); $vbox->pack_start($label, $true, $true, 0); $button = new Gtk::Button("Close"); $main_vbox->pack_start($button, $false, $false, 0); $button->can_default($true); $button->grab_default(); $button->signal_connect("clicked", sub { gui_window_close("about"); $window->destroy; }); $window->show_all(); } sub gui_button_quit { my $window; my $main_vbox; my $vbox; my $hbox; my $button; my $label; return if(gui_window_open("quit")); $window = new Gtk::Window("toplevel"); $window->set_policy($false, $false, $false); $window->signal_connect("destroy", sub { gui_window_close("quit"); } ); $window->signal_connect("event", \&gui_window_event, "quit"); $window->set_title("Quit $mdh"); $window->set_usize(150, 0); $window->border_width(5); $main_vbox = new Gtk::VBox($false, 0); $window->add($main_vbox); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(10); $main_vbox->pack_start($vbox, $false, $true, 0); $label = new Gtk::Label("Quit $mdh:"); $label->set_alignment(0.5, 0.5); $vbox->pack_start($label, $false, $false, 0); $label = new Gtk::Label("Are you sure?"); $label->set_alignment(0.5, 0.5); $vbox->pack_start($label, $false, $false, 0); $hbox = new Gtk::HBox($true, 3); $main_vbox->pack_start($hbox, $false, $true, 0); $button = make_button("Yes"); $button->signal_connect("clicked", sub { mdh_exit(); }); $hbox->pack_start($button, $false, $true, 0); $button = make_button("No"); $button->signal_connect("clicked", sub { gui_window_close("quit"); $window->destroy; }); $hbox->pack_start($button, $false, $true, 0); $window->show_all(); } sub gui_button_run { my $window; my $main_vbox; my $vbox; my $hbox; my $combo; my $button; my $use_xterm = $false; return if(gui_window_open("run")); $window = new Gtk::Window("toplevel"); $window->set_policy($false, $false, $false); $window->signal_connect("destroy", sub { gui_window_close("run"); }); $window->signal_connect("event", \&gui_window_event, "run"); $window->set_title("$mdh Run..."); $window->set_usize(150, 0); $main_vbox = new Gtk::VBox($false, 0); $main_vbox->border_width(5); $window->add($main_vbox); $combo = new Gtk::Combo(); $main_vbox->pack_start($combo, $false, $false, 0); $combo->entry->signal_connect("activate", sub { gui_button_run_command($combo->entry, $use_xterm); gui_window_close("run"); $window->destroy; }); # $combo->entry->signal_connect("changed", \&gui_button_run_autocomplete); if(@run_history) { my $hist = $run_history[$#run_history]; $combo->set_popdown_strings(reverse(@run_history)); $combo->entry->set_text($hist); $combo->entry->select_region(0, length($hist)); } $combo->entry->grab_focus(); $combo->disable_activate(); $button = new Gtk::CheckButton("Run in terminal"); $button->set_active($false); $button->signal_connect("clicked", sub { $use_xterm = opp($use_xterm); }); $main_vbox->pack_start($button, $false, $false, 3); $hbox = new Gtk::HBox($true, 3); $main_vbox->pack_start($hbox, $false, $true, 0); $vbox = new Gtk::VBox($false, 0); $main_vbox->pack_start($vbox, $false, $true, 0); $button = make_button("Run"); $button->signal_connect("clicked", sub { gui_button_run_command($combo->entry, $use_xterm); gui_window_close("run"); $window->destroy; }); $hbox->pack_start($button, $false, $true, 0); $button = make_button("Cancel"); $button->signal_connect("clicked", sub { gui_window_close("run"); $window->destroy; }); $hbox->pack_start($button, $false, $true, 0); $window->show_all(); } sub gui_button_run_command { my ($combo, $use_xterm) = @_; my $cmd = $combo->get_text(); my $command; return unless($cmd); $command = $panel{'run'}{'item'} . " -e " if($use_xterm); $command .= $cmd; # include the xterm command? maybe find another way to toggle xterm # usage, but not save it in the command... push(@run_history, $command); # limit the run history to 25 items shift(@run_history) if($#run_history > 24); mdh_run_command($command); return(1); } #sub gui_button_run_autocomplete { # my $entry = $_[0]; # # return unless($entry); # # my $text = $entry->get_text; # # return unless($text); # # # The idea would be to mimic Window's Run Command dialog: # # @run_history contains "xterm" as an example, and the user types # # x, xterm (or the last matching command) would be filled in, but # # 'term' would be highlighted so the user could continue to type. # # # # Currently nothing is selected. I've tried using $entry = new # # Gtk::Editable("Gtk::Entry"), but that makes no difference. # # # # Also tried to $entry->signal_block($id), then unblock after. That # # seems to help a bit, but still nothing useful. # # # # Could be some logic I'm not understanding when the GtkEntry is # # updated. # foreach my $hist (reverse(@run_history)) { # if($hist =~ /^$text/ && $hist ne $text) { # $entry->set_text($hist); # $entry->set_position(length($text)); # $entry->select_region(length($text), length($hist)); # # last; # } # } #} sub gui_panel_refresh { my $p = $_[0]; timeout_del($p) if($panel{$p}{'disable'} == $true); return(0) unless(defined($panel{$p}{'func'})); panel_push($p); return(1); } sub gui_button_cal { my $window; my $main_vbox; my $vbox; my $calendar; my $button; my $frame; my $label; return if(gui_window_open("cal")); $window = new Gtk::Window("toplevel"); $window->set_title("$mdh Calendar"); $window->border_width(5); $window->signal_connect("destroy", sub { gui_window_close("cal"); }); $window->signal_connect("event", \&gui_window_event, "cal"); $window->set_policy($false, $false, $true); $main_vbox = new Gtk::VBox($false, 0); $window->add($main_vbox); $frame = new Gtk::Frame("$mdh Calendar"); $frame->set_shadow_type($frame_shadow_type); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $calendar = new Gtk::Calendar(); $calendar->mark_day((localtime())[3]); $calendar->select_day(0); $vbox->pack_start($calendar, $false, $false, 0); $main_vbox->pack_start($frame, $false, $false, 0); $button = new Gtk::Button("Close"); $main_vbox->add($button); $button->signal_connect("clicked", sub { gui_window_close("cal"); $window->destroy; }); $button->can_default($true); $button->grab_default(); $window->show_all(); } sub gui_button_pad { my $window; my $main_vbox; my $vbox; my $vbox2; my $frame; my $button; my $notebook; my $table; my $vscrollbar; my %text; my $tt; my $i; my $window_x = $panel{'button_pad'}{'geom_x'}; my $window_y = $panel{'button_pad'}{'geom_y'}; my $window_h = $panel{'button_pad'}{'geom_h'}; my $window_w = $panel{'button_pad'}{'geom_w'}; return if(gui_window_open("pad")); $window = new Gtk::Dialog(); $window->border_width(5); $window->set_title("$mdh Scratch-Pad"); $window->set_policy($false, $true, $false); $window->set_usize(300, 300); $window->set_default_size($window_w, $window_h); if(defined($window_x) && defined($window_y)) { $window->set_uposition($window_x, $window_y); } $window->signal_connect("configure_event", sub { gui_button_pad_config_geometry($window); } ); $window->signal_connect("destroy", sub { gui_window_close("pad"); gui_button_pad_set_text($notebook->get_current_page(), %text); }); $window->signal_connect("event", \&gui_window_event, "about"); $main_vbox = new Gtk::VBox($false, 0); $frame = new Gtk::Frame("$mdh Scratch-Pad"); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $notebook = new Gtk::Notebook(); $notebook->set_tab_pos("top"); $notebook->set_scrollable($true); $notebook->set_homogeneous_tabs($true); $vbox->pack_start($notebook, $true, $true, 0); $frame->add($vbox); $main_vbox->pack_start($frame, $true, $true, 0); $tt = new Gtk::Tooltips(); for($i = 0; $i <= 4; $i++) { my $p_no = $i + 1; my $p_item = $panel{'button_pad'}{$i}{'item'}; $vbox2 = new Gtk::VBox($false, 0); $table = new Gtk::Table(2, 5, $false); $vbox2->pack_start($table, $true, $true, 0); $text{$i} = new Gtk::Text(undef, undef); $text{$i}->set_editable($true); $table->attach($text{$i}, 0, 4, 0, 1, [ 'expand', 'shrink', 'fill' ], [ 'expand', 'shrink', 'fill' ], 0, 0); $text{$i}->insert(undef, undef, undef, $p_item) if($p_item); $vscrollbar = new Gtk::VScrollbar($text{$i}->vadj); $table->attach($vscrollbar, 4, 5, 0, 1, 'fill', [ 'expand', 'shrink', 'fill' ], 0, 0 ); $button = make_button("Clear"); $button->signal_connect("clicked", sub { gui_button_pad_clear_text($false, $notebook->get_current_page(), %text); }); $tt->set_tip($button, "Clear Pad $p_no", ""); $table->attach($button, 0, 2, 1, 2, ['fill'], ['fill'], 0, 0); $button = make_button("<<"); $button->signal_connect("clicked", sub { $notebook->prev_page(); $text{$notebook->get_current_page}->grab_focus(); }); $tt->set_tip($button, "Previous Pad", ""); $table->attach($button, 2, 3, 1, 2, ['fill'], ['fill'], 0, 0); $button = make_button(">>"); $button->signal_connect("clicked", sub { $notebook->next_page(); $text{$notebook->get_current_page}->grab_focus(); }); $tt->set_tip($button, "Next Pad", ""); $table->attach($button, 3, 4, 1, 2, ['fill'], ['fill'], 0, 0); $notebook->append_page($vbox2, (new Gtk::Label("Pad: $p_no"))); } $button = make_button("Clear All"); $button->signal_connect("clicked", sub { gui_button_pad_clear_text($true, $notebook->get_current_page(), %text); }); $tt->set_tip($button, "Clear All Scratch-Pads", ""); $window->action_area->pack_start($button, $true, $true, 0); $button = make_button("Close"); $button->signal_connect("clicked", sub { gui_window_close("pad"); gui_button_pad_set_text($notebook->get_current_page(), %text); $window->destroy; }); $tt->set_tip($button, "Close Scratch-Pad", ""); $window->action_area->pack_start($button, $true, $true, 0); $window->vbox->pack_start($main_vbox, $true, $true, 0); $window->show_all(); # go back to the last page used when the window was closed $notebook->set_page($panel{'button_pad'}{'page'}); $text{$notebook->get_current_page}->grab_focus(); # I can't seem to get the insertion point set to the end of the text # using set_point()? I must be doing something wrong... heh :/ $text{$notebook->get_current_page}->insert(undef, undef, undef, " "); $text{$notebook->get_current_page}->backward_delete(1); } sub gui_button_pad_set_text { my ($page, %text) = @_; my $key; foreach $key (keys(%text)) { $panel{'button_pad'}{$key}{'item'} = $text{$key}->get_chars(); } $panel{'button_pad'}{'page'} = $page; } sub gui_button_pad_clear_text { my ($clear_all, $page, %text) = @_; if($clear_all == $true) { my $key; foreach $key (keys(%text)) { $text{$key}->backward_delete($text{$key}->get_length); } } else { $text{$page}->backward_delete($text{$page}->get_length); } $text{$page}->grab_focus(); } sub gui_button_pad_config_geometry { my $window = $_[0]; # not sure what the deal is with these functions, but x and y are # alwlays returned as 0 and allocation seems to be one behind w/h. # my ($x, $y, $w, $h) = @{$window->allocation}; # my ($x, $y, $w, $h) = $w->window->get_geometry; my ($window_x, $window_y) = $window->window->get_position(); my ($window_h, $window_w) = $window->window->get_size(); $panel{'button_pad'}{'geom_x'} = $window_x; $panel{'button_pad'}{'geom_y'} = $window_y; $panel{'button_pad'}{'geom_w'} = $window_w; $panel{'button_pad'}{'geom_h'} = $window_h; } sub gui_button_lock { mdh_run_command($panel{'lock'}{'item'}); } sub gui_button_user { my ($menu, $sel) = @_; my $window; my $scrolled_window; my $main_vbox; my $vbox; my $hbox; my $frame; my $button; my $clist; my $tt; return if(gui_window_open("user")); my $row_sel = (defined($sel)) ? $sel : $panel{'user'}{'row'}; $window = new Gtk::Window("toplevel"); $window->set_title("Custom Buttons"); $window->set_default_size(415, 215); $window->set_policy($true, $true, $true); $window->border_width(5); $window->signal_connect("destroy", sub { user_unselect($clist); gui_window_close("user"); }); $window->signal_connect("event", \&gui_window_event, "user"); $main_vbox = new Gtk::VBox($false, 0); $window->add($main_vbox); $frame = new Gtk::Frame("Custom Buttons"); $frame->set_shadow_type($frame_shadow_type); $main_vbox->pack_start($frame, $true, $true, 0); $vbox = new Gtk::VBox($false, 0); $vbox->border_width(5); $frame->add($vbox); $scrolled_window = new Gtk::ScrolledWindow(undef, undef); $scrolled_window->set_policy("automatic", "always"); $vbox->pack_start($scrolled_window, $true, $true, 0); $clist = new_with_titles Gtk::CList(("Button Name", "Tooltip", "Command", "Enabled")); $clist->column_titles_passive(); $clist->set_shadow_type("none"); $clist->set_selection_mode("single"); $clist->set_column_width(0, 75); $clist->set_column_width(1, 115); $clist->set_column_width(2, 100); $clist->set_column_width(3, 50); $clist->signal_connect("select_row", \&user_select); $clist->signal_connect("unselect_row", sub { user_unselect($clist); }); foreach my $p (@mdh_user_buttons) { my $name = $panel{$p}{'name'} || $p; my $tt = $panel{$p}{'tooltip'} || ""; my $cmd = $panel{$p}{'item'} || ""; my $dis = $panel{$p}{'disable'}; $dis = $panel{$p}{'disable'} = $true unless(defined($dis)); $clist->append($name, $tt, $cmd, ($dis == $false) ? "Yes" : "No"); } $scrolled_window->add($clist); $hbox = new Gtk::HBox($true, 0); $hbox->border_width(5); $main_vbox->pack_start($hbox, $false, $true, 0); $tt = new Gtk::Tooltips(); $panel{'ue'}{'name'} = new Gtk::Entry(15); $panel{'ue'}{'name'}->signal_connect("changed", sub { user_update($clist); }); $tt->set_tip($panel{'ue'}{'name'}, "Button Name", ""); $hbox->pack_start($panel{'ue'}{'name'}, $true, $true, 2); $panel{'ue'}{'tooltip'} = new Gtk::Entry(50); $panel{'ue'}{'tooltip'}->signal_connect("changed", sub { user_update($clist); }); $tt->set_tip($panel{'ue'}{'tooltip'}, "Button Tooltip (optional)", ""); $hbox->pack_start($panel{'ue'}{'tooltip'}, $true, $true, 2); $panel{'ue'}{'item'} = new Gtk::Entry(255); $panel{'ue'}{'item'}->signal_connect("changed", sub { user_update($clist); }); $tt->set_tip($panel{'ue'}{'item'}, "Button Command", ""); $hbox->pack_start($panel{'ue'}{'item'}, $true, $true, 2); $hbox = new Gtk::HBox($true, 0); $hbox->border_width(5); $main_vbox->pack_start($hbox, $false, $true, 0); $panel{'ue'}{'b'} = make_button(""); $panel{'ue'}{'b'}->signal_connect("clicked", sub { user_toggle($clist); }); $tt->set_tip($panel{'ue'}{'b'}, "Toggle Button", ""); $hbox->pack_start($panel{'ue'}{'b'}, $true, $true, 3); $button = make_button("Save"); $button->signal_connect("clicked", sub { user_unselect($clist); gui_button_conf_close($true, ("", "")); gui_window_close("user"); $window->destroy; }); $tt->set_tip($button, "Save and Apply Custom Buttons", ""); $hbox->pack_start($button, $true, $true, 3); $button = make_button("Close"); $button->signal_connect("clicked", sub { user_unselect($clist); gui_window_close("user"); $window->destroy; }); $tt->set_tip($button, "Apply Custom Buttons", ""); $hbox->pack_start($button, $true, $true, 3); $window->show_all(); $clist->select_row($row_sel, 0); sub user_select { my ($clist, $row, $column, $event, @data) = @_; my $p = "user" . $row; $panel{'user'}{'row'} = $row; my $name = $panel{$p}{'name'} || $p; my $tt = $panel{$p}{'tooltip'} || ""; my $cmd = $panel{$p}{'item'} || ""; my $dis = $panel{$p}{'disable'}; $panel{'ue'}{'name'}->set_text($name); $panel{'ue'}{'tooltip'}->set_text($tt); $panel{'ue'}{'item'}->set_text($cmd); my $b = ($dis == $false) ? "Disable" : "Enable"; $panel{'ue'}{'b'}->child->set($b); $panel{'ue'}{'name'}->grab_focus(); } sub user_unselect { my $clist = $_[0]; my $row = $panel{'user'}{'row'}; my $p = "user" . $row; my $name = $panel{$p}{'name'}; my $tt = $panel{$p}{'tooltip'}; my $cmd = $panel{$p}{'item'}; my $dis = $panel{$p}{'disable'}; return unless($dis == $false && !($name && $cmd)); _panel_hide($p); $clist->set_text($row, 3, "No"); $panel{$p}{'disable'} = $true; } sub user_update { my $clist = $_[0]; my $name = $panel{'ue'}{'name'}->get_text; my $tt = $panel{'ue'}{'tooltip'}->get_text; my $cmd = $panel{'ue'}{'item'}->get_text; my $row = $panel{'user'}{'row'}; my $p = "user" . $row; $clist->set_text($row, 0, $name); $clist->set_text($row, 1, $tt); $clist->set_text($row, 2, $cmd); $panel{$p}{'name'} = clean_string($name); $panel{$p}{'tooltip'} = clean_string($tt); $panel{$p}{'item'} = clean_string($cmd); return unless($name); $panel{$p}{'sb'}->child->set($name); $panel{'t'}->set_tip($panel{$p}{'sb'}, ($tt) ? $tt : $name, ""); } sub user_toggle { my $clist = $_[0]; my $row = $panel{'user'}{'row'}; my $p = "user" . $row; my $name = $clist->get_text($row, 0); my $tt = $clist->get_text($row, 1); my $cmd = $clist->get_text($row, 2); $panel{'ue'}{'name'}->grab_focus(); return unless($name && $cmd); my $dis = opp($panel{$p}{'disable'}); $clist->set_text($row, 3, ($dis == $false) ? "Yes" : "No"); $panel{$p}{'disable'} = $dis; ($dis == $false) ? _panel_show($p) : _panel_hide($p); my $b = ($dis == $false) ? "Disable" : "Enable"; $panel{'ue'}{'b'}->child->set($b); # make sure at least one panel is shown check_panels(); } } ### ### Configuration File Related ### sub config_read { my $config = $panel{'mdh_config'}; my $config_old = 0; setup_time(); setup_mail(); setup_cpu(); setup_mem(); setup_net(); setup_temp(); $panel{'run'}{'item'} = "xterm"; $panel{'lock'}{'item'} = "xlock -mode blank"; $panel{'geom_x'} = 0; $panel{'geom_y'} = 0; $panel{'button_pad'}{'geom_w'} = 300; $panel{'button_pad'}{'geom_h'} = 300; $panel{'button_pad'}{'geom_x'} = undef; $panel{'button_pad'}{'geom_y'} = undef; $panel{'button_pad'}{'page'} = 0; $panel{'no_tooltip'} = $false; $panel{'button_menu'}{'disable'} = $false; $panel{'button_hide'}{'disable'} = $false; $panel{'button_run'}{'disable'} = $true; $panel{'button_pad'}{'disable'} = $true; $panel{'button_lock'}{'disable'} = $true; $panel{'hidden'} = $false; setup_user_buttons(); gui_window_close("conf"); gui_window_close("quit"); gui_window_close("about"); gui_window_close("user"); gui_window_close("cal"); gui_window_close("pad"); gui_window_close("run"); check_common(); unless(-f $config) { # import old (pre 1.0.24) config if(-f $ENV{'HOME'} . "/.mdhrc") { rename($ENV{'HOME'} . "/.mdhrc", $config); rename($ENV{'HOME'} . "/.mdh_data", $panel{'mdh_data'}); } else { return(0); } } open(CONFIG, $config) || return(0); while() { chomp; next if(/^#/ | /^\s+#/ | /^$/); my $val; if(m/^(\w+)\s+(\w+)\s+=\s+(.*)$/) { # cpu interval = 1 $val = clean_string($3); $panel{$1}{$2} = $val if(length($val)); } elsif(m/^(\w+)\s+=\s+(.*)$/) { # no_tooltip = 1 $val = clean_string($2); $panel{$1} = $val if(length($val)); } } close(CONFIG); # re-check settings, figure out which function to use setup_mail(); setup_temp(); check_common(); setup_user_button_defaults(); return(1); } # I'm sure there's a bunch missing from this sub clean_string { my $str = $_[0]; $str =~ s/^\s+//; $str =~ s/\s+$//; $str =~ s/\n//g; return($str); } sub setup_time { $panel{'time'}{'item'} = "%m/%d - %l:%M %p"; $panel{'time'}{'disable'} = $false; } sub setup_mail { $panel{'mail'}{'data'} = " 0/ 0"; $panel{'mail'}{'time'} = (0, 0); # post config_read if(defined($panel{'mail'}{'item'})) { if(-f $panel{'mail'}{'item'}) { $panel{'mail'}{'m_func'} = \&gui_get_mail_box; } elsif(-d $panel{'mail'}{'item'}) { $panel{'mail'}{'m_func'} = \&gui_get_mail_dir; } else { # not so sure about this, depend on the situation # this could be annoying if the mail client were to # remove the mailbox if all messages are deleted... #$panel{'mail'}{'disable'} = $true; } # the mailbox has (possibly) changed, refresh panel_push("mail") if($panel{'mail'}{'disable'} == $false); return; } my $mailbox = $ENV{'MAIL'}; my $maildir = $ENV{'MAILDIR'}; # pre config_read if($maildir && !$mailbox) { $panel{'mail'}{'item'} = $maildir; $panel{'mail'}{'m_func'} = \&gui_get_mail_dir; } else { $panel{'mail'}{'item'} = $mailbox; $panel{'mail'}{'m_func'} = \&gui_get_mail_box; } $panel{'mail'}{'interval'} = 30; $panel{'mail'}{'disable'} = $false; #$panel{'mail'}{'disable'} = $true if(!$panel{'mail'}{'item'}); } sub setup_cpu { if($^O eq "linux") { $panel{'cpu'}{'m_func'} = \&gui_get_cpu_linux; } else { $panel{'cpu'}{'m_func'} = \&gui_get_cpu_generic; } $panel{'cpu'}{'item'} = ""; $panel{'cpu'}{'interval'} = 3; $panel{'cpu'}{'disable'} = $false; } sub setup_mem { if($^O eq "linux") { $panel{'mem'}{'disable'} = $false; $panel{'mem'}{'m_func'} = \&gui_get_mem_linux; } else { $panel{'mem'}{'disable'} = $true; $panel{'mem'}{'m_func'} = sub { return("N/A"); }; } $panel{'mem'}{'item'} = ""; $panel{'mem'}{'interval'} = 3; } sub setup_net { if($^O eq "linux") { $panel{'net'}{'disable'} = $false; $panel{'net'}{'m_func'} = \&gui_get_net_linux; } else { $panel{'net'}{'disable'} = $true; $panel{'net'}{'m_func'} = sub { return("N/A"); }; } $panel{'net'}{'item'} = "eth0"; $panel{'net'}{'interval'} = 3; } sub setup_temp { if(defined($panel{'temp'}{'item'})) { # don't want to hammer on their server if($panel{'temp'}{'interval'} < 900) { $panel{'temp'}{'interval'} = 900; } if(!defined($panel{'temp'}{'prev'})) { $panel{'temp'}{'prev'} = $panel{'temp'}{'item'}; } return if($panel{'temp'}{'disable'} == $true); return if($panel{'temp'}{'item'} eq $panel{'temp'}{'prev'}); $panel{'temp'}{'item'} =~ tr/a-z/A-Z/; $panel{'temp'}{'time'} = 0; $panel{'temp'}{'data'} = "---"; $panel{'temp'}{'prev'} = $panel{'temp'}{'item'}; panel_push("temp"); return; } $panel{'temp'}{'item'} = "KGRB"; # Station-ID: Green Bay, WI $panel{'temp'}{'data'} = "---"; # Temp stored as Celcius $panel{'temp'}{'interval'} = 900; # 15 minutes $panel{'temp'}{'flag'} = 0; # 1 = Celcius, X = Ferenheight $panel{'temp'}{'disable'} = $true; } sub config_save { my $config = $panel{'mdh_config'}; my $open_err; my $run_item = $panel{'run'}{'item'}; my $lock_item = $panel{'lock'}{'item'}; unless($config) { print "$0: config_save: cannot save, no config file is set.\n"; return; } check_common(); open(CONFIG, "> $config") || ($open_err = $!); if($open_err) { print "$0: config_save: $config: $open_err\n"; return; } print CONFIG "# configuration for mdh ($mdh)\n"; print CONFIG "geom_x = " . $panel{'geom_x'} . "\n"; print CONFIG "geom_y = " . $panel{'geom_y'} . "\n"; print CONFIG "no_tooltip = " . $panel{'no_tooltip'} . "\n"; print CONFIG "run item = " . $run_item . "\n" if($run_item); print CONFIG "lock item = " . $lock_item . "\n" if($lock_item); foreach my $p (@mdh_panels) { my $dis = $panel{$p}{'disable'}; my $int = $panel{$p}{'interval'}; my $item = $panel{$p}{'item'}; my $flag = $panel{$p}{'flag'}; my $name = $panel{$p}{'name'}; my $tt = $panel{$p}{'tooltip'}; print CONFIG "$p disable = " . $dis . "\n"; next unless($p !~ /^button_/); print CONFIG "$p interval = " . $int . "\n" if($int); print CONFIG "$p item = " . $item . "\n" if($item); print CONFIG "$p flag = " . $flag . "\n" if($flag); next unless($p =~ /^user\d+$/); print CONFIG "$p name = " . $name . "\n" if($name); print CONFIG "$p tooltip = " . $tt . "\n" if($tt); } close(CONFIG); } # values that shouldn't or cannot change here sub check_common { $panel{'mem'}{'disable'} = $true unless(-f "/proc/meminfo"); $panel{'net'}{'disable'} = $true unless(-f "/proc/net/dev"); $panel{'time'}{'interval'} = 1; } ### ### Panel Hiding Related Functions ### sub gui_button_hide { my $panel_dir; # don't allow the panel to be hidden while the config window is open return if($panel{'gui_button_conf_window_open'} == $true); foreach my $p (@mdh_panels) { next if($p eq "button_hide" || $p eq "button_menu"); # the panel/button has been disabled in the config window next if($panel{$p}{'disable'} == $true); _panel_hide($p) if($panel{'hidden'} == $false); _panel_show($p) if($panel{'hidden'} == $true); } $panel{'hidden'} = opp($panel{'hidden'}); $panel_dir = ($panel{'hidden'} == $true) ? ">" : "<"; $panel{'button_hide'}{'sb'}->child->set($panel_dir); } sub _panel_toggle { my $p = $_[0]; ($panel{$p}{'disable'} == $true) ? _panel_show($p) : _panel_hide($p); } sub _panel_hide { my $p = $_[0]; $panel{$p}{'sb'}->hide() if($panel{$p}{'sb'}); } sub _panel_show { my $p = $_[0]; $panel{$p}{'sb'}->show() if($panel{$p}{'sb'}); } # make sure at least one panel is shown sub check_panels { foreach my $p (@mdh_panels) { return if($panel{$p}{'disable'} == $false); } # the menu button seems the obvious choice _panel_show("button_menu"); $panel{'button_menu'}{'disable'} = $false; } ### ### Tooltip Related Functions ### sub tooltip_toggle { my $button = $_[0]; ($button->active == $true) ? $panel{'t'}->enable() : $panel{'t'}->disable(); $panel{'no_tooltip'} = opp($button->active); } ### ### Misc Functions ### # yeah, $v = not $v works too, but the warnings on undef values annoy me. :P sub opp { ($_[0] == $true) ? return($false) : return($true); } # wrapper to create a Gtk button with the focus nonsense off sub make_button { my $face = $_[0] || "(null)"; my $button; $button = new Gtk::Button($face); $button->can_focus($false); return($button); } # this is pretty ugly, but I don't know of another way of keeping track of # open windows with Gtk and I'd prefer not to have multiple windows for the # same item (configuration, pad, etc) open at once. sub gui_window_open { my $w = $_[0]; return($false) unless($w); my $b = "button_" . $w; my $p = "gui_button_" . $w . "_window_open"; return($true) if($panel{$p} == $true); $panel{$p} = $true; $panel{$b}{'sb'}->set_relief("none") if($panel{$b}{'sb'}); return($false); } sub gui_window_close { my $w = $_[0]; return($false) unless($w); my $b = "button_" . $w; my $p = "gui_button_" . $w . "_window_open"; $panel{$p} = $false; $panel{$b}{'sb'}->set_relief($button_relief_style) if($panel{$b}{'sb'}); } sub mdh_exit { # close any open file handles foreach my $p (@mdh_panels) { close($panel{$p}{'fh'}) if(defined($panel{$p}{'fh'})); } Gtk->exit(0); } sub mdh_run_command { my $command = $_[0]; return unless(defined($command) && length($command)); system($command . " &"); # # doesn't work because of the _exit(), not sure what could be done # if(!fork()) { # #system($command) == 0 || mdh_run_notice($command); # exec($command) || mdh_run_notice($command); # # _exit(0); # } else { # waitpid(-1, WNOHANG); # } } #sub mdh_run_notice { # my $command = $_[0]; # # my $window; # my $vbox; # my $label; # my $button; # # $window = new Gtk::Window("toplevel"); # $window->set_title("$mdh: $command failed"); # $window->border_width(5); # $window->set_usize(250, 0); # $window->set_policy($false, $false, $false); # # $vbox = new Gtk::VBox($false, 0); # # $window->add($vbox); # # $label = new Gtk::Label("Execution of $command failed."); # $label->set_usize(225, 0); # $label->set_alignment(0, 0.5); # $label->set_line_wrap($true); # # $vbox->pack_start($label, $false, $false, 0); # # $button = new Gtk::Button("Close"); # $button->signal_connect("clicked", sub { $window->destroy; }); # # $vbox->pack_start($button, $false, $false, 5); # # $button->can_default($true); # $button->grab_default(); # # $window->show_all(); #} # event handler that closes the window if Escape is hit sub gui_window_event { my ($window, $p, $event) = @_; return(1) unless($window && $event); return(0) unless($event->{'type'} eq "key_press"); return(0) unless($event->{'keyval'} == $Gtk::Keysyms{'Escape'}); gui_window_close($p) if($p); $window->destroy; } sub timeout_del { my $p = $_[0]; return(0) unless(defined($p) && defined($panel{$p}{'th'})); Gtk->timeout_remove($panel{$p}{'th'}); $panel{$p}{'th'} = undef; return(1); } sub timeout_add { my $p = $_[0]; return(0) unless(defined($p)); my $interval = $panel{$p}{'interval'} * 1000; $panel{$p}{'th'} = Gtk->timeout_add($interval, \&gui_panel_refresh, $p); return(1); } # custom user buttons must be follow 'userX' scheme (config_save) and start # with 'user0'. this isn't so great, would be better to dynamically add and # remove user buttons, but it's much easier this way, acceptable for most. sub setup_user_buttons { my $i; my $b = ($mdh_total_user_buttons < 1) ? 1 : $mdh_total_user_buttons; for($i = 0; $i < $b; $i++) { my $p = "user" . $i; $panel{$p}{'name'} = $p; push(@mdh_user_buttons, $p); } } # don't want to override saved settings, 'disable' will ALWAYS be set, so # that's a reasonable test to see if there's anything setup for the button. # this should be run after we are done processing the users's config. sub setup_user_button_defaults { foreach my $p (@mdh_user_buttons) { my $x = $1 if($p =~ m/^user(\d+)$/); # heh, arg.. make sure 4 == the number of items defined below next unless($x > 4); unless(defined($panel{$p}{'disable'})) { $panel{$p}{'disable'} = $true; } } unless(defined($panel{'user0'}{'disable'})) { $panel{'user0'}{'name'} = "Moz"; $panel{'user0'}{'tooltip'} = "Mozilla"; $panel{'user0'}{'item'} = "mozilla"; $panel{'user0'}{'disable'} = $true; } unless(defined($panel{'user1'}{'disable'})) { $panel{'user1'}{'name'} = "GMC"; $panel{'user1'}{'tooltip'} = "GNOME File Manager"; $panel{'user1'}{'item'} = "gmc"; $panel{'user1'}{'disable'} = $true; } unless(defined($panel{'user2'}{'disable'})) { $panel{'user2'}{'name'} = "Term"; $panel{'user2'}{'tooltip'} = "X Terminal"; $panel{'user2'}{'item'} = "xterm"; $panel{'user2'}{'disable'} = $true; } unless(defined($panel{'user3'}{'disable'})) { $panel{'user3'}{'name'} = "XQF"; $panel{'user3'}{'tooltip'} = "Quake Server Browser"; $panel{'user3'}{'item'} = "xqf"; $panel{'user3'}{'disable'} = $true; } unless(defined($panel{'user4'}{'disable'})) { $panel{'user4'}{'name'} = "Mix"; $panel{'user4'}{'tooltip'} = "Audio Mixer"; $panel{'user4'}{'item'} = "aumix"; $panel{'user4'}{'disable'} = $true; } } # --- ChangeLog --------------------------------------------------------------- # # 1.0.11 - 12/18/2002: # if all panels/buttons are disabled, the menu button will be displayed. # # 1.0.12 - 12/21/2002: # finally figured out the label wrapping problem, must $label->set_usize(). # fallback to 'uptime' for CPU utilization if not linux, this should work # on pretty much anything with a similar uptime output. # # 1.0.13 - 12/22/2002: # maildir support (automatically detected), used by default if $MAIL is not # set but $MAILDIR is, can be setup in the configuration window. # minor variable related changes to mail functions. # # 1.0.14 - 12/23/2002: # fixed a problem when switching between mailbox and maildir formats. # mail counts will be refreshed immediately when changing mailboxes. # additional minor mail related cleanups. # # 1.0.15 - 12/23/2002: # fixed/updated gui_panel_refresh to remove the timer for any disabled # panel, not just mail. :) # created setup_net/cpu to determine the function to use at startup rather # than every time the corresponding gui_get_* function is called. # # 1.0.16 - 12/23/2002: # added Mem panel (displays total system usage in MB and percentage). # updated all panels to include a 1 space buffer before and after data. # # 1.0.17 - 12/24/2002: # created a common function to push the data to the panels. # made the various panels grab the data only if the panel is enabled. :I # # 1.0.18 - 12/25/2002: # addition of temperature panel, but there's still some issues to work out # if the server is unreachable/down without causing the whole toolbar to # freeze up while it's waiting... :I consider this feature experimental. # # 1.0.19 - 12/30/2002: # minor cleanups. # # 1.0.20 - 1/7/2003: # updated the configuration window to disable the various spinners/widgets # if that particular panel was disabled. # other minor cleanups. # # 1.0.21 - 1/9/2003: # fixed a problem with the temperature gathering; using index() to find # the first instance of '/' instead of the crappy regex previously used, # which wouldn't always work if there were multiple instances of '/' in # the string... # # 1.0.22 - 1/24/2003: # fixed a bug in the temperature panel: -18C = -0.4F, and that returns -0 # due to the formatting with sprintf(). # previous messages to the statusbars are removed. this should keep the # memory usage from continually growing as mdh runs. oops! :P # file handles for mail/cpu/net/etc are now opened only once and seek()'d # when refreshing data. # # 1.0.23 - 1/27/2003: # randomly changing output format from the national weather service sucks. # updated the temperature stuffs yet again. # # 1.0.24 - 2/7/2003: # all (default) configuration has been moved to ~/.mdh/, the old ~/.mdhrc # (and ~/.mdh_data) config will be imported. # mdh will exit unless $HOME is set, mainly to avoid confusion if the user # were to try and save the config, but nothing is saved for the next run. # signal handlers for SIGHUP (re-read config) and SIGUSR1 (refresh panel # data) were added. # mdh will no longer hang while processing the temperature updates from # weather.noaa.gov (if it's down, unreachable or slow). # fixed a bug with temperature cache and changing station-ids. # other minor cleanups. # # 1.0.25 - 2/8/2003: # fixed some fork() related problems (defunct processes). # fixed a possible race in SIGCHLD handler if weather.noaa.gov is down. # # 1.0.26 - 2/9/2003: # changed signal handler for temperature to SIGUSR2 instead of SIGCHLD, # allowing the user definable buttons, Run and Lock Workstation commands # to function again, oops. :I # # 1.0.27 - 2/13/2003: # fixed duplicate temperature cache entries if no ~/.mdh/data file exists # when the temperature panel is enabled. # added Force Update button to the Temperature configuration panel, this # will work at a minimum of 60 seconds (or $http_timeout if > 60) # between each attempt. remember, this service is provided by someone # else, so use it sparingly. # re-worked SIGUSR2 (child) race checks. # other minor cleanups. # # 1.0.28 - 2/15/2003: # fixed the scrollbars in the Scratch-Pad, they actually do stuff now. :) # # 1.0.29 - 2/19/2003: # added a general window event handler for the dialogs (Run, Scratch-Pad, # Configuration, etc) that will close the window if Escape is hit. # minor cleanups. # # 1.0.30 - 2/26/2003: # the fork() related code for the temperature panel has been removed due # to numerous problems, Gtk errors and/or defunct processes. I just can't # seem to get it to work. I've worked with fork before and nothing has # given me problems like this. Is it something I've done wrong or maybe # something with GtkPerl? :I # # 1.0.31 - 2/27/2003: # removed extraneous VBox from init_panel() and the like. # # --- ChangeLog ---------------------------------------------------------------