/* 
 *  pslash - a lightweight framebuffer splashscreen for embedded devices. 
 *
 *  Copyright (c) 2006 Matthew Allum <mallum@o-hand.com>
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *
 */

#include "psplash.h"

/* Globals, needed for signal handling */
static int ConsoleFd      = -1;
static int VTNum          = -1;
static int VTNumInitial   = -1;
static int Visible        =  1;

static void
vt_request (int UNUSED(sig))
{
  DBG("mark, visible:%i", Visible);

  if (Visible)
    {
      /* Allow Switch Away */
      if (ioctl (ConsoleFd, VT_RELDISP, 1) < 0)
	perror("Error cannot switch away from console");
      Visible = 0;

      /* FIXME: 
       * We likely now want to signal the main loop as to exit	 
       * and we've now likely switched to the X tty. Note, this
       * seems to happen anyway atm due to select() call getting
       * a signal interuption error - not sure if this is really
       * reliable however. 
      */
    }
  else
    {
      if (ioctl (ConsoleFd, VT_RELDISP, VT_ACKACQ))
	perror ("Error can't acknowledge VT switch");
      Visible = 1;
      /* FIXME: need to schedule repaint some how ? */
    }
}

static void
psplash_console_ignore_switches (void)
{
  struct sigaction    act;
  struct vt_mode      vt_mode;
  
  if (ioctl(ConsoleFd, VT_GETMODE, &vt_mode) < 0)
    {
      perror("Error VT_SETMODE failed");
      return;
    }

  act.sa_handler = SIG_IGN;
  sigemptyset (&act.sa_mask);
  act.sa_flags = 0;
  sigaction (SIGUSR1, &act, 0);
  
  vt_mode.mode = VT_AUTO;
  vt_mode.relsig = 0;
  vt_mode.acqsig = 0;

  if (ioctl(ConsoleFd, VT_SETMODE, &vt_mode) < 0)
    perror("Error VT_SETMODE failed");
}

static void
psplash_console_handle_switches (void)
{
  struct sigaction    act;
  struct vt_mode      vt_mode;
 
  if (ioctl(ConsoleFd, VT_GETMODE, &vt_mode) < 0)
    {
      perror("Error VT_SETMODE failed");
      return;
    }

  act.sa_handler = vt_request;
  sigemptyset (&act.sa_mask);
  act.sa_flags = 0;
  sigaction (SIGUSR1, &act, 0);
  
  vt_mode.mode   = VT_PROCESS;
  vt_mode.relsig = SIGUSR1;
  vt_mode.acqsig = SIGUSR1;

  if (ioctl(ConsoleFd, VT_SETMODE, &vt_mode) < 0)
    perror("Error VT_SETMODE failed");
}

void 
psplash_console_switch (void) 
{
  char           vtname[10];
  int            fd;
  struct vt_stat vt_state;

  if ((fd = open("/dev/tty0",O_WRONLY,0)) < 0)
    {
      perror("Error Cannot open /dev/tty0");
      return;
    }

  /* Find next free terminal */
  if ((ioctl(fd, VT_OPENQRY, &VTNum) < 0))
    {
      perror("Error unable to find a free virtual terminal");
      close(fd);
      return;
    }
  
  close(fd);
  
  sprintf(vtname,"/dev/tty%d", VTNum);

  if ((ConsoleFd = open(vtname, O_RDWR|O_NDELAY, 0)) < 0)
    {
      fprintf(stderr, "Error cannot open %s: %s\n", vtname, strerror(errno));
      return;
    }

  if (ioctl(ConsoleFd, VT_GETSTATE, &vt_state) == 0)
    VTNumInitial = vt_state.v_active;

  /* Switch to new free terminal */

  psplash_console_ignore_switches ();

  if (ioctl(ConsoleFd, VT_ACTIVATE, VTNum) != 0)
    perror("Error VT_ACTIVATE failed");
  
  if (ioctl(ConsoleFd, VT_WAITACTIVE, VTNum) != 0)
    perror("Error VT_WAITACTIVE failed\n");

  psplash_console_handle_switches ();
  
  if (ioctl(ConsoleFd, KDSETMODE, KD_GRAPHICS) < 0)
    perror("Error KDSETMODE KD_GRAPHICS failed\n");

  return;
}

void
psplash_console_reset (void)
{
  int              fd;
  struct vt_stat   vt_state;

  if (ConsoleFd < 0)
    return;

  /* Back to text mode */
  ioctl(ConsoleFd, KDSETMODE, KD_TEXT); 

  psplash_console_ignore_switches ();

  /* Attempt to switch back to initial console if were still active */
  ioctl (ConsoleFd, VT_GETSTATE, &vt_state);

  if (VTNum == vt_state.v_active)
    {
      if (VTNumInitial > -1)
        {
	  ioctl (ConsoleFd, VT_ACTIVATE, VTNumInitial);
	  ioctl (ConsoleFd, VT_WAITACTIVE, VTNumInitial);
	  VTNumInitial = -1;
        }
    }

  /* Cleanup */

  close(ConsoleFd); 

  if ((fd = open ("/dev/tty0", O_RDWR|O_NDELAY, 0)) >= 0)
    {
      ioctl (fd, VT_DISALLOCATE, VTNum);
      close (fd);
    }

  return;
}
