Beefy Boxes and Bandwidth Generously Provided by pair Networks DiBona
There's more than one way to do things.
 
PerlMonks

traymon.pl - Easily add a System Tray icon to an exisiting program

by bbfu (Curate)
 | Log in | Create a new user | The Monastery Gates | Super Search | 
 | Seekers of Perl Wisdom | Meditations | PerlMonks Discussion | 
 | Obfuscation | Reviews | Cool Uses For Perl | Perl News | Q&A | Tutorials | 
 | Poetry | Recent Threads | Newest Nodes | Donate | What's New | 

on Apr 04, 2004 at 06:18 UTC ( #342422=sourcecode: print w/ replies, xml ) Need Help??

Category: Win32 Stuff
Author/Contact Info http://bbfu.perlmonk.org
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();
}

Comment on traymon.pl - Easily add a System Tray icon to an exisiting program
Download Code
Re: traymon.pl - Easily add a System Tray icon to an exisiting program
by Zero_Flop (Pilgrim) on Apr 04, 2004 at 07:36 UTC
    Where do you get use Win32::GUI::Carp. I am running Activestate, ppm'd win32::Gui but got no Carp. I then did a search on the web for Win32::GUI::Carp and ended up with nothing, which I think is strange, becouse if it was common it should popup in published scripts.

    Thanks would love to try this out

      It's something I wrote myself. I thought I had posted it, along with my Tk::Carp, but apparently I'd forgotten to. I went ahead and posted it now. You can find it at Win32::GUI::Carp. I need to get both of these on CPAN, but I never got around to it.

      bbfu
      Black flowers blossom
      Fearless on my breath

        I can't get it to work. I downloaded Win32::GUI from PPM, downloaded and installed carp. I created an icon for my program, and created an INI with but two entries, one for the program, and one for the Icon. When I run Traymon, it starts the target program and then dies with the "Please tell Microsoft about this problem" dialog. I thought perhaps my Icon was causing the problem, so I commented out the Set_Icon call as a test. Same result. Any clues for the clueless? Thanks

Back to Code Catacombs

Login:
Password
remember me
What's my password?
Create A New User

Node Status?
node history
Node Type: sourcecode [id://342422]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (19)
ikegami
jdporter
FunkyMonk
holli
Gavin
atcroft
salva
jwkrahn
jethro
kennethk
thezip
Eyck
LanX
trwww
gmargo
ssandv
je44ery
MikeDexter
im2
As of 2010-02-09 22:35 GMT
Sections?
The Monastery Gates
Seekers of Perl Wisdom
Meditations
PerlMonks Discussion
Categorized Q&A
Tutorials
Obfuscated Code
Perl Poetry
Cool Uses for Perl
Perl News
Information?
PerlMonks FAQ
Guide to the Monastery
What's New at PerlMonks
Voting/Experience System
Tutorials
Reviews
Library
Perl FAQs
Other Info Sources
Find Nodes?
Nodes You Wrote
Super Search
List Nodes By Users
Newest Nodes
Recently Active Threads
Selected Best Nodes
Best Nodes
Worst Nodes
Saints in our Book
Leftovers?
The St. Larry Wall Shrine
Offering Plate
Awards
Craft
Snippets Section
Code Catacombs
Quests
Editor Requests
Buy PerlMonks Gear
PerlMonks Merchandise
Planet Perl
Perlsphere
Use Perl
Perl.com
Perl 5 Wiki
Perl Jobs
Perl Mongers
Perl Directory
Perl documentation
CPAN
Random Node
Voting Booth?

What level of existential comfort do you require?

Palace
Executive suite at the best hotel
Regular hotel in a decent part of town
Motel
Boarding house
Sleeping Bag on Couch in Basement
Any port in a storm
Camping under the freeway overpass
Jail
Other

Results (283 votes), past polls