/*****************************************************************************
FILE:               mouse.c
LANGUAGE:           c
SYSTEM:             POSIX
USER-INTERFACE:     NONE
DESCRIPTION
    
    This is a User Space Serial Mouse Driver for OPENSTEP 4.2.
    
AUTHORS
    <PJB> Pascal Bourguignon <pjb@informatimago.com>
MODIFICATIONS
    2003-11-24 <PJB> Created.
BUGS
LEGAL
    GPL
    
    Copyright Pascal Bourguignon 2003 - 2003
    
    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.
    
    This program is distributed in the hope that it will be
    useful, but WITHOUT ANY WARRANTY; without even the implied
    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
    PURPOSE.  See the GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public
    License along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330,
    Boston, MA 02111-1307 USA
*****************************************************************************/
#include <mach.h>
#include <cthreads.h>
#include <kernserv/kern_loader_reply_handler.h>
#include <syslog.h>
#include <stdio.h>
#include <string.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <bsd/libc.h>
#include <bsd/sys/ttydev.h>
#include <errno.h>

#include <bsd/dev/zsio.h>

#define SERIAL_SPEED      (B9600)
#define SPEED_INIT_STRING ("ÿ")

/*****************************/
/* Interface with kmouse LKS */
/*****************************/


    port_name_t kmouse_port;

    void kmouse_initialize()
    {
        kern_return_t   r;
        /* Look up the advertised port of kmouse. */
        r=netname_look_up(name_server_port,"","kmouse",&kmouse_port);
        if (r!=KERN_SUCCESS) {
            mach_error("Can't find kmouse's advertised port",r);
            fprintf(stderr,"You must load the kmouse kernel module first.\n");
            exit(1);
        }
    }

    void kmouse_update(int delta_x,int delta_y,
                           int left_button,int right_button)
    {
        kern_return_t   r;
#ifdef DEBUG
        fprintf(stderr,"Sending:  %04x\n",
                (delta_y&0x7f)<<9
                |(delta_x&0x7f)<<1
                |(left_button&0x01)
                |(right_button&0x01)<<8);
#endif
        r=kmouse_send_event(kmouse_port,
                            (delta_y&0x7f)<<9
                            |(delta_x&0x7f)<<1
                            |((left_button==0)?0:1)
                            |((right_button==0)?0:1)<<8);
        if(r!=KERN_SUCCESS) {
            mach_error("Error calling kmouse_send_event",r);
        }
    }


    void kmouse_dump_state(void)
    {
        kern_return_t r;
        int buttons=0;
        int flags=0;
        int clock=0;
        int location=0;
        r=kmouse_get_state(kmouse_port,&buttons,&flags,&clock,&location);
        if(r!=KERN_SUCCESS) {
            mach_error("Error calling kmouse_get_state",r);
        }else{
            printf("c=%08d f=%08x l=%08x b=%08x\n",
                   clock,flags,location,buttons);
            fflush(stdout);
        }
    }
        


/*****************************************/
/* interface between logitech and kmouse */
/*****************************************/

/*
    extern void event_left_button_changed(int mouse_id,int pressed);
    extern void event_middle_button_changed(int mouse_id,int pressed);
    extern void event_right_button_changed(int mouse_id,int pressed);
    extern void event_mouse_moved(int mouse_id,int dx,int dy);
    extern void event_mouse_changed(int mouse_id,int dx,int dy,
                                    int left,int middle,int right);
*/

/*
    When a mouse state change occurs, one or more of the first four
    above functions are called, and then the last one is called.
    This allow to implement either an incremental interface,
    or a global one. (kmouse uses event_mouse_changed).
*/


    void event_left_button_changed(int mouse_id,int pressed)
    {
#ifdef DEBUG
        fprintf(stderr,"                 Left   %s\n",pressed?"ON":"off");
#endif
    }/*event_left_button_changed*/


    void event_middle_button_changed(int mouse_id,int pressed)
    {
#ifdef DEBUG
        fprintf(stderr,"                 Middle %s\n",pressed?"ON":"off");
#endif
    }/*event_middle_button_changed*/


    void event_right_button_changed(int mouse_id,int pressed)
    {
#ifdef DEBUG
        fprintf(stderr,"                 Right  %s\n",pressed?"ON":"off");
#endif
    }/*event_right_button_changed*/

    void event_mouse_moved(int mouse_id,int dx,int dy)
    {
#ifdef DEBUG
        fprintf(stderr,"Moved  %3d,%3d\n",dx,dy);
#endif
    }/*event_mouse_moved*/


    void event_mouse_changed(int mouse_id,int dx,int dy,
                             int left,int middle,int right)
    {
        kmouse_update(dx,dy,left|middle,right|middle);
    }/*event_mouse_changed*/



/************************************/
/* Logitech Serial Mouse            */
/* (Actually, a PS/2 MouseManSensa) */
/************************************/


    typedef struct mouse_t {
        int     id;
        struct {
            char    left:1,middle:1,right:1;
        }       old_buttons;
        struct {
            char    left:1,middle:1,right:1;
        }       new_buttons;
        int     dx;
        int     dy;
        int     packet_length;
        char    packet[4];
        char    verbose;
        void(*decode)(struct mouse_t*);
    }       mouse_t;
    

    void serial_mouse_decode_packet(mouse_t* that)
        /*
            PRIVATE
        */
    {
        /*
            bit:   | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
            -------|---|---|---|---|---|---|---|---|
            Word 0 | 0 | 1 | LB| RB| V7| V6| H7| H6|
            Word 1 | 0 | 0 | H5| H4| H3| H2| H1| H0|
            Word 2 | 0 | 0 | V5| V4| V3| V2| V1| V0|
            Word 3 | 0 | 0 | MB|  0|  0|  0|  0|  0| (optional)
            
        */
        if(that->packet_length<3){
            return;
        }
        that->new_buttons.middle=that->old_buttons.middle;
        that->new_buttons.left=((that->packet[0]&0x20)==0)?0:1;
        that->new_buttons.right=((that->packet[0]&0x10)==0)?0:1;
        if(that->packet_length>3){
            that->new_buttons.middle=((that->packet[3]&0x20)==0)?0:1;
        }
        that->dx=(signed char)((that->packet[0]&0x3)<<6)|(that->packet[1]&0x3f);
        that->dy=(signed char)((that->packet[0]&0xc)<<4)|(that->packet[2]&0x3f);
    }/*serial_mouse_decode_packet*/


    void ps2_mouse_decode_packet(mouse_t* that)
        /*
            PRIVATE
        */
    {
        /*
            bit:   |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
            -------|-----|-----|-----|-----|-----|-----|-----|-----|
            Word 0 | Yov | Xov | Ysg | Xsg |  1  | MB  | RB  | LB  |
            Word 1 |                   X movement                  |
            Word 3 |                   Y movement                  |
            -------|-----|-----|-----|-----|-----|-----|-----|-----|
            
        */
        if(that->packet_length<3){
            return;
        }
        that->new_buttons.left=((that->packet[0]&0x01)==0)?0:1;
        that->new_buttons.right=((that->packet[0]&0x02)==0)?0:1;
        that->new_buttons.middle=((that->packet[0]&0x04)==0)?0:1;
        that->dx=(signed char)(((that->packet[0]&0x10)<<3)
                               |(that->packet[1]>>1));
        that->dx=(signed char)(((that->packet[0]&0x20)<<2)
                               |(that->packet[2]>>1));
    }/*serial_mouse_decode_packet*/


    void mouse_initialize(mouse_t* that,int id)
        /*
            DOES:    Initialize a mouse. The id will be passed to the event
                     routines and serves to identify the mouse device.
        */
    {
        that->id=id;
        that->old_buttons.left=0;
        that->old_buttons.middle=0;
        that->old_buttons.right=0;
        that->new_buttons.left=0;
        that->new_buttons.middle=0;
        that->new_buttons.right=0;
        that->dx=0;
        that->dy=0;
        that->packet_length=0;
        that->packet[0]=0;
        that->packet[1]=0;
        that->packet[2]=0;
        that->packet[3]=0;
        that->decode=serial_mouse_decode_packet; /* ps2_mouse_decode_packet; */
    }/*mouse_initialize*/


    void mouse_send_event(mouse_t* that)
        /*
            PRIVATE
        */
    {
        int changed=0;
        if(that->packet_length==4){
            /* only middle button event */
            if(that->old_buttons.middle!=that->new_buttons.middle){
                that->old_buttons.middle=that->new_buttons.middle;
                event_middle_button_changed(that->id,that->new_buttons.middle);
                changed=1;
            }
        }else{
            /* other events */
            if(that->old_buttons.left!=that->new_buttons.left){
                that->old_buttons.left=that->new_buttons.left;
                event_left_button_changed(that->id,that->new_buttons.left);
                changed=1;
            }
            if(that->old_buttons.right!=that->new_buttons.right){
                that->old_buttons.right=that->new_buttons.right;
                event_right_button_changed(that->id,that->new_buttons.right);
                changed=1;
            }
            if((that->dy!=0)||(that->dx!=0)){
                event_mouse_moved(that->id,that->dx,that->dy);
                changed=1;
            }
        }
        if(changed){
            event_mouse_changed(that->id,that->dx,that->dy,
                                that->new_buttons.left,
                                that->new_buttons.middle,
                                that->new_buttons.right);
        }
    }/*mouse_send_event*/


    void mouse_sent_byte(mouse_t* that,char byte)
        /*
            NOTE:    Entry point to the mouse. Just send it the bytes
                     received from the serial line one by one.
        */
    {
#ifdef DEBUG
        fprintf(stderr,"sent byte %02x\n",byte);
#endif
        if(byte&0x40){
            /* start of packet */
            that->packet[0]=0x7f&byte;
            that->packet_length=1;
        }else if(that->packet_length<4){
            that->packet[that->packet_length++]=0x7f&byte;
            if(3<=that->packet_length){
                that->decode(that);
                mouse_send_event(that);
            }
        }/* else too long packet, let's ignore it.*/
    }/*mouse_sent_byte*/



/********************/
/* The main program */
/********************/


    void serial_dump_modem_signals(int fd)
    {
        int flags=0;
        if(0==ioctl(fd,TIOCMGET,&flags)){
            int i;
            static struct {int bit;const char* on;const char* off;} bits[]={
                {DML_DSR,"DSR","dsr"},
                {DML_RNG,"RNG","rng"},
                {DML_CAR,"CAR","car"},
                {DML_CTS,"CTS","cts"},
                {DML_SR, "SR ","sr "},
                {DML_ST, "ST ","st "},
                {DML_RTS,"RTS","rts"},
                {DML_DTR,"DTR","dtr"},
                {DML_LE, "LE ","le "},
                {0,0,0}};
            for(i=0;bits[i].on;i++){
                printf("%4s",(bits[i].bit&flags)==0?bits[i].off:bits[i].on);
            }
            printf("\n");
        }else{
            printf("Cannot get modem flags.\n");
        }
        fflush(stdout);
    }/**/


    int serial_mouse_open(const char* path)
        /*
            DOES:    open the serial device at path (should be /dev/tty[ab]),
                     and initialize it to be used with a serial mouse.
            RETURN:  the opened file descriptor.
        */
    {
        int fd=open(path,O_RDWR|O_EXCL,0);
        if(fd<0){
            return(fd);
        }else{
            struct sgttyb s;
            s.sg_ispeed=SERIAL_SPEED;
            s.sg_ospeed=SERIAL_SPEED;
            s.sg_erase=0;
            s.sg_kill=0;
            s.sg_flags=RAW;
            ioctl(fd,TIOCSETP,s);
        }
        serial_dump_modem_signals(fd);
        ioctl(fd,TIOCCDTR,0); /* clear DTR */
        serial_dump_modem_signals(fd);
        sleep(1);
        ioctl(fd,TIOCSDTR,0); /* set DTR */
        serial_dump_modem_signals(fd);
        write(fd,SPEED_INIT_STRING,strlen(SPEED_INIT_STRING));
        serial_dump_modem_signals(fd);
        return(fd);
    }/*serial_mouse_open*/

#if 0
#include <math.h>

    funny()
    {
        int t;
        kmouse_initialize();
        for(t=0;t<60;t++){
            int dx=2*sin(2*3.14159265*60/t);
            int dy=2*cos(2*3.14159265*60/t);
            event_mouse_changed(0,dx,dy,0,0,0);
            /*sleep(1);*/
        }
        exit(0);
    }

#endif

    void serial_mouse(const char* serial_path)
    {
        mouse_t       mouse;
        unsigned char byte;
        int           serial_fd;
        mouse_initialize(&mouse,0);
        kmouse_initialize();
        serial_fd=serial_mouse_open(serial_path);
        if(serial_fd>=0){
            serial_dump_modem_signals(serial_fd);
            fprintf(stderr,"mouse driver started on port '%s'.\n",serial_path);
            do{
                int res;
                serial_dump_modem_signals(serial_fd);
                res=read(serial_fd,&byte,1);
                serial_dump_modem_signals(serial_fd);
                fprintf(stderr,"%4d  %02x.\n",res,byte);
                if(res<0){
                    switch(errno){
                    case EINTR:
                    case EAGAIN:
                        break;
                    default:
                        perror("read serial port");
                        break;
                    }
                }else if(res==1){
                    mouse_sent_byte(&mouse,byte);
                }
            }while(1);
        }else{
            perror("open serial port");
            exit(1);
        }
    }/*serial_mouse*/


    static void check(int gravity,int res,const char* label)
    {
        if(res!=0){
            perror(label);
            if(gravity){
                exit(1);
            }
        }
    }/*check*/


    static void log(const char* fmt,...)
    {   
        va_list ap;
        va_start(ap,fmt);
        vprintf(fmt,ap);
        va_end(ap); 
        fflush(stdout);
    }/*log*/


    static void virtual_mouse(void)
    {
        int dx=0;
        int dy=0;
        int left=0;
        int right=0;
        int last=0;
        int fd=fileno(stdin);
        struct sgttyb s;
        s.sg_ispeed=B9600;
        s.sg_ospeed=B9600;
        s.sg_erase=0;
        s.sg_kill=0;
        s.sg_flags=RAW;
        check(0,ioctl(fd,TIOCSETP,&s),"ioctl set line parameters");
        kmouse_initialize();
        log("kmouse initialized.\n");
        while(1){
            int res;
            unsigned char buffer;
            res=read(fd,&buffer,1);
            if(res==1){
                log("read %02x %c\r\n",buffer,buffer);
                switch(buffer){
                case 'y': kmouse_update(0,0,1,0); dx=0;dy=0;left=0; break;
                case 'p': kmouse_update(0,0,0,1); dx=0;dy=0;right=0; break;
                case 'u': dx=0;dy=0;left=1-left;  break;
                case 'o': dx=0;dy=0;right=1-right; break;
                case 'n': dx=0;dy=0;left=1-left;right=left; break;
                case 'i': dx=0;dy=+1; break;
                case 'j': dy=0;dx=+1; break;
                case 'k': dx=0;dy=-1; break;
                case 'l': dy=0;dx=-1; break;
                case 'q': case 3: return;
                default: last=0; continue;
                }
                last=buffer; 
                log("event %d %d %d %d\r\n",dx,dy,left,right);
                kmouse_update(dx,dy,left,right);
                kmouse_dump_state();
            }
        }
    }/*virtual_mouse*/
    


    static const char* basename(const char* path)
    {   
        const char* base=strrchr(path,'/');
        if(base==0){
            return(path);
        }else{
            return(base+1);
        }
    }/*basename*/


    static const char* complete_device(const char* device)
    {
        if(device[0]=='/'){
            return(device);
        }else{
            char* buffer=malloc(strlen(device)+6);
            if(buffer==0){
                perror("malloc");
                exit(1);
            }
            sprintf(buffer,"/dev/%s",device);
            return(buffer);
        }
    }/*complete_device*/


    int main(int argc,const char** argv)
    {
        const char* pname=basename(argv[0]);
        const char* device="/dev/ttyb";
        int         do_virtual_mouse=0;
        int         i=1;
        while(i<argc){
            if((strcmp(argv[i],"-d")==0)
               ||(strcmp(argv[i],"--device")==0)){
                i++;
                if(i<argc){
                    device=complete_device(argv[i++]);
                }else{
                    fprintf(stderr,"Missing a device after -d or --device.\n");
                    exit(1);
                }
            }else if((strcmp(argv[i],"-h")==0)
                     ||(strcmp(argv[i],"--help")==0)){
                i++;
                fprintf(stderr,"%s usage:\n\n\t%s [-h|--help] [-d|--device /dev/tty?] [-k|--keyboard]\n\n",pname,pname);
                exit(0);
            }else if((strcmp(argv[i],"-k")==0)
                     ||(strcmp(argv[i],"--keyboard")==0)){
                i++;
                do_virtual_mouse=1;
            }else{
                fprintf(stderr,"Invalid option '%s'.\n",argv[i]);
                exit(1);
            }
        }
        if(do_virtual_mouse){
            virtual_mouse();
        }else{
            serial_mouse(device);
        }
        return(0);
    }/*main*/


/*** mouse.c                          --                     --          ***/
