484 lines
13 KiB
C
Executable File
484 lines
13 KiB
C
Executable File
/*
|
|
* 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"
|
|
"<output-fd> <input-fd>",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;
|
|
}
|
|
|
|
|