/*****************************************************************************/
/*									     */
/* rdetool								     */
/*									     */
/*****************************************************************************/

/* 
   Written by Richard Evans.

   Topexpress Ltd. 
   Poseidon House, Castle Park 	Fax       : (+44) 223 315057   
   Cambridge, CB3 0RD, UK       E-Mail    : rde@uk.co.topexp (UK)
   					  : rde@topexpress.co.uk
*/

#include <stdio.h>
#include <string.h>

#include <xview/xview.h>
#include <xview/tty.h>
#include <xview/termsw.h>
#include <xview/icon.h>
#include <xview/font.h>
#include <xview/defaults.h>
#include <X11/keysym.h>

/**********************************************************************/
/*   Have this define for CRISP key settings.			      */
/**********************************************************************/
# define	CRISP
/* Icon and mask */

static short bits[] = 
{
#include "images/cmdtool.icon"
};

static short mask[] =  {
#include "images/cmdtool_mask.icon"
};


#define FN
#define KP
#define ALT
#define META

#ifdef FN
/* Mapping tables for example function key handling: plain, shift, ctrl, Alt, Shift-Alt, Meta and Shift-Meta.  Do not map L5-L10. */
# if defined(CRISP)
static char * Plain_fn[] = {NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
/* Rnn */		    "\033[208z", "\033[209z", "\033[210z",
			    "\033[211z", "\033[212z", "\033[213z",
			    "\033[214z", "\033[A", "\033[216z",
			    "\033[D", "\033[218z", "\033[C",
			    "\033[220z", "\033[B", "\033[222z", "\033R16",

/* Fnn */		    "\033[224z", "\033[225z", "\033[226z", "\033[227z",  
			    "\033[228z", "\033[229z", "\033[230z", "\033[231z",  
			    "\033[232z", "\033[233z", "\033[234z", "\033[235z"};

static char * Shift_fn[] = {"SL1", "SL2", "SL3", "SL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL, 
/* Rnn */		    "\033[208S", "\033[209S", "\033[210S",
			    "\033[211S", "\033[212S", "\033[213S",
			    "\033[214S", "\033[215S", "\033[216S",
			    "\033[217S", "\033[218S", "\033[219S",
			    "\033[220S", "\033[221S", "\033[222S", "\033R16",

/* Fnn */		    "\033[224S", "\033[225S", "\033[226S", "\033[227S",  
			    "\033[228S", "\033[229S", "\033[230S", "\033[231S",  
			    "\033[232S", "\033[233S", "\033[SF11~", "\033[SF12~"};


static char * Ctrl_fn[]  = {"CL1", "CL2", "CL3", "CL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
/* Rnn */		    "\033[208C", "\033[209C", "\033[210C",
			    "\033[211C", "\033[212C", "\033[213C",
			    "\033[214C", "\033[215C", "\033[216C",
			    "\033[217C", "\033[218C", "\033[219C",
			    "\033[220C", "\033[221C", "\033[222C", "\033R16",

/* Fnn */		    "\033[224C", "\033[225C", "\033[226C", "\033[227C",  
			    "\033[228C", "\033[229C", "\033[230C", "\033[231C",  
			    "\033[232C", "\033[233C", "\033[CF11~", "\033[CF12~"};


static char * Alt_fn[]   = {"AL1", "AL2", "AL3", "AL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
/* Rnn */		    "\033[208A", "\033[209A", "\033[210A",
			    "\033[211A", "\033[212A", "\033[213A",
			    "\033[214A", "\033[215A", "\033[216A",
			    "\033[217A", "\033[218A", "\033[219A",
			    "\033[220A", "\033[221A", "\033[222A", "\033R16",

/* Fnn */		    "\033[224A", "\033[225A", "\033[226A", "\033[227A",  
			    "\033[228A", "\033[229A", "\033[230A", "\033[231A",  
			    "\033[232A", "\033[233A", "\033[mF11~", "\033[mF12~"};


static char * AltS_fn[]  = {"BL1", "BL2", "BL3", "BL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
/* Rnn */		    "\033[208A", "\033[209A", "\033[210A",
			    "\033[211A", "\033[212A", "\033[213A",
			    "\033[214A", "\033[215A", "\033[216A",
			    "\033[217A", "\033[218A", "\033[219A",
			    "\033[220A", "\033[221A", "\033[222A", "\033R16",

/* Fnn */		    "\033[224A", "\033[225A", "\033[226A", "\033[227A",  
			    "\033[228A", "\033[229A", "\033[230A", "\033[231A",  
			    "\033[232A", "\033[233A", "\033F11", "\033F12"};


static char * Meta_fn[]  = {"ML1", "ML2", "ML3", "ML4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
/* Rnn */		    "\033[208A", "\033[209A", "\033[210A",
			    "\033[211A", "\033[212A", "\033[213A",
			    "\033[214A", "\033[215A", "\033[216A",
			    "\033[217A", "\033[218A", "\033[219A",
			    "\033[220A", "\033[221A", "\033[222A", "\033R16",

/* Fnn */		    "\033[224A", "\033[225A", "\033[226A", "\033[227A",  
			    "\033[228A", "\033[229A", "\033[230A", "\033[231A",  
			    "\033[232A", "\033[233A", "\033F11", "\033F12"};


static char * MetaS_fn[] = {"NL1", "NL2", "NL3", "NL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
/* Rnn */		    "\033[208A", "\033[209A", "\033[210A",
			    "\033[211A", "\033[212A", "\033[213A",
			    "\033[214A", "\033[215A", "\033[216A",
			    "\033[217A", "\033[218A", "\033[219A",
			    "\033[220A", "\033[221A", "\033[222A", "\033R16",

/* Fnn */		    "\033[224A", "\033[225A", "\033[226A", "\033[227A",  
			    "\033[228A", "\033[229A", "\033[230A", "\033[231A",  
			    "\033[232A", "\033[233A", "\033F11", "\033F12"};
# else
static char * Plain_fn[] = {NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
			    "R1",  "R2",  "R3",  "R4",  "R5",  "R6",  "R7",  "R8",  "R9",  "R10",  "R11",  "R12",  "R13",  "R14",  "R15",  "R16",
			    NULL,  "F2",  "F3",  "F4",  "F5",  "F6",  "F7",  "F8",  "F9",  "F10",  "F11",  "F12",  "F13",  "F14",  "F15",  "F16"};


static char * Shift_fn[] = {"SL1", "SL2", "SL3", "SL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL, 
			    "SR1", "SR2", "SR3", "SR4", "SR5", "SR6", "SR7", "SR8", "SR9", "SR10", "SR11", "SR12", "SR13", "SR14", "SR15", "SR16",
			    "SF1", "SF2", "SF3", "SF4", "SF5", "SF6", "SF7", "SF8", "SF9", "SF10", "SF11", "SF12", "SF13", "SF14", "SF15", "SF16"};


static char * Ctrl_fn[]  = {"CL1", "CL2", "CL3", "CL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
			    "CR1", "CR2", "CR3", "CR4", "CR5", "CR6", "CR7", "CR8", "CR9", "CR10", "CR11", "CR12", "CR13", "CR14", "CR15", "CR16",
			    "CF1", "CF2", "CF3", "CF4", "CF5", "CF6", "CF7", "CF8", "CF9", "CF10", "CF11", "CF12", "CF13", "CF14", "CF15", "CF16"};


static char * Alt_fn[]   = {"AL1", "AL2", "AL3", "AL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
			    "AR1", "AR2", "AR3", "AR4", "AR5", "AR6", "AR7", "AR8", "AR9", "AR10", "AR11", "AR12", "AR13", "AR14", "AR15", "AR16",
			    "AF1", "AF2", "AF3", "AF4", "AF5", "AF6", "AF7", "AF8", "AF9", "AF10", "AF11", "AF12", "AF13", "AF14", "AF15", "AF16"};


static char * AltS_fn[]  = {"BL1", "BL2", "BL3", "BL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
			    "BR1", "BR2", "BR3", "BR4", "BR5", "BR6", "BR7", "BR8", "BR9", "BR10", "BR11", "BR12", "BR13", "BR14", "BR15", "BR16",
			    "BF1", "BF2", "BF3", "BF4", "BF5", "BF6", "BF7", "BF8", "BF9", "BF10", "BF11", "BF12", "BF13", "BF14", "BF15", "BF16"};


static char * Meta_fn[]  = {"ML1", "ML2", "ML3", "ML4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
			    "MR1", "MR2", "MR3", "MR4", "MR5", "MR6", "MR7", "MR8", "MR9", "MR10", "MR11", "MR12", "MR13", "MR14", "MR15", "MR16",
			    "MF1", "MF2", "MF3", "MF4", "MF5", "MF6", "MF7", "MF8", "MF9", "MF10", "MF11", "MF12", "MF13", "MF14", "MF15", "MF16"};


static char * MetaS_fn[] = {"NL1", "NL2", "NL3", "NL4", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,
			    "NR1", "NR2", "NR3", "NR4", "NR5", "NR6", "NR7", "NR8", "NR9", "NR10", "NR11", "NR12", "NR13", "NR14", "NR15", "NR16",
			    "NF1", "NF2", "NF3", "NF4", "NF5", "NF6", "NF7", "NF8", "NF9", "NF10", "NF11", "NF12", "NF13", "NF14", "NF15", "NF16"};
# endif
#endif


#ifdef KP
/* Mapping table for keypad - + Enter Del Ins (all shift states) */
# if defined(CRISP)
static char * KP_map[] = {"\033[Om", "\033[Ok", "\033[OM", "\177", "\033[2z"};
static char * KP_alt_map[] =  {"\033[KM-~", "\033[KM+~", "\033[OM", "\177", "\033[2z"};
static char * KP_ctrl_map[] = {"\033[KC-~", "\033[KC+~", "\033[OM", "\177", "\033[2z"};
# else
static char * KP_map[] = {"Minus", "Plus", "Enter", "Del", "Ins"};
# endif
#endif

#ifdef ALT
/* Example prefix sent when Alt is pressed with normal (not function) key. */
# if defined(CRISP)
char * alt_prefix = "\033A";
# else
char * alt_prefix = "Alt-";
# endif
#endif

#ifdef META
/* Example prefix sent when Meta is pressed with normal (not function) key. */
# if defined(CRISP)
char * meta_prefix = "\033A";
# else
char * meta_prefix = "Meta-";
# endif
#endif


static Frame 	    frame;
static Notify_value key_proc();
static Tty	    tty;

void main(argc, argv)
  int 	  argc;
  char ** argv;
  { char 	 * me	   = argv[0];
    char 	 * init	   = 0;
    int 	   console = 0;
    int		   cmdtool = 0;
    char	 * cstring = "";
    char 	 * command;
    int 	   argn;
    Server_image   image;
    Server_image   maskimage;
    Icon 	   icon;
    Xv_window	   tty_window;
    Xv_Font        font;
    char 	   title[256];
    int		   left = sizeof(title)-1;

    /* Initialise Xview and eat up standard arguments */

    xv_init(XV_INIT_ARGC_PTR_ARGV, &argc, argv, NULL);

    /* 
       Accept shelltool-like command line options: -C and -I, also -S to select
       scrolling.
    */

    for (argn = 1; argn < argc && * argv[argn] == '-'; argn ++)
      { char * s = argv[argn];
	int    c = s[2] != 0 ? 0 : s[1];

	switch (c)
	  { case 'C': 
	      console = 1; 
	      cstring = " (CONSOLE)";
	      break;

	    case 'S': 
	      cmdtool = 1; 
	      break;

	    case 'I':
	      if (argn < argc-1) init = argv[++ argn];
	      break;

	    default:
	      fprintf(stderr, "%s: unknown option %s\n", me, argv[argn]);
	      exit(1);
	  }
      }

    /* Standard name is command */

    if (argn < argc)
      command = argv[1];
     else
      { command = (char *) getenv("SHELL");

	if (command == NULL)
	  command = "sh";
	 else
	  { char * e = strrchr(command, '/');

	    if (e != NULL)
	      command = e+1;
	  }
      }

    /* Create images for icon and mask */

    image     = (Server_image) xv_find(NULL, 
				       SERVER_IMAGE,
				       XV_WIDTH,          64,
				       XV_HEIGHT,         64,
				       SERVER_IMAGE_DEPTH, 1,
				       SERVER_IMAGE_BITS, bits,
				       NULL);

    maskimage = (Server_image) xv_find(NULL, SERVER_IMAGE,
				       XV_WIDTH,           64,
				       XV_HEIGHT,          64,
				       SERVER_IMAGE_DEPTH, 1,
				       SERVER_IMAGE_BITS,  mask,
				       NULL);

    /* Create icon */

    icon = (Icon) xv_find(NULL, 
			  ICON, 
			  ICON_IMAGE,      image, 
			  ICON_MASK_IMAGE, maskimage, 
			  ICON_LABEL,      (console ? "CONSOLE" : command), 
			  NULL);

    /* Create base frame: construct default name from this tool and command */

    if (strlen(me) + strlen(cstring) + strlen(command) + 3 < sizeof(title))
      { sprintf(title, "%s%s - %s", me, cstring, command);
	me = title;
      }

    frame = (Frame) xv_create(NULL, FRAME, FRAME_LABEL, me, FRAME_ICON, icon, NULL);
    
    if (frame == 0)
      { fprintf(stderr, "%s: couldn't create base frame\n", me);
	exit(1);
      }

    /* Create tty, using command given if necessary */

    if (argn == argc)
      tty = (Tty) xv_create(frame, TERMSW, TTY_QUIT_ON_CHILD_DEATH, TRUE, NULL);
     else
      { int     comms   = argc-argn;
	char ** my_argv = (char **) malloc((comms+1) * sizeof(char *));

	memcpy(my_argv, & argv[1], comms * sizeof(char *));
	my_argv[comms] = NULL;

	tty = xv_create(frame, TERMSW, TTY_ARGV, my_argv, TTY_QUIT_ON_CHILD_DEATH, TRUE, NULL);
	free(my_argv);
      }

    /* Extract subwindow from tty */

    tty_window = (Xv_window) xv_get(tty, OPENWIN_NTH_VIEW, 0);

    /* Set TTY window font if the frame font isn't the (scaled) default */

    font = xv_get(frame, XV_FONT);

    if (font != xv_find(NULL, FONT, FONT_SCALE, xv_get(font, FONT_SCALE), XV_AUTO_CREATE, FALSE, NULL))
      xv_set(tty_window, XV_FONT, font, NULL);

    /* Set window dimensions from defaults */

    if (defaults_get_string("window.geometry", "Window.Geometry", NULL) == NULL)
      { if (defaults_get_integer("window.width", "Window.Width", -1) == -1)
	  xv_set(tty, WIN_COLUMNS, defaults_get_integer("window.columns", "Window.Columns", 80), NULL);

	if (defaults_get_integer("window.height", "Window.Height", -1) == -1)
	  xv_set(tty, WIN_ROWS, defaults_get_integer("window.rows", "Window.Rows", 35), NULL);
      }

    /* Set command line arguments for save workspace requests */

    title[0] = 0;

    for (argn = 1; argn < argc; argn ++)
      { char * s = argv[argn];
	int    l = strlen(s)+1;

	if (l > left)
	  break;

	strcat(title, s); strcat(title, " ");
	left -= l;
      }

    if (argn == argc)
      xv_set(frame, WIN_CMD_LINE, title, NULL);
		
    /* If necessary, set console mode; otherwise set non-scrolling */

    if (console)
      xv_set(tty, TTY_CONSOLE, TRUE, NULL);
     else
      if (! cmdtool)
	xv_set(tty, TERMSW_MODE, TTYSW_MODE_TYPE, NULL);

    /* And send initial command? */

    if (init != 0)
      { ttysw_input(tty, init, strlen(init));
	ttysw_input(tty, "\n", 1);
      }

    /* Set notify function for event handling */

    notify_interpose_event_func(tty_window,  key_proc, NOTIFY_SAFE);

    /* 
       As an experiment, set the RETAINED flag to false.  Experience shows that
       the window repaints much faster with this flag false, at least on a
       SPARCstation 1.  Also it seems to avoid the shelltool problem whereby
       the borders don't get drawn properly sometimes.
    */

    xv_set(tty_window, WIN_RETAINED, FALSE, NULL);

    /* Start it up */

    window_fit(frame);
    xv_main_loop(frame);
    exit(0);
  }


/*****************************************************************************/
/*									     */
/* Event handling procedures						     */
/*									     */
/*****************************************************************************/


static Notify_value key_proc(window, event, arg, type)
  Xv_Window 	      window;
  Event 	    * event;
  Notify_arg 	      arg;
  Notify_event_type   type;
  /*
     TTY window keyboard translation function:

     	Converts Meta+x    into 0x80+x
		 Control+- into Control+_ (for Emacs `undo' function)
  */
  { if (event_is_down(event))
      { XEvent * x = event_xevent(event);
		  	
	if (event_is_ascii(event))
	  { 
#ifndef META
	    if (event_meta_is_down(event))
	      event_id(event) = event_id(event) | 0x80;
	     else
	      if (event_ctrl_is_down(event) && event_id(event) == '-')
		event_id(event) = 037;
	       else
#endif
#ifdef KP
		/*
		   Example code for handling key pad keys - + Enter Del Ins.

		   Note that the other keys are R1-R15 and can be detected with
		   event_is_key_right - see code for top keys below.
		*/
		{ char   * str = NULL;
		  char **kp_map = KP_map;
		  KeySym k = XLookupKeysym(& x -> xkey, 1);

#ifdef ALT
	    /* Check for Alt and Meta prefix: recheck for ascii in case of Rn */

	         if (event_is_ascii(event))
	      		if ((x -> xkey.state & Mod2Mask) != 0)
		        	kp_map = KP_alt_map;
#ifdef META
		       else
			if ((x -> xkey.state & Mod1Mask) != 0)
			        kp_map = KP_alt_map;
#endif
#endif
		  if (event_ctrl_is_down(event))
		  	kp_map = KP_ctrl_map;
		  switch (k != 0 ? k : XLookupKeysym(& x -> xkey, 0))
		    { case XK_KP_Add:      str = kp_map[1]; break;
		      case XK_KP_Subtract: str = kp_map[0]; break;
		      case XK_KP_0:	     str = kp_map[4]; break;
		      case XK_KP_Enter:    str = kp_map[2]; break;
		      case XK_KP_Decimal:  str = kp_map[3]; break;

		      /* Rn keys: may be keyboard dependent */

		      case XK_KP_Equal:      event_id(event) = KEY_RIGHT(4);  break;
		      case XK_KP_Divide:     event_id(event) = KEY_RIGHT(5);  break;
		      case XK_KP_Multiply:   event_id(event) = KEY_RIGHT(6);  break;
		      case XK_KP_7: 	     event_id(event) = KEY_RIGHT(7);  break;
		      case XK_KP_8: 	     event_id(event) = KEY_RIGHT(8);  break;
		      case XK_KP_9: 	     event_id(event) = KEY_RIGHT(9);  break;
		      case XK_KP_4: 	     event_id(event) = KEY_RIGHT(10); break;
		      case XK_KP_5: 	     event_id(event) = KEY_RIGHT(11); break;
		      case XK_KP_6: 	     event_id(event) = KEY_RIGHT(12); break;
		      case XK_KP_1: 	     event_id(event) = KEY_RIGHT(13); break;
		      case XK_KP_2: 	     event_id(event) = KEY_RIGHT(14); break;
		      case XK_KP_3: 	     event_id(event) = KEY_RIGHT(15); break;
		    }		      

		  if (str != NULL)
		    { ttysw_input(tty, str, strlen(str));
		      return(NOTIFY_DONE);
		    }
		}
#endif

#ifdef ALT
	    /* Check for Alt and Meta prefix: recheck for ascii in case of Rn */

	    if (event_is_ascii(event))
	      if ((x -> xkey.state & Mod2Mask) != 0)
		ttysw_input(tty, alt_prefix, strlen(alt_prefix));
#ifdef META
	       else
		if ((x -> xkey.state & Mod1Mask) != 0)
		  ttysw_input(tty, meta_prefix, strlen(meta_prefix));
#endif
#endif
	  }

#ifdef FN
	/* 
	   Outline code for mapping (Fn) function keys: will override Shelltool
	   .ttyswrc mechanism.

	   XVIEW does not recognise the Alt key as a modifier; to use it, one
	   approach is to bind it to an X11 keyboard modifier, mod2, say, and
	   then check the X event status.

	   For example:

		    xmodmap -e 'add mod2 = Alt_L'

	   Modifiers are checked in the following order: Alt, Ctrl, Shift; not
	   all possible combinations are handled in this example code.

	   To use F1, you may need to comment out the xmodmap command in
	   .xinitrc which binds it to the Help function.
	*/

	if (event_is_key_top(event) || event_is_key_left(event) || event_is_key_right(event))
	  { int    key = event_id(event) - KEY_LEFT(1);
	    char * str = Plain_fn[key];

	    /* Alt: assume bound to mod2 modifier */

	    if ((x -> xkey.state & Mod2Mask) != 0)
	      str = event_shift_is_down(event) ? AltS_fn[key] : Alt_fn[key];
	     else
	      if ((x -> xkey.state & Mod1Mask) != 0)
		str = event_shift_is_down(event) ? MetaS_fn[key] : Meta_fn[key];
	       else
		if (event_ctrl_is_down(event))
		  str = Ctrl_fn[key];
		 else
		  if (event_shift_is_down(event))
		    str = Shift_fn[key];

	    /* Send string to TTY */

	    if (str != NULL)
	      { ttysw_input(tty, str, strlen(str));
		return(NOTIFY_DONE);
	      }
	  }
#endif    
      }
    
    return(notify_next_event_func(window, event, arg, type));
  }

