/**
 * TrueReality - Display/remote/X/n64displayd.c
 * Copyright (C) 1998 Niki W. Waibel
 *
 * This program is free software; you can redistribute it and/
 * or modify it under the terms of the GNU General Public Li-
 * cence as published by the Free Software Foundation; either
 * version 2 of the Licence, or any later version.
 *
 * This program is distributed in the hope that it will be use-
 * ful, but WITHOUT ANY WARRANTY; without even the implied war-
 * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public Licence for more details.
 *
 * You should have received a copy of the GNU General Public
 * Licence along with this program; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 * USA.
 *
 * Information about me (the author):
 *   Niki W. Waibel, Reichenau 20, 6890 Lustenau, Austria - EUROPE
 *   niki.waibel@gmx.net
**/





#ifdef USE_REMOTE_DISPLAY
#ifdef UNIX
#ifdef SHM





#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/msg.h>
#include <sys/ipc.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "../../../config.h"
#include "../../../type_sizes.h"
#include "n64displayd.h"

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>

#include "../../../IPC/ipc.h"


int debug = 1;


Display         *dsp;
Screen          *scr;
Window          win;
Colormap        DefCMap;
XImage          *img;
GC              DefGC;
unsigned long   white, black;
KeySym          keysym;

char            title[] = "TrueReality from \"Niki W. Waibel\"";
int             WIDTH   = 640;
int             HEIGHT  = 480;

#ifdef SHM
   XShmSegmentInfo SHMInfo;
   XShmSegmentInfo SHMInfo2;
   int             UseSHM = 1;
#endif

PIXEL           *XBuf      = NULL;
char            *viram_reg = NULL;
long            flags      = 0;

int             pid;




#endif
#endif
#endif




int main(int argc, char **argv)
{
   
#ifdef USE_REMOTE_DISPLAY
#ifdef UNIX
#ifdef SHM



   signal(SIGHUP,  SIG_IGN);
   signal(SIGQUIT, SIG_IGN);
   signal(SIGUSR1, SIG_IGN);
   signal(SIGUSR2, SIG_IGN);
   signal(SIGCONT, SIG_IGN);


   
   if(InitIPC() < 0)
      exit(1);
   
   if(InitShmIDs() < 0)
      exit(1);
   

   
   /* forking into window- & IPC managing routines */

   pid = fork();

   switch(pid)
   {
      case -1:   /* error during fork */
         perror(argv[0]);
         printf("could not fork!\n\n");
         exit(1);

      case  0:   /* child (processes window events) */
         childLoop();
         exit(0);
   }

   /* parent is here */




   signal(SIGQUIT,   OnParentQuit);
   signal(SIGUSR2,   OnUsr2 );



   signal(SIGHUP,    SIG_IGN );
/*   signal(SIGINT,    OnInt   ); */
/*   signal(SIGILL,    OnIll   ); */
/*   signal(SIGTRAP,   OnSig   ); */
/*   signal(SIGABRT,   OnSig   ); */
/*   signal(SIGIOT,    OnSig   ); */
/*   signal(SIGBUS,    OnSig   ); */
/*   signal(SIGFPE,    OnSig   ); */
/*   signal(SIGKILL,   SIG_DFL ); */   /* This signal cannot be overridden */
   signal(SIGUSR1,   SIG_IGN );
/*   signal(SIGSEGV,   OnSig   ); */
/*   signal(SIGPIPE,   OnSig   ); */
/*   signal(SIGALRM,   OnSig   ); */
/*   signal(SIGTERM,   OnSig   ); */
/*   signal(SIGSTKFLT, OnSig   ); */
/*   signal(SIGCHLD,   OnSig   ); */
/*   signal(SIGCONT,   OnSig   ); */
/*   signal(SIGSTOP,   SIG_DFL ); */   /* This signal cannot be overridden */
/*   signal(SIGTSTP,   OnSig   ); */
/*   signal(SIGTTIN,   OnSig   ); */
/*   signal(SIGTTOU,   OnSig   ); */
/*   signal(SIGURG,    OnSig   ); */
/*   signal(SIGXCPU,   OnSig   ); */
/*   signal(SIGXFSZ,   OnSig   ); */
/*   signal(SIGVTALRM, OnSig   ); */
/*   signal(SIGPROF,   OnSig   ); */
/*   signal(SIGWINCH,  OnSig   ); */
/*   signal(SIGIO,     OnSig   ); */
/*   signal(SIGPOLL,   OnSig   ); */
/*   signal(SIGPWR,    OnSig   ); */
/*   signal(SIGUNUSED, OnSig   ); */

   doIPCLoop(pid);
   
   /* this should never be reached */
   printf("after wait forever\n"); fflush(stdout);


#endif
#endif
#endif


   
   return(0);   /* to make compiler happy */
}     


#ifdef USE_REMOTE_DISPLAY
#ifdef UNIX
#ifdef SHM




void OnUsr1(int arg)
{ 
   signal(SIGUSR1,   SIG_IGN );
   
   if(XBuf != NULL)
   {
      printf("Signal Usr1 received - DO DISPLAY REFRESH\n"); fflush(stdout);
   
      doDisplayRefresh();

      XFlush(dsp);
   }

   signal(SIGUSR1,   OnUsr1  );   /* This is for Linux (signal handler s set to default after a call) */
}





void OnUsr2(int arg)
{ 
/*   char buf[64];

*/

   signal(SIGUSR2,   SIG_IGN );
   
/*
   printf("Signal Usr2 received - GET IPC INFOS\n"); fflush(stdout);
   
   recvMsg(buf, 64);
   WriteIPC(ipc_server, buf + sizeof(long), 64 - sizeof(long));
   ReadIPC(ipc_server, buf, 64);
   sendMsg(buf);

   kill(pid, SIGCONT);
*/

   signal(SIGUSR2,   OnUsr2  );   /* This is for Linux (signal handler s set to default after a call) */
}





void OnHup(int arg)
{
   signal(SIGHUP,    SIG_IGN );
   
   printf("Signal Hup received - arg: %d\n", arg); fflush(stdout);
   
   signal(SIGHUP,    OnHup   );   /* This is for Linux (signal handler s set to default after a call) */
}





void OnChildQuit(int arg)
{
   signal(SIGQUIT,   SIG_IGN );
   
   printf("Signal Quit received - arg: %d\n"
          "You told me (the display) to quit.\n"
          "So I'll do, bye.\n\n", arg);
   fflush(stdout);

   shmdt(SHMInfo.shmaddr);
   shmdt(SHMInfo2.shmaddr);

   exit(1);
}





void OnParentQuit(int arg)
{
/*   struct msqid_ds m;
*/
   

   signal(SIGQUIT,   SIG_IGN );
   
   printf("Signal Quit received - arg: %d\n"
          "You told me (the IPC server) to quit.\n"
          "So I'll do, bye.\n\n", arg);
   fflush(stdout);

   WriteIPC(ipc_server, "QUIT", 4);

   RemoveListenIPC(ipc_server);

/*   msgctl(mid, IPC_RMID, &m);
*/
   exit(0);
}



void OnCont(int arg)
{
   signal(SIGCONT,   OnCont  );   /* This is for Linux (signal handler s set to default after a call) */
}



void doPrintText(char *text, int x, int y)
{
   XSetForeground(dsp, DefGC, white);
   XSetBackground(dsp, DefGC, black);

   XDrawImageString(dsp, win, DefGC, x, y, text, strlen(text));
}




void doPrintDisplayMode(flags)
{

   printf("DISPLAY:\n");
   switch(flags & 0x0003)
   {
      case 0:
         printf("   blank (no data, no sync)\n");
         break;

      case 1:
         printf("   reserved\n");
         break;

      case 2:
         printf("   5/5/5/3 (\"16\" bit)\n");
         break;
         
      case 3:
         printf("   8/8/8/8 (32 bit)\n");
         break;
   }
   if(flags & 0x0004)
      printf("   gamma_dither_enable\n");
   if(flags & 0x0008)
      printf("   gamma_enable\n");
   if(flags & 0x0010)
      printf("   divot_enable\n");
   if(flags & 0x0040)
      printf("   serrate\n");
   
   printf("   anti-alias (aa) mode\n");
   switch((flags & 0x0300) >> 8)
   {
      case 0:
         printf("      aa & resamp (always fetch extra lines)\n");
         break;

      case 1:
         printf("      aa & resamp (fetch extra lines if needed)\n");
         break;

      case 2:
         printf("      resamp only (treat as all fully covered)\n");
         break;
         
      case 3:
         printf("      neither (replicate pixels, no interpolate)\n");
         break;
   } 
}





long doReadMemWord(long *mem)
{
#ifdef ENDIAN_IS_BIG
      return(*mem);
#endif

#ifdef ENDIAN_IS_LITTLE
   long x;
   
   x = *mem;
   return(  (x << 24) + 
           ((x <<  8) & 0xff0000) +
           ((x >>  8) &   0xff00) + 
           ((x >> 24) &     0xff) );
#endif
}


/*
int sendMsg(char *str)
{
   char   *buf;
   size_t n;
   int retValue;


   
   n = strlen(str);
   
   buf = malloc(1 + n + sizeof(long));

   ((long *)buf)[0] = (long) n;

   strcpy(buf + sizeof(long), str);



   retValue = msgsnd(mid, (struct msgbuf *)buf, (int) n + 1, 0);



   free(buf);



   return(retValue);
}
*/



/*
int recvMsg(char *str, int max)
{
   int retValue;


   
   retValue = msgrcv(mid, (struct msgbuf *)str, (int) max - sizeof(long), 0, MSG_NOERROR);



   return(retValue);
}
*/



/*
void getFlags()
{
   char buf[64];



   sendMsg("GET ffffffff04400000");
   kill(getppid(), SIGUSR2);
   
   pause();
   
   recvMsg(buf, 64);
   


   sscanf(buf + 4, "%lx", &flags);
}
*/




void doDisplayRefresh()
{
   long new_width;


   
   /* video flags */
   
   flags = doReadMemWord( &((long *)viram_reg)[0] );
   doPrintDisplayMode(flags);
   
   
   
   /* video with */
   
   new_width = doReadMemWord( &((long *)viram_reg)[2] );
   if(new_width != WIDTH)
   {
      if(new_width < 320 || new_width > 640)
         WIDTH = 640;
      else
         WIDTH = (int)new_width;

      HEIGHT = WIDTH * 3 / 4;

      XResizeWindow(dsp, win, WIDTH, HEIGHT);
   }


   
   if(flags & 0x0003)
   {
      /* video origin */
   
      img->data = (char *)XBuf + doReadMemWord( &((long *)viram_reg)[1]);



      /* display it! */

      PutImage(0, 0, WIDTH, HEIGHT);
      
   }
   else
   {
      doPrintText(title, 0, HEIGHT);
      doPrintText("DISPLAY: blank (no data, no sync)", 20, 20);
   }


   
   if(new_width < 320 || new_width > 640)
   {
      doPrintText(title, 0, HEIGHT);
      doPrintText("DISPLAY: wrong width (640 will be used!)", 20, 20);
   }
}





void PutImage(int X, int Y, int W, int H)
{
#ifdef SHM
   if(UseSHM)
      XShmPutImage(dsp, win, DefGC, img, X, Y, (WIDTH-W)/2, (HEIGHT-H)/2, W, H, False);
   else
#endif
      XPutImage(dsp, win, DefGC, img, X, Y, (WIDTH-W)/2, (HEIGHT-H)/2, W, H);

  /* XFlush(dsp); */
}





int InitWindow()
{
   printf("Initializing Unix/X drivers:\n");
   

   
   printf("Opening display ..."); fflush(stdout);

   dsp = XOpenDisplay(NULL);
   
   if(!dsp)
   {
      printf(" FAILED\n\n"); fflush(stdout);
      return(-1);
   }

   scr     = DefaultScreenOfDisplay(dsp);
   
   white   = WhitePixelOfScreen(scr);
   black   = BlackPixelOfScreen(scr);
   
   DefGC   = DefaultGCOfScreen(scr);
   DefCMap = DefaultColormapOfScreen(scr);

   

   printf(" OK\n"
          "Opening window ..."); fflush(stdout);

   win = XCreateSimpleWindow(dsp, RootWindowOfScreen(scr), 0, 0, WIDTH, HEIGHT, 0, white, black);
   
   if(!win)
   {
      printf(" FAILED\n"); fflush(stdout);
      return(-1);
   }

   {
      XSizeHints Hints;
      XWMHints WMHints;



      Hints.flags       = PSize | PMinSize | PMaxSize;
      Hints.min_width   = 320;
      Hints.max_width   = 640;
      Hints.base_width  = WIDTH;
      Hints.min_height  = 240;
      Hints.max_height  = 480;
      Hints.base_height = HEIGHT;
      WMHints.input     = True;
      WMHints.flags     = InputHint;
  
      XSetWMHints(dsp, win, &WMHints);
      XSetWMNormalHints(dsp, win, &Hints);
      XStoreName(dsp, win, title);
   }

   /* input events */
   
   XSelectInput(dsp, win, ButtonPressMask | FocusChangeMask | ExposureMask | KeyPressMask | KeyReleaseMask);
   
   /* display window */
   
   XMapRaised(dsp, win);
   XClearWindow(dsp, win);
   /* XAutoRepeatOff(dsp); */



   printf(" OK\n"); fflush(stdout);


   return(0);
}





int InitShm()
{
   printf("Creating image ..." ); fflush(stdout);
   
   img = XShmCreateImage(dsp, DefaultVisualOfScreen(scr), sizeof(PIXEL)*8, ZPixmap, NULL, &SHMInfo, WIDTH, HEIGHT);

   if(!img)
   { 
      printf(" FAILED\n");
      return(-1);
   }


   printf(" OK\n"                     
          "Attaching shared memory ..."); fflush(stdout);
   
   XBuf = (PIXEL *) (img->data = SHMInfo.shmaddr = shmat(SHMInfo.shmid, 0, 0));
   viram_reg = SHMInfo2.shmaddr = shmat(SHMInfo2.shmid, 0, 0);
   
   if(!XBuf || !viram_reg) 
   {
      printf(" FAILED\n");
      return(-1);
   }
   

   

   SHMInfo.readOnly = False;
   SHMInfo2.readOnly = False;



   if(!XShmAttach(dsp, &SHMInfo))
   {
      printf(" FAILED\n");
      return(-1); 
   }         

   printf(" OK\n");

   return(0);
}




/*
int InitMsg()
{
   printf("Creating message queue ..." ); fflush(stdout);
   
   mid = msgget(IPC_PRIVATE, IPC_CREAT|0777);
   if(mid == -1)
   {
      printf(" FAILED\n");
      return(-1); 
   }         

   printf(" OK\n");

   return(0);
}
*/




int InitIPC()
{
   char buf[64];


   printf("Createing IPC listener socket and waiting for connection ..."); fflush(stdout);
   
   if(ListenIPC() < 0)
   {
      printf(" FAILED\n\n"); fflush(stdout);
      return(-1);
   }



   printf(" OK\n"  
          "Waiting for IPC sync ..."); fflush(stdout);

   ReadIPC(ipc_server, buf, 64);

   if(strncmp(buf, "8037", (size_t) 4))
   {
      WriteIPC(ipc_server, "QUIT", 4);
      
      printf(" FAILED\n"
             "Trying to remove the socket ..."); fflush(stdout);
             
      if(RemoveListenIPC(ipc_server) < 0)
      {
         printf(" FAILED\n"
                "You have to wait until the system removes the socket, otherwise\n"
                "you cannot use that socket again\n\n"); fflush(stdout);
      }
      else
      {
         printf(" OK\n\n"); fflush(stdout);
      }

      return(-1);
   }
   
   printf(" OK\n"); fflush(stdout);
   
   WriteIPC(ipc_server, "3780", 4);

   ReadIPC(ipc_server, buf, 64);

   WriteIPC(ipc_server, DISPLAYNAME, strlen(DISPLAYNAME));



   return(0);
}





int InitShmIDs()
{
   char buf[64];


   printf("Getting shared memory IDs via IPC ..."); fflush(stdout);

   ReadIPC(ipc_server, buf, 64);   /* 'SHMIDS 2' should be checked */
   
   ReadIPC(ipc_server, buf, 64);
   sscanf(buf, "%d", &SHMInfo.shmid);
   
   ReadIPC(ipc_server, buf, 64);
   sscanf(buf, "%d", &SHMInfo2.shmid);
   
   printf(" OK\n"); fflush(stdout);



   return(0);
}





void doIPCLoop(int pid)
{
   char buf[64];


   
   WriteIPC(ipc_server, "....", 4);

   for(;;)             /* just listen on IPC forever */
   {
      ReadIPC(ipc_server, buf, 64);

      if(!strncmp(buf, "QUIT", (size_t) 4))
      {
         printf("Got 'QUIT' from IPC\n"
                "Kill display server ...");
         fflush(stdout);
         
         kill(pid, SIGQUIT);
         
         printf(" OK\n"
                "I'll kill myself (IPC server), bye.\n\n"); fflush(stdout);

         WriteIPC(ipc_server, "DIED", 4);
                
         kill(getpid(), SIGQUIT);
      }
      else if(!strncmp(buf, "DISP", (size_t) 4))
      {
         printf("DisplayRefresh ...");
            
         kill(pid, SIGUSR1);
            
         printf(" end\n");
      }

   } /* for(;;) */
} /* doIPCLoop(int pid) */





void childLoop()
{
   int    i;
   char   text[20];
   XEvent event;





   signal(SIGHUP,  SIG_IGN);
   signal(SIGQUIT, SIG_IGN);
   signal(SIGUSR1, SIG_IGN);
   signal(SIGUSR2, SIG_IGN);
   signal(SIGCONT, SIG_IGN);


   
   if(InitWindow() < 0)
   {
      kill(getppid(), SIGQUIT);
      exit(1);
   }



   if(InitShm() < 0)
   {
      kill(getppid(), SIGQUIT);
      exit(1);
   }

   
   
   signal(SIGHUP,  OnHup);
   signal(SIGQUIT, OnChildQuit);
   signal(SIGUSR1, OnUsr1);
   signal(SIGUSR2, SIG_IGN);
   signal(SIGCONT, OnCont);


   
   for(;;)
   {
      /* read next event */
      XNextEvent(dsp, &event);

      switch(event.type)
      {
         case(Expose):
            if(event.xexpose.count == 0)
               //doDisplayRefresh();
               kill(getpid(), SIGUSR1);
            break;

         case(MappingNotify):
            XRefreshKeyboardMapping((XMappingEvent *)&event);
            break;
            
/*
         case(ButtonPress):
            XDrawImageString(event.xbutton.display, event.xbutton.window, DefGC,
 event.xbutton.x, event.xbutton.y, hi, strlen(hi));
            break;
*/

         case(KeyPress):
            i = XLookupString((XKeyEvent *)&event, text, 10, &keysym, 0);

            if(i == 1)
               switch(text[0])
               {
                  case '!':
                     printf("You pressed the '!'\n"
                            "... so I (the display) have to die.\n"
                            "But I'll kill the IPC server before ...\n\n");
                      
                     kill(getppid(), SIGQUIT);
               
                     exit(1);
                     
                  case 'r':
                     kill(getpid(), SIGUSR1);
                     break;
               }
            break;

      } /* end switch(event.type) */

   } /* end for(;;) */

} /* end void doLoop() */


#endif
#endif
#endif


