Zach wrote:
I've written a small Win32::GUI app that shows the
number of users logged in to our system. The number is
shown in a notification icon on the system tray. I
would like to add a popup feature where the user can
be notified when a certain event happens, say when the
count of logged in users researches 100. The popups I
am trying to implement are the kind that instant
messaging clients use to notify the user when someone
has signed in/out. Yahoo IM, AIM and MSN all use these
popups that literally popUP from the system tray area
and notify the user of something without taking focus.
I hear this is an extention of the shell notification
API. Any leads on how to do this with perl's
Win32:GUI?

I am not aware of a shell extension to do this - I believe that they are just windows under you're app's control. The only tricks are determining the 'working' area of the window, and which corner of the working area to use.

The attached code is something I did some time ago (I've sanitised it for posting here). It doesn't copy well with multiple monitors, or if the task bar is set to 'auto-hide'; otherwise I think it should give you everything that you need. You'll need Win32::API (for accessing the SystemParametersInfo() API call - this is now under development again and V0.46 (IIRC) is available - although older versions will probably do.

Regards,
Rob.


#!perl -w
use strict;
use warnings;

## Some params to allow quick playing with the
#  most likely options.  It would be nice to expose
#  these on the main window to allow for fiddling
#  without re-starting
my $animate_time    = 1000;  # ms
my $display_time    = 5000; # ms
my $notify_x        = 200;   # pixels
my $notify_y        = 130;   # pixels
my $notify_text_clr = 0x00FFFF;
my $notify_bkgd_clr = 0xD03030;

use Win32::GUI 1.05 qw(HWND_TOPMOST SWP_NOSIZE SWP_NOACTIVATE WS_POPUP);

# Need Win32::API to get use of the SystemParamtersInfo API call.  This
# call with the SPI_GETWORKAREA option gets us the desktop rectangle
# (of the primary monitor), excluding the areas taken up by the task bar
# and any other shell application toolbars - see calc_position()
use Win32::API();
Win32::API->Import('user32', 'SystemParametersInfo', 'NNPN', 'N')
        or die 'Failed to import SystemParametersInfo call';
sub SPI_GETWORKAREA() {0x0030}

# Main window for controlling the demo
my $mw = Win32::GUI::Window->new(
        -title => "User Notification",
        -size  => [300,100],
);

$mw->AddButton(
        -name    => 'CB',
        -text    => "Toggle notification",
        -pos     => [10,10],
        -onClick => \&toggle_notification,
);

# The notification window that we're really
# interested in
my $notify = Win32::GUI::Window->new(
        -size        => [$notify_x,$notify_y],
-addstyle => WS_POPUP, # Need WS_POPUP, as you can't remove the titlebar from
                                   # a window with WS_OVERLAPPED
        -titlebar    => 0,         # remove the title bar
        -toolwindow  => 1,         # prevent a button appearing in the task bar
        -topmost     => 1,         # make sure we're on the top
        -background  => $notify_bkgd_clr,
        -onMouseDown => \&toggle_notification,
        -onTimer     => \&toggle_notification,
);

$notify->AddTimer("HideTimer", 0);

$notify->AddLabel(
        -text       => "This notification message will disappear:\r\n" .
"(1) After a pre-set time ($display_time milliseconds).\r\n" .
                               "(2) By clicking this window.\r\n".
                               "(3) By clicking the 'toggle' button in the main 
window.",
    -pos        => [5,5],
        -width      => $notify->ScaleWidth() - 10,
        -height     => $notify->ScaleHeight() - 10,
        -background => $notify_bkgd_clr,
        -foreground => $notify_text_clr,
);

$mw->Show();
Win32::GUI::Dialog();
undef $notify;
exit(0);

sub toggle_notification {

        my $show = !$notify->IsVisible();

        my ($x, $y, $dir) = calc_position($show);

        if($show) {
                # Showing - set the posistion every time to
                # (1) ensure we are on top - other apps my well display
                # widows in the same location
                # (2) ensure we are in the correct location - the user may
                # have resized their desktop, or re-docked the task-bar
                # since we last displayed the window
                $notify->SetWindowPos(
                        HWND_TOPMOST,
                        $x, $y,
                        0,0,
                        SWP_NOSIZE|SWP_NOACTIVATE,
                );

                # Set a timer that will go off when it is time
                # to hide the window
                $notify->HideTimer->Interval($display_time);
        }
        else {
                # Hiding - turn off the timer - we don't
                # need it any more
                $notify->HideTimer->Interval(0);
        }

        # Show or hide the window, using animation
        $notify->Animate(
                -show      => $show,
                -activate  => 0,
                -animation => 'slide',
                -time      => $animate_time,
                -direction => $dir,
        );

        return 1;
}

# Calculate the position that the notification window will be displayed
# at, and which direction to perform the animation.
sub calc_position() {
        my ($show) = @_;

        my ($wa_l, $wa_t, $wa_r, $wa_b) = getWorkingArea();

        # Now determine the side of the screen the taskbar is on
        # (if we can) and set the parameters we want to use.
        # If we fail to determine the side we want (perhaps a non
        # standard shell is in use), the use bottom right of the
        # working area.
        # TODO: this only uses the primary monitor's working area;
        # we should really determine which screen the taskbar is
        # on, and use the working area of that screen.
        # TODO: this doesn't work correctly with autohide
        # taskbars either, as they overlap the working area
        my $class_name = "Shell_TrayWnd";
        my $hwnd_taskbar = Win32::GUI::FindWindow($class_name, '');
        
        my ($x, $y, $dir);

        if($hwnd_taskbar != 0) {
my ($tb_l, $tb_t, $tb_r, $tb_b) = Win32::GUI::GetWindowRect($hwnd_taskbar);
                #print "work Area: $wa_l\t$wa_t\t$wa_r\t$wa_b\n";
                #print "task Area: $tb_l\t$tb_t\t$tb_r\t$tb_b\n";

                if ($tb_r <= $wa_l) { # Taskbar on Left
                        $x = $wa_l;
                        $y = $wa_b - $notify_y;
                        $dir = $show ? "lr" : "rl";
                }
                elsif ($tb_b <= $wa_t) { # Taskbar on Top
                        $x = $wa_r - $notify_x;
                        $y = $wa_t;
                        $dir = $show ? "tb" : "bt";
                }
                elsif ($tb_l >= $wa_r) { # Taskbar on Right
                        $x = $wa_r - $notify_x;
                        $y = $wa_b - $notify_y;
                        $dir = $show ? "rl" : "lr";
                }
                else { # Taskbar is on Bottom, or not determined
                        $x = $wa_r - $notify_x;
                        $y = $wa_b - $notify_y;
                        $dir = $show ? "bt" : "tb";
                }
        }
        else { # We didn't find a taskbar, use bottom right
                $x = $wa_r - $notify_x;
                $y = $wa_b - $notify_y;
                $dir = $show ? "bt" : "tb";
        }


        return $x, $y, $dir;
}


sub getWorkingArea() {

        my $rect_buffer = pack('llll', 0, 0, 0, 0);
        my $result = SystemParametersInfo(SPI_GETWORKAREA, 0, $rect_buffer, 0);
        if ($result == 0) {
                die "SystemParameterInfo call failed";
        }
        my ($work_l, $work_t, $work_r, $work_b) = unpack('llll', $rect_buffer);

        return $work_l, $work_t, $work_r, $work_b;
}
__END__


Reply via email to