A Gtk2 IRC Chat Client

Discussion in 'Perl Misc' started by deadpickle, Feb 11, 2008.

  1. deadpickle

    deadpickle Guest

    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;
    }
     
    deadpickle, Feb 11, 2008
    #1
    1. Advertising

  2. deadpickle

    deadpickle Guest

    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;
    }
     
    deadpickle, Feb 12, 2008
    #2
    1. Advertising

  3. deadpickle

    zentara Guest

    On Mon, 11 Feb 2008 12:13:44 -0800 (PST), deadpickle
    <> wrote:

    >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

    --
    I'm not really a human, but I play one on earth.
    http://zentara.net/japh.html
     
    zentara, Feb 12, 2008
    #3
  4. deadpickle

    Ben Morrow Guest

    Quoth 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.


    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
     
    Ben Morrow, Feb 12, 2008
    #4
  5. deadpickle

    deadpickle Guest

    On Feb 12, 10:06 am, Ben Morrow <> wrote:
    > Quoth 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.

    >
    > 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" );
    }
     
    deadpickle, Feb 14, 2008
    #5
  6. deadpickle

    zentara Guest

    On Wed, 13 Feb 2008 20:43:10 -0800 (PST), deadpickle
    <> wrote:

    >On Feb 12, 10:06 am, Ben Morrow <> wrote:
    >> Quoth deadpickle <>:


    >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


    --
    I'm not really a human, but I play one on earth.
    http://zentara.net/japh.html
     
    zentara, Feb 14, 2008
    #6
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. John Wright

    IRC Client

    John Wright, Aug 3, 2004, in forum: ASP .Net
    Replies:
    1
    Views:
    961
    Lucas Tam
    Aug 3, 2004
  2. vb asp.net IRC client test

    , Oct 5, 2004, in forum: ASP .Net
    Replies:
    0
    Views:
    1,322
  3. Replies:
    0
    Views:
    2,207
  4. Replies:
    2
    Views:
    377
    Andrew Thompson
    Dec 5, 2007
  5. deadpickle

    Gtk2 IRC Client Sluggish and not Responsive

    deadpickle, Mar 5, 2008, in forum: Perl Misc
    Replies:
    0
    Views:
    177
    deadpickle
    Mar 5, 2008
Loading...

Share This Page