/*
 * DANG_BEGIN_MODULE
 *
 * This module contains the code to handle the signals telling DOSEMU
 * to switch in and out of the current console.
 *
 * 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/mouse.h"
#include "base/bios.h"
#include "base/port.h"
#include "base/detach.h"
#include "base/ioctlq.h"
#include "arch/signals.h"
#include "env/video.h"
#include "env/vc.h"

#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/kd.h>
#include <linux/vt.h>

static void allow_switch (void);

void scr_state_init(void)
{
  scr_state.vt_allow = 0;
  scr_state.vt_requested = 0;
  scr_state.mapped = 0;
  scr_state.pageno = 0;
  scr_state.virt_address = PAGE_ADDR(0);
  scr_state.current = 1;
}

static void parent_close_mouse(void)
{
  if (mice->intdrv) {
    if (mice->fd != -1) {
      io_select_remove(mice->fd, mice->add_to_io_select);
      DOS_SYSCALL(close(mice->fd));
      mice->fd = -1;
    }
  }
  else
    child_close_mouse();
}

static void parent_open_mouse(void)
{
  if (mice->intdrv) {
    if (mice->fd == -1) {
      mice->fd = DOS_SYSCALL(open(mice->dev, O_RDWR | O_NONBLOCK));
      if (mice->fd > 0)
	io_select_add(mice->fd, mice->add_to_io_select);
    }
  }
  else
    child_open_mouse();
}

static void SIGACQUIRE_call(void)
{
  vc_attach();
  parent_open_mouse();
}

static inline void forbid_switch(void)
{
  v_printf("VID: forbid_switch\n");
  scr_state.vt_allow = 0;
}

static void SIGRELEASE_call(void)
{
  if (scr_state.current == 1) {
    v_printf("VID: Releasing VC\n");
    if (!scr_state.vt_allow) {
      v_printf("VID: Disallowed vt switch!\n");
      return;
    }
    parent_close_mouse ();
    vc_detach();
  }

  scr_state.current = 0;	/* our console is no longer current */
  if (ioctl_execute(kbd_fd, VT_RELDISP, 1))	/* switch ok by me */
    v_printf("VID: VT_RELDISP failed!\n");
}

static void allow_switch(void)
{
  v_printf("VID: allow_switch\n");
  scr_state.vt_allow = 1;
  if (scr_state.vt_requested) {
    v_printf("VID: clearing old vt request\n");
    SIGRELEASE_call();
  }
}

static void vt_acquire(int sig, struct sigcontext_struct context)
{
  v_printf("VID: Acquiring VC\n");
  forbid_switch();
  if (ioctl(kbd_fd, VT_RELDISP, VT_ACKACQ))	/* switch acknowledged */
    v_printf("VID: VT_RELDISP failed (or was queued)!\n");
  allow_switch();
  signal_queue(SIGACQUIRE_call);
  scr_state.current = 1;
}

static void vt_release(int sig, struct sigcontext_struct context)
{
  v_printf("VID: release_vt\n");
  signal_queue(SIGRELEASE_call);
}

static int vc_active(int console_no)
{
  struct vt_stat vtstat;

  ioctl(kbd_fd, VT_GETSTATE, &vtstat);
  v_printf("VID: vc_active (ours=%d, cur=%d)\n", console_no, vtstat.v_active);
  return(vtstat.v_active == console_no);
}

void vc_init(void)
{
  struct stat chkbuf;
  int major, minor, console_no, other_no;

  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)) {
    console_no = minor;	/* get minor number */

    if (vc_active(console_no)) {
      other_no = (console_no == 1) ? 2 : 1;
      ioctl(kbd_fd, VT_ACTIVATE, other_no);
      while(vc_active(console_no)) signal_run();
      ioctl(kbd_fd, VT_ACTIVATE, console_no);
      while(!vc_active(console_no)) signal_run();
    }
  }
}

void set_process_control(void)
{
  struct vt_mode vt_mode;
  struct sigaction sa;

  vt_mode.mode = VT_PROCESS;
  vt_mode.waitv = 0;
  vt_mode.relsig = SIG_RELEASE;
  vt_mode.acqsig = SIG_ACQUIRE;
  vt_mode.frsig = 0;

  scr_state.vt_requested = 0;	/* a switch has not been attempted yet */
  allow_switch();

  NEWSETQSIG(SIG_RELEASE, vt_release);
  NEWSETQSIG(SIG_ACQUIRE, vt_acquire);

  if (ioctl_execute(kbd_fd, VT_SETMODE, (int) &vt_mode))
    v_printf("VID: initial VT_SETMODE failed!\n");
  v_printf("VID: Set process control\n");
}

void clear_process_control(void)
{
  struct vt_mode vt_mode;

  v_printf ("VID: Clearing process control\n");

  vt_mode.mode = VT_AUTO;
  ioctl (kbd_fd, VT_SETMODE, (int) &vt_mode);
  signal (SIG_RELEASE, SIG_IGN);
  signal (SIG_ACQUIRE, SIG_IGN);
}

void set_vc_screen_page(int page)
{
  v_printf("VID: set_vc_screen_page(%d)\n", page);

  WRITE_BYTE(BIOS_CURRENT_SCREEN_PAGE, page);
}

void vt_activate(int con_num)
{
  if (in_ioctl) {
    k_printf("KBD: can't ioctl for activate, in a signal handler\n");
    ioctl_execute(kbd_fd, VT_ACTIVATE, con_num);
  } else
    ioctl_execute(kbd_fd, VT_ACTIVATE, con_num);
}
