#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/XKBlib.h>
#include <Xm/MainW.h>
#include <Xm/RowColumn.h>
#include <Xm/ToggleB.h>

Display         *theDisplay;
XtAppContext    appContext;
int		xkbEventBase;
Widget		topLevel;
Widget		leds[XkbNumIndicators];
Atom		ledAtoms[XkbNumIndicators];
XmString	ledNames[XkbNumIndicators];
XkbDescPtr	xkb_desc;

void            valueChangedProc(Widget,XtPointer,XmToggleButtonCallbackStruct *);
XtCallbackRec   valueChangedCB[2]={(XtCallbackProc)valueChangedProc,NULL};

/************************************************************************/
/*									*/
/* Application Resources						*/
/*									*/
/************************************************************************/
#define	YES		1
#define	NO		0
#define	DONT_CARE	-1

typedef struct 
{
    int		wanted;
    int		wantAutomatic;
    int		wantExplicit;
    int		wantNamed;
    int		wantReal;
    int		wantVirtual;
    int		useUnion;
} OptionsRec;

OptionsRec options;

#define Offset(field) XtOffsetOf(OptionsRec,field)
XtResource resources[] = 
{
    {"wanted", "Wanted", XtRInt, sizeof(int),
     Offset(wanted), XtRImmediate, (XtPointer) DONT_CARE },
    {"wantAutomatic", "WantAutomatic", XtRInt, sizeof(int),
     Offset(wantAutomatic), XtRImmediate, (XtPointer)   DONT_CARE},
    {"wantExplicit", "WantExplicit", XtRInt, sizeof(int),
     Offset(wantExplicit), XtRImmediate, (XtPointer)    DONT_CARE},
    {"wantNamed", "WantNamed", XtRInt, sizeof(int),
     Offset(wantNamed), XtRImmediate, (XtPointer)       DONT_CARE},
    {"wantReal", "WantReal", XtRInt, sizeof(int),
     Offset(wantReal), XtRImmediate, (XtPointer)        DONT_CARE},
    {"wantVirtual", "WantVirtual", XtRInt, sizeof(int),
     Offset(wantVirtual), XtRImmediate, (XtPointer)     DONT_CARE},
    {"useUnion", "UseUnion", XtRInt, sizeof(int),
     Offset(useUnion), XtRImmediate, (XtPointer)        YES},
    NULL
};
#undef Offset

String fallbackResources[] = 
{
    "*mainWindow.width: 100",
    "*mainWindow.height: 50",
    NULL
};

XrmOptionDescRec optionDesc[] = 
{
    {"-watch", "*wanted", XrmoptionSepArg, (XtPointer) "0"},
    {"-automatic", "*wantAutomatic", XrmoptionNoArg, (XtPointer) "0"},
    {"+automatic", "*wantAutomatic", XrmoptionNoArg, (XtPointer) "1"},
    {"-explicit", "*wantExplicit", XrmoptionNoArg, (XtPointer) "0"},
    {"+explicit", "*wantExplicit", XrmoptionNoArg, (XtPointer) "1"},
    {"-named", "*wantNamed", XrmoptionNoArg, (XtPointer) "0"},
    {"+named", "*wantNamed", XrmoptionNoArg, (XtPointer) "1"},
    {"-real", "*wantReal", XrmoptionNoArg, (XtPointer) "0"},
    {"+real", "*wantReal", XrmoptionNoArg, (XtPointer) "1"},
    {"-virtual", "*wantVirtual", XrmoptionNoArg, (XtPointer) "0"},
    {"+virtual", "*wantVirtual", XrmoptionNoArg, (XtPointer) "1"},
    {"-intersection", "*useUnion", XrmoptionNoArg, (XtPointer) "0"},
    {"-union", "*useUnion", XrmoptionNoArg, (XtPointer) "1"}
};

/************************************************************************/
/*									*/
/* usage								*/
/*									*/
/************************************************************************/
void usage(char *program)
{
    printf("Usage: %s <options>\n",program);
    printf("Legal options include the usual X toolkit options plus:\n");
    printf("  -help           Print this message\n");
    printf("  -indpy <name>   Name of display to watch\n");
    printf("  -watch <leds>   Mask of LEDs to watch\n");
    printf("  [-+]automatic   (Don't) watch automatic LEDs\n");
    printf("  [-+]explicit    (Don't) watch explicit LEDs\n");
    printf("  [-+]named       (Don't) watch named LEDs\n");
    printf("  [-+]real        (Don't) watch real LEDs\n");
    printf("  [-+]virtual     (Don't) watch virtual LEDs\n");
    printf("  -intersection   Watch only LEDs in all desired sets\n");
    printf("  -union          Watch LEDs in any desired sets\n");
    printf("The default set of LEDs is -intersection +named +virtual\n");
    return;
}
/************************************************************************/
/*									*/
/*  XkbEventHandler	 						*/
/*									*/
/*  DESCRIPTION:							*/
/*									*/
/*      Handles events generated by the Xkb server extension.		*/
/*									*/
/************************************************************************/
Boolean XkbEventHandler(XEvent *event)
{
    XkbEvent 		*xkbEv = (XkbEvent *) event;
    
    if (xkbEv->any.xkb_type==XkbIndicatorStateNotify)  {
	register int 		i;
	register unsigned	bit;
	for (i=0,bit=1;i<XkbNumIndicators;i++,bit<<=1)
	    if ((xkbEv->indicators.changed&bit)&&(leds[i])) 
	    {
		if (xkbEv->indicators.state&bit)
		    XmToggleButtonSetState(leds[i],True,False);
		else
		    XmToggleButtonSetState(leds[i],False,False);
	    }
    }
    else if (xkbEv->any.xkb_type==XkbIndicatorMapNotify) {
	    unsigned change= xkbEv->indicators.changed;

	    if (XkbGetIndicatorMap(theDisplay,change,xkb_desc)!=Success) 
		fprintf(stderr,"Couldn't get changed indicator maps\n");
    }
      
    return True;
    
} /* XkbEventHandler */

/************************************************************************/
/*									*/
/* InitXkb								*/
/*									*/
/************************************************************************/
Boolean InitXkb(Display *theDisplay)
{
    int			i,opcode,errorBase,major,minor;
    XkbDescPtr 		xkb;
    unsigned int	bit;
    unsigned int	real,virtual,named,explicit,automatic;
    char 		*name;

    if (!XkbQueryExtension(theDisplay,
			   &opcode,
			   &xkbEventBase,
			   &errorBase,
			   &major,
			   &minor))
	return False;

    if (!XkbUseExtension(theDisplay,&major,&minor))
	return False;

    XkbSelectEvents(theDisplay,
		    XkbUseCoreKbd,
		    XkbIndicatorStateNotifyMask|XkbIndicatorMapNotifyMask,
		    XkbIndicatorStateNotifyMask|XkbIndicatorMapNotifyMask);

    XtSetEventDispatcher(theDisplay,
			 xkbEventBase+XkbEventCode,
			 XkbEventHandler);

    xkb=XkbGetMap(theDisplay,0,XkbUseCoreKbd);
    real=virtual=named=explicit=automatic=0;

    if (!xkb) 
    {
	fprintf(stderr,"Couldn't get keymap\n");
	return False;
    }
    if (XkbGetIndicatorMap(theDisplay,XkbAllIndicatorsMask,xkb)!=Success) 
    {
	fprintf(stderr,"Couldn't read indicator map\n");
	XkbFreeKeyboard(xkb,XkbAllComponentsMask,True);
	return False;
    }
    real=virtual=named=explicit=automatic=0;

    if (XkbGetNames(theDisplay,XkbIndicatorNamesMask,xkb)!=Success) 
    {
	fprintf(stderr,"Couldn't read indicator names\n");
	XkbFreeKeyboard(xkb,XkbAllComponentsMask,True);
	return False;
    }
    real=virtual=named=explicit=automatic=0;

    for (i=0,bit=1;i<XkbNumIndicators;i++,bit<<=1) 
    {
	XkbIndicatorMapPtr map= &xkb->indicators->maps[i];
	name = NULL;
	if (xkb->names->indicators[i]!=None)
	{
	    named|= bit;
	    name = XGetAtomName(theDisplay,xkb->names->indicators[i]);
	}
	if (name != NULL)
        {
	    ledAtoms[i] = xkb->names->indicators[i];
	    ledNames[i] = XmStringCreate(name,XmSTRING_DEFAULT_CHARSET);
	}
	else
	{
	    char temp[12];	
	    sprintf(temp,"led%d\0",i+1);
	    ledAtoms[i] = None;
	    ledNames[i] = XmStringCreate(temp,XmSTRING_DEFAULT_CHARSET);
	}
	if (xkb->indicators->phys_indicators&bit)
	    real|= bit;
	if ((((map->which_groups!=0)&&(map->groups!=0))||
	     ((map->which_mods!=0)&&
			((map->mods.real_mods!=0)||(map->mods.vmods!=0)))||
	     (map->ctrls!=0))&&
	    ((map->flags&XkbIM_NoAutomatic)==0)) {
	    automatic|= bit;
	}
	else explicit|= bit;
    }

    virtual = ~real;

    if (options.useUnion)
    {
        if ((options.wantReal==NO)      || (options.wantReal==DONT_CARE))
	    real = 0;
        if ((options.wantVirtual==NO)   || (options.wantVirtual==DONT_CARE))
	    virtual = 0;
        if ((options.wantNamed==NO)     || (options.wantNamed==DONT_CARE))
	    named = 0;
        if ((options.wantAutomatic==NO) || (options.wantAutomatic==DONT_CARE))
	    automatic = 0;
        if ((options.wantExplicit==NO)  || (options.wantExplicit==DONT_CARE))
	    explicit = 0;

	options.wanted |= real|virtual|named|automatic|explicit;
    } 
    else 
    {
	if (options.wanted == DONT_CARE)
	    options.wanted = ~0;

        if (options.wantReal==NO)
	    real = ~real;
	else if (options.wantReal==DONT_CARE)
	    real = ~0;

        if (options.wantVirtual==NO)
	    virtual = ~virtual;
        else if (options.wantVirtual==DONT_CARE)
	    virtual = ~0;

        if (options.wantNamed==NO)
	    named = ~named;
        else if (options.wantNamed==DONT_CARE)
	    named = ~0;

        if (options.wantAutomatic==NO)
	    automatic = ~automatic;
        else if (options.wantAutomatic==DONT_CARE)
	    automatic = ~0;

        if (options.wantExplicit==NO)
	    explicit = ~explicit;
        else if (options.wantExplicit==DONT_CARE)
	    explicit = ~0;

        options.wanted &= real&virtual&named&automatic&explicit;
    }

    XkbFreeKeyboard(xkb,XkbAllComponentsMask,True);
    return True;
    
} /* InitXkb */

/************************************************************************/
/*                                                                      */
/* valueChangedProc - called when a toggle button is pressed.           */
/*                                                                      */
/************************************************************************/
void valueChangedProc(Widget                    	w, 
                      XtPointer                 	clientData, 
                      XmToggleButtonCallbackStruct      *callbackData)
{
    int         	led = (int) clientData;
    XkbDescPtr		xkb;

    xkb = XkbGetMap(theDisplay,0,XkbUseCoreKbd);
    if (!xkb)
    {
	fprintf(stderr,"XkbGetMap failed\n");
        return;
    }
    
    if (XkbGetIndicatorMap(theDisplay,XkbAllIndicatorsMask,xkb)!=Success)
    {
	fprintf(stderr,"GetIndicatorMap failed\n");
	XkbFreeKeyboard(xkb,XkbAllComponentsMask,True);
	return;
    }

    /* The 'flags' field tells whether this indicator is automatic
     * (XkbIM_NoExplicit - 0x80), explicit (XkbIM_NoAutomatic - 0x40),
     * or neither (both - 0xC0).
     *
     * If NoAutomatic is set, the server ignores the rest of the 
     * fields in the indicator map (i.e. it disables automatic control 
     * of the LED).   If NoExplicit is set, the server prevents clients 
     * from explicitly changing the value of the LED (using the core 
     * protocol *or* XKB).   If NoAutomatic *and* NoExplicit are set, 
     * the LED cannot be changed (unless you change the map first).   
     * If neither NoAutomatic nor NoExplicit are set, the server will 
     * change the LED according to the indicator map, but clients can 
     * override that (until the next automatic change) using the core 
     * protocol or XKB.
     */
    switch (xkb->indicators->maps[led].flags &
	    (XkbIM_NoExplicit|XkbIM_NoAutomatic)) 
    {
	case XkbIM_NoExplicit|XkbIM_NoAutomatic:
	{
	    XmToggleButtonSetState(w,!callbackData->set,FALSE);
	    XkbFreeKeyboard(xkb,XkbAllComponentsMask,True);
	    return;
	}

	case XkbIM_NoAutomatic:
	{
	    if (ledAtoms[led] != None)
		XkbSetNamedIndicator(theDisplay,XkbUseCoreKbd,
				     ledAtoms[led],callbackData->set,
				     FALSE,NULL);
	    else
	    {
		XKeyboardControl	xkc;
		xkc.led= led;
		if (callbackData->set)
		     xkc.led_mode= LedModeOn;
		else xkc.led_mode= LedModeOff;
		XChangeKeyboardControl(theDisplay,KBLed|KBLedMode,&xkc);
		XSync(theDisplay,0);
	    }

	    XkbFreeKeyboard(xkb,XkbAllComponentsMask,True);
	    return;
	}
	
	case XkbIM_NoExplicit:
	break;
    }
    
    /* The 'ctrls' field tells what controls tell this indicator to
     * to turn on:  RepeatKeys (0x1), SlowKeys (0x2), BounceKeys (0x4),
     *              StickyKeys (0x8), MouseKeys (0x10), AccessXKeys (0x20),
     *		    TimeOut (0x40), Feedback (0x80), ToggleKeys (0x100),
     *		    Overlay1 (0x200), Overlay2 (0x400), GroupsWrap (0x800),
     *		    InternalMods (0x1000), IgnoreLockMods (0x2000),
     *		    PerKeyRepeat (0x3000), or ControlsEnabled (0x4000)
     */
    if (xkb->indicators->maps[led].ctrls)
    {
	unsigned long which = xkb->indicators->maps[led].ctrls;

	XkbGetControls(theDisplay,XkbAllControlsMask,xkb);
	if (callbackData->set)
	    xkb->ctrls->enabled_ctrls |= which;
	else
	    xkb->ctrls->enabled_ctrls &= ~which;
	XkbSetControls(theDisplay,which|XkbControlsEnabledMask,xkb);
    }
    
    /* The 'which_groups' field tells when this indicator turns on
     * for the 'groups' field:  base (0x1), latched (0x2), locked (0x4),
     *                          or effective (0x8).
     */
    if (xkb->indicators->maps[led].groups)
    {
	int		i;
       	unsigned int	group = 1;

	/* Turning on a group indicator is kind of tricky.  For
	 * now, we will just Latch or Lock the first group we find
	 * if that is what this indicator does.  Otherwise, we're
	 * just going to punt and get out of here.
	 */
	if (callbackData->set)
	{
	    for (i = XkbNumKbdGroups-1; i >= 0; i--)
		if ((1 << i) & 
		    xkb->indicators->maps[led].groups)
		    group = i;
	    if (xkb->indicators->maps[led].which_groups &
		(XkbIM_UseLocked | XkbIM_UseEffective))
		XkbLockGroup(theDisplay,XkbUseCoreKbd,group);
	    else if (xkb->indicators->maps[led].which_groups&XkbIM_UseLatched)
		XkbLatchGroup(theDisplay,XkbUseCoreKbd,group);
	    else
	    {
		XmToggleButtonSetState(w,!callbackData->set,FALSE);
		XkbFreeKeyboard(xkb,XkbAllComponentsMask,True);
		return;
	    }
	}
	/* Turning off a group indicator will mean that we just
	 * Lock the first group that this indicator doesn't watch.
	 */
	else
	{
	    for (i = XkbNumKbdGroups-1; i >= 0; i--)
		if (!((1 << i) & 
		      xkb->indicators->maps[led].groups))
		    group = i;
	    XkbLockGroup(theDisplay,XkbUseCoreKbd,group);
	}
    }
    
    /* The 'which_mods' field tells when this indicator turns on
     * for the modifiers:  base (0x1), latched (0x2), locked (0x4),
     *                     or effective (0x8).
     *
     * The 'real_mods' field tells whether this turns on when one of 
     * the real X modifiers is set:  Shift (0x1), Lock (0x2), Control (0x4),
     * Mod1 (0x8), Mod2 (0x10), Mod3 (0x20), Mod4 (0x40), or Mod5 (0x80). 
     *
     * The 'virtual_mods' field tells whether this turns on when one of
     * the virtual modifiers is set.
     *
     * The 'mask' field tells what real X modifiers the virtual_modifiers
     * map to?
     */
    if (xkb->indicators->maps[led].mods.real_mods ||
	xkb->indicators->maps[led].mods.mask)
    {
	XkbStateRec 	state;
	unsigned int	affect,mods;
	
	affect = (xkb->indicators->maps[led].mods.real_mods |
		  xkb->indicators->maps[led].mods.mask);
	
	if (callbackData->set)
	    mods = affect;
	else
	    mods = 0;
	
	if (xkb->indicators->maps[led].which_mods &
	    (XkbIM_UseLocked | XkbIM_UseEffective))
	    XkbLockModifiers(theDisplay,XkbUseCoreKbd,affect,mods);
	else if (xkb->indicators->maps[led].which_mods &
		 XkbIM_UseLatched)
	    XkbLatchModifiers(theDisplay,XkbUseCoreKbd,affect,mods);
	else
	{
	    XmToggleButtonSetState(w,!callbackData->set,FALSE);
	    XkbFreeKeyboard(xkb,XkbAllComponentsMask,True);
	    return;
	}
    }

    XkbFreeKeyboard(xkb,XkbAllComponentsMask,True);

} /* valueChangedProc */

/************************************************************************/
/*									*/
/* InitializeUI								*/
/*									*/
/************************************************************************/
void InitializeUI(Widget topLevel)
{
    Arg 		argList[3];
    char 		buf[256];
    int 		i;
    unsigned int	bit,n;
    Widget 		mainWindow,rowColumn;
    XmString		tempString;

    mainWindow = (Widget) XmCreateMainWindow(topLevel,"mainWindow",NULL,0);
    XtManageChild(mainWindow);
    rowColumn  = (Widget) XmCreateRowColumn(mainWindow,"rowColumn",NULL,0);
    XtManageChild(rowColumn);

    XkbGetIndicatorState(theDisplay,XkbUseCoreKbd,&n);
    for (i=0,bit=1;i<XkbNumIndicators;i++,bit<<=1) 
    {
	if (options.wanted&bit) 
	{
            /* [[[ WDW - If we wanted to be really fancy, we
	     *     would look for a "*ledxx.labelString" value
	     *     in the resource database so the I18N dudes
	     *	   can see localized strings. ]]]
	     */
	    XtSetArg(argList[0], XmNlabelString,ledNames[i]);
	    if (n&bit) XtSetArg(argList[1], XmNset, True);
	    else       XtSetArg(argList[1], XmNset, False);
	    sprintf(buf,"led%d\0",i);
            valueChangedCB[0].closure = (XtPointer) i;
	    XtSetArg(argList[2], XmNvalueChangedCallback, valueChangedCB);
	    leds[i]= XmCreateToggleButton(rowColumn,buf,argList,3);
	    XtManageChild(leds[i]);
	}
	else	    
	    leds[i]=0;
    }

} /* InitializeUI */

/************************************************************************/
/*									*/
/* main									*/
/*									*/
/************************************************************************/
#if NeedFunctionPrototypes
int main(int	argc, 
	  char	*argv[])
#else
int main(argc, argv)
    int		argc;
    char	*argv[];
#endif
{
    /********************************************************************/
    /*									*/
    /* Initialize the toolkit 						*/
    /*									*/
    /********************************************************************/
    Arg argList[2];
    topLevel = XtAppInitialize(&appContext, "xkbleds", 
			       optionDesc, XtNumber(optionDesc), 
			       &argc, argv, 
			       fallbackResources,
			       NULL, 0);
    XtSetArg(argList[0], XtNallowShellResize, TRUE);
    XtSetValues(topLevel,argList,1);
    XtGetApplicationResources(topLevel, (XtPointer)&options, resources,
			      XtNumber(resources), NULL, 0);

    if (argc > 1)
    {
	usage(argv[0]);
	exit(0);
    }

    /* Defaults
     */
    if ((options.wanted == DONT_CARE) &&
	(options.wantReal == DONT_CARE) &&
	(options.wantVirtual == DONT_CARE) &&
	(options.wantNamed == DONT_CARE) &&
	(options.wantAutomatic == DONT_CARE) &&
	(options.wantExplicit == DONT_CARE) &&
	(options.useUnion == YES))
    {
	options.wanted 		= 0;
	options.wantReal	= YES;
	options.wantNamed 	= YES;
	options.wantAutomatic	= YES;
    }

    /********************************************************************/
    /*									*/
    /* See if the server has XKB.					*/
    /*									*/
    /********************************************************************/
    theDisplay = XtDisplay(topLevel);
    if (!InitXkb(theDisplay))
    {
	fprintf(stderr,"Could not initialize XKB extension.\n");
	exit(0);
    }

    if (options.wanted == 0)
    {
	fprintf(stderr,"No LED's were selected.\n\n");
	usage(argv[0]);
	exit(0);
    }

    /********************************************************************/
    /*									*/
    /* Set up the UI and go.						*/
    /*									*/
    /********************************************************************/
    XtRealizeWidget(topLevel);
    InitializeUI(topLevel);
    XtAppMainLoop(appContext);
    
    /* NOT REACHED */
    exit(0L);
}
