/* pmemacs.c -- OS/2 Presentation Manager interface for GNU Emacs.
   Copyright (C) 1993 Eberhard Mattes.

This file is part of GNU Emacs.

GNU Emacs 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, or (at your option)
any later version.

GNU Emacs 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 GNU Emacs; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#define INCL_DOS
#define INCL_WIN
#define INCL_GPI
#define INCL_DEV
#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include "pmemacs.h"

#define HASH_SIZE 53

#define UWM_CREATE        (WM_USER+1000) /* Create a window */
#define UWM_DESTROY       (WM_USER+1001) /* Destroy a window */
#define UWM_POPUPMENU     (WM_USER+1002) /* Create a popup menu */
#define UWM_MENUBAR_TOP   (WM_USER+1003) /* Create top level of menubar */
#define UWM_MENUBAR_MENU  (WM_USER+1004) /* Create a menu of the menubar */
#define UWM_MENUBAR_DONE  (WM_USER+1005) /* Menubar item updated */

/* See termhooks.h */

#define UP_MODIFIER             1
#define DOWN_MODIFIER           2
#define ALT_MODIFIER            0x040000
#define SUPER_MODIFIER          0x080000
#define HYPER_MODIFIER          0x100000
#define SHIFT_MODIFIER          0x200000
#define CTRL_MODIFIER           0x400000
#define META_MODIFIER           0x800000

#define VK_CLOSE_FRAME          0x39 /* See keyboard.c */
#define VK_CENTER               0x3a /* See keyboard.c */
#define VK_MENU_BAR             0x3b /* See keyboard.c */

#define KC_ALTGR                0x10000

#define IDM_POPUP               1
#define IDM_MENUBAR             1000

/* The font_spec structure contains a font specification derived from
   a font name string. */
typedef struct
{
  /* The size of the font, in printer's points multipled by 10. */
  int size;

  /* Font selection: FATTR_SEL_UNDERSCORE etc. */
  int sel;

  /* The face name of the font. */
  char name[FACESIZE];
} font_spec;

/* The face_data structure defines a face (a font with attributes). */
typedef struct
{
  COLOR foreground;
  COLOR background;
  int font_id;                  /* Zero if no font associated */
  font_spec spec;
} face_data;

/* The font_data structure defines a font. */
typedef struct
{
  /* Face name, size and selection of the font. */
  font_spec spec;

  /* This flag is non-zero if an outline font is selected. */
  char outline;

  /* This flag is non-zero if the width of the font is wrong or if the
     font is an outline font -- we have to use GpiCharStringPosAt
     instead of GpiCharStringAt in that case. */
  char use_incr;
} font_data;


/* The frame_data structure holds all the data for a frame.  A pointer
   to this structure is stored at position 0 of the client window data
   area. */
typedef struct frame_data
{

  /* This field is used for chaining frames that share the same hash
     code. */
  struct frame_data *next;

  /* The width and height of the client window. */
  LONG cxClient, cyClient;

  /* Some font metrics: the character width, character height and
     descender height of the font.  All values are given in pixels. */
  LONG cxChar, cyChar, cyDesc;

  /* The frame window handle. */
  HWND hwndFrame;

  /* The client window handle.  The client window is a child window of
     the frame window HWNDFRAME. */
  HWND hwndClient;

  /* This presentation space handle is used for asynchronous painting
     of the client window. */
  HPS hpsClient;

  /* The background color of the default face of this frame, */
  COLOR background_color;

  /* The position of the cursor, in characters. */
  int cursor_x, cursor_y;

  /* This field is non-zero if the cursor should be visible. */
  int cursor_on;

  /* The type of the cursor, one of the CURSORTYPE_whatever constants. */
  int cursor_type;

  /* Whether the cursor is blinking or not. */
  int cursor_blink;

  /* The width and height, respectively, in characters of this frame. */
  int width, height;

  /* The lower 3 bits of this field are set if WM_BUTTONxUP should be
     ignored until WM_BUTTONxDOWN is received.  This field is set to 7
     (all three bits set) when a button message is received while the
     window doesn't have the focus.  A bit is cleared if a
     WM_BUTTONxDOWN message is received.  Bit 0 is used for button 1,
     bit 1 is used for button 2, and bit 2 is used for button 3. */
  int ignore_button_up;

  /* The identity of the frame.  In Emacs, this is the frame pointer.
     As we do hashing on this value, it is cast to unsigned long in
     pmterm.c before passing to this module. */
  ULONG id;

  /* If there is a popup menu, this field holds the handle for that
     menu.  Otherwise, it is NULL. */
  HWND hwndPopupMenu;

  /* This is the range of valid identities for the currently displayed
     popup menu. */
  int popup_menu_min, popup_menu_max;

  /* This is the list of Lisp_Objects associated with the items of the
     currently displayed popup menu. */
  ULONG *popup_menu_items;

  /* If there is a menubar, this field holds the handle for that
     menu.  Otherwise, it is NULL. */
  HWND hwndMenubar;

  /* This is the range of valid identities for the current menubar. */
  int menubar_min, menubar_max;

  /* This is the list of Lisp_Objects associated with the items of the
     current menubar. */
  ULONG *menubar_items;

  /* This field is non-zero if there is a menubar. */
  int menubar_flag;

  /* This is the default font of this frame. */
  font_spec font;

  /* This flag is non-zero when the window is in the process of being
     minimized.  It is set when a WM_MINMAXFRAME sent to the frame
     window indicates that the window is being minimized.  The flag is
     used when processing the WM_SIZE message: if it is set, WM_SIZE
     is ignored and the flag is cleared. */
  int minimizing;

  /* This flag is non-zero if the window is minimized. */
  int minimized;

  /* Ignore WM_SIZE if this flag is non-zero. */
  int ignore_wm_size;

  /* The Emacs modifier bit to be sent if the left Alt key is
     pressed. */
  int alt_modifier;

  /* The Emacs modifier bit to be sent if the right Alt key (AltGr on
     most non-US keyboards) is pressed. */
  int altgr_modifier;

  /* The most recently pressed deadkey.  When receiving an invalid
     composition message, both the deadkey and the next key are sent
     to Emacs.  If this field is zero, no deadkey has been pressed
     recently. */
  int deadkey;

  /* If this field is TRUE, we disable the PM-defined shortcuts
     (accelerators) such as F1, F10, Alt+F4. */
  int disable_shortcuts;

  /* This array maps OS/2 mouse button numbers to Emacs mouse button
     numbers.  The values are either 0 (if the button is to be
     ignored) or are in 1 through 3. */
  char buttons[3];

  /* This array contains non-zero values for all logical fonts defined
     for this frame. */
  char font_defined[255];

  /* List of character increments, based on the default font.  This is
     used to override non-integer character increments when using an
     outline font and to override wrong character increments when
     using a font of a size differing from the size of the default
     font. */
  LONG increments[512];

  /* Current state of various PM attributes.  This is used to speed up
     text output by avoiding GPI calls. */
  COLOR cur_color, cur_backcolor;
  LONG cur_charset, cur_backmix;

} frame_data;


typedef struct
{
  int panes, lines, x, y, title_size, button;
  char *data;
} menu_data;

typedef struct
{
  int entries;
  char *data;
} menubar_data;


/* The original window procedure of frame windows.  Frame windows are
   subclassed by FrameWndProc, which calls this window procedure. */
static PFNWP old_frame_proc = NULL;

/* The anchor-block handle.  It is shared by all threads.  This should
   be fixed, each thread that has a message queue should have its own
   anchor-block handle.  Currently, OS/2 ignores the anchor-block
   handle. */
static HAB hab;

/* The message queue handle of the PM thread. */
static HMQ hmq;

/* The size of the screen. */
static SWP swpScreen;

/* Requests from Emacs arrive in this pipe. */
static int inbound_pipe;

/* Events are sent through this pipe to Emacs. */
static int outbound_pipe;

/* Out-of-band data is sent through this pipe to Emacs. */
static int oob_pipe;

/* The handle of the object window of the PM thread.  See
   ObjectWndProc() for details. */
static HWND hwndObject;

/* This hash table is used for finding the frame_data structure for a
   frame ID. */
static frame_data *hash_table[HASH_SIZE];

/* Pointer to frame_data structure used while creating a new frame.
   This is dirty. */
static frame_data *new_frame;

/* Window class names. */
static const char szFrameClass[] = "pmemacs.frame";
static const char szClientClass[] = "pmemacs.child";
static const char szObjectClass[] = "pmemacs.object";

/* ID for next menu entry. */
static int menu_id;

/* Where to store the next menu event. */
static ULONG *menu_items;

/* The window handle of the current menubar submenu. */
static HWND hwndMenubarSubmenu = NULLHANDLE;

/* This is the height of a menubar, in pixels. */
static LONG cyMenu;

/* Post this event semaphore to beep. */
static HEV hevBeep;

/* The process ID of Emacs proper.  This is taken from the command
   line.  We cannot use getppid(), as PMSHELL is the parent process of
   all PM sessions. */
static int emacs_pid;

/* The quit character.  If this character is typed, send SIGINT to
   Emacs instead of a keyboard event.  The default is C-G. */
static int quit_char = 0x07;

/* This variable is TRUE after receiving a PMR_CLOSE request.  This is
   for avoiding errors due to broken pipes etc. */
static int terminating = FALSE;

/* The table of faces. */
static face_data *face_vector = NULL;
static int nfaces = 0;
static int nfaces_allocated = 0;

/* The table of fonts.  Local font IDs are shared by all frames to
   simplify things.  The Presentation Manager supports local font IDs
   1 through 254 and 0 (the default font). */
static font_data font_vector[255];
static int nfonts = 0;

/* If this variable is non-zero, send PME_MOUSEMOVE events for
   WM_MOUSEMOVE messages. */
static int track_mouse = FALSE;

/* The glyph coordinates of the previous PME_MOUSEMOVE event. */
static int track_x = -1;
static int track_y = -1;

/* Prototypes for functions which are used before defined. */

static void terminate_process (void);
static void delete_menu_items (HWND hwndMenu);


/* An error occured.  Display MSG in a message box, then quit. */

static void error (const char *msg)
{
  WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, msg, "PM Emacs error", 0,
                 MB_MOVEABLE | MB_OK | MB_ICONEXCLAMATION);
  /*...*/
  DosExit (EXIT_PROCESS, 1);
}


/* Display a message.  This is used for debugging. */

static void message (HWND hwndOwner, const char *fmt, ...)
{
  va_list arg_ptr;
  char tmp[200];

  va_start (arg_ptr, fmt);
  vsprintf (tmp, fmt, arg_ptr);
  WinMessageBox (HWND_DESKTOP, hwndOwner, tmp, "PM Emacs", 0,
                 MB_MOVEABLE | MB_OK | MB_ICONEXCLAMATION);
}

/* Allocate memory.  Note that DosAllocMem fails if SIZE is 0. */

static void *allocate (ULONG size)
{
  ULONG rc;
  void *p;

  if (size == 0)
    return (NULL);
  rc = DosAllocMem ((PVOID)&p, size, PAG_COMMIT | PAG_READ | PAG_WRITE);
  if (rc != 0)
    error ("Out of memory.");
  return (p);
}


/* Deallocate memory allocated by allocate(). */

static void deallocate (void *p)
{
  if (p != NULL)
    DosFreeMem (p);
}


/* Send data to Emacs.
   Note: This should abort more gracefully on failure. */

static void send (int fd, const void *src, size_t size)
{
  const char *s;
  ULONG n;

  if (terminating)
    return;
  s = src;
  while (size != 0)
    {
      if (DosWrite (fd, s, size, &n) != 0 || n == 0)
        error ("Cannot write to pipe");
      size -= n;
      s += n;
    }
}


/* Send an event to Emacs. */

static void send_event (const pm_event *src)
{
  send (outbound_pipe, src, sizeof (pm_event));
}

/* Send out-of-band data to Emacs. */

static void send_oob (const void *src, unsigned size)
{
  send (oob_pipe, src, size);
}


/* Clear all fonts of all the faces and reset the font IDs in the face
   table. */

static void clear_fonts (void)
{
  frame_data *f;
  int i;

  for (i = 1; i <= nfaces; ++i)
    face_vector[i].font_id = 0;
  for (i = 0; i < HASH_SIZE; ++i)
    for (f = hash_table[i]; f != NULL; f = f->next)
      memset (f->font_defined, 0, sizeof (f->font_defined));
  nfonts = 0;
}


/* Compare two font specifications. */

static int equal_font_spec (const font_spec *f1, const font_spec *f2)
{
  return (f1->size == f2->size
          && f1->sel == f2->sel
          && strcmp (f1->name, f2->name) == 0);
}


/* Parse a font string into a font specification. */

#define SKIP_FIELD while (*str != 0 && *str != '-') ++str; \
		   if (*str == '-') ++str

static int parse_font_spec (font_spec *dst, const unsigned char *str)
{
  int i;

  dst->size = 0;
  dst->sel = 0;
  dst->name[0] = 0;
  if (str[0] == '-')
    {
      ++str;
      SKIP_FIELD;               /* Foundry */
      i = 0;
      while (*str != 0 && *str != '-')
        {
          if (i < sizeof (dst->name) - 1)
            dst->name[i++] = *str;
          ++str;
        }
      dst->name[i] = 0;
      SKIP_FIELD;               /* Family */
      if (*str == 'b')
        dst->sel |= FATTR_SEL_BOLD;
      SKIP_FIELD;               /* Weight */
      if (*str == 'i')
        dst->sel |= FATTR_SEL_ITALIC;
      SKIP_FIELD;               /* Slant */
      SKIP_FIELD;               /* Width */
      SKIP_FIELD;               /* Additional style */
      SKIP_FIELD;               /* Pixel size */
      i = 0;
      while (*str >= '0' && *str <= '9')
        {
          i = i * 10 + (*str - '0');
          ++str;
        }
      if (i == 0)
        return (FALSE);
      dst->size = i;
      SKIP_FIELD;               /* Point size */
      SKIP_FIELD;               /* X resolution */
      SKIP_FIELD;               /* Y resolution */
      SKIP_FIELD;               /* Spacing */
      SKIP_FIELD;               /* Registry */
      return (TRUE);
    }
  else
    {
      while (*str >= '0' && *str <= '9')
        {
          dst->size = dst->size * 10 + (*str - '0');
          ++str;
        }
      dst->size *= 10;
      if (dst->size == 0)
        return (FALSE);
      for (;;)
        {
          if (strncmp (str, ".bold", 5) == 0)
            {
              dst->sel |= FATTR_SEL_BOLD;
              str += 5;
            }
          else if (strncmp (str, ".italic", 7) == 0)
            {
              dst->sel |= FATTR_SEL_ITALIC;
              str += 7;
            }
          else if (*str == '.')
            {
              ++str;
              break;
            }
          else
            return (FALSE);
        }
      if (*str == 0)
        return (FALSE);
      _strncpy (dst->name, str, sizeof (dst->name));
    }
  return (TRUE);
}


/* Create a logical font (using ID) matching PFM.  SELECTION contains
   font selection bits such as FATTR_SEL_UNDERSCORE. */

static int use_font (frame_data *f, int id, PFONTMETRICS pfm, int selection)
{
  FATTRS font_attrs;
  FONTMETRICS fm;

  font_attrs.usRecordLength = sizeof (font_attrs);
  font_attrs.fsSelection = selection;
  font_attrs.lMatch = pfm->lMatch;
  strcpy (font_attrs.szFacename, pfm->szFacename);
  font_attrs.idRegistry = 0;
  font_attrs.usCodePage = 0;
  font_attrs.lMaxBaselineExt = 0;
  font_attrs.lAveCharWidth = 0;
  font_attrs.fsType = 0;
  font_attrs.fsFontUse = 0;
  if (GpiCreateLogFont (f->hpsClient, NULL, id, &font_attrs) == GPI_ERROR)
    return (FALSE);
  GpiSetCharSet (f->hpsClient, id);
  f->cur_charset = id;
  return (TRUE);
}


/* Select a font matching FONT_NAME and FONT_SIZE (10 * pt).  If there
   is such a font, return TRUE.  Otherwise, no font is selected and
   FALSE is returned. */

static int select_font_internal (frame_data *f, int id, char *font_name,
                                 int font_size, int selection,
                                 font_data *font)
{
  ULONG fonts, temp, i;
  PFONTMETRICS pfm;

  temp = 0;
  fonts = GpiQueryFonts (f->hpsClient, QF_PUBLIC | QF_PRIVATE, font_name,
                         &temp, sizeof (FONTMETRICS), NULL);
  pfm = allocate (fonts * sizeof (FONTMETRICS));
  GpiQueryFonts (f->hpsClient, QF_PUBLIC | QF_PRIVATE, font_name,
                 &fonts, sizeof (FONTMETRICS), pfm);
  for (i = 0; i < fonts; ++i)
    if ((pfm[i].fsType & FM_TYPE_FIXED)
        && !(pfm[i].fsDefn & FM_DEFN_OUTLINE)
        && pfm[i].sNominalPointSize == font_size
        && use_font (f, id, pfm+i, selection))
      {
        font->outline = FALSE;
        font->use_incr = (selection != f->font.sel
                          || pfm[i].lMaxCharInc != f->cxChar
                          || pfm[i].lMaxBaselineExt != f->cyChar
                          || pfm[i].lMaxDescender != f->cyDesc);
        deallocate (pfm);
        return (TRUE);
      }
  for (i = 0; i < fonts; ++i)
    if ((pfm[i].fsType & FM_TYPE_FIXED)
        && (pfm[i].fsDefn & FM_DEFN_OUTLINE)
        && use_font (f, id, pfm+i, selection))
      {
        font->outline = TRUE;
        font->use_incr = TRUE;
        deallocate (pfm);
        return (TRUE);
      }
  deallocate (pfm);
  return (FALSE);
}


/* Select a font matching FONT_NAME and FONT_SIZE (10 * pt).  If this
   fails, use the default font of the frame.  If there is such a font,
   return TRUE.  Otherwise, no font is selected and FALSE is
   returned. */

static int select_font (frame_data *f, int id, char *font_name,
                        int font_size, int selection, font_data *font)
{
  int ok;

  ok = select_font_internal (f, id, font_name, font_size, selection, font);
  if (!ok)
    ok = select_font_internal (f, id, f->font.name, f->font.size, selection,
                               font);
  return (ok);
}


/* Create a font for a face. */

static int make_font (frame_data *f, face_data *face)
{
  int i;
  font_data *font;
  font_spec spec;

  spec = face->spec;
  if (spec.size == 0)
    spec.size = f->font.size;
  if (spec.name[0] == 0)
    strcpy (spec.name, f->font.name);
  if (face->font_id == 0)
    for (i = 1; i <= nfonts; ++i)
      if (equal_font_spec (&font_vector[i].spec, &spec))
        {
          face->font_id = i;
          break;
        }
  if (face->font_id == 0)
    {
      if (nfonts >= 254)
        face->font_id = 1;
      else
        {
          /* Note: Font ID 0 is not used, it means no font ID */
          face->font_id = ++nfonts;
          font_vector[face->font_id].spec = spec;
        }
    }
  font = &font_vector[face->font_id];
  select_font (f, face->font_id, font->spec.name, font->spec.size,
               spec.sel, font);
  f->font_defined[face->font_id] = 1;
  return (face->font_id);
}


/* Set the default font of the frame F, according to the font_name and
   font_size fields of *F. */

static void set_default_font (frame_data *f)
{
  FONTMETRICS fm;
  SIZEF cbox;
  HDC hdc;
  LONG xRes;
  USHORT size;
  int i;
  font_data font;

  clear_fonts ();
  if (!select_font_internal (f, 1, f->font.name, f->font.size, 0, &font))
    {
      parse_font_spec (&f->font, "10.Courier");
      select_font_internal (f, 1, f->font.name, f->font.size, 0, &font);
    }
  if (font.outline)
    {
      hdc = GpiQueryDevice (f->hpsClient);
      DevQueryCaps (hdc, CAPS_HORIZONTAL_FONT_RES, 1, &xRes);
      size = (USHORT)((double)f->font.size * (double)xRes / 7.2 + 0.5);
      cbox.cx = MAKEFIXED (size, 0);
      cbox.cy = MAKEFIXED (size, 0);
      GpiSetCharBox (f->hpsClient, &cbox);
    }
  GpiQueryFontMetrics (f->hpsClient, (LONG)sizeof (fm), &fm);
  f->cxChar = fm.lMaxCharInc;
  f->cyChar = fm.lMaxBaselineExt;
  f->cyDesc = fm.lMaxDescender;
  for (i = 0; i < 512; ++i)
    f->increments[i] = fm.lMaxCharInc;
}


/* Change the size of a frame.  The new size is taken from the WIDTH
   and HEIGHT fields of the frame data structure.  This function is
   also called to resize the PM window if the font has changed.  Raise
   the window to the top and keep the lower left-hand corner in place
   (instead of the upper left-hand corner) if FIRST is non-zero. */

static void set_size (frame_data *f, int first)
{
  RECTL rcl;
  SWP swp;
  POINTL ptl;
  ULONG options;

  options = SWP_SIZE | SWP_SHOW;
  if (first)
    options |= SWP_ZORDER;
  else
    options |= SWP_MOVE;

  /* Compute the rectangle of the frame window from the current
     position and the new size of the client window.  The upper left
     corner of the window is kept in place unless FIRST is non-zero
     (this is achieved by not setting SWP_MOVE). */

  WinQueryWindowPos (f->hwndClient, &swp);
  ptl.x = swp.x;                /* Upper left-hand corner */
  ptl.y = swp.y + swp.cy;
  WinMapWindowPoints (f->hwndFrame, HWND_DESKTOP, &ptl, 1);
  rcl.xLeft = ptl.x; rcl.xRight = rcl.xLeft + f->width * f->cxChar;
  rcl.yTop = ptl.y; rcl.yBottom = rcl.yTop - f->height * f->cyChar;
  WinCalcFrameRect (f->hwndFrame, &rcl, FALSE);

  /* When keeping the lower left-hand corner in place it might happen
     that the titlebar is moved out of the screen.  If this happens,
     move the window to keep the titlebar visible. */

  if (rcl.yTop > swpScreen.cy)
    {
      LONG offset = rcl.yTop - swpScreen.cy;
      rcl.yBottom -= offset;
      rcl.yTop -= offset;
      options |= SWP_MOVE;
    }

  /* Now resize the window.  If FIRST is non-zero, the Z-order of the
     window is also changed to raise the window to the top.
     Otherwise, the window is moved to keep the upper left-hand corner
     in place. */

  WinSetWindowPos (f->hwndFrame, HWND_TOP, rcl.xLeft, rcl.yBottom,
                   rcl.xRight - rcl.xLeft, rcl.yTop - rcl.yBottom, options);
}


/* Translate PM shift key bits to Emacs modifier bits. */

static int convert_modifiers (frame_data *f, int shift)
{
  int result;

  result = 0;
  if (shift & KC_ALT)
    result |= f->alt_modifier;
  if (shift & KC_CTRL)
    result |= CTRL_MODIFIER;
  if (shift & KC_SHIFT)
    result |= SHIFT_MODIFIER;
  if (shift & KC_ALTGR)         /* KC_ALTGR is a mock modifier */
    result |= f->altgr_modifier;
  return (result);
}


/* Send a keyboard event to Emacs.  F is the frame to which the event
   is sent, TYPE is the event subtype (PMK_ASCII or PMK_VIRTUAL).
   CODE is the character code, virtual key code or the symbol.
   MODIFIERS contains PM keyboard status bits for the shift keys, REP
   is the repeat count.  If the key matches quit_char, a SIGINT signal
   is sent instead of a keyboard event. */

static void send_key (frame_data *f, pm_key_type type, int code, int modifiers,
                      int rep)
{
  pm_event pme;
  int i;

  if (type == PMK_ASCII && code == quit_char && modifiers == 0)
    kill (emacs_pid, SIGINT);
  else
    {
      pme.key.header.type = PME_KEY;
      pme.key.header.frame = f->id;
      pme.key.type = type;
      pme.key.code = code;
      pme.key.modifiers = convert_modifiers (f, modifiers);
      for (i = 0; i < rep; ++i)
        send_event (&pme);
    }
}


/* Create a cursor for frame F, according to the cursor_type and
   cursor_blink fields of *F. */

static void create_cursor (frame_data *f)
{
  LONG cx, cy;
  ULONG flags;

  if (f->cxChar != 0 && f->cyChar != 0)
    {
      cx = f->cxChar;
      cy = f->cyChar;
      flags = CURSOR_SOLID;
      switch (f->cursor_type)
        {
        case CURSORTYPE_BAR:
          cx = 0;
          break;
        case CURSORTYPE_FRAME:
          flags = CURSOR_FRAME;
          break;
        case CURSORTYPE_UNDERLINE:
          cy = 0;
          break;
        case CURSORTYPE_HALFTONE:
          flags = CURSOR_HALFTONE;
          break;
        case CURSORTYPE_BOX:
          break;
        }
      if (f->cursor_blink)
        flags |= CURSOR_FLASH;
      WinCreateCursor (f->hwndClient, f->cursor_x, f->cursor_y, cx, cy,
                       flags, NULL);
      if (f->cursor_on)
        WinShowCursor (f->hwndClient, TRUE);
    }
}


/* Create a new cursor for the frame F if F has the input focus. */

static void set_cursor (frame_data *f)
{
  if (f->hwndClient == WinQueryFocus (HWND_DESKTOP))
    {
      WinDestroyCursor (f->hwndClient);
      create_cursor (f);
    }
}


/* Create a menu and return the window handle.  HWNDOWNER will be the
   owner of the menu.  ID is the window ID. */

static HWND create_menu_handle (HWND hwndOwner, ULONG id)
{
  return (WinCreateWindow (hwndOwner, WC_MENU, "", 0, 0, 0, 0, 0,
                           hwndOwner, HWND_TOP, id, NULL, NULL));
}

/* Create a pane (submenu) of a menu.  If MENUBAR is TRUE, three
   entries are made in the menu_items array.  Otherwise, one entry is
   made. */

static void create_pane (HWND hwndMenu, char **ptr,
                         char *title, int title_size, int menubar)
{
  MENUITEM mi;
  pm_menu_pane pmp;
  pm_menu_line pml;
  char buf[512], *p;
  int j, len;

  p = *ptr;
  memcpy (&pmp, p, sizeof (pmp)); p += sizeof (pmp);
  len = pmp.size;
  if (title != NULL && title_size > 0)
    {
      if (len >= title_size) len = title_size - 1;
      memcpy (title, p, len);
      title[len] = 0;
    }
  p += pmp.size;
  for (j = 0; j < pmp.lines; ++j)
    {
      memcpy (&pml, p, sizeof (pml)); p += sizeof (pml);
      len = pml.size;
      if (len >= sizeof (buf)) len = sizeof (buf) - 1;
      memcpy (buf, p, len); p += pml.size;
      buf[len] = 0;
      *menu_items++ = pml.item1;
      if (menubar)
        {
          *menu_items++ = pml.item2;
          *menu_items++ = pml.item3;
        }
      mi.iPosition = MIT_END;
      mi.afStyle = MIS_TEXT;
      mi.afAttribute = (pml.enable ? 0 : MIA_DISABLED);
      mi.hwndSubMenu = NULL;
      mi.hItem = 0;
      mi.id = menu_id++;
      WinSendMsg (hwndMenu, MM_INSERTITEM, MPFROMP (&mi), MPFROMP (buf));
    }
  *ptr = p;
}


/* Create a menu, adding panes (submenus) to HWNDMENU.  The owner of
   submenus will be HWNDOWNER.  MENUBAR is passed on to
   create_pane(). */

static void create_menu (HWND hwndMenu, HWND hwndOwner, int panes,
                         char **p, int menubar)
{
  MENUITEM mi;
  char buf[512];
  int i;

  if (panes == 1)
    create_pane (hwndMenu, p, NULL, 0, menubar);
  else
    for (i = 0; i < panes; ++i)
      {
        if (((pm_menu_pane *)*p)->size == 0)
          create_pane (hwndMenu, p, NULL, 0, menubar);
        else
          {
            mi.hwndSubMenu = create_menu_handle (hwndOwner, 0);
            mi.afStyle = MIS_SUBMENU | MIS_TEXT;
            create_pane (mi.hwndSubMenu, p, buf, sizeof (buf), menubar);
            mi.iPosition = MIT_END;
            mi.afAttribute = 0;
            mi.hItem = 0;
            mi.id = 0;
            WinSendMsg (hwndMenu, MM_INSERTITEM, MPFROMP (&mi), MPFROMP (buf));
          }
      }
}


/* Create a popup menu and return its window handle. */

static HWND create_popup_menu (frame_data *f, int panes, int lines, char *p)
{
  HWND hwnd;

  if (f->popup_menu_items != NULL)
    deallocate (f->popup_menu_items);
  f->popup_menu_items = allocate (lines * sizeof (*f->popup_menu_items));
  f->popup_menu_min = menu_id = IDM_POPUP;
  menu_items = f->popup_menu_items;
  hwnd = create_menu_handle (f->hwndClient, 0);
  create_menu (hwnd, f->hwndClient, panes, &p, FALSE);
  menu_items = NULL;
  f->popup_menu_max = menu_id;
  return (hwnd);
}


/* Destroy the popup menu of frame F. */

static void destroy_popup_menu (frame_data *f)
{
  WinDestroyWindow (f->hwndPopupMenu);
  f->hwndPopupMenu = NULLHANDLE;
}


/* Create the top level of the menubar. */

static HWND create_menubar (frame_data *f, int entries, char *p)
{
  HWND hwnd;
  MENUITEM mi;
  SWP swp;
  POINTL aptl[2];
  RECTL rcl;
  pm_menubar_entry pme;
  char buf[512];
  int i, len;

  if (f->menubar_flag != (entries != 0))
    {
      WinQueryWindowPos (f->hwndClient, &swp);
      aptl[0].x = swp.x;
      aptl[0].y = swp.y;
      aptl[1].x = swp.x + swp.cx;
      aptl[1].y = swp.y + swp.cy;
      WinMapWindowPoints (f->hwndFrame, HWND_DESKTOP, aptl, 2);
      rcl.xLeft = aptl[0].x; rcl.yBottom = aptl[0].y;
      rcl.xRight = aptl[1].x; rcl.yTop = aptl[1].y;
    }

  WinDestroyWindow (WinWindowFromID (f->hwndFrame, FID_MENU));
  hwnd = WinCreateWindow (f->hwndFrame, WC_MENU, "", MS_ACTIONBAR, 0, 0, 0, 0,
                          f->hwndFrame, HWND_TOP, FID_MENU, NULL, NULL);
  for (i = 0; i < entries; ++i)
    {
      memcpy (&pme, p, sizeof (pme)); p += sizeof (pme);
      len = pme.size;
      if (len >= sizeof (buf)) len = sizeof (buf) - 1;
      memcpy (buf, p, len); p += pme.size;
      buf[len] = 0;
      mi.iPosition = MIT_END;
      mi.afStyle = MIS_SUBMENU | MIS_TEXT;
      mi.afAttribute = 0;
      mi.hwndSubMenu = create_menu_handle (f->hwndFrame, IDM_MENUBAR + i);
      mi.hItem = 0;
      mi.id = IDM_MENUBAR + i;
      WinSendMsg (hwnd, MM_INSERTITEM, MPFROMP (&mi), MPFROMP (buf));
    }
  f->ignore_wm_size = TRUE;
  WinSendMsg (f->hwndFrame, WM_UPDATEFRAME, MPFROMSHORT (FCF_MENU), 0);
  f->ignore_wm_size = FALSE;
  if (f->menubar_flag != (entries != 0))
    {
      WinCalcFrameRect (f->hwndFrame, &rcl, FALSE);
      if (rcl.yTop > swpScreen.cy)
        {
          LONG offset = rcl.yTop - swpScreen.cy;
          rcl.yBottom -= offset;
          rcl.yTop -= offset;
        }
      WinSetWindowPos (f->hwndFrame, HWND_TOP, rcl.xLeft, rcl.yBottom,
                       rcl.xRight - rcl.xLeft, rcl.yTop - rcl.yBottom,
                       SWP_MOVE | SWP_SIZE);
      f->menubar_flag = (entries != 0);
    }
  return (hwnd);
}


/* Delete all menu items from menu HWNDMENU. */

static void delete_menu_items (HWND hwndMenu)
{
  int i, count, id;

  count = SHORT1FROMMR (WinSendMsg (hwndMenu, MM_QUERYITEMCOUNT, 0, 0));
  for (i = count - 1; i >= 0; --i)
    {
      id = SHORT1FROMMR (WinSendMsg (hwndMenu, MM_ITEMIDFROMPOSITION,
                                     MPFROMSHORT (i), 0));
      if (id == MIT_ERROR || id == MIT_NONE)
        break;
      WinSendMsg (hwndMenu, MM_DELETEITEM, MPFROM2SHORT (id, FALSE), 0);
    }
}


/* Update a submenu of the menubar of frame F. */

static void create_menubar_menu (frame_data *f, menu_data *md)
{
  HWND hwndMenu;
  char buf[512], *p;
  int len, id;

  p = md->data;
  len = md->title_size;
  if (len >= sizeof (buf)) len = sizeof (buf) - 1;
  memcpy (buf, p, len); p += md->title_size;
  buf[len] = 0;
  if (hwndMenubarSubmenu != NULLHANDLE)
    create_menu (hwndMenubarSubmenu, f->hwndFrame, md->panes, &p, TRUE);
}


/* Mouse button pressed or released.  Compute the coordinates of the
   character cell, convert the modifier bits, and send to Emacs.  This
   function returns FALSE if the message is to be passed on to the
   default window procedure (which in turn passes it on to the frame
   window) because this window doesn't have the focus.  That way, the
   window is made active and the button message is not sent to Emacs.
   Moreover, WM_BUTTONxUP messages are ignored after making the window
   active until an appropriate WM_BUTTONxDOWN message is received. */

static int mouse_button (HWND hwnd, MPARAM mp1, MPARAM mp2, int button,
                         int modifier)
{
  pm_event pme;
  frame_data *f;

  f = WinQueryWindowPtr (hwnd, 0);
  if (hwnd == WinQueryFocus (HWND_DESKTOP))
    {
      if (modifier == DOWN_MODIFIER)
        f->ignore_button_up &= ~(1 << button);
      if (f->buttons[button] != 0
          && (modifier == DOWN_MODIFIER
              || !(f->ignore_button_up & (1 << button))))
        {
          pme.button.header.type = PME_BUTTON;
          pme.button.header.frame = f->id;
          pme.button.button = f->buttons[button] - 1;
          pme.button.modifiers =
            modifier | convert_modifiers (f, (USHORT)SHORT2FROMMP (mp2));
          pme.button.x = (USHORT)SHORT1FROMMP (mp1) / f->cxChar;
          pme.button.y = (f->cyClient - (USHORT)SHORT2FROMMP (mp1))
            / f->cyChar;
          if (pme.button.x < 0) pme.button.x = 0;
          if (pme.button.y < 0) pme.button.y = 0;
          pme.button.timestamp = WinQueryMsgTime (hab);
          send_event (&pme);
          return (TRUE);
        }
    }
  else
    f->ignore_button_up = 7;

  /* Continue processing -- make window active */
  return (FALSE);
}


/* Client window procedure for the Emacs frames.  This function is
   called in the PM thread. */

static MRESULT ClientWndProc (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  HPS hps;
  HDC hdc;
  RECTL rcl, rclBound;
  SIZEL sizel;
  SWP swp;
  ULONG menu_item, options, cmd;
  unsigned fsflags, usch, usvk, usrep, ussc;
  pm_event pme;
  frame_data *f;
  menu_data *md;
  menubar_data *mbd;
  int i;

  switch (msg)
    {
    case WM_CREATE:

      /* Creating the client window.  The pointer to the frame_data
         structure is passed in a global variable as the window is
         create by WinCreateStdWindow.  Save the pointer in the window
         data area. */

      f = new_frame;
      WinSetWindowPtr (hwnd, 0, f);

      /* Setup the fields of the frame_data structure. */

      f->hwndClient = hwnd;
      f->hwndPopupMenu = NULL;
      f->hwndMenubar = NULL;
      hdc = WinOpenWindowDC (hwnd);
      sizel.cx = sizel.cy = 0;
      f->hpsClient = GpiCreatePS (hab, hdc, &sizel,
                                  PU_PELS | GPIT_MICRO | GPIA_ASSOC);

      /* Switch color table to RGB mode. */

      GpiCreateLogColorTable (f->hpsClient, LCOL_PURECOLOR, LCOLF_RGB,
                              0, 0, NULL);

      /* Select the default font and get the font metrics. */

      set_default_font (f);

      /* Put current attributes into cache. */
      f->cur_color = GpiQueryColor (f->hpsClient);
      f->cur_backcolor = GpiQueryBackColor (f->hpsClient);
      f->cur_backmix = GpiQueryBackMix (f->hpsClient);
      f->cur_charset = GpiQueryCharSet (f->hpsClient);
      return (0);

    case WM_PAINT:

      /* The client window needs repainting.  Let Emacs redraw the
         frame.  Here, we only erase the border of partial character
         boxes which may surround the Emacs frame.  When maximizing a
         window or when calling WinSetWindowPos() in another program,
         the window may be slightly larger than required, leaving a
         border at the right and bottom edges.  */

      f = WinQueryWindowPtr (hwnd, 0);
      hps = WinBeginPaint (hwnd, NULLHANDLE, &rclBound);
      GpiCreateLogColorTable (hps, LCOL_PURECOLOR, LCOLF_RGB, 0, 0, NULL);
      rcl.xLeft = 0; rcl.xRight = f->width * f->cxChar - 1;
      rcl.yTop = f->cyClient; rcl.yBottom = rcl.yTop - f->height * f->cyChar;
      GpiExcludeClipRectangle (hps, &rcl);
      WinQueryWindowRect (hwnd, &rcl);
      WinFillRect (hps, &rcl, f->background_color);
      WinEndPaint (hps);

      /* Send a redraw event to Emacs.  The minimal bounding rectangle
         for the update region is sent along with the event.  */

      pme.paint.header.type = PME_PAINT;
      pme.paint.header.frame = f->id;
      pme.paint.x0 = rclBound.xLeft / f->cxChar;
      pme.paint.x1 = rclBound.xRight / f->cxChar;
      pme.paint.y0 = (f->cyClient - rclBound.yTop) / f->cyChar;
      pme.paint.y1 = (f->cyClient - rclBound.yBottom) / f->cyChar;
      send_event (&pme);
      return (0);

    case WM_CLOSE:

      /* The window is to be closed on the user's request.  Send a
         keyboard event to Emacs and do not pass the message to the
         default window procedure (otherwise, WM_QUIT would be posted
         to the message queue).  */

      f = WinQueryWindowPtr (hwnd, 0);
      send_key (f, PMK_VIRTUAL, VK_CLOSE_FRAME, 0, 1);
      return (0);

    case WM_CHAR:

      /* Keyboard message.  Processing keys is quite messy as you will
         learn soon.  First, get the frame data pointer and extract
         the WM_CHAR message flags. */

      f = WinQueryWindowPtr (hwnd, 0);
      fsflags = (USHORT)SHORT1FROMMP (mp1);

      /* Ignore key-up messages.  Processing key-up messages might be
         useful for VK_NEWLINE and VK_ENTER (see below), but isn't
         implemented. */

      if (fsflags & KC_KEYUP)
        return (0);

      /* Extract the repeat count, the character code and the virtual
         key code. */

      usrep = CHAR3FROMMP (mp1);
      ussc = CHAR4FROMMP (mp1);
      usch = (USHORT)SHORT1FROMMP (mp2);
      usvk = (USHORT)SHORT2FROMMP (mp2);

      /* If it's a deadkey, remember the character code (it should
         have one) as it might be required if the user depresses a key
         which cannot be turned into a composite character.  Return
         after storing the character code. */

      if (fsflags & KC_DEADKEY)
        {
          f->deadkey = usch;
          return (0);
        }

      /* Handle invalid compositions: after depressing a deadkey, the
         user has depressed a key which cannot be turned into a
         composite character.  Insert the deadkey (accent) and
         continue processing with the second character.  We have to
         decrement the repeat count (that's not documented). */

      if (fsflags & KC_INVALIDCOMP)
        {
          if (f->deadkey == 0)
            return (0);
          send_key (f, PMK_ASCII, f->deadkey, 0, 1);
          if (usrep > 1)
            --usrep;
        }
      f->deadkey = 0;

      /* Ignore `ALT + keypad digit' keys -- they're used for entering
         the decimal code of a character and should not be seen by
         Emacs.  After releasing the ALT key, a WM_CHAR message for
         the character is sent where all the flag bits except KC_CHAR
         are zero.  Note that KC_VIRTUALKEY is not set for ALT-5,
         therefore we have to check the scan code to distinguish the
         keypad digit keys from the main keyboard digit keys.  This is
         dirty. */

      if ((fsflags & (KC_ALT|KC_SCANCODE|KC_CHAR)) == (KC_ALT|KC_SCANCODE))
        switch (ussc)
          {
          case 0x47: case 0x48: case 0x49:   /* 7 8 9 */
          case 0x4b: case 0x4c: case 0x4d:   /* 4 5 6 */
          case 0x4f: case 0x50: case 0x51:   /* 1 2 3 */
          case 0x52:                         /* 0     */
            return (0);
          }

      /* Handle some special virtual keys first. */

      if (fsflags & KC_VIRTUALKEY)
        switch (usvk)
          {
          case VK_ESC:

            /* The KC_CHAR bit is not set for the VK_ESC key.
               Therefore we have to handle that key here. */

            send_key (f, PMK_ASCII, 0x1b, fsflags, usrep);
            return (0);

          case VK_BACKSPACE:

            /* Translate backspace to DEL. */

            send_key (f, PMK_ASCII, 0x7f, fsflags, usrep);
            return (0);

          case VK_NEWLINE:
          case VK_ENTER:

            /* The NEWLINE and ENTER keys don't generate a code when
               depressed while ALT or SHIFT are depressed.  A code is
               generated when NEWLINE or ENTER are released!  This is
               probably a hack for people who keep their fingers too
               long on the shift keys, like me.  CTRL, however, works.
               For convenience (and compatibility), we translate C-RET
               to LFD here. */

            send_key (f, PMK_ASCII, (fsflags & KC_CTRL ? 0x0a : 0x0d),
                      0, usrep);
            return (0);
          }

      /* If a character code is present, use that and throw away all
         the modifiers.  Letters with KC_ALT or KC_CTRL set don't have
         KC_CHAR set.  With a non-US keyboard, however, the
         Presentation Manager seems to ignore the CTRL key when the
         right ALT key (ALTGR) is depressed.  That is, KC_CHAR and
         KC_CTRL are set and the character code is not changed by the
         CTRL key.

         If the (left) ALT key is depressed together with the right
         ALT key (ALTGR), KC_CHAR is not set, that is, a user with a
         German keyboard cannot enter M-{ by typing ALT+ALTGR+7.  He
         or she has to type ESC { instead. */

      if (fsflags & KC_CHAR)
        {
          if ((fsflags & KC_CTRL) && (usch >= 0x40 && usch <= 0x5f))
            send_key (f, PMK_ASCII, usch & 0x1f, 0, usrep);
          else
            send_key (f, PMK_ASCII, usch, fsflags & KC_CTRL, usrep);
          return (0);
        }

      /* Now process keys depressed while the CTRL key is down.
         KC_CHAR and KC_VIRTUALKEY are not set for ASCII keys when the
         CTRL key is down.  If both the ALT and CTRL keys are down,
         process the key here.  Remove the KC_CTRL modifier and
         translate the character code if the character is a regular
         control character.  Otherwise, keep the character code and
         set the CTRL modifier.  Ignore keys for which the character
         code is zero.  This happens for the / key on the numeric
         keypad.  If the character is Ctrl-G, we send a signal instead
         of an event. */

      if ((fsflags & (KC_CTRL|KC_VIRTUALKEY)) == KC_CTRL)
        {
          if (usch >= 0x40 && usch <= 0x7f)
            {
              send_key (f, PMK_ASCII, usch & 0x1f, fsflags & KC_ALT, usrep);
              return (0);
            }
          else if ((usch & 0xff) != 0)
            {
              send_key (f, PMK_ASCII, usch, fsflags & (KC_CTRL|KC_ALT), usrep);
              return (0);
            }
        }

      /* Now process keys depressed while the ALT key is down (and the
         CTRL key is up).  Simply keep the character code and the ALT
         modifier.  Ignore keys for which the character code is zero.
         This happens for Alt+Altgr+@, for instance.  The reason is
         unknown. */

      if ((fsflags & (KC_ALT|KC_VIRTUALKEY)) == KC_ALT && (usch & 0xff) != 0)
        {
          send_key (f, PMK_ASCII, usch, KC_ALT, usrep);
          return (0);
        }

      /* If the key is a virtual key and hasn't been processed above,
         generate a lispy keyboard event and keep all the modifiers
         (except SHIFT, in some cases).  Messages generated for shift
         keys etc. are ignored. */

      if (fsflags & KC_VIRTUALKEY)
        switch (usvk)
          {
          case VK_SHIFT:
          case VK_CTRL:
          case VK_ALT:
          case VK_ALTGRAF:
          case VK_NUMLOCK:
          case VK_CAPSLOCK:
            return (0);

          default:

            /* Remove the SHIFT modifier for the keys of the numeric
               keypad that share a number key (including . or ,) and a
               virtual key.  Otherwise, the HOME key, for instance,
               would yield `S-home' instead of `home' in numlock
               mode.  We look at the scan codes, which is dirty. */

            switch (ussc)
              {
              case 0x47: case 0x48: case 0x49:   /* 7 8 9 */
              case 0x4b: case 0x4c: case 0x4d:   /* 4 5 6 */
              case 0x4f: case 0x50: case 0x51:   /* 1 2 3 */
              case 0x52: case 0x53:              /* 0 .   */
                fsflags &= ~KC_SHIFT;
                break;
              }

            send_key (f, PMK_VIRTUAL, usvk, fsflags, usrep);
            return (0);
          }

      /* Generate keyboard events for keys that are neither character
         keys not virtual keys.  Both the KC_VIRTUALKEY and KC_CHAR
         bits are zero. We have to look at the scan code, which is
         dirty. */

      if (fsflags & KC_SCANCODE)
        switch (ussc)
          {
          case 0x4c:            /* `CENTER' key, `5' */
            send_key (f, PMK_VIRTUAL, VK_CENTER, fsflags & ~KC_SHIFT, usrep);
            return (0);

          case 0x5c:            /* `DIVIDE' key on numeric keypad */

            /* This key doesn't generate a character code if the
               German keyboard layout is active.  Looks like a bug. */

            send_key (f, PMK_ASCII, '/', fsflags, usrep);
            return (0);

          default:

            /* When hitting a letter or digit key while the right Alt
               key (AltGr on most non-US keyboards) is down, neither
               KC_ALT nor KC_CHAR nor KC_VIRTUALKEY is set.  (On US
               keyboards, the right Alt key is equivalent to the left
               Alt key.)  Fortunately, the character code seems to be
               valid.  Use altgr_modifier and the character code if
               the character code looks valid.  Note that AltGr is
               used on most non-US keyboards to enter special
               characters; on the German keyboard, for instance, you
               have to type AltGr+q to get @.  These key combinations
               have been handled above (KC_CHAR is set). */

            if (usch > 0)
              send_key (f, PMK_ASCII, usch, fsflags | KC_ALTGR, usrep);
            break;
          }
      return (0);

    case WM_TRANSLATEACCEL:

      /* Examine accelerator table.  We ignore the accelerator table
         if the shortcuts frame parameter is nil.  By ignoring the
         accelerator table, we receive WM_CHAR messages for F1, F10,
         A-f4, A-space etc. */

      f = WinQueryWindowPtr (hwnd, 0);
      if (f->disable_shortcuts)
        return (FALSE);
      break;

    case WM_COMMAND:

      /* Menu or button or accelerator.  It's important to set
         f->hwndPopupMenu to NULLHANDLE, see WM_MENUEND. */

      f = WinQueryWindowPtr (hwnd, 0);
      cmd = COMMANDMSG (&msg)->cmd;
      if (COMMANDMSG (&msg)->source == CMDSRC_MENU
          && f->hwndPopupMenu != NULLHANDLE
          && (cmd == 0
              || (cmd >= f->popup_menu_min && cmd < f->popup_menu_max)))
        {
          if (cmd == 0)
            menu_item = 0;
          else
            menu_item = f->popup_menu_items[cmd - f->popup_menu_min];
          send_oob (&menu_item, sizeof (menu_item));
          destroy_popup_menu (f);
          f->hwndPopupMenu = NULLHANDLE;
          return (0);
        }
      else if (COMMANDMSG (&msg)->source == CMDSRC_MENU
               && f->hwndMenubar != NULLHANDLE
               && (cmd >= f->menubar_min && cmd < f->menubar_max))
        {
          i = 3 * (cmd - f->menubar_min);
          send_key (f, PMK_VIRTUAL, VK_MENU_BAR, 0, 1);
          send_key (f, PMK_SYMBOL, f->menubar_items[i+0], 0, 1);
          if (f->menubar_items[i+1] != 0)
            send_key (f, PMK_SYMBOL, f->menubar_items[i+1], 0, 1);
          send_key (f, PMK_SYMBOL, f->menubar_items[i+2], 0, 1);
          return (0);
        }
      break;

    case WM_MENUEND:

      /* Menu terminates.  If f->hwndPopupMenu is non-NULL (and
         matches the menu for which this message is sent), the user
         hasn't selected a menu item.  That is, the menu was dismissed
         by pressing ESC or clicking outside the menu.  We send a 0 to
         Emacs by faking a menu selection.

         If the menu is a submenu of the menubar, delete all items of
         the menu. */

      f = WinQueryWindowPtr (hwnd, 0);
      if ((HWND)LONGFROMMP (mp2) == f->hwndPopupMenu)
        WinPostMsg (hwnd, WM_COMMAND,
                    MPFROMSHORT (0), MPFROM2SHORT (CMDSRC_MENU, FALSE));
      else if (SHORT1FROMMP (mp1) >= IDM_MENUBAR
               && SHORT1FROMMP (mp1) < IDM_MENUBAR + 1000)
        delete_menu_items (PVOIDFROMMP (mp2));
      return (0);

    case WM_BUTTON1DOWN:
      if (mouse_button (hwnd, mp1, mp2, 0, DOWN_MODIFIER))
        return ((MRESULT)TRUE);
      break;

    case WM_BUTTON1UP:
      if (mouse_button (hwnd, mp1, mp2, 0, UP_MODIFIER))
        return ((MRESULT)TRUE);
      break;

    case WM_BUTTON1DBLCLK:
      if (mouse_button (hwnd, mp1, mp2, 0, DOWN_MODIFIER)
          && mouse_button (hwnd, mp1, mp2, 0, UP_MODIFIER))
        return ((MRESULT)TRUE);
      break;

    case WM_BUTTON2DOWN:
      if (mouse_button (hwnd, mp1, mp2, 1, DOWN_MODIFIER))
        return ((MRESULT)TRUE);
      break;

    case WM_BUTTON2UP:
      if (mouse_button (hwnd, mp1, mp2, 1, UP_MODIFIER))
        return ((MRESULT)TRUE);
      break;

    case WM_BUTTON2DBLCLK:
      if (mouse_button (hwnd, mp1, mp2, 1, DOWN_MODIFIER)
          && mouse_button (hwnd, mp1, mp2, 1, UP_MODIFIER))
        return ((MRESULT)TRUE);
      break;

    case WM_BUTTON3DOWN:
      if (mouse_button (hwnd, mp1, mp2, 2, DOWN_MODIFIER))
        return ((MRESULT)TRUE);
      break;

    case WM_BUTTON3UP:
      if (mouse_button (hwnd, mp1, mp2, 2, UP_MODIFIER))
        return ((MRESULT)TRUE);
      break;

    case WM_BUTTON3DBLCLK:
      if (mouse_button (hwnd, mp1, mp2, 2, DOWN_MODIFIER)
          && mouse_button (hwnd, mp1, mp2, 2, UP_MODIFIER))
        return ((MRESULT)TRUE);
      break;

    case WM_SIZE:

      /* The size of the window has changed.  Store the new size and
         recreate the cursor.  The cursor must be recreated to install
         the new clipping rectangle.  While minimizing the window,
         this message is ignored. */

      f = WinQueryWindowPtr (hwnd, 0);
      if (f->ignore_wm_size)
        break;
      if (f->minimizing)
        {
          f->minimizing = FALSE;
          break;
        }
      f->cxClient = SHORT1FROMMP (mp2);
      f->cyClient = SHORT2FROMMP (mp2);
      set_cursor (f);

      /* Notify Emacs of the new window size. */

      pme.size.header.type = PME_SIZE;
      pme.size.header.frame = f->id;
      pme.size.width = f->cxClient / f->cxChar;
      pme.size.height = f->cyClient / f->cyChar;
      if (pme.size.width < 1) pme.size.width = 1;
      if (pme.size.height < 1) pme.size.height = 1;
      f->width = pme.size.width;
      f->height = pme.size.height;
      send_event (&pme);
      break;

    case WM_MOVE:

      /* The position of the window has been changed. */

      f = WinQueryWindowPtr (hwnd, 0);
      WinQueryWindowPos (f->hwndFrame, &swp);
      pme.framemove.header.type = PME_FRAMEMOVE;
      pme.framemove.header.frame = f->id;
      pme.framemove.top = swpScreen.cy - 1 - (swp.y + swp.cy);
      pme.framemove.left = swp.x;
      send_event (&pme);
      break;

    case WM_MOUSEMOVE:
      if (track_mouse)
        {
          f = WinQueryWindowPtr (hwnd, 0);
          pme.mouse.x = (USHORT)SHORT1FROMMP (mp1) / f->cxChar;
          pme.mouse.y = (f->cyClient - (USHORT)SHORT2FROMMP (mp1)) / f->cyChar;
          if (pme.mouse.x < 0) pme.mouse.x = 0;
          if (pme.mouse.y < 0) pme.mouse.y = 0;
          if (pme.mouse.x != track_x || pme.mouse.y != track_y)
            {
              pme.mouse.header.type = PME_MOUSEMOVE;
              pme.mouse.header.frame = f->id;
              send_event (&pme);
              track_x = pme.mouse.x; track_y = pme.mouse.y;
            }
        }
      break;

    case WM_SETFOCUS:

      /* Create the cursor when receiving the input focus, destroy the
         cursor when loosing the input focus. */

      f = WinQueryWindowPtr (hwnd, 0);
      if (SHORT1FROMMP (mp2))
        create_cursor (f);
      else
        WinDestroyCursor (hwnd);
      return (0);

    case UWM_POPUPMENU:
      f = (frame_data *)mp1;
      md = (menu_data *)mp2;
      f->hwndPopupMenu = create_popup_menu (f, md->panes, md->lines,
                                            md->data + md->title_size);
      options = (PU_MOUSEBUTTON1 | PU_MOUSEBUTTON2 | PU_MOUSEBUTTON3
                 | PU_KEYBOARD | PU_HCONSTRAIN | PU_VCONSTRAIN);
      if (md->button >= 1 && md->button <= 3)
        for (i = 0; i < 3; ++i)
          if (f->buttons[i] == md->button)
            switch (i)
              {
              case 0:
                options |= PU_MOUSEBUTTON1DOWN;
                break;
              case 1:
                options |= PU_MOUSEBUTTON2DOWN;
                break;
              case 2:
                options |= PU_MOUSEBUTTON3DOWN;
                break;
              }
      WinPopupMenu (f->hwndFrame, hwnd, f->hwndPopupMenu,
                    md->x, md->y, 0, options);
      return (0);

    case UWM_MENUBAR_TOP:
      f = (frame_data *)mp1;
      mbd = (menubar_data *)mp2;
      f->hwndMenubar = create_menubar (f, mbd->entries, mbd->data);
      return (0);

    case UWM_MENUBAR_MENU:
      f = (frame_data *)mp1;
      md = (menu_data *)mp2;
      create_menubar_menu (f, md);
      return (0);
    }
  return (WinDefWindowProc (hwnd, msg, mp1, mp2));
}


/* This window procedure is used for subclassing frame windows.  We
   subclass frame windows to set the grid for resizing the window to
   the character box.  */

static MRESULT FrameWndProc (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  HWND hwndClient;
  frame_data *f;
  MRESULT mr;
  PTRACKINFO pti;
  PSWP pswp;
  pm_event pme;

  switch (msg)
    {
    case WM_QUERYTRACKINFO:

      /* Fill-in TRACKINFO structure for resizing or moving a window.
         First call the normal frame window procedure to set up the
         structure. */

      mr = old_frame_proc (hwnd, msg, mp1, mp2);
      pti = (PTRACKINFO)mp2;

      /* If we're resizing the window, set the grid. */

      if (((pti->fs & TF_MOVE) != TF_MOVE)
          && ((pti->fs & TF_MOVE) || (pti->fs & TF_SETPOINTERPOS)))
        {
          hwndClient = WinWindowFromID (hwnd, FID_CLIENT);
          f = WinQueryWindowPtr (hwndClient, 0);
          if (f->cxChar != 0 && f->cyChar != 0)
            {
              pti->fs |= TF_GRID;
              pti->cxGrid = pti->cxKeyboard = f->cxChar;
              pti->cyGrid = pti->cyKeyboard = f->cyChar;
            }
        }
      return (mr);

    case WM_MINMAXFRAME:

      /* The frame window is being minimized or maximized.  We set a
         flag to suppress sending a resize event to Emacs when
         minimizing the window. */

      hwndClient = WinWindowFromID (hwnd, FID_CLIENT);
      f = WinQueryWindowPtr (hwndClient, 0);
      pswp = (PSWP)mp1;
      if (!f->minimized && (pswp->fl & SWP_MINIMIZE))
        {
          f->minimizing = f->minimized = TRUE;
          pme.header.type = PME_MINIMIZE;
          pme.header.frame = f->id;
          send_event (&pme);
        }
      else if (f->minimized && (pswp->fl & (SWP_RESTORE | SWP_MAXIMIZE)))
        {
          f->minimized = FALSE;
          pme.header.type = PME_RESTORE;
          pme.header.frame = f->id;
          send_event (&pme);
        }
      break;

    case WM_INITMENU:

      /* A menu is about to be displayed.  If the menu is a submenu of
         the menubar, let Emacs update the menu.

         Note: WinGetMsg below may cause a deadlock if messages fill
         up the message queue.  Probably WinWaitEventSem should be
         used instead. */

      if ((USHORT)SHORT1FROMMP (mp1) >= IDM_MENUBAR
          && (USHORT)SHORT1FROMMP (mp1) < IDM_MENUBAR+1000)
        {
          QMSG qmsg;

          hwndClient = WinWindowFromID (hwnd, FID_CLIENT);
          f = WinQueryWindowPtr (hwndClient, 0);
          hwndMenubarSubmenu = PVOIDFROMMP (mp2);
          pme.menubar.header.type = PME_MENUBAR;
          pme.menubar.header.frame = f->id;
          pme.menubar.number = (USHORT)SHORT1FROMMP (mp1) - IDM_MENUBAR;
          send_event (&pme);
          if (!WinGetMsg (hab, &qmsg, 0L, UWM_MENUBAR_DONE, UWM_MENUBAR_DONE))
            terminate_process ();
        }
      break;
    }
  return (old_frame_proc (hwnd, msg, mp1, mp2));
}


/* This is the window procedure for the object window of the PM
   thread.  It is used for creating and destroying windows as windows
   belong to the thread by which they have been created.  If there
   were no object window, we had no window of the PM thread to which
   the messages could be sent. */

static MRESULT ObjectWndProc (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  ULONG flFrameFlags;
  frame_data *f;
  PFNWP fun;

  switch (msg)
    {
    case UWM_CREATE:
      f = (frame_data *)mp1;

      /* Dirty trick: WinCreateStdWindow() doesn't accept a pCtlData
         parameter.  As using WinCreateWindow() would be too complicated
         for a frame window, the pointer is passed in a global variable.
         As this code cannot be reentered, this is safe. */

      new_frame = f;
      flFrameFlags = (FCF_TITLEBAR      | FCF_SYSMENU | FCF_ICON |
                      FCF_SIZEBORDER    | FCF_MINMAX  |
                      FCF_SHELLPOSITION | FCF_TASKLIST);
      f->hwndFrame = WinCreateStdWindow (HWND_DESKTOP, 0,
                                         &flFrameFlags, szClientClass,
                                         NULL, 0L, 0, 1, NULL);
      fun = WinSubclassWindow (f->hwndFrame, FrameWndProc);
      if (old_frame_proc != NULL && old_frame_proc != fun)
        abort ();
      old_frame_proc = fun;

      set_size (f, TRUE);
      WinSetActiveWindow (HWND_DESKTOP, f->hwndFrame);
      return (0);

    case UWM_DESTROY:
      f = (frame_data *)mp1;
      WinDestroyWindow (f->hwndFrame);
      return (0);
    }
  return (0);
}


/* Receive SIZE bytes from Emacs proper.  The data is stored to
   DST. */

static void receive (void *dst, ULONG size)
{
  char *d;
  ULONG n;

  d = dst;
  while (size != 0)
    {
      if (DosRead (inbound_pipe, d, size, &n) != 0 || n == 0)
        error ("Cannot read from pipe");
      size -= n;
      d += n;
    }
}


/* Return a pointer to the frame data for the frame with identity FID.
   If no such frame exists, NULL is returned, causing a protection
   violation later (this must not happen). */

static frame_data *find_frame (ULONG fid)
{
  frame_data *f;

  for (f = hash_table[fid % HASH_SIZE]; f != NULL; f = f->next)
    if (f->id == fid)
      return (f);
  return (NULL);
}


/* Create a new frame.  This function is called in the thread which
   reads the pipe.  Therefore, we must send a message to the PM thread
   to actually create the PM window. */

static void create_frame (pmr_create *pc)
{
  frame_data *f;
  ULONG fid, hash;

  fid = pc->header.frame;
  hash = fid % HASH_SIZE;
  f = allocate (sizeof (*f));
  f->next = hash_table[hash];
  hash_table[hash] = f;
  f->id = fid;
  f->width = pc->width;
  f->height = pc->height;
  f->background_color = RGB_WHITE;
  f->cursor_x = 0; f->cursor_y = 0;
  f->cursor_on = FALSE;
  f->cursor_type = CURSORTYPE_BOX;
  f->cursor_blink = TRUE;
  f->ignore_button_up = 0;
  f->minimized = FALSE;
  f->minimizing = FALSE;
  f->ignore_wm_size = FALSE;
  f->deadkey = 0;
  f->disable_shortcuts = FALSE;
  f->popup_menu_items = NULL;
  f->popup_menu_min = 0;
  f->popup_menu_max = 0;
  f->menubar_items = NULL;
  f->menubar_flag = FALSE;
  f->menubar_min = 0;
  f->menubar_max = 0;
  f->buttons[0] = 1;
  f->buttons[1] = 3;
  f->buttons[2] = 2;
  memset (f->font_defined, 0, sizeof (f->font_defined));

  /* We must not create the cursor until cxChar and cyChar are known.
     Therefore we set both to zero before creating the window to be
     able to detect that case. */

  f->cxChar = 0; f->cyChar = 0;

  parse_font_spec (&f->font, "10.Courier");
  f->alt_modifier = META_MODIFIER;
  f->altgr_modifier = ALT_MODIFIER;
  WinSendMsg (hwndObject, UWM_CREATE, MPFROMP (f), 0);
}


/* Destroy a frame.  A window can be destroyed only by the thread
   which created it.  Therefore we send a message to the PM thread to
   actually destroy the window. */

static void destroy_frame (ULONG fid)
{
  frame_data *f, **fp;

  for (fp = &hash_table[fid % HASH_SIZE]; *fp != NULL; fp = &(*fp)->next)
    {
      f = *fp;
      if (f->id == fid)
        {
          *fp = f->next;
          WinSendMsg (hwndObject, UWM_DESTROY, MPFROMP (f), 0);
          deallocate (f);
          return;
        }
    }
}


static void set_pos (frame_data *f, int left, int top)
{
  SWP swp;
  
  WinQueryWindowPos (f->hwndFrame, &swp);
  if (left != DONT_MOVE)
    {
      if (left >= 0)
        swp.x = left;
      else
        swp.x = swpScreen.cx - swp.cx + left;
    }
  if (top != DONT_MOVE)
    {
      if (top >= 0)
        swp.y = swpScreen.cy - 1 - (top + swp.cy);
      else
        swp.y = - top;
    }
  WinSetWindowPos (f->hwndFrame, HWND_TOP, swp.x, swp.y, 0, 0, SWP_MOVE);
}


#define CONV_BUTTON(c) ((c) >= '1' && (c) <= '3' ? (c) - '0' : 0)

/* Modify parameters of an existing frame. */

static void modify_frame (frame_data *f)
{
  pm_modify more;

  receive (&more, sizeof (more));
  if (more.font_name[0] != 0)
    {
      parse_font_spec (&f->font, more.font_name);
      set_default_font (f);
      set_cursor (f);
      if (more.width == 0 && more.height == 0)
        set_size (f, FALSE);
    }
  if (more.width != 0 || more.height != 0)
    {
      if (more.width != 0) f->width = more.width;
      if (more.height != 0) f->height = more.height;
      set_size (f, FALSE);
    }
  if (more.left != DONT_MOVE || more.top != DONT_MOVE)
    set_pos (f, more.left, more.top);
  if (more.cursor_type != 0)
    f->cursor_type = more.cursor_type;
  if (more.cursor_blink != 0)
    f->cursor_blink = (more.cursor_blink == PMR_TRUE);
  if (more.cursor_type != 0 || more.cursor_blink != 0)
    set_cursor (f);
  if (more.alt_modifier != 0)
    f->alt_modifier = more.alt_modifier;
  if (more.altgr_modifier != 0)
    f->altgr_modifier = more.altgr_modifier;
  if (more.disable_shortcuts != 0)
    f->disable_shortcuts = (more.disable_shortcuts == PMR_TRUE);
  if (more.buttons[0] != 0)
    {
      /* Note that the middle button is button 3 (not 2) under OS/2. */
      f->buttons[0] = CONV_BUTTON (more.buttons[0]);
      f->buttons[1] = CONV_BUTTON (more.buttons[2]);
      f->buttons[2] = CONV_BUTTON (more.buttons[1]);
    }
}


/* Get the current mouse position and send an appropriate out-of-band
   message to Emacs. */

static void get_mousepos (void)
{
  HWND hwnd;
  POINTL ptl;
  frame_data *f;
  int i;
  pmd_mousepos result;

  result.frame = 0; result.x = 0; result.y = 0;
  WinQueryPointerPos (HWND_DESKTOP, &ptl);
  hwnd = WinWindowFromPoint (HWND_DESKTOP, &ptl, TRUE);
  if (hwnd != NULLHANDLE)
    {
      for (i = 0; i < HASH_SIZE; ++i)
        for (f = hash_table[i]; f != NULL; f = f->next)
          if (hwnd == f->hwndClient)
            {
              WinMapWindowPoints (HWND_DESKTOP, f->hwndClient, &ptl, 1);
              result.frame = f->id;
              result.x = ptl.x / f->cxChar;
              result.y = (f->cyClient - ptl.y) / f->cyChar;
              if (result.x < 0) result.x = 0;
              if (result.y < 0) result.y = 0;
              break;
            }
    }
  send_oob (&result, sizeof (result));
}


/* Get the position of the frame window and send an appropriate
   out-of-band message to Emacs. */

static void get_framepos (frame_data *f)
{
  SWP swp;
  pmd_framepos result;

  WinQueryWindowPos (f->hwndFrame, &swp);
  result.left = swp.x;
  result.top = swpScreen.cy - 1 - (swp.y + swp.cy);
  send_oob (&result, sizeof (result));
}


/* Retrieve a text from the clipboard and send it as out-of-band data
   to Emacs.  The text is in CR/LF format, without trailing null
   character.  The byte count as 32-bit number precedes the text.  If
   GET_TEXT is zero, the text isn't sent. */

static void get_clipboard (int get_text)
{
  ULONG size, ulData;
  PCH str;
  int opened;

  size = 0;
  opened = WinOpenClipbrd (hab);
  if (opened)
    {
      ulData = WinQueryClipbrdData (hab, CF_TEXT);
      if (ulData != 0)
        {
          str = (PCH)ulData;
          size = strlen (str);
        }
    }
  send_oob (&size, sizeof (size));
  if (size != 0 && get_text)
    send_oob (str, size);
  if (opened)
    WinCloseClipbrd (hab);
}


/* Receive a buffer from Emacs and put the text into the clipboard.
   SIZE is the size of the text, in bytes.  The buffer is in CR/LF
   format, without trailing null character. */

static void put_clipboard (unsigned long size)
{
  char *buf;

  DosAllocSharedMem ((PVOID)&buf, NULL, size + 1,
                     PAG_COMMIT | PAG_READ | PAG_WRITE | OBJ_GIVEABLE);
  receive (buf, size);
  buf[size] = 0;
  if (WinOpenClipbrd (hab))
    {
      WinEmptyClipbrd (hab);
      WinSetClipbrdData (hab, (ULONG)buf, CF_TEXT, CFI_POINTER);
      WinCloseClipbrd (hab);
    }
}


/* Define a face. */

static void define_face (frame_data *f, pmr_face *pf, const char *name)
{
  face_data *face;
  font_spec spec;
  int i, gc;

  if (!parse_font_spec (&spec, name))
    {
      spec.size = 0;
      spec.name[0] = 0;
      spec.sel = f->font.sel;
    }
  if (pf->underline)
    spec.sel |= FATTR_SEL_UNDERSCORE;
  gc = 0;
  for (i = 1; i <= nfaces; ++i)
    if (face_vector[i].foreground == pf->foreground
        && face_vector[i].background == pf->background
        && equal_font_spec (&face_vector[i].spec, &spec))
      {
        gc = i;
        break;
      }
  if (gc == 0)
    {
      if (nfaces + 1 >= nfaces_allocated)
        {
          face_data *np;

          nfaces_allocated += 16;
          np = allocate (nfaces_allocated * sizeof (face_data));
          memcpy (np, face_vector, nfaces * sizeof (face_data));
          deallocate (face_vector);
          face_vector = np;
        }
      gc = ++nfaces;
      face = &face_vector[nfaces];
      face->spec = spec;
      face->foreground = pf->foreground;
      face->background = pf->background;
      face->font_id = 0;
    }
  send_oob (&gc, sizeof (gc));
}


/* Hide all windows and post a WM_QUIT message to the message queue of
   the PM thread.  We hide all windows with one call to avoid
   excessive repainting. */

static void terminate (void)
{
  frame_data *f;
  PSWP pswp;
  int i, j, n;

  terminating = TRUE;
  n = 0;
  for (i = 0; i < HASH_SIZE; ++i)
    for (f = hash_table[i]; f != NULL; f = f->next)
      ++n;
  if (n != 0)
    {
      pswp = allocate (n * sizeof (SWP));
      j = 0;
      for (i = 0; i < HASH_SIZE; ++i)
        for (f = hash_table[i]; f != NULL; f = f->next)
          if (j < n)
            {
              pswp[j].hwnd = f->hwndFrame;
              pswp[j].fl = SWP_HIDE;
              ++j;
            }
      WinSetMultWindowPos (hab, pswp, j);
      deallocate (pswp);
    }
  WinPostMsg (hwndObject, WM_QUIT, 0, 0);

  /* Sit for about 49 days to avoid reading from the pipe, which
     probably no longer exists now. */

  DosSleep ((ULONG)-1);
}


/* Build a font name in "Host Portable Character Representation".
   Here's an X11R4 example:
   -misc-fixed-bold-r-normal--13-100-100-100-c-80-iso8859-1 */

static int make_x_font_spec (unsigned char *dst, PFONTMETRICS pfm,
                             int bold, int italic)
{
  return sprintf (dst, "-os2-%s-%s-%s-normal--%d-%d-%d-%d-m-1-cp850",
                  pfm->szFamilyname,
                  bold ? "bold" : "medium",
                  italic ? "i" : "r",
                  pfm->lMaxCharInc,
                  pfm->sNominalPointSize,
                  pfm->sXDeviceRes,
                  pfm->sYDeviceRes);
}


/* Build a font name in OS/2 format (size.name).  We extend the syntax
   to size[.bold][.italic]name. */

static int make_pm_font_spec (unsigned char *dst, PFONTMETRICS pfm,
                              int bold, int italic)
{
  int i;

  i = sprintf (dst, "%d", (pfm->sNominalPointSize + 5) / 10);
  if (bold)
    i += sprintf (dst+i, ".bold");
  if (italic)
    i += sprintf (dst+i, ".italic");
  i += sprintf (dst+i, ".%s", pfm->szFamilyname);
  return (i);
}


/* Send a list of fonts to Emacs, for pm_list_fonts. */

static void fontlist (frame_data *f)
{
  pmd_fontlist answer;
  ULONG fonts, temp, i, len;
  PFONTMETRICS pfm;
  UCHAR *buf, *p, spec[100];
  int bold, italic;

  temp = 0;
  fonts = GpiQueryFonts (f->hpsClient, QF_PUBLIC | QF_PRIVATE, NULL,
                         &temp, sizeof (FONTMETRICS), NULL);
  pfm = allocate (fonts * sizeof (FONTMETRICS));
  GpiQueryFonts (f->hpsClient, QF_PUBLIC | QF_PRIVATE, NULL,
                 &fonts, sizeof (FONTMETRICS), pfm);
  answer.count = 0;
  answer.size = 0;
  for (i = 0; i < fonts; ++i)
    if (pfm[i].fsType & FM_TYPE_FIXED)
      {
        answer.count += 8;
        for (bold = 0; bold <= 1; ++bold)
          for (italic = 0; italic <= 1; ++italic)
            {
              answer.size += 1 + make_x_font_spec (spec, &pfm[i],
                                                   bold, italic);
              answer.size += 1 + make_pm_font_spec (spec, &pfm[i],
                                                    bold, italic);
            }
      }

  send_oob (&answer, sizeof (answer));

  buf = allocate (answer.size);
  p = buf;
  for (i = 0; i < fonts; ++i)
    if (pfm[i].fsType & FM_TYPE_FIXED)
      for (bold = 0; bold <= 1; ++bold)
        for (italic = 0; italic <= 1; ++italic)
          {
            len = make_x_font_spec (p + 1, &pfm[i], bold, italic);
            *p = (UCHAR)len;
            p += 1 + len;
            len = make_pm_font_spec (p + 1, &pfm[i], bold, italic);
            *p = (UCHAR)len;
            p += 1 + len;
          }
  send_oob (buf, answer.size);
  deallocate (buf);
  deallocate (pfm);
}


#define make_x(f,x) ((x) * (f)->cxChar)

#define make_y(f,y) ((f)->cyClient + (f)->cyDesc - ((y) + 1) * (f)->cyChar)

#define CURSOR_OFF(f) (void)((f)->cursor_on \
                             && WinShowCursor ((f)->hwndClient, FALSE))

#define CURSOR_BACK(f) (void)((f)->cursor_on \
                              && WinShowCursor ((f)->hwndClient, TRUE))


/* Read the pipe.  This function is run in a secondary thread.  Here,
   we receive requests from Emacs. */

static void pipe_thread (ULONG arg)
{
  HMQ hmq;
  POINTL ptl, aptl[3];
  RECTL rcl, rcl2;
  COLOR foreground, background;
  pm_request pmr;
  menu_data md;
  menubar_data mbd;
  frame_data *f;
  char buf[512], *p;
  int font_id;

  hmq = WinCreateMsgQueue (hab, 0);
  for (;;)
    {
      receive (&pmr, sizeof (pmr));
#if 0
      sprintf (buf, "PMR %d", pmr.header.type);
      WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, buf, "debug", 0,
                     MB_MOVEABLE | MB_OK | MB_ICONEXCLAMATION);
#endif
      switch (pmr.header.type)
        {
        case PMR_CREATE:
          create_frame (&pmr.create);
          break;

        case PMR_DESTROY:
          destroy_frame (pmr.header.frame);
          break;

        case PMR_MODIFY:
          f = find_frame (pmr.header.frame);
          modify_frame (f);
          break;

        case PMR_GLYPHS:
          receive (buf, pmr.glyphs.count);
          f = find_frame (pmr.glyphs.header.frame);
          CURSOR_OFF (f);
          ptl.x = make_x (f, pmr.glyphs.x);
          ptl.y = make_y (f, pmr.glyphs.y);

          /* Note: pmr.glyphs.count must be <= 512 */

          /* Get the font ID for the face.  If no font has been
             created for that face, the font ID is zero.
             font_defined[0] is always zero, therefore make_font()
             will be called. */
          font_id = face_vector[pmr.glyphs.face].font_id;
          if (!f->font_defined[font_id])
            font_id = make_font (f, &face_vector[pmr.glyphs.face]);
          if (font_id != f->cur_charset)
            {
              GpiSetCharSet (f->hpsClient, font_id);
              f->cur_charset = font_id;
            }
          foreground = face_vector[pmr.glyphs.face].foreground;
          if (foreground != f->cur_color)
            {
              GpiSetColor (f->hpsClient, foreground);
              f->cur_color = foreground;
            }
          background = face_vector[pmr.glyphs.face].background;
          if (font_vector[font_id].use_incr)
            {
              if (f->cur_backmix != BM_LEAVEALONE)
                {
                  GpiSetBackMix (f->hpsClient, BM_LEAVEALONE);
                  f->cur_backmix = BM_LEAVEALONE;
                }
              rcl.xLeft = ptl.x;
              rcl.yBottom = ptl.y - f->cyDesc;
              rcl.xRight = rcl.xLeft + pmr.glyphs.count * f->cxChar;
              rcl.yTop = rcl.yBottom + f->cyChar;
              WinFillRect (f->hpsClient, &rcl, background);
              GpiCharStringPosAt (f->hpsClient, &ptl, NULL, CHS_VECTOR,
                                  pmr.glyphs.count, buf, f->increments);
            }
          else
            {
              if (f->cur_backmix != BM_OVERPAINT)
                {
                  GpiSetBackMix (f->hpsClient, BM_OVERPAINT);
                  f->cur_backmix = BM_OVERPAINT;
                }
              if (background != f->cur_backcolor)
                {
                  GpiSetBackColor (f->hpsClient, background);
                  f->cur_backcolor = background;
                }
              GpiCharStringAt (f->hpsClient, &ptl, pmr.glyphs.count, buf);
            }
          CURSOR_BACK (f);
          break;

        case PMR_CLEAR:
          f = find_frame (pmr.glyphs.header.frame);
          CURSOR_OFF (f);
          rcl.xLeft = 0; rcl.xRight = f->cxClient;
          rcl.yBottom = 0; rcl.yTop = f->cyClient;
          WinFillRect (f->hpsClient, &rcl, f->background_color);
          CURSOR_BACK (f);
          break;

        case PMR_CLREOL:
          f = find_frame (pmr.glyphs.header.frame);
          CURSOR_OFF (f);
          rcl.xLeft = make_x (f, pmr.clreol.x0);
          rcl.xRight = make_x (f, pmr.clreol.x1);
          rcl.yBottom = make_y (f, pmr.clreol.y) - f->cyDesc;
          rcl.yTop = rcl.yBottom + f->cyChar;
          WinFillRect (f->hpsClient, &rcl, f->background_color);
          CURSOR_BACK (f);
          break;

        case PMR_LINES:
          f = find_frame (pmr.glyphs.header.frame);
          CURSOR_OFF (f);
          aptl[0].x = 0;
          aptl[1].x = f->cxClient;
          aptl[2].x = 0;
          rcl.xLeft = 0;
          rcl.xRight = f->cxClient;
          if (pmr.lines.count > 0)
            {
              aptl[0].y = make_y (f, pmr.lines.max_y);
              aptl[1].y = make_y (f, pmr.lines.y + pmr.lines.count);
              aptl[2].y = make_y (f, pmr.lines.max_y - pmr.lines.count);
              rcl.yBottom = make_y (f, pmr.lines.y + pmr.lines.count);
              rcl.yTop = make_y (f, pmr.lines.y);
            }
          else
            {
              aptl[0].y = make_y (f, pmr.lines.max_y + pmr.lines.count);
              aptl[1].y = make_y (f, pmr.lines.y);
              aptl[2].y = make_y (f, pmr.lines.max_y);
              rcl.yBottom = make_y (f, pmr.lines.y);
              rcl.yTop = make_y (f, pmr.lines.max_y + pmr.lines.count);
            }
          aptl[0].y += f->cyChar - f->cyDesc;
          aptl[1].y += f->cyChar - f->cyDesc;
          aptl[2].y += f->cyChar - f->cyDesc;
          rcl.yBottom += f->cyChar - f->cyDesc;
          rcl.yTop += f->cyChar - f->cyDesc;

          /* Use GpiBitblt if the source rectangle is completely
             visible.  Otherwise, repaint the target rectangle. */

          rcl2.xLeft = 0; rcl2.xRight = f->cxClient - 1;
          rcl2.yBottom = aptl[2].y;
          rcl2.yTop = aptl[2].y + aptl[1].y - aptl[0].y - 1;
          if (GpiRectVisible (f->hpsClient, &rcl2) == RVIS_VISIBLE)
            GpiBitBlt (f->hpsClient, f->hpsClient, 3, aptl, ROP_SRCCOPY,
                       BBO_IGNORE);
          else
            {
              rcl2.xLeft = 0; rcl2.xRight = f->cxClient;
              rcl2.yBottom = aptl[0].y; rcl2.yTop = aptl[1].y;
              WinInvalidateRect (f->hwndClient, &rcl2, FALSE);
            }

          /* Clear the lines uncovered. */

          WinFillRect (f->hpsClient, &rcl, f->background_color);
          CURSOR_BACK (f);
          break;

        case PMR_CURSOR:

          /* Turn on or off cursor; change cursor position. */

          f = find_frame (pmr.cursor.header.frame);
          f->cursor_x = make_x (f, pmr.cursor.x);
          f->cursor_y = make_y (f, pmr.cursor.y) - f->cyDesc;
          f->cursor_on = pmr.cursor.on;

          if (f->hwndClient == WinQueryFocus (HWND_DESKTOP))
            {
              if (f->cursor_on)
                create_cursor (f);
              else
                WinDestroyCursor (f->hwndClient);
            }
          break;

        case PMR_BELL:

          /* Sound the bell.  We don't want to wait until DosBeep()
             completes, therefore we delegate work to another
             thread. */

          DosPostEventSem (hevBeep);
          break;

        case PMR_NAME:
          f = find_frame (pmr.name.header.frame);
          receive (buf, pmr.name.count);
          buf[pmr.name.count] = 0;
          WinSetWindowText (f->hwndFrame, buf);
          break;

        case PMR_VISIBLE:
          f = find_frame (pmr.visible.header.frame);
          if (pmr.visible.visible)
            WinSetWindowPos (f->hwndFrame, NULLHANDLE, 0, 0, 0, 0,
                             SWP_RESTORE | SWP_SHOW);
          else
            WinShowWindow (f->hwndFrame, FALSE);
          break;

        case PMR_FOCUS:
          f = find_frame (pmr.header.frame);
          WinSetActiveWindow (HWND_DESKTOP, f->hwndFrame);
#if 0
          WinSetWindowPos (f->hwndFrame, HWND_TOP, 0, 0, 0, 0, SWP_ZORDER);
#endif
          break;

        case PMR_ICONIFY:
          f = find_frame (pmr.header.frame);
          WinSetWindowPos (f->hwndFrame, NULLHANDLE, 0, 0, 0, 0, SWP_MINIMIZE);
          break;

        case PMR_SIZE:
          f = find_frame (pmr.size.header.frame);
          f->width = pmr.size.width;
          f->height = pmr.size.height;
          set_size (f, FALSE);
          break;

        case PMR_POPUPMENU:
          f = find_frame (pmr.popupmenu.header.frame);
          if (pmr.popupmenu.size <= sizeof (buf))
            p = buf;
          else
            p = allocate (pmr.popupmenu.size);
          receive (p, pmr.popupmenu.size);
          md.panes = pmr.popupmenu.panes;
          md.lines = pmr.popupmenu.lines;
          md.button = pmr.popupmenu.button;
          md.x = make_x (f, pmr.popupmenu.x);
          md.y = make_y (f, pmr.popupmenu.y);
          md.data = p;
          md.title_size = pmr.popupmenu.title_size;
          WinSendMsg (f->hwndClient, UWM_POPUPMENU,
                      MPFROMP (f), MPFROMP (&md));
          if (p != buf)
            deallocate (p);
          break;

        case PMR_MENUBAR:
          f = find_frame (pmr.menubar.header.frame);
          if (pmr.menubar.size <= sizeof (buf))
            p = buf;
          else
            p = allocate (pmr.menubar.size);
          receive (p, pmr.menubar.size);
          mbd.entries = pmr.menubar.menus;
          mbd.data = p;
          WinSendMsg (f->hwndClient, UWM_MENUBAR_TOP,
                      MPFROMP (f), MPFROMP (&mbd));
          if (p != buf)
            deallocate (p);
          break;

        case PMR_MENU:
          f = find_frame (pmr.menu.header.frame);
          if (pmr.menu.size != 0)
            {
              if (pmr.menu.size <= sizeof (buf))
                p = buf;
              else
                p = allocate (pmr.menu.size);
              receive (p, pmr.menu.size);
              md.panes = pmr.menu.panes;
              md.lines = pmr.menu.lines;
              md.data = p;
              md.title_size = pmr.menu.title_size;
              if (f->menubar_items == NULL)
                f->menubar_items = allocate (1000
                                             * sizeof (*f->menubar_items));
              f->menubar_min = menu_id = IDM_MENUBAR + 1000;
              menu_items = f->menubar_items;
              WinSendMsg (f->hwndClient, UWM_MENUBAR_MENU,
                          MPFROMP (f), MPFROMP (&md));
              f->menubar_max = menu_id;
              if (p != buf)
                deallocate (p);
            }
          WinPostMsg (f->hwndFrame, UWM_MENUBAR_DONE, 0, 0);
          break;

        case PMR_MOUSEPOS:
          get_mousepos ();
          break;

        case PMR_FRAMEPOS:
          f = find_frame (pmr.header.frame);
          get_framepos (f);
          break;

        case PMR_PASTE:
          get_clipboard (pmr.paste.get_text);
          break;

        case PMR_CUT:
          put_clipboard (pmr.cut.size);
          break;

        case PMR_QUITCHAR:
          quit_char = pmr.quitchar.quitchar;
          break;

        case PMR_CLOSE:
          terminate ();
          break;

        case PMR_RAISE:
          f = find_frame (pmr.header.frame);
          WinSetWindowPos (f->hwndFrame, HWND_TOP, 0, 0, 0, 0, SWP_ZORDER);
          break;

        case PMR_LOWER:
          f = find_frame (pmr.header.frame);
          WinSetWindowPos (f->hwndFrame, HWND_BOTTOM, 0, 0, 0, 0, SWP_ZORDER);
          break;

        case PMR_FACE:
          f = find_frame (pmr.face.header.frame);
          receive (buf, pmr.face.name_length);
          buf[pmr.face.name_length] = 0;
          define_face (f, &pmr.face, buf);
          break;

        case PMR_BACKGROUND:
          f = find_frame (pmr.background.header.frame);
          f->background_color = pmr.background.background;
          break;

        case PMR_FONTLIST:
          f = find_frame (pmr.header.frame);
          fontlist (f);
          break;

        case PMR_TRACKMOUSE:
          track_mouse = pmr.track.flag;
          track_x = -1; track_y = -1;
          break;

        case PMR_SETPOS:
          f = find_frame (pmr.setpos.header.frame);
          set_pos (f, pmr.setpos.left, pmr.setpos.top);
          break;

        default:
          error ("Unknown message type");
        }
    }
}


/* Generate sound.  This function is run in a thread of its own.  As
   DosBeep() blocks until the previous DosBeep() ends, we donate a
   separate thread to calling it to avoid blocking the pipe. */

static void beep_thread (ULONG arg)
{
  ULONG count;

  for (;;)
    {
      DosWaitEventSem (hevBeep, SEM_INDEFINITE_WAIT);
      if (DosResetEventSem (hevBeep, &count) == 0)
        while (count > 0)
          {
            DosBeep (880, 50);  /* Same as WA_WARNING */
            --count;
          }
    }
}


/* Terminate the program. */

static void terminate_process (void)
{
  WinDestroyMsgQueue (hmq);
  WinTerminate (hab);
  exit (0);
}


/* Entrypoint.  On the command line, four numbers are passed.  The
   first one is the process ID of Emacs, the other numbers are the
   file handles for the input, output, and out-of-band data pipes,
   respectively. */

int main (int argc, char *argv[])
{
  QMSG qmsg;
  TID tid;
  int i;

  /* Parse the command line. */

  if (argc - 1 != 4)
    return (1);
  emacs_pid = atoi (argv[1+0]);
  inbound_pipe = atoi (argv[1+1]);
  outbound_pipe = atoi (argv[1+2]);
  oob_pipe = atoi (argv[1+3]);

  /* Initialize the (hash) table of frames. */

  for (i = 0; i < HASH_SIZE; ++i)
    hash_table[i] = NULL;

  /* Initialize the Presentation Manager and create a message queue
     for this thread, the PM thread. */

  hab = WinInitialize (0);
  hmq = WinCreateMsgQueue (hab, 0);

  /* Obtain the size of the screen. */

  WinQueryWindowPos (HWND_DESKTOP, &swpScreen);

  /* Register window classes. */

  WinRegisterClass (hab, szClientClass, ClientWndProc,
                    CS_SIZEREDRAW | CS_MOVENOTIFY, 4L);
  WinRegisterClass (hab, szFrameClass, FrameWndProc, 0, 0L);
  WinRegisterClass (hab, szObjectClass, ObjectWndProc, 0, 0L);

  /* Create the object window.  See ObjectWndProc() for details. */

  hwndObject = WinCreateWindow (HWND_OBJECT, szObjectClass, "", 0,
                                0, 0, 0, 0, NULLHANDLE, HWND_BOTTOM,
                                0, NULL, NULL);

  /* Obtain the height of the menubar. */
  cyMenu = WinQuerySysValue (HWND_DESKTOP, SV_CYMENU);

  /* Create the event semaphore for triggering the sound thread. */

  DosCreateEventSem (NULL, &hevBeep, 0, FALSE);

  /* Start the threads used for reading the pipe and for beeping. */

  DosCreateThread (&tid, beep_thread, 0, 2, 0x8000);
  DosCreateThread (&tid, pipe_thread, 0, 2, 0x8000);

  /* This is the message loop of the PM thread.  This loop ends when
     receiving a WM_QUIT message. */

  while (WinGetMsg (hab, &qmsg, 0L, 0, 0))
    WinDispatchMsg (hab, &qmsg);
  terminate_process ();
  return (0);
}
