view src/msw-proc.c @ 213:78f53ef88e17 r20-4b5

Import from CVS: tag r20-4b5
author cvs
date Mon, 13 Aug 2007 10:06:47 +0200
parents
children d44af0c54775
line wrap: on
line source

/* mswindows specific event-handling.
   Copyright (C) 1997 Jonathan Harris.

This file is part of XEmacs.

XEmacs 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.

XEmacs 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 XEmacs; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

/* Synched up with: Not in FSF. */

/* Authorship:

   Jonathan Harris, November 1997 for 20.4.
 */

/*
 * Comment:
 *
 * Windows user-input type events are stored in a per-thread message queue
 * and retrieved using GetMessage(). It is not possible to wait on this
 * queue and on other events (eg process input) simultaneously. Also, the
 * main event-handling code in windows (the "windows procedure") is called
 * asynchronously when windows has certain other types of events ("nonqueued
 * messages") to deliver. The documentation doesn't appear to specify the
 * context in which the windows procedure is called, but I assume that the
 * thread that created the window is temporarily highjacked for this purpose.
 *
 * We spawn off a single thread to deal with both kinds of messages. The
 * thread turns the windows events into emacs_events and stuffs them in a
 * queue which XEmacs reads at its leisure. This file contains the code for
 * the thread. This scheme also helps to prevent weird synchronisation and
 * deadlock problems that might occur if the windows procedure was called
 * when XEmacs was already in the middle of processing an event. 
 *
 * Unfortunately, only the thread that created a window can retrieve messages
 * destined for that window ("GetMessage does not retrieve messages for
 * windows that belong to other threads..."). This means that our message-
 * processing thread also has to do all window creation. We handle this
 * bogosity by getting the main XEmacs thread to send special user-defined
 * messages to the message-processing thread to instruct it to create windows.
 */


#include <config.h>
#include "lisp.h"

#include "console-msw.h"
#include "device.h"
#include "frame.h"
#include "events.h"
#include "event-msw.h"

#define MSWINDOWS_FRAME_STYLE WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_TILEDWINDOW
#define MSWINDOWS_POPUP_STYLE WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_CAPTION|WS_POPUP

static LRESULT WINAPI mswindows_wnd_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
static Lisp_Object mswindows_find_console (HWND hwnd);
static Lisp_Object mswindows_find_frame (HWND hwnd);
static Lisp_Object mswindows_key_to_emacs_keysym(int mswindows_key);

/*
 * Entry point for the "windows" message-processing thread
 */
DWORD mswindows_win_thread()
{
  WNDCLASS wc;
  MSG msg;
  mswindows_waitable_info_type info;

  /* Register the main window class */
  wc.style = CS_OWNDC;	/* One DC per window */
  wc.lpfnWndProc = (WNDPROC) mswindows_wnd_proc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;	/* ? */
  wc.hInstance = NULL;	/* ? */
  wc.hIcon = LoadIcon (NULL, XEMACS_CLASS);
  wc.hCursor = LoadCursor (NULL, IDC_ARROW);
  wc.hbrBackground = NULL; /* GetStockObject (WHITE_BRUSH); */
  wc.lpszMenuName = NULL;	/* XXX FIXME? Add a menu? */
  wc.lpszClassName = XEMACS_CLASS;
  RegisterClass(&wc);		/* XXX FIXME: Should use RegisterClassEx */

  info.type = mswindows_waitable_type_dispatch;
  mswindows_add_waitable(&info);

  /* Ensure our message queue is created XXX FIXME: Is this necessary? */
  PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE);

  /* Notify the main thread that we're ready */
  assert(PostThreadMessage (mswindows_main_thread_id, WM_XEMACS_ACK, 0, 0));

  /* Main windows loop */
  while (1)
  {
    GetMessage (&msg, NULL, 0, 0);

    /*
     * Process things that don't have an associated window, so wouldn't
     * get sent to mswindows_wnd_proc
     */

    /* Request from main thread */
    if (msg.message>=WM_XEMACS_BASE && msg.message<=WM_XEMACS_END)
      mswindows_handle_request(&msg);

    /* Timeout */
    else if (msg.message ==  WM_TIMER)
    {
      Lisp_Object emacs_event;
      struct Lisp_Event *event;

      KillTimer(NULL, msg.wParam);
      EnterCriticalSection (&mswindows_dispatch_crit);
      emacs_event = Fmake_event (Qnil, Qnil);
      event = XEVENT(emacs_event);

      event->channel = Qnil;
      event->timestamp = msg.time;
      event->event_type = timeout_event;
      event->event.timeout.interval_id = msg.wParam;
      mswindows_enqueue_dispatch_event (emacs_event);
      LeaveCriticalSection (&mswindows_dispatch_crit);
    }
    else
      /* Pass on to mswindows_wnd_proc */
      DispatchMessage (&msg);
  }
}

/*
 * The windows procedure for the window class XEMACS_CLASS
 * Stuffs messages in the mswindows event queue
 */
static LRESULT WINAPI mswindows_wnd_proc(HWND hwnd, UINT message, WPARAM wParam,
				   LPARAM lParam)
{
  /* Note: Remember to initialise these before use */
  Lisp_Object emacs_event;
  struct Lisp_Event *event;

  static int mods = 0;
  MSG msg = { hwnd, message, wParam, lParam, 0, {0,0} };
  msg.time = GetMessageTime();

#if 0 /* XXX */
  stderr_out("Message %04x, wParam=%04x, lParam=%08lx\n", message, wParam, lParam);
#endif
  switch (message)
  {
  case WM_KEYDOWN:
  case WM_SYSKEYDOWN:
    switch(wParam)
    {
    case VK_SHIFT:
      mods |= MOD_SHIFT;
      break;
    case VK_CONTROL:
      mods |= MOD_CONTROL;
      break;
    case VK_MENU:
      mods |= MOD_META;
      break;
    default:
      /* Handle those keys that TranslateMessage won't generate a WM_CHAR for */
      {
        Lisp_Object keysym;
        if (!NILP (keysym = mswindows_key_to_emacs_keysym(wParam)))
	{
          EnterCriticalSection (&mswindows_dispatch_crit);
	  emacs_event = Fmake_event (Qnil, Qnil);
	  event = XEVENT(emacs_event);

          event->channel = mswindows_find_console(hwnd);
          event->timestamp = msg.time;
          event->event_type = key_press_event;
          event->event.key.keysym = keysym;
	  event->event.key.modifiers = mods;
	  mswindows_enqueue_dispatch_event (emacs_event);
          LeaveCriticalSection (&mswindows_dispatch_crit);
	  return (0);
	}
      }
    }
    TranslateMessage (&msg);  /* Maybe generates WM_[SYS]CHAR in message queue */
    goto defproc;

  case WM_KEYUP:
  case WM_SYSKEYUP:
    switch(wParam)
    {
    case VK_SHIFT:
      mods &= ~MOD_SHIFT;
      break;
    case VK_CONTROL:
      mods &= ~MOD_CONTROL;
      break;
    case VK_MENU:
      mods &= ~MOD_META;
      break;
    }
    TranslateMessage (&msg);
    goto defproc;

  case WM_CHAR:
  case WM_SYSCHAR:
    {
      EnterCriticalSection (&mswindows_dispatch_crit);
      emacs_event = Fmake_event (Qnil, Qnil);
      event = XEVENT(emacs_event);

      event->channel = mswindows_find_console(hwnd);
      event->timestamp = msg.time;
      event->event_type = key_press_event;
      event->event.key.modifiers = mods;
      event->event.key.modifiers = lParam & 0x20000000 ? MOD_META : 0; /* redundant? */
      if (wParam<' ')	/* Control char not handled under WM_KEYDOWN */
      {
	event->event.key.keysym = make_char(wParam+'a'-1);
	event->event.key.modifiers |= MOD_CONTROL;   /* redundant? */
      }
      else
      {
	/* Assumes that emacs keysym == ASCII code */
	event->event.key.keysym = make_char(wParam);
      }
      mswindows_enqueue_dispatch_event (emacs_event);
      LeaveCriticalSection (&mswindows_dispatch_crit);
    }
    break;

  case WM_LBUTTONDOWN:
  case WM_MBUTTONDOWN:
  case WM_RBUTTONDOWN:
  case WM_LBUTTONUP:
  case WM_MBUTTONUP:
  case WM_RBUTTONUP:
    {
      /* XXX FIXME: Do middle button emulation */
      EnterCriticalSection (&mswindows_dispatch_crit);
      emacs_event = Fmake_event (Qnil, Qnil);
      event = XEVENT(emacs_event);

      event->channel = mswindows_find_frame(hwnd);
      event->timestamp = msg.time;
      event->event_type =
	(message==WM_LBUTTONDOWN || message==WM_MBUTTONDOWN ||
	 message==WM_RBUTTONDOWN) ?
	 button_press_event : button_release_event;
#if 0
	((wParam & MK_CONTROL) ? MOD_CONTROL : 0) |
	 ((wParam & MK_SHIFT) ? MOD_SHIFT : 0);
#endif
      event->event.button.button =
	(message==WM_LBUTTONDOWN || message==WM_LBUTTONUP) ? 1 :
	 ((message==WM_RBUTTONDOWN || message==WM_RBUTTONUP) ? 3 : 2);
      event->event.button.x = LOWORD(lParam);
      event->event.button.y = HIWORD(lParam);
      event->event.button.modifiers = mods;
      
      mswindows_enqueue_dispatch_event (emacs_event);
      LeaveCriticalSection (&mswindows_dispatch_crit);
    }
    break;

  case WM_MOUSEMOVE:
    {
      EnterCriticalSection (&mswindows_dispatch_crit);
      emacs_event = Fmake_event (Qnil, Qnil);
      event = XEVENT(emacs_event);

      event->channel = mswindows_find_frame(hwnd);
      event->timestamp = msg.time;
      event->event_type = pointer_motion_event;
      event->event.motion.x = LOWORD(lParam);
      event->event.motion.y = HIWORD(lParam);
      event->event.motion.modifiers = mods;
      
      mswindows_enqueue_dispatch_event (emacs_event);
      LeaveCriticalSection (&mswindows_dispatch_crit);
    }
    break;

  case WM_PAINT:
    if (GetUpdateRect(hwnd, NULL, FALSE))
    {
      PAINTSTRUCT paintStruct;

      EnterCriticalSection (&mswindows_dispatch_crit);
      emacs_event = Fmake_event (Qnil, Qnil);
      event = XEVENT(emacs_event);

      event->channel = mswindows_find_frame(hwnd);
      event->timestamp = msg.time;
      event->event_type = magic_event;
      BeginPaint (hwnd, &paintStruct);
      EVENT_MSWINDOWS_MAGIC_TYPE(event) = message;
      EVENT_MSWINDOWS_MAGIC_DATA(event) = paintStruct.rcPaint;
      EndPaint (hwnd, &paintStruct);

      mswindows_enqueue_dispatch_event (emacs_event);
      LeaveCriticalSection (&mswindows_dispatch_crit);
    }
    break;

  case WM_SIZE:
    /* We only care about this message if our size has really changed */
    if (wParam==SIZE_RESTORED || wParam==SIZE_MAXIMIZED || wParam==SIZE_MINIMIZED)
    {
      RECT rect;
      EnterCriticalSection (&mswindows_dispatch_crit);
      emacs_event = Fmake_event (Qnil, Qnil);
      event = XEVENT(emacs_event);

      event->channel = mswindows_find_frame(hwnd);
      event->timestamp = msg.time;
      event->event_type = magic_event;
      if (wParam==SIZE_MINIMIZED)
	rect.left = rect.top = rect.right = rect.bottom = -1;
      else
	GetClientRect(hwnd, &rect);
      EVENT_MSWINDOWS_MAGIC_TYPE(event) = message;
      EVENT_MSWINDOWS_MAGIC_DATA(event) = rect;

      mswindows_enqueue_dispatch_event (emacs_event);
      LeaveCriticalSection (&mswindows_dispatch_crit);
    }
    break;

  case WM_SETFOCUS:
  case WM_KILLFOCUS:
    {
      EnterCriticalSection (&mswindows_dispatch_crit);
      emacs_event = Fmake_event (Qnil, Qnil);
      event = XEVENT(emacs_event);

      event->channel = mswindows_find_frame(hwnd);
      event->timestamp = msg.time;
      event->event_type = magic_event;
      EVENT_MSWINDOWS_MAGIC_TYPE(event) = message;

      mswindows_enqueue_dispatch_event (emacs_event);
      LeaveCriticalSection (&mswindows_dispatch_crit);
    }
    break;

  case WM_QUIT:
    /* XXX FIXME: Should do something here! */
  defproc:
  default:
    return DefWindowProc (hwnd, message, wParam, lParam);
  }
  return (0);
}


/*
 * Make a request to the message-processing thread to do things that
 * can't be done in the main thread.
 */
LPARAM
mswindows_make_request(UINT message, WPARAM wParam, mswindows_request_type *request)
{
  MSG msg;
  assert(PostThreadMessage (mswindows_win_thread_id, message, wParam,
			    (LPARAM) request));
  GetMessage (&msg, NULL, WM_XEMACS_ACK, WM_XEMACS_ACK);
  return (msg.lParam);
}


/* 
 * Handle a request from the main thread to do things that have to be
 * done in the message-processing thread.
 */
static void
mswindows_handle_request (MSG *msg)
{
  mswindows_request_type *request = (mswindows_request_type *) msg->lParam;

  switch (msg->message)
  {
  case WM_XEMACS_CREATEWINDOW:
    {
    struct frame *f = request->thing1;
    Lisp_Object *props = request->thing2;
    Lisp_Object name, height, width, popup, top, left;
    RECT rect;
    DWORD style;
    HWND hwnd;

    name = Fplist_get (*props, Qname, Qnil);
    height = Fplist_get (*props, Qheight, Qnil);
    width = Fplist_get (*props, Qwidth, Qnil);
    popup = Fplist_get (*props, Qpopup, Qnil);
    top = Fplist_get (*props, Qtop, Qnil);
    left = Fplist_get (*props, Qleft, Qnil);

    style = (NILP(popup)) ? MSWINDOWS_FRAME_STYLE : MSWINDOWS_POPUP_STYLE;

    rect.left = rect.top = 0;
    rect.right = INTP(width) ? XINT(width) : 640;
    rect.bottom = INTP(height) ? XINT(height) : 480;
#ifdef HAVE_MENUBARS
    AdjustWindowRect(&rect, style, TRUE);
#else
    AdjustWindowRect(&rect, style, FALSE);
#endif

    hwnd = CreateWindow (XEMACS_CLASS,
	STRINGP(f->name) ? XSTRING_DATA(f->name) :
	  (STRINGP(name) ? XSTRING_DATA(name) : XEMACS_CLASS),
	style,
	INTP(left) ? XINT(left) : CW_USEDEFAULT,
	INTP(top) ? XINT(top) : CW_USEDEFAULT,
	rect.right-rect.left, rect.bottom-rect.top,
	NULL, NULL, NULL, NULL);
    assert(PostThreadMessage (mswindows_main_thread_id, WM_XEMACS_ACK, 0, (LPARAM) hwnd));
    }
    return;

  case WM_XEMACS_SETTIMER:
    {
    UINT id;
    id=SetTimer (NULL, 0, (UINT) request->thing1, NULL);
    assert(PostThreadMessage (mswindows_main_thread_id, WM_XEMACS_ACK, 0, id));
    }
    break;

  case WM_XEMACS_KILLTIMER:
    {
    KillTimer (NULL, (UINT) request->thing1);
    assert(PostThreadMessage (mswindows_main_thread_id, WM_XEMACS_ACK, 0, 0));
    }
    break;

  default:
    assert(0);
  }
}


/*
 * Translate a mswindows virtual key to a keysym.
 * Only returns non-Qnil for keys that don't generate WM_CHAR messages
 * or whose ASCII codes (like space) xemacs doesn't like.
 * Virtual key values are defined in winresrc.h
 * XXX I'm not sure that KEYSYM("name") is the best thing to use here.
 */
Lisp_Object mswindows_key_to_emacs_keysym(int mswindows_key)
{
  switch (mswindows_key)
  {
  /* First the predefined ones */
  case VK_BACK:		return QKbackspace;
  case VK_TAB:		return QKtab;
  case '\n':		return QKlinefeed;  /* No VK_LINEFEED in winresrc.h */
  case VK_RETURN:	return QKreturn;
  case VK_ESCAPE:	return QKescape;
  case VK_SPACE:	return QKspace;
  case VK_DELETE:	return QKdelete;

  /* The rest */
  case VK_PRIOR:	return KEYSYM ("prior");
  case VK_NEXT:		return KEYSYM ("next");
  case VK_END:		return KEYSYM ("end");
  case VK_HOME:		return KEYSYM ("home");
  case VK_LEFT:		return KEYSYM ("left");
  case VK_UP:		return KEYSYM ("up");
  case VK_RIGHT:	return KEYSYM ("right");
  case VK_DOWN:		return KEYSYM ("down");
  case VK_INSERT:	return KEYSYM ("insert");
  case VK_HELP:		return KEYSYM ("help");
  case VK_F1:		return KEYSYM ("F1");
  case VK_F2:		return KEYSYM ("F2");
  case VK_F3:		return KEYSYM ("F3");
  case VK_F4:		return KEYSYM ("F4");
  case VK_F5:		return KEYSYM ("F5");
  case VK_F6:		return KEYSYM ("F6");
  case VK_F7:		return KEYSYM ("F7");
  case VK_F8:		return KEYSYM ("F8");
  case VK_F9:		return KEYSYM ("F9");
  case VK_F10:		return KEYSYM ("F10");
  case VK_F11:		return KEYSYM ("F11");
  case VK_F12:		return KEYSYM ("F12");
  case VK_F13:		return KEYSYM ("F13");
  case VK_F14:		return KEYSYM ("F14");
  case VK_F15:		return KEYSYM ("F15");
  case VK_F16:		return KEYSYM ("F16");
  case VK_F17:		return KEYSYM ("F17");
  case VK_F18:		return KEYSYM ("F18");
  case VK_F19:		return KEYSYM ("F19");
  case VK_F20:		return KEYSYM ("F20");
  case VK_F21:		return KEYSYM ("F21");
  case VK_F22:		return KEYSYM ("F22");
  case VK_F23:		return KEYSYM ("F23");
  case VK_F24:		return KEYSYM ("F24");
  }
  return Qnil;
}


/*
 * Find the console that matches the supplied mswindows window handle
 */
static Lisp_Object
mswindows_find_console (HWND hwnd)
{
  Lisp_Object concons;

  CONSOLE_LOOP (concons)
    {
      Lisp_Object console = XCAR (concons);
      /* We only support one console so this must be it */
      return console;
    }

  return Qnil;
}

/*
 * Find the frame that matches the supplied mswindows window handle
 */
static Lisp_Object
mswindows_find_frame (HWND hwnd)
{
  Lisp_Object frmcons, devcons, concons;

  FRAME_LOOP_NO_BREAK (frmcons, devcons, concons)
    {
      struct frame *f;
      Lisp_Object frame = XCAR (frmcons);
      f = XFRAME (frame);
      if (FRAME_TYPE_P(f, mswindows))	    /* Might be a stream-type frame */
	if (FRAME_MSWINDOWS_HANDLE(f)==hwnd)
	  return frame;
    }
  assert(0);  /* XXX Can't happen! we only get messages for our windows */
  return Qnil;
}

/*
 * Random helper functions for debugging.
 * Intended for use in the MSVC "Watch" window which doesn't like
 * the aborts that the error_check_foo() functions can make.
 */
struct lrecord_header *DHEADER(Lisp_Object obj)
{
  return LRECORDP(obj) ? XRECORD_LHEADER(obj) : NULL;
    /* (lrecord_header*)(obj & 0xfffffff) */
}

struct Lisp_Event *DEVENT(Lisp_Object obj)
{
  return (EVENTP (obj)) ? XEVENT(obj) : NULL;
}

struct Lisp_Cons *DCONS(Lisp_Object obj)
{
  return (CONSP (obj)) ? XCONS(obj) : NULL;
}

Lisp_Object DCAR(Lisp_Object obj)
{
  return (CONSP (obj)) ? XCAR(obj) : 0;
}

Lisp_Object DCDR(Lisp_Object obj)
{
  return (CONSP (obj)) ? XCDR(obj) : 0;
}

char *DSTRING(Lisp_Object obj)
{
  return (STRINGP (obj)) ? XSTRING_DATA(obj) : NULL;
}

struct Lisp_Vector *DVECTOR(Lisp_Object obj)
{
  return (VECTORP (obj)) ? XVECTOR(obj) : NULL;
}

struct Lisp_Symbol *DSYMBOL(Lisp_Object obj)
{
  return (SYMBOLP (obj)) ? XSYMBOL(obj) : NULL;
}

char *DSYMNAME(Lisp_Object obj)
{
  return (SYMBOLP (obj)) ? XSYMBOL(obj)->name->_data : NULL;
}