13.3. Gtk2::Helper - Simple Chat

13.3.1. Objective

This lesson will build on our newly acquired knowledge of Gtk2::Helper. We will create a simple chat program. We will introduce a few nice ticks to give the user a more pleasant time communicating.

Table 13-4. Gtk2 object classes used

Gtk2 Class Name

13.3.2. Requirements

The chat program needs the following requirements:

13.3.3. The Screenshot

Figure 13-2. Simple Chat

13.3.4. The Code

The program can be found here: 'Chat program'.


How to chat

You need to run two instances of the program on the same machine. When calling the program, specify who you want to be, "shrek" or "donkey". To post a message, enter it on the Gtk2::Entry and click the "OK" button to send it.

  1 #!/usr/bin/perl -w
  3 use strict;
  4 use IO::Socket;
  6 use Gtk2 -init;
  7 use Glib qw/TRUE FALSE/;
  8 use Gtk2::Helper;
  9 use Gtk2::Pango;
 10 use Gtk2::Gdk::Keysyms;
 12 my ($who) = $ARGV[0];
 13 #check and warn if we received wrong argument
 14 unless(($who)&&($who =~ m/shrek|donkey/)){
 15 print "usage <program name> <shrek | donkey>\n";
 16 exit;
 17 }
 19 my($sock,$MAXLEN, $LISTEN_PORT, $SEND_PORT,$tag_send,$tag_receive,$img_big,$img_send,$img_rec);
 20 $MAXLEN = 1024;
 22 #set the variables up if you are shrek:
 23 if ($who =~ m/shrek/){
 25 	$LISTEN_PORT 	= 5151;
 26 	$SEND_PORT 	= 5152;
 27 	$tag_send	= 'shrek';
 28 	$tag_receive 	= 'donkey';
 29 	$img_big 	= './pix/shrek.gif';
 30 	$img_send 	= './pix/shrek_small.jpg';
 31 	$img_rec 	= './pix/donkey_small.gif';
 33 }
 35 #set the variables up if you are donkey:
 36 if ($who =~ m/donkey/){
 38 	$LISTEN_PORT 	= 5152;
 39 	$SEND_PORT 	= 5151;
 40 	$tag_send	= 'donkey';
 41 	$tag_receive 	= 'shrek';
 42 	$img_big 	= './pix/donkey.gif';
 43 	$img_send 	= './pix/donkey_small.gif';
 44 	$img_rec 	= './pix/shrek_small.jpg';
 45 }
 47 my $tview;
 49 #---------------------------------
 50 #set up a udp server waiting for incomming messages
 51 $sock = IO::Socket::INET->new(LocalPort => $LISTEN_PORT, Proto => 'udp')
 52 or die "socket: $@";
 54 #add a Gtk2::Helper watch on any incomming connections
 55 Gtk2::Helper->add_watch ( fileno $sock, 'in',sub{ 
 56 	my ($fd,$condition,$fh) = @_;
 57 	#call 'watch_callback' to handle the incomming data	
 58 	\&watch_callback($fh,$tview);
 59 	},$sock);
 61 print "Awaiting UDP messages on port $LISTEN_PORT\n";
 62 #---------------------------------
 64 #Gtk2::Rc->parse ('/usr/share/themes/Anger/gtk/gtkrc');
 65 #this is parsing our theme file, giving a personal touch
 66 Gtk2::Rc->parse ('gtkrc');
 68 #standard window creation, placement, and signal connecting
 69 my $window = Gtk2::Window->new('toplevel');
 70 $window->signal_connect('delete_event' => sub { exit;});
 71 $window->set_border_width(5);
 72 $window->set_position('center_always');
 73 #again just some fine touches
 74 $window->set_title("I am $tag_send");
 75 $window->set_icon_from_file($img_send);
 77 #this vbox will geturn the bulk of the gui
 78 my ($vbox) = &ret_vbox();
 80 #add and show the vbox
 81 $window->add($vbox);
 82 $window->show();
 84 #our main event-loop
 85 Gtk2->main();
 87 sub ret_vbox {
 89 my $vbox = Gtk2::VBox->new(FALSE,0);
 90 	#add an image to indicate who you are
 91 	my $img_who = Gtk2::Image->new_from_file($img_big);
 93 $vbox->pack_start($img_who,TRUE,TRUE,0);
 95 	my $frame = Gtk2::Frame->new("Simple Chat - I am $tag_send");
 97 	#method of Gtk2::Container
 98 	$frame->set_border_width(5);
100 		my $sw = Gtk2::ScrolledWindow->new (undef, undef);
101     		$sw->set_shadow_type ('etched-out');
102 		$sw->set_policy ('automatic', 'automatic');
103 		#This is a method of the Gtk2::Widget class,it will force a minimum 
104 		#size on the widget. Handy to give intitial size to a 
105 		#Gtk2::ScrolledWindow class object
106 		$sw->set_size_request (300, 300);
107 		#method of Gtk2::Container
108 		$sw->set_border_width(5);
110 			$tview = Gtk2::TextView->new();
111 			#we do not want to edit anything
112 			$tview->set_editable(FALSE);
113 			$tview->set_cursor_visible (FALSE);
115   			my $buffer = $tview->get_buffer();
117 			#create a mark at the end of the buffer, and on each
118 			#'insert_text' we tell the textview to scroll to that mark
119 			$buffer->create_mark ('end', $buffer->get_end_iter, FALSE);
120 			$buffer->signal_connect (insert_text => sub {
121 				$tview->scroll_to_mark ($buffer->get_mark ('end'),
122 	                        0.0, TRUE, 0, 0.5);
123 			});
125 			#create a tag for the shreck		
126 			$buffer->create_tag ("shrek",
127 					style =>'italic',
128 					weight => PANGO_WEIGHT_ULTRALIGHT,
129 					family => 'flubber',
130 					foreground => "#189f3b",
131 					size => 20000,
132 					);
134 			#create a tag for the donkey		
135 			$buffer->create_tag ("donkey",
136 					style =>'italic',
137 					weight => PANGO_WEIGHT_ULTRALIGHT,
138 					family => 'davis',
139 					foreground => "blue",
140 					size => 20000,
141 					);
143 		$sw->add($tview);
144 	$frame->add($sw);
145 $vbox->pack_start($frame,TRUE,TRUE,4);
146 	#--------------------------------------
147 	my $hbox = Gtk2::HBox->new(FALSE,5);
149 		my $ent_send = Gtk2::Entry->new;
150 	$hbox->pack_start($ent_send,TRUE,TRUE,0);
152 		my $btn_send = Gtk2::Button->new_from_stock('gtk-ok');
153 		#connect the 'key_press_signal' to a handler that will
154 		#filter for 'Return'; if TRUE, trigger a button click
155 		$ent_send->signal_connect('key_press_event'=> sub {
156 			my ($widget,$event) = @_;
157 			if($event->keyval == $Gtk2::Gdk::Keysyms{Return}) {
158                  		$btn_send->clicked;
159                  		return 1;
160 			}
162 		});
164 		$btn_send->signal_connect("clicked" =>sub {
165 			#get the contents of the entry
166 			my $msg_send = $ent_send->get_text;
167 			#clear the entry
168 			$ent_send->set_text("");
169 			#grab focus again for the next round of talks
170 			$ent_send->grab_focus;
171 			#if there was bogus input, ignore it!
172 			if ($msg_send !~ m/^\s*$/){
173 				#open up a UDP client connection
174 				my $server_host = "";
175 				my $sock = IO::Socket::INET->new(Proto => 'udp',
176                               	PeerPort  => $SEND_PORT,
177                               	PeerAddr  => $server_host)
178     				or die "Creating socket: $!\n";
179 				#send the message out on the socket
180 				$sock->send($msg_send."\n") or die "send: $!";
181 				#update the screen locally
182 				&update_buffer($buffer,$msg_send,TRUE);	
183 			}
184 		});
186 	$hbox->pack_end($btn_send,TRUE,TRUE,0);	
187 	#--------------------------------------
188 $vbox->pack_start($hbox,TRUE,TRUE,4);
189 	#set initial focus
190 	$vbox->set_focus_child($hbox);
192 $vbox->show_all();
193 return $vbox;
194 }
197 sub watch_callback {
199 	my ($fh,$tview) = @_;
200 	my $msg;
201 	$fh->recv($msg, $MAXLEN) or die "recv: $!";
202 		print $msg."\n";
203 		my $buffer = $tview->get_buffer();
204 		&update_buffer($buffer,$msg,FALSE);
206 	return 1;
207 }
209 Gtk2->main;
211 sub update_buffer {
213 	my ($buffer,$msg,$send)= @_;
215 	$msg = $msg."\n";
216 	if ($send) {
218 		my $iter = $buffer->get_end_iter;
219 		$buffer->insert_pixbuf ($iter,  Gtk2::Gdk::Pixbuf->new_from_file ($img_send));
220 		$buffer->insert_with_tags_by_name($iter, $msg,$tag_send); 
222 	}else{
224 		my $iter = $buffer->get_end_iter;
225 		$buffer->insert_pixbuf ($iter,  Gtk2::Gdk::Pixbuf->new_from_file ($img_rec));
226 		$buffer->insert_with_tags_by_name($iter, $msg,$tag_receive); 
228 	}
229 }

13.3.5. Code to fulfill the requirements

We choose two famous characters from the movie "Shrek" to represent the chatters in our program.

Two people must be able to communicate with each other over a TCP/IP network

This is the core function of the program, the rest is just nice ways of presenting it. We use the IO::Socket Perl module. We use UPD instead of TCP because it is simpler and faster. When we specify "shrek" as an argument to the program, it will set up a certain set of variables, specific to "shrek". If we specify "donkey" as an argument to the program, it will set up a certain set of variables, specific to "donkey" [1]

Depending on the chosen variable values, it will set up a UDP server on a specified port and make use of Gtk2::Helper to wait for any incoming messages. Note that our filehandle ($fh) is now in the form of a network socket ($sock).

#set up a udp server waiting for incoming messages
$sock = IO::Socket::INET->new(LocalPort => $LISTEN_PORT, Proto => 'udp')
or die "socket: $@";

#add a Gtk2::Helper watch on any incoming connections
Gtk2::Helper->add_watch ( fileno $sock, 'in',sub{ 
	my ($fd,$condition,$fh) = @_;
	#call 'watch_callback' to handle the incoming data	
print "Awaiting UDP messages on port $LISTEN_PORT\n";

We use a Gtk2::Entry widget to type our posting into. When we want to send the message, we click on the "OK" button. This will set up a UDP connection to the listening server, and send the message out on that network socket.


This chat demo is talking to IP "", which is the IP of the local machine. Should you want to talk to a different IP, just change it in code, or as an exercise, add an Gtk2::Entry widget where you can specify who to talk to.

#open up a UDP client connection
my $server_host = "";
my $sock = IO::Socket::INET->new(Proto => 'udp',
	PeerPort  => $SEND_PORT,
	PeerAddr  => $server_host)
    		or die "Creating socket: $!\n";
#send the message out on the socket
$sock->send($msg_send."\n") or die "send: $!";

To add an aesthetic touch, the GUI must be given a theme.

To do this, we make use of an "rc" file. [2] This is like a style sheet, which Gtk+ use to format the appearance of specified widgets. Every modern Linux distribution will have default "rc" files that is used by Gtk+. This will give programs an universal look and feel. In our program we specify an extra "rc" file to use:

#this is parsing our theme file, giving a personal touch
Gtk2::Rc->parse ('gtkrc');

The rc file we use was taken from the "Anger" theme and modified.


It is always helpful to look at existing "rc" files when creating your own.


Remember that rc files are not using Perl. The class names are the standard Gtk+ names. EG GtkButton VS Gtk2::Button.

We will look at a few snipets from the "rc" file and comment on them.
bg_pixmap[NORMAL] = "./pix/bg.png"
This will fill the background of the window with tiled images of the image file specified.
#create a style for the enrty where we 
#enter our text

style "ent"
	font_name = "Times Bold 15"
	base[NORMAL] = "blue"
	text[NORMAL] = "red"
#Bind this style to any GtkEnry class

class "GtkEntry" style "ent"
The above piece will change the background and the text color of all the GtkEntry widgets. It will also change the font. "font_name" uses the more friendly Pango format. This is recomended above "font" which uses the more complex XLFD font description.
#create a style for the label inside a GtkFrame

style "frame_label" = "default" {

	font_name = "Times Bold 20"
	fg[NORMAL] = "#1d6629"

#Bind this style to any GtkFrame's GtkEnry.

widget_class "*.GtkFrame.GtkLabel" style "frame_label"
When you want to change the text of a GtkFrame, remember, the text is actually part of a GtkLabel inside the GtkFrame widget! If you forget this, you may become frustrated, as nothing will happen when you bind the style to GtkFrame. Use the widget_class binding form for this to avoid changing ALL GtkLabel widgets by accident.

To change the GtkButton, we made use of the "image" theme engine. The theme engines can be quite cryptic and may not be well documented. The best advice is to investigate existing uses of them to gain a better understanding.

When the two persons chat, the program should be able to function without the use of a mouse.

To do this we give focus to Gtk2::Entry on initial startup. When the program starts we set the initial focus to the Gtk2::HBox. This will default to the Gtk2::Entry because it was packed into the Gtk2::HBox first. Gtk2::Entry is now ready to accept input from the keyboard.

#set initial focus
When the button is clicked, the focus moves from the Gtk2::Entry to the button. In the signal handling code, we have to move the focus on the button back to the Gtk2::Entry. This will allow us to start entering our next message into the Gtk2::Entry after we clicked the button.
#clear the entry
#grab focus again for the next round of talks

The keyboard can be used to trigger a "click" on the Gtk2::Button. Use of the key_press_event signal of Gtk2::Entry. Connect it to a closure that will cause the click method of Gtk2::Button to be called whenever the "return" key is pressed.

$ent_send->signal_connect('key_press_event'=> sub {
my ($widget,$event) = @_;
	if($event->keyval == $Gtk2::Gdk::Keysyms{Return}) {
                 return 1;

The latest postings must always be visible

To do this, we put a Gtk2::TextMark at the end of the Gtk2::TextBuffer and call the scroll_to_mark method of the Gtk2::TextView each time text is inserted into the Gtk2::TextBuffer.

#create a mark at the end of the buffer, and on each
	#'insert_text' we tell the textview to scroll to that mark
	$buffer->create_mark ('end', $buffer->get_end_iter, FALSE);
	$buffer->signal_connect (insert_text => sub {
	$tview->scroll_to_mark ($buffer->get_mark ('end'),
		0.0, TRUE, 0, 0.5);



Networking falls outside the scope of this book. To get extra information, you can "google" for phrases like "perl socket demo" or "perl socket tutorial". "socket" can also be replaced by "network". There are quite a number of good tutorials available to gain background knowledge.


Should you require more info on the format of rc files: Appendix C