/*  SVGATextMode -- An SVGA textmode manipulation/enhancement tool
 *
 *  Copyright (C) 1995  Koen Gadeyne
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


/***
 *** tty resizing code for SVGATextMode.
 *** (c) 1995 Koen Gadeyne (kmg@barco.be)
 ***
 *** DOS resizing code for SVGATextMode.
 *** (c) 1995 Stephen Lee (sl14@cornell.edu)
 ***/


#ifndef DOS

/* If we need __KERNEL__, include those headers first, because otherwise they'll be incompletely included. */
#define __KERNEL__
#include <linux/tty.h>	/* for MAX_NR_CONSOLES */
#undef __KERNEL__

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <asm/page.h>	/* for PAGE_SIZE */
#include <linux/vt.h>   /* for VT_RESIZE */
#include <sys/utsname.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <termios.h>
#include <fcntl.h>

#include "misc.h"
#include "messages.h"
#include "ttyresize.h"


int opentty(char *devname)
{
  int fd;

  fd = open(devname, O_WRONLY | O_NOCTTY);
  if (fd < 0)
  {
     perror("open");
     PERROR(("Could not open %s for writing", devname));
     return -1;
  }
  return(fd);
}

void get_ttysize(int fd, char *devname, struct winsize *this_winsize)
{
  if (ioctl(fd, TIOCGWINSZ, this_winsize))
  {
     close(fd);
     perror("TIOCGWINSZ");
     PERROR(("Could not read Terminal size for %s", devname));
  } 
}

#ifndef NO_RESIZE

/*
 * Check if required operation is supported by current kernel version.
 */

int check_kernel_version(int req_ver, int req_rel, int req_pl, char* reason)
{
  struct utsname my_utsname;
  int kversion, kv_ver, kv_rel, kv_pl;
  int req_version = req_ver*1000000+req_rel*1000+req_pl;

  if (uname(&my_utsname))
  {
    perror("uname");
    PWARNING(("Could not get kernel version number. Assuming worst case..."));
    return(FALSE);
  }
  
  PDEBUG(("Checking if kernel supports %s (required: %d.%d.%d ; this: %s)",
           reason, req_ver, req_rel, req_pl, my_utsname.release)); 
  sscanf(my_utsname.release, "%d.%d.%d", &kv_ver, &kv_rel, &kv_pl);
  kversion = kv_ver*1000000+kv_rel*1000+kv_pl;
  return(kversion >= req_version);
}

void set_ttysize(int fd, char *devname, struct winsize *this_winsize, int cols, int rows)
{
  /* no need to skip this if the screen is not resized. The kernel already does that */

  PDEBUG(("set_tysize: Resizing %s from %dx%d to %dx%d", devname, this_winsize->ws_col, this_winsize->ws_row, cols, rows));
  this_winsize->ws_col = cols;
  this_winsize->ws_row = rows;
  if (ioctl(fd, TIOCSWINSZ, this_winsize))
  {
     close(fd);
     perror("TIOCSWINSZ");
     PERROR(("Could not set Terminal size for %s", devname));
  }
}

void resizetty(char *devicename, int cols, int rows)
{
  struct winsize my_winsize;
  int fd;

  fd = opentty(devicename);
  get_ttysize(fd, devicename, &my_winsize);
  set_ttysize(fd, devicename, &my_winsize, cols, rows);
  close(fd);
}

/*
 * Make real RAM free, by allocating and then releasing it.
 */

int make_ram_free(size_t bytes)
{
  /*
   * 1. Linux has a "lazy" malloc().  Memory is not actually allocated
   * until it is used.  Conclusion: We must use the RAM to actually
   * get it.
   *
   * 2. Linux has a "lazy" free().  Memory is not (necessarily) actually
   * freed until the program exits.  Conclusion: We must fork off a
   * child so that it can exit.
   *
   * Written by Michael Shields <shields@tembel.org>.
   */

  pid_t pid;
  int status;
  char *p;
  size_t i;

  if (!bytes)
    return 0;
    
  PDEBUG(("Freeing %d bytes of RAM for VT_RESIZE", bytes));

  pid = fork();

  switch(pid) {
  case -1:
    return -1;

  case 0:
    p = malloc(bytes);
    if (!p)
      exit(1);

    /* Dirty the pages.  This is better than calloc(). */
    for (i = 0; i < bytes; i += PAGE_SIZE)
        p[i] = 1;

    free(p);
    exit(0);

  default:
    waitpid(pid, &status, 0);
    return (WIFEXITED(status) && !WEXITSTATUS(status)) ? 0 : -1;
  }
}


/*
 * VT_RESIZE lets the KERNEL know that the screen has been resized,
 * so it writes the chars in the correct place in video memory
 *
 * This is done BEFORE programming any hardware, so if it gives an "out of memory" error on heavily loaded machines,
 * If it exits with an "ioctl: invalid argument" (because the kernel doesn't support VT_RESIZE),
 * the screen isn't left all messed up. (suggested by Alessandro Rubini).
 *
 * NOTE: VT_RESIZE only sets the kernel parameters for correctly writing into the VGA memory. It does NOT resize
 * terminals (the stty stuff). So it only makes the HARDWARE behave correctly. Terminals are another story.
 *
 */

int generic_VT_RESIZE(void* p_struct_size, void* dummy, int allow1x1, int memsize, int cmd, char* descr)
{
  int fd;
  int two_attempts=FALSE;

  PDEBUG(("%s", descr));
  fd = opentty("/dev/console");

  /*
   * First, get some memory, since the kernel cannot do a shrink_buffers() (=reduce file cache buffers in order to
   * get some more memory) while it is running with GFP_KERNEL priority to execute the VT_RESIZE. Resizing to large 
   * screens can require more memory than currently available.
   *
   * We need two bytes for each character (character + attribute), per console.
   */
  if (make_ram_free(memsize))
    PERROR(("malloc(): Could not get %d bytes of memory. Close some applications and try again...", memsize));
    
  /* The Real Thing (TM) ... */
  if (ioctl(fd, cmd, p_struct_size))
  {
    perror(descr);
    if (errno==ENOMEM)
    {
      if (allow1x1)
      {
        two_attempts=TRUE;
        /* Our first attempt failed. Get more memory by temporarily resizing the screen to 1x1, and THEN back to the new size */
        PMESSAGE(("Not enough free RAM. Trying via 1x1 screen..."));
          if (ioctl(fd, cmd, dummy))
          {
            perror(descr);
            PERROR(("Could not even resize screen to 1x1 to free more RAM.\n\
             You REALLY need to free some memory."));
          }
        /* now try again, but first reclaim the RAM. It could have been snatched away again. */
        make_ram_free(memsize);  /* no need to report errors at this stage. Nobody can read them, and they are useless */
        if (ioctl(fd, cmd, p_struct_size))
        {
            perror(descr);
            /* bailing out at this point is a bit useless, since we are then left with a 1x1 screen...*/
            PWARNING(("Could not set kernel screen size parameters. Serious trouble! (you are probably left with a 1x1 screen...)"));
        }
        PWARNING(("Could not get enough RAM for %s.\n\
         Tried via 1x1 screen to get even more memory.\n\
         All screens will be erased, except those that redraw themselves.", descr));
      }
      else PERROR(("Not enough free _physical_ RAM to resize the screen.\n\
       Consider using the '-m' command line switch to clear more memory,\n\
       BUT this clears ALL screens in the process!"));
    }
  }
  return(two_attempts);
}

int do_VT_RESIZE(int cols, int rows, int allow1x1)
{
  struct vt_sizes my_vt_size, dummy_vt_size;      /* passes the new screen size on to the kernel */
  int ram_needed = cols * rows * 2 * MAX_NR_CONSOLES;

  my_vt_size.v_rows = rows;
  my_vt_size.v_cols = cols;
  my_vt_size.v_scrollsize = 0; /* kernel tries to get as many scroll-back lines as possible by itself (?) */
  
  dummy_vt_size.v_rows = 1;
  dummy_vt_size.v_cols = 1;
  dummy_vt_size.v_scrollsize = 0;

  return(generic_VT_RESIZE(&my_vt_size, &dummy_vt_size, allow1x1, ram_needed, VT_RESIZE, "VT_RESIZE"));
}

/*
 * if VT_RESIZEX not supported (i.e. when compiling on < 1.3.3 kernels), define it.
 * this is just te keep the compiler happy
 */
 
#ifndef VT_RESIZEX
#  define VT_RESIZEX  0x560A
   typedef struct vt_consize { 
      ushort v_rows; ushort v_cols; ushort v_vlin; ushort v_clin; ushort v_vcol; ushort v_ccol;
    } vt_consize;
#endif


int do_VT_RESIZEX(int cols, int rows, int vlin, int clin, int vcol, int ccol, int allow1x1)
{
  struct vt_consize my_vt_size, dummy_vt_size;      /* passes the new screen size on to the kernel */
  int ram_needed = cols * rows * 2 * MAX_NR_CONSOLES;

  my_vt_size.v_rows = rows;
  my_vt_size.v_cols = cols;
  my_vt_size.v_vlin = vlin;
  my_vt_size.v_clin = clin;
  my_vt_size.v_vcol = vcol;
  my_vt_size.v_ccol = ccol;
  
  
  dummy_vt_size.v_rows = 1;
  dummy_vt_size.v_cols = 1;
  dummy_vt_size.v_vlin = clin;
  dummy_vt_size.v_clin = clin;
  dummy_vt_size.v_vcol = ccol;
  dummy_vt_size.v_ccol = ccol;
  
  return(generic_VT_RESIZE(&my_vt_size, &dummy_vt_size, allow1x1, ram_needed, VT_RESIZEX, "VT_RESIZEX"));
}

/*
 * resize all specified VT's.
 *
 * This function resizes the TERMINALS (equivalent to "stty rows ... cols ...) , if any were given in a "Terminals" line .
 * Another fine suggestion by Kenneth Albanowski
 *
 * It complements VT_RESIZE, which only resizes "the hardware". This resizes "the software".
 */
  
void resize_specified_vts(char terminaldevs[MAX_NR_CONSOLES][64], int numterminals, int cols, int rows)
{
  int i;
  char devstr[64];  

  PDEBUG(("Resizing all terminals specified in 'Terminals' line (when needed)"));
  
  for (i=0; i<numterminals; i++)
  {
    strcat(strcpy(devstr, "/dev/"), terminaldevs[i]);
    resizetty(devstr, cols, rows);
  }
}


/*
 * Resize the VT's reported active from VT_GETSTATE, unfortunately
 * the return param is a short so we can only hope to know about the
 * first 16.  This won't do anything to VT's beyond those first 16.
 * Returns 0 on success and -1 on failure.
 *
 * If you have >16 VT's to resize, you'll have to use the "Terminals" line.
 *
 * Written by Reuben Sumner <rasumner@undergrad.math.uwaterloo.ca>
 * and adapted a little by myself... (sorry, I couldn't resist)
 */

void resize_active_vts(int cols, int rows)
{
   int fd;
   struct vt_stat vst;
   unsigned short mask;
   int i;
   char devicename[64];
   
   PDEBUG(("Resizing all active VT's when needed"));
   
   fd = opentty("/dev/console");
   if (ioctl(fd, VT_GETSTATE, &vst))
   {
      perror("VT_GETSTATE");
      PERROR(("Could not do VT_GETSTATE on /dev/console"));
   }
   close(fd);

   for (mask = 1, i = 0; i < 16; i +=1, mask <<= 1)
   {
      if ((vst.v_state & mask) != 0)       /* only resize active VT's */
      {
        sprintf(devicename,"/dev/tty%d",i);
        resizetty(devicename, cols, rows);
      }
   }
}

#endif /* NO_RESIZE */

int check_if_resize(int cols, int rows)
{
  struct winsize my_winsize;
  int fd;
  char devicename[]="/dev/console";
  
  PDEBUG(("Checking if new mode requires screen resizing (from %s)", devicename));

  fd = opentty(devicename);
  get_ttysize(fd, devicename, &my_winsize);
  close(fd);
  return((my_winsize.ws_col != cols) || (my_winsize.ws_row != rows));
}


#else

/***
 *** DOS resizing code for SVGATextMode.
 *** (c) 1995 Stephen Lee (sl14@cornell.edu)
 ***/

/* for DJGPP v2 */
#include <pc.h>
#include <go32.h>
#include <sys/farptr.h>

#include "messages.h"

int resize_DOS(int cols, int rows)
{
  unsigned bioscolsp = 0x0044a;
  unsigned biosrowsp = 0x00484;
  
  _farpokew(_dos_ds, bioscolsp, cols);
  _farpokeb(_dos_ds, biosrowsp, rows - 1);
	
  return 0;
}

int check_if_resize(int cols, int rows)
{
  PDEBUG(("Checking if new mode requires screen resizing (from BIOS)"));

  return((ScreenCols() != cols) || (ScreenRows() != rows));
}

#endif
