/*
 * dosmemulator, Matthias Lautner
 * 
 * DANG_BEGIN_MODULE
 * 
 * This handles the keyboard.
 * 
 * Two keyboard modes are handled 'raw' and 'xlate'. 'Raw' works with codes
 * as sent out by the kernel and 'xlate' uses plain ASCII as used over
 * serial lines. The mapping for different languages & the two ALT-keys is
 * done here, but the definitions are elsewhere. Only the default (US)
 * keymap is stored here.
 * 
 * DANG_END_MODULE
 * 
 * DANG_BEGIN_CHANGELOG
 * 
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 * 
 * $Log: $
 * 
 * DANG_END_CHANGELOG
 */

#include "dosemu.h"
#include "base/memory.h"
#include "base/termio.h"
#include "base/shared.h"
#include "base/keyb.h"
#include "base/ioctlq.h"
#include "arch/signals.h"
#include "env/vc.h"

#include <termios.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/kd.h>
#include <sys/stat.h>

static void keyb_clear_raw_mode(void);
static void keyb_set_raw_mode(void);

static void child_set_kbd_flag(int);
static void child_clr_kbd_flag(int);
static int child_kbd_flag(int);

/* initialize these in keyboard_init()! */
static Bit16u child_kbd_flags = 0;
static int old_kbd_flags = -1;	/* flags for STDIN before our fcntl */
static int kbcount = 0;
static Bit8u kbbuf[KBBUF_SIZE];
static Bit8u *kbp;
static Bit8u erasekey;
static struct termios oldtermios;	/* original terminal modes */

static struct termios save_termios;

static void print_termios(struct termios term)
{
  k_printf("KBD: TERMIOS Structure:\n");
  k_printf("KBD: 	c_iflag=%lx\n", term.c_iflag);
  k_printf("KBD: 	c_oflag=%lx\n", term.c_oflag);
  k_printf("KBD: 	c_cflag=%lx\n", term.c_cflag);
  k_printf("KBD: 	c_lflag=%lx\n", term.c_lflag);
  k_printf("KBD: 	c_line =%x\n", (int)term.c_line);
}

#ifdef CONFIG_ENV_X
static void keyboard_X_init(void)
{
  scr_state.console_no = 0;
  config.console_keyb = 0;
  config.console_video = 0;
  config.console = 0;
  config.mapped_bios = 0;
  config.vga = 0;
  config.graphics = 0;
  if (config.speaker == SPKR_NATIVE)
    config.speaker = SPKR_EMULATED;
  child_kbd_flags = 0;
  scr_state.current = 1;
}
#endif

/*
 * DANG_BEGIN_FUNCTION keyboard_init
 * 
 * description: Initialize the keyboard to DOSEMU deafaults plus those
 * requested in the configs if allowable.
 * 
 * DANG_END_FUNCTION
 */
int keyboard_init(void)
{
  struct termios newtermio;	/* new terminal modes */
  struct stat chkbuf;
  int major, minor;

#ifdef CONFIG_ENV_X
  if (config.X) {
    keyboard_X_init();
    return 0;
  }
#endif

  if (config.usesX) {
    kbd_fd = dup(keypipe);
    if (kbd_fd < 0) {
      error("ERROR: Couldn't duplicate STDIN !\n");
      leavedos(66);
    }
  }
  else {
    kbd_fd = STDIN_FILENO;
  }

  fstat(kbd_fd, &chkbuf);
  major = chkbuf.st_rdev >> 8;
  minor = chkbuf.st_rdev & 0xff;

  /* console major num is 4, minor 64 is the first serial line */
  if ((major == 4) && (minor < 64)) {
    scr_state.console_no = minor;	/* get minor number */
    config.console = 1;
  }
  else {
    if (config.console_keyb || config.console_video)
      k_printf("KEYB: STDIN not a console-can't do console modes!\n");
    scr_state.console_no = 0;
    config.console_keyb = 0;
    config.console_video = 0;
    config.mapped_bios = 0;
    config.vga = 0;
    config.graphics = 0;
    config.console = 0;
    if (config.speaker == SPKR_NATIVE)
      config.speaker = SPKR_EMULATED;
  }

  old_kbd_flags = fcntl(kbd_fd, F_GETFL);
  fcntl(kbd_fd, F_SETFL, O_RDONLY | O_NONBLOCK);

  if (use_sigio && !config.kbd_tty) {
    k_printf("KBD: Using SIGIO\n");
    io_select_add(kbd_fd, 1);
  }
  else {
    k_printf("KBD: Not using SIGIO\n");
    io_select_add(kbd_fd, 0);
  }

  if (tcgetattr(kbd_fd, &oldtermios) < 0) {
    error("ERROR: Couldn't tcgetattr(STDIN,...) !\n");
    leavedos(66);
  }
  /*
   * DANG_BEGIN_REMARK
   * 
   * Code is called at start up to set up the terminal line for non-raw mode.
   * 
   * DANG_END_REMARK
   */
  newtermio = oldtermios;
  newtermio.c_iflag &= (ISTRIP | IGNBRK | IXON | IXOFF);
  /* newtermio.c_oflag &= ~OPOST; newtermio.c_cflag &= ~(HUPCL); */
  newtermio.c_cflag &= ~(CLOCAL | CSIZE | PARENB);
  newtermio.c_cflag |= CS8;
  newtermio.c_lflag &= 0;	/* ISIG */
  newtermio.c_cc[VMIN] = 1;
  newtermio.c_cc[VTIME] = 0;
  erasekey = newtermio.c_cc[VERASE];
  cfgetispeed(&newtermio);
  cfgetospeed(&newtermio);
  if (tcsetattr(kbd_fd, TCSANOW, &newtermio) < 0) {
    error("ERROR: Couldn't tcsetattr(STDIN,TCSANOW,...) !\n");
  }
  child_kbd_flags = 0;
  scr_state.current = 1;
  if (config.console_keyb) {
    keyb_set_raw_mode();
  }
  if (-1 == init_slang_keymaps()) {
    error("ERROR: Unable to initialize S-Lang keymaps.\n");
    leavedos(31);
  }
  return 0;
}

void keyboard_close(void)
{
  if (kbd_fd != -1) {
    if (config.console_keyb) {
      k_printf("KBD: keyboard_close:clear raw keyb\n");
      keyb_clear_raw_mode();
    }
    if ((config.console_keyb || config.console_video) && !config.usesX) {
      k_printf("KBD: keyboard_close: clear process control\n");
      clear_process_control();
    }
    fcntl(kbd_fd, F_SETFL, old_kbd_flags);
    tcsetattr(kbd_fd, TCSANOW, &oldtermios);

    close(kbd_fd);
    kbd_fd = -1;
  }
}

static void keyb_set_raw_mode(void)
{
  struct termios buf;

  k_printf("KBD: Setting keyboard to RAW mode\n");
  if (!config.console_video)
    fprintf(stderr, "\nKBD: Entering RAW mode for DOS!\n");
  ioctl_execute(kbd_fd, KDSKBMODE, K_RAW);

  if (tcgetattr(kbd_fd, &save_termios) < 0)
    return;

  buf = save_termios;

  buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  buf.c_iflag &= ~(IMAXBEL | IGNBRK | IGNCR | IGNPAR | BRKINT | INLCR | ICRNL | INPCK | ISTRIP | IXON | IUCLC | IXANY | IXOFF | IXON);
  buf.c_cflag &= ~(CSIZE | PARENB);
  buf.c_oflag &= ~(OCRNL | OLCUC | ONLCR | OPOST);
  buf.c_cflag |= CS8;
  buf.c_cc[VMIN] = 1;
  buf.c_cc[VTIME] = 0;

  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &buf) < 0) {
    k_printf("KBD: Setting to RAW mode failed.\n");
    return;
  }
  if (tcgetattr(kbd_fd, &buf) < 0) {
    k_printf("Termios ERROR\n");
  }
  k_printf("KBD: Setting TERMIOS Structure.\n");
  print_termios(buf);
}

static void keyb_clear_raw_mode(void)
{
  ioctl_execute(kbd_fd, KDSKBMODE, K_XLATE);
  if (tcsetattr(kbd_fd, TCSAFLUSH, &save_termios) < 0) {
    k_printf("KBD: Resetting Keyboard to K_XLATE mode failed.\n");
  }
}

#include "keyb_slang.c"

void keyb_get_keys(void)
{
  int cc;

  if (config.console_keyb) {	/* Keyboard at the console */
    if (scr_state.current != 1) {
      k_printf("KBD: Not current screen!\n");
      return;
    }
    kbcount = 0;
    kbp = kbbuf;

    cc = RPT_SYSCALL(read(kbd_fd, &kbp[kbcount], KBBUF_SIZE - 1));
    k_printf("KBD: cc found %d characters (Raw)\n", cc);
    if (cc == -1)
      return;

    if (cc > 0) {
      int i;

      for (i = 0; i < cc; i++) {
	k_printf("KEY: readcode: %d \n", kbp[kbcount + i]);
	child_set_flags(kbp[kbcount + i]);
	keyb_queue_scancode(kbp[kbcount + i]);
	k_printf("KBD: cc pushing %d'th character\n", i);
      }
    }
  }
  else {	/* Not keyboard at the console (not config.console_keyb) */
    do_slang_getkeys();
  }
}

void child_set_flags(int sc)
{
  switch (sc) {
    case 0xe0:
    case 0xe1:
      child_set_kbd_flag(4);
      return;
    case 0x2a:
      if (!child_kbd_flag(4))
	child_set_kbd_flag(1);
      child_clr_kbd_flag(4);
      return;
    case 0x36:
      child_set_kbd_flag(1);
      child_clr_kbd_flag(4);
      return;
    case 0x1d:
    case 0x10:
      child_set_kbd_flag(2);
      child_clr_kbd_flag(4);
      return;
    case 0x38:
      if (!child_kbd_flag(4))
	child_set_kbd_flag(3);
      child_clr_kbd_flag(4);
      return;
    case 0xaa:
      if (!child_kbd_flag(4))
	child_clr_kbd_flag(1);
      child_clr_kbd_flag(4);
      return;
    case 0xb6:
      child_clr_kbd_flag(4);
      child_clr_kbd_flag(1);
      return;
    case 0x9d:
    case 0x90:
      child_clr_kbd_flag(4);
      child_clr_kbd_flag(2);
      return;
    case 0xb8:
      child_clr_kbd_flag(3);
      child_clr_kbd_flag(4);
      return;
    case 0x3b:
    case 0x3c:
    case 0x3d:
    case 0x3e:
    case 0x3f:
    case 0x40:
    case 0x41:
    case 0x42:
    case 0x43:
    case 0x44:
    case 0x57:
    case 0x58:
      child_clr_kbd_flag(4);
      if (
#ifdef CONFIG_ENV_X
	   !config.X &&
#endif
	(child_kbd_flag(3) && child_kbd_flag(2) && !child_kbd_flag(1))) {
	int fnum = sc - 0x3a;

	k_printf("KDB: Doing VC switch\n");
	if (fnum > 10)
	  fnum -= 0x12;		/* adjust if f11 or f12 */

	/*
	 * can't just do the ioctl() here, as ReadKeyboard will probably
	 * have been called from a signal handler, and ioctl() is not
	 * reentrant. hence the delay until out of the signal handler...
	 */
	child_clr_kbd_flag(3);
	child_clr_kbd_flag(2);
	vt_activate(fnum);
	return;
      }
      return;
    case 0x51:
      if (
#ifdef CONFIG_ENV_X
	   !config.X &&
#endif
	(child_kbd_flag(2) && child_kbd_flag(3) && !child_kbd_flag(1))) {
	dbug_printf("ctrl-alt-pgdn\n");
	leavedos(42);
      }
      child_clr_kbd_flag(4);
      return;
    default:
      child_clr_kbd_flag(4);
      return;
  }
}

void keyb_set_leds(void)
{
  unsigned int led_state = 0;

  if (kbd_flag(KF_SCRLOCK)) {
    led_state |= (1 << LED_SCRLOCK);
  }
  if (kbd_flag(KF_NUMLOCK)) {
    led_state |= (1 << LED_NUMLOCK);
  }
  if (kbd_flag(KF_CAPSLOCK)) {
    led_state |= (1 << LED_CAPSLOCK);
  }
  k_printf("KBD: set_leds called\n");
  ioctl_execute(kbd_fd, KDSETLED, led_state);
}

/*
 * These are added to allow the CHILD process to keep its own flags on
 * keyboard status
 */

static void child_set_kbd_flag(int flag)
{
  child_kbd_flags |= (1 << flag);
}

static void child_clr_kbd_flag(int flag)
{
  child_kbd_flags &= ~(1 << flag);
}

static int child_kbd_flag(int flag)
{
  return ((child_kbd_flags >> flag) & 1);
}
