A Gtk2 IRC Chat Client

D

deadpickle

I am trying to build a chat client that uses IRC and is built in the
framework of Gtk2. So far it works well but not great. Right now the
client connects to a server and joins a channel. Also, the client can
send messages to the channel and amazingly they are received on the
channel. The problem I am encountering is that the chat client is not
receiving all the raw lines sent by the IRC server (for an example of
what I mean try this script: http://www.oreilly.com/pub/h/1964). If it
is not receiving all the lines then it can only send messages not
receive them. So I am looking for help on how I can get the program to
receive messages so that they can be displayed in the window.

#!/usr/local/bin/perl -w
use strict;
use Gtk2 '-init';
use Glib qw/TRUE FALSE/;
use IO::Socket;

#-------------------Shared Variables-------------------
my $server = "irc.freenode.net";
my $nick = "simple";
my $login = "simple";
my $channel = "#GRRUVI";
my $sock;

#-------------------Main Loop-------------------
my $window = Gtk2::Window->new('toplevel');
$window->signal_connect( delete_event => sub {
close($sock);
Gtk2->main_quit;
});

$window->set_default_size( 300, 200 );

my $table = Gtk2::Table->new(2, 1, FALSE);
my $scroller = Gtk2::ScrolledWindow->new;
my $textview = Gtk2::TextView->new;
my $entry = Gtk2::Entry->new;

$scroller->add($textview);

$table->attach_defaults($scroller, 0, 1, 0, 1);
$table->attach_defaults($entry, 0, 1, 1, 2);

$window->add($table);

# allows for sending each line with an enter keypress
my $send_sig = $entry->signal_connect ('key-press-event' => sub {
my ($widget,$event)= @_;
if( $event->keyval() == 65293){ # a return key press
my $text = $entry->get_text;
if(defined $sock){ print $sock "PRIVMSG $channel :$text
\r\n";}
$entry->set_text('');
$entry->set_position(0);
}
});

$entry->signal_handler_block($send_sig); #not connected yet
$entry->set_editable(0);

$window->show_all;

connecting();

Glib::IO->add_watch( fileno $sock, [qw/in hup err/], \&incoming_data,
$sock );

Gtk2->main;

#-------------------Connect to Server-------------------
sub connecting {
# Connect to the IRC server.
$sock = new IO::Socket::INET(
PeerAddr => $server,
PeerPort => 6667,
Proto => 'tcp'
) or die "Can't connect\n";

# Log on to the server.
print $sock "NICK $nick\r\n";
print $sock "USER $login 8 * :perl IRC Hacks Robot\r\n";

# Read lines from the server until it tells us we have connected.
while (my $input = <$sock>) {
# Check the numerical responses from the server.
if ($input =~ /004/) {
# We are now logged in.
last;
}
elsif ($input =~ /433/) {
die "Nickname is already in use.";
}
}
# Join the channel.
print $sock "JOIN $channel\r\n";

$entry->set_editable(1);
$entry->grab_focus;
$entry->signal_handler_unblock ($send_sig);

Gtk2->main_iteration while Gtk2->events_pending;
}

#-------------------Incoming data-------------------
sub incoming_data {

my ( $fd, $condition, $fh ) = @_;

if ( $condition eq 'in' ) {
my $input = scalar <$fh>;
chop $input;
# if ( defined $data ) {
# # do something useful with the text.
# my $buffer = $textview->get_buffer;
# $buffer->insert( $buffer->get_end_iter, $data );
# }

if ($input =~ /^PING(.*)$/i) {
# We must respond to PINGs to avoid being disconnected.
print $sock "PONG $1\r\n";
}
else {
# Print the raw line received by the bot.
print "$input\n";
}
}
return TRUE;
}
 
D

deadpickle

I noticed a few things:
I tested the program using mIRC. I logged into the channel with the
client and under another name with mIRC. Logging in works fine, I can
see the clients nickname in the channel. What the problem seems to be
is when the MOTD is displayed. It seems that only a few lines of the
MOTD are displayed before the 'in' condition is stopped and the
message stops displaying in the terminal window. When I type in mIRC,
a line of the MOTD is displayed in terminal window. The $input
variable seems to be behind the real-time sending of the messages. I'm
not sure how to remove this lag and I'm looking for ideas.

#!/usr/local/bin/perl -w
use strict;
use Gtk2 '-init';
use Glib qw/TRUE FALSE/;
use IO::Socket;

#-------------------Shared Variables-------------------
my $server = "irc.freenode.net";
my $nick = "simple";
my $login = "simple";
my $channel = "#GRRUVI";
my $sock;

#-------------------Main Loop-------------------
my $window = Gtk2::Window->new('toplevel');
$window->signal_connect( delete_event => sub {
close($sock);
Gtk2->main_quit;
});

$window->set_default_size( 300, 200 );

my $table = Gtk2::Table->new(2, 1, FALSE);
my $scroller = Gtk2::ScrolledWindow->new;
my $textview = Gtk2::TextView->new;
my $entry = Gtk2::Entry->new;

$scroller->add($textview);

$table->attach_defaults($scroller, 0, 1, 0, 1);
$table->attach_defaults($entry, 0, 1, 1, 2);

$window->add($table);

# allows for sending each line with an enter keypress
my $send_sig = $entry->signal_connect ('key-press-event' => sub {
my ($widget,$event)= @_;
if( $event->keyval() == 65293){ # a return key press
my $text = $entry->get_text;
if(defined $sock){ print $sock "PRIVMSG $channel :$text
\r\n";}
$entry->set_text('');
$entry->set_position(0);
}
});

$entry->signal_handler_block($send_sig); #not connected yet
$entry->set_editable(0);

$window->show_all;

connecting();

Glib::IO->add_watch( fileno $sock, [qw/in hup err/], \&incoming_data,
$sock );

Gtk2->main;

#-------------------Connect to Server-------------------
sub connecting {
# Connect to the IRC server.
$sock = new IO::Socket::INET(
PeerAddr => $server,
PeerPort => 6667,
Proto => 'tcp'
) or die "Can't connect\n";

# Log on to the server.
print $sock "NICK $nick\r\n";
print $sock "USER $login 8 * :perl IRC Hacks Robot\r\n";

# Read lines from the server until it tells us we have connected.
while (my $input = <$sock>) {
# Check the numerical responses from the server.
if ($input =~ /004/) {
# We are now logged in.
last;
}
elsif ($input =~ /433/) {
die "Nickname is already in use.";
}
}
# Join the channel.
print $sock "JOIN $channel\r\n";

$entry->set_editable(1);
$entry->grab_focus;
$entry->signal_handler_unblock ($send_sig);

Gtk2->main_iteration while Gtk2->events_pending;
}

#-------------------Incoming data-------------------
sub incoming_data {

my ( $fd, $condition, $fh ) = @_;

if ( $condition eq 'in' ) {
my $input = scalar <$fh>;
chop $input;
# if ( defined $data ) {
# # do something useful with the text.
# my $buffer = $textview->get_buffer;
# $buffer->insert( $buffer->get_end_iter, $data );
# }

if ($input =~ /^PING(.*)$/i) {
# We must respond to PINGs to avoid being disconnected.
print $sock "PONG $1\r\n";
}
else {
# Print the raw line received by the bot.
print "$input\n";
}
}
return TRUE;
}
 
Z

zentara

I am trying to build a chat client that uses IRC and is built in the
framework of Gtk2. So far it works well but not great. Right now the
client connects to a server and joins a channel. Also, the client can
send messages to the channel and amazingly they are received on the
channel. The problem I am encountering is that the chat client is not
receiving all the raw lines sent by the IRC server (for an example of
what I mean try this script: http://www.oreilly.com/pub/h/1964). If it
is not receiving all the lines then it can only send messages not
receive them. So I am looking for help on how I can get the program to
receive messages so that they can be displayed in the window.

Try using sysread on the $fh, instead of a blocking <$fh>.

Search groups.google for things like "sysread socket", "non-blocking
socket read", etc,etc.

There are various types of sysread syntax tricks to coax the data out of
the socket.

zentara
 
B

Ben Morrow

Quoth deadpickle said:
I noticed a few things:
I tested the program using mIRC. I logged into the channel with the
client and under another name with mIRC. Logging in works fine, I can
see the clients nickname in the channel. What the problem seems to be
is when the MOTD is displayed. It seems that only a few lines of the
MOTD are displayed before the 'in' condition is stopped and the
message stops displaying in the terminal window. When I type in mIRC,
a line of the MOTD is displayed in terminal window. The $input
variable seems to be behind the real-time sending of the messages. I'm
not sure how to remove this lag and I'm looking for ideas.

You are reading the filehandle in buffered mode, which is not compatible
with select (which is used by Glib::IO->add_watch). Either switch to
sysread, or push a :unix layer to switch to unbuffered mode.

Ben
 
D

deadpickle

You are reading the filehandle in buffered mode, which is not compatible
with select (which is used by Glib::IO->add_watch). Either switch to
sysread, or push a :unix layer to switch to unbuffered mode.

Ben

Yep your both right. Adding sysread corrected the problem and now I
have another question. I am trying to complete the IRC Client with
giving the user the ability to disconnect and reconnect to the IRC
server. The problem I am encountering is that when the user
disconnects the Glib::IO tries to read from the closed socket. I tried
to undefine the Glib::IO but that hasent work.

#!/usr/local/bin/perl -w

use strict;
use Gtk2 '-init';
use Glib qw/TRUE FALSE/;
use IO::Socket;

#-------------------Global Variables-------------------
my $chat_state = 'Connect';
my $sock;
my $channel;
my $nick;
my $irc;
my $chat_entry;
my $chat_send_sig;
my $chat_textview;
my $chat_button;
my $watch;

#############################################
#specials that will be input by the efault file
$channel = '#GRRUVI';
$nick = 'Lahowetz';
$irc = 'irc.freenode.net';
my $login = $nick;

#############################################

#-------------------Main Loop-------------------
&chat_build;

Gtk2->main;

#-------------------chat Build-------------------
sub chat_build {
my $chat_window = Gtk2::Window->new('toplevel');
$chat_window->set_title('Chat Client');
$chat_window->set_position('center');
$chat_window->set_default_size( 300, 200 );
$chat_window->signal_connect(delete_event=> sub{exit});

my $chat_scroll = Gtk2::ScrolledWindow->new;
$chat_textview = Gtk2::TextView->new;
$chat_entry = Gtk2::Entry->new;
my $chat_vbox = Gtk2::VBox->new;

my $chat_buffer = $chat_textview->get_buffer;
$chat_buffer->create_mark( 'end', $chat_buffer->get_end_iter,
FALSE );
$chat_buffer->signal_connect(insert_text => sub {
$chat_textview->scroll_to_mark( $chat_buffer->get_mark('end'),
0.0, TRUE, 0, 0.5 );
});
$chat_button = Gtk2::Button->new;
$chat_button->set_label($chat_state);

$chat_scroll->add($chat_textview);
$chat_vbox->add($chat_scroll);
$chat_vbox->pack_start( $chat_entry, FALSE, FALSE, 0 );
$chat_vbox->pack_start( $chat_button, FALSE, FALSE, 0 );
$chat_window->add($chat_vbox);

# allows for sending each line with an enter keypress
$chat_send_sig = $chat_entry->signal_connect ('key-press-event' =>
sub {
my ($widget,$event)= @_;
if( $event->keyval() == 65293){ # a return key press
my $text = $chat_entry->get_text;
if(defined $sock){ print $sock "PRIVMSG $channel :$text
\r\n";}
$chat_entry->set_text('');
$chat_entry->set_position(0);
post($nick, $text);
}
});

$chat_entry->signal_handler_block($chat_send_sig); #not connected
yet
$chat_entry->set_editable(0);
$chat_textview->set_editable(0);
$chat_textview->set_cursor_visible(0);

$chat_window->show_all;

$chat_button->signal_connect("clicked" => sub {
if ($chat_state eq 'Connect') {
$chat_button->set_label('Disconnect');
$chat_state='Disconnect';
connecting();
}
else {
$chat_button->set_label('Connect');
$chat_state='Connect';
close $sock;
undef $watch;
}
});

return;
}

#-------------------Connect to IRC Server-------------------
sub connecting {
# Connect to the IRC server.
$sock = new IO::Socket::INET(
PeerAddr => $irc,
PeerPort => 6667,
Proto => 'tcp',
) or die "Can't connect\n";

if (defined $sock){
my $sys_msg = "Connected to $irc ...";
post($nick, $sys_msg);
}

# Log on to the server.
print $sock "NICK $nick\r\n";
print $sock "USER $login 8 * :Just a Tester\r\n";

# Read lines from the server until it tells us we have connected.
while (my $input = <$sock>) {
# Check the numerical responses from the server.
if ($input =~ /004/) {
# We are now logged in.
my $sys_msg = "Logged in to $irc ...";
post($nick, $sys_msg);
print $sock "JOIN $channel\r\n";
$watch = Glib::IO->add_watch( fileno $sock, [qw/in hup
err/], \&incoming_data, $sock );
$chat_entry->set_editable(1);
$chat_entry->grab_focus;
$chat_entry->signal_handler_unblock ($chat_send_sig);
last;
}
elsif ($input =~ /433/) {
my $sys_msg = "Nickname is already in use";
post($nick, $sys_msg);
$chat_state = 'Connect';
$chat_button->set_label($chat_state);
$chat_entry->signal_handler_block($chat_send_sig);
close $sock;
last;
}
}

Gtk2->main_iteration while Gtk2->events_pending;
}

#-------------------Watch for IRC Inputs-------------------
sub incoming_data {
my ( $fd, $condition, $fh ) = @_;

if ( $condition eq 'in' ) {
my $input;
sysread $fh, $input, 1000000;
chop $input;

if ($input =~ /^PING(.*)$/i) {
# We must respond to PINGs to avoid being disconnected.
print $sock "PONG $1\r\n";
}
if ($input =~ m/PRIVMSG\s($channel)/) {
print "$input\n";
my @sender = split(/!/, $input);
my @message = split(/:/, $input);
my $length = length($sender[0]);
my $who = substr($sender[0], 1, $length);
post($who, $message[2]);
}
if ($input =~ m/QUIT/) {
print "$input\n";
my @sender = split(/!/, $input);
my $length = length($sender[0]);
my $who = substr($sender[0], 1, $length);
my $sys_msg = "$who has QUIT!";
post($who, $sys_msg);
}
if ($input =~ m/JOIN/) {
print "$input\n";
my @sender = split(/!/, $input);
my $length = length($sender[0]);
my $who = substr($sender[0], 1, $length);
my $sys_msg = "$who has JOINED $channel!";
post($who, $sys_msg);
}
else {
# Print the raw line received by the bot.
print "$input\n";
}
}
return TRUE;
}

#-------------------Post messages in the Window-------------------
sub post{
my ($name, $msg) = @_;

my $chat_buffer = $chat_textview->get_buffer;
$chat_buffer->insert( $chat_buffer->get_end_iter, "$name: $msg
\n" );
}
 
Z

zentara

Yep your both right. Adding sysread corrected the problem and now I
have another question. I am trying to complete the IRC Client with
giving the user the ability to disconnect and reconnect to the IRC
server. The problem I am encountering is that when the user
disconnects the Glib::IO tries to read from the closed socket. I tried
to undefine the Glib::IO but that hasent work.

Without messing with your code, I can tell you what you need to do.

You need to stop the current connection, then reconnect. There are
a couple of ways to do it. It is probably best to have a disconnect and
connect subs.

But basically you need to return false from your io callback and close
the socket.

Usually, you would do it something like

sub disconnect{

Gtk2::Helper->remove_watch( $sock );
close $sock;

#here you would reenable the Connect button,
#and do miscelaneous clean-up

#the reconnect again with a fresh socket
#and GLIB::IO watch on the new $sock.
# would be done in the "connect sub".
return 0;
}

zentara
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top