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

Eli Zaretskii <[email protected]> Sat, 01 Aug 2026 19:01:15 +0300
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
> From: dANiuu zHaO <[email protected]>
> Date: Fri, 31 Jul 2026 23:32:09 +0800
> Cc: [email protected]
> 
> [PATCH 3] Implement preedit text rendering for the IME window on w32
> 
> > This is the second patch.
> > I fixed the static buffer issue from yesterday by switching to on-demand allocation.> 
> > For w32term.c, I removed the direct execution of Lisp code and instead communicate by writing to inev's
> args, following the preedit handling approach in xterm.c.
> > I kept the w32-ime-preedit flag to toggle the default IME window.
> > The hook-based approach has been completely removed. Everything now works through event reading.
> 
> This is a version based on the previous patch. This version adds the following features:
> 
> - When composing CJK strings, the IME window no longer jitters along with the cursor, keeping the behavior
> consistent with the native Windows input method behavior.
> - When composing CJK strings, while Emacs renders the preedit text, the cursor can respond to arrow keys —
> specifically <left> and <right> behavior.
> 
> If you have any questions or concerns about the code, please feel free to contact me.
> 
> I have set up a dedicated example repository to document the development of this patch from scratch:
> https://github.com/zHaOdANiuu/emacs-w32-draw-preedit.patch
> 
> Later on, I will attempt to implement the font size feature described in 
> https://debbugs.gnu.org/cgi/bugreport.cgi?bug=68339
> , in order to improve the overall Windows input method experience in Emacs.

Thanks.  I would prefer to start with what was described in bug#68339.
The reason is twofold: (1) it is a less radical change, in that the
window shown by the IME is still used, we just adjust its font size;
(2) modifying overlays to present the preedit text will cause the
following redisplay of the selected Emacs window to be more thorough,
thus more expensive and slower.  I'm also not sure what this means if
and when the relevant text is in a non-selected window (if this is at
all possible).  This is why I prefer to begin with a more conservative
change that fixes the original problem by adjusting the font of the
preedit window, and leave these more extensive changes (really,
improvements) for later.

A few comments to the code:

> --- a/src/w32term.c
> +++ b/src/w32term.c
> @@ -5387,6 +5387,45 @@ w32_read_socket (struct terminal *terminal,
>  	    }
>  	  break;
>  
> +	case WM_IME_COMPOSITION:
> +	  {
> +	    f = w32_window_to_frame (dpyinfo, msg.msg.hwnd);
> +	    Lisp_Object str = Qnil;
> +	    int size = (int) msg.msg.lParam;
> +
> +	    if (size > 0)
> +	      {
> +		int cursor_pos;
> +		int utf8_len;
> +		char *utf8_buf;
> +		wchar_t *wbuf = (wchar_t *) msg.msg.wParam;
> +		utf8_len = WideCharToMultiByte (CP_UTF8, 0,
> +						wbuf, size / sizeof(wchar_t),
> +						NULL, 0,
> +						NULL, NULL);
> +		utf8_buf = alloca (utf8_len);
> +		utf8_len = WideCharToMultiByte(CP_UTF8, 0,
> +					       wbuf, size / sizeof(wchar_t),
> +					       utf8_buf, utf8_len,
> +					       NULL, NULL);
> +		str = make_string_from_utf8 (utf8_buf, utf8_len);
> +
> +                /* offaet end, read cursor position.  */
> +		memcpy (&cursor_pos, (char *) wbuf + size, sizeof (cursor_pos));
> +                /* see xic_preedit_draw_callback function.  */
> +		Fput_text_property (make_fixnum (min (SCHARS (str), max (0, cursor_pos))),
> +				    make_fixnum (min (SCHARS (str), max (0, cursor_pos) + 1)),
> +				    Qcursor, Qt, str);
> +	      }
> +
> +	    inev.kind = PREEDIT_TEXT_EVENT;
> +	    inev.arg = str;
> +	    inev.modifiers = msg.dwModifiers;
> +	    XSETFRAME (inev.frame_or_window, f);
> +	    inev.timestamp = msg.msg.time;

I believe the processing of the string and the Fput_text_property call
should be moved to where this input event is processed in
make_lispy_event.  The input event structure should have the original
preedit data passed via the inev.arg member, and all the rest of the
processing above should be in make_lispy_event.

> +  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.  */);

The first line of the doc string should be a single complete sentence,
so that commands like 'apropos', which show only the first line, could
present helpful summary of the variable's doc.

More importantly, this doc string explains the technical details of
the effect of the variable, whereas it should explain the effect in
user-level terms.