Interprocess Communication

A Photon application can't always work in isolation — sometimes it needs to communicate with other processes.

This chapter describes:

The QNX Neutrino operating system supports various methods of interprocess communication (IPC), including:

These methods can be used in a Photon application, as long as you're careful. However, it's best to use Photon connections:

On the other hand, here's why raw Neutrino messages and/or pulses might sometimes be better:

The Photon main event-handling loop that your application calls is responsible for handling Photon events so that widgets update themselves and your callback functions are called.

This simple event-driven model of programming used with the Photon widget library presents some challenges for the application developer because the event-handling loop performs an unconditional MsgReceive() to obtain events from Photon. This means your application has to be careful if it needs to call MsgReceive(), or Photon events might go astray and the user interface might not be updated.

If you need to:

you'll need a way to hook your application code into the event-handling loop. Similarly, you may want to be able to add time-outs to your application and associate callback functions with them.

Connections

The process of establishing a connection uses an object called a connector. The connector is a name that the server creates and owns, and the client attaches its connection to. The connector is used only for establishing a connection.

The connector has a numeric ID and may also have a name associated with it. Both the name and the ID are unique in their Photon session. Here are a few examples of how the name can be used:

Naming conventions

You can define unique names for your connectors by following these naming conventions:

Typical scenario

Here's how you typically use connections:

  1. The server calls PtConnectorCreate() to set up a connector, specifying a function to call whenever a client connects to the connector.
  2. If the client needs a connector ID to find the connector, the server calls PtConnectorGetId() to determine the ID. The server then needs to give the ID to the client.
  3. The client looks for a connector by calling PtConnectionFindName() or PtConnectionFindId(). If these functions succeed, they return a client connection object of type PtConnectionClient_t.

    The client can make repeated attempts (within a specified time limit or until the server terminates) to find the server by calling PtConnectionWaitForName().

  4. If the client finds the connector, the library sets up a connection to it and invokes the callback that the server specified when it created the connector, passing a PtConnectionServer_t server connection object to the routine.
  5. The server's callback uses PtConnectionAddMsgHandlers() to set up a handler for any messages from the client.
  6. The client uses PtConnectionSend() or PtConnectionSendmx() to send a message to the server. The client blocks until the server replies.
  7. The library invokes the server's message handler, which calls PtConnectionReply() or PtConnectionReplymx() to reply to the client.
  8. The client and server continue to exchange messages.
  9. If the client wants to break the connection, it calls PtConnectionClientDestroy(); if the server wants to break the connection, it calls PtConnectionServerDestroy().
  10. When the server no longer needs the connector, it destroys it by calling PtConnectorDestroy().

You can pass user data with the connection. The server calls PtConnectionServerSetUserData() to specify the data that the client can retrieve by calling PtConnectionClientGetUserData(). Similarly, the client calls PtConnectionClientSetUserData() to specify the data that the server can retrieve by calling PtConnectionServerGetUserData().

You can set up functions to handler errors; the server does this by calling PtConnectionServerSetError(), and the client by calling PtConnectionClientSetError().

The server can also use events to communicate with the client:

  1. The client sets up one or more event handlers by calling PtConnectionAddEventHandlers(). You can set up different types of messages, and handlers for each type.
  2. To send an event to the client, the server calls PtConnectionNotify(), which in turn can call PtConnectionFlush() if there isn't enough room for the notification in the server's buffer.
  3. The server can change the size of the buffer by calling PtConnectionResizeEventBuffer().

Local connections

It's possible for a process to create a connection to itself. The behavior of such a connection differs a little bit from a normal connection:

Example

This application uses a connector to determine if there's already another instance of the application running. The program takes two command-line options:

-e
If another instance of the application is already running, tell it to exit.
-f file
If another instance of the application is already running, tell it to open the given file; otherwise just open the file.

Here's the code:

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "abimport.h"
#include "proto.h"

enum MyMsgType {
    MY_MSGTYPE_EXIT, MY_MSGTYPE_OPEN_DOC, MY_MSGTYPE_TOFRONT
    };

enum MyReplyType {
    MY_REPTYPE_SUCCESS, MY_REPTYPE_BADMSG
    };

struct MyMsg {
    char docname[ PATH_MAX ];
    };

struct MyReply {
    enum MyReplyType status;
    };

/* Handle a message from a client: */
static PtConnectionMsgFunc_t msghandler;

static void const *msghandler(
        PtConnectionServer_t *connection, void *data,
        unsigned long type, void const *msgptr,
        unsigned msglen, unsigned *reply_len
        )
{
    struct MyMsg const *msg = (struct MyMsg const*) msgptr;
    static struct MyReply reply;

    reply.status = MY_REPTYPE_SUCCESS;
    switch ( type ) {
        case MY_MSGTYPE_EXIT :
            PtConnectionReply( connection, sizeof(reply),
                               &reply );
            PtExit( EXIT_SUCCESS );
            break;

        case MY_MSGTYPE_OPEN_DOC :
            reply.status = OpenNewDocument( msg->docname );
            break;

        case MY_MSGTYPE_TOFRONT :
            break;

        default :
            reply.status = MY_REPTYPE_BADMSG;
        }

    PtWindowToFront( ABW_base );
    *reply_len = sizeof(reply);
    return &reply;
}

/* Set up a new connection: */
static PtConnectorCallbackFunc_t connector_callback;

static void connector_callback(
               PtConnector_t *connector,
               PtConnectionServer_t *connection,
               void *data )
{
    static const PtConnectionMsgHandler_t
        handlers = { 0, msghandler };
    if ( PtConnectionAddMsgHandlers( connection,
                                     &handlers, 1 ) != 0 ) {
        fputs( "Unable to set up connection handler\n", stderr );
        PtConnectionServerDestroy( connection );
    }
}

/* Application Options string */
const char ApOptions[] =
    AB_OPTIONS "ef:"; /* Add your options in the "" */

/* Application initialization function */
int init( int argc, char *argv[] )
{
    struct MyMsg msg;
    int opt;
    long msgtype = MY_MSGTYPE_TOFRONT;
    const char *document = NULL;
    static const char name[] = "me@myself.com/ConnectionExample";

    while ( ( opt = getopt( argc, argv, ApOptions ) ) != -1 )
        switch ( opt ) {
            case '?' :
                PtExit( EXIT_FAILURE );

            case 'e' :
                msgtype = MY_MSGTYPE_EXIT;
                break;

            case 'f' :
                document = optarg;
            }

    if ( document )
        if ( msgtype == MY_MSGTYPE_EXIT ) {
            fputs(
               "You can't specify both the -e and -f options\n",
               stderr );
            PtExit( EXIT_FAILURE );
        } else {
            msgtype = MY_MSGTYPE_OPEN_DOC;
            strncpy( msg.docname, document,
                     sizeof(msg.docname)-1 );
        }

    while ( PtConnectorCreate( name, connector_callback, 0 )
            == NULL ) {

            /* If this failed, another instance of the app must
               be already running */

            PtConnectionClient_t *clnt;
            if ( ( clnt = PtConnectionFindName( name, 0, 0 ) )
                 != 0 ) {
                struct MyReply reply;
                int result = PtConnectionSend( clnt, msgtype,
                            &msg, &reply, sizeof(msg),
                            sizeof(reply) );
                PtConnectionClientDestroy( clnt );
                if ( result == 0 )
                    PtExit( reply.status );
            }
    }

    /* Since PtConnectorCreate() has succeeded, we're the only
       instance of the app running */

    if ( msgtype == MY_MSGTYPE_EXIT ) {
        fputs( "Can't tell it to exit; it's not running\n",
               stderr );
        PtExit( EXIT_FAILURE );
        }

    if ( document )
        OpenNewDocument( document );

    return Pt_CONTINUE;
}

Sending QNX messages

A Photon application can use MsgSend() to pass messages to another process, but the other process needs to MsgReply() promptly, as Photon events aren't processed while the application is blocked. (Promptness isn't an issue if your application has multiple threads that process events, and you call PtLeave() before MsgSend(), and PtEnter() after. For a discussion of writing applications that use multiple threads, see the Parallel Operations chapter.)

As an example, here's a callback that extracts a string from a text widget, sends it to another process, and displays the reply in the same text widget:

/* Callback that sends a message to another process      */

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/neutrino.h> /* Needed for MsgSend() */

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "globals.h"
#include "abimport.h"
#include "proto.h"

extern int coid;

int
send_msg_to_b( PtWidget_t *widget,
               ApInfo_t *apinfo,
               PtCallbackInfo_t *cbinfo )

{
  char *a_message;

  /* eliminate 'unreferenced' warnings */
  widget = widget, apinfo = apinfo, cbinfo = cbinfo;

  /* Get the string from the text widget. */

  PtGetResource (ABW_msg_text, Pt_ARG_TEXT_STRING, 0, 0);

  /* Send the string to another process. */

  a_message = (char *)args[0].value;
  if ( MsgSend (coid, a_message, msg_size,
             rcv_msg, msg_size) == -1)
  {
    perror ("Send to B failed");
    PtExit (-1);
  }

  /* Remember the UI is "hung" until the other
     process replies! */

  /* Display the reply in the same text widget. */

  PtSetResource (ABW_msg_text, Pt_ARG_TEXT_STRING,
                 rcv_msg, 0);

  return( Pt_CONTINUE );

}

For more information on messages, see the QNX Neutrino System Architecture guide.

Receiving QNX messages

To handle non-Photon events in a Photon application, you need to register an input handling procedure (or input handler), otherwise the events are lost. This is because the unconditional MsgReceive() performed by the Photon event handling loop ignores non-Photon events.


Note: You can create your own channel and call MsgReceive() on it, but remember that your application and its interface will be blocked until that process sends a message. It's better to use an input handler as described in this section.

An input handler is responsible for handling messages received by the application from a particular process. When you register the handler with the widget library, you identify the pid the input handler is associated with.

You can define more than one input handler in your application for a pid. When a message is received from that process, the widget library starts calling the handlers in the list, starting with the last one registered, and continuing up the list in reverse order until a handler recognizes the message (that is, it doesn't return Pt_CONTINUE). See the description of Pt_CONTINUE below.

You can register a nonspecific input handler by specifying a value of zero for the pid. This handler is called when the application receives:

Adding an input handler

To register an input handler, call PtAppAddInput() when you initialize the application. The syntax is given below; for more information, see the Photon Library Reference.

PtInputId_t *PtAppAddInput(
                 PtAppContext_t app_context,
                 pid_t pid,
                 PtInputCallbackProc_t input_func,
                 void *data );

The arguments are:

app_context
The address of the application context, a PtAppContext_t structure that manages all the data associated with this application. Pass NULL for this argument, so that the default context is used.
pid
The process ID of the process whose messages this handler is to deal with, or 0 if the handler is for messages from all processes.
input_func
Your input handler, of type PtInputCallbackProc_t. For details, see the Photon Library Reference.
data
Extra data to be passed to the input handler.

PtAppAddInput() returns a pointer to an input-handler ID, which you'll need if you want to remove the input handler later.

The prototype for an input handler is as follows:

int input_proc( void *data,
                int rcvid,
                void *msg,
                size_t msglen );

The arguments are:

data
A pointer to any extra data you want to pass to the input handler.
rcvid
The rcvid of the process that sent the message.
msg
A pointer to the message sent.
msglen
The size of the message buffer. If the actual message is longer than the buffer, load the rest of the message by calling MsgRead().

You can also declare the input handler to be of type PtInputCallbackProcF_t to take advantage of the prototype checking done by the compiler.


Note: If your input handler changes the display, it should call PtFlush() to make sure the display is updated.

An input handler must return one of the following:

Pt_CONTINUE
The input handler doesn't recognize the message. If there are other input handlers attached to the same process ID, they're called. If there are no input handlers attached specifically to this process ID, or if all input handlers attached specifically to this process ID return Pt_CONTINUE, the library looks for input handlers attached to rcvid 0. If all the input handlers return Pt_CONTINUE, the library replies to the message with an ENOSYS.
Pt_END
The message has been recognized and processed and the input handler needs to be removed from the list. No other input handlers are called for this message.
Pt_HALT
The message has been recognized and processed but the input handler needs to stay on the list. No other input handlers are called for this message.

name_attach() and PtAppAddInput()

If possible, you should use a Photon connection instead of name_attach() to establish a connection with another process. However, you can't use a Photon connection in these cases:

PtAppAddInput() and name_attach() both try to create a channel with _NTO_CHF_COID_DISCONNECT and _NTO_CHF_DISCONNECT set (see the QNX Neutrino Library Reference). If your application calls both functions, you need to let Photon use the same channel as name_attach(), by calling PhChannelAttach() first, like this:

PhChannelAttach( chid, -1, NULL );

before calling name_attach() or PtAppAddInput().

If you want to create a separate channel for Photon, it doesn't matter whether you create it and give it to PhChannelAttach() before or after calling name_attach(). But keep in mind that since certain mechanisms in Photon library expect the Photon channel to have the two DISCONNECT flags, they might not work properly if it doesn't. One such mechanism is the detection of broken connections (see PtConnectionClientSetError() and PtConnectionServerSetError()) and anything that relies on it.

Removing an input handler

To remove an input handler:

Message buffer size

As described above, arguments to your input function include:

msg
A pointer to an event buffer that was used to receive the message.
msglen
The size of the buffer.

This buffer might not be large enough to hold the entire message. One way of handling this is to have the first few bytes of the message indicate the message type and from that determine how big the message should be. Once you know the message size, you can:

Alternatively, you can set the event buffer to be the size of the largest message your application will receive (if known). This can be done by calling PtResizeEventMsg(). You'd typically call this before you expect to receive any messages.


Note: PtResizeEventMsg() won't reduce the message buffer beyond a certain minimum size. This is so that the widget library will continue to function.

Example — logging error messages

The following code fragment shows how a nonspecific input handler may be used to respond to error-logging messages from any process. When one of these messages is received, the application displays the message's contents in a multiline text widget. (This example assumes log_message is declared elsewhere.)

int input_proc(void *client_data, int rcvid, void *msg,
               size_t msglen)
{
   struct log_message *log = (struct log_message *)msg;

   /* Only process log messages */
   if (log->type == LOG_MSG)
   {
      PtWidget_t *text = (PtWidget_t *)client_data;
      struct log_message header;
      int msg_offset = offsetof(struct log_message, msg);
      int log_msglen;
      int status;

      /* See if our entire header is in the buffer --
         it should be */
      if (msglen < msg_offset)
      {
         /* Read in the whole header */
         if (MsgRead(rcvid, &header, msg_offset, 0)  == -1)
         {
            status = errno;
            MsgError( rcvid, status);
            return Pt_HALT;    /* bail out */
         }
         log = &header;
      }

      log_msglen = msg_offset+log->msg_len;

      /* See if the whole message is in the buffer */

      if (msglen < log_msglen)
      {
         struct log_message *log_msg =
            (struct log_message *)alloca(log_msglen);

         /* Read the remainder of the message into
            space on the stack */

         if (log_msg == NULL ||
             MsgRead( rcvid, log_msg, log_msglen, 0) == -1)
         {
            status = errno;
            MsgError( rcvid, status);
            return Pt_HALT;    /* bail out */
         }
         log = log_msg;
      }

      add_msg(text, log);
      status = 0;
      MspReply( rcvid, 0, 0, 0);
   }

   return Pt_HALT;
}

This application registers the input_proc() function as an input handler for handling non-Photon messages from any other process.

The input_proc() function first checks the message type of the incoming message. If the input handler isn't responsible for this type of message, it returns immediately. This is important because any other nonspecific input handlers that were registered will be called as well, and only one of them should respond to a given message.

If the type of message received is a log message, the function makes sure that Photon has read the entire message into the Photon event buffer. This can be determined by looking at the message length provided as the msglen to the input handler. If part of the message isn't in the event buffer, a message buffer is allocated and MsgRead() is called to get the whole message. The input_proc() function then calls add_msg() to add the message to the text widget and replies to the message.

When input_proc() is complete, it returns the value Pt_HALT. This instructs the Photon widget library not to remove the input handler.

Photon pulses

In addition to synchronous message-passing, Photon supports pulses. A process that wants to notify another process but doesn't want to wait for a reply can use a Photon pulse. For example, a server can use a pulse to contact a client process in a situation where sending a message would leave both SEND-blocked (and hence deadlocked).

A Photon pulse is identified by a negative PID that can be used as the pid argument to PtAppAddInput(). This PID is local to your application. If you want another process to send pulses to you, you must “arm” the pulse using PtPulseArm(). This creates a PtPulseMsg_t object that can be sent to the other process in a message. The other process will then be able to send pulses by calling MsgDeliverEvent() function.


Note: Under the QNX Neutrino OS version 6, PtPulseMsg_t is a sigevent structure. The bits in msg.sigev_value.sival_int that correspond to _NOTIFY_COND_MASK are clear, but can be set by the application that sends the pulse. For more information, see ionotify() in the QNX Neutrino Library Reference.

PtPulseArm() (described in the Photon Library Reference) simply takes a sigevent structure. PtPulseArmFd() and PtPulseArmPid() are for compatibility with earlier version of the QNX Neutrino OS and the Photon microGUI.


Let's look at the code you'll need to write to support pulses in a:

Photon application that receives a pulse

It's the recipient of a Photon pulse that has to do the most preparation. It has to:

  1. Create the pulse.
  2. Arm the pulse.
  3. Send the pulse message to the process that will deliver it.
  4. Register an input handler for the pulse message.
  5. Deliver the pulse to itself, if necessary.
  6. Destroy the pulse when it's no longer needed.

The sections below discuss each step, followed by an example.


Note: Before exiting, the recipient process should tell the delivering process to stop sending pulses.

Creating a pulse

To create a Photon pulse, call PtAppCreatePulse():

pid_t PtAppCreatePulse( PtAppContext_t app,
                        int priority );

The arguments are:

app
The address of the application context, a PtAppContext_t structure that manages all the data associated with this application. You should pass NULL for this argument, so that the default context is used.
priority
The priority of the pulse. If this is -1, the priority of the calling program is used.

PtAppCreatePulse() returns a pulse process ID, which is negative but never -1. This is the receiver's end of the pulse.

Arming a pulse

Arming the pulse fills in the sigevent structure, which can be used for most of the QNX Neutrino calls that take this type of argument.


Note: There's nothing wrong with having more than one process deliver the same pulse, although the recipient won't be able to tell which process sent it.

To arm a pulse, call PtPulseArm(). The prototype is:

int PtPulseArm( PtAppContext_t app,
                pid_t pulse,
                struct sigevent *msg );

The arguments are:

app
A pointer to the PtAppContext_t structure that defines the current application context (typically NULL).
pulse
The pulse created by PtAppCreatePulse().
msg
A pointer to a pulse message that this function creates. This is the deliverer's end of the pulse, and we'll need to send it to that process, as described below.

This function returns a pointer to a pulse message ID, which you'll need later.

Sending the pulse message to the deliverer

The method you use to send the pulse message depends on the process that will deliver the pulse. For example,

Registering an input handler

Registering an input handler for the pulse is similar to registering one for a message; see Adding an input handler earlier in this chapter. Pass the pulse ID returned by PtAppCreatePulse() as the pid parameter to PtAppAddInput().

The rcvid argument for the input handler won't necessarily have the same value as the pulse ID: it matches the pulse ID on the bits defined by _NOTIFY_DATA_MASK (see ionotify() in the QNX Neutrino Library Reference), but the other bits are taken from the Neutrino pulse that was received.

Delivering a pulse to yourself

If the application needs to send a pulse to itself, it can call PtAppPulseTrigger():

int PtAppPulseTrigger( PtAppContext_t app,
                       pid_t pulse );

The parameters for this function are the PtAppContext_t structure that defines the application context (typically NULL) and the pulse ID returned by PtAppCreatePulse().

Destroying a pulse

When your application no longer needs the pulse, it can be destroyed by calling PtAppDeletePulse():

int PtAppDeletePulse( PtAppContext_t app,
                      pid_t pulse_pid );

The parameters are the PtAppContext_t structure that defines the application context (typically NULL) and the pulse ID returned by PtAppCreatePulse().

Example — message queues

Here's an application that receives Photon pulses. It opens a message queue (/dev/mqueue/testqueue by default), sets up a pulse, and uses mq_notify() to give itself a pulse when there's something to read from the message queue:

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "abimport.h"
#include "proto.h"

mqd_t mqd = -1;
struct sigevent sigev;

static void readqueue( void ) {
    static unsigned counter;
    unsigned mprio;
    ssize_t msize;
    char mbuf[ 4096 ];
    while ( ( msize = mq_receive( mqd, mbuf, sizeof(mbuf),
                                  &mprio ) ) >= 0 ) {
        char hbuf[ 40 ];
        PtTextModifyText( ABW_mtext, 0, 0, -1, hbuf,
            sprintf( hbuf, "Msg #%u (prio %d):\n", ++counter,
                     mprio )
            );
        PtTextModifyText( ABW_mtext, 0, 0, -1, mbuf, msize );
        }
    if ( errno != EAGAIN )
        perror( "mq_receive" );
    }

static int input_fun( void *data, int rcvid, void *message,
                      size_t mbsize ) {
    
    if ( mq_notify( mqd, &sigev ) == -1 )
        perror( "mq_notify" );
    readqueue();
    return Pt_HALT;
    }

pid_t pulse;

/* Application Options string */
const char ApOptions[] =
    AB_OPTIONS ""; /* Add your options in the "" */


int init( int argc, char *argv[] ) {
    if  (   ( pulse = PtAppCreatePulse( NULL, -1 ) ) == 0
        ||  PtAppAddInput( NULL, pulse, input_fun, NULL ) ==
            NULL ) {
        fputs( "Initialization failed\n", stderr );
        exit( EXIT_FAILURE );
        }
    PtPulseArm( NULL, pulse, &sigev );
    /* eliminate 'unreferenced' warnings */
    argc = argc, argv = argv;
    return( Pt_CONTINUE );
    }

int open_queue( PtWidget_t *link_instance, ApInfo_t *apinfo,
                PtCallbackInfo_t *cbinfo ) {
    const char *name;
    PtArg_t arg;

    if ( mqd >= 0 )
        mq_close( mqd );

    PtSetArg( &arg, Pt_ARG_TEXT_STRING, &name, 0 );
    PtGetResources( ABW_qname, 1, &arg );
    if ( ( mqd = mq_open( name, O_RDONLY | O_CREAT | O_NONBLOCK,
                          S_IRUSR | S_IWUSR, NULL ) ) < 0 )
        perror( name );
    else
        if ( mq_notify( mqd, &sigev ) == -1 ) {
            perror( "mq_notify" );
            mq_close( mqd );
            mqd = -1;
            }
        else
            readqueue();

    /* eliminate 'unreferenced' warnings */
    link_instance = link_instance, apinfo = apinfo;
    cbinfo = cbinfo;

    return( Pt_CONTINUE );
    }

Photon application that delivers a pulse

A Photon application that's going to deliver a pulse must:

Processing signals

If your application needs to process signals, you'll need to set up a signal handler. The problem is that you can't call Photon functions from a signal handler because the widget library isn't signal-safe or reentrant.

To get around this problem, the Photon library includes a signal handler. You register a signal-processing function, and Photon calls it after


Caution: By handling signals in this way, you're not getting strict realtime performance, since your signal-processing function isn't called right away.

Adding a signal-processing function

To add a signal-processing function, use the PtAppAddSignalProc() function. You typically call this in

You'll need to include <signal.h>.

The syntax for PtAppAddSignalProc() is as follows:

int PtAppAddSignalProc( PtAppContext_t app,
                        sigset_t const *set,
                        PtSignalProc_t func,
                        void *data);

The arguments are as follows:

app
The address of the application context, a PtAppContext_t structure that manages all the data associated with this application. Specify NULL for this argument, so that the default context is used.
set
A pointer to the set of signals that should cause the signal-processing function to be called. Use the sigemptyset() and sigaddset() functions to build this set. See the QNX Neutrino Library Reference for more information.
func
The signal-processing function. See PtSignalProc_t in the Photon Library Reference.
data
Any data to be passed to the function.

PtAppAddSignalProc() returns 0 on success, or -1 if an error occurs.

Your signal-processing function has the following prototype:

int signalProcFunctions (int signum
                         void *data);

The arguments are:

signum
The number of the signal to be processed.
data
The data parameter specified in the call to PtAppAddSignalProc().

If you want the signal handler to remain installed, return Pt_CONTINUE. To remove it for the current signal, return Pt_END (if the function was registered for other signals, it's still called if they're raised).

Removing a signal-processing function

To remove a signal-processing function:

Other I/O mechanisms

If your application needs to perform I/O such as reading from or writing to a pipe, you should add an fd handler. An fd handler is a function that's called by the main event loop when a given file descriptor (fd) is ready for input or output:

These functions are described in the Photon Library Reference.


Note: If your fd handler changes the display, it should call PtFlush() to make sure the display is updated.