At this point we can start to build some real Motif applications. These are all going to be fairly simple, but what I'd like to demonstrate is that the knowledge you now have is sufficient to create useful applications. Large involved applications can be found aplenty in Young's book. I will also demonstrate several more widgets in these applications.
This first application is called "mkill". It is extremely simple, and also quite useful, at least for me. When it is executed, it sits on the screen in a small window displaying a single button labeled "Push to kill". When you push the button, the program forks and starts up the "ps" command. It reads the output of "ps" through a pipe, and places it in a selection box. You then click on the item that you wish to kill, and it is killed.
I created this application to solve a simple problem: I HATE to have to type ps, then wait, then look up the process number and then kill that process number. This solves the problem. You still have to wait some, for ps to do it's thing, but then you just point and click.
The connection to ps is made simple here by using a library called "link". This library handles the forking and pipe set-up. I'll include the source for it at the bottom of the file. It is very short and simple.
To try mkill (not to be confused with xkill, a separate program), type in the following code (and the link library) and compile:
#include <X11/Intrinsic.h> #include <Xm/Xm.h> #include <Xm/PushB.h> #include <Xm/SelectioB.h> #include "link.h" #define OK 1 #define CANCEL 2 Widget toplevel, dialog; Boolean first_time=True; void dialogCB(Widget w, int client_data, XmAnyCallbackStruct *call_data) { char *procstr,s[100],num[100]; XmSelectionBoxCallbackStruct *selection; switch (client_data) { case OK: selection=(XmSelectionBoxCallbackStruct *) call_data; XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &procstr); /* extract the process number from the line and kill that process number. */ strncpy(num,procstr,5); strcpy(s,"kill -9 "); strcat(s,num); system(s); XtFree(selection); break; case CANCEL: break; } XtUnmanageChild(w); } void buttonCB(Widget w, caddr_t client_data, XmAnyCallbackStruct *call_data) { Arg al[10]; int ac; XmString list[100]; int list_cnt; struct link_handle l; char s[200]; /* In the version of Motif I use, there is a bug that causes the selection box's list to be unchangeable once it has been loaded. This means that you can load it the first time, but after that the list cannot be changed to anything else. The only way I have found to get around the problem is to destroy the widget each time, recreate it, and then reload it with the new list. You might try it without recreating the widget each time and see if it works for you. If it does, then you should create the widget once in the main program instead of every time, and you can also remove this destruction code.*/ if (first_time) first_time=False; else XtDestroyWidget(dialog); /* recreate the selection box dialog */ ac = 0; XtSetArg(al[ac],XmNautoUnmanage,False); ac++; XtSetArg(al[ac],XmNmustMatch,True); ac++; XtSetArg(al[ac], XmNselectionLabelString, XmStringCreateLtoR ("Pick a process to kill.", XmSTRING_DEFAULT_CHARSET)); ac++; dialog = XmCreateSelectionDialog(toplevel, "dialog", al, ac); XtAddCallback (dialog, XmNokCallback, dialogCB, OK); XtAddCallback (dialog, XmNcancelCallback, dialogCB, CANCEL); XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON)); /* establish a link to ps with the link library, and place the strings from it into the selection box. */ link_open(&l,"ps","-g"); link_read(&l,s); list_cnt=0; while (!link_read(&l,s)) { s[strlen(s)-1]='\0'; /* get rid of the '\n' */ list[list_cnt++]= XmStringCreateLtoR(s, XmSTRING_DEFAULT_CHARSET); } link_close(&l); /* add the list to the selection box */ ac=0; XtSetArg(al[ac], XmNlistItems, list); ac++; XtSetArg(al[ac],XmNlistItemCount, list_cnt); ac++; XtSetValues(dialog, al, ac); XtManageChild(dialog); } main(int argc, char *argv[]) { Widget button; Arg al[10]; int ac; toplevel=XtInitialize(argv[0],"",NULL,0,&argc,argv); /* create the "push to kill" button */ ac=0; XtSetArg(al[ac],XmNlabelString, XmStringCreate("Push to kill", XmSTRING_DEFAULT_CHARSET)); ac++; button=XtCreateManagedWidget("label",xmPushButtonWidgetClass, toplevel,al,ac); XtAddCallback (button, XmNactivateCallback, buttonCB, NULL); XtRealizeWidget(toplevel); XtMainLoop(); }
If you look at this code, you will see that it is exactly the same code as that used in the selection box demo in tutorial 6. Two things have been added. First, in the buttonCB routine, the link library is used to create a pipe to ps and to read the output of ps. This output is then given to the selection box. Then in dialogCB, the selected string is read out of call_data, the process number is extracted, and a system call is made to kill the process.
If you have problems when you try to run this, see if the command "ps g" works at the unix command line. You may want to change the "g" parameter to something else, or even make it just "" to solve the problem.
The link library header file is given below:
/* Link module, v1.0, 5/4/90 Marshall Brain */ /* This module allows a program to form links to other seperately executing programs and communicate with them. Links can be opened and closed, and the program using this unit can write to and read from the other program over the link. */ /* This module will not work with all programs. If the program does anything weird with stdout, or if it fails to flush stdout correctly, then this module will fail. If you are creating a stand-alone program that you wish to link to another program with this library, then you MUST make sure that stdout is flushed correctly. Either call "fflush(stdout)" after every printf, or call "setbuf(stdout,NULL)" at the beginning of the program to eliminate buffering. */ #include <stdio.h> #include <strings.h> #include <signal.h> struct link_handle { int pipefd1[2],pipefd2[2]; int pid; FILE *fpin,*fpout; }; extern link_open(struct link_handle *l, char name[], char param[]); /* open a link to another program named name, passing a param to the program if desired. This routine will execute name in parallel and you can start communicating with it with link_read and link_write.*/ extern link_close(struct link_handle *l); /* Close the link to a program that has terminated. Use link_kill if the program needs to be terminated as well.*/ extern int link_read(struct link_handle *l,char s[]); /* read from the program started with link_open.*/ extern link_write_char(struct link_handle *l,char c); /* write a char, without a newline, to the program.*/ extern link_write(struct link_handle *l,char s[]); /* write a string to the program, with a newline.*/ extern link_kill(struct link_handle *l); /*kill the program and close the link. If the program has terminated on its own use link_close instead.*/
The link library code file is given below:
/* Link module, v1.0, 5/4/90 Marshall Brain */ #include "link.h" link_open(struct link_handle *l, char name[],char param[]) { pipe(l->pipefd1); pipe(l->pipefd2); if((l->pid=fork())==0) /*child*/ { close(l->pipefd1[0]); close(1); dup(l->pipefd1[1]); close(2); /*2 new lines*/ dup(l->pipefd1[1]); close(l->pipefd2[1]); close(0); dup(l->pipefd2[0]); execlp(name,name,param,(char*)0); } else { l->fpin=fdopen(l->pipefd1[0],"r"); l->fpout=fdopen(l->pipefd2[1],"w"); close(l->pipefd1[1]); close(l->pipefd2[0]); } } link_close(struct link_handle *l) { wait((union wait*)0); close(l->pipefd1[1]); close(l->pipefd2[0]); fclose(l->fpin); fclose(l->fpout); l->pid=0; } int link_read(struct link_handle *l,char s[]) { int eof_flag; if (fgets(s,100,l->fpin)==NULL) eof_flag=1; else eof_flag=0; return(eof_flag); } link_write_char(struct link_handle *l,char c) { fprintf(l->fpout,"%c",c); fflush(l->fpout); } link_write(struct link_handle *l,char s[]) { fprintf(l->fpout,"%s\n",s); fflush(l->fpout); } link_kill(struct link_handle *l) { kill(l->pid,SIGKILL); link_close(l); }