bug#81506: [PATCH] Extend w32 window events to expose IME composition string interface

Eli Zaretskii <[email protected]>
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
> From: dANiuu zHaO <[email protected]>
> Date: Tue, 28 Jul 2026 14:41:09 +0800
> 
> Sorry, I forgot to attach the patch file. Here it is.
> 
> dANiuu zHaO <[email protected]> 于2026年7月28日周二 10:09写道:
> 
>  hello.
> 
>  This patch adds IME composition string support for the
>  MS-Windows (w32) platform.
> 
>  Add WM_IME_COMPOSITION event processing
> 
>  Handle the composition string from IME and hide the
>  composition string window.  Add an Elisp hook to deliver the
>  composition result to Lisp code.
> 
>  * src/w32fns.c (w32_wnd_proc): Hide composition string window.
>  * src/w32term.c (w32_read_socket): Read composition result to Lisp code.
> 
>  After setting up the hook, the composed string can be drawn using Elisp overlays.
>  I have created a separate repository to demonstrate the functionality of this patch:
> 
>  https://github.com/zHaOdANiuu/emacs-w32-draw-preedit.patch
> 
>  I am in the process of completing the FSF copyright assignment.

Thanks, but please tell more: what is the purpose of these changes and
what functionalities they add/improve?  Emacs on Windows already
supports IME, AFAIK, so does this fix some problems in that support
(and if so, which ones), or does it add features?

I see that you added a hook, but I think we should discuss the use
cases for such a hook.  Also, we generally avoid installing features
on Windows that have no equivalents on GNU/Linux, so does Emacs have
anything similar on X?

When we discussed IME a day or two ago, I thought you wanted to work
on supporting IME in the -nw sessions.  But this patch only affects
the GUI sessions AFAICT.  Is it somehow related to what we discussed,
or is this a separate issue?

A few specific comments to the code:

> +/* Fixed buffer for receiving IME composition (preedit) string.
> +   IME composition strings are typically short, so 64 characters
> +   is sufficient for most cases. The buffer is sized to 2 * 64
> +   bytes because each wide character (wchar_t) occupies 2 bytes
> +   under UTF-16 encoding on Windows. */
> +static char composition_ime_buf[2 * 64];

GNU Coding Standards frown on arbitrary limitations like the above.
Why do we need to have such a small limit?  Can't we use MAX_ALLOCA or
somesuch?

Also, this is a static buffer, so what will happen if another
WM_IME_COMPOSITION message is received before the previous one is
processed by the read_socket_hook?

> +	      /* After hiding the preedit window, the original position is a bit too
> +		 high. Subtracting one line height gives a better vertical balance. */
> +	      form.ptCurrentPos.y += FRAME_LINE_HEIGHT (f);
> +	      /* Setting rcArea to -9999 is to hide the preedit window.
> +		 Based on the documentation description, in the WM_IME_SETCONTEXT
> +		 event, removing the ISC_SHOWUICOMPOSITIONWINDOW flag from lParam
> +		 does work. However, this approach has side effects — once the
                          ^^
Please leave two spaces between sentences, per our conventions, here
and elsewhere.

> +      else if (lParam & GCS_COMPSTR)
> +	{
> +	  int size, wlen;
> +	  wchar_t *wbuf = alloca (sizeof(composition_ime_buf));

This doesn't look right: you are allocating a wchar_t buffer using the
byte count of a 'char' buffer.  This assumes that the wchar_t
characters, when you convert them to UTF-8, will never take more than
2 bytes.  Is that really guaranteed?  I'd prefer to assume at most 4
bytes per character.

> +	  memset(composition_ime_buf, 0, sizeof(composition_ime_buf));
               ^^
Style: we leave one space between the function name and the opening
parenthesis.

> +	  wlen = WideCharToMultiByte (CP_UTF8, 0,
> +				      wbuf,
> +				      size / sizeof (wchar_t),
> +				      composition_ime_buf,
> +				      sizeof (composition_ime_buf),
> +				      NULL, NULL);

Can WideCharToMultiByte possibly return zero (meaning a failure
indication) here?

> +	case WM_IME_COMPOSITION:
> +	  {
> +	    int len = (int) msg.msg.lParam;
> +	    char *buf = (char *) msg.msg.wParam;
> +	    Lisp_Object str = make_string_from_utf8 (buf, len);
> +	    CALLN (Frun_hook_with_args, Qw32_ime_composition_hook, str);

Can't say I like calling Lisp from inside read_socket_hook.  Stefan,
is that safe enough?

> +  DEFVAR_BOOL ("w32-ime-preedit",
> +	       w32_ime_preedit,
> +	       doc: /* Non-nil means report IME preedit strings to
> +`w32-ime-composition-hook'.  When nil, the IME preedit is not
> +reported and the IME works as normal.  */);
> +  w32_ime_preedit = 0;
> +
> +  DEFVAR_LISP ("w32-ime-composition-hook",
> +	       Vw32_ime_composition_hook,
> +	       doc: /* Hook run when the IME composition string changes.
> +This hook is called during IME composition, before the composed text
> +is finalized and inserted into the buffer.  The hook function receives
> +one argument, the current composition string (as a multibyte string).  */);
> +  Vw32_ime_composition_hook = Qnil;
> +  DEFSYM (Qw32_ime_composition_hook, "w32-ime-composition-hook");

Do we really need both a hook and the guard variable?  Can't we use
the hook itself, if non-nil, as the guard?
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.