#ifndef lint
static char *rcsid_uwm_c = "$Header: uwm.c,v 10.4 86/02/01 16:24:27 tony Rel $";
#endif	lint

/************************************************************************
 *									*
 *			Copyright (c) 1986 by				*
 *		Digital Equipment Corporation, Maynard, MA		*
 *		         All Rights Reserved.				*
 *									*
 *	Permission to use, copy, modify, and distribute this software	*
 *	and its documentation is hereby granted only to licensees of 	*
 *	The Regents of the University of California pursuant to their	*
 *	license agreement for the Berkeley Software Distribution 	*
 *	provided that the following appears on all copies.		*
 *									*
 *            "LICENSED FROM DIGITAL EQUIPMENT CORPORATION		*
 *                      COPYRIGHT (C) 1986				*
 *                 DIGITAL EQUIPMENT CORPORATION			*
 *                         MAYNARD, MA					*
 *                     ALL RIGHTS RESERVED.				*
 *									*
 *      THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT	* 
 *	NOTICE AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL	*
 *	EQUIPMENT CORPORATION.  DIGITAL MAKES NO REPRESENTATIONS	*
 *	ABOUT SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE. IT IS	*
 *	SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.		*
 *									*
 * 	IF THE UNIVERSITY OF CALIFORNIA OR ITS LICENSEES MODIFY 	*
 *	THE SOFTWARE IN A MANNER CREATING DERIVATIVE COPYRIGHT 		*
 *	RIGHTS APPROPRIATE COPYRIGHT LEGENDS MAY BE PLACED ON THE	*
 *	DERIVATIVE WORK IN ADDITION TO THAT SET FORTH ABOVE."		*
 *									*
 ************************************************************************/
 

/*
 * MODIFICATION HISTORY
 *
 * 000 -- M. Gancarz, DEC Ultrix Engineering Group
 */

#ifndef lint
static char *sccsid = "@(#)uwm.c	3.8	1/24/86";
#endif

#include <sys/time.h>
#include "uwm.h"

#ifdef PROFIL
#include <signal.h>
/*
 * Dummy handler for profiling.
 */
ptrap()
{
    Exit(0);
}
#endif

#include <fcntl.h>

char *malloc();

static short gray_bits[16] = {
    0xaaaa, 0x5555, 0xaaaa, 0x5555,
    0xaaaa, 0x5555, 0xaaaa, 0x5555,
    0xaaaa, 0x5555, 0xaaaa, 0x5555,
    0xaaaa, 0x5555, 0xaaaa, 0x5555
};

int (*F_error)(), (*F_exec)(), (*F_exit)();
char *F_exec_string;

Bool ChkMline();
char *sfilename;
extern FILE *yyin;

short hi;			/* Button event high detail. */
short lo;			/* Button event low detail. */
int x, y;			/* Mouse X and Y coordinates. */
int cur_x, cur_y;		/* Current mouse X and Y coordinates. */
int str_width;			/* Width in pixels of output string. */
int pop_width, pop_height;	/* Pop up window width and height. */
int context;			/* Root, window, or icon context. */
Bool func_stat;			/* If true, function swallowed a ButtonUp. */
Bool delta_done;		/* If true, then delta functions are done. */
Binding *bptr;			/* Pointer to Bindings list. */
char *root_name;		/* Root window name. */
char *display = NULL;		/* Display name pointer. */
char message[128];		/* Error message buffer. */
char *rc_file;			/* Pointer to $HOME/.uwmrc. */
Bitmap gray_bitmap;		/* Gray bitmap used for gray pixmap. */
Display *dpy;			/* Display info pointer. */
Window event_win;		/* Event window. */
Window sub_win;			/* Subwindow for XUpdateMouse calls. */
WindowInfo root_info;		/* Root window info. */
WindowInfo event_info;		/* Event window info. */
XButtonEvent button_event;	/* Button input event. */

dummy()
{
    XGrabMouse();
    XUngrabMouse();
}

/*
 * Main program.
 */
init_uwm(argc, argv, environ)
int argc;
char **argv;
char **environ;
{
#ifdef PROFIL
    signal(SIGTERM, ptrap);
#endif

    /*
     * Set up internal defaults.
     */
    strcpy(IFontName, DEF_FONT);
    strcpy(PFontName, DEF_FONT);
    strcpy(MFontName, DEF_FONT);
    CursorFunc = DEF_FUNC;
    Delta = DEF_DELTA;
    IBorderWidth = DEF_ICON_BORDER_WIDTH;
    HIconPad = DEF_ICON_PADDING;
    VIconPad = DEF_ICON_PADDING;
    PBorderWidth = DEF_POP_BORDER_WIDTH;
    PPadding = DEF_POP_PADDING;
    MBorderWidth = DEF_MENU_BORDER_WIDTH;
    HMenuPad = DEF_MENU_PADDING;
    VMenuPad = DEF_MENU_PADDING;
    MenuRadius = DEF_MENU_RADIUS; /* -DEH */
    PieMenus = DEF_PIE_MENUS; /* -DEH */
    Volume = DEF_VOLUME;

    /*
     * Set XErrorFunction to be non-terminating.
     */
    XErrorHandler(XError);

    /* 
     * Parse the command line arguments.
     */
    Argv = argv;
    Environ = environ;
    argc--, argv++;
    while (argc) {
        if (!(strcmp(*argv, "-f"))) {
            argc--, argv++;
            if ((argc == 0) || (Startup_File[0] != '\0'))
                Usage();
            strncpy(Startup_File, *argv, NAME_LEN);
        }
        else display = *argv;
	argc--, argv++;
    }

    /*
     * Initialize the default bindings.
     */
    InitBindings();

    /*
     * Read in and parse $HOME/.uwmrc, if it exists.
     */
    sfilename = rc_file = malloc(NAME_LEN);
    sprintf(rc_file, "%s/.uwmrc", getenv("HOME"));
    if ((yyin = fopen(rc_file, "r")) != NULL) {
        Lineno = 1;
        yyparse();
        fclose(yyin);
        if (Startup_File_Error)
            Error("Bad .uwmrc file...aborting");
    }

    /* 
     * Read in and parse the startup file from the command line, if
     * specified.
     */
    if (Startup_File[0] != '\0') {
        sfilename = Startup_File;
        if ((yyin = fopen(Startup_File, "r")) == NULL) {
    	sprintf(message, "Cannot open startup file '%s'", Startup_File);
            Error(message);
        }
        Lineno = 1;
        yyparse();
        fclose(yyin);
        if (Startup_File_Error)
            Error("Bad startup file...aborting");
    }

    /*
     * Verify the menu bindings.
     */
    VerifyMenuBindings();
    if (Startup_File_Error)
        Error("Bad startup file...aborting");

    /* 
     * Open the display.
     */
    if ((dpy = XOpenDisplay(display)) == NULL)
        Error("Unable to open display");

    /*
     * Force child processes to disinherit the TCP file descriptor.
     * This helps shell commands forked and exec'ed from menus
     * to work properly.
     */
    if ((status = fcntl(dpyno(), F_SETFD, 1)) == -1) {
        perror("uwm: child cannot disinherit TCP fd");
        Error("TCP file descriptor problems");
    }

    /*
     * If the root window has not been named, name it.
     */
    status = XFetchName(RootWindow, &root_name);
    if (status == FAILURE) Error("Can't fetch Root Window name string");
    if (root_name == NULL) XStoreName(RootWindow, " X Root Window ");
    if (root_name) free(root_name);

    /*
     * Gather information about the root window.
     */
    status = XQueryWindow(RootWindow, &root_info);
    if (status == FAILURE)
        Error("Can't acquire root window information from X server");

    ScreenHeight = root_info.height;	/* True height of entire screen */
    ScreenWidth = root_info.width;	/* True width of entire screen */

    /*
     * Create and store the icon background pixmap.
     */
    gray_bitmap = XStoreBitmap(16, 16, gray_bits);
    GrayPixmap = XMakePixmap(gray_bitmap, BlackPixel, WhitePixel);

    /*
     * Set up icon window, icon cursor and pop-up window color parameters.
     */
    if (Reverse) {
        IconCursorFunc = GXcopyInverted;
        IBorder = WhitePixmap;
        IBackground = GrayPixmap;
        ITextForground = WhitePixel;
        ITextBackground = BlackPixel;
        PBorder = BlackPixmap;
        PBackground = WhitePixmap;
        PTextForground = BlackPixel;
        PTextBackground = WhitePixel;
        MBorder = WhitePixmap;
        MBackground = BlackPixmap;
        MTextForground = WhitePixel;
        MTextBackground = BlackPixel;
    }
    else {
        IconCursorFunc = GXcopy;
        IBorder = BlackPixmap;
        IBackground = GrayPixmap;
        ITextForground = BlackPixel;
        ITextBackground = WhitePixel;
        PBorder = WhitePixmap;
        PBackground = BlackPixmap;
        PTextForground = WhitePixel;
        PTextBackground = BlackPixel;
        MBorder = BlackPixmap;
        MBackground = WhitePixmap;
        MTextForground = BlackPixel;
        MTextBackground = WhitePixel;
    }

    /*
     * Store all the cursors.
     */
    StoreCursors();

    /* 
     * grab the mouse buttons according to the map structure
     */
    Grab_Buttons();

    /*
     * Load the selected fonts.
     */
    IFont = XGetFont(IFontName);
    if (IFont == FAILURE) {
        sprintf(message, "Unable to get font '%s'.", IFontName);
        Error(message);
    }
    PFont = XGetFont(PFontName);
    if (PFont == FAILURE) {
        sprintf(message, "Unable to get font '%s'.", PFontName);
        Error(message);
    }
    MFont = XGetFont(MFontName);
    if (MFont == FAILURE) {
        sprintf(message, "Unable to get font '%s'.", MFontName);
        Error(message);
    }

    /*
     * Retrieve the information structure for the specifed fonts and
     * set the global font information pointers.
     */
    status = XQueryFont(IFont, &IFontInfo);
    if (status == FAILURE) {
        sprintf(message, "Unable to query X server for info on font '%s'.",
                IFontName);
        Error(message);
    }
    status = XQueryFont(PFont, &PFontInfo);
    if (status == FAILURE) {
        sprintf(message, "Unable to query X server for info on font '%s'.",
                PFontName);
        Error(message);
    }
    status = XQueryFont(MFont, &MFontInfo);
    if (status == FAILURE) {
        sprintf(message, "Unable to query X server for info on font '%s'.",
                MFontName);
        Error(message);
    }

    /*
     * Calculate size of the resize pop-up window.
     */
    str_width = XQueryWidth(PText, PFont);
    pop_width = str_width + (PPadding << 1);
    PWidth = pop_width + (PBorderWidth << 1);
    pop_height = PFontInfo.height + (PPadding << 1);
    PHeight = pop_height + (PBorderWidth << 1);

    /*
     * Create the pop-up window.  Create it at (0, 0) for now.  We will
     * move it where we want later.
     */
    Pop = XCreateWindow(RootWindow,
                        0, 0,
                        pop_width, pop_height,
                        PBorderWidth,
                        PBorder, PBackground);
    if (Pop == FAILURE) Error("Can't create pop-up dimension display window.");

    /*
     * Create the menus for later use.
     */
    CreateMenus();

    /*
     * Tell the user we're alive and well.
     */
    XFeep(Volume);
    XFlush();
}

/*
 * Interface to Unix time.
 */

get_time(tvp)
struct timeval *tvp;
{
	gettimeofday(tvp, (char *)0);
}

/*
 * Do uwm command poop.
 * button_event should be set up with a button down event to process.
 */

uwm_poop()
{
  delta_done = FALSE;
  
  if (determine_context() == FAILURE)
    return;

  /*
   * Get the button event detail.
   */
  lo = (button_event.detail & ValueMask);
  hi = KeyMask(button_event.detail);

  /*
   * If the function ate the ButtonUp event, then return.
   */
  if (!determine_function(ButtonDown))
    while(TRUE) {
      /*
       * Wait for the next button event.
       */
      if (XPending() && GetButton(&button_event)) {
	/*
	 * If it's not a release of the same button that was pressed,
	 * don't do the function bound to 'ButtonUp'.
	 */
	if ((button_event.type != ButtonReleased) ||
	    (lo != (button_event.detail & ValueMask)) ||
	    (hi != KeyMask(button_event.detail)))
	  break;
	
	if (determine_context() != FAILURE)
	  determine_function(ButtonUp);

	break;
      }
      
      if (!delta_done) {
	XUpdateMouse(RootWindow, &cur_x, &cur_y, &sub_win);

	if ((abs(cur_x - x) > Delta) ||
	    (abs(cur_y - y) > Delta)) {
	      /*
	       * Delta functions are done once (and only once.)
	       */
	      delta_done = TRUE;
	      
	      if (determine_context() == FAILURE)
		break;

	      /*
	       * If the function ate the ButtonUp event,
	       * then restart the loop.
	       */
	      if(determine_function(DeltaMotion))
		break;
	    }
      }
    }
  XFlush();
}

/*
 * Determine the event window, mouse coordinates,
 * and context. Returns status.
 */
determine_context()
{
  status = XInterpretLocator(RootWindow,
			     &x, &y,
			     &event_win,
			     button_event.location);

  if (status != FAILURE) {

    if (event_win == 0) {
      event_win = RootWindow;
      context = ROOT;

    } else {
      status = XQueryWindow(event_win, &event_info);
      if (status != FAILURE) {

	if (event_info.type & IsIcon)
	  context = ICON;

	else
	  context = WINDOW;
      }
    }
  }
  return(status);
}

/*
 * Determine the function to call and call it. 
 * Return true if the function ate the ButtonUp event.
 */

int determine_function(function_mask)
int function_mask;
{
  for(bptr = Blist; bptr; bptr = bptr->next) {
    
    if ((bptr->button != lo) ||
	(KeyMask(bptr->mask) != hi) ||
	(bptr->context != context) ||
	!(bptr->mask & function_mask))
      continue;
    
    /*
     * Found a match! Invoke the function.
     */
    if ((*bptr->func)(event_win,
		      (int)bptr->mask & ~ButtonMods,
		      bptr->button,
		      x, y,
		      bptr->menu)) {
	return(1);
      }
  }

  return(0);
}

/*
 * Initialize the default bindings.  First, write the character array
 * out to a temp file, then point the parser to it and read it in.
 * Afterwards, we unlink the temp file.
 */
InitBindings()
{
    char *mktemp();
    char *tempfile = TEMPFILE;	/* Temporary filename. */
    register FILE *fp;		/* Temporary file pointer. */
    register char **ptr;	/* Default bindings string array pointer. */

    /*
     * Create and write the temp file.
     */
    sfilename = mktemp(tempfile);
    if ((fp = fopen(tempfile, "w")) == NULL) {
        perror("uwm: cannot create temp file");
        Exit(1);
    }
    for (ptr = DefaultBindings; *ptr; ptr++) {
        fputs(*ptr, fp);
        fputc('\n', fp);
    }
    fclose(fp);

    /*
     * Read in the bindings from the temp file and parse them.
     */
    if ((yyin = fopen(tempfile, "r")) == NULL) {
        perror("uwm: cannot open temp file");
        Exit(1);
    }
    Lineno = 1;
    yyparse();
    fclose(yyin);
    unlink(tempfile);
    if (Startup_File_Error)
        Error("Bad default bindings...aborting");

    /*
     * Parse the system startup file, if one exists.
     */
    if ((yyin = fopen(SYSFILE, "r")) != NULL) {
        sfilename = SYSFILE;
        Lineno = 1;
        yyparse();
        fclose(yyin);
        if (Startup_File_Error)
            Error("Bad system startup file...aborting");
    }
}

/*
 * Verify menu bindings by checking that a menu that is mapped actually
 * exists.  Stash a pointer in the binding to the relevant menu info data
 * structure.
 * Check nested menu consistency.
 */
VerifyMenuBindings()
{
    Binding *bptr;
    MenuLink *mptr;

    for(bptr = Blist; bptr; bptr = bptr->next) {
        if (bptr->func == Menu) {
            for(mptr = Menus; mptr; mptr = mptr->next) {
                if(!(strcmp(bptr->menuname, mptr->menu->name))) {
                    bptr->menu = mptr->menu;
                    break;
                }
            }
            if (mptr == NULL) {
                fprintf(stderr,
                        "uwm: non-existent menu reference: \"%s\"\n",
                        bptr->menuname);
                Startup_File_Error = TRUE;
            }
        }
    }
    CheckMenus();
}

/*
 * Check nested menu consistency by verifying that every menu line that
 * calls another menu references a menu that actually exists.
 */
CheckMenus()
{
    MenuLink *ptr;
    Bool errflag = FALSE;

    for(ptr = Menus; ptr; ptr = ptr->next) {
        if (ChkMline(ptr->menu))
            errflag = TRUE;
    }
    if (errflag)
        Error("Nested menu inconsistency");
}

Bool ChkMline(menu)
MenuInfo *menu;
{
    MenuLine *ptr;
    MenuLink *lptr;
    Bool errflag = FALSE;

    for(ptr = menu->line; ptr; ptr = ptr->next) {
        if (ptr->type == IsMenuFunction) {
            for(lptr = Menus; lptr; lptr = lptr->next) {
                if(!(strcmp(ptr->text, lptr->menu->name))) {
                    ptr->menu = lptr->menu;
                    break;
                }
            }
            if (lptr == NULL) {
                fprintf(stderr,
                        "uwm: non-existent menu reference: \"%s\"\n",
                        ptr->text);
                errflag = TRUE;
            }
        }
    }
    return(errflag);
}

/*
 * Grab the mouse buttons according to the bindings list.
 */
Grab_Buttons()
{
    Binding *bptr;

    for(bptr = Blist; bptr; bptr = bptr->next)
        Grab(bptr->mask);
}

/*
 * Grab a mouse button according to the given mask.
 */
Grab(mask)
short mask;
{
    short m = LeftMask | MiddleMask | RightMask;

    switch (mask & m) {
    case LeftMask:
        status = XGrabButton(RootWindow, LeftButtonCursor,
                             mask & ~ButtonMods,
                             EVENTMASK);
        if (status == FAILURE)
            Error("Can't grab left mouse button.");
        break;

    case MiddleMask:
        status = XGrabButton(RootWindow, MiddleButtonCursor,
                             mask & ~ButtonMods,
                             EVENTMASK);
        if (status == FAILURE)
            Error("Can't grab middle mouse button.");
        break;

    case RightMask:
        status = XGrabButton(RootWindow, RightButtonCursor,
                             mask & ~ButtonMods,
                             EVENTMASK);
        if (status == FAILURE)
            Error("Can't grab right mouse button.");
        break;
    }
}

/*
 * error routine for .uwmrc parser
 */
yyerror(s)
char*s;
{
    fprintf(stderr, "uwm: %s: %d: %s\n", sfilename, Lineno, s);
    Startup_File_Error = TRUE;
}

/*
 * Print usage message and quit.
 */
Usage()
{
  fputs("Usage:  uwm [-f <file>] [<host>:<display>]\n", stderr);
  Exit(1);
}

/*
 * error handler for X I/O errors
 */
XIOError(dsp)
Display *dsp;
{
    perror("uwm");
    Exit(3);
}

Exit(result)
int result;
{
    (*F_exit)(result);
}


