/*
 *  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
 *
 *  This 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 software 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 software; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 *  USA.
 */

/*
 * vncviewer.c
 */

#include <stdio.h>
#include <time.h>
#include <string.h>

#include "oslib/os.h"
#include "oslib/osbyte.h"
#include "oslib/wimp.h"
#include "oslib/osspriteop.h"
#include "oslib/colourtrans.h"

#include "vncviewer.h"
#include "vnckeys.h"
#include "display.h"
#include "antitwit.h"

#define CFGFILE_LEN 1023
#define SH (1<<16)


DISPLAY display;

// choices
int savewhenquit = 0;
int fullscreen = 0;
int bgcolour = 0;
int offsetx = 0, offsety = 0;
int antitwit = 0;
int keepontop = 0;
int openontop = 0;
int noborder = 0;
char password[9];
int guess = 0;
int debug = 0;
int supporttight = 0;
int bpp = 16;
int displaycursor = 0;
int checkclientbitmap = 0;
int dither8 = 0;
int scale = 100;
int addrectshift = 1;
int fastcopy = 0;
int altkey = VNCKEY_ALT_L;

#define CLOSEACTION_QUIT                0
#define CLOSEACTION_ICONBAR             1
#define CLOSEACTION_TOGGLESCALE         2
int closeaction = CLOSEACTION_QUIT;

// iconbar
wimp_i iconbarhandle;
osspriteop_area *iconbarsprite = NULL;

unsigned short rgb332_rgb555_table[256];
static wimp_w window;
static int connected = 0;

static int CreateSprite(void);
static int CreateWindow(void);
static void SendMouse(int force);
static void RedrawWindow(wimp_draw *draw, bool *more);

static unsigned short pressedkeys[32];
static int pressedkeyscount = 0;
static int releasekeystime = 0x7f000000;
static void release_keys(void);
static void send_keys(int key1, int key2);
static void send_key(int key);

static void close_window(void);
static void set_window_extent(void);

static int lastmousex, lastmousey, lastmousetime, lastmousebuttons, mouseinside;
static int lastkey, lastkeytime;
static int screensizex, screensizey, visiblex, visibley;
static char transtable[256];

static int done = 0;

static unsigned int versmaj = 0, versmin;


// Called with release = 1 to release the special keys at the end
// (very useful if the connection is shared).
void check_ctrl_alt(int release) {
  static int altpressed = 0;
  static int ctrlpressed = 0;
  static int checktime = 0;
  int alt = 0, ctrl = 0;

  if (!release) {
    if (clock() < checktime)
      return;
    checktime = clock()+3;
    xos_byte(osbyte_SCAN_KEYBOARD, 0x80 | 1, 0, &ctrl, NULL);
    xos_byte(osbyte_SCAN_KEYBOARD, 0x80 | 2, 0, &alt, NULL);
    ctrl = !!ctrl;
    alt = !!alt;
  }

  if (alt != altpressed) {
    if (alt)
      SendKeyEvent(altkey, 1);
    else
      SendKeyEvent(altkey, 0);
    altpressed = alt;
  }

  if (ctrl != ctrlpressed) {
    if (ctrl)
      SendKeyEvent(VNCKEY_CTRL_L, 1);
    else
      SendKeyEvent(VNCKEY_CTRL_L, 0);
    ctrlpressed = ctrl;
  }
}


osspriteop_area *create_scaled_down_version(int sizex, int sizey) {
// create a scaled down version of the current screen
  osspriteop_area *area;
  int step, x, y, r, g, b, i;
  unsigned short *rd, *wr, *tmp;

  area = malloc(2*(sizex+1)*sizey+256);
  if (!area)   return NULL;

  area->size = 2*(sizex+1)*sizey+256;
  area->sprite_count = 0;
  area->first = area->used = 16;
  if (xosspriteop_create_sprite(osspriteop_USER_AREA, area, "sprite",
                                0, sizex, sizey, (os_mode)0x281680b5)) {
    free(area);
    return NULL;
  }

  tmp = malloc(2*sizex*display.sizey);
  if (!tmp) {
    free(area);
    return NULL;
  }
  // first scale down horizontally
  step = (1024*sizex)/display.sizex;
  rd = display.data;
  for (y = display.sizey; y > 0; y--) {
    wr = tmp + sizex*(display.sizey-y);
    x = r = g = b = 0;
    for (i = display.sizex; i > 0; i--) {

      r += (*rd)       & 31;
      g += ((*rd)>>5)  & 31;
      b += ((*rd)>>10) & 31;
      rd++;

      x += step;
      if (x >= 1024) {
        r = (r*step)/x;
        g = (g*step)/x;
        b = (b*step)/x;
        if (r > 31)   r = 31;
        if (g > 31)   g = 31;
        if (b > 31)   b = 31;
        *wr++ = r | (g<<5) | (b<<10);
        x -= 1024;
        r = g = b = 0;
      }
    }
  }
  // then scale down vertically
  step = (1024*sizey)/display.sizey;
  for (x = sizex; x > 0; x--) {
    rd = tmp + (sizex-x);
    wr = (unsigned short *)(((char *)area) + 60);
    wr += sizex-x;
    y = r = g = b = 0;
    for (i = display.sizey; i > 0; i--) {

      r += (*rd)       & 31;
      g += ((*rd)>>5)  & 31;
      b += ((*rd)>>10) & 31;
      rd += sizex;

      y += step;
      if (y >= 1024) {
        r = (r*step)/y;
        g = (g*step)/y;
        b = (b*step)/y;
        if (r > 31)   r = 31;
        if (g > 31)   g = 31;
        if (b > 31)   b = 31;
        *wr = r | (g<<5) | (b<<10);
        wr += sizex;
        y -= 1024;
        r = g = b = 0;
      }
    }
  }

  free(tmp);

  return area;
}



void poll() {
  wimp_block wimp;
  wimp_event_no action;

  /* Some events are queued to avoid reentrancy problems. -- VL */
  xwimp_poll(wimp_QUEUE_MOUSE|wimp_QUEUE_KEY, &wimp, NULL, &action);
  switch (action) {
  case wimp_OPEN_WINDOW_REQUEST:
    xwimp_open_window(&wimp.open);
    break;

  case wimp_CLOSE_WINDOW_REQUEST:
    close_window();
    break;

  case wimp_POINTER_ENTERING_WINDOW:
    mouseinside = 1;
    if (!displaycursor)   xosbyte(osbyte_SELECT_POINTER, 0, 0);
    xwimp_set_caret_position(window, -1, -1000, -1000, 40, -1);
    break;

  case wimp_POINTER_LEAVING_WINDOW:
    mouseinside = 0;
    if (!displaycursor)   xosbyte(osbyte_SELECT_POINTER, 1, 0);
    break;

  case wimp_REDRAW_WINDOW_REQUEST:
    {
      bool more;
      xwimp_redraw_window(&wimp.redraw, &more);
      RedrawWindow(&wimp.redraw, &more);
    }
    break;
  case wimp_USER_MESSAGE:
  case wimp_USER_MESSAGE_RECORDED:
  case wimp_USER_MESSAGE_ACKNOWLEDGE:
    switch (wimp.message.action) {
    case message_QUIT:
      done = 1;
      break;
    }
  }
}


int main(int argc, char **argv) {
  unsigned int ip = 0;
  int arg, displayn, i;
  int wimpmsg[4];
  char configfile[CFGFILE_LEN+1];

  { // Get ViNCe version
    FILE *f;
    f = fopen("<ViNCe$Dir>.Version", "r");
    if (f != NULL) {
      fscanf(f, "VERSION=%u.%u", &versmaj, &versmin);
      fclose(f);
    }
  }

  // parse arguments
  displayn = 1; // default display
  strcpy(password, "mypass");
  *configfile = '\0';

  for (arg = 1; arg < argc; arg++) {
    if (strcmp(argv[arg], "-display") == 0) {
      arg++;
      displayn = atoi(argv[arg]);
    } else if (strcmp(argv[arg], "-config") == 0) {
      arg++;
      strncpy(configfile, argv[arg], CFGFILE_LEN+1);
      if (configfile[CFGFILE_LEN] != '\0') {
        fprintf(stderr, "Config file name too long\n");
        exit(1);
      }
    } else if (strcmp(argv[arg], "-password") == 0) {
      arg++;
      strncpy(password, argv[arg], 8);
      password[8] = '\0';
    } else if (strcmp(argv[arg], "-8bpp") == 0) {
      bpp = 8;
    } else if (strcmp(argv[arg], "-dither8") == 0) {
      dither8 = 1;
    } else if (strcmp(argv[arg], "-tight") == 0) {
      supporttight = 1;
    } else if (strcmp(argv[arg], "-save") == 0) {
      savewhenquit = 1;
    } else if (strcmp(argv[arg], "-checkclientbitmap") == 0) {
      checkclientbitmap = 1;
    } else if (strcmp(argv[arg], "-cursor") == 0) {
      displaycursor = 1;
    } else if (strcmp(argv[arg], "-debug") == 0) {
      debug = 1;
    } else if (strcmp(argv[arg], "-full") == 0) {
      fullscreen = 1;
      arg++;
      if (sscanf(argv[arg], "%x", &bgcolour) != 1)
        bgcolour = 0x00000000;
      else
        bgcolour <<= 8;
    } else if (strcmp(argv[arg], "-guess") == 0) {
      guess = 3;
    } else if (strcmp(argv[arg], "-bottom") == 0) {
      openontop = 0;
    } else if (strcmp(argv[arg], "-top") == 0) {
      openontop = 1;
    } else if (strcmp(argv[arg], "-noborder") == 0) {
      noborder = 1;
    } else if (strcmp(argv[arg], "-ontop") == 0) {
      keepontop = 1;
    } else if (strcmp(argv[arg], "-anti") == 0) {
      antitwit = 1;
    } else if (strcmp(argv[arg], "-fastcopy") == 0) {
      fastcopy = 1;
    } else if (strcmp(argv[arg], "-scale") == 0) {
      arg++;
      scale = atoi(argv[arg]);
    } else if (strcmp(argv[arg], "-closewindow") == 0) {
      arg++;
      closeaction = atoi(argv[arg]);
    } else if (strcmp(argv[arg], "-altsendsmeta") == 0) {
      altkey = VNCKEY_META_L;
    } else if (arg == argc-1) {
      // the last argument must be ipaddress
      unsigned int ip0, ip1, ip2, ip3;
      if (sscanf(argv[argc-1], "%u.%u.%u.%u", &ip0, &ip1, &ip2, &ip3) == 4)
        ip = (ip0<<0) | (ip1<<8) | (ip2<<16) | (ip3<<24);
      else {
        fprintf(stderr, "The last argument must be the IP address\n");
        exit(1);
      }
    }
  }

  if (*configfile) {
    FILE *fh;
    fh = fopen(configfile, "r");
    if (fh) {
      while (!feof(fh)) {
        char temp[512], arg[256], val[256];

        fgets(temp, 255, fh);
        temp[255] = '\0';
        if (strchr(temp, '=')) {
          char *t;
          t = strchr(temp, '=');
          *t++ = '\0';
          sscanf(temp, "%s", arg);
          sscanf(t, "%s", val);
        } else
          *arg = '\0';

        if (strcmp(arg, "display") == 0) {
          displayn = atoi(val);
        } else if (strcmp(arg, "password") == 0) {
          strncpy(password, val, 8);
          password[8] = '\0';
        } else if (strcmp(arg, "8bpp") == 0) {
          if (atoi(val) == 1)  bpp = 8;
        } else if (strcmp(arg, "dither8") == 0) {
          dither8 = !!atoi(val);
        } else if (strcmp(arg, "tight") == 0) {
          supporttight = !!atoi(val);
        } else if (strcmp(arg, "save") == 0) {
          savewhenquit = !!atoi(val);
        } else if (strcmp(arg, "checkclientbitmap") == 0) {
          checkclientbitmap = !!atoi(val);
        } else if (strcmp(arg, "cursor") == 0) {
          displaycursor = !!atoi(val);
        } else if (strcmp(arg, "save") == 0) {
          savewhenquit = !!atoi(val);
        } else if (strcmp(arg, "full") == 0) {
          fullscreen = 1;
          if (sscanf(val, "%x", &bgcolour) != 1)
            bgcolour = 0x00000000;
          else
            bgcolour <<= 8;
        } else if (strcmp(arg, "guess") == 0) {
          guess = atoi(val);
        } else if (strcmp(arg, "noborder") == 0) {
          noborder = !!atoi(val);
        } else if (strcmp(arg, "ontop") == 0) {
          keepontop = !!atoi(val);
        } else if (strcmp(arg, "openontop") == 0) {
          openontop = !!atoi(val);
        } else if (strcmp(arg, "anti") == 0) {
          antitwit = !!atoi(val);
        } else if (strcmp(arg, "fastcopy") == 0) {
          fastcopy = !!atoi(val);
        } else if (strcmp(arg, "scale") == 0) {
          scale = atoi(val);
        } else if (strcmp(arg, "addrectshift") == 0) {
          addrectshift = atoi(val);
        } else if (strcmp(arg, "closewindow") == 0) {
          closeaction = atoi(val);
        } else if (strcmp(arg, "altsendsmeta") == 0) {
          altkey = atoi(val) ? VNCKEY_META_L : VNCKEY_ALT_L;
        } else if (strcmp(arg, "server") == 0) {
          unsigned int ip0, ip1, ip2, ip3;
          if (sscanf(val, "%u.%u.%u.%u", &ip0, &ip1, &ip2, &ip3) == 4)
            ip = (ip0<<0) | (ip1<<8) | (ip2<<16) | (ip3<<24);
        }
      } // endwhile
      fclose(fh);
    } // endif
  }

  if (ip == 0) {
    fprintf(stderr, "The IP address isn't defined\n");
    exit(1);
  }

  // reset our display structure
  display.displaycontext = NULL;

  // dependencies
  if (scale < 25)   scale = 25;
  if (scale > 200)  scale = 200;
  if (bpp == 8)     supporttight = 0;
  if (bpp != 16)    dither8 = 0;

  if (closeaction != CLOSEACTION_ICONBAR)
    closeaction = CLOSEACTION_QUIT;

  // setup as a Wimp task
  // select which messages to receive
  wimpmsg[0] = message_MODE_CHANGE;
  wimpmsg[1] = 0;

  // initialise the wimp
  if (xwimp_initialise(310, "ViNCe", (wimp_message_list *) wimpmsg,
      NULL, NULL))
    exit(1);

  // RGB332 table - used when either -8bpp or -dither8 is set
  for (i = 0; i < 256; i++) {
    int r, g, b;
    r = i & 7;
    g = (i>>3) & 7;
    b = (i>>6) & 7;
    r = (r*1168)/256;
    g = (g*1168)/256;
    b = (b*2728)/256;
    rgb332_rgb555_table[i] = r | (g<<5) | (b<<10);
  }

  // Now enter the main loop, processing VNC messages.
  done = 0;
  lastmousex = lastmousey = lastmousetime = -1000;
  mouseinside = 0;
  lastkey = 0;
  lastkeytime = 0;

  while (!done) {
    wimp_block wimp;
    wimp_event_no action;

    ((int *) &wimp.key)[7] = 1<<15; // Set bit 15 to detect DeepKeys' presence
    xwimp_poll(0, &wimp, NULL, &action);

    switch (action) {
      case wimp_NULL_REASON_CODE:

        if (connected) {
          int clk;

          check_ctrl_alt(0);
          clk = clock();
          if (keepontop && (clk > keepontop)) {
            wimp_window_state state;

            state.w = window;
            xwimp_get_window_state(&state);
            state.next = wimp_TOP;
            xwimp_open_window((wimp_open *)&state);

            keepontop = clock() + 50;

            if (displaycursor)
              xosbyte(osbyte_SELECT_POINTER, 1, 0);
            else
              xosbyte(osbyte_SELECT_POINTER, 0, 0);
            xwimp_set_caret_position(window, -1, -1000, -1000, 40, -1);
          }

          if (clk > releasekeystime)   release_keys();

          if ((clk > lastmousetime) && (mouseinside))   SendMouse(0);

          if (!HandleRFBServerMessage()) {
            fprintf(stderr, "Failed to read server msg\n");
            done = 3;
          }
          {
            static int flushtime = 0;
            if (clock() > flushtime) {
              display_flush();
              flushtime = clock() + 3;
            }
          }

        } else {
          // make a TCP connection to the VNC server
          Bool ok;
          int attempts = guess;
          int port = 5900 + displayn;

          do {
            ok = ConnectToRFBServer(ip, port);
            if (ok)
              break;
            port++;
          } while (attempts-- > 0);

          if (!ok) {
            done = 4;
          } else {
            // Initialise the VNC connection, including reading the password
            if (!InitialiseRFBConnection()) {
              done = 5;
            } else {
              // Tell the VNC server which pixel format and encodings we want to use
              SetFormatAndEncodings();
              // create sprite with the correct size
              display.sizex = si.framebufferWidth;
              display.sizey = si.framebufferHeight;
              CreateSprite();
              CreateWindow();
              connected = 1;
              SendFramebufferUpdateRequest(0,0,display.sizex,display.sizey,0);
            }
          }
        }
        break;

      case wimp_CLOSE_WINDOW_REQUEST:
        close_window();
        break;

      case wimp_POINTER_ENTERING_WINDOW:
        mouseinside = 1;
        SendMouse(1);
        if (!displaycursor)   xosbyte(osbyte_SELECT_POINTER, 0, 0);
        xwimp_set_caret_position(window, -1, -1000, -1000, 40, -1);
        break;

      case wimp_POINTER_LEAVING_WINDOW:
        mouseinside = 0;
        if (!displaycursor)   xosbyte(osbyte_SELECT_POINTER, 1, 0);
        break;

      case wimp_KEY_PRESSED:
        if (mouseinside) {
          wimp_key_no key;

          release_keys();
          key = wimp.key.c;
          if (key >= 384) {
            if (key < 512) {
              static int keymap[] = {
                0,             VNCKEY_F1,      VNCKEY_F2,      VNCKEY_F3,
                VNCKEY_F4,     VNCKEY_F5,      VNCKEY_F6,      VNCKEY_F7,
                VNCKEY_F8,     VNCKEY_F9,      VNCKEY_TAB,     VNCKEY_END,
                VNCKEY_LEFT,   VNCKEY_RIGHT,   VNCKEY_DOWN,    VNCKEY_UP,
                0,             VNCKEY_F1|SH,   VNCKEY_F2|SH,   VNCKEY_F3|SH,
                VNCKEY_F4|SH,  VNCKEY_F5|SH,   VNCKEY_F6|SH,   VNCKEY_F7|SH,
                VNCKEY_F8|SH,  VNCKEY_F9|SH,   VNCKEY_TAB|SH,  VNCKEY_END|SH,
                VNCKEY_LEFT|SH,VNCKEY_RIGHT|SH,VNCKEY_PAGEDOWN,VNCKEY_PAGEUP,
                0,             VNCKEY_F1,      VNCKEY_F2,      VNCKEY_F3,
                VNCKEY_F4,     VNCKEY_F5,      VNCKEY_F6,      VNCKEY_F7,
                VNCKEY_F8,     VNCKEY_F9,      VNCKEY_TAB,     VNCKEY_END,
                VNCKEY_LEFT,   VNCKEY_RIGHT,   VNCKEY_DOWN,    VNCKEY_UP,
                0,             VNCKEY_F1|SH,   VNCKEY_F2|SH,   VNCKEY_F3|SH,
                VNCKEY_F4|SH,  VNCKEY_F5|SH,   VNCKEY_F6|SH,   VNCKEY_F7|SH,
                VNCKEY_F8|SH,  VNCKEY_F9|SH,   VNCKEY_TAB|SH,  VNCKEY_END|SH,
                VNCKEY_LEFT|SH,VNCKEY_RIGHT|SH,VNCKEY_PAGEDOWN,VNCKEY_PAGEUP,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                VNCKEY_F10,    VNCKEY_F11,    VNCKEY_F12,    VNCKEY_INSERT,
                0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                VNCKEY_F10|SH, VNCKEY_F11|SH, VNCKEY_F12|SH, VNCKEY_INSERT|SH,
                0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                VNCKEY_F10,    VNCKEY_F11,    VNCKEY_F12,    VNCKEY_INSERT,
                0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                VNCKEY_F10|SH, VNCKEY_F11|SH, VNCKEY_F12|SH, VNCKEY_INSERT|SH,
                0, 0
              };
              int vnckey;
              vnckey = keymap[key-384];
              if (vnckey) {
                int extra;
                extra = ((int *) &wimp.key)[7]; // written by DeepKeys module
                switch ((extra & (1<<15)) == 0 && (key & ~0x31) == 0x18e ?
                  extra >> 16 : 0) {
                case 33:
                  if (extra & 3)
                    send_keys(VNCKEY_SHIFT_L, VNCKEY_PAGEUP);
                  else
                    send_key(VNCKEY_PAGEUP);
                  break;
                case 54:
                  if (extra & 3)
                    send_keys(VNCKEY_SHIFT_L, VNCKEY_PAGEDOWN);
                  else
                    send_key(VNCKEY_PAGEDOWN);
                  break;
                case 89:
                  if (extra & 3)
                    send_keys(VNCKEY_SHIFT_L, VNCKEY_UP);
                  else
                    send_key(VNCKEY_UP);
                  break;
                case 99:
                  if (extra & 3)
                    send_keys(VNCKEY_SHIFT_L, VNCKEY_DOWN);
                  else
                    send_key(VNCKEY_DOWN);
                  break;
                default:
                  if (vnckey & SH)
                    send_keys(VNCKEY_SHIFT_L, vnckey & ~SH);
                  else
                    send_key(vnckey);
                }
              }
            }
          } else {
            switch (key) {
            case wimp_KEY_ESCAPE:  { // Escape or Ctrl-[
              int extra;
              extra = ((int *) &wimp.key)[7]; // written by DeepKeys module
              send_key((extra & (1<<15)) || (extra >> 16) == 0 ?
                VNCKEY_ESCAPE : '['); // If DeepKeys not present, Escape sent.
              break;
            }
            case wimp_KEY_DELETE:  { // Delete or Ctrl-?
              int extra;
              extra = ((int *) &wimp.key)[7]; // written by DeepKeys module
              send_key((extra & (1<<15)) || (extra >> 16) == 52 ?
                VNCKEY_DELETE : '?'); // If DeepKeys not present, Delete sent.
              break;
            }
            case wimp_KEY_HOME:    { // Home or Ctrl-^
              int extra;
              extra = ((int *) &wimp.key)[7]; // written by DeepKeys module
              send_key((extra & (1<<15)) || (extra >> 16) == 32 ?
                VNCKEY_HOME : '^'); // If DeepKeys not present, Home sent.
              break;
            }
            case 0:                  send_key('@');
                                     break;
            default:
              if (key >= ' ')
                send_key(key);
              else if (key > 26)
                send_key(key+64);
              else if (key > 0) {
                int shift;
                xos_byte(osbyte_SCAN_KEYBOARD, 0x80 | 0, 0, &shift, NULL);
                if (key == wimp_KEY_RETURN) { // Return or Enter or Ctrl-M
                  int extra;
                  extra = ((int *) &wimp.key)[7]; // written by DeepKeys module
                  if ((extra & (1<<15)) || (extra >> 16) != 84) {
                    // DeepKeys not present or it is not Ctrl-M
                    send_key(VNCKEY_ENTER);
                    break;
                  }
                } // key == wimp_KEY_RETURN
                else if (key == wimp_KEY_BACKSPACE) { // Backspace or Ctrl-H
                  int extra;
                  extra = ((int *) &wimp.key)[7]; // written by DeepKeys module
                  if ((extra & (1<<15)) || (extra >> 16) == 30) {
                    // DeepKeys not present or it is Backspace
                    send_key(VNCKEY_BACKSPACE);
                    break;
                  }
                } // key == wimp_KEY_BACKSPACE
                send_key(key + (shift ? 64 : 96));
              } // key > 0
              break;
            }
          }
        }
        break;

      case wimp_MOUSE_CLICK:
        if (wimp.pointer.w == wimp_ICON_BAR) {
          wimp_window_state state;

          state.w = window;
          xwimp_get_window_state(&state);
          state.next = wimp_TOP;
          xwimp_open_window((wimp_open *)&state);
          xwimp_delete_icon(wimp_ICON_BAR, iconbarhandle);
        } else
          SendMouse(1);
        break;

      case wimp_OPEN_WINDOW_REQUEST:
        xwimp_open_window(&wimp.open);
        break;

      case wimp_REDRAW_WINDOW_REQUEST:
        {
          bool more;
          xwimp_redraw_window(&wimp.redraw, &more);
          RedrawWindow(&wimp.redraw, &more);
        }
        break;

      case wimp_USER_MESSAGE:
      case wimp_USER_MESSAGE_RECORDED:
      case wimp_USER_MESSAGE_ACKNOWLEDGE:
        switch (wimp.message.action) {
        case message_QUIT:
          done = 1;
          break;
        case message_MODE_CHANGE:
          xcolourtrans_select_table_for_sprite(display.displaycontext,
                                       (osspriteop_id)"sprite",
                                       (os_mode)-1, (os_palette *)-1,
                                       (osspriteop_trans_tab *)transtable, 0);
          break;
        }
    }
  }

  if (connected)
    check_ctrl_alt(1);

  if (savewhenquit)
     xosspriteop_save_sprite_file(osspriteop_USER_AREA,
               (osspriteop_area *)display.displaycontext,
                    "<ViNCe$Dir>.screendump");

  xosbyte(osbyte_SELECT_POINTER, 1, 0);
  CloseConnection();

  return 0;
}


int CreateSprite() {

  osspriteop_area *area;

  display.displaycontext = malloc(2*(display.sizex+1)*display.sizey+256);
  if (!display.displaycontext)   return 1;

  area = display.displaycontext;
  area->size = 2*(display.sizex+1)*display.sizey+256;
  area->sprite_count = 0;
  area->first = area->used = 16;
  if (xosspriteop_create_sprite(osspriteop_USER_AREA, area, "sprite",
                  0, display.sizex, display.sizey, (os_mode)0x281680b5))
      return 1;

  display.shortsperline = (display.sizex+1) & ~1;
  display.data = (unsigned short *)(((char *)area)+60);

  xcolourtrans_select_table_for_sprite(display.displaycontext,
                              (osspriteop_id)"sprite",
                              (os_mode)-1, (os_palette *)-1,
                              (osspriteop_trans_tab *)transtable, 0);
  return 0;
}


int CreateWindow() {

  wimp_window win;
  int sx, sy;
  static char titletext[24];

  xos_read_mode_variable((os_mode)-1, os_MODEVAR_XWIND_LIMIT, &sx, NULL);
  xos_read_mode_variable((os_mode)-1, os_MODEVAR_YWIND_LIMIT, &sy, NULL);
  screensizex = 2*(sx+1);
  screensizey = 2*(sy+1);

  visiblex = display.sizex*2*scale/100;
  visibley = display.sizey*2*scale/100;
  offsetx = (screensizex - visiblex)/2;
  offsety = (screensizey - visibley)/2;

  // create a window
  if (fullscreen) {
    win.visible.x0 = 0;
    win.visible.y0 = 0;
    win.visible.x1 = screensizex;
    win.visible.y1 = screensizey;
  } else {
    win.visible.x0            = offsetx;
    win.visible.y0            = offsety;
    win.visible.x1            = win.visible.x0 + visiblex;
    win.visible.y1            = win.visible.y0 + visibley;
  }
  win.next                  = wimp_BOTTOM;
  if (noborder)
    win.flags  = wimp_WINDOW_MOVEABLE   | wimp_WINDOW_NO_BOUNDS  | wimp_WINDOW_NEW_FORMAT;
  else
    win.flags  = wimp_WINDOW_VSCROLL    | wimp_WINDOW_HSCROLL     |
                 wimp_WINDOW_SIZE_ICON  | wimp_WINDOW_TITLE_ICON  |
                 wimp_WINDOW_BACK_ICON  | wimp_WINDOW_TOGGLE_ICON |
                 wimp_WINDOW_MOVEABLE   | // wimp_WINDOW_NO_BOUNDS |
                 wimp_WINDOW_CLOSE_ICON | wimp_WINDOW_NEW_FORMAT;
  win.title_fg              = 7;
  win.title_bg              = 2;
  win.work_fg               = 7;
  win.work_bg               = 0xff;
  win.scroll_outer          = 3;
  win.scroll_inner          = 1;
  win.highlight_bg          = 12;
  if (fullscreen) {
    win.extent.x0             = -offsetx;
    win.extent.y0             = -offsety;
    win.extent.x1             = visiblex + offsetx;
    win.extent.y1             = visibley + offsety;
  } else {
    win.extent.x0             = 0;
    win.extent.y0             = 0;
    win.extent.x1             = visiblex;
    win.extent.y1             = visibley;
  }
  win.title_flags           = wimp_ICON_TEXT      | wimp_ICON_HCENTRED |
                              wimp_ICON_VCENTRED  | wimp_ICON_INDIRECTED;
  win.work_flags            = wimp_BUTTON_CLICK<<12;
  win.sprite_area           = (osspriteop_area *)1;
  win.xmin = win.ymin = 100;
  if (versmaj > 0)
    sprintf(titletext, "ViNCe (version %u.%u)", versmaj, versmin);
  else
    sprintf(titletext, "ViNCe");
  win.title_data.indirected_text.text = titletext;
  win.title_data.indirected_text.validation = NULL;
  win.title_data.indirected_text.size = strlen(titletext);
  win.icon_count            = 0;
  if (xwimp_create_window(&win, &window))  return 1;

  set_window_extent();

  return 0;
}

void set_window_extent() {
  wimp_window_state state;

  visiblex = display.sizex*2*scale/100;
  visibley = display.sizey*2*scale/100;
  offsetx = (screensizex - visiblex)/2;
  offsety = (screensizey - visibley)/2;

// set extent

  state.w = window;
  xwimp_get_window_state(&state);
  if (keepontop || openontop)
    state.next = wimp_TOP;
  else
    state.next = wimp_BOTTOM;
  xwimp_open_window((wimp_open *)&state);
}

void SendMouse(int force) {

  wimp_pointer ptr;
  wimp_window_state state;
  int x, y, but;

  state.w = window;
  xwimp_get_window_state(&state);

  lastmousetime = clock() + 1;
  xwimp_get_pointer_info(&ptr);

  x = 100*(ptr.pos.x - (state.visible.x0 - state.xscroll))/(2*scale);
  y = display.sizey - 100*(ptr.pos.y - (state.visible.y1 - state.yscroll))/(2*scale);
  but = 0;
  if (ptr.buttons & 1)  but |= 4;
  if (ptr.buttons & 2)  but |= 2;
  if (ptr.buttons & 4)  but |= 1;
  if (force || x != lastmousex || y != lastmousey || lastmousebuttons != but) {
    SendPointerEvent(x, y, but);
    lastmousex = x;
    lastmousey = y;
    lastmousebuttons = but;
  }
}


void UpdateWindow(int x, int y, int w, int h) {

  int x1, y1;
  wimp_draw draw;
  bool more;

  x1 = 2*(x+w);
  y1 = 2*(display.sizey-y);
  x = 2*x;
  y = 2*(display.sizey-(y+h));
  x = x*scale/100;
  y = y*scale/100;
  x1 = x1*scale/100;
  y1 = y1*scale/100;

  draw.w = window;
  draw.box.x0 = x-2;
  draw.box.y0 = y-2;
  draw.box.x1 = x1+2;
  draw.box.y1 = y1+2;
  xwimp_update_window(&draw, &more);
  RedrawWindow(&draw, &more);
}


void RedrawWindow(wimp_draw *draw, bool *more) {

  int x, y;
  os_factors scaling;

  scaling.xmul = scaling.ymul = scale;
  scaling.xdiv = scaling.ydiv = 100;

  x = draw->box.x0 - draw->xscroll;
  y = draw->box.y1 - draw->yscroll;
  while (*more) {
    int antix0, antiy0, antix1, antiy1;

    antix0 = draw->clip.x0;
    antiy0 = draw->clip.y0;
    antix1 = draw->clip.x1;
    antiy1 = draw->clip.y1;
    xosspriteop_put_sprite_scaled(osspriteop_USER_AREA,
                                  display.displaycontext,
                                  (osspriteop_id)"sprite", x, y, 0, &scaling,
                                  (osspriteop_trans_tab *)transtable);
    if (fullscreen) {
      xcolourtrans_set_gcol(bgcolour, 0, 0, NULL, NULL);
      if (antix0 < x) {
        xos_plot(os_MOVE_TO, 0, 0);
        xos_plot(os_PLOT_TO | os_PLOT_RECTANGLE, offsetx, screensizey);
      }
      if (antiy0 < x) {
        xos_plot(os_MOVE_TO, offsetx, 0);
        xos_plot(os_PLOT_TO | os_PLOT_RECTANGLE, offsetx + visiblex, offsety);
      }
      if (antix1 > visiblex + x) {
        xos_plot(os_MOVE_TO, offsetx + visiblex, 0);
        xos_plot(os_PLOT_TO | os_PLOT_RECTANGLE, screensizex, screensizey);
      }
      if (antiy1 > visibley + y) {
        xos_plot(os_MOVE_TO, offsetx, offsety + visibley);
        xos_plot(os_PLOT_TO | os_PLOT_RECTANGLE, offsetx + visiblex, screensizey);
      }
    }
    xwimp_get_rectangle(draw, more);
    if (antitwit)  antitwitter(antix0, antiy0, antix1-antix0, antiy1-antiy0);
  }
}


void release_keys() {

  int i;

  for (i = 0; i < pressedkeyscount; i++) {
    SendKeyEvent(pressedkeys[i], 0);
  }
  pressedkeyscount = 0;
  releasekeystime = 0x7f000000;          // never
}


void send_keys(int key1, int key2) {

  if (pressedkeyscount >= 30)   return;
  pressedkeys[pressedkeyscount+0] = key2;
  pressedkeys[pressedkeyscount+1] = key1;
  pressedkeyscount += 2;
  SendKeyEvent(key1, 1);
  SendKeyEvent(key2, 1);
  releasekeystime = clock()+4;
}


void send_key(int key) {

  if (pressedkeyscount >= 32)   return;
  pressedkeys[pressedkeyscount] = key;
  pressedkeyscount++;
  SendKeyEvent(key, 1);
  releasekeystime = clock()+4;
}



void close_window() {

  switch (closeaction) {
  case CLOSEACTION_QUIT:
    done = 2;
    break;
  case CLOSEACTION_ICONBAR:
    {
      int x, y;

      if (iconbarsprite)   free(iconbarsprite);
      y = 42;
      x = (1+(y*display.sizex)/display.sizey) & ~1;
      iconbarsprite = create_scaled_down_version(x, y);
      if (iconbarsprite) {
        wimp_icon_create icon;

        icon.w = (wimp_w) -1;
        icon.icon.extent.x0 = 0;
        icon.icon.extent.y0 = -4;
        icon.icon.extent.x1 = 2*x;
        icon.icon.extent.y1 = icon.icon.extent.y0 + 2*y;
        icon.icon.flags = 0x0000311a;
        icon.icon.data.indirected_sprite.id = (osspriteop_id)"sprite";
        icon.icon.data.indirected_sprite.area = (osspriteop_area *)iconbarsprite;
        icon.icon.data.indirected_sprite.size = strlen("sprite")+1;
        xwimp_create_icon(&icon, &iconbarhandle);
        xwimp_close_window(window);
      } else
        done = 2;
    }
    break;
  case CLOSEACTION_TOGGLESCALE:
    {
      static int firsttime = 1, toggled = 0, scale1, scale2;

      if (firsttime) {
        firsttime = 0;
        scale1 = scale;
        if (scale1 == 100)
          scale2 = 25;
        else
          scale2 = 100;
      }
      if (toggled)
        scale = scale1;
      else
        scale = scale2;
      toggled = !toggled;

      set_window_extent();
      UpdateWindow(0, 0, display.sizex, display.sizey);
    }
    break;
  }
}

int fast_copy_ok() {
  if (fastcopy && scale == 100) {
    wimp_window_state state;
    wimp_window_flags mask = wimp_WINDOW_NOT_COVERED|wimp_WINDOW_FULL_SIZE;

    state.w = window;
    xwimp_get_window_state(&state);
    return (state.flags & mask) == mask;
  }
  else
    return 0;
}

void BlockCopy(int rx, int ry, int rw, int rh, int sx, int sy) {
  int x0, y0, x1, y1, xd, yd;

  display_flush();
  x0 = 2*sx;
  x1 = 2*(sx+rw);
  y0 = 2*(display.sizey-(sy+rh));
  y1 = 2*(display.sizey-sy);
  xd = 2*rx;
  yd = 2*(display.sizey-(ry+rh));
  xwimp_block_copy(window, x0, y0, x1, y1, xd, yd);
}
