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?