/* A program to read commands from an infrared remote control,    */
/* convert the IR commands to X key events and send the events    */
/* to a specified X application.                                  */
/* compile using:                                                 */
/* gcc -I/usr/include/X11 -L/usr/X11R6/lib -lX11 -o xirrc xirrc.c */
/*                                                                */
/* Sample invocation:                                             */
/* ./xirrc /dev/ttyS1                                             */
/*                                                                */
/* Part of this program is based on GTKeyboard so this software   */
/* falls under the GPL.  See http://opop.nols.com/gtkeyboard.html */

#define XK_LATIN1
#define XK_MISCELLANY

#include <stdio.h>
#include <unistd.h>
#include <termio.h>
#include <termios.h>
#include <sys/fcntl.h>
#include <sys/signal.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <X11/Xlib.h>
#include <X11/keysymdef.h>
#include <X11/cursorfont.h>


/****************** Global Routine Definitions *****************/
int  ir_x_init(int argc, char *argv[]); /* set up X display */
int  ir_open(char *);  /* open serial port to IR reciever */
Window Select_Window(Display *);  /* Select the IR kbd window */


/****************** Global Variables for X *********************/
Display *display;
XKeyEvent xev;


/***************** How this program works ******************
   Open the serial port to the IR receiver
   Initialize the X display
   Select the window to receive the characters
   Select loop {
      Get characters from the IR receiver
      Convert character to X keycode
      Send Key to the X client
   }
***********************************************************/



main (int argc, char *argv[])
{
    fd_set rfds;     /* bit masks for select statement */
    int    irfd;     /* File Descriptor to IR serial port */
    char   ir_cmd;   /* character from IR remote */


    /* Open the serial port to the infrared receiver */
    irfd = ir_open(argv[1]);

    /* Initialize the X display */
    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Unable to open X display\n");
        exit(1);
    }

    /* Select the X app to receive the keycodes */
    printf("Please select the X app to receive the IR keycodes\n");
    xev.window = Select_Window(display);

    /* Loop getting IR codes from the receiver and sending them to the X app */
    while (1) {
        FD_ZERO(&rfds);
        FD_SET(irfd, &rfds);

        (void) select(irfd+1, &rfds, (fd_set *)NULL,
                (fd_set *)NULL, (struct timeval *) NULL);
        if (FD_ISSET(irfd, &rfds)) {
            /* Get IR command byte */
            read(irfd, &ir_cmd, 1);

            /* Convert IR command byte to a keycode */
            switch (ir_cmd) {
                case 0x14: /* Mute: xmms key is 'c' (pause) */
                         xev.keycode= XKeysymToKeycode(display, XK_c);
                         break;
                case 0x2e: /* Power: xmms key is 'x' (play) */
                         xev.keycode= XKeysymToKeycode(display, XK_x);
                         break;
                case 0x05: /* Sleep: xmms key is 'v' (stop) */
                         xev.keycode= XKeysymToKeycode(display, XK_v);
                         break;
                case 0x12: /* +Volume: xmms key is Cursor Up */
                         xev.keycode= XKeysymToKeycode(display, XK_Up);
                         break;
                case 0x13: /* -Volume: xmms key is Cursor down */
                         xev.keycode= XKeysymToKeycode(display, XK_Down);
                         break;
                case 0x10: /* +Channel: xmms key is 'b' (next song) */
                         xev.keycode= XKeysymToKeycode(display, XK_b);
                         break;
                case 0x11: /* -Channel: xmms key is 'z' (prev song) */
                         xev.keycode= XKeysymToKeycode(display, XK_z);
                         break;
                case 0x7f: /* Sony EOF byte.  Ignored */
                         continue;
                         break;
                default: printf("Unknown IR code: 0x%02x.  Ignored\n", ir_cmd);
                         continue;
                         break;
            }


            /* Send keycode to the X app */
            xev.serial       = 0;
            xev.send_event   = 0;
            xev.type         = KeyPress;
            xev.display      = display;
            xev.root         = None;
            xev.subwindow    = None;
            xev.time         = 0;
            xev.same_screen  = 1;
            xev.state        = 0;
            xev.x            = 1;
            xev.y            = 1;
            xev.x_root       = 1;
            xev.y_root       = 1;
            (void) XSendEvent(display, xev.window, True, KeyPressMask, 
                              (XEvent *)&xev);
            XFlush(display);

            /* Key release may be needed for some programs */
            xev.type         = KeyRelease;
            (void) XSendEvent(display, xev.window, True, KeyReleaseMask,
                              (XEvent *)&xev);
            XFlush(display);
        }
    }
}


/* ir_open:  Open the serial port specified */
/* returns:  The opened file descriptor */
int ir_open(char *serport)
{
    struct  termios    tbuf;
    int     fd_dev;

    if (( fd_dev = open(serport, (O_RDWR | O_NDELAY), 0)) < 0 ) {
        fprintf(stderr,"Unable to open tty port specified\n");
        exit(1);
    }

    tbuf.c_cflag = CS7|CREAD|B9600|CLOCAL;
    tbuf.c_iflag = IGNBRK;
    tbuf.c_oflag = 0;
    tbuf.c_lflag = 0;
    tbuf.c_cc[VMIN] = 1; /* character-by-character input */
    tbuf.c_cc[VTIME]= 0; /* no delay waiting for characters */
    if (tcsetattr(fd_dev, TCSANOW, &tbuf) < 0) {
        fprintf(stderr, "xirrc: Unable to set device '%s' parameters\n", serport);
        exit(1);
    }
    if (( fd_dev = open(serport, (O_RDWR), 0)) < 0 ) {
        fprintf(stderr, "Unable to open tty port specified\n");
        exit(1);
    }

    return(fd_dev);  /* return the serial port file descriptor */
}



/*
 * Routine to let user select a window using the mouse
 * This routine is taken from GTKeyboard.
 */
Window Select_Window(Display *dpy)
{
    int status;
    Cursor cursor;
    XEvent event;
    Window target_win = None;
    Window root = DefaultRootWindow(display);
    int buttons = 0;

    /* Make the target cursor */
    cursor = XCreateFontCursor(dpy, XC_crosshair);
    
    /* Grab the pointer using target cursor, letting it room all over */
    status = XGrabPointer(dpy, root, False,
             (ButtonPressMask | ButtonReleaseMask), GrabModeSync,
             GrabModeAsync, root, cursor, CurrentTime);
    if (status != GrabSuccess) {
        printf("Can't grab mouse!\n");
    } /* End if */
    
    /* Let the user select a window... */
    while ((target_win == None) || (buttons != 0)) 
    {
        /* allow one more event */
        XAllowEvents(dpy, SyncPointer, CurrentTime);
        XWindowEvent(dpy, root, (ButtonPressMask|ButtonReleaseMask), &event);

        switch (event.type) {
            case ButtonPress:
                if (target_win == None) {
                    target_win = event.xbutton.subwindow; /* get window */
                    if (target_win == None)
                        target_win = root;
                } /* End if */
                buttons++;
                break;
            case ButtonRelease:
                if (buttons > 0) /* may have started with keys down */
                    buttons--;
                break;
        } /* End switch */
    } /* End while */
     
    XUngrabPointer(dpy, CurrentTime);      /* Done with pointer */
    XFlush(dpy);                           /* restore cursor */
    return(target_win);
} /* End Select_Window */

