Description: |
Monitors another process, showing an icon in the system tray while the other process is running. This process can be an already active process (specified by PID), or can be a command to be launched by this program. Programs launched by traymon.pl do not have a console window, so it can be used to monitor them as background processes.
If you have some control over the program to be monitored, and can get it to set a named event, you can also include icon frames to animate the system tray icon while the event is set. This can be used to tell when the program is performing some special action, has received a connection from a user, etc.
You can also define a custom menu to be shown when the icon is right-clicked. The menu actions can be any valid Perl code, and a couple of convinience functions are predefined. The default menu consists of an 'Exit' option, that terminates the program being monitored (via kill 9). |
#!/usr/bin/perl
use warnings;
use strict;
use Getopt::Long qw(GetOptions :config no_ignore_case no_auto_abbrev);
use Win32;
use Win32::GUI;
use Win32::GUI::Carp 'fatalsToDialog', 'windie';
use Win32::Event;
use Win32::Process;
use Config::IniFiles;
use constant ICON_NUM => 1;
# Program to monitor
our $command;
our $directory = '.';
our $child;
# Icon and animation
our $event;
our $tip = '';
our $icon;
our @frames;
our $anim_delay = 125;
our $rewind;
# Menu
our @menu = ('E&xit' => '\&terminate');
# GUI Controls
our $win;
our $menu;
our $niTray;
# Help
our $usage = <<"EOU";
Usage: $0 [ -e [PROGRAM] | -p [PID] ] -i [ICON FILE] [OPTIONS]
or: $0 INIFILE
EOU
our $details = <<'EOD';
Monitor another program by showing a system tray icon while it is runn
+ing.
-e, --execute COMMAND Specify a command to execute and monitor
-E, --event NAME Specify the name of the event to watch for
-f, --frame FILE Include a frame for animating the icon
-h, --help Show this information screen
-i, --icon FILE Specify the .ico file to display
-p, --pid PID Specify the PID to monitor
-r, --rewind Play the icon animation backwards to loop
-t, --tip TIP Tip to display when hovering over icon
You must specify either a command or a PID to monitor, as well as an
icon file.
You may also specify a named event to listen for and one or more other
icon files as frames for the animation to play while the named event
is set. Each frame of animation should be a single .ico file. These
files are shown in the order they are specified. If the --rewind
option is given, the animation will play backwards when it reaches the
last frame, instead of simply starting over from the first frame.
Alternatively, you may specify an INI file containing the above
information, and possibly a menu. The format of the INI file should
be as follows:
[ Program ]
Command = String (may use $^X variable)
Directory = String [optional]
[ Icon ]
Icon = Icon file
Tip = String [optional]
Event = String [optional]
Rewind = True|False [optional]
Delay = Number (ms) [optional]
Frame = Icon file [optional]
Frame = Icon file [optional]
...
[ Menu ] [optional]
Label = Perl code
- = undef # Separator
Label = Perl code
> Nested Label = Perl code
...
The value of each menu entry can be any Perl code that returns a
reference to a subroutine to be executed when the menu item is
selected, or undef. There are two special functions that may be used
from the menu code: terminate(), and winexec().
terminate() forces the monitored program to quit (via signal 9), and
removes the tray icon. You can use it as:
Exit = \&terminate
winexec() launches another program in the background, without causing
the tray icon to freeze. You can use it as:
View Log = sub { winexec('notepad.exe logfile.log') }
The default menu consists of a single Exit item which calls terminate(
+).
EOD
GetOptions(
'h|help' => sub { print $usage, $details; exit; },
'e|execute=s' => \$command,
'p|pid=i' => \$child,
'i|icon=s' => \$icon,
'f|frame=s' => \@frames,
'd|delay=i' => \$anim_delay,
'E|event=s' => \$event,
'r|rewind!' => \$rewind,
't|tip=s' => \$tip,
) or windie $usage;
read_ini(shift) if @ARGV;
windie $usage unless $child || $command and $icon;
$child = winexec($command) if $command;
start_gui();
# Startup Functions
sub read_ini {
my $file = shift;
my $cfg = Config::IniFiles->new(-file => $file)
or windie("Unable to load $file\n");
$command = $cfg->val('Program', 'Command' ) || $command;
$directory = $cfg->val('Program', 'Directory') || $directory;
$event = $cfg->val('Icon', 'Event' ) || $event;
$tip = $cfg->val('Icon', 'Tip' ) || $tip;
$icon = $cfg->val('Icon', 'Icon' ) || $icon;
@frames = $cfg->val('Icon', 'Frame' );
$anim_delay = $cfg->val('Icon', 'Delay' ) || $anim_delay;
$rewind = $cfg->val('Icon', 'Rewind' ) || $rewind;
$rewind = lc($rewind) ne 'false' || $rewind;
# Load the menu
if($cfg->SectionExists('Menu')) {
@menu = map { $_ => scalar $cfg->val('Menu', $_) } $cfg->Parameter
+s('Menu');
}
}
sub start_gui {
$win = Win32::GUI::Window->new();
$icon = Win32::GUI::Icon->new($icon);
if($event) {
$event = Win32::Event->new(1, 0, $event);
@frames = map { new Win32::GUI::Icon($_) } sort map { glob $_ } @f
+rames;
push @frames, reverse @frames if $rewind;
}
$win->Text($tip);
$win->AddTimer('Poll', 500);
set_icon($icon);
# Translate the menu
for(my $i=0; $i<$#menu; $i+=2) {
my $ref = eval $menu[$i+1];
windie("Error in menu item $menu[$i]: $@\n") if $@;
no strict 'refs';
*{"MenuItem$i\_Click"} = $ref if defined $ref;
$menu[$i] = " > $menu[$i]";
$menu[$i+1] = "MenuItem$i";
}
unshift @menu, ('Tray' => 'Tray');
$menu = Win32::GUI::MakeMenu(@menu);
Win32::GUI::Dialog();
}
# GUI Functions
sub set_icon {
my $img = shift;
unless($niTray) {
$niTray = $win->AddNotifyIcon(
-id => ICON_NUM,
-name => 'Tray',
-icon => $img,
-tip => $tip,
);
} else {
Win32::GUI::NotifyIcon::Modify(
$win,
-id => ICON_NUM,
-name => 'Tray',
-icon => $img,
-tip => $tip,
);
}
}
sub clear_icon {
if($niTray) {
$win->AddNotifyIcon(
-id => ICON_NUM,
-name => 'Tray',
);
undef $niTray;
}
}
{
my $frame;
my $atimer;
sub start_animation {
return if defined $frame;
$atimer = $win->AddTimer('Animate', $anim_delay);
$frame = 0;
}
sub stop_animation {
return unless defined $frame;
undef $frame;
$atimer->Kill();
set_icon($icon);
}
sub Animate_Timer {
$frame = ($frame + 1) % @frames;
set_icon($frames[$frame]);
return 1;
}
}
sub Poll_Timer {
if(not kill 0, $child) {
clear_icon();
return -1;
}
if($event and $event->wait(0) == 1) {
start_animation();
} else {
stop_animation();
}
return 1;
}
sub Tray_RightClick {
$win->TrackPopupMenu($menu->{Tray}, Win32::GUI::GetCursorPos());
return 1;
}
# User Functions
sub terminate {
clear_icon();
kill 9, $child;
return -1;
}
sub winexec {
my $cmd = join ' ', @_;
my $pgm = $#_ > 1 ? shift : (split /(?<!\\) /, $cmd)[0];
$pgm =~ s/(\$\^X)/$1/eeg;
Win32::Process::Create(
my $obj,
$pgm,
$cmd,
0,
DETACHED_PROCESS,
$directory,
) or windie "Unable to start `$pgm': " . Win32::FormatMessage(
Win32::GetLastError()
);
return $obj->GetProcessID();
}
|