/* * remote.c * * Module for handling remote controls supported by FXTV. * * NOTE: For legacy reasons, this module contains both the remote-generic * routines (TVREMOTE*, etc.) as well as the X10 Remote-specific code * (TVX10REMOTE*, etc.). FIXME: We should break these out at some point. * * (C) 1998 Randall Hopper * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. 2. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* ******************** Include Files ************** */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ # include # if __FreeBSD_version >= 500013 # include # else # include # endif #else # include #endif #include #include "tvdebug.h" #include "tvutil.h" #include "remote.h" #include "haup_remote.h" #include "pixelview_remote.h" /* ******************** Local defines ************** */ /* FIXME: Remove this old code someday */ #if 0 # define OLD_DEV_SYSMOUSE_STUFF #endif /* This is an easy way to keep fxtv buildable on FreeBSD versions that */ /* have "old" versions of the moused/syscons stuff -- e.g. 2.2.1 */ #if !defined(MOUSE_SYS_PACKETSIZE) || !defined(_PATH_MOUSEREMOTE) # undef OLD_DEV_SYSMOUSE_STUFF # define _PATH_MOUSEREMOTE "foo" #endif #define ARRAY_SIZ(a) (sizeof(a) / sizeof(a[0])) #define ELAPSED_MSEC(t1,t2) \ (( (t2).tv_usec - (t1).tv_usec ) / 1000 + \ ( (t2).tv_sec - (t1).tv_sec ) * 1000) #define X10_KEY_SHIFT 0x6b typedef enum { REMOTE_TYPE_X10, REMOTE_TYPE_HAUPPAUGE, REMOTE_TYPE_PIXELVIEW } REMOTE_TYPE; /* ******************** Private variables ************** */ static REMOTE_TYPE Remote_type = -1; static int Remote_fd = -1; static XtInputId Remote_input_id; static TVREMOTE_CB_FUNCT *Remote_cb = NULL; #ifdef OLD_DEV_SYSMOUSE_STUFF static struct termios Save_term_attr; static int Save_modem_attr; static unsigned char Events[ MOUSE_SYS_PACKETSIZE * 50 ]; ssize_t Events_len = 0; #endif static struct { char *name; int code; int shifted; } X10_keys[] = { /*{ "Shift" , X10_KEY_SHIFT },*/ { "Power" , 0x0f, 0 }, { "AllOn" , 0x0f, 1 }, { "PC" , 0x2b, 0 }, { "CD" , 0xab, 0 }, { "Web" , 0x8b, 0 }, { "DVD" , 0xcb, 0 }, { "Phone" , 0x4b, 0 }, { "0" , 0x40, 0 }, { "1" , 0x41, 0 }, { "Home" , 0x41, 1 }, { "2" , 0x42, 0 }, { "Up" , 0x42, 1 }, { "3" , 0x43, 0 }, { "PgUp" , 0x43, 1 }, { "4" , 0x44, 0 }, { "Left" , 0x44, 1 }, { "5" , 0x45, 0 }, { "6" , 0x46, 0 }, { "Right" , 0x46, 1 }, { "7" , 0x47, 0 }, { "End" , 0x47, 1 }, { "8" , 0x48, 0 }, { "Down" , 0x48, 1 }, { "9" , 0x49, 0 }, { "PgDn" , 0x49, 1 }, { "ENTER" , 0x4a, 0 }, { "VolUp" , 0x06, 0 }, { "BrightUp" , 0x06, 1 }, { "VolDn" , 0x07, 0 }, { "BrightDn" , 0x07, 1 }, { "ChanUp" , 0x02, 0 }, { "On" , 0x02, 1 }, { "ChanDn" , 0x03, 0 }, { "Off" , 0x03, 1 }, { "MUTE" , 0x05, 0 }, { "AllOff" , 0x05, 1 }, { "A-B" , 0x5d, 0 }, { "DISP" , 0x5c, 0 }, { "PLAY" , 0x0d, 0 }, { "REW" , 0x1c, 0 }, { "FF" , 0x1d, 0 }, { "STOP" , 0x0e, 0 }, { "REC" , 0xff, 0 }, { "PAUSE" , 0x4e, 0 }, { "LAST" , 0x4f, 0 } }; /* ******************** Forward declarations ************** */ /* ******************** Function Definitions ************** */ #ifdef OLD_DEV_SYSMOUSE_STUFF static void setmousespeed( int fd, int old, int new, unsigned cflag) /* This method was lifted from moused.c v1.18 (980312) by Michael */ /* Smith and tweaked just a bit. */ { struct termios tty; char *c; if (tcgetattr(fd, &tty) < 0) { printf("unable to get status of mouse fd", 0); return; } tty.c_iflag = IGNBRK | IGNPAR; tty.c_oflag = 0; tty.c_lflag = 0; tty.c_cflag = (tcflag_t)cflag; tty.c_cc[VTIME] = 0; tty.c_cc[VMIN] = 1; switch (old) { case 9600: cfsetispeed(&tty, B9600); cfsetospeed(&tty, B9600); break; case 4800: cfsetispeed(&tty, B4800); cfsetospeed(&tty, B4800); break; case 2400: cfsetispeed(&tty, B2400); cfsetospeed(&tty, B2400); break; case 1200: default: cfsetispeed(&tty, B1200); cfsetospeed(&tty, B1200); } if (tcsetattr(fd, TCSADRAIN, &tty) < 0) { printf("unable to set status of mouse fd", 0); return; } switch (new) { case 9600: c = "*q"; cfsetispeed(&tty, B9600); cfsetospeed(&tty, B9600); break; case 4800: c = "*p"; cfsetispeed(&tty, B4800); cfsetospeed(&tty, B4800); break; case 2400: c = "*o"; cfsetispeed(&tty, B2400); cfsetospeed(&tty, B2400); break; case 1200: default: c = "*n"; cfsetispeed(&tty, B1200); cfsetospeed(&tty, B1200); } #ifdef OLD if (rodent.rtype == MOUSE_PROTO_LOGIMOUSEMAN || rodent.rtype == MOUSE_PROTO_LOGI) { if (write(fd, c, 2) != 2) { printf("unable to write to mouse fd", 0); return; } } #endif usleep(100000); if (tcsetattr(fd, TCSADRAIN, &tty) < 0) printf("unable to set status of mouse fd", 0); } #endif /**@BEGINFUNC************************************************************** Prototype : static int DebounceAndGetShift( int key, int *shift ) Purpose : Debounce the current key event, and return the SHIFT status to apply to the key. Programmer : 17-May-98 Randall Hopper Parameters : key - I: key event shift - O: T = shift active; F = not Returns : T = process key; F = skip it Globals : None. **@ENDFUNC*****************************************************************/ static int DebounceAndGetShift( int key, int *shift ) { static struct { struct timeval shift_time_last; int shifting; int keycode; struct timeval time_1; struct timeval time_last; int skipped; } Deb; struct timeval time_cur; int process = 0; /* Update current time */ gettimeofday( &time_cur, NULL ); /* Update SHIFT status */ if ( ELAPSED_MSEC( Deb.time_last, time_cur ) > 250 ) Deb.shifting = 0; else if ( key == X10_KEY_SHIFT ) { Deb.shifting = 1; Deb.shift_time_last = Deb.time_1 = time_cur; Deb.keycode = key; Deb.skipped = 0; } *shift = Deb.shifting; if ( key == X10_KEY_SHIFT ) process = 0; /* Handle other keys */ /* If new key, or last press too old, restart debounce */ else if (( key != Deb.keycode ) || ( ELAPSED_MSEC( Deb.time_last, time_cur ) >= 500 )) { Deb.keycode = key; Deb.time_1 = time_cur; Deb.skipped = 0; process = 1; } else if ( ELAPSED_MSEC( Deb.time_1, time_cur ) >= 500 ) { /* If same key, hit lately, been a while since 1st hit, */ /* and we've skipped a few occurances of this key (i.e. */ /* we're pretty sure they're holding it down) */ /* process and let key repeat. */ if ( Deb.skipped >= 3 ) process = 1; /* If same key, hit lately, been a while since 1st hit, */ /* and we "haven't" skipped a few occurances of this key, */ /* it's likely that the user is just pressing it fast (channel */ /* surfing), not just holding it down. Process and restart */ /* debounce. */ else { Deb.keycode = key; Deb.time_1 = time_cur; Deb.skipped = 0; process = 1; } } /* If same key, hit lately, and "hasn't" been a while, debounce */ else { process = 0; Deb.skipped++; } Deb.time_last = time_cur; return process; } /**@BEGINFUNC************************************************************** Prototype : static void ProcessMouseRemoteKey( int btn ) Purpose : When we detect that a mouse remote key event has occurred, this routine is called to debounce and process it. Programmer : 28-May-98 Randall Hopper Parameters : btn - I: mouse remote btn Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ static void ProcessMouseRemoteKey( int btn ) { int shift, j; /* Debounce the key and get shift state */ if ( !DebounceAndGetShift( btn, &shift ) ) { RMPRINTF(( "TVX10REMOTE: Event debounced (ignored)\n" )); return; } /* Identify code */ for ( j = 0; j < ARRAY_SIZ(X10_keys); j++ ) if (( btn == X10_keys[j].code ) && ( shift == X10_keys[j].shifted )) break; if ( j < ARRAY_SIZ(X10_keys) ) { RMPRINTF(( "TVX10REMOTE: KEY EVENT = <%s> %s\n", X10_keys[j].name, (shift ? "(SHIFTED)" : "") )); if ( Remote_cb ) Remote_cb( X10_keys[j].name ); } else RMPRINTF(( "TVX10REMOTE: Unknown key event...skipped\n" )); } #ifdef OLD_DEV_SYSMOUSE_STUFF /**@BEGINFUNC************************************************************** Prototype : static void TVX10RemoteInputProcSysMouse( XtPointer cb_data, int *fd, XtInputId *id ) Purpose : File descriptor activity callback for the mouse remote device. Programmer : 17-May-98 Randall Hopper Parameters : cb_data - I: fd - I: remote file desc id - I: Xt input id Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ static void TVX10RemoteInputProcSysMouse( XtPointer cb_data, int *fd, XtInputId *id ) { unsigned char buf[ MOUSE_SYS_PACKETSIZE * 2 ]; ssize_t len; int i, j, btn; /* Loop while still data */ while (1) { /* Grab some data */ len = read( *fd, buf, sizeof(buf) ); if ( len <= 0 ) return; RMPRINTF(( "TVX10REMOTE: Got %d new bytes\n", len )); /* Tack new data onto queue */ if ( Events_len + len > sizeof(Events) ) { fprintf( stderr, "TVX10RemoteInputProc: Buffer overflow...resetting\n" ); Events_len = 0; return; } memcpy( Events+Events_len, buf, len ); Events_len += len; /* Pick out events from data stream */ for ( i = 0; i < Events_len; ) { /* Skip to beginning of next event sequence */ if ( (Events[i] & MOUSE_SYS_SYNCMASK) != MOUSE_SYS_SYNC ) { RMPRINTF(( "TVX10REMOTE: Discarded: %.2x\n", Events[i] )); i++; continue; } /* At event beginning. If not enough for one event, bail */ if ( Events_len-i < MOUSE_SYS_PACKETSIZE ) break; /* Print event */ RMPRINTF(( "%30cTVX10REMOTE EVENT = ", ' ' )); for ( j = 0; j < MOUSE_SYS_PACKETSIZE; j++ ) RMPRINTF(( "%.2x ", buf[i+j] )); RMPRINTF(( "\n" )); /* If this event isn't a special btn (i.e. Z axis) event, */ /* skip it */ if ( (Events[i+5] | Events[i+6]) == 0 ) { i += MOUSE_SYS_PACKETSIZE; RMPRINTF(( "TVX10REMOTE: Event ignored\n" )); continue; } /* Rebuild code from Z event */ /* FIXME: The algorithm in syscons.c for the second byte: */ /* buf[5] = (j >> 1) & 0x7f; */ /* buf[6] = (j - (j >> 1)) & 0x7f; */ /* looks wrong. */ /* One would think the second one would be: */ /* buf[6] = (j >> 7) & 0x01 */ /* For now, we deal with syscons.c as it is. */ if (( Events[i+5] == 0x7f ) && ( Events[i+6] == 0x00 )) Events[i+6] = 0x80; /* Code 0xFF falls off the end */ btn = Events[i+5] + Events[i+6]; ProcessMouseRemoteKey( btn ); i += MOUSE_SYS_PACKETSIZE; } /* Dump processed data */ Events_len -= i; memmove( Events, &Events[i], Events_len ); } } #else /**@BEGINFUNC************************************************************** Prototype : static void TVX10RemoteInputProcMouseRemSocket( XtPointer cb_data, int *fd, XtInputId *id ) Purpose : File descriptor activity callback for the mouse remote device. Programmer : 17-May-98 Randall Hopper Parameters : cb_data - I: fd - I: remote file desc id - I: Xt input id Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ static void TVX10RemoteInputProcMouseRemSocket( XtPointer cb_data, int *fd, XtInputId *id ) { unsigned char btn; /* Loop while still data */ while (1) { /* Grab a key (until buffer empty) */ if ( read( *fd, &btn, 1 ) <= 0 ) break; /* And process */ RMPRINTF(( "TVX10REMOTE: Got a btn: %d\n", btn )); ProcessMouseRemoteKey( btn ); } } #endif /**@BEGINFUNC************************************************************** Prototype : static void TVX10REMOTEClose( void ) Purpose : Called when we're exiting the program to cleanup and close the mouse remote device file descriptor Programmer : 17-May-98 Randall Hopper Parameters : None. Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ static void TVX10REMOTEClose( void ) { /* Do nothing if we didn't open the remote */ if ( Remote_fd < 0 ) return; /* Unregister fildesc activity proc */ XtRemoveInput( Remote_input_id ); #ifdef OLD_DEV_SYSMOUSE_STUFF /* Restore device state */ tcsetattr( Remote_fd, TCSANOW, &Save_term_attr ); ioctl( Remote_fd, TIOCMODS, &Save_modem_attr ); #endif /* Close device */ close( Remote_fd ); Remote_fd = -1; } /**@BEGINFUNC************************************************************** Prototype : void TVX10REMOTEOpen( XtAppContext app_ctx, TVREMOTE_CB_FUNCT *cb ) Purpose : Open procedure for the X10 mouse remote device. If we can't open the device (permissions, etc.), we just return without doing anything. For other errors we bail out of the program. Programmer : 17-May-98 Randall Hopper Parameters : app_ctx - I: Xt app context (used to register fd callback) rem_type - I: type of remote (e.g. X10) cb - I: funct to call to process key events Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ void TVX10REMOTEOpen( XtAppContext app_ctx, TVREMOTE_CB_FUNCT *cb ) { int fd, flags; #ifdef OLD_DEV_SYSMOUSE_STUFF mousemode_t mouse_mode; int level; #endif struct sockaddr_un remsrv_addr; #ifdef OLD_DEV_SYSMOUSE_STUFF /* Open device */ fd = Remote_fd = open( rem_dev, O_RDWR | O_NONBLOCK, 0 ); if ( Remote_fd < 0 ) { fprintf( stderr, "Can't open mouse remote device for read/write: " "\"%s\"\n" "Continuing without remote.\n", rem_dev); return; } RMPRINTF(( "TVX10REMOTE: Device open.\n" )); /* Save device state */ tcgetattr( fd, &Save_term_attr ); ioctl( fd, TIOCMODG, &Save_modem_attr ); /* Set sysmouse for Level 1 (8 byte) reporting */ level = 1; ioctl( fd, MOUSE_SETLEVEL, &level ); level = (ioctl( fd, MOUSE_GETLEVEL, &level ) == 0) ? level : 0; if ( level != 1 ) { fprintf( stderr, "Can't set sysmouse to Level 1 format\n" ); exit(1); } /* Sanity checks */ ioctl( fd, MOUSE_GETMODE, &mouse_mode ); if (( mouse_mode.packetsize != MOUSE_SYS_PACKETSIZE ) || ( mouse_mode.syncmask[0] != MOUSE_SYS_SYNCMASK ) || ( mouse_mode.syncmask[1] != MOUSE_SYS_SYNC )) { fprintf( stderr, "Level 1 sysmouse format isn't as expected\n" ); exit(1); } /* Set up mouse parameters (sysmouse/MouseSystems config) */ setmousespeed(fd, 1200, 1200, (CS8 | CSTOPB | CREAD | CLOCAL | HUPCL )); #else /* Connect remote event server (moused) via UNIX-domain stream socket */ bzero( &remsrv_addr, sizeof(remsrv_addr)); remsrv_addr.sun_family = AF_UNIX; strcpy(remsrv_addr.sun_path, _PATH_MOUSEREMOTE); # ifndef SUN_LEN # define SUN_LEN(unp) ( ((char *)(unp)->sun_path - (char *)(unp)) + \ strlen((unp)->path) ) # endif if ( (fd = Remote_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { fprintf( stderr, "Can't create client socket %s: %s\n", _PATH_MOUSEREMOTE, strerror(errno) ); return; } if ( connect( fd, (struct sockaddr *) &remsrv_addr, SUN_LEN(&remsrv_addr)) < 0 ) { fprintf( stderr, "Can't connect to socket %s: %s\n", _PATH_MOUSEREMOTE, strerror(errno) ); close(fd); return; } flags = fcntl( fd, F_GETFL, 0 ); if ( fcntl( fd, F_SETFL, flags | O_NONBLOCK ) == -1 ) { fprintf( stderr, "Can't make socket %s non-blocking: %s\n", _PATH_MOUSEREMOTE, strerror(errno) ); close(fd); return; } #endif /* Register filedesc activity proc with dispatch procedure */ Remote_input_id = XtAppAddInput( app_ctx, fd, (XtPointer) XtInputReadMask, #ifdef OLD_DEV_SYSMOUSE_STUFF TVX10RemoteInputProcSysMouse, NULL ); #else TVX10RemoteInputProcMouseRemSocket, NULL ); #endif RMPRINTF(( "TVX10REMOTE: Device configured and ready.\n" )); /* Store off callback function */ Remote_cb = cb; /* Arrange to get called when we exit the app */ atexit( TVX10REMOTEClose ); } /**@BEGINFUNC************************************************************** Prototype : void TVX10REMOTEFlush( void ) Purpose : Flush any stray input from the remote. Why do we have this? Sometimes we need to perform an operation that takes a while (e.g. switching video modes) which may have been instigated by a remote event. This messes up the debouncing we do for the remote. So we flush the events after we perform the long operation. Programmer : 31-May-98 Randall Hopper Parameters : void Returns : void Globals : None. **@ENDFUNC*****************************************************************/ void TVX10REMOTEFlush( void ) { unsigned char btn; /* Do nothing if we didn't open the remote */ if ( Remote_fd < 0 ) return; /* Loop while still data */ while (1) { /* Grab a key (until buffer empty) */ if ( read( Remote_fd, &btn, 1 ) <= 0 ) break; RMPRINTF(( "TVX10REMOTE: Flushing buffer (key = %d)\n", btn )); } } /*---------------------------------------------------------------------------*/ /*------------ Hereafter lies the "generic" remote routines ---------------*/ /*---------------------------------------------------------------------------*/ /**@BEGINFUNC************************************************************** Prototype : void TVREMOTEOpen( XtAppContext app_ctx, char rem_type[], TVREMOTE_CB_FUNCT *cb ) Purpose : Open procedure for the mouse remote device. If we can't open the device (permissions, etc.), we just return without doing anything. For other errors we bail out of the program. Programmer : 17-May-98 Randall Hopper Parameters : app_ctx - I: Xt app context (used to register fd callback) rem_type - I: type of remote (e.g. X10, Hauppauge, etc.) cb - I: funct to call to process key events Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ void TVREMOTEOpen( XtAppContext app_ctx, char rem_type[], TVREMOTE_CB_FUNCT *cb ) { char type_str[20]; /* Branch to the appropriate Open method based on remote type. */ /* If invalid, error message and bail out of the program. */ type_str[0] = '\0'; strncat( type_str, rem_type, sizeof(type_str)-1 ); TVUTILstrupr( type_str ); if ( STREQ( type_str, "X10" ) ) { TVX10REMOTEOpen ( app_ctx, cb ); Remote_type = REMOTE_TYPE_X10; } else if ( STREQ( type_str, "HAUPPAUGE" ) ) { TVHAUPREMOTEOpen( app_ctx, cb ); Remote_type = REMOTE_TYPE_HAUPPAUGE; } else if ( STREQ( type_str, "PIXELVIEW" ) ) { TVPIXELVIEWREMOTEOpen( app_ctx, cb ); Remote_type = REMOTE_TYPE_PIXELVIEW; } else { fprintf( stderr, "TVREMOTEOpen: Only X10, HAUPPAUGE, and PIXELVIEW remotes " "are supported.\n" ); exit(1); } } /**@BEGINFUNC************************************************************** Prototype : void TVREMOTEFlush( void ) Purpose : Flush any stray input from the remote. Why do we have this? Sometimes we need to perform an operation that takes a while (e.g. switching video modes) which may have been instigated by a remote event. This messes up the debouncing we do for the remote. So we flush the events after we perform the long operation. Programmer : 31-May-98 Randall Hopper Parameters : void Returns : void Globals : None. **@ENDFUNC*****************************************************************/ void TVREMOTEFlush( void ) { switch ( Remote_type ) { case REMOTE_TYPE_X10 : TVX10REMOTEFlush(); break; case REMOTE_TYPE_HAUPPAUGE : TVHAUPREMOTEFlush(); break; case REMOTE_TYPE_PIXELVIEW : TVPIXELVIEWREMOTEFlush(); break; } }