/* int10.c (emx+gcc) */

/* Issue software interrupt 0x10, test int86() */
/* DOS only (see also vio.c) */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>

#pragma pack(1)
static union
{
  struct
    {
      char signature[4];
      unsigned short version;
      char *oem;
      unsigned long capabilities;
      unsigned short *modes;
    } info;
  struct
    {
      unsigned short mode_attr;
      unsigned char window_a_attr;
      unsigned char window_b_attr;
      unsigned short window_gran;
      unsigned short window_size;
      unsigned short window_a_start;
      unsigned short window_b_start;
      void *window_fun;
      unsigned short bytes_per_line;
      unsigned short width;
      unsigned short height;
      unsigned char cell_width;
      unsigned char cell_height;
      unsigned char planes;
      unsigned char bits_per_pixel;
      unsigned char banks;
      unsigned char memory_model;
      unsigned char bank_size;
      unsigned char image_pages;
    } mode;
  char fill[256];
} vesa;
#pragma pack(4)

static void usage (void)
{
  fputs ("Usage:\n"
         "int10 info               Display current video mode number\n"
         "int10 vesa-info          Display VESA information\n"
         "int10 mode <mode_number> Set video mode\n"
         "int10 text <text>        Display text\n"
         "int10 font <size>        Select font (sizes: 8x8, 8x14, 8x16)\n"
         "int10 vres <lines>       Select vertical resolution (200, 350, 480)\n",
         stderr);
  exit (1);
}


static void sel_font (int al)
{
  union REGS r;

  r.h.ah = 0x00; r.h.al = 0x03;
  _int86 (0x10, &r, &r);
  r.h.ah = 0x11; r.h.al = (unsigned char)al; r.h.bl = 0;
  _int86 (0x10, &r, &r);
  r.h.ah = 0x12; r.h.bl = 0x20;
  _int86 (0x10, &r, &r);
}


static void sel_vres (int al)
{
  union REGS r;

  r.h.ah = 0x12; r.h.bl = 0x30; r.h.al = al;
  _int86 (0x10, &r, &r);
  r.h.ah = 0x00; r.h.al = 0x03;
  _int86 (0x10, &r, &r);
}


int main (int argc, char *argv[])
{
  union REGS r;

  if (_osmode == OS2_MODE)
    {
      fputs ("This program does not run under OS/2.\n", stderr);
      return 1;
    }
  if (argc == 2 && strcmp (argv[1], "info") == 0)
    {
      r.h.ah = 0x0f;
      _int86 (0x10, &r, &r);
      printf ("Mode 0x%x, %d columns\n", r.h.al, r.h.ah);
    }
  else if (argc == 3 && strcmp (argv[1], "mode") == 0)
    {
      long mode;
      char *p;

      errno = 0;
      mode = strtol (argv[2], &p, 0);
      if (errno != 0 || mode < 0 || mode > 255 || *p != 0)
        usage ();
      r.h.ah = 0x00;
      r.h.al = (unsigned char)mode;
      _int86 (0x10, &r, &r);
    }
  else if (argc >= 3 && strcmp (argv[1], "text") == 0)
    {
      int i, len;
      char *p;

      len = 0;
      for (i = 2; i < argc; ++i)
        len += 1 + strlen (argv[i]);
      p = malloc (len + 1);
      if (p == NULL)
        {
          fputs ("Out of memory\n", stderr);
          exit (2);
        }
      strcpy (p, argv[2]);
      for (i = 3; i < argc; ++i)
        {
          strcat (p, " ");
          strcat (p, argv[i]);
        }

      /* Call BIOS to get the cursor position into DH and DL -- will
         be used below. */

      r.h.ah = 0x03;            /* Get cursor position and size */
      r.h.bh = 0;               /* Page number */
      _int86 (0x10, &r, &r);

      /* Use BIOS to display a string.  The cursor position is in DH
         and DL.  Unfortunately, _int86() doesn't suport the EBP
         register. */

      asm volatile ("pushl %%ebp;"
                    "movb $0x13, %%ah;" /* Write string (>=EGA) */
                    "movb $0x01, %%al;" /* Update cursor, format=char */
                    "movb $0x00, %%bh;" /* Page number */
                    "movb $0x07, %%bl;" /* Attribute: white on black */
                    "movl %0, %%ecx;"   /* Number of characters */
                    "movl %1, %%ebp;"   /* String */
                    "int $0x10;"
                    "popl %%ebp"
                    :                   /* Output */
                    : "g"(len), "g"(p)  /* Input */
                    : "ax", "bx", "cx", "dx" /* Clobber */
                    );
    }
  else if (argc == 2 && strcmp (argv[1], "vesa-info") == 0)
    {
      unsigned short *p;

      r.x.ax = 0x4f00;
      r.e.edi = (unsigned long)&vesa;
      _int86 (0x10, &r, &r);
      if (r.x.ax == 0x004f && memcmp (vesa.info.signature, "VESA", 4) == 0)
        {
          printf ("VESA version 0x%x\n", vesa.info.version);
          printf ("OEM: %s\n", vesa.info.oem);
          printf ("Capabilities: 0x%.8lx\n", vesa.info.capabilities);
          printf ("Modes:\n");
          for (p = vesa.info.modes; *p != 0xffff; ++p)
            {
              printf ("0x%.4x: ", *p);
              r.x.ax = 0x4f01;
              r.x.cx = *p;
              r.e.edi = (unsigned long)&vesa;
              _int86 (0x10, &r, &r);
              if (!(vesa.mode.mode_attr & 1))
                printf ("Mode not supported!\n        ");
              if (vesa.mode.mode_attr & 2)
                {
                  printf ("Width: %u, height: %u, bits per pixel: %u\n",
                          vesa.mode.width, vesa.mode.height,
                          vesa.mode.bits_per_pixel);
                  printf ("        ");
                }
              printf ("Attributes: mode: 0x%.2x, window A: 0x%.2x, "
                      "window B: 0x%.2x\n", vesa.mode.mode_attr,
                      vesa.mode.window_a_attr, vesa.mode.window_b_attr);
              printf ("        Window granularity: %dK, window size: %dK\n",
                      vesa.mode.window_gran, vesa.mode.window_size);
              printf ("        Start segment: window A: 0x%.4x, "
                      "window B: 0x%.4x\n",
                      vesa.mode.window_a_start, vesa.mode.window_b_start);
              printf ("        Bytes per scan line: %d\n",
                      vesa.mode.bytes_per_line);
              printf ("\n");
            }
        }
      else
        printf ("VESA not supported\n");
    }
  else if (argc == 3 && strcmp (argv[1], "font") == 0)
    {
      if (strcmp (argv[2], "8x8") == 0)
        sel_font (0x12);
      else if (strcmp (argv[2], "8x14") == 0)
        sel_font (0x11);
      else if (strcmp (argv[2], "8x16") == 0)
        sel_font (0x14);
      else
        usage ();
    }
  else if (argc == 3 && strcmp (argv[1], "vres") == 0)
    {
      switch (atoi (argv[2]))
        {
          case 200: sel_vres (0); break;
          case 350: sel_vres (1); break;
          case 480: sel_vres (2); break;
          default: usage (); break;
        }
    }
  else
    usage ();
  return 0;
}
