Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Gtk2 TrayIcon Demo

by zentara (Archbishop)
on Oct 28, 2008 at 18:17 UTC ( #720058=sourcecode: print w/replies, xml ) Need Help??
Category: Gui Programming
Author/Contact Info zentara of perlmonks
Description: UPDATE Oct 29, 2008 I just found out that the separate module TrayIcon is not crossplatform ( not work on win32), and I should have used StatusIcon( a part of Gtk2 ) instead. I will try to update this as soon as I can to using StatusIcon. Sorry for any inconvenience.

This is a little demo to show some tricks to make a nice Gtk2::TrayIcon based app. It does nothing but tail itself, but includes a generic outline for including multi-colored icons, flashing the icon, and a configuration setup which you can easily modify.

A left click on the icon will popup a window tailling $0. This can be useful if you want to tail /var/log/messages( with correct permissions), etc, or if you want to setup an IO::Notify type watch on a directory. A directory and file selector option are in the configuration window. I may put the output of Sys::Info into a table as a real use.

A middle click will start the icon blinking, a second middle-click stops it. This just demos how to make your trayicon flash efficiently.

A right-click brings up the menu. It is full of easily modifiable code, to allow you to do what you want. All the checkbuttons do is change icon color, but you can make them do whatever you want.

I originally started to write a TrayIcon app, similar to Gufw a PyGtk2 TrayIcon app to monitor a firewall, but decided to write a generic one first.

After configuration, if you hit the "accept" button, it will save the options to __DATA__ for the next startup.

#!/usr/bin/perl
use warnings;
use strict;
use Gtk2 '-init';
use Gtk2::TrayIcon;
use MIME::Base64;

my %icon;
build_icons(); # r,g,b,y
my $icon_cur = $icon{ 'g' };
my $flash = 0; #flash control
my %val;

while(<DATA>){
  chomp; 
  my($key,$value) = split(/=/,$_);
  if( defined $key){
    $val{$key} = $value;
  }
}

# check data reloaded properly
#foreach my $key(sort keys %val){print $key.'='.$val{$key}."\n"}

# default global variables if not saved to __DATA__ 
$val{'dir'}  ||= './';
$val{'file'} ||= $0;
$val{'check1'} ||= 1;
$val{'check2'} ||= 0;
$val{'check3'} ||= 0;
$val{'var1'} ||= 'VAR1';
$val{'var2'} ||= 'VAR2';
$val{'var3'} ||= 'VAR3';
$val{'var4'} ||= 'VAR4';
$val{'var5'} ||= 'VAR5';
$val{'var6'} ||= 'VAR6';
$val{'var7'} ||= 'VAR7';
$val{'var8'} ||= 'VAR8';
$val{'var9'} ||= 'VAR9';
$val{'var10'} ||= 'VAR10';

#icon goes in box, box goes in tray
my $icon     = Gtk2::Image->new_from_pixbuf( $icon_cur );
my $eventbox = Gtk2::EventBox->new;
$eventbox->add( $icon );
my $trayicon = Gtk2::TrayIcon->new( 'Info' );
$trayicon->add( $eventbox );

#tooltip
my $tooltip = Gtk2::Tooltips->new;
$tooltip->set_tip( $trayicon, "Info v1.0" );

$eventbox->signal_connect( 'button_press_event', \&click );

#show tray
$trayicon->show_all;

#end event loop
Gtk2->main;

#handles tray clicks
sub click {
  #left mouse button

   if ( $_[ 1 ]->button == 1 ) {
           &pop_it;
   }

   elsif ( $_[ 1 ]->button == 2 ) {
           if($flash == 1){
               $flash = 0;
             }else{
              $flash = 1;
              &start_flash;
           }
   }

 #right mouse button
   elsif ( $_[ 1 ]->button == 3 ) {
      &config_it;
   }
   return 1;
}


sub start_flash{
 
  $tooltip->set_tip( $trayicon, "Danger Will Robinson!!" );
   my $toggle = -1;
   Glib::Timeout->add(
      500,
      sub {
          $toggle *= -1;
          if($toggle > 0){
             $icon->set_from_pixbuf ($icon{'y'})
               }else{           
            $icon->set_from_pixbuf (undef);
              }
       if($flash > 0){return 1}
        else{ 
          $icon->set_from_pixbuf ($icon_cur);
          $tooltip->set_tip( $trayicon, "Info v1.0" );
          return 0
         };
      }
   );
}


sub pop_it{

   my $popup = Gtk2::Window->new( 'toplevel' );
   $popup->set_position( 'center' );

  my $vbox = Gtk2::VBox->new( 0, 6 );
  $popup->add($vbox);
  $vbox->set_border_width(2);

  my $hbox= Gtk2::HBox->new( 0, 6 );
  $vbox->pack_end($hbox,0,0,2);

  my $ebutton = Gtk2::Button->new_from_stock('gtk-close');
  $hbox->pack_end( $ebutton, 0, 0, 0 );
  $ebutton->signal_connect( clicked => sub{ $popup->hide_all  } );

  # Create a textbuffer to contain that string 
  my $textbuffer = Gtk2::TextBuffer->new();
  my $tail = `tail -n 20 $val{'file'}`;
  $textbuffer->set_text($tail);

# Create a textview using that textbuffer 
my $textview = Gtk2::TextView->new_with_buffer($textbuffer);

# Add the textview to a scrolledwindow 
my $scrolledwindow = Gtk2::ScrolledWindow->new( undef, undef );
$scrolledwindow->add($textview);
$scrolledwindow->set_size_request (300, 300);
$vbox->pack_start($scrolledwindow, 1, 1, 0 );
$popup->show_all;

}


#right click menu
sub config_it {
   
# change Mode or whatever.... a menu entry   
   my $menu = Gtk2::Menu->new();

  my $menu_Z1 = Gtk2::ImageMenuItem->new_with_label( "Mode Z1" );
   $menu_Z1->signal_connect(
      activate => sub {
        #change the icon when Z1 is clicked in the menu
         $icon->set_from_pixbuf ($icon{'b'});
         $tooltip->set_tip( $trayicon, "Mode Z1" );
      }
   );
   $menu_Z1->set_image( Gtk2::Image->new_from_stock( 'gtk-refresh', 'm
+enu' ) );
   $menu->add( $menu_Z1);

# launch configure window 
   my $menu_pref = Gtk2::ImageMenuItem->new_with_label( "Configure" );
   $menu_pref->signal_connect( activate => \&configure );
   $menu_pref->set_image(
      Gtk2::Image->new_from_stock( 'gtk-preferences', 'menu' ) );
   $menu->add( $menu_pref );

#separator
   my $menu_sep = Gtk2::SeparatorMenuItem->new();
   $menu->add( $menu_sep );

#Quit
   my $menu_quit = Gtk2::ImageMenuItem->new_with_label( "Quit" );
   $menu_quit->signal_connect( activate => \&exit_it );
   $menu_quit->set_image( Gtk2::Image->new_from_stock( 'gtk-quit', 'me
+nu' ) );
   $menu->add( $menu_quit );
   $menu->show_all;

  $menu->popup( undef, undef, undef, undef, 0, 0 );
   return 1;
}


#configuration dialog window
sub configure {

#Create the new window
   my $config_window = Gtk2::Window->new( 'toplevel' );
   $config_window->set_title( "Configuration" );
   $config_window->set_position( 'center' );

#VBox container
   my $table_config = Gtk2::Table->new( 3, 5, 0 );

#Create Notebook
   my $config_notebook = Gtk2::Notebook->new;
   $config_notebook->set_tab_pos( 'top' );

#the First Page; Config file select
   my $vbox_p1 = Gtk2::VBox->new( 0, 1 );
   my $hbox_1_p1   = Gtk2::HBox->new( 0, 1 );
   my $label_1_p1 = Gtk2::Label->new( 'Directory' );
   my $entry_1_p1  = Gtk2::Entry->new;
   $entry_1_p1->set_width_chars (60);
   $entry_1_p1->set_position(60);
   my $button_1_p1 = Gtk2::Button->new_with_mnemonic( "_Browse" );
   $button_1_p1->set_size_request( 80, 32 );
   my $align_button_1_p1 = Gtk2::Alignment->new( 0.5, 0.5, 0, 0 );
   $align_button_1_p1->add( $button_1_p1 );
   $entry_1_p1->set_text( $val{'dir'} );
   $hbox_1_p1->pack_start( $label_1_p1, 0, 0, 1 );
   $hbox_1_p1->pack_start( $entry_1_p1, 1, 1, 1 );
   $hbox_1_p1->pack_start( $align_button_1_p1, 0, 0, 1 );

   my $hbox_2_p1   = Gtk2::HBox->new( 0, 1 );
   my $label_2_p1 = Gtk2::Label->new( 'Tail File' );
   my $entry_2_p1  = Gtk2::Entry->new;
   $entry_2_p1->set_width_chars (60);
   $entry_2_p1->set_position(60);
   my $button_2_p1 = Gtk2::Button->new_with_mnemonic( "_Browse" );
   $button_2_p1->set_size_request( 80, 32 );
   my $align_button_2_p1 = Gtk2::Alignment->new( 0.5, 0.5, 0, 0 );
   $align_button_2_p1->add( $button_2_p1 );
   $entry_2_p1->set_text( $val{'file'} );
   $hbox_2_p1->pack_start( $label_2_p1, 0, 0, 1 );
   $hbox_2_p1->pack_start( $entry_2_p1, 1, 1, 1 );
   $hbox_2_p1->pack_start( $align_button_2_p1, 0, 0, 1 );

   $vbox_p1->pack_start( $hbox_1_p1, 0, 0, 1 );
   $vbox_p1->pack_start( $hbox_2_p1, 0, 0, 1 );

   $button_1_p1->signal_connect(
      'clicked' => sub {
        
    my $fs = Gtk2::FileChooserDialog->new(
        'Choose a Directory',
     $config_window, 'select-folder',
        'gtk-cancel' => 'cancel',
        'gtk-ok' => 'accept'
    );

    my $response = $fs->run();
    if ( "accept" eq $response ) {
        my $dir =  $fs->get_filename();
        $entry_1_p1->set_text( $dir );
    }
    $fs->destroy;
}
 );

   $button_2_p1->signal_connect(
      'clicked' => sub {
        
    my $fs = Gtk2::FileChooserDialog->new(
        "FS", $config_window, 'open',
        "Cancel" => "cancel",
        "OK"     => "accept",
    );
    my $response = $fs->run();
    if ( "accept" eq $response ) {
        my $file =  $fs->get_filename();
        $entry_2_p1->set_text( $file );
    }
    $fs->destroy;
}
 );


my $checkbutton1 = Gtk2::CheckButton->new('Some Feature1');
$checkbutton1->set_active( $val{'check1'} );
$vbox_p1->pack_start( $checkbutton1, 0, 0, 1 );
$checkbutton1->signal_connect( clicked => \&check_button_callback1 );

my $checkbutton2 = Gtk2::CheckButton->new_with_label('Some Feature2');
$checkbutton2->set_active( $val{'check2'} );
$vbox_p1->pack_start( $checkbutton2, 0, 0, 1 );
$checkbutton2->signal_connect( clicked => \&check_button_callback2 );

my $checkbutton3 = Gtk2::CheckButton->new_with_mnemonic('Some Feature3
+');
$checkbutton3->set_active( $val{'check3'} );
$vbox_p1->pack_start( $checkbutton3, 0, 0, 1 );
$checkbutton3->signal_connect( clicked => \&check_button_callback3 );



#the Second Page; 
   my $vbox_p2 = Gtk2::VBox->new( 0, 1 );
   my $label_1_p2   = Gtk2::Label->new( 'Label 1' );
   my $entry_1_p2   = Gtk2::Entry->new;
   my $label_2_p2 = Gtk2::Label->new( 'Label2' );
   my $entry_2_p2 = Gtk2::Entry->new;
   $entry_1_p2->set_text( $val{'var1'} );
   $entry_2_p2->set_text( $val{'var2'} );
   $vbox_p2->pack_start( $label_1_p2,   0, 0, 1 );
   $vbox_p2->pack_start( $entry_1_p2,   0, 0, 1 );
   $vbox_p2->pack_start( $label_2_p2, 0, 0, 1 );
   $vbox_p2->pack_start( $entry_2_p2, 0, 0, 1 );


#the Third Page; 
   my $table_p3        = Gtk2::Table->new( 4, 2, 0 );
   my $label_1_p3      = Gtk2::Label->new( 'First Label' );
   my $entry_1_p3      = Gtk2::Entry->new;
   my $label_2_p3      = Gtk2::Label->new( 'Second label' );
   my $entry_2_p3      = Gtk2::Entry->new;
   my $label_3_p3      = Gtk2::Label->new( 'Third Label' );
   my $entry_3_p3      = Gtk2::Entry->new;
   my $label_4_p3      = Gtk2::Label->new( 'Fourth Label' );
   my $entry_4_p3      = Gtk2::Entry->new;
   $entry_1_p3->set_text( $val{'var3'} );
   $entry_2_p3->set_text( $val{'var4'} );
   $entry_3_p3->set_text( $val{'var5'} );
   $entry_4_p3->set_text( $val{'var6'} );
#   $entry_3_p3->set_editable( 0 );  # if no edit desired
   $table_p3->attach_defaults( $label_1_p3, 0, 1, 0, 1 );
   $table_p3->attach_defaults( $entry_1_p3, 1, 2, 0, 1 );
   $table_p3->attach_defaults( $label_2_p3, 0, 1, 1, 2 );
   $table_p3->attach_defaults( $entry_2_p3, 1, 2, 1, 2 );
   $table_p3->attach_defaults( $label_3_p3, 0, 1, 2, 3 );
   $table_p3->attach_defaults( $entry_3_p3, 1, 2, 2, 3 );
   $table_p3->attach_defaults( $label_4_p3, 0, 1, 3, 4 );
   $table_p3->attach_defaults( $entry_4_p3, 1, 2, 3, 4 );

#the Fourth Page; 
   my $vbox_p4 = Gtk2::VBox->new( 0, 1 );
   my $table_1_p4 = Gtk2::Table->new( 4, 2, 0 );
   my $frame_1_p4  = Gtk2::Frame->new( 'Some Cool Title' );
   my $label_1_p4  = Gtk2::Label->new( 'First Label' );
   my $entry_1_p4  = Gtk2::Entry->new;
   my $label_2_p4 = Gtk2::Label->new( 'Second Label' );
   my $entry_2_p4 = Gtk2::Entry->new;
   $entry_1_p4->set_text( $val{'var7'} );
   $entry_2_p4->set_text( $val{'var8'} );
   
   $table_1_p4->attach_defaults( $label_1_p4, 0, 1, 0, 1 );
   $table_1_p4->attach_defaults( $entry_1_p4, 1, 2, 0, 1 );
   $table_1_p4->attach_defaults( $label_2_p4, 0, 1, 1, 2 );
   $table_1_p4->attach_defaults( $entry_2_p4, 1, 2, 1, 2 );
   $frame_1_p4->add( $table_1_p4 );
   
   my $table_2_p4       = Gtk2::Table->new( 4, 2, 0 );
   my $frame_2_p4       = Gtk2::Frame->new( 'Another Cool Title' );
   my $label_3_p4       = Gtk2::Label->new( 'Third Label' );
   my $entry_3_p4       = Gtk2::Entry->new;
   my $label_4_p4       = Gtk2::Label->new( 'Fourth Label' );
   my $entry_4_p4       = Gtk2::Entry->new;
   $entry_3_p4->set_text( $val{'var9'} );
   $entry_4_p4->set_text( $val{'var10'});
   $table_2_p4->attach_defaults( $label_3_p4, 0, 1, 0, 1 );
   $table_2_p4->attach_defaults( $entry_3_p4, 1, 2, 0, 1 );
   $table_2_p4->attach_defaults( $label_4_p4, 0, 1, 1, 2 );
   $table_2_p4->attach_defaults( $entry_4_p4, 1, 2, 1, 2 );
   $frame_2_p4->add( $table_2_p4 );
  
   $vbox_p4->pack_start( $frame_1_p4, 0, 0, 1 );
   $vbox_p4->pack_start( $frame_2_p4, 0, 0, 1 );


#append pages
   $config_notebook->append_page( $vbox_p1,   "File Stuff" );
   $config_notebook->append_page( $vbox_p2,   "More Items" );
   $config_notebook->append_page( $table_p3, "More in a Table" );
   $config_notebook->append_page( $vbox_p4,  "Another Table Set" );

#add button to the main window
   my $button_accept = Gtk2::Button->new_with_mnemonic( "_Accept" );
   my $button_cancel = Gtk2::Button->new_with_mnemonic( "_Cancel" );

#pack them into the dialog window
   $table_config->attach_defaults( $config_notebook, 0, 5, 0, 1 );
   $table_config->attach_defaults( $button_accept,   1, 2, 2, 3 );
   $table_config->attach_defaults( $button_cancel,   3, 4, 2, 3 );
   $config_window->add( $table_config );

   $config_window->show_all;

#Button Functions
   $button_cancel->signal_connect( 'clicked' => sub { $config_window->
+destroy } );
   
   $button_accept->signal_connect(
      'clicked' => sub {
         my @settings = (
            ( $val{'dir'}      = $entry_1_p1->get_text ),
            ( $val{'file'}     = $entry_2_p1->get_text ),
            ( $val{'check1'}   = $checkbutton1->get_active() || 0 ),
            ( $val{'check2'}   = $checkbutton2->get_active() || 0 ),
            ( $val{'check3'}   = $checkbutton3->get_active() || 0 ),
            ( $val{'var1'}     = $entry_1_p2->get_text ),
            ( $val{'var2'}     = $entry_2_p2->get_text ),
            ( $val{'var3'}     = $entry_1_p3->get_text ),
            ( $val{'var4'}     = $entry_2_p3->get_text ),
            ( $val{'var5'}     = $entry_3_p3->get_text ),
            ( $val{'var6'}     = $entry_4_p3->get_text ),
            ( $val{'var7'}     = $entry_1_p4->get_text ),
            ( $val{'var8'}     = $entry_2_p4->get_text ),
            ( $val{'var9'}     = $entry_3_p4->get_text ),
            ( $val{'var10'}    = $entry_4_p4->get_text ),
         );
         save_default();
         $config_window->destroy;
      }
   );
   

   return 1;
}

#Exit 
sub exit_it {
   Gtk2->main_quit;
   return 0;
}

#save to DATA when apply is clicked
sub save_default {

open(SELF,"+<$0")||die $!;
while(<SELF>){last if /^__DATA__/}
  truncate(SELF,tell SELF);

 foreach my $key(sort keys %val ) {
      print SELF $key.'='.$val{$key}."\n"; #default line ending
   }
   
 truncate(SELF,tell SELF);
 close SELF;

}

sub check_button_callback1 {
  my ($button) = @_;
     if ($button->get_active) {
       # if control reaches here, the check button is on
       $icon->set_from_pixbuf ($icon{'r'});
       $tooltip->set_tip( $trayicon, "Mode RED" );
     } else {
    # if control reaches here, the check button is off
          $icon->set_from_pixbuf ($icon_cur);
         $tooltip->set_tip( $trayicon, "Mode NORMAL" );
      }
}  

sub check_button_callback2 {
  my ($button) = @_;
     if ($button->get_active) {
       # if control reaches here, the check button is on
       $icon->set_from_pixbuf ($icon{'y'});
       $tooltip->set_tip( $trayicon, "Mode YELLOW" );
     } else {
    # if control reaches here, the check button is off
         $icon->set_from_pixbuf ($icon_cur);
         $tooltip->set_tip( $trayicon, "Mode NORMAL" );
      }
}  

sub check_button_callback3 {
  my ($button) = @_;
     if ($button->get_active) {
       # if control reaches here, the check button is on
       $icon->set_from_pixbuf ($icon{'b'});
       $tooltip->set_tip( $trayicon, "Mode BLUE" );
     } else {
    # if control reaches here, the check button is off
         $icon->set_from_pixbuf ($icon_cur);
         $tooltip->set_tip( $trayicon, "Mode NORMAL" );
      }
}  



sub build_icons {
   my %data;

   $data{ 'r' } = decode_base64(
      'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+
+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAB00lEQVRYw+3WP2gUURAG8N++iwoRxRSilSiiHN4iWJhe7M
+TCQiJW
IqiFtUIQQWyENNpZxkYxjVgKNnb24hUGQe0s0gj+PcztWtw7WC63d2TJbQrvwbK8x2Pmm2
+++mXlJ
nue2cwXbvKYApgBmqsFON55l7f8rBQv4gm/xv1AVQLLpRtSjfx2NwmkXM1XSUJWBxpj9tA
+wnDuDj
mP2E+wDH+yKun4Ge0pPCB3ndjegXslh+GfK6NfBjYF8jgJDux+7trIIz2DXYUesB0It+ea
+jDkC7X
wcBpzJbk/KKQvhbSxiQBPB8hug5O4s5kAIT0K/ZE54NRZtiLOdwU0qtbByCkTSH9hANDor
++BD9FO
wM4IYinqZUsYeIHDBbX3o7+NFVwvtPXv8c4+fBbSZrUHSQ/9I5yLxgZVfx6vYjkmuILHQ5
+pTNw6q
a7L223IAPYfHcBZHo+MyCluR9h2R8k68O4unmB82QbCKl3iHN3GurCV50rqEyziCZjQ6bD
+2JrKwi
kbU7Bcbm8BMHcRcXSgLo072O93iQ5ElrXB/v4iEWo+PuiLdiP1UnYsOaH2P7zygAv7Eka9
++vMC8S
HMJf3IvVUiLCjQDWcAvPSqPdPKAGTsXULBYqKf8H7mhziLP34moAAAAASUVORK5CYII='
   );


   $data{ 'g' } = decode_base64(
      'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+
+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAABwUlEQVRYw+2VvS8EURTFf8/4CKJQTKOgEIlEqFCIRnSUhE
+6iUOhV
Go3Kf0ChkiipNWqFQuErEtFJSEREfO7O0dyNid2Z3X3sbmHfy82bdzPv3TPnnnvHSaKWo4
+EajzqA
OoBGn0MOl+cT+l8pmANugEdb53wBuHIbkdGfAYKYOws0+qTBl4GgyL5ehhUHcFVkX3ERfm
+9jraAq
IrQgLmbewX+TghcgsvKLAFVbA895xFQLgMOFQHstq2ACaMnD5TsklWyIEPGMiFDe3C7nrp
+yVy8AI
0JaQ81mHO3C4oJIp2E0R3TswBKxWJAWIW6M+W4D+LOLd7BaxWPq9xQP3I64teITIxAIvIc
+7t+dPW
N8QdIvwrAKex4PG5guhAjMd8T8bCh4m13wuAqX0H8ZCg+GlEA6IV0YZYLvBOZKycIcaSAD
+hJuebS
B0wCvcAUECbIZgC4AJqAZhNfaNWxA4wWOBMBl8AecAIc2n/lHsQ8Yh9xYvQlzW3EIKIZ0f
+KDsU7z
dyO2TANKYCWyFB0jZkgJmJsZxIZRHqRoBYQzG0AclXD3axqAF8SaT3czED2ILsRmKoQCrj
+vEQtrX
egAKEMOI9R9lHH0BkYPO/OE8jeIAAAAASUVORK5CYII='
   );

   $data{ 'b' } = decode_base64(
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTA
+AAACXBI
WXMAAAsTAAALEwEAmpwYAAAB3UlEQVRYw+3WPYhTQRQF4G+SuIKiuEXAShRRFhcEC7cXO7
+GwkBUr
EdTCUhQWyUMkINhoZ7k2ijZip2BjZy82LhbaWaQR/xc3MzYTeMT8sGGTFGbg8ZjhzZwz59
+577gsp
JdMcFVMeMwIzArVRNhUp/rPWDJX/KwTL+IQv+b08KoGwWSPK8m+gWlpuozZKGEZVoDpkPi
+vDsRP4
MGQ+Xh/A4U4ST1yBnOmh9ECatBH9RMzlF5EmnQPfu+aTI1CkWMfOaVbBCWzvdtSJEMi3X+
+0FWKS4
OgkFjmNHn5ifLVJ8VaRYHSeBJwOSbh1HcXMsBIoUP2NXBu++ZcRuzONqkeLFLWvHRYoLeI
+H9JbAO
gSu4hoXcomtZia9YbIZKaysUeFYCDyXwG3iKyyVb/5a/2YOPmfzmFcjZfh+n8mHdWX8aL3
+M5BlzA
gx7m1M6N6lIzVN70JZABD+EkDmbgeh/Si3iPbZjLktdzdTzCUo89EWt4jrd4nftKKzRi+x
+zO40CO
5Vwf4IdZlTWEZqislxSbxw/sRQNn+lygI/cG3uFOaMT2MB9v4x5WMnB7wL9iJ1RHsmEtDT
+n79yAC
v3C3GSq3R+gXAfvwB7dytfROwh4EWriOx/1uOwKhKo7l0KyUKin9BYeXmN9loYmUAAAAAE
+lFTkSu
QmCC');


   $data{ 'y' } = decode_base64(
      'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+
+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAB2UlEQVRYw+3WP2gUQRQG8N/mokJEMRyi2UIUUQ4FwcL0Yi
+cWFhKx
EkEtrBWCCGIjpNHOMjaKNmIp2NjZi41BUKuzSBYE/x7mbi12DpbL7R1ZcnuFN7AsM8y897
+3vvffN
RGmaGueYMuYxATABMF3qVBJvXKs3/68ULOALvoX/QlkA0aaFKKN/HbXcahvTZdJQloHakP
+mkDUcO
4OOQ+Yh1gKPdIq6egazSo9wHadVC9Aud0H4dpFXXwI+eeYUAkngvdo6zC05jR6+iVgMgi3
+65r8Mk
Xq6CgVOYKcj5BUn8WhLXRgng2YCia+EEbo8GQBJ/xa7gvDfKDnZjFjck8ZWtA5DEDUn8Cf
+v6RH8d
H4KdKWwPIJZCvWwJAy9wMFft3ehv4Tmu5WT9e9izB58lcaPcgyRD/xBng7Heqj+HV6EdI1
+zGoz7i
1A4X1VX15ttiAJnDIziDw8FxEYXHA+3bAuWtsHcGTzDf50wHK3iJd3gT7pXVKF2bu4hLOI
+RGMNpv
PA6srCBSb7ZyjM3iJ/bjDs4XBNClex3vcT9K1+aG6XgbD7AYHLcHvBW7qToWBGt+iO0/gw
+D8xpJ6
816J+yLCAfzF3dAtBUW4EcAqbuJpYbSbB1TDyZCaxVwnpf8AkwJ+jVuvoFMAAAAASUVORK
+5CYII='
   );

# this properly renders them to pixbuf
   foreach my $key ( keys %data ) {
      $icon{ $key } = do {
         my $loader = Gtk2::Gdk::PixbufLoader->new();
         $loader->write( $data{ $key } );
         $loader->close();
         $loader->get_pixbuf();
      };
   }
}
__DATA__

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: sourcecode [id://720058]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (7)
As of 2020-09-24 00:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    If at first I donít succeed, I Ö










    Results (132 votes). Check out past polls.

    Notices?