This chapter describes how to integrate two tools into FUSE using the C interface:
Section 4.1 describes the integration of the minigrep tool.
Section 4.2 describes the integration of a tool with a Motif interface (a Viewer tool).
Examples of the TIL files and integration source code are also included.
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
.
Define the functions and messages that the tool is using for FUSE integration. At a minimum, you are likely to use the following:
Initialize your tool in FUSE using the
FUSE_init
function.
This step is required.
Inform the message server of tool state changes
using the
FUSE_state_change
function.
Communicate with the Control Panel about tool start,
tool group changes, and tool stop.
Your tool must receive the
ToolControlCommand
message.
This step is required.
This is done for you by the code
generated by the TIL compiler.
Integrate your tool's operation with other FUSE
tools using the FUSE tool messages and the
FUSE_RECV
,
FUSE_SEND
,
FUSE_reply
, and
functions.FUSE_message_finish
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
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:
Send the
ToolControlNotification
message with the
FUSE_SEND_ToolControlNotification
function to inform the Control Panel that your tool is about to
stop.
Receive the
ToolControlCommand
message with the
FUSE_RECV_ToolControlCommand
function to receive a request from the Control Panel to stop the
tool.
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
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:
They must be declared in the tool's TIL file in the same state that the tool sends and receives the messages.
The parameters in the message must match the parameters defined for the message in the TIL file.
The message parameters must be one of the supported
data types:
float, int, char, char *
, or
long
.
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 }
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; } }
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:
The
path
attribute specifies that an Xterm window
should be started; then the
minigrep
tool is
executed in that window (with the
-e
option).
The default message library is included in the Messages section and the macros referencing the messages are declared in each state.
The messages for communicating with the FUSE Editor are declared in the Messages section and enabled only in the processing state.
The required start state is declared.
By convention,
a running state is declared even though
minigrep
does not receive trigger messages.
Other states are also declared
to define various stages of program execution.
/* ** 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 } } } }
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:
List the fuse
library needed to fully integrate messaging functions in LIBRARIES.
This library is located in
$FUSE_TOP/lib
as
libfuse.a
.
Specify the pathname for the fuse library,
$FUSE_TOP
/lib
, in
LDFLAGS
.
Remove all previous binary and object files to ensure a new build in the clean statements.
Compile the TIL file for the tool using the til command.
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) $<
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:
Enable debugging for your tool in the Makefile by specifing the debugging option in the compiler comand.
Choose Debug FUSE Tools... from the Control Panel Tools menu and select your tool in the Tool Debugging dialog box.
Select minigrep
from the tools menu.
This will start
the debugger on minigrep.
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.
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.
Return to the debugger and start program execution.
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:
You can iconify and deiconify the Viewer, and raise its window above the window stack on the screen.
By using a user-defined message,
viewFile
,
you can display a file for read-only access with the Viewer.
You can stop (exit) the Viewer.
Integrating the Viewer consists of the following steps:
Create the Viewer TIL file.
Write the integration code.
Build the Viewer.
Debug the Viewer.
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.
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:
|
Contains function prototypes and declarations
for all the
|
|
Contains functions definitions for all
the
|
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 }; }; }; };
class
: The TIL file
defines a class for the Viewer tool, with the attributes, messages,
and states specified below.
Attributes
: Starts the Attributes
clause.
label
: The tool name (Viewer)
that appears on the Tools menu.
accel
: The keyboard accelerator
for the Viewer.
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.
multiple
:
true
means that you can run multiple copies of the Viewer in the same
tool group.
server
:
false
means that the Viewer is not a server; it has a user interface.
grouping
:
local
means that the Viewer will communicate only with tools in its own
group.
language
:
C
means that the Viewer uses the C interface for messaging.
active
:
true
means that the Viewer is available and will appear on the Tools
menu.
dialog
:
hide
,
when specified for all four invocation stages (normal,
auto, recall
, and
request
), means that
no configuration dialog box will be used.
Messages
: Starts the
Messages
clause, which specifies each message that the Viewer can send or
receive in the form of a C function prototype.
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.
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.
The
ToolDisplayCommand
message
changes the tool display (iconify, deiconify, and raise).
The Control
Panel sends this message.
The
ToolDisplayNotification
message
sends notification of a tool display change.
The Control Panel receives
this message.
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
.
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.
While the Viewer is in the
running
state, it can send the
ToolDisplayNotification
message and receive the
ToolDisplayCommand
and
viewFile
messages.
The key aspects of writing the Viewer integration code are as follows:
Initialize the Viewer in
FUSE with the
FUSE_init
routine and monitor the
FUSE communication socket (register_socket
) for
messages.
Add change-of-state calls as necessary after the start state so that the Viewer can communicate with the Control Panel.
Write
FUSE_RECV_message-name
routines to handle incoming messages.
Add
FUSE_SEND_message-name
calls
to send messages to notify FUSE of actions taken and request services
of other tools.
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.
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.
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.
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.
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); }
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.
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.
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.
The
XtAppMainLoop
function listens
to FUSE messages as well as X messages.
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.
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).
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.
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.
The
XmTextSetString
routine
sets the default value of the working directory when the Viewer
opens a file.
The
FUSE_SEND_ToolWorkingDataNotification
routine sends notification of the Viewer's working data to the Control
Panel.
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.
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.
If the
command
parameter value
is "STOP
", the Viewer notifies the Control Panel
it is exiting and then exits.
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).
This message notifies the Control Panel that the window has been iconified.
This message notifies the Control Panel that the window has been deiconified.
This message notifies the Control Panel that the window has been raised to the top of the window stack
The
viewFile
message is a user-defined
message that requests the Viewer to load the specified file.
No
FUSE tool currently uses this message.
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