[gtkada] Gdk_Input sockets and IO

Bobby D. Bryant bdbryant at mail.utexas.edu
Thu Nov 29 10:18:37 CET 2001


tony wrote:

> I'm trying to get my gtk application to listen and on a certain socket port,
> and get a gtk on screen reaction to this signal.......
>
> has anyone any examples of this they could send me........

Here's a partial solution.  Unfortunately my complete example is scatttered
through a large program, so I'm just giving you snippets that outline the
solution, rather than a full example.  (Sorry, but my mail client is going to
insert some serious wrapping in the following, and probably screw up the
tabbing as well.)

BTW, I have not updated my GtkAda to 1.2.12 yet, but this is how I am doing it
under 1.2.11.


First notice this from near the bottom of gtk-main.ads -

   -----------
   -- Input --
   -----------
   --  The following functions are used to react when new data is available on
   --  a file descriptor (file, socket, pipe, ...)
   --  They have not been bound, since apparently there is no easy way in Ada
   --  to get the file descriptor for an open file, and there is no standard
   --  socket package.
   --  Instead, you should consider having a separate task that takes care of
   --  monitoring over sources of input in your application.

   --  gtk_input_add_full
   --  gtk_input_remove
   --  key_snooper_install
   --  key_snooper_remove

The gtk_input_* functions provide the functionality, so you just have to bind
them.  The bindings are already done in my two attachments; I call the packge
Gdk.Addendum as a throwdown solution, since I hope this code or something
similar will be put in one of the existing GtkAda source files someday.  (In
those files I assign the copyright to BB&C, since it's mostly just a few minor
tweaks to one of the files that came in their GtkAda distribution.  However,
don't expect them to answer questions about it!)

Notice that I use gtk_input_add rather than the gtk_input_add_full mentioned in
the comment above.


My solution also requires AdaSockets, which you can find at
http://www-inf.enst.fr/ANC/.  I am using version 0.1.12; earlier versions may
not work, because I requested a new feature needed for use with these GTK+
input functions, and the maintainer added it at 0.1.12 or perhaps slightly
earlier.


Somewhere you'll need to instantiate the generic Gtk.Addendum.Input, thus -

   package Message_Handler is
     new Gdk.Addendum.Input( Data_Type => Positive ); -- Indexes client.


Somewhere you'll need to create a port and use Input_Add to set up a listener
for it.  Here's the code I'm using, including comments that indicate that I
don't really know what the heck I'm doing.  It references some variables
declared elsewhere, but you can determine their types by looking at the Sockets
package spec -

   ----------------------------------------------------------------------------

   -- Create a port for the clients:
   procedure Create_Port is
   begin
      -- Create the port:
      Sockets.Socket( Sock   => Listener_Socket
                      --Domain => Socket_Domain := AF_INET,
                      --Typ    => Socket_Type   := SOCK_STREAM
                      );
--******************************
--***ppp*** what's all this? ***
--******************************
      Sockets.Setsockopt( Socket  => Listener_Socket,
                          Level   => Sockets.SOL_SOCKET,
                          Optname => Sockets.SO_REUSEADDR,
                          Optval  => 1 );
      begin
         Sockets.Bind( Socket => Listener_Socket,
                       Port   => Port_Number );
      exception
         when others =>
            Put_Line( "[Server] *** Unhandled exception calling Sockets.Bind."
);
            raise;
      end;
      Sockets.Listen( Socket     => Listener_Socket
                      --Queue_Size : in Positive := 5
                      );
      -- Set up GTK to service connect requests:
      Listener_Handler
        := Gdk.Addendum.Input_Add( Source    => Glib.Gint( Sockets.Get_FD(
Listener_Socket ) ),
                                   --***ppp*** the Get_FD is C.Int.
                                   Condition => Gdk.Types.Input_Read,
                                   Func      => Handle_Connection'Access );
      Put_Line( "[Server] Established listener on port." );
   exception
      when others =>
         Put_Line( "[Server] *** Unhandled exception in Create_Port." );
         raise;
   end Create_Port;


Now, whenever something hits on the port my function Handle_Connection will be
invoked by GTK+.  Handle_Connection does some application-specific checking to
see whether it wants this connection, and if it does it creates a socket for it
and uses Message_Handler.Add to assign a handler function to this specific
socket.  Again there are references to variables declared elsewhere, but you
can determine the necessary types by looking at the package specs.  (Also
notice that the array of sockets is part of the application design, and not
actually a requirement for using this stuff.)  Here's the code -

   ----------------------------------------------------------------------------

   -- React to a new connection to the listener socket:
   procedure Handle_Connection is
   begin
--      Put_Line( "[Server] Got a hit on the port..." );
      -- Verify that we have not used up all the sockets:
      if ( New_Socket_Index > Client_Socket'Last ) then
         Put_Line( "[Server] *** Error: Too many client connections." );
         Put_Line( "[Server] *** Connection rejected." );
      else
         -- Create a socket for this client:
         Sockets.Accept_Socket( Socket     => Listener_Socket,
                                New_Socket => Client_Socket( New_Socket_Index )
);
         -- Log it:
         Put( "[Server] Socket # " );
         Put( New_Socket_Index, Width => 0 );
         Put( " was assigned FD # " );
         Put( Integer( Sockets.Get_FD( Client_Socket( New_Socket_Index ) ) ),
              Width => 0 );
         --***ppp*** danger! "integer" is really "interfaces.c.int" ***
         Put_Line( "." );
         -- Set up a listener for the socket:
         Client_Handler( New_Socket_Index )
           := Message_Handler.Add( Source    => Glib.Gint( Sockets.Get_FD(
Client_Socket( New_Socket_Index ) ) ),
                                   Condition => Gdk.Types.Input_Read,
                                   Func      => Handle_Client_Message'Access,
                                   D         => New_Socket_Index );
         -- Update the record pointer to the next slot:
         New_Socket_Index := New_Socket_Index + 1;
--         Put_Line( "[Server] ...handled hit." );
      end if; -- new_socket_index > client_socket'last.
   end Handle_Connection;


Now, whenever input is available on the socket for this client, GTK+ will wake
up my Handle_Client_Message function.  This is the operation you were asking
about.  I'll throw in the first part of my application-specific example.  The
types are application specific, as is package Packet -

   ----------------------------------------------------------------------------

   -- React to a new message on a specific socket:
   procedure Handle_Client_Message( Socket_Index : in Positive ) is
      Buffer : Buffer_Type;
      Packet : Packet_Type;
      --
      This_Client    : Positive;
[...snip application-specific declarations...]
   begin
      -- Read the message:
      Sockets.Receive( Socket => Client_Socket( Socket_Index ),
                       Data   => Buffer );
      Packet := To_Packet( Buffer );

      -- Decode the message:
      case Packet.Message is
[...snip...]
      end case; -- packet.message
   exception
      when Sockets.Connection_Closed =>
         -- N.B. - For for some odd reason the socket is marked "ready to read"

         --        when the connected process exits. So just close it.
         Put( "[Server] Socket # " );
         Put( Socket_Index, Width => 0 );
         Put_Line( " is closed; removing." );
         Gdk.Addendum.Input_Remove( Client_Handler( Socket_Index ) );
      when others                    =>
         Put_Line( "[Server] *** Unhandled exception in Handle_Client_Message.
***" );
         raise;
   end Handle_Client_Message;

The exception handles a quirk: when the OS detects that the socket has been
closed from the other end, it marks the socket as "ready to read", with the
result that GTK+ invokes Handle_Client_Message per the earlier setup.  But then
I get an exception when I try to read the socket.

BTW, I've only run this code under Linux.

Hope this helps,

Bobby Bryant
Austin, Texas

-------------- next part --------------
-- gdk-addendum.ads
-----------------------------------------------------------------------
--          GtkAda - Ada95 binding for the Gimp Toolkit              --
--                                                                   --
--                     Copyright (C) 1998-2000                       --
--        Emmanuel Briot, Joel Brobecker and Arnaud Charlet          --
--                                                                   --
-- This library is free software; you can redistribute it and/or     --
-- modify it under the terms of the GNU General Public               --
-- License as published by the Free Software Foundation; either      --
-- version 2 of the License, or (at your option) any later version.  --
--                                                                   --
-- This library is distributed in the hope that it will be useful,   --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of    --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU --
-- General Public License for more details.                          --
--                                                                   --
-- You should have received a copy of the GNU General Public         --
-- License along with this library; if not, write to the             --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330,      --
-- Boston, MA 02111-1307, USA.                                       --
--                                                                   --
-- As a special exception, if other files instantiate generics from  --
-- this unit, or you link this unit with other files to produce an   --
-- executable, this  unit  does not  by itself cause  the resulting  --
-- executable to be covered by the GNU General Public License. This  --
-- exception does not however invalidate any other reasons why the   --
-- executable file  might be covered by the  GNU Public License.     --
-----------------------------------------------------------------------


with Glib; use Glib; -- Defines Gint.
with Gdk.Types;      -- Defines type Gdk_Input_Condition.

package Gdk.Addendum is

   --   type Gdk_Input_Condition is...   already defined in gdk-types.ads.

   -----------
   -- Input --
   -----------
   --  The following functions are used to react when new data is available on
   --  a file descriptor (file, socket, pipe, ...)

   -- Note: I used Gint for the file descriptor, since the C version uses gint.

   type Input_Handler_Id is new Gint;

   type Input_Callback is access procedure;
   function Input_Add (Source    : in Gint;
                       Condition : in Gdk.Types.Gdk_Input_Condition;
                       Func      : in Input_Callback )
                       return Input_Handler_Id;
   -- Register an input callback with no user data.

   --  <doc_ignore>
   generic
      type Data_Type (<>) is private;
   package Input is
      type Callback is access procedure (D : in Data_Type);
      function Add (Source    : in Gint;
                    Condition : in Gdk.Types.Gdk_Input_Condition;
                    Func      : in Callback;
                    D         : in Data_Type)
                    return Input_Handler_Id;
   end Input;
   --  !!Warning!! The instances of this package must be declared at library
   --  level, as they are some accesses to internal functions that happen
   --  when the callback is called.
   --  </doc_ignore>

   procedure Input_Remove (Id : in Input_Handler_Id );
   -- Unregister an input callback.

private
   --  Some services can be directly bound...

   pragma Import (C, Input_Remove, "gtk_input_remove");

end Gdk.Addendum;
-------------- next part --------------
-- gdk-addendum.ads
-----------------------------------------------------------------------
--          GtkAda - Ada95 binding for the Gimp Toolkit              --
--                                                                   --
--                     Copyright (C) 1998-2000                       --
--        Emmanuel Briot, Joel Brobecker and Arnaud Charlet          --
--                                                                   --
-- This library is free software; you can redistribute it and/or     --
-- modify it under the terms of the GNU General Public               --
-- License as published by the Free Software Foundation; either      --
-- version 2 of the License, or (at your option) any later version.  --
--                                                                   --
-- This library is distributed in the hope that it will be useful,   --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of    --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU --
-- General Public License for more details.                          --
--                                                                   --
-- You should have received a copy of the GNU General Public         --
-- License along with this library; if not, write to the             --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330,      --
-- Boston, MA 02111-1307, USA.                                       --
--                                                                   --
-- As a special exception, if other files instantiate generics from  --
-- this unit, or you link this unit with other files to produce an   --
-- executable, this  unit  does not  by itself cause  the resulting  --
-- executable to be covered by the GNU General Public License. This  --
-- exception does not however invalidate any other reasons why the   --
-- executable file  might be covered by the  GNU Public License.     --
-----------------------------------------------------------------------


with Glib; use Glib;         -- Defines Gint.
with Gdk.Types;              -- Defines type Gdk_Input_Condition.
with System;                 -- For System.Address.
with Unchecked_Deallocation;
with Unchecked_Conversion;

with
  Ada.Text_IO;

use
  Ada.Text_IO;

package body Gdk.Addendum is

   ---------------
   -- Input_Add --
   ---------------

   function Input_Add (Source    : in Gint;
                       Condition : in Gdk.Types.Gdk_Input_Condition;
                       Func      : Input_Callback )
                       return Input_Handler_Id
   is
      function Internal (Source    : Gint;
                         Condition : Gdk.Types.Gdk_Input_Condition;
                         Func      : Input_Callback;
                         Data : System.Address )
                         return Input_Handler_Id;
      pragma Import (C, Internal, "gdk_input_add");
   begin
      return Internal (Source, Condition, Func, System.Null_Address);
   end Input_Add;


   ----------
   -- Input--
   ----------

   package body Input is

      type Data_Type_Access is access Data_Type;
      type Cb_Record is
         record
            Func : Callback;
            Data : Data_Type_Access;
         end record;
      type Cb_Record_Access is access Cb_Record;

      procedure Free_Data (D : in System.Address);
      pragma Convention (C, Free_Data);

      function Convert is new Unchecked_Conversion
        (System.Address, Cb_Record_Access);

      procedure General_Cb (D : in System.Address);
      pragma Convention (C, General_Cb);

      ----------
      -- Free --
      ----------

      procedure Free_Data (D : in System.Address) is
         procedure Internal is new Unchecked_Deallocation
           (Cb_Record, Cb_Record_Access);
         procedure Internal2 is new Unchecked_Deallocation
           (Data_Type, Data_Type_Access);
         Data : Cb_Record_Access := Convert (D);
      begin
         Internal2 (Data.Data);
         Internal (Data);
      end Free_Data;

      ----------------
      -- General_Cb --
      ----------------

      procedure General_Cb (D : in System.Address) is
         Data : Cb_Record_Access := Convert (D);
      begin
         Data.Func (Data.Data.all);
      end General_Cb;

      ---------
      -- Add --
      ---------

      function Add (Source    : in Gint;
                    Condition : in Gdk.Types.Gdk_Input_Condition;
                    Func      : in Callback;
                    D         : in Data_Type)
                    return Input_Handler_Id
      is

         function Internal (Source    : in Gint;
                            Condition : in Gdk.Types.Gdk_Input_Condition;
                            Func      : in System.Address;
                            Data      : in System.Address;
                            Destroy   : in System.Address)
                            return        Input_Handler_Id;
         pragma Import (C, Internal, "gdk_input_add_full");

         function Convert is new Unchecked_Conversion
           (Cb_Record_Access, System.Address);
         Data : Cb_Record_Access := new Cb_Record'(Func => Func,
                                                   Data => new Data_Type'(D));
      begin
         return Internal (Source, Condition,
                          General_Cb'Address,
                          Convert (Data), Free_Data'Address);
      end Add;

   end Input;

-------------------------------------------------------------------------------
begin

   pragma Debug( Put_Line( "*Elaborating package GDK-addendum..." ) );
   null; -- the pragma is not a statement!

exception
   when others =>
      raise;

end Gdk.Addendum;


More information about the gtkada mailing list