/*  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.
 */


/***
 *** SVGATextMode, an SVGA textmode setting program
 *** (c) 1995 Koen Gadeyne (kmg@barco.be)
 ***
 *** Edited on 132x64 screen. Hence the LONG lines. For best viewing conditions, use this program to make itself more readable :-)
 ***/


#include "misc.h"
#include "ttyresize.h"       /* must be first ! */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>

#ifndef DOS
#  include <asm/io.h>
#  include <linux/fs.h>
#  include <sys/ioctl.h>
#  include <sys/kd.h>
#endif

/*
 * this is not very clean, but it throws out all unix ttyy resizing stuff, which also makes it compile clean in DOS
 * However, DOS DOES support resizing. Don't let that confuse you. This is a hack for easy compiling.
 */
#ifdef DOS
#  define NO_RESIZE
#endif


#include "vga_prg.h"
#include "confdefs.h"
#include "setclock.h"
#include "parse_clock.h"
#include "parse_modeline.h"
#include "parse_font.h"
#include "parse_opt.h"
#include "validate.h"
#include "configfile.h"
#include "messages.h"
#include "run_extprog.h"

/*
 * global variables
 */

char *CommandName;
char ConfigFile[1024]=CONFIGFILE; 
int debug_messages=FALSE;

void usage()
{
     PMESSAGE(("version %s. (c) 1995 Koen Gadeyne.\n Usage: %s [options] [textmodelabel]\n\n\
     Options: -n  Don't program VGA hardware\n\
                  (parse config file and report mode parameters)\n\
              -d  print debugging information\n\
              -h  print usage information\n\
              -r  don't run ResetProg\n\
              -f  don't run FontProg\n\
              -c  don't change pixel clock\n\
              -v  don't validate H/V frequencies with limits in config file\n\
              -s  scan for all valid text modes from the config file\n\
              -a  always do a full resize, even if changing to same size screen\n\
              -m  allow 1x1 screen to avoid `VT_RESIZE: out of memory' error\n\
                  (relatively dangerous - read SVGATextMode(8) manual first!)\n\
              -t <ConfigFile>\n\
                  Use alternative config file instead of the default\n\
                  (default = %s)\n\n\
     Textmodelabel: an existing label from the config file `%s'\n\
                    (optional when `DefaultMode' defined in config file)\n",VERSION, CommandName, CONFIGFILE, ConfigFile));
}

/****************************************************************************************************************************/

int main (int argc, char* argv[])
{
  char* req_label=NULL;
  char *arg_str;
  char tempstr[1024]="";
  
  t_clockdef ck;
  t_fontdef fnt;
  int clockindex=0;
  const char *str_chipsets[NUM_CHIPSETS] = CHIPSET_STRINGS;
  
  /* sync range structure. 32 min/max pairs should do */
  float hsync_range[MAX_RANGE_SPECS][2];
  float vsync_range[MAX_RANGE_SPECS][2];

  int chipset = CS_NONE;

  int optionmask=0;
  int program_hardware=TRUE;
  int scanmodes=FALSE;
  int validate=TRUE;
  int alwaysresize=FALSE;
  int resize1x1=FALSE;
  char c;
    
  modestruct mode;
  
  int cursor_start=0, cursor_end=15;

#ifndef NO_RESIZE
  int resetprog=FALSE;
  char resetprogpath[1024]="";
  char termstrings[MAX_NR_CONSOLES][64];
  int terminals=-1;   /* -1 means no "Terminals" line */
#endif

  int sresize=TRUE;         /* will be set when screen has been resized and vice-versa */
  
  int run_resetprog=TRUE, run_fontprog=TRUE, run_pixclock=TRUE;
  int change_cursor=FALSE;

  FILE *param_file;
    

 /*
  * command-line argument parsing
  */

  CommandName = argv[0];

  while ((c = getopt (argc, argv, "ndhrfcsamvt:")) != EOF)
    switch (c)
    {
      case 'n': program_hardware=FALSE;
                break;
      case 'd': debug_messages=TRUE;
                break;
      case 'h': usage();
                exit(0);
                break;
      case 'r': run_resetprog=FALSE;
                break;
      case 'f': run_fontprog=FALSE;
                break;
      case 'c': run_pixclock=FALSE;
                break;
      case 's': scanmodes=TRUE;
                break;
      case 'm': resize1x1=TRUE;
                break;
      case 'v': validate=FALSE;
                break;
      case 'a': alwaysresize=TRUE;
                break;
      case 't': strcpy(ConfigFile, optarg);
                break;
      case '?': usage();
                PERROR(("Bad option '-%c'\n",(char)optopt));
                exit(-1);
                break;
      default: PERROR(("getopt returned unknown token '%c'.",c));
    }

  PVERSION
    
 /*
  * open parameter file
  */
  param_file = open_param_file(ConfigFile);

 /*
  * look for a text mode label. If none, look for a "defaultmode" in the config file
  */
  
  if (!scanmodes)
  {
    if (optind < argc)
       req_label = argv[optind];
    else
       {
         if ((arg_str=findlabel(param_file, "DefaultMode", LABEL_OPTIONAL+LABEL_FROMSTART)))
         {
           sscanf(arg_str, "%*s %s", req_label);
           PDEBUG(("No mode label on command line: using '%s' as default mode"));
         }
         else
         {
           usage();
           PERROR(("No textmode label on commandline, and no 'DefaultMode' in config file."));
         }
       }
  }


 /*
  * get chipset definition 
  */
  sscanf(findlabel(param_file, "ChipSet", LABEL_REQUIRED+LABEL_FROMSTART), "%*s %s", tempstr);
  chipset = findoption(tempstr, str_chipsets, NUM_CHIPSETS, "chip set");
  

 /*
  * Option parsing. Not all chips support all options
  */
  
  optionmask = parse_opt(param_file, chipset, str_chipsets[chipset]);

 /*
  * parse clock configuration in config file, fill out structure with info
  */

  parse_clock(param_file, chipset, &ck);
  
 /*
  * Get sync range limits (if any).
  */

  PDEBUG(("Scanning config file for scan frequency limits"));
  get_range(param_file, "HorizSync", hsync_range, "Horizontal Sync Frequency", DEFLT_HSYNC_MIN, DEFLT_HSYNC_MAX);
  get_range(param_file, "VertRefresh", vsync_range, "Vertical Refresh Frequency", DEFLT_VSYNC_MIN, DEFLT_VSYNC_MAX);
  
 /*
  * If user selected "scan" mode, scan all allowed modes. Otherwise, start searching for requested mode
  */
  
  if (scanmodes)
  {
    scan_valid_modes(param_file, chipset, &ck, hsync_range, vsync_range, optionmask, validate);
    fclose(param_file);
    exit(0);
  }

 /*
  * find last occurence of requested text mode line: This will allow user-patched mode lines at end of TextConfig file
  * to get preference over the ones above it, which normally are the default ones (as suggested by Kenneth Albanowski).
  */

  arg_str = findlabel(param_file, req_label, LABEL_REQUIRED+LABEL_FROMSTART+LABEL_LAST);
  if (!parsemodeline(arg_str, &mode))
    PERROR(("Badly formed textmode line at label '%s' in config file '%s'",req_label,ConfigFile));
     
  PDEBUG(("Font size will be %dx%d", mode.font_width, mode.font_height));
  PDEBUG(("sync polarities: Hsync: %c, Vsync: %c", (mode.h_polarity<0) ? '-' : '+', (mode.v_polarity<0) ? '-' : '+'));
  
#ifndef NO_RESIZE
 /*
  * See what terminals will need resizing, if any are given.
  */

  terminals=-1;
  arg_str = findlabel(param_file, "Terminals", LABEL_OPTIONAL+LABEL_FROMSTART);
  if (arg_str)
  {
    strtok(arg_str," ");
    terminals = 0; /* "Terminals" line found */
    while ((arg_str=strtok(NULL," ")))
    {
      strcpy(termstrings[terminals++], arg_str);
      if (terminals >= MAX_NR_CONSOLES) PERROR(("Too many terminals defined on 'Terminals' line (max=%d)", MAX_NR_CONSOLES));
    }
    PDEBUG(("Terminals: found %d terminal strings", terminals));
  }

 /*
  * get reset-program (will be called at end of SVGATextMode mode switch)
  */

  resetprog = GetPathOption(param_file, "ResetProg", LABEL_OPTIONAL+LABEL_FROMSTART, resetprogpath);

#endif

 /*
  * get font program path and font path. If not defined, default path will be used.
  * also get the default font table.
  */

  if (optionmask & OPT_LOADFONT)
  {
    parse_font(param_file, &fnt);
  }

 /*
  * get cursor definition
  */

  change_cursor=FALSE;
  if ((arg_str = findlabel(param_file, "Cursor", LABEL_OPTIONAL+LABEL_FROMSTART)))
  {
    strtok(arg_str," ");
    change_cursor=TRUE;
    if (!(arg_str=strtok(NULL," ")))
      PERROR(("Cursor definition: no <start>-<end> position given in config file."));
    else
    {
      if (sscanf(arg_str, "%d-%d", &cursor_start, &cursor_end) != 2)
        PERROR(("Cursor definition: malformed <start>-<end>."));
      check_int_range(cursor_start, 0, 31, "cursor start line");
      check_int_range(cursor_end, cursor_start, 31, "cursor end line");
      /* scale the cursor to fit the chosen font size */
      cursor_start = (cursor_start * mode.font_height) / 32;
      cursor_end = (cursor_end * mode.font_height) / 32;
    }
  }
  else
  {
    /* default cursor size */
    cursor_start = mode.font_height-2;
    cursor_end = mode.font_height-1;
  }
  PDEBUG(("Cursor will be from line %d to %d", cursor_start, cursor_end));


 /*
  * show the user what mode will be programmed, and also check if it is allowed.
  */
  
  check_and_show_mode(chipset, &mode, &ck, hsync_range, vsync_range, optionmask, validate, str_chipsets[chipset]);


 /*
  * Close the config file. We don't need it anymore.
  */

  fclose(param_file);

 /*
  * start changing some things now.
  */

  if (program_hardware)
  {
     sresize = check_if_resize(mode.cols, mode.rows);

     /* FIRST check for IO permissions, to avoid first resizing the screen, and then seeing that we can't
        write to the VGA regs */
         
     get_VGA_io_perm(chipset);
     
     /* sync disks if requested. Is there any way to do this and be SURE _all_ data has been flushed? */
     if (optionmask & OPT_SYNC)
     {
       PMESSAGE(("Syncing disks..."));
       sync(); sleep(2); /* same as in "reboot" command. Hopefully enough */
     }
     
#ifndef DOS
#  ifndef NO_RESIZE
   
     if (sresize || alwaysresize)
     {
      /*
       * first see if current kernel version supports resizing.
       */
       
       if (!check_kernel_version(1,1,54, "Virtual Terminal resizing"))
         PERROR(("Screen resizing not allowed (kernel version must be >= 1.1.54). Use a non-resizing text mode, or upgrade your kernel."));

      /*
       * Resize the screen. Still needs LOTS more error checking to avoid dropping out in the middle, leaving
       * the user with a garbled screen.
       *
       * sresize will be TRUE when resizing tty's should be forced (due to the 2nd attempt do_VT_RESIZE will do
       * when not enough memory is free).
       *
       */

        /*
         * ALWAYS do a VT_RESIZE, even if we already did a VT_RESIZEX on a 1.3.3 or higher kernel, 
         * until those kernel programmers make this unambiguous
         */
      
       if (do_VT_RESIZE(mode.cols, mode.rows, resize1x1)) sresize=TRUE;
         
       if (check_kernel_version(1,3,3, "VT_RESIZEX"))
         {
           if (do_VT_RESIZEX(mode.cols, mode.rows, mode.activelines, mode.font_height, mode.activepix/8*mode.font_width, mode.font_width, resize1x1)) sresize=TRUE;
         }
         
      /*
       * resize terminals. If specified in "terminals" line, do just those.
       * If not specified, find out which ones are active, and resize those.
       * This is obsoleted by kernel v1.3.3 and up (they do it themselves upon
       * a call to VT_RESIZE)
       */
          
       if (!check_kernel_version(1,3,3, "Automatic TTY resizing (SIGWINCH)"))
       {
         if (terminals >= 0) resize_specified_vts(termstrings, terminals, mode.cols, mode.rows);
           else resize_active_vts(mode.cols, mode.rows);
       }
     }

#  else 
     /*
      * no resizing support: do NOT allow resizing. Check if some stubborn moosehead tries anyway.
      */
      
      if (sresize) PERROR(("Resizing is not allowed (NO_RESIZE defined during compilation)."));
#  endif
#else   /* DOS */
      if (sresize) resize_DOS(mode.cols, mode.rows);
#endif   

   /*
    * now get to the REAL hardware stuff !
    */

    unlock(chipset);

    if (chipset==CS_S3)
    {
      if ((Inb_CRTC(0x31) & 0x40) && (!(optionmask & OPT_LOADFONT)))
        PERROR(("This S3 card is currently in 'high speed text display font fetch mode'. Also, font loading is not enabled. You MUST enable font loading, or the font will be completely screwed-up after SVGATextMode has finished"));
    }

    special(chipset); /* change chipset-specific things, if needed */

    if (run_pixclock)
      do_clock(chipset, &ck, mode.pclock, clockindex, optionmask);
    else PDEBUG(("Clock will NOT be programmed! (due to command line switch '-c') !"));
    
    Set_MAX_SCANLINE (mode.font_height);
    
    set_V_timings(mode.activelines, mode.start_vsync, mode.stop_vsync, mode.totalv);
    set_H_timings(mode.activepix, mode.start_hsync, mode.stop_hsync, mode.totalh);
    
    interlace(chipset, &mode);
    
    Outbit_CRTC(0x09, 7, (mode.mode_flags & DOUBLESCAN) ? 1 : 0);

    Set_CURSOR_START(cursor_start) ; Set_CURSOR_END(cursor_end);
    
    Set_Underline_location((optionmask & OPT_NO_UL) ? 31 : mode.font_height);

    Set_HSYNC_POLARITY(mode.h_polarity) ; Set_VSYNC_POLARITY(mode.v_polarity);

    Set_Textmode;  /* just in case some jerk set us in graphics mode. Or if he's in X-windows: surprise surprise ! */

#ifndef DOS
    /* set console to text mode */
    ioctl(opentty("/dev/console"), KDSETMODE, KD_TEXT);
#endif

    if (set_charwidth(mode.font_width)) PERROR(("Illegal character width: %d",mode.font_width));
    
    SCREEN_ON;
    
    /* could do a safe_wait_vsync() here to check if we have some refresh */

  }
  
 /*
  * call the external font loading program, if enabled by the 'option loadfont' line
  */

#ifdef S3_HS_TEXT
 if (mode.pclock > 36.0)  Outbit_CRTC(0x3A, 5, 1);   /* prepare S3 for high speed font store mode */
#endif

 if ((optionmask & OPT_LOADFONT) && (program_hardware) && (run_fontprog))
 {
   if (!(fnt.font_table[mode.font_width-8][mode.font_height-1]))
   {
     PWARNING(("Font loading enabled, but no font specified for %dx%d text mode.", mode.font_width, mode.font_height));
     PWARNING(("No font will be loaded."));
   }
   else
   {
     sprintf(tempstr,"%s %s/%s", fnt.fontprogpath, fnt.fontpath, fnt.font_table[mode.font_width-8][mode.font_height-1]);
     Run_extern_Prog(tempstr);
   }
 }

#ifdef S3_HS_TEXT
 if (mode.pclock > 36.0)
 {
   Outbit_CRTC(0x3A, 5, 0);   /* high speed font store done */
   Outbit_CRTC(0x31, 6, 1);   /* enable page mode high speed font fetch mode */
 }
#endif


#ifndef NO_RESIZE
 /*
  * call the reset program (which could be used to notify applications about the screen changes)
  * But only when the screen has been resized.
  *
  * Of course, nobody needs this when resizing is impossible (NO_RESIZE defined)
  */

 if ((resetprog) && (program_hardware) && (run_resetprog))
 {
    if (!sresize && !alwaysresize)
      PDEBUG(("Screen not resized. ResetProg not executed."));
    else
    {
      sprintf(tempstr,"%s %d %d", resetprogpath, mode.cols, mode.rows);
      Run_extern_Prog(tempstr);
    }
  }       
#endif

  return(0);
}

