/***********************************************************
* Artsoft Retro-Game Library                               *
*----------------------------------------------------------*
* (c) 1994-2006 Artsoft Entertainment                      *
*               Holger Schemel                             *
*               Detmolder Strasse 189                      *
*               33604 Bielefeld                            *
*               Germany                                    *
*               e-mail: info@artsoft.org                   *
*----------------------------------------------------------*
* msdos.c                                                  *
***********************************************************/

#include "system.h"


#if defined(PLATFORM_MSDOS)

#include "sound.h"
#include "joystick.h"
#include "misc.h"
#include "setup.h"
#include "pcx.h"

#define AllegroDefaultScreen() (display->screens[display->default_screen])

/* allegro driver declarations */
DECLARE_GFX_DRIVER_LIST(GFX_DRIVER_VBEAF GFX_DRIVER_VESA2L GFX_DRIVER_VESA1)
DECLARE_COLOR_DEPTH_LIST(COLOR_DEPTH_8)
DECLARE_DIGI_DRIVER_LIST(DIGI_DRIVER_SB)
DECLARE_MIDI_DRIVER_LIST()
DECLARE_JOYSTICK_DRIVER_LIST(JOYSTICK_DRIVER_STANDARD)

/* allegro global variables */
extern volatile int key_shifts;
extern int num_joysticks;
extern JOYSTICK_INFO joy[];
extern int i_love_bill;

/* internal variables of msdos.c */
static boolean keyboard_auto_repeat = TRUE;
static int key_press_state[MAX_SCANCODES];
static XEvent event_buffer[MAX_EVENT_BUFFER];
static int pending_events;
static boolean joystick_event;
static boolean mouse_installed = FALSE;
static int last_mouse_pos;
static int last_mouse_b;
static int last_joystick_state;
static BITMAP* video_bitmap;

static RGB global_colormap[MAX_COLORS];
static int global_colormap_entries_used = 0;

boolean wait_for_vsync;

static BITMAP *Read_PCX_to_AllegroBitmap(char *);

static void allegro_init_drivers()
{
  int i;

  for (i = 0; i < MAX_EVENT_BUFFER; i++)
    event_buffer[i].type = 0;

  for (i = 0; i < MAX_SCANCODES; i++)
    key_press_state[i] = KeyReleaseMask;

  last_mouse_pos = mouse_pos;
  last_mouse_b = 0;

  pending_events = 0;
  clear_keybuf();

  /* enable Windows friendly timer mode (already default under Windows) */
  i_love_bill = TRUE;

  install_keyboard();
  install_timer();
  if (install_mouse() > 0)
    mouse_installed = TRUE;

  last_joystick_state = 0;
  joystick_event = FALSE;
}

static boolean allegro_init_audio()
{
  reserve_voices(NUM_MIXER_CHANNELS, 0);

  if (install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL) == -1)
    if (install_sound(DIGI_SB, MIDI_NONE, NULL) == -1)
      return FALSE;

  return TRUE;
}

static boolean hide_mouse(Display *display, int x, int y,
			  unsigned int width, unsigned int height)
{
  if (mouse_x + display->mouse_ptr->w < x || mouse_x > x + width)
    return FALSE;
  if (mouse_y + display->mouse_ptr->h < y || mouse_y > y + height)
    return FALSE;

  show_mouse(NULL);

  return TRUE;
}

static void unhide_mouse(Display *display)
{
  if (mouse_installed)
    show_mouse(video_bitmap);
}

static KeySym ScancodeToKeySym(byte scancode)
{
  switch(scancode)
  {
    case KEY_ESC:		return XK_Escape;
    case KEY_1:			return XK_1;
    case KEY_2:			return XK_2;
    case KEY_3:			return XK_3;
    case KEY_4:			return XK_4;
    case KEY_5:			return XK_5;
    case KEY_6:			return XK_6;
    case KEY_7:			return XK_7;
    case KEY_8:			return XK_8;
    case KEY_9:			return XK_9;
    case KEY_0:			return XK_0;
    case KEY_MINUS:		return XK_minus;
    case KEY_EQUALS:		return XK_equal;
    case KEY_BACKSPACE:		return XK_BackSpace;
    case KEY_TAB:		return XK_Tab;
    case KEY_Q:			return XK_q;
    case KEY_W:			return XK_w;
    case KEY_E:			return XK_e;
    case KEY_R:			return XK_r;
    case KEY_T:			return XK_t;
    case KEY_Y:			return XK_y;
    case KEY_U:			return XK_u;
    case KEY_I:			return XK_i;
    case KEY_O:			return XK_o;
    case KEY_P:			return XK_p;
    case KEY_OPENBRACE:		return XK_braceleft;
    case KEY_CLOSEBRACE:	return XK_braceright;
    case KEY_ENTER:		return XK_Return;
    case KEY_LCONTROL:		return XK_Control_L;
    case KEY_A:			return XK_a;
    case KEY_S:			return XK_s;
    case KEY_D:			return XK_d;
    case KEY_F:			return XK_f;
    case KEY_G:			return XK_g;
    case KEY_H:			return XK_h;
    case KEY_J:			return XK_j;
    case KEY_K:			return XK_k;
    case KEY_L:			return XK_l;
    case KEY_COLON:		return XK_colon;
    case KEY_QUOTE:		return XK_apostrophe;
    case KEY_TILDE:		return XK_asciitilde;
    case KEY_LSHIFT:		return XK_Shift_L;
    case KEY_BACKSLASH:		return XK_backslash;
    case KEY_Z:			return XK_z;
    case KEY_X:			return XK_x;
    case KEY_C:			return XK_c;
    case KEY_V:			return XK_v;
    case KEY_B:			return XK_b;
    case KEY_N:			return XK_n;
    case KEY_M:			return XK_m;
    case KEY_COMMA:		return XK_comma;
    case KEY_STOP:		return XK_period;
    case KEY_SLASH:		return XK_slash;
    case KEY_RSHIFT:		return XK_Shift_R;
    case KEY_ASTERISK:		return XK_KP_Multiply;
    case KEY_ALT:		return XK_Alt_L;
    case KEY_SPACE:		return XK_space;
    case KEY_CAPSLOCK:		return XK_Caps_Lock;
    case KEY_F1:		return XK_F1;
    case KEY_F2:		return XK_F2;
    case KEY_F3:		return XK_F3;
    case KEY_F4:		return XK_F4;
    case KEY_F5:		return XK_F5;
    case KEY_F6:		return XK_F6;
    case KEY_F7:		return XK_F7;
    case KEY_F8:		return XK_F8;
    case KEY_F9:		return XK_F9;
    case KEY_F10:		return XK_F10;
    case KEY_NUMLOCK:		return XK_Num_Lock;
    case KEY_SCRLOCK:		return XK_Scroll_Lock;
    case KEY_HOME:		return XK_Home;
    case KEY_UP:		return XK_Up;
    case KEY_PGUP:		return XK_Page_Up;
    case KEY_MINUS_PAD:		return XK_KP_Subtract;
    case KEY_LEFT:		return XK_Left;
    case KEY_5_PAD:		return XK_KP_5;
    case KEY_RIGHT:		return XK_Right;
    case KEY_PLUS_PAD:		return XK_KP_Add;
    case KEY_END:		return XK_End;
    case KEY_DOWN:		return XK_Down;
    case KEY_PGDN:		return XK_Page_Down;
    case KEY_INSERT:		return XK_Insert;
    case KEY_DEL:		return XK_Delete;
    case KEY_PRTSCR:		return XK_Print;
    case KEY_F11:		return XK_F11;
    case KEY_F12:		return XK_F12;
    case KEY_LWIN:		return XK_Meta_L;
    case KEY_RWIN:		return XK_Meta_R;
    case KEY_MENU:		return XK_Menu;
    case KEY_PAD:		return XK_VoidSymbol;
    case KEY_RCONTROL:		return XK_Control_R;
    case KEY_ALTGR:		return XK_Alt_R;
    case KEY_SLASH2:		return XK_KP_Divide;
    case KEY_PAUSE:		return XK_Pause;

    case NEW_KEY_BACKSLASH:	return XK_backslash;
    case NEW_KEY_1_PAD:		return XK_KP_1;
    case NEW_KEY_2_PAD:		return XK_KP_2;
    case NEW_KEY_3_PAD:		return XK_KP_3;
    case NEW_KEY_4_PAD:		return XK_KP_4;
    case NEW_KEY_5_PAD:		return XK_KP_5;
    case NEW_KEY_6_PAD:		return XK_KP_6;
    case NEW_KEY_7_PAD:		return XK_KP_7;
    case NEW_KEY_8_PAD:		return XK_KP_8;
    case NEW_KEY_9_PAD:		return XK_KP_9;
    case NEW_KEY_0_PAD:		return XK_KP_0;
    case NEW_KEY_STOP_PAD:	return XK_KP_Separator;
    case NEW_KEY_EQUALS_PAD:	return XK_KP_Equal;
    case NEW_KEY_SLASH_PAD:	return XK_KP_Divide;
    case NEW_KEY_ASTERISK_PAD:	return XK_KP_Multiply;
    case NEW_KEY_ENTER_PAD:	return XK_KP_Enter;

    default:			return XK_VoidSymbol;
  }
}

Pixel AllegroAllocColorCell(int r, int g, int b)
{
  byte pixel_mapping = 0;
  int i;

  r >>= 10;
  g >>= 10;
  b >>= 10;

  /* try to use existing colors from the global colormap */
  for (i = 0; i < global_colormap_entries_used; i++)
  {
    if (r == global_colormap[i].r &&
	g == global_colormap[i].g &&
	b == global_colormap[i].b)		/* color found */
    {
      pixel_mapping = i;
      break;
    }
  }

  if (i == global_colormap_entries_used)	/* color not found */
  {
    if (global_colormap_entries_used < MAX_COLORS)
      global_colormap_entries_used++;

    i = global_colormap_entries_used - 1;

    global_colormap[i].r = r;
    global_colormap[i].g = g;
    global_colormap[i].b = b;

    set_palette(global_colormap);

    pixel_mapping = i;
  }

  return pixel_mapping;
}

void XMapWindow(Display *display, Window window)
{
  int x, y;
  unsigned int width, height;
  boolean mouse_off;

  x = AllegroDefaultScreen().x;
  y = AllegroDefaultScreen().y;
  width = AllegroDefaultScreen().width;
  height = AllegroDefaultScreen().height;

  mouse_off = hide_mouse(display, x, y, width, height);
  blit((BITMAP *)window, video_bitmap, 0, 0, x, y, width, height);

  if (mouse_off)
    unhide_mouse(display);
}

Display *XOpenDisplay(char *display_name)
{
  Screen *screen;
  Display *display;
  BITMAP *mouse_bitmap = NULL;
  char *mouse_filename = getCustomImageFilename(program.msdos_cursor_filename);

  if ((mouse_bitmap = Read_PCX_to_AllegroBitmap(mouse_filename)) == NULL)
    return NULL;

  screen = malloc(sizeof(Screen));
  display = malloc(sizeof(Display));

  screen[0].cmap = 0;
  screen[0].root = 0;
  screen[0].white_pixel = AllegroAllocColorCell(0xFFFF, 0xFFFF, 0xFFFF);
  screen[0].black_pixel = AllegroAllocColorCell(0x0000, 0x0000, 0x0000);
  screen[0].video_bitmap = NULL;

  display->default_screen = 0;
  display->screens = screen;
  display->mouse_ptr = mouse_bitmap;

  allegro_init();
  allegro_init_drivers();
  set_color_depth(8);

  /* force Windows 95 to switch to fullscreen mode */
  set_gfx_mode(GFX_AUTODETECT, 320, 200, 0, 0);
  rest(200);
  set_gfx_mode(GFX_AUTODETECT, XRES, YRES, 0, 0);

  return display;
}

Window XCreateSimpleWindow(Display *display, Window parent, int x, int y,
			   unsigned int width, unsigned int height,
			   unsigned int border_width, unsigned long border,
			   unsigned long background)
{
  video_bitmap = create_video_bitmap(XRES, YRES);
  clear_to_color(video_bitmap, background);

  AllegroDefaultScreen().video_bitmap = video_bitmap;
  AllegroDefaultScreen().x = x;
  AllegroDefaultScreen().y = y;
  AllegroDefaultScreen().width = XRES;
  AllegroDefaultScreen().height = YRES;

  set_mouse_sprite(display->mouse_ptr);

#if 0
  set_mouse_sprite_focus(1, 1);
#endif

  set_mouse_speed(1, 1);
  set_mouse_range(AllegroDefaultScreen().x + 1,
		  AllegroDefaultScreen().y + 1,
		  AllegroDefaultScreen().x + video.width + 1,
		  AllegroDefaultScreen().y + video.height + 1);

  show_video_bitmap(video_bitmap);

  return (Window)video_bitmap;
}

Status XStringListToTextProperty(char **list, int count,
				 XTextProperty *text_prop_return)
{
  char *string;

  if (count >= 1)
  {
    string = malloc(strlen(list[0] + 1));
    strcpy(string, list[0]);
    text_prop_return->value = (unsigned char *)string;
    return 1;
  }
  else
    text_prop_return = NULL;

  return 0;
}

void XFree(void *data)
{
  checked_free(data);
}

GC XCreateGC(Display *display, Drawable d, unsigned long value_mask,
	     XGCValues *values)
{
  XGCValues *gcv;
  gcv = malloc(sizeof(XGCValues));
  gcv->foreground = values->foreground;
  gcv->background = values->background;
  gcv->graphics_exposures = values->graphics_exposures;
  gcv->clip_mask = values->clip_mask;
  gcv->clip_x_origin = values->clip_x_origin;
  gcv->clip_y_origin = values->clip_y_origin;
  gcv->value_mask = value_mask;
  return (GC)gcv;
}

void XSetClipMask(Display *display, GC gc, Pixmap pixmap)
{
  XGCValues *gcv = (XGCValues *)gc;

  gcv->clip_mask = pixmap;
  gcv->value_mask |= GCClipMask;
}

void XSetClipOrigin(Display *display, GC gc, int x, int y)
{
  XGCValues *gcv = (XGCValues *)gc;

  gcv->clip_x_origin = x;
  gcv->clip_x_origin = y;
}

void XFillRectangle(Display *display, Drawable d, GC gc, int x, int y,
		    unsigned int width, unsigned int height)
{
  boolean mouse_off = FALSE;

  if ((BITMAP *)d == video_bitmap)
  {
    x += AllegroDefaultScreen().x;
    y += AllegroDefaultScreen().y;
    freeze_mouse_flag = TRUE;
    mouse_off = hide_mouse(display, x, y, width, height);
  }

  rectfill((BITMAP *)d, x, y, x + width - 1, y + height - 1,
	   ((XGCValues *)gc)->foreground);

  if (mouse_off)
    unhide_mouse(display);

  freeze_mouse_flag = FALSE;
}

Pixmap XCreatePixmap(Display *display, Drawable d, unsigned int width,
		     unsigned int height, unsigned int depth)
{
  BITMAP *bitmap = NULL;

  if (gfx_capabilities & GFX_HW_VRAM_BLIT &&
      width == gfx.scrollbuffer_width && height == gfx.scrollbuffer_height)
    bitmap = create_video_bitmap(width, height);

  if (bitmap == NULL)
    bitmap = create_bitmap(width, height);

  return (Pixmap)bitmap;
}

void XSync(Display *display, Bool discard_events)
{
  wait_for_vsync = TRUE;
}

inline void XCopyArea(Display *display, Drawable src, Drawable dest, GC gc,
		      int src_x, int src_y,
		      unsigned int width, unsigned int height,
		      int dest_x, int dest_y)
{
  boolean mouse_off = FALSE;

  if ((BITMAP *)src == video_bitmap)
  {
    src_x += AllegroDefaultScreen().x;
    src_y += AllegroDefaultScreen().y;
  }

  if ((BITMAP *)dest == video_bitmap)
  {
    dest_x += AllegroDefaultScreen().x;
    dest_y += AllegroDefaultScreen().y;
    freeze_mouse_flag = TRUE;
    mouse_off = hide_mouse(display, dest_x, dest_y, width, height);
  }

  if (wait_for_vsync)
  {
    wait_for_vsync = FALSE;
    vsync();
  }

  if (((XGCValues *)gc)->value_mask & GCClipMask)
    masked_blit((BITMAP *)src, (BITMAP *)dest, src_x, src_y, dest_x, dest_y,
		width, height);
  else
    blit((BITMAP *)src, (BITMAP *)dest, src_x, src_y, dest_x, dest_y,
	 width, height);

  if (mouse_off)
    unhide_mouse(display);

  freeze_mouse_flag = FALSE;
}

static BITMAP *Image_to_AllegroBitmap(Image *image)
{
  BITMAP *bitmap;
  byte *src_ptr = image->data;
  byte pixel_mapping[MAX_COLORS];
  unsigned int depth = 8;
  int i, x, y;

  if (image->type == IMAGETYPE_TRUECOLOR && depth == 8)
    Error(ERR_EXIT, "cannot handle true-color images on 8-bit display");

  /* allocate new allegro bitmap structure */
  if ((bitmap = create_bitmap_ex(depth, image->width, image->height)) == NULL)
  {
    errno_pcx = PCX_NoMemory;
    return NULL;
  }

  clear(bitmap);

  /* try to use existing colors from the global colormap */
  for (i = 0; i < MAX_COLORS; i++)
  {
    if (!image->rgb.color_used[i])
      continue;

    pixel_mapping[i] = AllegroAllocColorCell(image->rgb.red[i],
					     image->rgb.green[i],
					     image->rgb.blue[i]);
  }

  /* copy bitmap data */
  for (y = 0; y < image->height; y++)
    for (x = 0; x < image->width; x++)
      putpixel(bitmap, x, y, pixel_mapping[*src_ptr++]);

  return bitmap;
}

static BITMAP *Read_PCX_to_AllegroBitmap(char *filename)
{
  BITMAP *bitmap;
  Image *image;

  /* read the graphic file in PCX format to internal image structure */
  if ((image = Read_PCX_to_Image(filename)) == NULL)
    return NULL;

  /* convert internal image structure to allegro bitmap structure */
  if ((bitmap = Image_to_AllegroBitmap(image)) == NULL)
    return NULL;

  set_palette(global_colormap);

  return bitmap;
}

int Read_PCX_to_Pixmap(Display *display, Window window, GC gc, char *filename,
		       Pixmap *pixmap, Pixmap *pixmap_mask)
{
  BITMAP *bitmap;

  if ((bitmap = Read_PCX_to_AllegroBitmap(filename)) == NULL)
    return errno_pcx;

  *pixmap = (Pixmap)bitmap;

  /* pixmap_mask will never be used in Allegro (which uses masked_blit()),
     so use non-NULL dummy pointer to empty Pixmap */
  *pixmap_mask = (Pixmap)DUMMY_MASK;

  return PCX_Success;
}

int XReadBitmapFile(Display *display, Drawable d, char *filename,
		    unsigned int *width_return, unsigned int *height_return,
		    Pixmap *bitmap_return,
		    int *x_hot_return, int *y_hot_return)
{
  BITMAP *bitmap;

  if ((bitmap = Read_PCX_to_AllegroBitmap(filename)) == NULL)
    return BitmapOpenFailed;

  *width_return = bitmap->w;
  *height_return = bitmap->h;
  *x_hot_return = -1;
  *y_hot_return = -1;
  *bitmap_return = (Pixmap)bitmap;

  return BitmapSuccess;
}

void XFreePixmap(Display *display, Pixmap pixmap)
{
  if (pixmap != DUMMY_MASK &&
      (is_memory_bitmap((BITMAP *)pixmap) ||
       is_screen_bitmap((BITMAP *)pixmap)))
    destroy_bitmap((BITMAP *)pixmap);
}

void XFreeGC(Display *display, GC gc)
{
  XGCValues *gcv = (XGCValues *)gc;

  checked_free(gcv);
}

void XUnmapWindow(Display *display, Window window)
{
}

void XCloseDisplay(Display *display)
{
  BITMAP *bitmap = video_bitmap;

  if (is_screen_bitmap(bitmap))
    destroy_bitmap(bitmap);

  checked_free(display->screens);
  checked_free(display);

  /* return to text mode (or DOS box on Windows screen) */
  set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
}

void XNextEvent(Display *display, XEvent *event_return)
{
  while (!pending_events)
    XPending(display);

  memcpy(event_return, &event_buffer[pending_events], sizeof(XEvent));
  pending_events--;
}

static void NewKeyEvent(int key_press_state, KeySym keysym)
{
  XKeyEvent *xkey;

  if (pending_events >= MAX_EVENT_BUFFER)
    return;

  pending_events++;
  xkey = (XKeyEvent *)&event_buffer[pending_events];
  xkey->type = key_press_state;
  xkey->state = (unsigned int)keysym;
}

#define HANDLE_RAW_KB_ALL_KEYS			0
#define HANDLE_RAW_KB_MODIFIER_KEYS_ONLY	1

static int modifier_scancode[] =
{
  KEY_LSHIFT,
  KEY_RSHIFT,
  KEY_LCONTROL,
  KEY_RCONTROL,
  KEY_ALT,
  KEY_ALTGR,
  KEY_LWIN,
  KEY_RWIN,
  KEY_CAPSLOCK,
  KEY_NUMLOCK,
  KEY_SCRLOCK,
  -1
};

static void HandleKeyboardRaw(int mode)
{
  int i;

  for (i = 0; i < MAX_SCANCODES; i++)
  {
    int scancode, new_state, event_type;
    char key_pressed;

    if (mode == HANDLE_RAW_KB_MODIFIER_KEYS_ONLY)
    {
      if ((scancode = modifier_scancode[i]) == -1)
	return;
    }
    else
      scancode = i;

    key_pressed = key[scancode];
    new_state = (key_pressed ? KeyPressMask : KeyReleaseMask);
    event_type = (key_pressed ? KeyPress : KeyRelease);

    if (key_press_state[i] == new_state)	/* state not changed */
      continue;

    key_press_state[i] = new_state;

    NewKeyEvent(event_type, ScancodeToKeySym(scancode));
  }
}

static void HandleKeyboardEvent()
{
  if (keypressed())
  {
    int key_info = readkey();
    int scancode = (key_info >> 8);
    int ascii = (key_info & 0xff);
    KeySym keysym = ScancodeToKeySym(scancode);

    if (scancode == KEY_PAD)
    {
      /* keys on the numeric keypad return just scancode 'KEY_PAD'
	 for some reason, so we must handle them separately */

      if (ascii >= '0' && ascii <= '9')
	keysym = XK_KP_0 + (KeySym)(ascii - '0');
      else if (ascii == '.')
	keysym = XK_KP_Separator;
    }
    else if (ascii >= ' ' && ascii <= 'Z')
      keysym = XK_space + (KeySym)(ascii - ' ');
    else if (ascii == '^')
      keysym = XK_asciicircum;
    else if (ascii == '_')
      keysym = XK_underscore;
    else if (ascii == 'Ä')
      keysym = XK_Adiaeresis;
    else if (ascii == 'Ö')
      keysym = XK_Odiaeresis;
    else if (ascii == 'Ü')
      keysym = XK_Udiaeresis;
    else if (ascii == 'ä')
      keysym = XK_adiaeresis;
    else if (ascii == 'ö')
      keysym = XK_odiaeresis;
    else if (ascii == 'ü')
      keysym = XK_udiaeresis;
    else if (ascii == 'ß')
      keysym = XK_ssharp;

    NewKeyEvent(KeyPress, keysym);
  }
  else if (key_shifts & (KB_SHIFT_FLAG | KB_CTRL_FLAG | KB_ALT_FLAG))
  {
    /* the allegro function keypressed() does not give us single pressed
       modifier keys, so we must detect them with the internal global
       allegro variable 'key_shifts' and then handle them separately */

    HandleKeyboardRaw(HANDLE_RAW_KB_MODIFIER_KEYS_ONLY);
  }
}

int XPending(Display *display)
{
  XButtonEvent *xbutton;
  XMotionEvent *xmotion;
  int i;

  /* When using 'HandleKeyboardRaw()', keyboard input is also stored in
     the allegro keyboard input buffer and would be available a second
     time by calling 'HandleKeyboardEvent()'. To avoid double keyboard
     events, the allegro function 'clear_keybuf()' must be called each
     time when switching from calling 'HandleKeyboardRaw()' to calling
     'HandleKeyboardEvent()' to get keyboard input, which is actually
     done by 'XAutoRepeatOn()' which sets keyboard_auto_repeat to TRUE. */

  /* keyboard event */
  if (keyboard_auto_repeat)
    HandleKeyboardEvent();
  else
    HandleKeyboardRaw(HANDLE_RAW_KB_ALL_KEYS);

  /* mouse motion event */
  if (mouse_pos != last_mouse_pos)
  {
    last_mouse_pos = mouse_pos;
    pending_events++;
    xmotion = (XMotionEvent *)&event_buffer[pending_events];
    xmotion->type = MotionNotify;
    xmotion->x = mouse_x - AllegroDefaultScreen().x;
    xmotion->y = mouse_y - AllegroDefaultScreen().y;
  }

  /* mouse button event */
  if (mouse_b != last_mouse_b)
  {
    for (i = 0; i < 3; i++)	/* check all three mouse buttons */
    {
      int bitmask = (1 << i);

      if ((last_mouse_b & bitmask) != (mouse_b & bitmask))
      {
	int mapping[3] = { 1, 3, 2 };

	pending_events++;
        xbutton = (XButtonEvent *)&event_buffer[pending_events];
        xbutton->type = (mouse_b & bitmask ? ButtonPress : ButtonRelease);
        xbutton->button = mapping[i];
	xbutton->x = mouse_x - AllegroDefaultScreen().x;
	xbutton->y = mouse_y - AllegroDefaultScreen().y;
      }
    }
    last_mouse_b = mouse_b;
  }

  return pending_events;
}

KeySym XLookupKeysym(XKeyEvent *key_event, int index)
{
  return key_event->state;
}

int XLookupString(XKeyEvent *key_event, char *buffer, int buffer_size,
		  KeySym *key, XComposeStatus *compose)
{
  *key = key_event->state;
  return 0;
}

void XSetForeground(Display *display, GC gc, unsigned long pixel)
{
  XGCValues *gcv = (XGCValues *)gc;

  gcv->foreground = pixel;
}

void XDrawLine(Display *display, Drawable d, GC gc,
	       int x1, int y1, int x2, int y2)
{
  XGCValues *gcv = (XGCValues *)gc;
  boolean mouse_off = FALSE;

  if ((BITMAP *)d == video_bitmap)
  {
    x1 += AllegroDefaultScreen().x;
    y1 += AllegroDefaultScreen().y;
    x2 += AllegroDefaultScreen().x;
    y2 += AllegroDefaultScreen().y;
    freeze_mouse_flag = TRUE;
    mouse_off = hide_mouse(display, MIN(x1, x2), MIN(y1, y2),
			   MAX(x1, x2) - MIN(x1, x2),
			   MAX(y1, y2) - MIN(y1, y2));
  }

  line((BITMAP *)d, x1, y1, x2, y2, gcv->foreground);

  if (mouse_off)
    unhide_mouse(display);

  freeze_mouse_flag = FALSE;
}

void XDestroyImage(XImage *ximage)
{
}

void XDestroyWindow(Display *display, Window window)
{
}

Bool XQueryPointer(Display *display, Window window,
		   Window *root, Window *child, int *root_x, int *root_y,
		   int *win_x, int *win_y, unsigned int *mask)
{
  *win_x = mouse_x - AllegroDefaultScreen().x;
  *win_y = mouse_y - AllegroDefaultScreen().y;

  return True;
}

void XAutoRepeatOn(Display *display)
{
  keyboard_auto_repeat = TRUE;
  clear_keybuf();
}

void XAutoRepeatOff(Display *display)
{
  keyboard_auto_repeat = FALSE;
}

void AllegroDrawLine(Drawable d, int from_x, int from_y, int to_x, int to_y,
		     Pixel color)
{
  boolean mouse_off = FALSE;

  if ((BITMAP *)d == video_bitmap)
  {
    int dx = AllegroDefaultScreen().x;
    int dy = AllegroDefaultScreen().y;
    int x1, y1, x2, y2;

    from_x += dx;
    from_y += dy;
    to_x += dx;
    to_y += dy;

    x1 = (from_x < to_x ? from_x : to_x);
    y1 = (from_y < to_y ? from_y : to_y);
    x2 = (from_x < to_x ? to_x : from_x);
    y2 = (from_y < to_y ? to_y : from_y);

    freeze_mouse_flag = TRUE;
    mouse_off = hide_mouse(display, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
  }

  line((BITMAP *)d, from_x, from_y, to_x, to_y, color);

  if (mouse_off)
    unhide_mouse(display);

  freeze_mouse_flag = FALSE;
}

Pixel AllegroGetPixel(Drawable d, int x, int y)
{
  return getpixel((BITMAP *)d, x, y);
}

void AllegroZoomBitmap(Drawable src, Drawable dst,
		       int src_width, int src_height,
		       int dst_width, int dst_height)
{
  stretch_blit((BITMAP *)src, (BITMAP *)dst,
	       0, 0, src_width, src_height, 0, 0, dst_width, dst_height);
}

void MSDOSOpenAudio(void)
{
  if (allegro_init_audio())
  {
    audio.sound_available = TRUE;
    audio.music_available = TRUE;
    audio.loops_available = TRUE;
    audio.sound_enabled = TRUE;

    audio.num_channels = NUM_MIXER_CHANNELS;
    audio.music_channel = MUSIC_CHANNEL;
    audio.first_sound_channel = FIRST_SOUND_CHANNEL;

    Mixer_InitChannels();
  }
}

void MSDOSCloseAudio(void)
{
  /* nothing to be done here */
}

void NetworkServer(int port, int serveronly)
{
  Error(ERR_WARN, "networking not supported in DOS version");
}


/* ========================================================================= */
/* joystick functions                                                        */
/* ========================================================================= */

void MSDOSInitJoysticks()
{
  int i;

  /* start from scratch */
  remove_joystick();

  /* try to access two joysticks; if that fails, try to access just one */
  if (install_joystick(JOY_TYPE_2PADS) == 0 ||
      install_joystick(JOY_TYPE_AUTODETECT) == 0)
    joystick.status = JOYSTICK_ACTIVATED;

  for (i = 0; i < MAX_PLAYERS; i++)
  {
    char *device_name = setup.input[i].joy.device_name;
    int joystick_nr = getJoystickNrFromDeviceName(device_name);

    if (joystick_nr >= num_joysticks)
      joystick_nr = -1;

    /* misuse joystick file descriptor variable to store joystick number */
    joystick.fd[i] = joystick_nr;
  }
}

boolean MSDOSReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
{
  /* the allegro global variable 'num_joysticks' contains the number
     of joysticks found at initialization under MS-DOS / Windows */

  if (nr < 0 || nr >= num_joysticks)
    return FALSE;

  poll_joystick();

  if (x != NULL)
    *x = joy[nr].stick[0].axis[0].pos;
  if (y != NULL)
    *y = joy[nr].stick[0].axis[1].pos;

  if (b1 != NULL)
    *b1 = joy[nr].button[0].b;
  if (b2 != NULL)
    *b2 = joy[nr].button[1].b;

  return TRUE;
}

#endif /* PLATFORM_MSDOS */
