[Contents] [Prev. Chapter] [Next Section] [Next Chapter] [Index] [Help]

4 Integrating a Tool Into FUSE

This chapter describes how to integrate two tools into FUSE using the C interface:

Examples of the TIL files and integration source code are also included.


[Contents] [Prev. Chapter] [Next Section] [Next Chapter] [Index] [Help]

4.1 Integrating the minigrep Tool into FUSE

The minigrep program uses grep to locate a string entered by a user and then, in the FUSE Editor, displays each string at its location in its file. The source code uses #ifdef FUSE_INTEGRATION and #endif statements to surround the code required for FUSE integration with the standard minigrep code. This allows the tool to be built for either FUSE or non-FUSE use. Refer to Appendix A for a complete listing of the C source files for minigrep.

All source, object, and binary executable files for the example are available in the directory $FUSE_TOP/rundata/examples/EnCASE/minigrep. The $FUSE_TOP environment variable is defined to be the directory where you installed FUSE. If you installed FUSE in the default directory, the directory is /usr/opt/fuse.

Appendix A also lists a source file, TIL file, and makefile for an example of using the script interface to integrate dxdiff into FUSE. These files are in the directory $FUSE_TOP/rundata/examples/EnCASE/dxdiff.


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.1.1 Writing the Message Functions

Define the functions and messages that the tool is using for FUSE integration. At a minimum, you are likely to use the following:


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.1.1.1 Initializing the minigrep Tool in FUSE

Your tool must call the FUSE_init(void(*register_fct)()) function to initialize the minigrep tool in FUSE. This step is required. Use this function to declare the callback function that informs the tool of the socket on which FUSE messages are received, as well as the pointer to the function that processes the messages that appear on the socket.

If you are using the fusescript command interface, you do not need to initialize the tool.

If your tool changes states after starting, you should also inform the message server of the state change.

The following code fragments show the startup routine, initialization routine, and the state change routine for the minigrep example.

The startup routine is called doStartState; it calls the initialization routine and changes the state of the tool to running:

/*  void doStartState()
**       
**  initializes the minigrep application.       
*/       
       
void doStartState()
{
#ifdef FUSE_INTEGRATION
  int fuseInit;
#endif

  /*  initialize the value of the stdinFD */
  stdinFD = stdin->_file;

#ifdef FUSE_INTEGRATION
  /* initialize FUSE messaging environment */
  fuseInit = FUSE_init( fuseRegistration );
  if ( fuseInit != 0 ) {
    printf("\nFUSE_init failed.  Messaging is not possible.");
    changeState(stopping);
  }
#endif
  changeState(running);
}

The routine used to change states is called changeState:

/*  void changeState(int newState)
**
**  changes the value of the currentState global variable to the new state
**  and sends a FUSE_state_change message initiating the state change.
*/

void changeState(int newState)
{
  if ( newState == running ) {
    printf("\nAt the prompt, please enter a match expression (to be located)");
    printf("\n\and a file-expression (file(s) to be searched)...");
    printf("\nmatch expression <q to quit>: ");
  }
  else if ( newState == getFileExp ) {
    printf("file expression: ");
  }
  fflush(stdout);
  currentState = newState;
#ifdef FUSE_INTEGRATION
  FUSE_state_change(stateNames[newState]);
#endif
}

The routine that registers the tool in FUSE and declares the message-handling information is called fuseRegistration:

/*  void fuseRegistration( int socket_id, void (*fuseMsgHandler)()
)
**
**  This function is the function passed to the required call to FUSE_init.
**  The call to FUSE_init calls this function to associate the FUSE message
**  server to minigrep's incoming message-handling routine.
**
**  Applications  that are based on Motif or the X Toolkit need to call
**  XtAddInput() or XtAppAddInput() to register the socket_id in the
**  XT layer.
**
**  Since minigrep is NOT based on Motif or the X Toolkit, the socket_id
**  must be monitored (a file descriptor will be used for the
**  minigrep tool in the `select' system call) and fuseMsgHandler()
**  will be called when incoming data appears on the socket.
*/

#ifdef FUSE_INTEGRATION

void fuseRegistration( int socket_id, void (*fuseMsgHandler)() )
{
  toolFD = socket_id;
  toolMessageHandler = fuseMsgHandler;
}

#endif


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.1.1.2 Communicating with the Control Panel

You work with the Control Panel to send and receive messages about stopping the tool. These messages are part of the message_lib.msg file that you include in the TIL file. In the source code, you need to:

In the minigrep example, both messages are used. The ToolControlNotification message is sent in doStoppingState, as follows:

void doStoppingState()
{
#ifdef FUSE_INTEGRATION
  FUSE_SEND_ToolControlNotification("STOP");
#endif
}

The ToolControlCommand message is received as follows:

/*
**  void FUSE_RECV_ToolControlCommand(char *command, char *param)
**
**  This function implements the procedure to be executed when a
**  ToolControlCommand is sent to the minigrep tool.  At this time, the
**  only reason this message is sent is to request a tool to "STOP".
**  At this time, the `param' parameter is not used though its use is
**  reserved for possible later use.
**  The message, ToolControlCommand, is a message that all FUSE tools
**  are required to receive.  The message is specified in
**  $(FUSE_TOP)/rundata/schema/message_lib.msg.
**  A tool receives this message when the user has indicated from the
**  Control Panel (or other nonlocal source) that the tool should
**  be stopped.  The tool integrator is required to write the body of
**  this function in order to specify the behavior that should happen
**  when the tool needs to stop.  At the very least, the tool needs to
**  tell the Control Panel it is stopping (by a
**  ToolControlNotification("STOP") message) and then exiting the tool.
**  If desired, the tool can execute some cleanup procedure before
**  sending the "STOP" message.  The body of this function should execute
**  that cleanup behavior if it is desired.

#ifdef FUSE_INTEGRATION
void FUSE_RECV_ToolControlCommand(char *command, char *param)
{
  printf
  ("\n\nminigrep received ToolControlCommand(\"%s\", \"%s\")...",
  command, param);
  if ( !strcmp(command, "STOP") ) {
    printf("\nExiting...");
    fflush(stdout);
    FUSE_SEND_ToolControlNotification("STOP");
    sleep(3);
    exit();
  }
}
#endif


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.1.1.3 Specifying Interoperation with Other FUSE Tools

To make the tool work with other FUSE tools, you define functions for sending messages to FUSE tools and for processing messages sent by FUSE tools. Use the FUSE_SEND_message-name function to send messages and use the FUSE_RECV_message-name function to process messages. Any messages that you send and process must follow these rules:

In the minigrep example, the FUSE Editor is used to display the instances of the specified string. The minigrep tool needs to send two messages to the Editor: one to start the Editor and open the file, and another to go to a particular line number in the file. The minigrep tools uses the normalizeGrepOutput function to access the FUSE Editor.

/*
**  void normalizeGrepOutput(char* string)
**
**  If the (global variable) NumberOfFiles = 1,
**  write out the value of (global variable) SingleFile to outputString,
**  and concatenate ":" and value of string on the end.
**  Otherwise, use string unaltered.
**
**  When integrated with FUSE, step through the lines that were
**  found at a rate of 1 line every 5 seconds.
**
*/

void normalizeGrepOutput(char* string)
{
  char outputString[MAX_RETURN_LENGTH];
  char fileName[MAX_RETURN_LENGTH];
  char lineString[MAX_EXPRESSION_LENGTH];
  int lineNumber;
  char fullPathName[MAX_RETURN_LENGTH];
  void selectFD();

  if ( NumberOfFiles == 1 ) {
    sprintf(outputString, "%s:%s", SingleFile, string);
  }
  else {
    sprintf(outputString, "%s", string);
  }
  printf("%s", outputString);

#ifdef FUSE_INTEGRATION
  { /* This block gets the file name and line number from outputString */
    char c;
    int i,j;

    i = j = 0;
    while ( (c = outputString[i]) != `:' ) {
      fileName[j++] = outputString[i++];
    }
    fileName[j++] = `\0';
    i++;
    j = 0;
    while ( (c = outputString[i]) != `:' ) {
      lineString[j++] = outputString[i++];
    }
    lineString[j++] = NULL;
    lineNumber = atoi(lineString);
  }
  /* FUSE Editor needs full pathname so append working directory path
  ** unless the user entered a file expression that starts with a
  ** slash "/"
  */
  if (string[0] != `/') {
    getwd(fullPathName);
    strcat(fullPathName, "/");
    strcat(fullPathName, fileName);
    }
  else {
    strcpy(fullPathName, fileName);
   }

  /* If Editor doesn't have fullPathName in its buffer, get it.
  ** If Editor does have fullPathName in its buffer, this message is ignored.
  **    (Minimizing the number of these calls would be better, however.)
  */
   FUSE_SEND_EditorOpenFile(NULL, NULL, fullPathName);
   /* Goes to the given lineNumber in the buffer.  */
   FUSE_SEND_EditorGotoLine(NULL, NULL, "line", lineNumber);
   sleep(3);

#endif
}


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.1.1.4 Writing the Responses to Message Receipt

To receive messages, the tool must be listening for them. You need to write a function to listen for FUSE messages as well as any other messages the tool needs.

The minigrep example does not use Motif or the X Toolkit. It uses the selectFD function to monitor all necessary file descriptors for input. The selectFD function behaves like the XtMainLoop function, except that it looks only for available messages on the file descriptor you specify using the select system call. This event-loop type of programming is recommended for FUSE integration.

/*  void selectFD()
**
**  looks at the appropriate file descriptors to determine if input is
**  available.
*/
void selectFD()
{
  int numFilesReady;         /* the number of file descriptors ready to */
                             /* be processed.  Returned from call to */
                             /* select. */
  struct timeval timeout;    /* a timeval structure (defined in
                             ** <sys/time.h> used to timeout the select
                             ** after .5 seconds
                             */

  timeout.tv_sec = 0;
  timeout.tv_usec = 500;
/*
**  FD_SET (defined in types.h) sets the proper bit in the readFDS (file
**  descriptor set) so any system calls to select will know to look for
**  possible input on the file desriptors.  Since the args to select are
**  modified in place, re-initialize readFDS each time so the select looks
**  for input on both the toolFD and the stdinFD.
**
**  We always need stdinFD.  We need toolFD only if we're
**  integrating with FUSE and need to listen to its message server.
*/

/* initialize the file descriptor mask */
  FD_ZERO(&readFDS);

  FD_SET(stdinFD, &readFDS);

/*  Look for activity on the grepFD when it is open (i.e., >= 0) */
  if ( grepFD >= 0) {
    FD_SET(grepFD, &readFDS);
  }

#ifdef FUSE_INTEGRATION
  FD_SET(toolFD, &readFDS);
#endif

/* Use select system call to listen to the file descriptor(s).
** MAXSELFD is defined in types.h and specifies the maximum number of
** file desriptors that can exist.  We use this as the limit of file
** descriptors on which to look for input.
*/

  numFilesReady = select(MAXSELFD, &readFDS, 0, 0, &timeout);
  if ( numFilesReady < 0 ) {
    printf("\nSELECT returned with an error condition.  Shutting down.");
    changeState(stopping);
    return;
  }
}


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.1.2 Creating the minigrep Tool Til File

The TIL file declares all the FUSE interactions you use in your tool. The TIL file example for the minigrep program contains the following code elements:

/*
**  minigrep.til
**  The FUSE Tool Integration Language (til) file for describing how
**  the minigrep tool will integrate with FUSE via FUSE EnCASE
*/
/*  $Id: DOCU_005.HTM,v 1.1 1997/05/07 13:33:13 catano Exp $
 *
 * Copyright (c) 1994
 * Digital Equipment Corporation
 * All rights reserved.
 *
 * This software is furnished under a license and may be used and
 * copied  only  in accordance with the terms of such license and
 * with the  inclusion  of  the  above  copyright  notice.   This
 * software  or  any  other copies thereof may not be provided or
 * otherwise made available to any other person.  No title to and
 * ownership of the software is hereby transferred.
 *
 * The information in this software is subject to change  without
 * notice  and should not be construed as a commitment by Digital
 * Equipment Corporation.
 *
 * Digital assumes no responsibility for the use  or  reliability
 * of its software on equipment which is not supplied by Digital.
 *
*/
class MINIGREP = {
  Attributes {
      label = "Minigrep";
      accel = "Meta+G";
   /* run fuse_minigrep executable in a xterm (terminal emulator) */
      path = "$(FUSE_TERM) -xrm `*title:DEC FUSE:
       Minigrep' -xrm `*iconName: Minigrep' -e
       $(FUSE_TOP)/rundata/examples/EnCASE/minigrep/fuse_minigrep";
      multiple = false;
      server = false;
      language = "C";
      grouping = local;
      active = true;
      dialog {
          normal = show;
          auto = show;
          recall = show;
          request = show; 
     }
  }

/* The default set of FUSE messages needed by every FUSE tool is kept in
** $(FUSE_TOP)/rundata/schema/message_lib.msg.  This file defines the
** necessary prototypes for the minimal set of needed messages and defines
** 2 macros for use:
**  MESSAGE_LIB_SEND : the minimal set of messages to be sent
**  MESSAGE_LIB_RECV : the minimal set of messages to be received
** The TIL compiler knows where to find the file so the full path is not
** necessary.
*/
    Messages {
#include "message_lib.msg"

/* FUSE Editor messages that we may send */
/* NOTE: EditorOpenFile is a trigger for the editor, NOT for minigrep
*/
        char *EditorOpenFile(char *filename);
        char *EditorGotoLine(char *option, int lineNum);
    }
    States {
        start {
            sends {
                /* The required messages to be sent */
                       MESSAGE_LIB_SEND
            }
            receives {
                /* The required messages to be received */
                       MESSAGE_LIB_RECV
            }
        }
        running {
            sends {
                /* The required messages to be sent */
                       MESSAGE_LIB_SEND
            }
            receives {
                /* The required messages to be received */
                       MESSAGE_LIB_RECV
           }
        }
        getFileExp {
           sends {
               /* The required messages to be sent */
                      MESSAGE_LIB_SEND
           }
           receives {
               /* The required messages to be received */
                      MESSAGE_LIB_RECV
           }
        }
        processing {
           sends {
               /* The Editor message we want to send */
                      EditorOpenFile,
                      EditorGotoLine,
               /* The required messages to be sent */
                      MESSAGE_LIB_SEND
        }
           receives {
               /* The required messages to be received */
                      MESSAGE_LIB_RECV
           }
        }
        stopping {
           sends {
              /* The required messages to be sent */
                     MESSAGE_LIB_SEND
           }
           receives {
             /* The required messages to be received */
                     MESSAGE_LIB_RECV
           }
        }
    }
}


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.1.3 Building the minigrep Tool

To build a minigrep tool, write a makefile to compile the TIL file, link the files into FUSE, and compile the tool's source program. The makefile should include the following:

After you create the makefile, use the Builder to build your program.

The following makefile for minigrep shows how to include the FUSE library, link flags, use the TIL compiler, and use C compiler flags. To build the program, start FUSE and then start the Builder. On the makefile graph, select the node and click on the Start button in the Builder main window.

#  Makefile
#
#  for minigrep example for FUSE EnCASE
#

        # $Id: DOCU_005.HTM,v 1.1 1997/05/07 13:33:13 catano Exp $

# *
# * Copyright (c) 1994
# * Digital Equipment Corporation
# * All rights reserved.
# *
# * This software is furnished under a license and may be used and
# * copied  only  in accordance with the terms of such license and
# * with the  inclusion  of  the  above  copyright  notice.   This
# * software  or  any  other copies thereof may not be provided or
# * otherwise made available to any other person.  No title to and
# * ownership of the software is hereby transferred.
# *
# * The information in this software is subject to change  without
# * notice  and should not be construed as a commitment by Digital
# * Equipment Corporation.
# *
# * Digital assumes no responsibility for the use  or  reliability
# * of its software on equipment which is not supplied by Digital.
# *

# Library Name needed to integrate minigrep with FUSE:
# fuse : found in $FUSE_TOP/lib as libfuse.a (see LDFLAGS below)
#
LIBRARIES= -lfuse

# Linker
LD= /bin/ld

# Linker flags
# The place for FUSE libraries is /usr/kits/fuse/lib
LDFLAGS= -L$(FUSE_TOP)/lib

# Include search path
# The place for FUSE include files is /usr/kits/fuse/include
INCLUDES= -I$(FUSE_TOP)/include

# C compiler flags
CFLAGS= -g

# Source files
SRCS= minigrep.c FUSE_MINIGREP.c

# Object files
OBJS= FUSE_MINIGREP.o

################ targets below ##################

all : minigrep fuse_minigrep

minigrep : normal_minigrep.o
    $(CC) -o $@ normal_minigrep.o

fuse_minigrep : fuse_minigrep.o FUSE_MINIGREP.o
    cc -o $@ $(LDFLAGS) $(OBJS) $(LIBRARIES) fuse_minigrep.o

normal_minigrep.o : minigrep.c minigrep.h
    cc -c $(CFLAGS) $(OBJS) $(INCLUDES) -o normal_minigrep.o minigrep.c

fuse_minigrep.o : minigrep.c minigrep.h FUSE_MINIGREP.h
    cc -c $(CFLAGS) $(OBJS) $(INCLUDES) -DFUSE_INTEGRATION \
           -o fuse_minigrep.o minigrep.c

FUSE_MINIGREP.c FUSE_MINIGREP.h : minigrep.til
    til -cMINIGREP minigrep.til

clean :
    rm -f minigrep
    rm -f fuse_minigrep
    rm -f *.o
    rm -f FUSE*.*
    rm -f fuseschema.msl
    rm -f tools.rc

#
# til -install works by rebuilding tools.rc and fuseschema.msg in
# $FUSE_TOP/rundata/schema from the .til files that are already
# in $FUSE_TOP/rundata/schema.  Thus, til -install takes NO extra
# options (since it just reads all the .til files in
# $FUSE_TOP/rundata/schema).  To make this work, we need to copy
# the minigrep.til file in our working directory (from which make
# is being run) to $FUSE_TOP/rundata/schema.
#
install :
    make fuse_minigrep
    cp minigrep.til $FUSE_TOP/rundata/schema
    til -install

 .c.o:
    $(CC) -c $(CFLAGS) $(INCLUDES) $<


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.1.4 Debugging the Tool Integration

To debug the tool integration before replacing the startup script and schema currently being used by FUSE, start FUSE using the -D option and specify the local schema and startup script in the -schema and -rc options. For -schema, specify the absolute pathname of the schema file, fuseschema.msl, produced by the TIL compiler. For -rc, specify the absolute pathname of the startup script, tools.rc, produced by the TIL compiler.

For the minigrep example, there is a setup file setup.ksh to define an alias for starting FUSE with the minigrep tool:

#
# setup.ksh (Setup for the kornshell)
#
#    defines a fusetest alias (that brings up a fuse with the necessary
#            schema for minigrep) so you can test the minigrep application
#
#
alias fusetest='fuse -D -schema $FUSE_TOP/rundata/examples/EnCASE/ \
minigrep/fuseschema.msl -rc $FUSE_TOP/rundata/examples/EnCASE/ \
minigrep/tools.rc'
#
# end of file
#

If you specify a value of true for the active attribute in the TIL file, the tool is automatically listed in the FUSE configuration menus. If you specify a value of false for the active attribute, use the Modify Tools option from the Control Panel Tools menu to make the tool available for startup.

Use the FUSE tools to debug your tool integration, as follows:

  1. Enable debugging for your tool in the Makefile by specifing the debugging option in the compiler comand.

  2. Choose Debug FUSE Tools... from the Control Panel Tools menu and select your tool in the Tool Debugging dialog box.

  3. Select minigrep from the tools menu. This will start the debugger on minigrep.

  4. Start the Cross-Referencer and specify your tool's executable file as its target. Then, you can use the Functions query to check if messaging functions are properly defined for sending and receiving messages.

  5. Start the Message Monitor to view messages sent among tools. Messages to and from your tool are labeled with your tool class and the number of your tool. You can also filter messages to see only a particular type of message.

  6. Return to the debugger and start program execution.


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.2 Integrating a Tool with a Motif Interface into FUSE

This section describes the integration of a tool with a Motif interface (a Viewer tool) into FUSE. The Viewer performs a few basic functions through requests from the Control Panel:

Integrating the Viewer consists of the following steps:

  1. Create the Viewer TIL file.

  2. Write the integration code.

  3. Build the Viewer.

  4. Debug the Viewer.

  5. Install the Viewer.

The following sections describe the first three steps. General instructions for debugging and installing a tool are contained in Section 4.1.4 and Section 3.2.4 respectively.


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.2.1 Creating the Viewer Til File

Use the til command to compile the TIL file as explained in Section 3.2. Note that the-c option produces the .c and .h files for the Viewer's messaging functions. For example, enter the following command:

% til -c -noschema viewer.til

It creates the following files:

FUSE_VIEWER.h

Contains function prototypes and declarations for all the FUSE_SEND_message-name and FUSE_RECV_message-name routines defined in the TIL file.

FUSE_VIEWER.c

Contains functions definitions for all the FUSE_SEND_message-name routines defined in the TIL file. Function definitions for the FUSE_RECV_message-name routines are supplied by you, the tool integrator (see Section 4.2.2.3).

A TIL file, viewer.til, for the Viewer tool is shown below, with explanatory comments keyed to the callouts.

class VIEWER = {  1
    Attributes {  2
        label    = "Viewer"; 3
        accel    = "Meta+V"; 4
        path = "/usr/users/build/viewer"; 5
        multiple = true; 6
        server   = false; 7
        grouping = local; 8
        language = "C"; 9
        active   = true; 10
        dialog { 11
            normal  = hide;
            auto    = hide;
            request = hide;
            recall  = hide;
        };
    };
    Messages {  12
    #include "message_lib.msg" 13
        void ToolWorkingDataNotification(char *wd,char *tgtd, char *tgt, char *other);
        char *QuerySystemInformation(char *info);
        trigger char *viewFile(char *file);  14
        void ToolDisplayCommand(char *command, char *param); 15
        void ToolDisplayNotification(char *event); 16

    States {  17
        start   { 18
            sends {    MESSAGE_LIB_SEND
                       ToolWorkingDataNotification,
                       QuerySystemInformation,
                       ToolDisplayNotification,
            };
            receives { MESSAGE_LIB_RECV,
                       ToolDisplayCommand
            };
        };
        running { 19
            sends {    MESSAGE_LIB_SEND
                       ToolDisplayNotification,
            };
            receives { MESSAGE_LIB_RECV,
                       ToolDisplayCommand,
                       viewFile
            };
        };
    };
};

  1. class: The TIL file defines a class for the Viewer tool, with the attributes, messages, and states specified below.

  2. Attributes: Starts the Attributes clause.

  3. label: The tool name (Viewer) that appears on the Tools menu.

  4. accel: The keyboard accelerator for the Viewer.

  5. path: The location of the executable file. During developemnt, specify your local area. After testing the tool and prior to installing it into FUSE, specify the public area where all FUSE tools are located.

  6. multiple: true means that you can run multiple copies of the Viewer in the same tool group.

  7. server: false means that the Viewer is not a server; it has a user interface.

  8. grouping: local means that the Viewer will communicate only with tools in its own group.

  9. language: C means that the Viewer uses the C interface for messaging.

  10. active: true means that the Viewer is available and will appear on the Tools menu.

  11. dialog: hide, when specified for all four invocation stages (normal, auto, recall, and request), means that no configuration dialog box will be used.

  12. Messages: Starts the Messages clause, which specifies each message that the Viewer can send or receive in the form of a C function prototype.

  13. The default message library, message_lib.msg, must be included in the Messages clause. This message library includes prototypes for messages and macros called MESSAGE_LIB_SEND and MESSAGE_LIB_RECV that expand to include required message and state information.

  14. The user-defined viewFile message is defined as trigger. This means that, if the message is sent to the Viewer by another tool and the Viewer is not in the running state, the Viewer will be invoked to handle the message.

  15. The ToolDisplayCommand message changes the tool display (iconify, deiconify, and raise). The Control Panel sends this message.

  16. The ToolDisplayNotification message sends notification of a tool display change. The Control Panel receives this message.

  17. States: Starts the States clause, which specifies the states and the messages that can be sent and received while the Viewer is in each state. At a minimum, the MESSAGE_LIB_SEND and MESSAGE_LIB_RECEIVE macros must be listed for each state to enable the sending and receiving of the required FUSE messages defined in message_lib.msg.

  18. The messages for the start state, which is required, are specified. In this state, the Viewer can send the ToolWorkingDataNotification, QuerySystemInformation, and ToolDisplayNotification messages. It can receive the ToolDisplayCommand message.

  19. While the Viewer is in the running state, it can send the ToolDisplayNotification message and receive the ToolDisplayCommand and viewFile messages.


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.2.2 Writing the Viewer Integration Code

The key aspects of writing the Viewer integration code are as follows:

The following sections describe how these points are implemented in the Viewer integration code. The discussion is keyed to the Viewer'smain.c source file, which is in Section 4.2.2.4.


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.2.2.1 Initializing the Viewer in FUSE

The Viewer must register with FUSE by calling the FUSE_init routine. This routine sets up a socket for communication between FUSE and the Viewer. See the descriptions of callouts 2, 4, 5, and 6 in the example in Section 4.2.2.4.


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.2.2.2 Communicating with the Control Panel

In order to receive messages, the Viewer must tell the Control Panel what state it is in. The Viewer starts in the start state by default. To send and receive messages that are defined in the TIL file for the running state, the Viewer must issue the following call:

FUSE_state_change("running")

See callout 11 in the example in Section 4.2.2.4.


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.2.2.3 Sending and Receiving Messages

The function definitions for all FUSE_SEND_message-name routines that are specified in the TIL file are created automatically in FUSE_VIEWER.c by the TIL compiler (see Section 4.2.1). The parameters to the routines are determined by the message definitions (see Chapter 5).

To receive a message, you must write a routine called FUSE_RECV_message-name The function prototype is in FUSE_VIEWER.h. The parameters are determined by the particular message. When a message is received by the main loop, it is forwarded to the FUSE message handler, which in turn calls this routine. This routine does the actual work requested by the message, if any, or it calls a subroutine to do so. The routines that receive messages for the Viewer are defined at callouts 12, 14, and 18 in the example in Section 4.2.2.4.

At a minimum, the Viewer must receive a message to request that the Viewer be stopped (callout 12). The message name is ToolControlCommand with the parameter "STOP". The required routine is therefore FUSE_RECV_ToolControlCommand.

The Viewer must also notify FUSE when it stops by calling the routine FUSE_SEND_ToolControlNotification("STOP") (see callout 13). This routine should be called before each exit point in the program. For the Viewer, there are two exit points.

Some FUSE_SEND_message-name routines take a callback routine as parameter. After the message is processed by the FUSE message handler, FUSE calls the callback routine, which you supply. If you are not concerned with the reply, supply a null pointer. An example of a callback routine in the Viewer code is the working_data routine in main.c (see callouts 3 and 7). Note that the Viewer sets the tool state to running in the routine, after the current working directory has been set. This indicates to FUSE that the Viewer is ready to start processing requests.

You must provide a FUSE_RECV_message-name routine for each message that the Viewer receives. The Viewer receives ToolDisplayCommand messages to be iconified, deiconified, or raised from the Control Panel (callouts 14-17). Note that the Viewer informs FUSE of the display status by sending the ToolDisplayNotification message. This information could be used to change the font of the Viewer entry on the Tools menu accordingly.

The Viewer also receives a user-defined message, viewFile, that requests the Viewer to load a specified file (callout 18). This is not a FUSE message and is therefore not documented in Chapter 5. No tool currently sends this message. However, if you were to integrate another tool into FUSE, you could have that tool send this message to the Viewer.


[Contents] [Prev. Chapter] [Prev. Section] [Next Section] [Next Chapter] [Index] [Help]

4.2.2.4 Viewer Integration Source Code

The complete main.c source file for the Viewer integration code follows. Explanatory comments, keyed to the callouts, follow the source code.

#define MAIN_C_MODULE

#include "viewer_ui.h"
#include "viewer.h"
#include "FUSE_VIEWER.h" 1
extern void  load_callback ();
extern void  search_callback ();
extern void  history_callback ();
extern void  activate_callback ();
extern void  create_callback ();
extern void  file_select_callback ();
extern void  main_menu__callback ();              

static MRMRegisterArg   DRMnames[] = {
  {"create_callback",   (caddr_t) create_callback},
  {"activate_callback",   (caddr_t) activate_callback},
  {"load_callback",   (caddr_t) load_callback},
  {"search_callback",   (caddr_t) search_callback},
  {"history_callback",   (caddr_t) history_callback},
  {"file_select_callback",   (caddr_t) file_select_callback},
  {"main_menu_callback",   (caddr_t) main_menu_callback}
};

static int DRMnames_count = sizeof (DRMnames) / sizeof (MRMRegisterArg);

static  XtAppContext AppCont;  /* App Context needed for comm */
static  void register_socket(int socket, void (*server)());
static  void working_data();

extern int errno;
static char *uid_file_list [1];

/*-------------------------------------------------------------------------*
 * main - Main Viewer routine to initialize Viewer and start the main loop *
 *-------------------------------------------------------------------------*/
main (argc, argv)
    unsigned int        argc;                   /* command line
args count */
    char                *argv[];                /* pointers to arguments
  */
{
  char *uid_file;
  char *getenv();

  uid_file = (char *)XtMalloc(1024);
  sprintf(uid_file,"%s/viewer.uid",UID_FILE_LOC);

  uid_file_list[0] = uid_file;

  DXmInitialize ();
  MrmInitialize ();                              /* init MRM, before Xt */

  initialize_object_ids ();
#define CLASS "viewer"

  XtAppInitialize (&AppCont, CLASS,
      NULL, 0, &argc, argv, NULL, NULL, 0);

  display = XtOpenDisplay(AppCont, NULL, argv[0], CLASS, NULL, NULL,
     &argc, &argv);

  object_ids [UI_INDEX_TOP_LEVEL]  =
    XtAppCreateShell(argv[0],CLASS,
       applicationShellWidgetClass,
       display,NULL,0);

  initialize_viewer ();

  if (MrmOpenHierarchy (1, uid_file_list, NULL, &hierarchy) != MrmSUCCESS)
    {
      print_warning ("can't open hierarchy\n");
      return 0;
    };

  MrmRegisterNames (DRMnames, DRMnames_count);

  if (MrmFetchWidget (hierarchy, "main_object", object_ids[UI_INDEX_TOP_LEVEL],
        &object_ids [UI_INDEX_MAIN_OBJECT], &dummy_class)
      != MrmSUCCESS)
    {
      print_warning ("can't fetch main window\n");
      return 0;
    };

  FUSE_init(register_socket); 2

  FUSE_SEND_QuerySystemInformation(working_data, NULL, "WORKING_DATA"); 3

  XtManageChild (object_ids [UI_INDEX_MAIN_OBJECT]);

  XtRealizeWidget (object_ids [UI_INDEX_TOP_LEVEL]);

  XtAppMainLoop (AppCont);  4
}

/*----------------------------------------------------------------------*
 * register_socket - instruct XtAppMainLoop to monitor a socket         *
 *----------------------------------------------------------------------*/
static void register_socket(int s,void (*server)()) 5
{
  XtAppAddInput(AppCont,s,XtInputReadMask,server,NULL); 6
};

/*---------------------------------------------------------------------*
 * static void working_data(userdata, wd_file) -                       *
 *      Callback routine for FUSE_SEND_QuerySystemInformation. Opens   *
 *      the temp file specified by "wd_file", reads in the current     *
 *      working directory, and loads the name of the current working   *
 *      directory into the file entry in the Viewer.                   *
 *---------------------------------------------------------------------*/
static void working_data(userdata, wd_file) 7
     char *wd_file;
     char *userdata;
{
  char working_dir[4096],
  *ptr;
  int error;
  FILE *infile;

  working_dir[0] = `\0';
  wd_file +=2;
  if ((infile = fopen(wd_file,"r")) != NULL){
    fgets(working_dir,4095,infile);
    fclose(infile);
    for (ptr = working_dir; *ptr && *ptr != ` `; ptr++)
      ;
    *ptr = `\0';
  } else {
    error = errno;
    fprintf(stderr,"(Viewer) can't open temp file %s\n",wd_file);
    FUSE_SEND_ToolControlNotification("STOP"); 8
    exit(error);
  };

  XmTextSetString (object_ids [UI_INDEX_LOAD_ENTRY], working_dir); 9

  /* The working directory is set. Notify the Control Panel what it is, then
   * change the state to the running state to receive and send further
   * messages. */

  FUSE_SEND_ToolWorkingDataNotification(working_dir,working_dir,"*","*"); 10
  free(wd_file);
  FUSE_state_change("running"); 11
}
/************************************************************************
* FUSE-integration message handling routines:                           *
*       FUSE_RECV_ToolControlCommand                                    *
*       FUSE_RECV_ToolDisplayCommand                                    *
*       FUSE_RECV_viewFile                                              *
*************************************************************************/

/*----------------------------------------------------------------------*
 * void FUSE_RECV_ToolControlCommand(char *command, char *param) -      *
 *      Routine called by Viewer to receive the ToolControlCommand      *
 *      message.                                                        *
 *----------------------------------------------------------------------*/
void FUSE_RECV_ToolControlCommand(char *command, char *param) 12
{
  if ( !strcmp(command, "STOP") ) {
    FUSE_SEND_ToolControlNotification("STOP"); 13
    exit(0);
  }
}

/*----------------------------------------------------------------------*
 * void FUSE_RECV_ToolDisplayCommand(char *command, char *param) -      *
 *      Viewer receives the ToolDisplayCommand message from the Control *
 *      Panel.  The following values for "command" are handled:         *
 *          "ICONIFY" - iconify the Viewer                              *
 *          "DEICONIFY" - deiconify the Viewer                          *
 *          "RAISE" - raise the Viewer to the top of the window stack   *
 *      The "param" parameter is not used.                              *
 *----------------------------------------------------------------------*/
void FUSE_RECV_ToolDisplayCommand(char *command, char *param) 14
{
  XWindowAttributes attr;
  Window the_window = XtWindow( object_ids [UI_INDEX_TOP_LEVEL] );
  Display *display = XtDisplay( object_ids [UI_INDEX_TOP_LEVEL] );
    if (XGetWindowAttributes(display,the_window, &attr))
    {
      /* if request to iconify */
      if (!strcmp(command,"ICONIFY"))
        {
          if (attr.map_state != IsUnmapped)
            XIconifyWindow(display,the_window,0);
          FUSE_SEND_ToolDisplayNotification("ICONIFY"); 15
        }
      else if (!strcmp(command,"DEICONIFY"))

      /* request to deiconify */
        {
          if (attr.map_state == IsUnmapped)
            XMapWindow(display,the_window);
          FUSE_SEND_ToolDisplayNotification("DEICONIFY"); 16
        }
      else if (!strcmp(command,"RAISE"))
      /* request to raise */
        {
          XMapRaised(display,the_window);
          FUSE_SEND_ToolDisplayNotification("RAISE"); 17
        }
    }
}
/*----------------------------------------------------------------------*
 * void FUSE_RECV_viewFile(char *file,  int ID) -                       *
 *      receives the viewFile message from Control Panel and loads      *
 *      the file in the "file" parameter in to the Viewer               *
 *----------------------------------------------------------------------*/

void FUSE_RECV_viewFile(char *file,  int ID) 18
{
  XmTextSetString (object_ids [UI_INDEX_LOAD_ENTRY], file);
  load_file(file);
}

  1. FUSE_VIEWER.h, which is generated by the til compiler, contains function prototypes and declarations for all the FUSE_SEND_message-name and FUSE_RECV_message-name routines.

  2. The FUSE_init routine, which is required, creates the socket for FUSE to communicate with the Viewer. The parameter to FUSE_init is the name of the routine, written by the user, to set up monitoring the socket. FUSE_init calls this routine, passing it the socket and a pointer to the FUSE message handling routine.

  3. The FUSE_SEND_QuerySystemInformation routine specifies working_data as the callback routine (see item 7). It will be called with the working or default directory of the Viewer files. It looks at the file provided by FUSE, extracts the working directory, and stores it.

  4. The XtAppMainLoop function listens to FUSE messages as well as X messages.

  5. The socket-handling routine (register_socket) is written by the user. It enables the receipt of messages from the message server and associates the message server with the Viewer's message handling routine.

  6. Applications like the Viewer that use the Motif interface can instruct the main loop to monitor the socket using the XtAddInput or XtAppAddInput routine. When a message arrives on the socket, the main loop calls the message-handling routine (the second parameter to register_socket routine).

  7. The FUSE_SEND_QuerySystemInformation routine (see item 3) calls the working_data routine (a callback routine), which finds the current working directory and parses the reply for future use.

    Tools not using the Motif interface must monitor the socket themselves. When a message is received, it must be handed to the server routine provided by the FUSE_init routine.

  8. If the working_data routine is unable to parse the working directory, a message is sent to the Control Panel notifying it that the Viewer is stopping.

  9. The XmTextSetString routine sets the default value of the working directory when the Viewer opens a file.

  10. The FUSE_SEND_ToolWorkingDataNotification routine sends notification of the Viewer's working data to the Control Panel.

  11. The FUSE_state_change routine sends a message to the Control Panel to set the state to running. This will enable the Viewer to send and receive messages that were specified for the running state in the TIL file. To receive a trigger message, a tool must be in the running state.

  12. The Viewer receives a command from the Control Panel through the FUSE_RECV_ToolControlCommand message. If the command is "STOP", it is handled by the Viewer as described in item 13.

  13. If the command parameter value is "STOP", the Viewer notifies the Control Panel it is exiting and then exits.

  14. The Viewer receives the FUSE_RECV_ToolDisplayCommand message from the Control Panel. The Viewer handles the "ICONIFY", "DEICONIFY", and "RAISE" parameter values (see items 15, 16, and 17).

  15. This message notifies the Control Panel that the window has been iconified.

  16. This message notifies the Control Panel that the window has been deiconified.

  17. This message notifies the Control Panel that the window has been raised to the top of the window stack

  18. The viewFile message is a user-defined message that requests the Viewer to load the specified file. No FUSE tool currently uses this message.


[Contents] [Prev. Chapter] [Prev. Section] [Next Chapter] [Index] [Help]

4.2.3 Building the Viewer Tool

An example of the Makefile for the Viewer follows.

If a tool calls any fuse routines other than message- passing routines (which are defined in FUSE_<tool_ class>.h), the build must include fuse.h. For the Viewer, fuse.h is already included in FUSE_VIEWER.c.

Any modules in which the tool makes calls to FUSE message passing routines or FUSE_init must include FUSE_<tool_ class>.h. For the Viewer, all FUSE integration changes have been localized to main.c. Therefore, main.c includes FUSE_VIEWER.h.

Notice that the -g flag was added in line 63 for debugging purposes. Both til andtil -c commands (lines 115 and 109 respectively) are required to integrate the Viewer and compile it with the C files linked to the messaging functions.

For general instructions on debugging and installing a tool, see Section 4.1.4 and Section 3.2.4, respectively.

36   PROG = viewer
37   SCHEMA_CLASS = VIEWER
38   SYSTEM = OSF1_alpha
39
40 MOTIF_TOP = /usr/lib/X11
41
42 MOTIF_LIB_DIR = $(MOTIF_TOP)/lib
43
44 CLIENT_SRC = $(MOTIF_TOP)/include
45   UIL_SRC = $(CLIENT_SRC)/uil
46
47 SYS_INCLUDES = /usr/include
48
49 LIB_DIRS = -L$(FUSE_TOP)/lib -L$(MOTIF_LIB_DIR)
50 LIBRARIES = -lfuse -lMrm -lDXm -lXm -lXt -lX11 -lm
51   LDFLAGS = $(LIB_DIRS) $(LIBRARIES)
52
53
54   CLIENTSRC = /usr/bin/X11
55   UILSRC = $(CLIENTSRC)
56
57 INCLUDEDIR = $(TOP)/usr/lib/DXM/lib -I/usr/lib/DXM/lib/Xt
58 X11INCLUDES = /usr/include/X11
59 SYSINCLUDES = /usr/include
60
61   CC = cc
62   RM = rm -f
63 CDEBUGFLAGS = -g -DUID_FILE_LOC=\"
64   DEFINES = -D$(SYSTEM)
65   INCLUDES = -I. -I$(FUSE_TOP)/include
                -I$(INCLUDEDIR) -I$(X11INCLUDES) -I$(SYSINCLUDES)
66 # LOCALLIBS = -L$(FUSE_TOP)/lib-L$(LIBDIR)/DXm-lfuse -lDXm $(MRESOURCELIB)
                 $(XMLIB) $(XMTOOLLIB)
67   CFLAGS = $(CDEBUGFLAGS) $(INCLUDES) $(STD_DEFINES) $(DEFINES)
68 # SYSLIBS = $(X11LIB) -lm
69  UIL = $(UILSRC)/uil
70  DESTDIR =
71
72
73
74 .SUFFIXES: .uil .uid .h .uil .idx .til
75
76 SRCS = FUSE_$(SCHEMA_CLASS).c main.c $(PROG)_cb.c $(PROG).c utilities .c
77
78 OBJS = FUSE_$(SCHEMA_CLASS).o main.o $(PROG)_cb.o $(PROG).o utilities. o
79
80 C_HDRS = $(PROG).h
81
82 UIL_HDRS = $(PROG)_ui.uil
83
84 UILS = main.uil $(PROG).uil
85
86 #UIDS = main.uid $(PROG).uid
87 UIDS = viewer.uid
88
89 all:: $(PROG) $(UIDS)
90
91 .c.o:
92   $(RM) $@
93   $(CC) -c $(CFLAGS) $*.c
94
95 viewer.uid: main.uil $(PROG).uil $(PROG)_ui.uil
96   $(RM) $@
97   $(UIL) main.uil -o $*.uid -I/usr/include/X11/uil
98
99 $(PROG): $(OBJS)  100     $(RM) $@
101   $(CC) -o $@ $(OBJS) $(LDFLAGS)
102
103 clean:
104   $(RM) *~ core *.o $(PROG) *.uid FUSE* fuseschema.msl tools.rc
105
106
107 FUSE_$(SCHEMA_CLASS).c : $(PROG).til
108   $(RM) FUSE_$(SCHEMA_CLASS).*
109   til -c$(SCHEMA_CLASS) $(PROG).til
110
111
112 # ------ administrative targets -----
113
114 schema:
115          til


[Contents] [Prev. Chapter] [Prev. Section] [Next Chapter] [Index] [Help]