21.3 An Example
The signal handling problem can also be solved with a timer, using the same approach as with a work procedure. the
source code demonstrates the use of a timer in a more realistic application. The program displays an array of
DrawnButtons that start application programs. While an application is running, the associated button is insensitive, so
that the user can only run one instance of the application. When the application exits, the button is reactivated, so that
the user can select it again. XtSetLanguageProc() is only available in X11R5; there is no corresponding
function in X11R4.
/* app_box.c −− make an array of DrawnButtons that, when activated,
* executes a program. When the program is running, the drawn button
* associated with the program is insensitive. When the program dies,
* reactivate the button so the user can select it again.
*/
#include <Xm/DrawnB.h>
#include <Xm/RowColumn.h>
#include <signal.h>
#ifndef SYSV
#include <sys/wait.h>
#else
#define SIGCHLD SIGCLD
#endif /* SYSV */
#define MAIL_PROG "/bin/mail"
typedef struct {
Widget drawn_w;
char *pixmap_file;
char *exec_argv[6]; /* 6 is arbitrary, but big enough */
int pid;
} ExecItem;
ExecItem prog_list[] = {
{ NULL, "terminal", { "xterm", NULL }, 0 },
{ NULL, "flagup", { "xterm", "−e", MAIL_PROG, NULL }, 0 },
{ NULL, "calculator", { "xcalc", NULL }, 0 },
{ NULL, "xlogo64", { "foo", NULL }, 0 },
};
XtAppContext app; /* application context for the whole program */
GC gc; /* used to render pixmaps in the widgets */
void reset(), reset_btn(), redraw_button(), exec_prog();
main(argc, argv)
int argc;
char *argv[];
{
Widget toplevel, rowcol;
Pixmap pixmap;
Pixel fg, bg;
int i;
/* we want to be notified when child programs die */
signal (SIGCHLD, reset);
XtSetLanguageProc (NULL, NULL, NULL);
toplevel = XtVaAppInitialize (&app, "Demos",
21 Signal Handling 21.3 An Example
592
NULL, 0, &argc, argv, NULL, NULL);
rowcol = XtVaCreateWidget ("rowcol",
xmRowColumnWidgetClass, toplevel,
XmNorientation, XmHORIZONTAL,
NULL);
/* get the foreground and background colors of the rowcol
* so the gc (DrawnButtons) will use them to render pixmaps.
*/
XtVaGetValues (rowcol,
XmNforeground, &fg,
XmNbackground, &bg,
NULL);
gc = XCreateGC (XtDisplay (rowcol),
RootWindowOfScreen (XtScreen (rowcol)), NULL, 0);
XSetForeground (XtDisplay (rowcol), gc, fg);
XSetBackground (XtDisplay (rowcol), gc, bg);
for (i = 0; i < XtNumber (prog_list); i++) {
/* the pixmap is taken from the name given in the structure */
pixmap = XmGetPixmap (XtScreen (rowcol),
prog_list[i].pixmap_file, fg, bg);
/* Create a drawn button 64x64 (arbitrary, but sufficient)
* shadowType has no effect till pushButtonEnabled is false.
*/
prog_list[i].drawn_w = XtVaCreateManagedWidget ("dbutton",
xmDrawnButtonWidgetClass, rowcol,
XmNwidth, 64,
XmNheight, 64,
XmNpushButtonEnabled, True,
XmNshadowType, XmSHADOW_ETCHED_OUT,
NULL);
/* if this button is selected, execute the program */
XtAddCallback (prog_list[i].drawn_w,
XmNactivateCallback, exec_prog, &prog_list[i]);
/* when the resize and expose events come, redraw pixmap */
XtAddCallback (prog_list[i].drawn_w,
XmNexposeCallback, redraw_button, pixmap);
XtAddCallback (prog_list[i].drawn_w,
XmNresizeCallback, redraw_button, pixmap);
}
XtManageChild (rowcol);
XtRealizeWidget (toplevel);
XtAppMainLoop (app);
}
/* redraw_button() −− draws the pixmap into its DrawnButton
* using the global GC. Get the width and height of the pixmap
* being used so we can either center it in the button or clip it.
*/
void
redraw_button(button, client_data, call_data)
Widget button;
XtPointer client_data;
XtPointer call_data;
{
Pixmap pixmap = (Pixmap) client_data;
21 Signal Handling 21.3 An Example
593
XmDrawnButtonCallbackStruct *cbs =
(XmDrawnButtonCallbackStruct *) call_data;
int srcx, srcy, destx, desty, pix_w, pix_h;
int drawsize, border;
Dimension bdr_w, w_width, w_height;
short hlthick, shthick;
Window root;
/* get width and height of the pixmap. don't use srcx and root */
XGetGeometry (XtDisplay (button), pixmap, &root, &srcx, &srcx,
&pix_w, &pix_h, &srcx, &srcx);
/* get the values of all the resources that affect the entire
* geometry of the button.
*/
XtVaGetValues (button,
XmNwidth, &w_width,
XmNheight, &w_height,
XmNborderWidth, &bdr_w,
XmNhighlightThickness, &hlthick,
XmNshadowThickness, &shthick,
NULL);
/* calculate available drawing area, width 1st */
border = bdr_w + hlthick + shthick;
/* if window is bigger than pixmap, center it; else clip pixmap */
drawsize = w_width − 2 * border;
if (drawsize > pix_w) {
srcx = 0;
destx = (drawsize − pix_w) / 2 + border;
}
else {
srcx = (pix_w − drawsize) / 2;
pix_w = drawsize;
destx = border;
}
drawsize = w_height − 2 * border;
if (drawsize > pix_h) {
srcy = 0;
desty = (drawsize − pix_h) / 2 + border;
}
else {
srcy = (pix_h − drawsize) / 2;
pix_h = drawsize;
desty = border;
}
XCopyArea (XtDisplay (button), pixmap, cbs−>window, gc,
srcx, srcy, pix_w, pix_h, destx, desty);
}
/* exec_proc() −− the button has been pressed; fork() and call
* execvp() to start up the program. If the fork or the execvp
* fails (program not found?), the sigchld catcher will get it
* and clean up. If the program is successful, set the button's
* sensitivity to False (to prevent the user from execing again)
* and set pushButtonEnabled to False to allow shadowType to work.
*/
void
21 Signal Handling 21.3 An Example
594
exec_prog(drawn_w, client_data, call_data)
Widget drawn_w;
XtPointer client_data;
XtPointer call_data;
{
ExecItem *program = (ExecItem *) client_data;
XmDrawnButtonCallbackStruct *cbs =
(XmDrawnButtonCallbackStruct *) call_data;
switch (program−>pid = fork ()) {
case 0: /* child */
execvp (program−>exec_argv[0], program−>exec_argv);
perror (program−>exec_argv[0]); /* command not found? */
_exit (255);
case −1:
printf ("fork() failed.0);
}
/* The child is off executing program... parent continues */
if (program−>pid > 0) {
XtVaSetValues (drawn_w,
XmNpushButtonEnabled, False,
NULL);
XtSetSensitive (drawn_w, False);
}
}
/* reset() −− a program died, so find out which one it was and
* reset its corresponding DrawnButton widget so it can be reselected
*/
void
reset()
{
int pid, i;
#ifdef SYSV
int status;
#else
union wait status;
#endif /* SYSV */
if ((pid = wait (&status)) == −1)
/* an error of some kind (fork probably failed); ignore it */
return;
for (i = 0; i < XtNumber (prog_list); i++)
if (prog_list[i].pid == pid) {
/* program died −− now reset item. But not here! */
XtAppAddTimeOut (app, 0, reset_btn, prog_list[i].drawn_w);
return;
}
printf ("Pid #%d ???0, pid); /* error, but not fatal */
}
/* reset_btn() −− reset the sensitivity and pushButtonEnabled resources
* on the drawn button. This cannot be done within the signal
* handler or we might step on an X protocol packet since signals are
* asynchronous. This function is safe because it's called from a timer.
*/
void
reset_btn(drawn_w)
21 Signal Handling 21.3 An Example
595
Widget drawn_w; /* client_data from XtAppAddTimeOut() */
{
XtVaSetValues(drawn_w,
XmNpushButtonEnabled, True,
NULL);
XtSetSensitive (drawn_w, True);
}
The output of the program is shown in the figure.
Output of app_box.c
The program in the source code is almost identical in design to the code fragment that used a work procedure, but it is
more like something you might actually write. The program uses DrawnButtons to represent different application
programs. The idea is that when a button is pressed, the program corresponding to the image drawn on the button is
run. The button turns insensitive for as long as the application is alive. When the user exits the program, the button's
state is restored so the user can select it again.
Each button has a data structure associated with it that specifies the file that contains the icon bitmap, an argv that
represents the program to be run, the process ID associated with the program's execution, and a handle to the button
itself. The callback routine for each button spawns a new process, sets the button to insensitive, and immediately
returns control to the main event loop. The process ID is saved in the button's data structure. When the external
process terminates, a SIGCHLD signal is sent to the main program and the button is reset.
As a general note, it is crucial that you understand that the new process does not attempt to interact with the widgets in
its parent application or read events associated with the same display connection as its parent process. Even though the
child has access to the same data structures as the parent, it cannot use its parent's connection to the X server because
multiple processes cannot share an X server connection. If a child process intends to interact with the X server, it must
close its existing connection and open a new one.
In our application, we play it safe by running a completely new application using execvp(). This system call
executes a program provided it can be found in the user's PATH, so we don't need to specify full pathnames to the
applications. If the program cannot be found for whatever reason, the child process dies immediately and the
reset() signal handler is called by the operating system.
The reset() signal handler is called whenever a child process dies. At this point, the child needs to be reaped and
the state of the button needs to be reset. The wait() system call is used to reap the child; this routine can be called
from within reset() because it doesn't make any Xlib calls. However, we cannot reset the button's state by calling
XtVaSetValues() and XtSetSensitive() because these routines would ultimately result in Xlib calls.
Therefore, rather than actually resetting the button in reset(), we call XtAppAddTimeOut() to install a timer
routine. This Xt call is safe in a signal handler because it does not make any calls to Xlib; the timer is handled entirely
on the client side.
21 Signal Handling 21.3 An Example
596
Get Volume 6A: Motif Programming Manual now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.