/* 
 * Motif
 *
 * Copyright (c) 1987-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these librararies and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
*/ 
/* 
 * HISTORY
*/ 
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


#ifdef REV_INFO
#ifndef lint
static char rcsid[] = "$TOG: VirtKeys.c /main/22 1999/06/02 14:45:52 samborn $"
#endif
#endif
/* (c) Copyright 1990, 1991, 1992 HEWLETT-PACKARD COMPANY */
/* (c) Copyright 1990 MOTOROLA, INC. */

#ifndef X_NOT_STDC_ENV
#include <stdlib.h>
#endif

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <X11/keysym.h>
#include <Xm/DisplayP.h>
#include <Xm/TransltnsP.h>
#include <Xm/AtomMgr.h>
#include <Xm/XmosP.h>
#include "MapEventsI.h"
#include "VirtKeysI.h"
#include "XmosI.h"
#include "XmI.h"


#define defaultFallbackBindings _XmVirtKeys_fallbackBindingString

#define BUFFERSIZE	2048
#define MAXLINE		256

/********    Static Function Declarations    ********/

static Boolean CvtStringToVirtualBinding(Display *dpy,
					 XrmValuePtr args,
					 Cardinal *num_args,
					 XrmValuePtr fromVal,
					 XrmValuePtr toVal,
					 XtPointer *closure_ret);
static void FillBindingsFromDB(Display *dpy,
			       XrmDatabase rdb,
			       XmVKeyBinding *keys,
			       Cardinal *num_keys);
static Boolean GetBindingsProperty(Display *display,
				   String property,
				   String *binding);
static void FindVirtKey(Display *dpy,
                        KeyCode keycode,
                        Modifiers modifiers,
                        Modifiers *modifiers_return,
                        KeySym *keysym_return);
static Modifiers EffectiveStdModMask(Display *dpy,
				     KeySym *kc_map,
				     int ks_per_kc);
static void LoadVendorBindings(Display *display,
			       char *path,
			       FILE *fp,
			       String *binding);

/********    End Static Function Declarations    ********/

static XmConst XmVirtualKeysymRec virtualKeysyms[] =
{
  { XmVosfActivate,		osfXK_Activate	      },
  { XmVosfAddMode,		osfXK_AddMode	      },
  { XmVosfBackSpace,		osfXK_BackSpace	      },
  { XmVosfBackTab,		osfXK_BackTab	      }, /* Defunct */
  { XmVosfBeginData,		osfXK_BeginData	      }, /* Defunct */
  { XmVosfBeginLine,		osfXK_BeginLine	      },
  { XmVosfCancel,		osfXK_Cancel	      },
  { XmVosfClear,		osfXK_Clear	      },
  { XmVosfCopy,			osfXK_Copy	      },
  { XmVosfCut,			osfXK_Cut	      },
  { XmVosfDelete,		osfXK_Delete	      },
  { XmVosfDeselectAll,		osfXK_DeselectAll     },
  { XmVosfDown,			osfXK_Down	      },
  { XmVosfEndData,		osfXK_EndData	      }, /* Defunct */
  { XmVosfEndLine,		osfXK_EndLine	      },
  { XmVosfEscape,		osfXK_Escape	      }, /* Defunct */
  { XmVosfExtend,		osfXK_Extend	      }, /* Defunct */
  { XmVosfHelp,			osfXK_Help	      },
  { XmVosfInsert,		osfXK_Insert	      },
  { XmVosfLeft,			osfXK_Left	      },
  { XmVosfLeftLine,		osfXK_LeftLine	      }, /* X11R6 */
  { XmVosfMenu,			osfXK_Menu	      },
  { XmVosfMenuBar,		osfXK_MenuBar	      },
  { XmVosfNext,			osfXK_Next	      }, /* X11R6 */
  { XmVosfNextField,		osfXK_NextField	      }, /* Defunct */
  { XmVosfNextMenu,		osfXK_NextMenu	      }, /* Defunct */
  { XmVosfNextMinor,		osfXK_NextMinor	      }, /* X11R6 */
  { XmVosfPageDown,		osfXK_PageDown	      },
  { XmVosfPageLeft,		osfXK_PageLeft	      },
  { XmVosfPageRight,		osfXK_PageRight	      },
  { XmVosfPageUp,		osfXK_PageUp	      },
  { XmVosfPaste,		osfXK_Paste	      },
  { XmVosfPrevField,		osfXK_PrevField	      }, /* Defunct */
  { XmVosfPrevMenu,		osfXK_PrevMenu	      }, /* Defunct */
  { XmVosfPrimaryPaste,		osfXK_PrimaryPaste    },
  { XmVosfPrior,		osfXK_Prior	      }, /* X11R6 */
  { XmVosfPriorMinor,		osfXK_PriorMinor      }, /* X11R6 */
  { XmVosfQuickPaste,		osfXK_QuickPaste      },
  { XmVosfReselect,		osfXK_Reselect	      },
  { XmVosfRestore,		osfXK_Restore	      },
  { XmVosfRight,		osfXK_Right	      },
  { XmVosfRightLine,		osfXK_RightLine	      }, /* X11R6 */
  { XmVosfSelect,		osfXK_Select	      },
  { XmVosfSelectAll,		osfXK_SelectAll	      },
  { XmVosfSwitchDirection,	osfXK_SwitchDirection }, /* X11R6 */
  { XmVosfUndo,			osfXK_Undo	      },
  { XmVosfUp,			osfXK_Up	      }
};

static XmConst XmDefaultBindingStringRec fallbackBindingStrings[] =
{
  { "Acorn Computers Ltd", 
      _XmVirtKeys_acornFallbackBindingString },
  { "Apollo Computer Inc.",
      _XmVirtKeys_apolloFallbackBindingString },
  { "DECWINDOWS DigitalEquipmentCorp.",
      _XmVirtKeys_decFallbackBindingString },
  { "Data General Corporation  Rev 04",
      _XmVirtKeys_dgFallbackBindingString },
  { "Double Click Imaging, Inc. KeyX",
      _XmVirtKeys_dblclkFallbackBindingString },
  { "Hewlett-Packard Company",
      _XmVirtKeys_hpFallbackBindingString },
  { "International Business Machines",
      _XmVirtKeys_ibmFallbackBindingString },
  { "Intergraph Corporation",
      _XmVirtKeys_ingrFallbackBindingString },
  { "Megatek Corporation",
      _XmVirtKeys_megatekFallbackBindingString },
  { "Motorola Inc. (Microcomputer Division) ",
      _XmVirtKeys_motorolaFallbackBindingString },
  { "Silicon Graphics Inc.",
      _XmVirtKeys_sgiFallbackBindingString },
  { "Silicon Graphics",
      _XmVirtKeys_sgiFallbackBindingString },
  { "Siemens Munich by SP-4's Hacker Crew",
      _XmVirtKeys_siemensWx200FallbackBindingString },
  { "Siemens Munich (SP-4's hacker-clan)",
      _XmVirtKeys_siemens9733FallbackBindingString },
  { "Sun Microsystems, Inc.",
      _XmVirtKeys_sunFallbackBindingString },
  { "X11/NeWS - Sun Microsystems Inc.",
      _XmVirtKeys_sunFallbackBindingString },
  { "Tektronix, Inc.",
      _XmVirtKeys_tekFallbackBindingString }
};

/*ARGSUSED*/
static Boolean 
CvtStringToVirtualBinding(Display    *dpy,
			  XrmValuePtr args,
			  Cardinal   *num_args,
			  XrmValuePtr fromVal,
			  XrmValuePtr toVal,
			  XtPointer  *closure_ret )
{
    char 	 *str = (char *)fromVal->addr;
    XKeyEvent	  event;
    int		  count, tmp;
    int		 *eventTypes;
    KeySym       *keysyms;
    unsigned int *modifiers;
    int		  j;
    int		  codes_per_sym;
    KeyCode	  minK;
    Modifiers	  used_mods;

    /* Lookup codes_per_sym, and let Xt cache the result instead of */
    /* always downloading a new copy with XGetKeyboardMapping(). */
    /* This also initializes Xt's per-display data structures, so */
    /* we can use XtTranslateKey() instead of XLookupString(). */
    (void) XtGetKeysymTable(dpy, &minK, &codes_per_sym);

    count = _XmMapKeyEvents(str, &eventTypes, &keysyms, &modifiers);
    if (count > 0)
      {
	Boolean fini;

	for (tmp = 0; tmp < count; tmp++)
	  {
	    fini = False;  

	    /*
	     * Here's a nasty bit of code. If some vendor defines one of
	     * the standard modifiers to be the mode switch mod, the
	     * keysym returned by XtTranslateKey is the mode-shifted 
	     * one. This may or may not be bogus, but we have to live
	     * with it :-(. Soo, we need to translate the keysym to a
	     * keycode, then ask someone to translate the combo for us.
	     */
	    event.display = dpy;
	    event.keycode = XKeysymToKeycode(dpy, keysyms[tmp]);
	    
	    /*
	     * In case the guy specifies a symbol that is modified (like
	     * HP's Del which is <shift><escape>), we'll find the implied
	     * modifers and 'OR' it together with the explicitly stated
	     * modifers.
	     */
	    event.state = 0;
	    if (XKeycodeToKeysym(dpy, event.keycode, 0) != keysyms[tmp])
	      for (j = 1; j < codes_per_sym; j++)
		if (XKeycodeToKeysym(dpy, event.keycode, j) == keysyms[tmp])
		  {

                  /* 
		   * Gross Hack for Hobo keyboard .. 
		   * Assumptions: 
		   * 	1. Hobo keyboard has XK_Return  as the first entry
		   *		and XK_KP_Enter as the 4th entry in its
		   *            keycode to keysym key map.
		   *	2. This fix is only designed to work for the precise 
		   *            combination of the Sun server with vendor 
		   *            string "Sun Microsystems, Inc." and the Hobo 
		   *            keyboard, as the fix assumes knowledge of the 
		   *            server keycode to keysym key map.
		   */

		    if ((keysyms[tmp] == XK_KP_Enter) &&
                        (j == 4) &&
                        (XKeycodeToKeysym(dpy, event.keycode, 0) 
			    == XK_Return) &&
			(strcmp("Sun Microsystems, Inc.", ServerVendor(dpy)) 
			 == 0)) 
		    {
			fini = True;
		    } else
			event.state = 1 << (j-1);

		    break;
		  }

	    if (!fini) {
		event.state |= modifiers[tmp];
		XtTranslateKey(dpy, event.keycode, event.state, &used_mods,
			       keysyms + tmp);
	    }
	  }
	
	/* Fail if insufficient storage was provided. */
	if ((toVal->addr != NULL) &&
	    (toVal->size < sizeof(XmKeyBindingRec) * count))
	  {
	    toVal->size = sizeof(XmKeyBindingRec) * count;

	    XtFree((char*) eventTypes);
	    XtFree((char*) keysyms);
	    XtFree((char*) modifiers);
	    return False;
	  }

	/* Allocate storage if none was provided. */
	toVal->size = sizeof(XmKeyBindingRec) * count;
	if (toVal->addr == NULL)
	  toVal->addr = XtMalloc(toVal->size);

	/* Copy the data. */
	for (tmp = 0; tmp < count; tmp++)
	  {
	    ((XmKeyBinding) toVal->addr)[tmp].keysym = keysyms[tmp];
	    ((XmKeyBinding) toVal->addr)[tmp].modifiers = modifiers[tmp];
	  }

	XtFree((char*) eventTypes);
	XtFree((char*) keysyms);
	XtFree((char*) modifiers);
	return True;
      }

    /* The value supplied could not be converted. */
    XtDisplayStringConversionWarning(dpy, str, XmRVirtualBinding);
    return False;
}

static void
FillBindingsFromDB(Display       *dpy,
		   XrmDatabase    rdb,
		   XmVKeyBinding *keys,
		   Cardinal      *num_keys)
{
  XrmName 	    xrm_name[2];
  XrmClass 	    xrm_class[2];
  XrmRepresentation rep_type;
  XrmValue 	    value;
  Cardinal	    vk_num;
  XrmQuark	    XmQVirtualBinding = XrmPermStringToQuark(XmRVirtualBinding);
  XrmQuark	    XmQString         = XrmPermStringToQuark(XmRString);

  xrm_class[0] = XmQVirtualBinding;
  xrm_class[1] = 0;
  
  /* Previously entries in display.bindings corresponded exactly to */
  /* entries in virtualKeysyms.  Since multiple keysyms can be bound */
  /* to a single virtual key, display.num_bindings and the virtkey */
  /* field in XmVKeyBindingRec were added.  This also allows us to */
  /* remove null bindings from the table, improving search times. */

  /* Allocate the initial arrays. */
  *num_keys = 0;
  *keys = NULL;
  
  /* Process each known virtual key. */
  for (vk_num = 0; vk_num < XtNumber(virtualKeysyms); vk_num++)
    {
      Boolean	   free_keys = False;
      XmKeyBinding new_keys = NULL;
      Cardinal	   new_num = 0;

      xrm_name[0] = XrmPermStringToQuark(virtualKeysyms[vk_num].name);
      xrm_name[1] = 0;
      if (XrmQGetResource(rdb, xrm_name, xrm_class, &rep_type, &value))
	{
	  if (rep_type == XmQVirtualBinding)
	    {
	      /* Resource already exists in the desired format? */
	      new_keys = (XmKeyBinding) value.addr;
	      new_num = value.size / sizeof(XmKeyBindingRec);
	    }

	  else if (rep_type == XmQString) 
	    {
	      /* Convert String resource to key bindings. */
	      XrmValue toVal;
	      toVal.addr = NULL;
	      toVal.size = 0;
	      if (XtCallConverter(dpy, CvtStringToVirtualBinding,
				  NULL, 0, &value, &toVal, NULL))
		{
		  new_keys = (XmKeyBinding) toVal.addr;
		  new_num = toVal.size / sizeof(XmKeyBindingRec);
		  free_keys = True;
		}
	    }

	  /* Append the new bindings to the end of the table. */
	  if (new_num > 0)
	    {
	      int tmp;

	      *keys = (XmVKeyBinding) 
		XtRealloc((char*) *keys, 
			  (*num_keys + new_num) * sizeof(XmVKeyBindingRec)); 

	      for (tmp = 0; tmp < new_num; tmp++)
		{
		  (*keys)[*num_keys + tmp].keysym = new_keys[tmp].keysym;
		  (*keys)[*num_keys + tmp].modifiers = new_keys[tmp].modifiers;
		  (*keys)[*num_keys + tmp].virtkey =
		    virtualKeysyms[vk_num].keysym; 
		}

	      *num_keys += new_num;
	    }

	  if (free_keys)
	    XtFree((char *)new_keys);
	}
    }
}

static Boolean 
GetBindingsProperty(Display *display,
		    String   property,
		    String  *binding)
{
  char		*prop = NULL;
  Atom		actual_type;
  int		actual_format;
  unsigned long	num_items;
  unsigned long	bytes_after;


  if ( binding == NULL ) 
    return False;

  XGetWindowProperty (display, 
		      RootWindow(display, 0),
		      XInternAtom(display, property, FALSE),
		      0, (long)1000000,
		      FALSE, XA_STRING,
		      &actual_type, &actual_format,
		      &num_items, &bytes_after,
		      (unsigned char **) &prop);

  if ((actual_type != XA_STRING) ||
      (actual_format != 8) || 
      (num_items == 0))
    {
      if (prop != NULL) 
	XFree(prop);
      return False;
    }
  else
    {
      *binding = prop;
      return True;
    }
}
	   
/*
 * This routine is called by the XmDisplay Initialize method to set
 * up the virtual bindings table, XtKeyProc, and event handler.
 */
void 
_XmVirtKeysInitialize(Widget widget)
{
  XmDisplay xmDisplay = (XmDisplay) widget;
  Display *dpy = XtDisplay(xmDisplay);
  XrmDatabase keyDB;
  String bindingsString;
  String fallbackString = NULL;
  Boolean needXFree = False;
  
  if (!XmIsDisplay(widget))
    return;
  
  bindingsString = xmDisplay->display.bindingsString;
  xmDisplay->display.lastKeyEvent = NULL;
  
  if (bindingsString == NULL) 
    {
      /* XmNdefaultVirtualBindings not set, try _MOTIF_BINDINGS */
      if (GetBindingsProperty(XtDisplay(xmDisplay),
			      XmS_MOTIF_BINDINGS,
			      &bindingsString) == True) 
	{
	  needXFree = True;
	}
      else if (GetBindingsProperty(XtDisplay(xmDisplay),
				   XmS_MOTIF_DEFAULT_BINDINGS,
				   &bindingsString) == True) 
	{
	  needXFree = True;
	}
      else 
	{
	  /* property not set, find a useful fallback */
	  _XmVirtKeysLoadFallbackBindings(XtDisplay(xmDisplay),
					  &fallbackString);
	  bindingsString = fallbackString;
	}
    }
  
  XtSetTypeConverter(XmRString, XmRVirtualBinding, CvtStringToVirtualBinding, 
		     NULL, 0, XtCacheNone, (XtDestructor)NULL);
  keyDB = XrmGetStringDatabase( bindingsString );
  FillBindingsFromDB (XtDisplay(xmDisplay), keyDB,
		      &xmDisplay->display.bindings,
		      &xmDisplay->display.num_bindings);

  XrmDestroyDatabase(keyDB);
  if (needXFree) 
    XFree (bindingsString);
  if (fallbackString) 
    XtFree (fallbackString);
  
  XtSetKeyTranslator(dpy, (XtKeyProc)XmTranslateKey);
}

/*
 * This routine is called by the XmDisplay Destroy method to free
 * up the virtual bindings table.
 */
void 
_XmVirtKeysDestroy(Widget widget)
{
  XmDisplay xmDisplay = (XmDisplay) widget;

  XtFree((char*)xmDisplay->display.lastKeyEvent);
  XtFree((char*)xmDisplay->display.bindings);
}

static void 
FindVirtKey(Display *dpy,
	    KeyCode keycode,
	    Modifiers modifiers,
	    Modifiers *modifiers_return,
	    KeySym *keysym_return )
{
  Cardinal      i;
  XmDisplay     xmDisplay = (XmDisplay) XmGetXmDisplay( dpy);
  XmVKeyBinding keyBindings = xmDisplay->display.bindings;
  KeyCode       min_kcode;
  int           ks_per_kc;
  KeySym       *ks_table = XtGetKeysymTable( dpy, &min_kcode, &ks_per_kc);
  KeySym       *kc_map = &ks_table[(keycode - min_kcode) * ks_per_kc];
  Modifiers     EffectiveSMMask = EffectiveStdModMask( dpy, kc_map, ks_per_kc);
  
  /* Get the modifiers from the actual event */
  Modifiers VirtualStdMods = 0;
  Modifiers StdModMask;

  for (i = 0; i < xmDisplay->display.num_bindings; i++)
    {
      unsigned j = ks_per_kc;
      KeySym vks = keyBindings[i].keysym;
      
      if (vks)
	{
	  while (j--)
	    {
	      /* Want to walk through keymap (containing all possible
	       * keysyms generated by this keycode) to compare against
	       * virtual key keysyms.  Any keycode that can possibly
	       * generate a virtual keysym must be sure to return all
	       * modifiers that are in the virtual key binding, since
	       * this means that those modifiers are now part of the
	       * "standard modifiers" for this keycode.  (A "standard
	       * modifier" is a modifier that can affect which keysym
	       * is generated from a particular keycode.)
	       */
	      if ((j == 1)  &&  (kc_map[j] == NoSymbol))
		{   
		  KeySym uc, lc;
		  
		  XtConvertCase( dpy, kc_map[0], &lc, &uc);
		  if ((vks == lc)  ||  (vks == uc))
		    VirtualStdMods |= keyBindings[i].modifiers;
		  break;
		} 
	      else if (vks == kc_map[j])
		{
		  /* The keysym generated by this keycode can possibly
		   * be influenced by the virtual key modifier(s), so must
		   * add the modifier(s) associated with this virtual
		   * key to the returned list of "standard modifiers".
		   * The Intrinsics requires that the set of modifiers
		   * returned by the keyproc is constant for a given
		   * keycode.
		   */
		  VirtualStdMods |= keyBindings[i].modifiers;
		  break;
		}
	    } 
	}
    }
  
  /* Don't want to return standard modifiers that do not
   * impact the keysym selected for a particular keycode,
   * since this blocks matching of translation productions
   * which use ":" style translations with the returned
   * standard modifier in the production.  The ":" style
   * of production is essential for proper matching of
   * Motif translations (PC numeric pad, for instance).
   *
   * Recent fixes to the Intrinsics should have included this
   * change to the set of standard modifiers returned from
   * the default key translation routine, but could not be
   * done for reasons of backwards compatibility (which is
   * not an issue for Xm, since we do not export this facility).
   * So, we do the extra masking here after the return from
   * the call to XtTranslateKey.
   */
  *modifiers_return &= EffectiveSMMask;
  
  /* Modifiers present in the virtual binding table for the
   * keysyms associated with this keycode, which are or might
   * have been used to change the keysym generated by this
   * keycode (to a virtual keysym), must be included in the
   * returned set of standard modifiers.  Remember that "standard
   * modifiers" for a keycode are those modifiers that can affect
   * which keysym is generated by that keycode.
   */
  *modifiers_return |= VirtualStdMods;
  
  /* Effective standard modifiers that are set in the event
   * will be 0 in the following bit mask, which will be used
   * to collapse conflicting modifiers in the virtual
   * key binding table, as described below.
   */
  StdModMask = ~(modifiers & EffectiveSMMask);
  
  for (i = 0; i < xmDisplay->display.num_bindings; i++)
    {
      XmVKeyBinding currBinding = &keyBindings[i];
      KeySym vks = currBinding->keysym;
      
      /* The null binding should not be interpreted as a match
       * keysym is zero (e.g. pre-edit terminator)
       */
      /* Those modifiers that are effective standard modifiers and
       * that are set in the event will be ignored in the following
       * conditional (i.e., the state designated in the virtual key
       * binding will not be considered), since these modifiers have
       * already had their affect in the determination of the value
       * of *keysym_return.  This allows matching that is consistent
       * with industry-standard interpretation for keys such as
       * those of the PC-style numeric pad.  This apparent loss of
       * binding semantics is an unavoidable consequence of specifying
       * a modifier in the virtual binding table that is already being
       * used to select one of several keysyms associated with a
       * particular keycode (usually as printed on the keycap).
       * The disambiguation of the collapsing of key bindings
       * is based on "first match" in the virtual key binding table.
       */
      if (vks && (vks == *keysym_return) &&
	  ((currBinding->modifiers & StdModMask) ==
	   (modifiers & VirtualStdMods & StdModMask)))
	{
	  *keysym_return = currBinding->virtkey;
	  break;
	}
    }
}

static Modifiers
EffectiveStdModMask(Display *dpy,
		    KeySym *kc_map,
		    int ks_per_kc)
{
  /* This routine determines which set of modifiers can possibly
   * impact the keysym that is generated by the keycode associated
   * with the keymap passed in.  The basis of the algorithm used
   * here is described in section 12.7 "Keyboard Encoding" of the
   * R5 "Xlib - C Language X Interface" specification.
   */
  KeySym uc;
  KeySym lc;
  
  /* Since the group modifier can be any of Mod1-Mod5 or Control, we will
   * return all of these bits if the group modifier is found to be effective.
   * Lock will always be returned (for backwards compatibility with
   * productions assuming that Lock is always a "don't care" modifier
   * for non-alphabetic keys).  Shift will be returned unless it has
   * no effect on the selection of keysyms within either group.
   */
  Modifiers esm_mask = (Mod5Mask | Mod4Mask | Mod3Mask | Mod2Mask | Mod1Mask |
			ControlMask | LockMask | ShiftMask);
  switch (ks_per_kc)
    {
    default:	/* CR 8799: Ignore non-standard modifier groups. */
    case 4:
      if (kc_map[3] != NoSymbol)
	{
	  /* The keysym in position 4 is selected when the group
	   * modifier is set and the Shift (or Lock) modifier is
	   * set, so both Shift/Lock and the group modifiers are
	   * all "effective" standard modifiers.
	   */
	  break;
	} 
    case 3:
      if (kc_map[2] == NoSymbol)
	{
	  /* Both Group 2 keysyms are NoSymbol, so the group
	   * modifier has no effect; only Shift and Lock remain
	   * as possible effective modifiers.
	   */
	  esm_mask = ShiftMask | LockMask;
	}
      else
	{
	  XtConvertCase( dpy, kc_map[2], &lc, &uc);
	  if (lc != uc)
	    {   
	      /* The Group 2 keysym is case-sensitive, so group
	       * modifiers and Shift/Lock modifiers are effective.
	       */
	      break;
	    }
	} 
      /* At this fall-through, the group modifier bits have been
       * decided, while the case is still out on Shift/Lock.
       */
    case 2:
      if (kc_map[1] != NoSymbol)
	{
	  /* Shift/Lock modifier selects keysym from Group 1,
	   * so leave those bits in the mask.  The group modifier
	   * was determined above, so leave those bits in the mask.
	   */
	  break;
	} 
    case 1:
      if (kc_map[0] != NoSymbol)
	{
	  XtConvertCase( dpy, kc_map[0], &lc, &uc);
	  if (lc != uc)
	    {
	      /* The Group 1 keysym is case-sensitive, so Shift/Lock
	       * modifiers are effective.
	       */
	      break;
	    }
	} 
      /* If we did not break out of the switch before this, then
       * the Shift modifier is not effective; mask it out.
       */
      esm_mask &= ~ShiftMask;
    case 0:
      break;
    } 

  return esm_mask;
}

void
XmTranslateKey(Display     *dpy,
#if NeedWidePrototypes
	       unsigned int keycode,
#else
	       KeyCode      keycode,
#endif /* NeedWidePrototypes */
	       Modifiers    modifiers,
	       Modifiers   *modifiers_return,
	       KeySym      *keysym_return )
{
  _XmDisplayToAppContext(dpy);
  _XmAppLock(app);
  XtTranslateKey(dpy, keycode, modifiers, modifiers_return, keysym_return);

  FindVirtKey(dpy, keycode, modifiers, modifiers_return, keysym_return);
  _XmAppUnlock(app);
}

int
XmeVirtualToActualKeysyms(Display      *dpy,
			  KeySym        virtKeysym,
			  XmKeyBinding *actualKeyData)
{
  int           matches;
  Cardinal      index;
  XmDisplay     xmDisplay = (XmDisplay)XmGetXmDisplay (dpy);
  XmVKeyBinding keyBindings = xmDisplay->display.bindings;
  _XmDisplayToAppContext(dpy);
  
  _XmAppLock(app);
  /* Initialize the return parameters. */
  *actualKeyData = NULL;

  /* Count the number of matches. */
  matches = 0;
  for (index = 0; index < xmDisplay->display.num_bindings; index++)
    if (keyBindings[index].virtkey == virtKeysym)
      matches++;

  /* Allocate the return array. */
  if (matches > 0)
    {
      *actualKeyData = (XmKeyBinding) 
	XtMalloc(matches * sizeof(XmKeyBindingRec));

      matches = 0;
      for (index = 0; index < xmDisplay->display.num_bindings; index++)
	if (keyBindings[index].virtkey == virtKeysym)
	  {
	    (*actualKeyData)[matches].keysym = keyBindings[index].keysym;
	    (*actualKeyData)[matches].modifiers = keyBindings[index].modifiers;
	    matches++;
	  }
    }

  _XmAppUnlock(app);
  return matches;
}

Boolean 
_XmVirtKeysLoadFileBindings(char   *fileName,
			    String *binding )
{
  FILE *fileP;
  int buffersize;
  int count;
  int firsttime;
  char line[256];
  Boolean skip;
  
  if ((fileP = fopen (fileName, "r")) != NULL) 
    {
      skip = False;
      count = 0;
      buffersize = 1;
      firsttime = 1;

      while (fgets(line, sizeof(line), fileP) != NULL) {

	/* handle '!' comments; they can extend across mutliple reads */
	if (skip) {
	  if (line[strlen(line) - 1] == '\n') skip = False;
	  continue;
	}
	if (line[0] == '!') {
	  if (line[strlen(line) - 1] == '\n') continue;
	  else {
	    skip = True;
	    continue;
	  }	    
	}

	/* must be >=, because buffersize is always 1 bigger for '\0' */
	if (count + strlen(line) >= buffersize) {
	  buffersize += BUFFERSIZE;
	  *binding = XtRealloc(*binding, buffersize);

	  /* always make sure that the end of *binding is null terminated */
	  if (firsttime) { 
	    *binding[0] = '\0';
	    firsttime = 0;
	  }
	}

	count += strlen(line);

	strcat(*binding, line);
      }
     
      /* trim unused buffer space */
      *binding = XtRealloc (*binding, count + 1);

      fclose (fileP);
      return True;
    }

  return False;
}

static void 
LoadVendorBindings(Display *display,
		   char    *path,
		   FILE    *fp,
		   String  *binding )
{
  char buffer[MAXLINE];
  char *bindFile;
  char *vendor;
  char *vendorV;
  char *ptr;
  char *start;
  
  vendor = ServerVendor(display);
  vendorV = XtMalloc (strlen(vendor) + 20); /* assume rel.# is < 19 digits */
  sprintf (vendorV, "%s %d", vendor, VendorRelease(display));
  
  while (fgets (buffer, MAXLINE, fp) != NULL) 
    {
      ptr = buffer;
      while (*ptr != '"' && *ptr != '!' && *ptr != '\0') 
	ptr++;
      if (*ptr != '"') 
	continue;

      start = ++ptr;
      while (*ptr != '"' && *ptr != '\0') 
	ptr++;
      if (*ptr != '"') 
	continue;

      *ptr = '\0';
      if ((strcmp (start, vendor) == 0) || (strcmp (start, vendorV) == 0)) 
	{
	  ptr++;
	  while (isspace((unsigned char)*ptr) && *ptr) 
	    ptr++;
	  if (*ptr == '\0') 
	    continue;

	  start = ptr;
	  while (!isspace((unsigned char)*ptr) && *ptr != '\n' && *ptr)
	    ptr++;
	  *ptr = '\0';

	  bindFile = _XmOSBuildFileName (path, start);
	  if (_XmVirtKeysLoadFileBindings (bindFile, binding)) 
	    {
	      XtFree (bindFile);
	      break;
	    }
	  XtFree (bindFile);
	}
    }

  XtFree (vendorV);
}

int 
_XmVirtKeysLoadFallbackBindings(Display	*display,
				String	*binding )
{
  enum { XmA_MOTIF_BINDINGS, XmA_MOTIF_DEFAULT_BINDINGS, NUM_ATOMS };
  static char *atom_names[] = {XmS_MOTIF_BINDINGS, XmS_MOTIF_DEFAULT_BINDINGS};

  XmConst XmDefaultBindingStringRec *currDefault;
  int i;
  FILE *fp;
  char *homeDir;
  char *fileName;
  char *bindDir;
  static XmConst char xmbinddir_fallback[] = XMBINDDIR_FALLBACK;
  Atom atoms[XtNumber(atom_names)];
  
  *binding = NULL;
  
  assert(XtNumber(atom_names) == NUM_ATOMS);
  XInternAtoms(display, atom_names, XtNumber(atom_names), False, atoms);

  /* Load .motifbind - necessary, if mwm and xmbind are not used */
  homeDir = XmeGetHomeDirName();
  fileName = _XmOSBuildFileName(homeDir, MOTIFBIND);
  _XmVirtKeysLoadFileBindings(fileName, binding);
  XtFree(fileName);
  
  /* Look for a match in the user's xmbind.alias */
  if (*binding == NULL) 
    {
      fileName = _XmOSBuildFileName (homeDir, XMBINDFILE);
      if ((fp = fopen (fileName, "r")) != NULL) 
	{
	  LoadVendorBindings (display, homeDir, fp, binding);
	  fclose (fp);
	}
      XtFree (fileName);
    }
  
  if (*binding != NULL) 
    {
      /* Set the user property for future Xm applications. */
      XChangeProperty (display, RootWindow(display, 0),
		       atoms[XmA_MOTIF_BINDINGS],
		       XA_STRING, 8, PropModeReplace,
		       (unsigned char *)*binding, strlen(*binding));
      return 0;
    }
  
  /* Look for a match in the system xmbind.alias */
  if (*binding == NULL) 
    {
      if ((bindDir = getenv(XMBINDDIR)) == NULL)
	bindDir = (char*) xmbinddir_fallback;
      fileName = _XmOSBuildFileName (bindDir, XMBINDFILE);
      if ((fp = fopen (fileName, "r")) != NULL) 
	{
	  LoadVendorBindings (display, bindDir, fp, binding);
	  fclose (fp);
	}
      XtFree (fileName);
    }
  
  /* Check hardcoded fallbacks (for 1.1 bc) */
  if (*binding == NULL) 
    {
      for (i = 0, currDefault = fallbackBindingStrings;
	   i < XtNumber(fallbackBindingStrings);
	   i++, currDefault++) 
	{
	  if (strcmp(currDefault->vendorName, ServerVendor(display)) == 0) 
	    {
	      *binding = XtMalloc (strlen (currDefault->defaults) + 1);
	      strcpy (*binding, currDefault->defaults);
	      break;
	    }
	}
    }
  
  /* Use generic fallback bindings */
  if (*binding == NULL) 
    {
      *binding = XtMalloc (strlen (defaultFallbackBindings) + 1);
      strcpy (*binding, defaultFallbackBindings);
    }
  
  /* Set the fallback property for future Xm applications */
  XChangeProperty (display, RootWindow(display, 0),
		   atoms[XmA_MOTIF_DEFAULT_BINDINGS],
		   XA_STRING, 8, PropModeReplace,
		   (unsigned char *)*binding, strlen(*binding));
  
  return 0;
}
