#define CHINESE_KERNEL


/* Copyright(c) 1993
   The below program is used to display chinese character in screen
   without X window in LINUX.The program is protected under GNU copyleft
   .The detailed is as GNU's COPYING which i include it in the package.
   You can distribute this program freely.But YOU MUST DISTRIBUTE THE
   COMPLETE CODE IN THE PACKAGE.If you have modified the package,you
   must note it in README file and told others how to get the origin 
   version.
*/
/*
   ChangeLog
   BuildDate 08/22/1993
       08/28/1993
          (1) Move part of code to ttyserver.c
       09/04/1993
          (1) Version 0.2 start.
	  (2) The virtual screen only interrupt when screen is in
	      graphics mode.
*/

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/kd.h>
#include <setjmp.h>
#include <sys/time.h>
#include <sys/vt.h>
#include <termios.h>
#include "chinese.h"
#include "chmsg.h"
#include "vga.h"
#define DEBUG
#ifdef DEBUG
#define D(p)  (p)
#else
#define D(p)  
#endif


/***********************************************/
/*   Get the control of virtual console from   */
/*   kernel                                    */
/***********************************************/
static FILE *out;
static int lastmode;
static struct vt_mode vmode[CONS_NUM];
static int ttyfd;
int infd[CONS_NUM];
static char cap[CONS_NUM];
static char buf[262];
static struct winsize ttywinsize;
static struct termios save_termios[CONS_NUM];
static int nowconsole;
static int have_screen_control;
static void unlock_vt(int signo)
{
  signal(SIGREL,unlock_vt);
  ch_restoretext();
  D(fprintf(out,"Release start %d\n",nowconsole));
  ioctl(infd[nowconsole],VT_RELDISP,1);
  D(fprintf(out,"Release end %d\n",nowconsole));
  have_screen_control = 0;
}

static void lock_vt(int signo)
{
  unsigned long mode;
  ChineseMessage msg;
  struct vt_stat vstate;
  static int forbid_switch = 0;
  void (*old_alrm)(int);

  old_alrm = signal(SIGALRM,SIG_IGN);
  signal(SIGACQ,lock_vt);
  if (forbid_switch)
    return;
  forbid_switch = 1;
  ioctl(infd[nowconsole],VT_RELDISP,VT_ACKACQ);
  have_screen_control = 1;
  ioctl(infd[nowconsole],VT_GETSTATE,(int)&vstate);
  nowconsole = vstate.v_active-1;
  ch_change_console(nowconsole);

  D(fprintf(out,"Captured %d\n",nowconsole));
  if (old_alrm)
    signal(SIGALRM,old_alrm);
  forbid_switch = 0;
  
}

static char ttys[] = {"/dev/tty0"};
static void Install_vt_control()
{
  int curcons,mask,fd;
  struct vt_mode mode;
  struct vt_stat stat;
  
  ttyfd = open("/dev/tty",O_WRONLY);
  ioctl(ttyfd,VT_GETSTATE,(int)&stat);
  mask = 2;  
  nowconsole = stat.v_active-1;
  for(curcons = 0;curcons < CONS_NUM;curcons++)
    {
      ttys[8] = curcons+'1';

      if (stat.v_state & mask)
	{
	  fd = open(ttys,O_RDWR|O_NDELAY);
	  ioctl(fd,VT_GETMODE,(int)&vmode[curcons]);
	  close(fd);
	}
      mask << 1;
    }
  close(ttyfd);
  signal(SIGREL,unlock_vt);
  signal(SIGACQ,lock_vt);
}

void Restore_vt_control()
{
  int curcons,mask,fd;
  struct vt_mode mode;
  struct vt_stat stat;

  ioctl(0,VT_GETSTATE,(int)&stat);
  mask = 2;  
  nowconsole = stat.v_active-1;
  for(curcons = 0;curcons < CONS_NUM;curcons++)
    {
      if (stat.v_state & mask)
	{
	  tcsetattr(infd[curcons],TCSAFLUSH,&save_termios[curcons]);
	  ioctl(infd[curcons],VT_SETMODE,(int)&vmode[curcons]);
	  ioctl(infd[curcons],KDSETMODE,KD_TEXT);
	}
      mask << 1;
    }
}  

static int chpid;
static int msgid;
static struct msqid_ds mbuf;
static CBuffer *buffer;
static int input_tty;
static jmp_buf reset;

static daemon_init()
{
  int   fd;
  
  chdir("/");
  umask(0);

  out = fopen("/dev/tty8","w");
}



outc(int con,int c)
{
  static char outbuf[256];
  static int ptr=0;
  static int i;
  

  if (c == -1)
    {
      _cwrite(con,outbuf,ptr);
      ptr = 0;
      return;
    }
  outbuf[ptr] = c;
  ptr++;
  if (ptr == 255)
    {
      _cwrite(con,outbuf,ptr);
      ptr = 0;
    }
}

interrupt_vt_control(int curcons)
{
  struct vt_mode mode;
  struct winsize size;
  struct termios buf;
  struct vt_stat stat;

  if (curcons < 0 || curcons >= CONS_NUM)
    return;
  
  if (cap[curcons])
    return;
  
  mode = vmode[curcons];
  mode.mode = VT_PROCESS;
  mode.relsig = SIGREL;
  mode.acqsig = SIGACQ;
  ioctl(infd[curcons],VT_SETMODE,(int)&mode);
  buf = save_termios[curcons];
  buf.c_cc[VMIN] = 1;
  buf.c_cc[VTIME]=0;

  buf.c_lflag &= ~(ECHO |ICANON | IEXTEN | ISIG);
  buf.c_iflag &= ~(BRKINT |ICRNL | INPCK | ISTRIP | IXON);
  buf.c_cflag &= ~(CSIZE | PARENB);
  buf.c_cflag |= CS8;
  buf.c_oflag &= ~(OPOST);

  tcsetattr(infd[curcons],TCSAFLUSH,&buf);
  /* In the mean time the nowconsole can be a wrong value */
  /* because the just opened console is not under control */
  /* in the last switch,so we reget the nowconsole here.  */
  ioctl(infd[curcons],VT_GETSTATE,(int) &stat);
  nowconsole = stat.v_active-1;
  ch_change_console(nowconsole);
}
restore_vt_control(int curcons)
{
  int i;
  char buf[80];

  if (curcons < 0 || curcons >= CONS_NUM)
    return;

  ioctl(infd[curcons],VT_SETMODE,&vmode[curcons]);
  /* In the mean time,the screen will be filled with trash */
  /* So write blank to screen                              */
  for(i=0;i<80;i++)
    buf[i] = ' ';
  for(i=0;i<25;i++)
    write(infd[0],buf,80);
}

read_chinese_channel()
{
  static int state = 0;
  static int datalen;
  static int con;
  int len,i;
  
  unsigned char *ss = buf;

  len = read(0,buf,256);
  for(i=0;i<len;i++)
    {
      switch(state)
	{
	case 0:
	  if (*ss == 255)
	    state = 1;
	  break;
	case 1:
	  if (*ss == 254)
	    state = 2;
	  break;
	case 2:
	  datalen = *ss;
	  state = 3;
	  break;
	case 3:
	  datalen += 256*(*ss);
	  state = 4;
	  break;
	case 4:
	  con = *ss;
	  if (con < 0 || con >= CONS_NUM)
	    state = 0;
	  else
	    state = 5;
	  if (con == nowconsole && have_screen_control)
	    ch_write_to_screen(1);
	  else
	    ch_write_to_screen(0);
	  break;
	case 5:
	  switch(*ss)
	    {
	    case CCMD_START:
	      interrupt_vt_control(con);
	      D(fprintf(out,"START\n"));
	      state = 0;
	      cap[con] = 1;
	      break;
	    case CCMD_END:
	      if (!cap[con])
		return;
	      cap[con] = 0;
	      restore_vt_control(con);
	      state = 0;
	      break;
	    case CCMD_PRINT:
	      if (!cap[con])
		return;
	      D(fprintf(out,"PRINT %d bytes\n",datalen));
	      state = 6;
	      break;
	    case CCMD_GRAPH:
	      if (!cap[con])
		return;
	      
	      ch_graphicmode(con);
	      
	      state = 0;
	      ioctl(infd[con],KDSETMODE,KD_GRAPHICS);
	      D(fprintf(out,"GRAPHIC %d\n",con));
	      break;
	    case CCMD_TEXT:
	      if (!cap[con])
		return;
	      D(fprintf(out,"TEXT\n"));
	      ch_textmode(con);
	      ioctl(infd[con],KDSETMODE,KD_TEXT);
	      state = 0;
	      break;
	    case CCMD_INPUT:
	      if (!cap[con])
		return;
	      state = 9;
	      break;
	    case CCMD_ACK:
	      if (!cap[con])
		return;
	      buf[0] = 255;buf[1] = 254;
	      buf[2] = 6;buf[3] = 0;
	      buf[4] = con;
	      buf[5] = 255;buf[6] = 254; buf[7] = buf[8] = 0;
	      buf[9] = con;buf[10] = CCMD_ACK;
	      write(1,buf,11);
	      D(fprintf(out,"%d:ACK\n",con));
	      state = 0;
	    case CCMD_SWINSZ:
	      D(fprintf(out,"SWINSZ\n"));
	      if (!cap[con])
		return;
	      state =  10;
	      break;
	    default:
	      D(fprintf(out,"Error %d\n",*ss));
	      state = 0;
	    }
	  break;
	case 6:
	  if (nowconsole == con)
	    ch_write_to_screen(1);
	  else
	    ch_write_to_screen(0);
	  /*This should't happen */
	  if (datalen == 0)
	    {
	      state = 0;
	      break;
	    }
	  outc(con,*ss);
	  datalen--;
	  if (datalen == 0)
	    {
	      state = 0;
	      outc(con,-1);
	    }
	  break;
	case 9:
	  ch_inputmethod(*ss);
	  break;
	case 10:
	  {
	    static int x,y;
	    switch(datalen)
	      {
	      case 2:
		D(fprintf(out,"X= %d\n",*ss));
		x = *ss;
		datalen = 1;
		break;
	      case 1:
		y = *ss;
		D(fprintf(out,"Y=%d\n",y));
		ch_setwsize(con,x,y);
		state = 0;
		break;
	      default:
		datalen--;
	      }
	  }
	  break;
	      	      
	default:
	  state = 0;
	}
      ss++;
    }
}
	    
	  
read_tty(int tty)
{
  int len;
  int fd;
  static int lasttty= -1;
  char trbuf[256];

  fd = infd[tty];
  if (lasttty != tty)
    {
      /*  Send the head code */
      len = read(fd,buf+5,100);
      if (len < 0)
	return;
      buf[0] = 255;buf[1] = 254;buf[2] = len;buf[3] = 0;buf[4] = tty;
      len = write(1,buf,len+5)-5;
      lasttty = tty;
    }
  else
    {
      len = read(fd,buf,255);
      len = ch_read_from_buffer(buf,trbuf,len);
      if (len < 0)
	return;
      len = write(1,trbuf,len);
    }
  
  D(fprintf(out,"Input to tty%d %dbytes\n",lasttty,len));
}

ioloop()
{
  fd_set readfds;
  int maxfds,mask,i;

  while(1)
    {
      FD_ZERO(&readfds);
      FD_SET(0,&readfds);
      maxfds = 0;
      for(i=0;i<CONS_NUM;i++)
	{
	  if (cap[i])
	    {
	      FD_SET(infd[i],&readfds);
	      if (infd[i] > maxfds)
		maxfds = infd[i];
	    }
	}

      if (select(maxfds+1,&readfds,NULL,NULL,NULL)>=0)
	{
	  if (FD_ISSET(0,&readfds))
	    {
	      /* stdin */
	      read_chinese_channel();
	    }
	  
	  for(i = 0;i < CONS_NUM;i++)
	    {
	      if (cap[i])
		{
		  if (FD_ISSET(infd[i],&readfds))
		    read_tty(i);
		}
	    }
	}
    }
}


void newsigint(int signo)
{
  signal(SIGINT,newsigint);
  adm_end();
  exit(0);
}

void newsigterm(int signo)
{
  signal(SIGTERM,newsigterm);
  adm_end();
  exit(0);
}
void newsigkill(int signo)
{
  adm_end();
  exit(0);
}

void newsigsegv(int signo)
{
  adm_end();
  exit(0);
}

signal_init()
{
  signal(SIGINT,newsigint);
  signal(SIGTERM,newsigterm);
  signal(SIGQUIT,newsigterm);
  signal(SIGSEGV,newsigsegv);
  signal(SIGKILL,newsigkill);
}

main()
{
  int i;
  struct winsize size;
  struct termios buf;


  atexit(adm_end);
  daemon_init();
  Install_vt_control();
  atexit(Restore_vt_control);


  fprintf(out,"Chinese driver 0.2 by Yu-Chung Wang 09/04/1993\n");
  adm_init();
  adm_end();
  size.ws_row = 29;
  size.ws_col = 80;
  size.ws_xpixel = 29*16;
  size.ws_ypixel = 80*8;
  for(i=0;i<CONS_NUM;i++)
    {
      ttys[8] = i + '1';
      infd[i] = open(ttys,O_RDWR);
      cap[i] = 0;
      tcgetattr(infd[1],&save_termios[i]);
    }
  signal_init();
  fprintf(out,"Chinese driver init succefully\n");
  ioloop();
  adm_end();
}









