/*
 * main.c
 *
 * KinoAMP Front End
 */

#include "config.h"
#include "lib.h"
#include "main.h"
#include "msg.h"
#include "playlist.h"
#include "ro_file.h"
#include "ka_error.h"

// icon flags
#define IC_DEF       0x07000121
#define IC_SELECTED  (1<<21)
#define IC_SHADED    (1<<22)
// item flags
#define IT_TICKED    1

static struct
{
  menu_block_t blk;
  menu_item_t info;
  menu_item_t dvds;
  menu_item_t choices;
  menu_item_t saved;
  menu_item_t film_info;
  menu_item_t playlist;
  menu_item_t reload;
  menu_item_t help;
  menu_item_t quit;
}
  iconbar_menu =
{
  {"KinoAMP", NULL, 8, 7, 2, 7, 0, 200, 44, 0},
  {0x100, -1, IC_DEF, "Info"        , NULL, 5},
  {    0, -1, IC_DEF, "Dvds"        , NULL, 5},
  {    0, -1, IC_DEF, "Choices..."  , NULL, 11},
  {    0, -1, IC_DEF, "Saved"       , NULL, 6},
  {    0, -1, IC_DEF, "Film Info...", NULL, 13},
  {    0, -1, IC_DEF, "Playlist..." , NULL, 12},
  {    0, -1, IC_DEF, "Reload"      , NULL, 7},
  {    0, -1, IC_DEF, "Help..."     , NULL, 8},
  { 0x80, -1, IC_DEF, "Quit"        , NULL, 5}
};

static struct
{
  menu_block_t blk;
  menu_item_t drive[4];
}
  dvds_menu =
{
  {"Dvd drives", NULL, 11, 7, 2, 7, 0, 200, 44, 0},
  {{0x100, -1, IC_DEF, "Drive 0", NULL, 8},
   {    0, -1, IC_DEF, "Drive 1", NULL, 8},
   {    0, -1, IC_DEF, "Drive 2", NULL, 8},
   { 0x80, -1, IC_DEF, "Drive 3", NULL, 8}}
};

static struct
{
  menu_block_t blk;
  menu_item_t mag[5];
}
  zoom_menu =
{
  {"Zoom", NULL, 5, 7, 2, 7, 0, 200, 44, 0},
  {{0x100, -1, IC_DEF, "50%" , NULL, 4},
   {    0, -1, IC_DEF, "100%", NULL, 5},
   {    0, -1, IC_DEF, "200%", NULL, 5},
   {    0, -1, IC_DEF, "300%", NULL, 5},
   { 0x80, -1, IC_DEF, "400%", NULL, 5}}
};

static struct
{
  menu_block_t blk;
  menu_item_t col[5];
}
  col_menu =
{
  {"Colours", NULL, 8, 7, 2, 7, 0, 200, 44, 0},
  {{0x100, -1, IC_DEF, "256 colours", NULL, 12},
   {    0, -1, IC_DEF, "4 thousand" , NULL, 12},
   {    0, -1, IC_DEF, "32 thousand", NULL, 12},
   {    0, -1, IC_DEF, "64 thousand", NULL, 12},
   { 0x80, -1, IC_DEF, "16 million" , NULL, 11}}
};

static struct
{
  menu_block_t blk;
  menu_item_t module[2];
}
  audioplayer_menu =
{
  {"Player", NULL, 7, 7, 2, 7, 0, 200, 44, 0},
  {{0x100, -1, IC_DEF, "AMPlayer"  , NULL, 9},
   { 0x80, -1, IC_DEF, "DiskSample", NULL, 11}}
};

static struct
{
  menu_block_t blk;
  menu_item_t mode[4];
}
  mode_menu =
{
  {"Mode", NULL, 5, 7, 2, 7, 0, 200, 44, 0},
  {{0x100, -1, IC_DEF, "Multitasking", NULL, 13},
   {    0, -1, IC_DEF, "Desktop"     , NULL, 8},
   {    0, -1, IC_DEF, "Auto"        , NULL, 5},
   { 0x80, -1, IC_DEF, "Manual"      , NULL, 7}}
};

static struct
{
  menu_block_t blk;
  menu_item_t scaler[4];
}
  scaler_menu =
{
  {"Scaler", NULL, 7, 7, 2, 7, 0, 200, 44, 0},
  {{0x100, -1, IC_DEF, "Fast"  , NULL, 5},
   {    0, -1, IC_DEF, "System", NULL, 7},
   {    0, -1, IC_DEF, "Linear", NULL, 7},
   { 0x80, -1, IC_DEF, "Bilinear", NULL, 9}}
};

static struct
{
  menu_block_t blk;
  menu_item_t method[4];
}
  deinterlace_menu =
{
  {"Deinterlace method", NULL, 19, 7, 2, 7, 0, 200, 44, 0},
  {{0x100, -1, IC_DEF, "None"  , NULL, 5},
   {    0, -1, IC_DEF, "Blend", NULL, 6},
   {    0, -1, IC_DEF, "Blend (x2)", NULL, 11},
   { 0x80, -1, IC_DEF, "Fields x2", NULL, 10}}
};

static struct
{
  menu_block_t blk;
  menu_item_t method[3];
}
  resizemode_menu =
{
  {"Resize", NULL, 7, 7, 2, 7, 0, 200, 44, 0},
  {{0x100, -1, IC_DEF, "Source"    , NULL,  7},
   {    0, -1, IC_DEF, "Pan & scan", NULL, 11},
   { 0x80, -1, IC_DEF, "Stretch"   , NULL,  8}}
};

static struct
{
  menu_block_t blk;
  menu_item_t method[10];
}
  resizeto_menu =
{
  {"Aspect ratios", NULL, 14, 7, 2, 7, 0, 200, 44, 0},
  {{0x100, -1, IC_DEF, "Monitor"   , NULL,  8},
   {    0, -1, IC_DEF, "  5:   4"  , NULL,  9},
   {    0, -1, IC_DEF, "  4:   3"  , NULL,  9},
   {    0, -1, IC_DEF, " 16:  10"  , NULL,  9},
   {    0, -1, IC_DEF, " 16:   9"  , NULL,  9},
   {    0, -1, IC_DEF, "256: 135"  , NULL,  9},
   { 0x80, -1, IC_DEF, " 21:   9"  , NULL,  9}}
};

// Options window icon numbers
#define ICON_OK                0
#define ICON_CANCEL            1
#define ICON_HELP              2

#define ICON_MAIN              4
#define ICON_IMAGE             5
#define ICON_AUDIO             6
#define ICON_WINDOW            7
#define ICON_MULTITASK         8
#define ICON_DESKTOP           9
#define ICON_FULLSCREEN       10
#define ICON_MANUAL           11

// Op_Main window icon numbers
#define ICON_MODE_STR          3
#define ICON_MODE_MENU         4
#define ICON_AUTO_EXIT         5
#define ICON_LOOP              6
#define ICON_ALL_FRAMES        7

#define ICON_RANDOM           10

#define ICON_ACCEL_INTELDMA   13
#define ICON_ACCEL_GEMINUS    14
#define ICON_ACCEL_VIDEOOVERLAY 15
#define ICON_ACCEL_VIDEOOVERLAY_NOT_MULTITASKING 16

// Op_Image window icon numbers
#define ICON_LOCK_ASPECT       2
#define ICON_PIXEL_ASPECT      3
#define ICON_DEF_VIDEO         6
#define ICON_DEINTERLACE_STR  29
#define ICON_DEINTERLACE_MENU 30
#define ICON_RESIZEMODE_STR   32
#define ICON_RESIZEMODE_MENU  33
#define ICON_MONITOR_X        35
#define ICON_MONITOR_Y        37
#define ICON_RESIZETO_STR     39
#define ICON_RESIZETO_MENU    40

#define ICON_BRIL_LABEL        7
#define ICON_BRIL_WELL         8
#define ICON_BRIL_BACK         9
#define ICON_BRIL_BAR         10
#define ICON_BRIL_DEC         11
#define ICON_BRIL_INC         12
#define ICON_BRIL_VAL         13

#define ICON_CONT_LABEL       14
#define ICON_CONT_WELL        15
#define ICON_CONT_BACK        16
#define ICON_CONT_BAR         17
#define ICON_CONT_DEC         18
#define ICON_CONT_INC         19
#define ICON_CONT_VAL         20

#define ICON_COL_LABEL        21
#define ICON_COL_WELL         22
#define ICON_COL_BACK         23
#define ICON_COL_BAR          24
#define ICON_COL_DEC          25
#define ICON_COL_INC          26
#define ICON_COL_VAL          27

// Op_Audio window icon numbers
#define ICON_PLAY_AUDIO        2
#define ICON_APL_LABEL         3
#define ICON_APL_STR           4
#define ICON_APL_MENU          5
#define ICON_SAVE_AUDIO        6
#define ICON_SYNC              7
#define ICON_AUTO_AUDIO        8

#define ICON_CHAN_LABEL        9
#define ICON_CHAN_NUM         10
#define ICON_CHAN_DEC         11
#define ICON_CHAN_INC         12

#define ICON_VOL_LABEL        13
#define ICON_VOL_WELL         14
#define ICON_VOL_BACK         15
#define ICON_VOL_BAR          16
#define ICON_VOL_DEC          17
#define ICON_VOL_INC          18
#define ICON_VOL_VAL          19
#define ICON_LANGUAGE         23

// Op_Window window icon numbers
#define ICON_MULTI_WIN         2
#define ICON_SCROLLS           3
#define ICON_CONTROLS          4
#define ICON_SKIN_LABEL        5
#define ICON_SKIN_STR          6
#define ICON_SKIN_MENU         7

// Op_MulTask window icon numbers
#define ICON_MT_FIT_SIZE       2
#define ICON_MT_ZOOM_LABEL     3
#define ICON_MT_ZOOM_STR       4
#define ICON_MT_ZOOM_MENU      5
#define ICON_MT_SCAL_LABEL     6
#define ICON_MT_SCAL_STR       7
#define ICON_MT_SCAL_MENU      8
#define ICON_MT_MONO           9
#define ICON_MT_TO_BGR        10
#define ICON_MT_DITHER        11

// Op_Desktop window icon numbers
#define ICON_DT_FIT_SCREEN     2
#define ICON_DT_ZOOM_LABEL     3
#define ICON_DT_ZOOM_STR       4
#define ICON_DT_ZOOM_MENU      5
#define ICON_DT_SCAL_LABEL     6
#define ICON_DT_SCAL_STR       7
#define ICON_DT_SCAL_MENU      8
#define ICON_DT_FORCE_SCAL     9
#define ICON_DT_MONO          10
#define ICON_DT_TO_BGR        11
#define ICON_DT_DITHER        12

// Op_Auto/Op_Manual window icon numbers
#define ICON_FIT_SCREEN        2
#define ICON_ZOOM_LABEL        3
#define ICON_ZOOM_STR          4
#define ICON_ZOOM_MENU         5
#define ICON_COL_LAB           6
#define ICON_COL_STR           7
#define ICON_COL_MENU          8
#define ICON_RES_LABEL         9
#define ICON_RES_STR          10
#define ICON_RES_MENU         11
#define ICON_SCAL_LABEL       12
#define ICON_SCAL_STR         13
#define ICON_SCAL_MENU        14
#define ICON_FORCE_SCAL       15
#define ICON_MONO             16
#define ICON_TO_BGR           17
#define ICON_DITHER           18

// Iconbar menu options
enum { OPTION_INFO
     , OPTION_DVDS
     , OPTION_SETUP
     , OPTION_SAVED
     , OPTION_FILMINFO
     , OPTION_PLAYLIST
     , OPTION_RELOAD
     , OPTION_HELP
     , OPTION_QUIT
     , OPTION_MAX
     };

// Audio player menu options
enum { OPTION_AMPLAYER
     , OPTION_DISKSAMPLE
     };

static const char appname[] = "KinoAMP Configuration";
static const char choices_file[] = "<KinoChoices$Dir>.Choices";
static const char skins_dir[] = "<KinoSkin$Dir>";
static const char filminfo_file[] = "<KinoChoices$Dir>.Info";

// for screen modes

#define MAX_RES 64

typedef struct
{
  int  width;
  int  height;
  int  col_mask;
  char menu[16];
} res_info;
static res_info res[MAX_RES];

typedef struct res_menu_s
{
  menu_block_t blk;
  menu_item_t res[MAX_RES];
} res_menu_t;
static res_menu_t * res_menu; // pointer to menu structure

// for skins

#define MAX_SKINS 32
typedef struct skin_menu_s
{
  menu_block_t blk;
  menu_item_t skin[1024];
} skin_menu_t;
static skin_menu_t * skin_menu; // pointer to menu structure

// structures used when creating menus
static const menu_block_t def_res = {"Resolution", NULL, 11, 7, 2, 7, 0, 200, 44, 0};
static const menu_block_t def_skin = {"Skins",NULL, 6, 7, 2, 7, 0, 200, 44, 0};
static const menu_item_t def_item = {0, -1, IC_DEF, "", NULL, 0};

window_data  win_data[WIN_MAX];

static struct
{
  ka_config_t  config;
  ka_config_t  prev_config;
  video_mode*  cur_mode;
  int          quit;
  int          cur_win; // section displayed
  int          cur_menu;
  int          num_res;
  int          glb_col_mask;
  char         skin[32][MAX_SKINS];
  int          num_skin;
// for playlist
  char*        save_name;
  int          save_name_size;
  int          drag;
  struct
  {
    int high;
    int low;
  } film_info_stamp;
} App = {{0}, {0}, NULL, 0, -1, -1, 0, 0, {0}, 0, NULL, 0, 0};

#define DRAG_SAVE 1

static int stricmp(const char* pa, const char* pb)
{
	int val = 0;

	if (_swix(Territory_Collate, _INR(0, 3) | _OUT(0)
			, -1, pa, pb, 1, &val))
		val = strcmp(pa, pb);

	return val;
}

/*
 * read_menus
 * ----------
 * load menu text from Messages file if possible
 */
static void read_menus(void)
{
  msg_load_menu("iconbar_m", &iconbar_menu.blk);
  msg_load_menu("zoom_m", &zoom_menu.blk);
  msg_load_menu("colours_m", &col_menu.blk);
  msg_load_menu("aplayer_m", &audioplayer_menu.blk);
  msg_load_menu("mode_m", &mode_menu.blk);
  msg_load_menu("scaler_m", &scaler_menu.blk);
  msg_load_menu("deinterlace_m", &deinterlace_menu.blk);
  msg_load_menu("resizemode_m", &resizemode_menu.blk);
  msg_load_menu("resizeto_m", &resizeto_menu.blk);
  msg_load_menu("res_m", &res_menu->blk);
  msg_load_menu("skin_m", &skin_menu->blk);

  iconbar_menu.info.sub = win_data[WIN_INFO].win_handle;

  // hidet unavailable CDFS drives
  int drives = 0;
  _swix(CDFS_GetNumberOfDrives, _OUT(0), &drives);
  if (drives > 4) drives = 4;

  if (drives)
  {
    iconbar_menu.dvds.sub = (int) &dvds_menu;
    dvds_menu.drive[drives-1].item_flags |= 0x80;
  }

  // grey out unavailable colours
  for (int i = 0; i < 5; i++)
  {
    if (!(App.glb_col_mask & (1 << i)))
      col_menu.col[i].icon_flags |= IC_SHADED;
  }
}

/*
 * open_menu
 * ---------
 */
static void open_menu(int x, int y, int menu_tag, menu_block_t* menu_struct)
{
  _kernel_swi_regs regs;

  // to recreate the menu if adjust is pressed
  static int prev_x, prev_y;
  if (x)
    prev_x = x;
  else if (prev_x)
    x = prev_x;
  else return;
  if (y)
    prev_y = y;
  else if (prev_y)
    y = prev_y;
  else return;

  regs.r[1] = (int) menu_struct;
  regs.r[2] = x;
  regs.r[3] = y;
  _kernel_swi(Wimp_CreateMenu, &regs, &regs);

  App.cur_menu = menu_tag;
}

/*
 * drag_start
 * ----------
 * Called when saving a file by dragging the icon.
 */
static void drag_start(int winhdl, int iconhdl)
{
  _kernel_swi_regs  regs;
  int blk[9], box[10];
  char* spr = "file_fff";

  box[0] = winhdl;
  box[1] = iconhdl;
  regs.r[1] = (int) box;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);

  blk[0] = winhdl;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);

  box[2] += blk[1] - blk[5];
  box[3] += blk[4] - blk[6];
  box[4] += blk[1] - blk[5];
  box[5] += blk[4] - blk[6];

  regs.r[0] = 0;
  regs.r[1] = 1;
  regs.r[2] = (int) spr;
  regs.r[3] = (int) &box[2];
  _kernel_swi(DragASprite_Start, &regs, &regs);

  App.drag = DRAG_SAVE;
}

/*
 * drag_return
 * -----------
 * called when a dragged file icon is dropped.
 */
static void drag_return(void)
{
  _kernel_swi_regs  regs;
  int blk[16];

  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);
  if (App.drag == DRAG_SAVE)
  {
    char* leafname = strrchr(App.save_name, '.');
    if(leafname)
      leafname++;
    else
      leafname = App.save_name;

    blk[5] = blk[3]; // window handle
    blk[6] = blk[4]; // icon handle
    blk[7] = blk[0]; // mouse x
    blk[8] = blk[1]; // mouse y
    blk[0] = 56;
    blk[1] = 0;
    blk[2] = 0;
    blk[3] = 0;
    blk[4] = MESSAGE_DATASAVE;
    blk[9] = 256; // size
    blk[10] = FILETYPE_TEXT;
    snprintf((char*) &blk[11], 20, "%s", leafname);

    regs.r[0] = 17;
    regs.r[1] = (int) blk;
    regs.r[2] = blk[5]; // window handle
    regs.r[3] = blk[6]; // icon handle
    _kernel_swi(Wimp_SendMessage, &regs, &regs);

    _kernel_swi(DragASprite_Stop, &regs, &regs);
    App.drag = 0;
  }
}

/*
 * read_skins
 * ----------
 * Compiles a list of skin directories.
 */
static void read_skins(const char* name)
{
  int i, found;
  _kernel_swi_regs regs;
  struct
  {
    int i[5];
    char c[32];
  } b;

  regs.r[0] = 10;
  regs.r[1] = (int) name;
  regs.r[2] = (int) &b;
  regs.r[3] = 1;
  regs.r[4] = 0;
  regs.r[5] = sizeof(b);
  regs.r[6] = 0;
  i = 0;

  while ((regs.r[4] != -1) && (i < 64))
  {
    if (_kernel_swi(OS_GBPB, &regs, &regs))
    {
      report_error(1, "error100"); // (Cannot find skins directory)
      return;
    }
    if ((b.i[4] == 2) && (regs.r[4] != -1))
      snprintf(App.skin[i++], sizeof(App.skin[0]), "%s", b.c);
  }

  if (i == 0)
  {
    report_error(1, "error101"); // (Skins directory empty)
    return;
  }
  App.num_skin = i;

  // check that config.skin exists, if not choose first in list
  i = found = 0;
  while ((i < App.num_skin) && !found)
  {
    if (strcmp(App.config.skin, App.skin[i]) == 0)
      found = 1;
    i++;
  }
  if (!found)
    strncpy(App.config.skin, App.skin[0], sizeof(App.config.skin));

  // create menu structure
  if ((skin_menu = malloc(sizeof(menu_block_t) + App.num_skin*sizeof(menu_item_t))) == NULL)
  {
    report_error(1, "error104"); // (cannot allocate buffer)
    return;
  }

  skin_menu->blk = def_skin;

  for (i = 0; i < App.num_skin; i++)
  {
    skin_menu->skin[i] = def_item;
    skin_menu->skin[i].text = App.skin[i];
    skin_menu->skin[i].len = strlen(App.skin[i]);
  }

  skin_menu->skin[0].item_flags |= 0x100;
  skin_menu->skin[App.num_skin - 1].item_flags |= 0x80;
}

/*
 * adjust_colours
 * --------------
 * Adjust number of colours according to an availability mask.
 */
static int adjust_colours(int cols, int mask)
{
  int i = 1 << cols;

  if (!(i & mask))
  {
    if (mask & (i - 1))
    {
      // Use less colours if possible
      while (!(i & mask))
      {
        cols--;
        i >>= 1;
      }
    }
    else if (mask & ~(i - 1))
    {
      // Use more colours
      while (!(i & mask))
      {
        cols++;
        i <<= 1;
      }
    }
  }

  return cols;
}

/*
 * adjust_resolution
 * -----------------
 * Adjust number of colours according to an availability mask.
 */
static void adjust_resolution(video_mode* mode)
{
  int i = 1 << mode->cols;

  if ((mode->id >= 0) && (i & res[mode->id].col_mask))
    return;

  for (int j = App.num_res - 1; j >= 0; j--)
  {
    if (i & res[j].col_mask)
    {
      mode->width = res[j].width;
      mode->height = res[j].height;
      mode->id = j;
      break;
    }
  }
}

/*
 * read_modes
 * ----------
 * Reads the available screen modes defined for this system. Creates a
 * structure containing resolutions and the associated maximum colour depth
 * for that resolution. Checks that the current configuration screen mode
 * is available and if not, chooses the nearest available one.
 */
static void read_modes(void)
{
  _kernel_swi_regs regs;

  App.glb_col_mask = 0;

  // Try to obtain a list of supported colour depths, RISCOS Select
  regs.r[0] = 12; // get display driver decriptor
  regs.r[1] = -1; // current driver
  regs.r[2] = 3;  // list of colour depths
  regs.r[3] = 0;  // request descriptor size
  regs.r[4] = 0;
  if (_kernel_swi(OS_ScreenMode, &regs, &regs) == NULL)
  {
    int* blk;
    int size = regs.r[4];

    if ((blk = malloc(size)) == 0)
    {
      report_error(1, "error104"); // (Cannot allocate mode block)
      return;
    }
    regs.r[0] = 12; // get display driver decriptor
    regs.r[1] = -1; // current driver
    regs.r[2] = 3;  // list of colour depths
    regs.r[3] = (int) blk;
    regs.r[4] = size;
    _kernel_swi(OS_ScreenMode, &regs, &regs);
    size = size / 4;

    for (int i = 0; i < size; i++)
    {
      if (blk[i] == 3)
        App.glb_col_mask |= (1 << KA_COL_256); // 256, 8bpp
      else if (blk[i] == 4)
        App.glb_col_mask |= (1 << KA_COL_32K); // 32K, 16bpp
      else if (blk[i] == 5)
        App.glb_col_mask |= (1 << KA_COL_16M); // 16M, 32bpp
      else if (blk[i] == 0x40000004)
        App.glb_col_mask |= (1 << KA_COL_64K); // 64K, 16bpp
    }
    free(blk);
  }
  else
  {
    // Try to obtain a list of supported colour depths, RISC OS 5
    regs.r[0] = 11; // set/read current driver number
    regs.r[1] = -1; // read
    if (_kernel_swi(OS_ScreenMode, &regs, &regs) == NULL)
    {
      regs.r[4] = 17 + (regs.r[1] << 24);
      regs.r[9] = 42;
      if (_kernel_swi(OS_CallAVector, &regs, &regs) == NULL)
      {
        int* blk = (int*) regs.r[0];

        for (int i = regs.r[1]; i > 0; i--, blk += 3)
        {
          if ((blk[2] == 3) && (blk[1] & 0x80))
            App.glb_col_mask |= (1 << KA_COL_256); // 256, 8bpp
          else if (blk[2] == 4)
          {
            if (blk[0] == 4095)
              App.glb_col_mask |= (1 << KA_COL_4K); // 4K, 16bpp
            else if (blk[0] == 65535)
            {
              if (blk[1] & 0x80)
                App.glb_col_mask |= (1 << KA_COL_64K); // 64K, 16bpp
              else
                App.glb_col_mask |= (1 << KA_COL_32K); // 32K, 16bpp
            }
          }
          else if (blk[2] == 5)
            App.glb_col_mask |= (1 << KA_COL_16M); // 16M, 32bpp
        }
      }
    }
  }

  // Build a list of screen modes
  {
    int i, j, w, h, c, l, f, size;
    int *blk, *ptr, *blk_end;
    video_mode* mode;

    regs.r[0] = 2; // enumerate screen modes
    regs.r[2] = 0; // skip none
    regs.r[6] = 0; // request required block size
    regs.r[7] = 0;
    _kernel_swi(OS_ScreenMode, &regs, &regs);
    size = -regs.r[7];

    if((blk = malloc(size)) == 0)
    {
      report_error(1, "error104"); // (Cannot allocate mode block)
      return;
    }

    regs.r[0] = 2;
    regs.r[2] = 0;
    regs.r[6] = (int) blk;
    regs.r[7] = size;
    _kernel_swi(OS_ScreenMode, &regs, &regs);

    // We now have a list of the available modes
    blk_end = blk + (size / 4);
    ptr = blk;
    j = -1;
    App.config.video.modes[KA_MODE_MANUAL].id = -1;
    App.config.video.modes[KA_MODE_AUTO].id = 0;

    // Build a list of modes to choose from.
    while (ptr < blk_end)
    {
      switch (ptr[1] & 0xff)
      {
        case 1:
        {
          w = ptr[2];
          h = ptr[3];
          l = ptr[4];
          c = 1 << (1 << l);
          f = 0;
        }
        break;
        case 3:
        {
          w = ptr[2];
          h = ptr[3];
          c = ptr[4] + 1;
          f = ptr[5];
          l = ptr[6];
        }
        break;
        default:
          ptr += ptr[0] /4;
          continue;
      }

      // Keep only modes with at least 256 colours
      if ((l >= 3) && (l <= 5))
      {
        // new resolution ?
        if ((j < 0) || (w != res[j].width) || (h != res[j].height))
        {
          j++;
          if (j >= MAX_RES) break;

          res[j].width = w;
          res[j].height = h;
          res[j].col_mask = 0;
          snprintf(res[j].menu, sizeof(res[0].menu), "%d x %d", w, h);

          // check that the current config is available
          mode = &App.config.video.modes[KA_MODE_MANUAL];
          if ((mode->width == w)
          &&  (mode->height == h))
            mode->id = j;

          // check that the auto min resolution is available
          mode = &App.config.video.modes[KA_MODE_AUTO];
          if ((mode->width == w)
          &&  (mode->height == h))
            mode->id = j;
        }

        // add colour depth to current resolution
        if (l == 3) // 256
          res[j].col_mask |= (1 << KA_COL_256);
        else if (l == 4) // 4k, 32k or 64k
        {
          if (c == (1<<12)) // 4K
            res[j].col_mask |= (1 << KA_COL_4K);
          else if ((c == (1<<16)) && (f & 0x80)) // 64K, OS 5
            res[j].col_mask |= (1 << KA_COL_64K);
          else // 32K OS 4,5 or 32/64K (Select)
          {
            const int mask = (1 << KA_COL_64K) | (1 << KA_COL_32K);
            if (App.glb_col_mask & mask)
              res[j].col_mask |= (App.glb_col_mask & mask);
            else
              res[j].col_mask |= (1 << KA_COL_32K);
          }
        }
        else // 16m
          res[j].col_mask |= (1 << KA_COL_16M);
      }

      ptr += ptr[0] / 4;
    }
    App.num_res = j + 1;

    // Merge depths in global depth mask (for pre-GraphicsV/Select cases)
    for (i = 0; i <= j; i++)
      App.glb_col_mask |= res[i].col_mask;

    free(blk);
  }

  int i;

  // Ensure that configured number of colours is available
  for (i = 0; i < KA_MODE_MAX; i++)
  {
    App.config.video.modes[i].cols
      = adjust_colours(App.config.video.modes[i].cols, App.glb_col_mask);
  }

  // Ensure that configured resolution is available in that number of colours,
  // otherwise use largest resolution using that number of colours.
  adjust_resolution(&App.config.video.modes[KA_MODE_MANUAL]);

  // create menu structure
  if ((res_menu = malloc(sizeof(menu_block_t) + App.num_res*sizeof(menu_item_t))) == NULL)
  {
    report_error(1, "error104"); // (cannot allocate buffer)
    return;
  }
  res_menu->blk = def_res;
  for(i = 0; i < App.num_res; i++)
  {
    res_menu->res[i] = def_item;
    res_menu->res[i].text = res[i].menu;
    res_menu->res[i].len = strlen(res[i].menu);
  }
  res_menu->res[0].item_flags |= 0x100;
  res_menu->res[App.num_res - 1].item_flags |= 0x80;
}

/*
 * slider_inc_dec
 * --------------
 * update the value by the given increment but reverse the increment
 * if adjust is pressed
 */
static void slider_inc_dec(int inc, int button, int *value, int max)
{
  if (button == MOUSE_ADJUST)
    inc = -inc;

  *value += inc;

  if (inc > 0)
  {
    if (*value > max)
      *value = max;
  }
  else
  {
    if (*value < 0)
      *value = 0;
  }
}

/*
 * slider_click
 * ------------
 * update the slider data from the mouse click position
 */
static void slider_click(int window, int slider, int mouse_x, int *value, int max)
{
  int b[10], win_x_min;
  _kernel_swi_regs regs;

  b[0] = window;
  regs.r[1] = (int) b;
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  win_x_min = b[1];
  b[1] = slider;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  *value = ((mouse_x - win_x_min - b[2]) * max) / (b[4] - b[2]);
}

/*
 * slider_value
 * ------------
 * Display new value for slider and update text value
 */
static void slider_value(int window, int slider, int value, int max)
{
  _kernel_swi_regs regs;
  int b[10], line_len;
  char s[16];

  b[0] = window;
  b[1] = slider;
  regs.r[1] = (int) b;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  line_len = b[4] - b[2];
  b[1] = slider + 1;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);

  regs.r[0] = b[0];
  regs.r[1] = b[1];
  regs.r[2] = b[2];
  regs.r[3] = b[3];
  regs.r[4] = b[2] + (line_len * value) / max;
  regs.r[5] = b[5];
  _kernel_swi(Wimp_ResizeIcon, &regs, &regs);
  regs.r[0] = b[0];
  regs.r[1] = b[2];
  regs.r[2] = b[3];
  regs.r[3] = b[2] + line_len;
  regs.r[4] = b[5];
  _kernel_swi(Wimp_ForceRedraw, &regs, &regs);

  snprintf(s, sizeof(s), "%d", value);
  icon_text_change(window, slider + 4, s);
}

/*
 * open_main_window
 * ----------------
 * Updates and opens the Choices window. If it's already open, brings it
 * to the stack top.
 */
static void open_main_window(void)
{
  _kernel_swi_regs regs;
  int blk[10];

  // open window
  blk[0] = win_data[WIN_OPTIONS].win_handle;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  _kernel_swi(Wimp_OpenWindow, &regs, &regs);
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  blk[0] = App.cur_win;
  blk[7] = -1; // bring to front
  _kernel_swi(Wimp_OpenWindow, &regs, &regs);
  blk[0] = win_data[WIN_OPTIONS].win_handle;
  blk[7] = App.cur_win;
  _kernel_swi(Wimp_OpenWindow, &regs, &regs);
}

static void close_main_window(void)
{
  if (App.cur_win != -1)
    _swi(Wimp_CloseWindow, _IN(1), &App.cur_win);

  _swi(Wimp_CloseWindow, _IN(1), &win_data[WIN_OPTIONS].win_handle);
}

static void set_section(int window)
{
  int icon = ICON_MAIN;

  if (App.cur_win == window)
    return;

  if (window == win_data[WIN_OP_MAIN].win_handle)
    icon = ICON_MAIN;
  else if (window == win_data[WIN_OP_IMAGE].win_handle)
    icon = ICON_IMAGE;
  else if (window == win_data[WIN_OP_AUDIO].win_handle)
    icon = ICON_AUDIO;
  else if (window == win_data[WIN_OP_WINDOW].win_handle)
    icon = ICON_WINDOW;
  else if (window == win_data[WIN_OP_MULTASK].win_handle)
  {
    icon = ICON_MULTITASK;
    App.cur_mode = &App.config.video.modes[KA_MODE_MULTITASK];
  }
  else if (window == win_data[WIN_OP_DESKTOP].win_handle)
  {
    icon = ICON_DESKTOP;
    App.cur_mode = &App.config.video.modes[KA_MODE_DESKTOP];
  }
  else if (window == win_data[WIN_OP_AUTO].win_handle)
  {
    icon = ICON_FULLSCREEN;
    App.cur_mode = &App.config.video.modes[KA_MODE_AUTO];
  }
  else if (window == win_data[WIN_OP_MANUAL].win_handle)
  {
    icon = ICON_MANUAL;
    App.cur_mode = &App.config.video.modes[KA_MODE_MANUAL];
  }

  icon_setselected(win_data[WIN_OPTIONS].win_handle, icon, 1);

  if (App.cur_win != -1)
  {
    _swi(Wimp_CloseWindow, _IN(1), &App.cur_win);

    App.cur_win = window;
    open_main_window();
  }

  App.cur_win = window;
}

/*
 * update_window_info
 * ------------------
 * Updates the Icon flags and text for the Choices window.
 */
static void update_window_info(int init)
{
  const ka_config_t* config = &App.config;
  ka_config_t* prev_config = &App.prev_config;
  const video_mode* mode;
  video_mode* prev_mode;
  int w, i, b;
  int diff;

  if (init)
  {
    prev_config->debug = ~config->debug;
    prev_config->control = ~config->control;
    prev_config->audio.cfg = ~config->audio.cfg;
    prev_config->audio.stream = -1;
    prev_config->audio.volume = -1;
    prev_config->audio.module = -1;
    prev_config->video.stream = -1;
    prev_config->video.colour = -1;
    prev_config->video.brightness = -1;
    prev_config->video.contrast = -1;
    prev_config->video.cfg = ~config->video.cfg;
    prev_config->video.mode = -1;
    prev_config->video.deinterlace = -1;
    prev_config->video.resizemode = -1;
    prev_config->video.resizeto = -1;
    prev_config->video.monitor_aspect.x = -1;
    prev_config->video.monitor_aspect.y = -1;

    for (i = 0; i < KA_MODE_MAX; i++)
    {
      mode = &config->video.modes[i];
      prev_mode = &prev_config->video.modes[i];
      prev_mode->id = -1;
      prev_mode->cols = -1;
      prev_mode->zoom = -1;
      prev_mode->scaler = -1;
      prev_mode->cfg = ~mode->cfg;
    }

    prev_config->skin[0] = 0;
  }

  //
  // Update General section
  //
  w = win_data[WIN_OP_MAIN].win_handle;

  diff = config->control ^ prev_config->control;
  if (diff)
  {
    if (diff & cfg_ctrl_autoexit)
      icon_setselected(w, ICON_AUTO_EXIT, (config->control & cfg_ctrl_autoexit) != 0);

    if (diff & cfg_ctrl_loop)
    {
      icon_setselected(w, ICON_LOOP, (config->control & cfg_ctrl_loop) != 0);
      icon_setshaded(w, ICON_AUTO_EXIT, (config->control & cfg_ctrl_loop) != 0);
    }

    if (diff & cfg_ctrl_random)
      icon_setselected(w, ICON_RANDOM, (config->control & cfg_ctrl_random) != 0);

    if (diff & cfg_ctrl_accel_inteldma)
      icon_setselected(w, ICON_ACCEL_INTELDMA, (config->control & cfg_ctrl_accel_inteldma) != 0);

    if (diff & cfg_ctrl_accel_geminus)
      icon_setselected(w, ICON_ACCEL_GEMINUS, (config->control & cfg_ctrl_accel_geminus) != 0);

    if (diff & cfg_ctrl_accel_videooverlay)
      icon_setselected(w, ICON_ACCEL_VIDEOOVERLAY, (config->control & cfg_ctrl_accel_videooverlay) != 0);

    if (diff & cfg_ctrl_accel_videooverlay_not_multitasking)
      icon_setselected(w, ICON_ACCEL_VIDEOOVERLAY_NOT_MULTITASKING, (config->control & cfg_ctrl_accel_videooverlay_not_multitasking) != 0);
  }

  if (config->video.mode != prev_config->video.mode)
    icon_text_change(w, ICON_MODE_STR, mode_menu.mode[config->video.mode].text);

  if ((config->debug ^ prev_config->debug) & cfg_displayall)
    icon_setselected(w, ICON_ALL_FRAMES, (config->debug & cfg_displayall) != 0);

  //
  // Update Image section
  //
  w = win_data[WIN_OP_IMAGE].win_handle;

  diff = config->control ^ prev_config->control;
  if (diff)
  {
    // colour sliders
    if (diff & cfg_ctrl_defaultcols)
    {
      icon_setselected(w, ICON_DEF_VIDEO, (config->control & cfg_ctrl_defaultcols) != 0);

      b = (config->control & cfg_ctrl_defaultcols) != 0;
      for(i = ICON_BRIL_LABEL; i <= ICON_COL_VAL; i++)
        icon_setshaded(w, i, b);
    }

    //
  }

  diff = config->video.cfg ^ prev_config->video.cfg;
  if (diff)
  {
    if (diff & cfg_video_lock_aspect)
    {
      icon_setselected(w, ICON_LOCK_ASPECT, (config->video.cfg & cfg_video_lock_aspect) != 0);

      b = (config->video.cfg & cfg_video_lock_aspect) == 0;
      icon_setshaded(w, ICON_PIXEL_ASPECT, b);
    }

    if (diff & cfg_video_pixel_aspect)
      icon_setselected(w, ICON_PIXEL_ASPECT, (config->video.cfg & cfg_video_pixel_aspect) != 0);
  }

  if (config->video.deinterlace != prev_config->video.deinterlace)
    icon_text_change(w, ICON_DEINTERLACE_STR, deinterlace_menu.method[config->video.deinterlace].text);

  if (config->video.resizemode != prev_config->video.resizemode)
    icon_text_change(w, ICON_RESIZEMODE_STR, resizemode_menu.method[config->video.resizemode].text);

  if (config->video.resizeto != prev_config->video.resizeto)
    icon_text_change(w, ICON_RESIZETO_STR, resizeto_menu.method[config->video.resizeto].text);

  if (config->video.monitor_aspect.x != prev_config->video.monitor_aspect.x)
    icon_text_format(w, ICON_MONITOR_X, "%d", config->video.monitor_aspect.x);

  if (config->video.monitor_aspect.y != prev_config->video.monitor_aspect.y)
    icon_text_format(w, ICON_MONITOR_Y, "%d", config->video.monitor_aspect.y);

  if (config->video.brightness != prev_config->video.brightness)
    slider_value(w, ICON_BRIL_BACK, config->video.brightness, 200);

  if (config->video.contrast != prev_config->video.contrast)
    slider_value(w, ICON_CONT_BACK, config->video.contrast, 200);

  if (config->video.colour != prev_config->video.colour)
    slider_value(w, ICON_COL_BACK, config->video.colour, 200);

  //
  // Update Audio section
  //
  w = win_data[WIN_OP_AUDIO].win_handle;

  diff = config->audio.cfg ^ prev_config->audio.cfg;
  if (diff)
  {
    if (diff & cfg_audio_play)
    {
      icon_setselected(w, ICON_PLAY_AUDIO, (config->audio.cfg & cfg_audio_play) != 0);

      b = (config->audio.cfg & cfg_audio_play) == 0;

      // grey out section when not playing audio
      // treat channel selection later
      for(i = ICON_APL_LABEL; i <= ICON_AUTO_AUDIO; i++)
        icon_setshaded(w, i, b);
      for(i = ICON_VOL_LABEL; i <= ICON_VOL_VAL; i++)
        icon_setshaded(w, i, b);
    }

    if (diff & cfg_audio_save)
      icon_setselected(w, ICON_SAVE_AUDIO, (config->audio.cfg & cfg_audio_save) != 0);

    if (diff & cfg_audio_sync)
      icon_setselected(w, ICON_SYNC, (config->audio.cfg & cfg_audio_sync) != 0);

    if (diff & cfg_audio_autoselect)
      icon_setselected(w, ICON_AUTO_AUDIO, (config->audio.cfg & cfg_audio_autoselect) != 0);

    if (diff & (cfg_audio_play | cfg_audio_autoselect))
    {
      // grey out channel selection when auto audio or not playing audio
      b = (config->audio.cfg & cfg_audio_play) == 0;
      b |= (config->audio.cfg & cfg_audio_autoselect) != 0;
      for(i = ICON_CHAN_LABEL; i <= ICON_CHAN_INC; i++)
        icon_setshaded(w, i, b);
    }
  }

  if (config->audio.module != prev_config->audio.module)
    icon_text_change(w, ICON_APL_STR, audioplayer_menu.module[config->audio.module].text);

  if (config->audio.stream != prev_config->audio.stream)
  {
    char s[2];
    s[0] = config->audio.stream + '0';
    s[1] = 0;
    icon_text_change(w, ICON_CHAN_NUM, s);
  }

  // volume slider
  if (config->audio.volume != prev_config->audio.volume)
    slider_value(w, ICON_VOL_BACK, config->audio.volume, 127);

  // language
  if (strcmp(config->language, prev_config->language))
    icon_text_change(w, ICON_LANGUAGE, config->language);

  //
  // Update Window section
  //
  w = win_data[WIN_OP_WINDOW].win_handle;

  diff = config->control ^ prev_config->control;
  if (diff)
  {
    if (diff & cfg_ctrl_multiple_wins)
      icon_setselected(w, ICON_MULTI_WIN, (config->control & cfg_ctrl_multiple_wins) != 0);

    if (diff & cfg_ctrl_scroll_bars)
      icon_setselected(w, ICON_SCROLLS, (config->control &  cfg_ctrl_scroll_bars) != 0);

    if (diff & cfg_ctrl_controls)
      icon_setselected(w, ICON_CONTROLS, (config->control & cfg_ctrl_controls) != 0);
  }

  if (strcmp(config->skin, prev_config->skin))
    icon_text_change(w, ICON_SKIN_STR, config->skin);

  //
  // Update Multitasking mode section
  //
  w = win_data[WIN_OP_MULTASK].win_handle;
  mode = &config->video.modes[KA_MODE_MULTITASK];
  prev_mode = &prev_config->video.modes[KA_MODE_MULTITASK];

  diff = mode->cfg ^ prev_mode->cfg;
  if (diff)
  {
    if (diff & cfg_video_fit_screen)
      icon_setselected(w, ICON_MT_FIT_SIZE, (mode->cfg & cfg_video_fit_screen) != 0);

//    if (diff & cfg_video_force_scaler)
//      icon_setselected(w, ICON_MT_FORCE_SCAL, (mode->cfg & cfg_video_force_scaler) != 0);

    if (diff & cfg_video_mono)
      icon_setselected(w, ICON_MT_MONO, (mode->cfg & cfg_video_mono) != 0);

    if (diff & cfg_video_to_BGR)
      icon_setselected(w, ICON_MT_TO_BGR, (mode->cfg & cfg_video_to_BGR) != 0);

    if (diff & cfg_video_dither)
      icon_setselected(w, ICON_MT_DITHER, (mode->cfg & cfg_video_dither) != 0);
  }

  if (mode->zoom != prev_mode->zoom)
    icon_text_change(w, ICON_MT_ZOOM_STR, zoom_menu.mag[mode->zoom/100].text);

  if (mode->scaler != prev_mode->scaler)
    icon_text_change(w, ICON_MT_SCAL_STR, scaler_menu.scaler[mode->scaler].text);

  //
  // Update Desktop mode section
  //
  w = win_data[WIN_OP_DESKTOP].win_handle;
  mode = &config->video.modes[KA_MODE_DESKTOP];
  prev_mode = &prev_config->video.modes[KA_MODE_DESKTOP];

  diff = mode->cfg ^ prev_mode->cfg;
  if (diff)
  {
    if (diff & cfg_video_fit_screen)
    {
      icon_setselected(w, ICON_DT_FIT_SCREEN, (mode->cfg & cfg_video_fit_screen) != 0);
      b = (mode->cfg & cfg_video_fit_screen) != 0;
      for(i = ICON_DT_ZOOM_LABEL; i <= ICON_DT_ZOOM_MENU; i++)
        icon_setshaded(w, i, b);
    }

    if (diff & cfg_video_force_scaler)
      icon_setselected(w, ICON_DT_FORCE_SCAL, (mode->cfg & cfg_video_force_scaler) != 0);

    if (diff & cfg_video_mono)
      icon_setselected(w, ICON_DT_MONO, (mode->cfg & cfg_video_mono) != 0);

    if (diff & cfg_video_to_BGR)
      icon_setselected(w, ICON_DT_TO_BGR, (mode->cfg & cfg_video_to_BGR) != 0);

    if (diff & cfg_video_dither)
      icon_setselected(w, ICON_DT_DITHER, (mode->cfg & cfg_video_dither) != 0);
  }

  if (mode->zoom != prev_mode->zoom)
    icon_text_change(w, ICON_DT_ZOOM_STR, zoom_menu.mag[mode->zoom/100].text);

  if (mode->scaler != prev_mode->scaler)
    icon_text_change(w, ICON_DT_SCAL_STR, scaler_menu.scaler[mode->scaler].text);

  //
  // Update Automatic mode section
  //
  w = win_data[WIN_OP_AUTO].win_handle;
  mode = &config->video.modes[KA_MODE_AUTO];
  prev_mode = &prev_config->video.modes[KA_MODE_AUTO];

  diff = mode->cfg ^ prev_mode->cfg;
  if (diff)
  {
    if (diff & cfg_video_fit_screen)
    {
      b = (mode->cfg & cfg_video_fit_screen) != 0;
      for(i = ICON_ZOOM_LABEL; i <= ICON_ZOOM_MENU; i++)
        icon_setshaded(w, i, b);

      icon_setselected(w, ICON_FIT_SCREEN, b);
    }

    if (diff & cfg_video_force_scaler)
      icon_setselected(w, ICON_FORCE_SCAL, (mode->cfg & cfg_video_force_scaler) != 0);

    if (diff & cfg_video_mono)
      icon_setselected(w, ICON_MONO, (mode->cfg & cfg_video_mono) != 0);

    if (diff & cfg_video_to_BGR)
      icon_setselected(w, ICON_TO_BGR, (mode->cfg & cfg_video_to_BGR) != 0);

    if (diff & cfg_video_dither)
      icon_setselected(w, ICON_DITHER, (mode->cfg & cfg_video_dither) != 0);
  }

  if (mode->zoom != prev_mode->zoom)
    icon_text_change(w, ICON_ZOOM_STR, zoom_menu.mag[mode->zoom/100].text);

  if (mode->cols != prev_mode->cols)
   icon_text_change(w, ICON_COL_STR, col_menu.col[mode->cols].text);

  if (mode->id != prev_mode->id)
    icon_text_change(w, ICON_RES_STR, res[mode->id].menu);

  if (mode->scaler != prev_mode->scaler)
    icon_text_change(w, ICON_SCAL_STR, scaler_menu.scaler[mode->scaler].text);

  //
  // Update Manual mode section
  //
  w = win_data[WIN_OP_MANUAL].win_handle;
  mode = &config->video.modes[KA_MODE_MANUAL];
  prev_mode = &prev_config->video.modes[KA_MODE_MANUAL];

  diff = mode->cfg ^ prev_mode->cfg;
  if (diff)
  {
    if (diff & cfg_video_fit_screen)
    {
      icon_setselected(w, ICON_FIT_SCREEN, (mode->cfg & cfg_video_fit_screen) != 0);
      b = (mode->cfg & cfg_video_fit_screen) != 0;
      for(i = ICON_ZOOM_LABEL; i <= ICON_ZOOM_MENU; i++)
        icon_setshaded(w, i, b);
    }

    if (diff & cfg_video_force_scaler)
      icon_setselected(w, ICON_FORCE_SCAL, (mode->cfg & cfg_video_force_scaler) != 0);

    if (diff & cfg_video_mono)
      icon_setselected(w, ICON_MONO, (mode->cfg & cfg_video_mono) != 0);

    if (diff & cfg_video_to_BGR)
      icon_setselected(w, ICON_TO_BGR, (mode->cfg & cfg_video_to_BGR) != 0);

    if (diff & cfg_video_dither)
      icon_setselected(w, ICON_DITHER, (mode->cfg & cfg_video_dither) != 0);
  }

  if (mode->zoom != prev_mode->zoom)
    icon_text_change(w, ICON_ZOOM_STR, zoom_menu.mag[mode->zoom/100].text);

  if (mode->cols != prev_mode->cols)
   icon_text_change(w, ICON_COL_STR, col_menu.col[mode->cols].text);

  if (mode->id != prev_mode->id)
    icon_text_change(w, ICON_RES_STR, res[mode->id].menu);

  if (mode->scaler != prev_mode->scaler)
    icon_text_change(w, ICON_SCAL_STR, scaler_menu.scaler[mode->scaler].text);

  // save current config state
  *prev_config = *config;
}

static int update_config(ka_config_t* config)
{
  int w = win_data[WIN_OP_IMAGE].win_handle;
  config->video.monitor_aspect.x = atoi(icon_gettext(w, ICON_MONITOR_X));
  config->video.monitor_aspect.y = atoi(icon_gettext(w, ICON_MONITOR_Y));

  if ((config->video.monitor_aspect.x / config->video.monitor_aspect.y > 4)
  ||  (config->video.monitor_aspect.y / config->video.monitor_aspect.x > 4))
  {
    report_error(0, "error111:%d %d", 4, 4);
    return 0;
  }

  w = win_data[WIN_OP_AUDIO].win_handle;
  strncpy(config->language, icon_gettext(w, ICON_LANGUAGE), sizeof(config->language) - 1);
  config->language[sizeof(config->language) - 1] = 0;

  return 1;
}

typedef struct info_header_s
{
  char ident[4];       // "Inf2"
  int  version;        // 10
} info_header_t;

/*
 * display_film_info
 * -----------------
 * Displays information about the last film played. Reads the Info data file,
 * updates the FilmInfo window Icon text strings and opens the window.
 */
static void display_film_info(const char* info_file, int show)
{
  _kernel_swi_regs regs;
  static info_header_t info;
  FILE * f;
  int window = win_data[WIN_FILMINFO].win_handle;
  int i, len, blk[10];
  static char s[10][256];

  for (i = 0; i < 10; i++)
    *s[i] = 0; // clear strings

  if ((f = fopen(info_file, "rb")) == 0)
  {
    if (!show)
      return;
    report_error(0, "error105:%s", info_file); // (Cannot open file)
  }
  else
  {
    if (fread(&info, 8, 1, f) != 1)
      report_error(0, "error106"); // (Cannot read data)
    else if (strncmp("Inf2", info.ident, 4) != 0)
      report_error(0, "error109"); // (Data not understood)
    else if (info.version != 10)
      report_error(0, "error109"); // (Data not understood)
    else
    {
      for (i = 0; i < 10; i++)
      {
        if (fread(&len, sizeof(len), 1, f) != 1)
        {
          report_error(0, "error106"); // (Cannot read data)
          break;
        }
        else if (len >= sizeof(s[0]))
        {
          report_error(0, "error107"); // (String too long)
          break;
        }
        else if (fread(s[i], sizeof(char), len, f) != len)
        {
          report_error(0, "error106"); // (Cannot read data)
          break;
        }
      }
    }
    fclose(f);
  }

  // update text (icon numbers are 6..15)
  for(i = 0; i < 10; i++)
    icon_text_change(window, i+6, s[i]);

  if (show)
  {
    // open window
    blk[0] = window;
    regs.r[1] = (int) blk;
    _kernel_swi(Wimp_GetWindowState, &regs, &regs);
    blk[7] = -1; // bring to front
    _kernel_swi(Wimp_OpenWindow, &regs, &regs);
  }
}



/*
 * wimp_msg
 * --------
 * Handles all messages
 */
static void wimp_msg(int blk[], int msg)
{
  _kernel_swi_regs regs;
  static char s[MAXPATH];

  switch (blk[4]) // message action
  {
    case MESSAGE_QUIT:
    {
      App.quit = 1;
    }
    break;
    case MESSAGE_DATALOAD: // file dropped on window or Iconbar icon
    {
      const char* filename = (char*) &blk[11];
      switch(blk[10]) // filetype
      {
        case 0x1000: // directory
        {
          if(blk[5] == win_data[WIN_PLAYLIST].win_handle)
          {
            if((blk[6] >= ICON_DROP_BOX) && (blk[6] <= ICON_FILE_DIR))
              playlist_dir_add(filename);  // add all mpeg files to the playlist
          }
          else
          {
            const char* folder = strrchr(filename, FILE_FILESYSTEM_CHAR);
            if (!folder)
              folder = filename;
            else
              folder++;

            if (strrchr(folder, FILE_FOLDER_CHAR))
              folder = strrchr(folder, FILE_FOLDER_CHAR) + 1;

            if (!stricmp("VIDEO_TS", folder))
            {
              snprintf(s, sizeof(s), "StartDesktopTask Run Kino:RunKino %s", filename);
              _kernel_oscli(s);
            }
            else  // treat a directory as if it were a playlist, send to player
              playlist_send(blk[10], filename);
          }
        }
        break;
        case FILETYPE_TEXT: // if a playlist, send to the player
        {
          if(blk[5] != win_data[WIN_PLAYLIST].win_handle)
            playlist_send(blk[10], filename);
        }
        break;
        default:
        {
          if (playlist_accept(blk[10]))
          {
            if(blk[5] == win_data[WIN_PLAYLIST].win_handle)
            {
              if((blk[6] >= ICON_DROP_BOX) && (blk[6] <= ICON_FILE_DIR))
                playlist_add(filename); // add file to playlist
            }
            else // send file to player
            {
              snprintf(s, sizeof(s), "Filer_Run %s", filename);
              _kernel_oscli(s);
            }
          }
        }
        break;
      }

      if(msg == 18)
      {
        regs.r[0] = 19;                // user message acknowledge
        regs.r[1] = (int)blk;
        regs.r[2] = blk[1];            // task handle of sender
        blk[3] = blk[2];               // my ref
        blk[4] = MESSAGE_DATALOADACK;  // action
        _kernel_swi(Wimp_SendMessage, &regs, &regs);
      }
    }
    break;
    case MESSAGE_DATASAVEACK: // end of drag, save playlist
    {
      const char* filename = (const char*) &blk[11];
      playlist_save(filename);
      // update path in icon
      icon_text_change(win_data[WIN_PLAYLIST].win_handle, ICON_FILENAME, filename);
    }
    break;
    case 12345: // from the player when choices have been saved
    {
      if(msg == 17)
      {
        config_read(&App.config, choices_file);
        read_modes();
        read_skins(skins_dir);
        update_window_info(0);
      }
    }
    break;
    case MESSAGE_HELPREQUEST:
    {
      msg_help(blk, App.cur_menu);
    }
    break;
  }
}

/*
 * allowed_char
 * ------------
 * Returns 1 if the supplied character is a valid filename character
 */
static int allowed_char(char c)
{
  if(c < 0x20)
    return 0;
  switch(c)
  {
    case '$': case '&': case '%': case '@': case 0x5c:
    case '^': case ':': case '.': case ',': case '#':
    case '*': case '"': case '|':
      return 0;
  }
  return 1;
}

/*
 * loadtemplates
 * -------------
 */
static void loadtemplates(const char* name)
{
  static const char const* templates[WIN_MAX] =
  { "FilmInfo"
  , "Info"
  , "Options"
  , "Op_Image"
  , "Op_Audio"
  , "Op_Auto"
  , "Op_Desktop"
  , "Op_Main"
  , "Op_Manual"
  , "Op_MulTask"
  , "Op_Window"
  , "Playlist"
  };

  _kernel_swi_regs regs;
  int i, blk[10];

  regs.r[0] = READ_CATINFO;
  regs.r[1] = (int) name;
  _kernel_swi(OS_FILE, &regs, &regs);

  if (regs.r[0] != IS_FILE)
  {
    report_error(1, "error105:%s", name); // (Cannot open template file)
    App.quit = 1;
    return;
  }

  regs.r[1] = (int) name;
  _kernel_swi(Wimp_OpenTemplate, &regs, &regs);

  for (i = 0; i < WIN_MAX; i++)
  {
    if (loadtemplate(templates[i], &win_data[i], 0) == 0)
    {
      report_error(1, "error110:%s", templates[i]); // (%s template not found)
      App.quit = 1;
      return;
    }
  }

  _kernel_swi(Wimp_CloseTemplate, &regs, &regs);
  App.cur_win = -1;

  // get pointer to playlist filename string
  blk[0] = win_data[WIN_PLAYLIST].win_handle;
  blk[1] = ICON_FILENAME;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  App.save_name = (char*) blk[7]; // pointer will be used elsewhere
  App.save_name_size = blk[9];

  // make sure save_name is terminated properly
  i = 0;
  while((i < App.save_name_size) && allowed_char(App.save_name[i]))
    i++;
  App.save_name[i] = 0;
}

static int keyboard_pollCtrl(void)
{
  int pressed;

  _swix(OS_Byte, _INR(0,1)|_OUT(1), 121, 0x81, &pressed);

  return (pressed != 0);
}

/*
 * mouse_click
 * -----------
 */
static void mouse_click(const mouse_t* m)
{
  // Menu anywhere
  if (m->button == MOUSE_MENU)
  {
    if (m->window == ICONBAR)
      open_menu(m->x - 64, (44*OPTION_MAX)+96, MENU_ICONBAR, &iconbar_menu.blk);
    else  // window
      open_menu(m->x, m->y, MENU_ICONBAR, &iconbar_menu.blk);
    return;
  }

  // Iconbar
  if (m->window == ICONBAR)
  {
    switch (m->button)
    {
      case MOUSE_SELECT:
        if (keyboard_pollCtrl())
          _swix(Wimp_StartTask, _IN(0), "Kino:RunKino ::CDFS0\n");
        else
          open_main_window();
      break;

      case MOUSE_ADJUST:
        _kernel_oscli("Filer_OpenDir <KinoSave$Dir>");
      break;
    }
    return;
  }

  // Playlist window
  if (m->window == win_data[WIN_PLAYLIST].win_handle)
  {
    switch (m->icon)
    {
      case ICON_FILE_TEXT:
        if (m->button == 4*16) // start a drag
          drag_start(m->window, m->icon);
      break;

      case ICON_SAVE_PL: // save playlist
      {
        char * filename;
        static char s[MAXPATH];

        // if filename contains a path, use it, else save to default dir
        if (strrchr(App.save_name, '.'))
          filename = App.save_name;
        else
        {
          snprintf(s, sizeof(s), "<KinoSave$Dir>.%s", App.save_name);
          filename = s;
        }
        playlist_save(filename);
      }
      break;

      case ICON_INCL_SUB:
        recursive ^= 1;
      break;

      case ICON_CANCEL_PL:
        playlist_open();
      break;
    }
    return;
  }

  // Main window
  if (m->window == win_data[WIN_OPTIONS].win_handle)
  {
    switch (m->icon)
    {
      case ICON_OK:
      {
		if (update_config(&App.config))
		{
	        config_save(&App.config, choices_file);
    	    if (m->button == MOUSE_SELECT)
        	  close_main_window();
        }
      }
      break;

      case ICON_CANCEL:
      {
		config_read(&App.config, choices_file);
        read_modes();
        read_skins(skins_dir);
        update_window_info(0);

        if (m->button == MOUSE_SELECT)
          close_main_window();
      }
      break;

      case ICON_HELP:
      {
        _kernel_oscli("Filer_Run <Kino$Dir>.!Help");
      }
      break;

      case ICON_MAIN:
      {
        set_section(win_data[WIN_OP_MAIN].win_handle);
      }
      break;

      case ICON_IMAGE:
      {
        set_section(win_data[WIN_OP_IMAGE].win_handle);
      }
      break;

      case ICON_AUDIO:
      {
        set_section(win_data[WIN_OP_AUDIO].win_handle);
      }
      break;

      case ICON_WINDOW:
      {
        set_section(win_data[WIN_OP_WINDOW].win_handle);
      }
      break;

      case ICON_MULTITASK:
      {
        set_section(win_data[WIN_OP_MULTASK].win_handle);
      }
      break;

      case ICON_DESKTOP:
      {
        set_section(win_data[WIN_OP_DESKTOP].win_handle);
      }
      break;

      case ICON_FULLSCREEN:
      {
        set_section(win_data[WIN_OP_AUTO].win_handle);
      }
      break;

      case ICON_MANUAL:
      {
        set_section(win_data[WIN_OP_MANUAL].win_handle);
      }
      break;
    }
  }

  // Op_Main window
  if (m->window == win_data[WIN_OP_MAIN].win_handle)
  {
    switch (m->icon)
    {
      case ICON_MODE_MENU:
        open_menu(m->x, m->y, MENU_MODE, &mode_menu.blk);
      break;

      case ICON_AUTO_EXIT:
        App.config.control ^= cfg_ctrl_autoexit;
      break;

      case ICON_LOOP:
        App.config.control ^= cfg_ctrl_loop;
      break;

      case ICON_ALL_FRAMES:
        App.config.debug ^= cfg_displayall;
      break;

      //
      // Playlist options
      //

      case ICON_RANDOM:
        App.config.control ^= cfg_ctrl_random;
      break;

      //
      // Hardware options
      //

      case ICON_ACCEL_INTELDMA:
        App.config.control ^= cfg_ctrl_accel_inteldma;
      break;

      case ICON_ACCEL_GEMINUS:
        App.config.control ^= cfg_ctrl_accel_geminus;
      break;

      case ICON_ACCEL_VIDEOOVERLAY:
        App.config.control ^= cfg_ctrl_accel_videooverlay;
      break;

      case ICON_ACCEL_VIDEOOVERLAY_NOT_MULTITASKING:
        App.config.control ^= cfg_ctrl_accel_videooverlay_not_multitasking;
      break;
    }
  }

  // Op_Image window
  if (m->window == win_data[WIN_OP_IMAGE].win_handle)
  {
    switch (m->icon)
    {
      case ICON_LOCK_ASPECT:
        App.config.video.cfg ^= cfg_video_lock_aspect;
      break;

      case ICON_PIXEL_ASPECT:
        App.config.video.cfg ^= cfg_video_pixel_aspect;
      break;

      case ICON_DEINTERLACE_MENU:
        open_menu(m->x, m->y, MENU_DEINTERLACE, &deinterlace_menu.blk);
      break;

      case ICON_RESIZEMODE_MENU:
        open_menu(m->x, m->y, MENU_RESIZEMODE, &resizemode_menu.blk);
      break;

      case ICON_RESIZETO_MENU:
        open_menu(m->x, m->y, MENU_RESIZETO, &resizeto_menu.blk);
      break;

      //
      // Colours options
      //

      case ICON_DEF_VIDEO:
        App.config.control ^= cfg_ctrl_defaultcols;
      break;

      case ICON_BRIL_BACK:
      case ICON_BRIL_BAR:
        slider_click(m->window, ICON_BRIL_BACK, m->x, &App.config.video.brightness, 200);
      break;

      case ICON_BRIL_DEC:
        slider_inc_dec(-1, m->button, &App.config.video.brightness, 200);
      break;

      case ICON_BRIL_INC:
        slider_inc_dec(1, m->button, &App.config.video.brightness, 200);
      break;

      case ICON_CONT_BACK:
      case ICON_CONT_BAR:
        slider_click(m->window, ICON_CONT_BACK, m->x, &App.config.video.contrast, 200);
      break;

      case ICON_CONT_DEC:
        slider_inc_dec(-1, m->button, &App.config.video.contrast, 200);
      break;

      case ICON_CONT_INC:
        slider_inc_dec(1, m->button, &App.config.video.contrast, 200);
      break;

      case ICON_COL_BACK:
      case ICON_COL_BAR:
        slider_click(m->window, ICON_COL_BACK, m->x, &App.config.video.colour, 200);
      break;

      case ICON_COL_DEC:
        slider_inc_dec(-1, m->button, &App.config.video.colour, 200);
      break;

      case ICON_COL_INC:
        slider_inc_dec(1, m->button, &App.config.video.colour, 200);
      break;
    }
  }

  // Op_Audio window
  if (m->window == win_data[WIN_OP_AUDIO].win_handle)
  {
    switch (m->icon)
    {
      case ICON_PLAY_AUDIO:
        App.config.audio.cfg ^= cfg_audio_play;
      break;

      case ICON_APL_MENU:
        open_menu(m->x, m->y, MENU_AUDIOPLAYER, &audioplayer_menu.blk);
      break;

      case ICON_SAVE_AUDIO:
        App.config.audio.cfg ^= cfg_audio_save;
      break;

      case ICON_SYNC:
        App.config.audio.cfg ^= cfg_audio_sync;
      break;

      case ICON_AUTO_AUDIO:
        App.config.audio.cfg ^= cfg_audio_autoselect;
      break;

      case ICON_CHAN_DEC:
        if(--App.config.audio.stream < 1)
          App.config.audio.stream = 1;
      break;

      case ICON_CHAN_INC:
        if(++App.config.audio.stream > 9)
          App.config.audio.stream = 9;
      break;

      case ICON_VOL_BACK:
      case ICON_VOL_BAR:
        slider_click(m->window, ICON_VOL_BACK, m->x, &App.config.audio.volume, 127);
      break;

      case ICON_VOL_DEC:
        slider_inc_dec(-1, m->button, &App.config.audio.volume, 127);
      break;

      case ICON_VOL_INC:
        slider_inc_dec(1, m->button, &App.config.audio.volume, 127);
      break;
    }
  }

  // Op_Window window
  if (m->window == win_data[WIN_OP_WINDOW].win_handle)
  {
    switch (m->icon)
    {
      case ICON_MULTI_WIN:
        App.config.control ^= cfg_ctrl_multiple_wins;
      break;

      case ICON_SCROLLS:
        App.config.control ^= cfg_ctrl_scroll_bars;
      break;

      case ICON_CONTROLS:
        App.config.control ^= cfg_ctrl_controls;
      break;

      case ICON_SKIN_MENU:
        open_menu(m->x, m->y, MENU_SKIN, &skin_menu->blk);
      break;
    }
  }

  // Op_MulTask window
  if (m->window == win_data[WIN_OP_MULTASK].win_handle)
  {
    video_mode* mode = &App.config.video.modes[KA_MODE_MULTITASK];

    switch (m->icon)
    {
      case ICON_MT_FIT_SIZE:
        mode->cfg ^= cfg_video_fit_screen;
      break;

      case ICON_MT_ZOOM_MENU:
        open_menu(m->x, m->y, MENU_ZOOM, &zoom_menu.blk);
      break;

      case ICON_MT_SCAL_MENU:
        open_menu(m->x, m->y, MENU_SCALER, &scaler_menu.blk);
      break;
/*
      case ICON_MT_FORCE_SCAL:
        mode->cfg ^= cfg_video_force_scaler;
      break;
*/
      case ICON_MT_MONO:
        mode->cfg ^= cfg_video_mono;
      break;

      case ICON_MT_TO_BGR:
        mode->cfg ^= cfg_video_to_BGR;
      break;

      case ICON_MT_DITHER:
        mode->cfg ^= cfg_video_dither;
      break;
    }
  }

  // Op_Desktop window
  if (m->window == win_data[WIN_OP_DESKTOP].win_handle)
  {
    video_mode* mode = &App.config.video.modes[KA_MODE_DESKTOP];

    switch (m->icon)
    {
      case ICON_DT_FIT_SCREEN:
        mode->cfg ^= cfg_video_fit_screen;
        if (mode->cfg & cfg_video_fit_screen)
          mode->cfg |= cfg_video_force_scaler;
      break;

      case ICON_DT_ZOOM_MENU:
        open_menu(m->x, m->y, MENU_ZOOM, &zoom_menu.blk);
      break;

      case ICON_DT_SCAL_MENU:
        open_menu(m->x, m->y, MENU_SCALER, &scaler_menu.blk);
      break;

      case ICON_DT_FORCE_SCAL:
        mode->cfg ^= cfg_video_force_scaler;
      break;

      case ICON_DT_MONO:
        mode->cfg ^= cfg_video_mono;
      break;

      case ICON_DT_TO_BGR:
        mode->cfg ^= cfg_video_to_BGR;
      break;

      case ICON_DT_DITHER:
        mode->cfg ^= cfg_video_dither;
      break;
    }
  }

  // Op_Auto window
  if (m->window == win_data[WIN_OP_AUTO].win_handle)
  {
    video_mode* mode = &App.config.video.modes[KA_MODE_AUTO];

    switch (m->icon)
    {
      case ICON_FIT_SCREEN:
        mode->cfg ^= cfg_video_fit_screen;
        if (mode->cfg & cfg_video_fit_screen)
          mode->cfg |= cfg_video_force_scaler;
      break;

      case ICON_ZOOM_MENU:
        open_menu(m->x, m->y, MENU_ZOOM, &zoom_menu.blk);
      break;

      case ICON_COL_MENU:
        open_menu(m->x, m->y, MENU_COLOURS, &col_menu.blk);
      break;

      case ICON_RES_MENU:
        open_menu(m->x, m->y, MENU_RESOLUTION, &res_menu->blk);
      break;

      case ICON_SCAL_MENU:
        open_menu(m->x, m->y, MENU_SCALER, &scaler_menu.blk);
      break;

      case ICON_FORCE_SCAL:
        mode->cfg ^= cfg_video_force_scaler;
      break;

      case ICON_MONO:
        mode->cfg ^= cfg_video_mono;
      break;

      case ICON_TO_BGR:
        mode->cfg ^= cfg_video_to_BGR;
      break;

      case ICON_DITHER:
        mode->cfg ^= cfg_video_dither;
      break;
    }
  }

  // Op_Manual window
  if (m->window == win_data[WIN_OP_MANUAL].win_handle)
  {
    video_mode* mode = &App.config.video.modes[KA_MODE_MANUAL];

    switch (m->icon)
    {
      case ICON_FIT_SCREEN:
        mode->cfg ^= cfg_video_fit_screen;
        if (mode->cfg & cfg_video_fit_screen)
          mode->cfg |= cfg_video_force_scaler;
      break;

      case ICON_ZOOM_MENU:
        open_menu(m->x, m->y, MENU_ZOOM, &zoom_menu.blk);
      break;

      case ICON_COL_MENU:
        open_menu(m->x, m->y, MENU_COLOURS, &col_menu.blk);
      break;

      case ICON_RES_MENU:
        open_menu(m->x, m->y, MENU_RESOLUTION, &res_menu->blk);
      break;

      case ICON_SCAL_MENU:
        open_menu(m->x, m->y, MENU_SCALER, &scaler_menu.blk);
      break;

      case ICON_FORCE_SCAL:
        mode->cfg ^= cfg_video_force_scaler;
      break;

      case ICON_MONO:
        mode->cfg ^= cfg_video_mono;
      break;

      case ICON_TO_BGR:
        mode->cfg ^= cfg_video_to_BGR;
      break;

      case ICON_DITHER:
        mode->cfg ^= cfg_video_dither;
      break;
    }
  }

  update_window_info(0);
}

/*
 * wimp_scroll
 * -----------
 */
static void wimp_scroll(int blk[])
{
  int val = (blk[8] & 3) ? 0: (blk[8] >> 2);
  val += (blk[9] & 3) ? 0: (blk[9] >> 2);
  if (!val) return;

  // Op_Image window
  if (blk[0] == win_data[WIN_OP_IMAGE].win_handle)
  {
    switch (blk[10])
    {
      case ICON_BRIL_BACK-1:
      case ICON_BRIL_BACK:
      case ICON_BRIL_BAR:
        slider_inc_dec(val, MOUSE_SELECT, &App.config.video.brightness, 200);
      break;

      case ICON_CONT_BACK-1:
      case ICON_CONT_BACK:
      case ICON_CONT_BAR:
        slider_inc_dec(val, MOUSE_SELECT, &App.config.video.contrast, 200);
      break;

      case ICON_COL_BACK-1:
      case ICON_COL_BACK:
      case ICON_COL_BAR:
        slider_inc_dec(val, MOUSE_SELECT, &App.config.video.colour, 200);
      break;
    }
  }

  // Op_Audio window
  if (blk[0] == win_data[WIN_OP_AUDIO].win_handle)
  {
    switch (blk[10])
    {
      case ICON_VOL_BACK-1:
      case ICON_VOL_BACK:
      case ICON_VOL_BAR:
        slider_inc_dec(val, MOUSE_SELECT, &App.config.audio.volume, 127);
      break;
    }
  }

  update_window_info(0);
}

/*
 * menu_selection
 * --------------
 */
static void menu_selection(int blk[])
{
  int zoom = 100;
  mouse_t m;
  _kernel_swi_regs regs;

  regs.r[1] = (int) &m;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);

  switch(App.cur_menu)
  {
    case MENU_ICONBAR:
    {
      switch(blk[0])
      {
        case OPTION_DVDS:
        {
          if (blk[1] >= 0)
          {
            char run[] = "Kino:RunKino ::CDFS0\n";
            run[strlen(run) - 2] = '0' + blk[1];
            _swix(Wimp_StartTask, _IN(0), run);
          }
        }
        break;

        case OPTION_SETUP:
          open_main_window();
        break;

        case OPTION_SAVED:
          _kernel_oscli("Filer_OpenDir <KinoSave$Dir>");
        break;

        case OPTION_FILMINFO:
          display_film_info(filminfo_file, 1);
        break;

        case OPTION_PLAYLIST:
          playlist_open();
        break;

        case OPTION_RELOAD: // read choices file again
          config_read(&App.config, choices_file);
          read_modes();
          read_skins(skins_dir);
          update_window_info(0);
        break;

        case OPTION_HELP:
          _kernel_oscli("Filer_Run <Kino$Dir>.!Help");
        break;

        case OPTION_QUIT:
          App.quit = 1;
        break;
      }
      if (m.button == 1) // adjust
        open_menu(0, 0, MENU_ICONBAR, &iconbar_menu.blk);
    }
    break;
    case MENU_MODE:
    {
      App.config.video.mode = blk[0];
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_MODE, &mode_menu.blk);
    }
    break;
    case MENU_COLOURS:
    {
      App.cur_mode->cols = blk[0];

      if (App.cur_win == win_data[WIN_OP_MANUAL].win_handle)
      {
        // Check available modes, may need to reduce resolution
        adjust_resolution(&App.config.video.modes[KA_MODE_MANUAL]);
      }
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_COLOURS, &col_menu.blk);
    }
    break;
    case MENU_RESOLUTION:
    {
      const res_info* info = &res[blk[0]];
      App.cur_mode->width = info->width;
      App.cur_mode->height = info->height;
      App.cur_mode->id = blk[0];

      if (App.cur_win == win_data[WIN_OP_MANUAL].win_handle)
      {
        // May need to adjust number of colours
        App.cur_mode->cols = adjust_colours(App.cur_mode->cols, info->col_mask);
      }
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_RESOLUTION, &res_menu->blk);
    }
    break;
    case MENU_SCALER:
    {
      App.cur_mode->scaler = blk[0];
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_SCALER, &scaler_menu.blk);
    }
    break;
    case MENU_SKIN:
    {
      strncpy(App.config.skin, App.skin[blk[0]], sizeof(App.config.skin));
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_SKIN, &skin_menu->blk);
    }
    break;
    case MENU_ZOOM:
    {
      zoom = blk[0] * 100;
      if(zoom == 0) zoom = 50;

      App.cur_mode->zoom = zoom;
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_ZOOM, &zoom_menu.blk);
    }
    break;
    case MENU_AUDIOPLAYER:
    {
      App.config.audio.module = blk[0];
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_AUDIOPLAYER, &audioplayer_menu.blk);
    }
    break;
    case MENU_DEINTERLACE:
    {
      App.config.video.deinterlace = blk[0];
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_DEINTERLACE, &deinterlace_menu.blk);
    }
    break;
    case MENU_RESIZEMODE:
    {
      App.config.video.resizemode = blk[0];
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_RESIZEMODE, &resizemode_menu.blk);
    }
    break;
    case MENU_RESIZETO:
    {
      App.config.video.resizeto = blk[0];
      update_window_info(0);

      if(m.button == 1) // adjust
        open_menu(0, 0, MENU_RESIZETO, &resizeto_menu.blk);
    }
    break;
  }
}

/*
 * main
 * ----
 * Main Wimp Poll loop.
 */
int main(void)
{
  _kernel_swi_regs regs;
  int task_handle;
  int blk[64];
  int msglist[] = {0};

  // find out if we are already running
  regs.r[0] = 0;
  while (regs.r[0] >= 0)
  {
    regs.r[1] = (int) blk;
    regs.r[2] = 4*sizeof(int);
    _kernel_swi(TaskManager_EnumerateTasks, &regs, &regs);
    if (strcmp((char*) blk[1], appname) == 0)
      return 0;
  }

  regs.r[0] = 350;
  regs.r[1] = *(int*) "TASK";
  regs.r[2] = (int) appname;
  regs.r[3] = (int) msglist;
  _kernel_swi(Wimp_Initialise, &regs, &regs);
  task_handle = regs.r[1];

  // create iconbar icon
  blk[0] = -1; // r/h icon
  blk[1] = 0;
  blk[2] = 0;
  blk[3] = 68;
  blk[4] = 68;
  blk[5] = 0x0000301a;
  strcpy((char*) &blk[6], "!kinoamp");
  regs.r[0] = 0;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_CreateIcon, &regs, &regs);

  msg_open("KinoIRes:Messages");
  loadtemplates("KinoIRes:Templates");
  config_init(&App.config);
  config_read(&App.config, choices_file);
  read_modes();
  read_skins(skins_dir);
  read_menus();
  set_section(win_data[WIN_OP_MAIN].win_handle);
  update_window_info(1);

  while (!App.quit)
  {
    int time;

    _swix(OS_ReadMonotonicTime, _OUT(0), &time);
    regs.r[0] = 0; // poll mask
    regs.r[1] = (int) blk;
    regs.r[2] = time + 1000;
    _kernel_swi(Wimp_PollIdle, &regs, &regs);
    switch (regs.r[0]) // result
    {
      case 0:
      {
        int high, low;
        if (_swix(OS_File, _INR(0, 1) |_OUTR(2, 3), 23, filminfo_file, &high, &low) == 0)
        {
           if ((high != App.film_info_stamp.high)
           ||  (low != App.film_info_stamp.low))
           {
             App.film_info_stamp.high = high;
             App.film_info_stamp.low = low;
             display_film_info(filminfo_file, 0);
           }
        }
      }
      break;
      case 2:
      {
        if ((blk[0] == win_data[WIN_OPTIONS].win_handle)
        &&  (App.cur_win != -1))
        {
          blk[0] = App.cur_win;
          _kernel_swi(Wimp_OpenWindow, &regs, &regs);
          blk[0] = win_data[WIN_OPTIONS].win_handle;
          blk[7] = App.cur_win;
        }
        _kernel_swi(Wimp_OpenWindow, &regs, &regs);
      }
      break;
      case 3:
      {
        if (blk[0] == win_data[WIN_OPTIONS].win_handle)
          close_main_window();
        if (blk[0] == win_data[WIN_PLAYLIST].win_handle)
          playlist_close();
        _swi(Wimp_CloseWindow, _IN(1), blk);

      }
      break;
      case 6: mouse_click((const mouse_t*) blk); break;
      case 7: drag_return(); break;
      case 8: _swi(Wimp_ProcessKey, _IN(0), blk[6]); break;
      case 9: menu_selection(blk); break;
      case 10: wimp_scroll(blk); break;
      case 17:
      case 18:
      case 19: wimp_msg(blk, regs.r[0]); break;
    }
  }

  playlist_close();

  msg_close();

  regs.r[0] = task_handle;
  regs.r[1] = *(int*) "TASK";
  _kernel_swi(Wimp_CloseDown, &regs, &regs);

  return 0;
}
