/* * tkFvwm.c -- * * This file implements the "fvwm" command, which permits the * comunication between the fvwm window manager and modules * written in Tcl. * * Copyright(c) 1995 Andres Aravena (andres@aravena.mic.cl) * Portions Copyright(c) 1988-1994 The Regents of the University of California. * Portions Copyright(c) 1994 Sun Microsystems, Inc. * This file can be redistributed under the same terms of the Tcl/Tk core. * */ /* Updated for tk4.1, fvwm2 (and fvwm95) by M. Crimmins 1/96. * Various enhancements, bugfixes 2/96, 6/96 * * Athr: Michael D. Beynon (mdb) * Date: 04/18/1996 : mdb - Converted Tk_*() calls that were moved to * Tcl_*() calls. A Tcl_File type is now needed * for the Tcl_CreateFileHandler() call. */ static char version[] = "tkFvwm.c version 4.1"; #include "tcl.h" #include "tk.h" #include "fvwmlib.h" /* from fvwm source */ #include "module.h" /* from fvwm source */ /* * These arrays contain the message types from fvwm. Unfortunately, * they change from time to time. Up to date as of fvwm-2.0.43. */ static char * fvwmName[] = {"DeadPipe", "NewPage", "NewDesk", "AddWindow", "RaiseWindow", "LowerWindow", "ConfigureWindow", "FocusChange", "DestroyWindow", "Iconify", "Deiconify", "WindowName", "IconName", "ResClass", "ResName", "EndWindowlist", "IconLocation", "Map", "Error", "ConfigInfo", "EndConfigInfo", "IconFile", "DefaultIcon", "String", "MiniIcon", "WindowShade", "DeWindowShade"}; static unsigned long fvwmCode[] = {0, M_NEW_PAGE, M_NEW_DESK, M_ADD_WINDOW, M_RAISE_WINDOW, M_LOWER_WINDOW, M_CONFIGURE_WINDOW, M_FOCUS_CHANGE, M_DESTROY_WINDOW, M_ICONIFY, M_DEICONIFY, M_WINDOW_NAME, M_ICON_NAME, M_RES_CLASS, M_RES_NAME, M_END_WINDOWLIST, M_ICON_LOCATION, M_MAP, M_ERROR, M_CONFIG_INFO, M_END_CONFIG_INFO, M_ICON_FILE, M_DEFAULTICON, M_STRING, M_MINI_ICON, M_WINDOWSHADE, M_DEWINDOWSHADE}; static char *fvwmScript[MAX_MESSAGES+1] = {"exit", NULL}; static Tcl_Interp *fvwmInterp; static int fvwmFD[2] = {-1,-1}; /* * These macros give access to the interface data, making easy to switch * to other data structures, if needed. */ #define FD fvwmFD #define SCRIPT(I) fvwmScript[I] #define CODE(I) fvwmCode[I] #define NAME(I) fvwmName[I] #define CHECK(X) if(X==TCL_OK) {} else {return TCL_ERROR;} /* *-------------------------------------------------------------- * * DeadPipe -- * * The following function is requiered by ReadFvwmPacket * It's called whenever the pipe is closed, as when FVWM dies. * * Results: The script in SCRIPT(0) is evaluated in the context of * fvwmInterp. Note that no expansion of percents is realized, * because DeadPipe is not bound to any FVWM message. * * Side effects: * If not changed by the user, DeadPipe evaluates "exit", so * the interpreter is finished. * *-------------------------------------------------------------- */ void DeadPipe(int dummy) { int ans; Tcl_DeleteFileHandler(FD[1]); /* Tcl_FreeFile(Tcl_GetFile((ClientData)FD[1], TCL_UNIX_FD)); */ close(FD[0]); close(FD[1]); ans=Tcl_GlobalEval(fvwmInterp,SCRIPT(0)); if(ans==TCL_ERROR) { Tcl_AddErrorInfo(fvwmInterp, "\n (command bound to fvwm)"); Tcl_BackgroundError(fvwmInterp); } } /* *-------------------------------------------------------------- * * ExpandPercents -- * * Given a command and a FVWM Packet, produce a new command * by replacing % constructs in the original command * with information from the FVWM packet. * * This is a modification of the analog function in tkBind.c, * which has the following copyright: * * Copyright (c) 1989-1994 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * * Results: * The new expanded command is appended to the dynamic string * given by dsPtr. * * Side effects: * None. * *-------------------------------------------------------------- */ static void ExpandPercents(before, body, dsPtr) register char *before; /* Command containing percent * expressions to be replaced. */ unsigned long *body; /* FVWM data packet */ Tcl_DString *dsPtr; /* Dynamic string in which to append * new command. */ { int spaceNeeded, cvtFlags; /* Used to substitute string as proper Tcl * list element. */ unsigned long number; int length; #define NUM_SIZE 40 register char *string; char numStorage[NUM_SIZE+1]; while (1) { /* * Find everything up to the next % character and append it * to the result string. */ for (string = before; (*string != 0) && (*string != '%'); string++) { /* Empty loop body. */ } if (string != before) { Tcl_DStringAppend(dsPtr, before, string-before); before = string; } if (*before == 0) { break; } /* * There's a percent sequence here. Process it. */ number = 0; string = "??"; switch (before[1]) { case 'W': sprintf(numStorage, "0x%lx", body[0]); /* window id */ string = numStorage; goto doString; case 'x': number = body[3]; /* window horizontal pos */ goto doNumber; case 'y': number = body[4]; /* window vertical pos */ goto doNumber; case 'w': number = body[5]; /* window width */ goto doNumber; case 'h': number = body[6]; /* window height */ goto doNumber; case 't': number = body[7]; /* desktop (ConfigureWindow packet) */ goto doNumber; case 'f': sprintf(numStorage, "0x%lx", body[8]); /* flags */ string = numStorage; goto doString; case 'N': string = (char *)(&body[3]); /* window, icon or class name */ goto doString; case 'T': number = body[0]; /* new desktop (NewDesk packet) */ goto doNumber; case 'D': number = body[2]; /* new desktop (NewPage packet) */ goto doNumber; case 'X': number = body[0]; /* new desk horizontal pos */ goto doNumber; case 'Y': number = body[1]; /* new desk vertical pos */ goto doNumber; default: numStorage[0] = before[1]; numStorage[1] = '\0'; string = numStorage; goto doString; } doNumber: sprintf(numStorage, "%ld", number); string = numStorage; doString: spaceNeeded = Tcl_ScanElement(string, &cvtFlags); length = Tcl_DStringLength(dsPtr); Tcl_DStringSetLength(dsPtr, length + spaceNeeded); spaceNeeded = Tcl_ConvertElement(string, Tcl_DStringValue(dsPtr) + length, cvtFlags | TCL_DONT_USE_BRACES); Tcl_DStringSetLength(dsPtr, length + spaceNeeded); before += 2; } } /* *-------------------------------------------------------------- * * FvwmMsgHandler -- * * This function is called when the pipe from fvwm has data to be * read by the module. * * Results: * If a handler for the given message has been defined, it is * interpreted on the fvwmInterp global context. * * Side effects: * None. * *-------------------------------------------------------------- */ static void FvwmMsgHandler(ClientData clientData, int mask) { unsigned long header[HEADER_SIZE], *body; int i; if(mask & TCL_EXCEPTION) { DeadPipe(0); } ReadFvwmPacket(FD[1],header,&body); for(i=1; i<=MAX_MESSAGES; i++) { if((CODE(i)==header[1]) && (SCRIPT(i)!=NULL) && (SCRIPT(i)[0]!='\0')) { int ans; Tcl_DString ds; Tcl_DStringInit(&ds); ExpandPercents(SCRIPT(i),body,&ds); ans=Tcl_GlobalEval(fvwmInterp,Tcl_DStringValue(&ds)); if(ans==TCL_ERROR) { Tcl_AddErrorInfo(fvwmInterp, "\n (command bound to fvwm)"); Tcl_BackgroundError(fvwmInterp); } Tcl_DStringFree(&ds); } } free(body); } void FvwmClose (ClientData data) { Tcl_DeleteFileHandler(FD[1]); /* Tcl_FreeFile(Tcl_GetFile((ClientData)FD[1], TCL_UNIX_FD));*/ close(FD[1]); close(FD[2]); } /* *-------------------------------------------------------------- * * FvwmCmd -- * * This procedure is invoked to process the "fvwm" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * The fvwmInterp variable gets defined the first time the "init" * option is used. The message handlers may be modified. * *-------------------------------------------------------------- */ int FvwmCmd(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]) { int i,len; if (argc<2) { interp->result = "Wrong # of args"; return TCL_ERROR; } if((argv[1][0]=='i') && !strcmp(argv[1],"init")) { if(argc!=4) { sprintf(interp->result,"Wrong # of args, should be: %s init" " ",argv[0]); return TCL_ERROR; } if((FD[0] != -1)||(FD[1] != -1)) { interp->result="fvwm already initialized"; return TCL_ERROR; } CHECK(Tcl_GetInt(interp,argv[2],&FD[0])); CHECK(Tcl_GetInt(interp,argv[3],&FD[1])); Tcl_CreateFileHandler(FD[1], TCL_READABLE|TCL_EXCEPTION, FvwmMsgHandler, clientData); fvwmInterp=interp; Tcl_CreateExitHandler(FvwmClose,(ClientData)NULL); return TCL_OK; } if((argv[1][0]=='s') && !strcmp(argv[1],"send")) { if(argc!=4) { sprintf(interp->result,"Wrong # of args, should be: %s send" "windowID message",argv[0]); return TCL_ERROR; } if(FD[0] == -1) { interp->result="fvwm not initialized"; return TCL_ERROR; } CHECK(Tcl_GetInt(interp,argv[2],&i)); SendInfo(FD,argv[3],i); return TCL_OK; } len=strlen(argv[1]); for(i=0; i<=MAX_MESSAGES; i++) { if(!strncasecmp(argv[1],NAME(i),len)) { if(argc==2) { if(SCRIPT(i)!=NULL) interp->result=SCRIPT(i); return TCL_OK; } if(argc==3) { if(SCRIPT(i)!=NULL) { free(SCRIPT(i)); } SCRIPT(i)=(char*)ckalloc(strlen(argv[2])+1); strcpy(SCRIPT(i),argv[2]); return TCL_OK; } sprintf(interp->result,"Wrong # of args, should be: %s %s" " ?script?",argv[0],NAME(i)); return TCL_ERROR; } } sprintf(interp->result,"unknown option %s",argv[1]); return TCL_ERROR; } /*REMAINING STUFF TAKEN FROM FVWM2'S LIBRARY CODE; FVWM COPYRIGHTS APPLY*/ /************************************************************************ * * Reads a single packet of info from fvwm. Prototype is: * unsigned long header[HEADER_SIZE]; * unsigned long *body; * int fd[2]; * void DeadPipe(int nonsense); * Called if the pipe is no longer open * * * ReadFvwmPacket(fd[1],header, &body); * * Returns: * > 0 everything is OK. * = 0 invalid packet. * < 0 pipe is dead. (Should never occur) * body is a malloc'ed space which needs to be freed * **************************************************************************/ int ReadFvwmPacket(int fd, unsigned long *header, unsigned long **body) { int count,total,count2,body_length; char *cbody; extern void DeadPipe(int); if((count = read(fd,header,HEADER_SIZE*sizeof(unsigned long))) >0) { if(header[0] == START_FLAG) { body_length = header[2]-HEADER_SIZE; *body = (unsigned long *) safemalloc(body_length * sizeof(unsigned long)); cbody = (char *)(*body); total = 0; while(total < body_length*sizeof(unsigned long)) { if((count2= read(fd,&cbody[total], body_length*sizeof(unsigned long)-total)) >0) { total += count2; } else if(count2 < 0) { DeadPipe(1); } } } else count = 0; } if(count <= 0) DeadPipe(1); return count; } /*********************************************************************** * * Procedure: * safemalloc - mallocs specified space or exits if there's a * problem * ***********************************************************************/ char *safemalloc(int length) { char *ptr; if(length <= 0) length = 1; ptr = (char *)malloc(length); if(ptr == (char *)0) { fprintf(stderr,"malloc of %d bytes failed. Exiting\n",length); exit(1); } return ptr; } /*********************************************************************** * * Procedure: * SendInfo - send a command back to fvwm * ***********************************************************************/ void SendInfo(int *fd,char *message,unsigned long window) { int w; if(message != NULL) { write(fd[0],&window, sizeof(unsigned long)); w=strlen(message); write(fd[0],&w,sizeof(int)); write(fd[0],message,w); /* keep going */ w=1; write(fd[0],&w,sizeof(int)); } } int Tkfvwm_Init(interp) Tcl_Interp *interp; /* Interpreter in which the package is * to be made available. */ { int code; code = Tcl_PkgProvide(interp, "Tkfvwm", "4.1"); if (code != TCL_OK) { return code; } Tcl_CreateCommand(interp, "fvwm", FvwmCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; }