bug#81506: [PATCH] Extend w32 window events to expose IME composition string interface
dANiuu zHaO <[email protected]>
| Newsgroups | gmane.emacs.bugs |
|---|---|
| Message-ID | <CAEOE9jcM58vHef+04HK67Ex1NLi38adgfG2xoOtFp0CoOJreuA@mail.gmail.com> |
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. >
0001-Extend-w32-window-events-to-expose-IME-composition-string-interface.patch
(application/octet-stream, 6.1 KB)
From f5465c81c95364a40aeb7dff16669280f08a9dbb Mon Sep 17 00:00:00 2001 From: zdn <[email protected]> Date: Tue, 28 Jul 2026 08:40:32 +0800 Subject: [PATCH] Extend w32 window events to expose IME composition string interface 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. --- src/w32fns.c | 83 +++++++++++++++++++++++++++++++++++++++++++++------ src/w32term.c | 25 ++++++++++++++++ 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/src/w32fns.c b/src/w32fns.c index 726fe52..6774a26 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -271,6 +271,13 @@ #define CCHDEVICENAME 32 /* Flag to selectively ignore WM_IME_CHAR messages. */ static int ignore_ime_char = 0; +/* 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]; + /* W95 mousewheel handler */ extern unsigned int msh_mousewheel; unsigned int msh_mousewheel = 0; @@ -5058,15 +5065,41 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) form.ptCurrentPos.x = w32_system_caret_x; form.ptCurrentPos.y = w32_system_caret_y; - form.rcArea.left = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, 0); - form.rcArea.top = (WINDOW_TOP_EDGE_Y (w) - + w32_system_caret_hdr_height); - form.rcArea.right = (WINDOW_BOX_RIGHT_EDGE_X (w) - - WINDOW_RIGHT_MARGIN_WIDTH (w) - - WINDOW_RIGHT_FRINGE_WIDTH (w)); - form.rcArea.bottom = (WINDOW_BOTTOM_EDGE_Y (w) - - WINDOW_BOTTOM_DIVIDER_WIDTH (w) - - w32_system_caret_mode_height); + if (w32_ime_preedit) + { + /* 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 + preedit window is hidden, dynamically toggling the w32_ime_preedit + variable from Elisp cannot bring the original preedit window back. + As a workaround, I resorted to setting the preedit window + coordinates (to off-screen) instead. + The following was my original code: + + case WM_IME_SETCONTEXT: + if (w32_ime_preedit) + lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; + goto dflt; + See https://learn.microsoft.com/en-us/windows/win32/intl/wm-ime-setcontext + */ + form.rcArea.left = -9999; + } + else + { + form.rcArea.left = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, 0); + form.rcArea.top = (WINDOW_TOP_EDGE_Y (w) + + w32_system_caret_hdr_height); + form.rcArea.right = (WINDOW_BOX_RIGHT_EDGE_X (w) + - WINDOW_RIGHT_MARGIN_WIDTH (w) + - WINDOW_RIGHT_FRINGE_WIDTH (w)); + form.rcArea.bottom = (WINDOW_BOTTOM_EDGE_Y (w) + - WINDOW_BOTTOM_DIVIDER_WIDTH (w) + - w32_system_caret_mode_height); + } /* Punt if the window was deleted behind our back. */ if (!BUFFERP (w->contents)) @@ -5088,6 +5121,38 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) supported by the input method... */ goto dflt; + case WM_IME_COMPOSITION: + if (!w32_ime_preedit) + goto dflt; + + if (lParam & GCS_RESULTSTR) + { + my_post_msg (&wmsg, hwnd, WM_IME_COMPOSITION, (WPARAM) "", 0); + } + else if (lParam & GCS_COMPSTR) + { + int size, wlen; + wchar_t *wbuf = alloca (sizeof(composition_ime_buf)); + HIMC context = get_ime_context_fn (hwnd); + + memset(composition_ime_buf, 0, sizeof(composition_ime_buf)); + size = get_composition_string_fn (context, GCS_COMPSTR, + wbuf, sizeof (composition_ime_buf)); + release_ime_context_fn (hwnd, context); + + wlen = WideCharToMultiByte (CP_UTF8, 0, + wbuf, + size / sizeof (wchar_t), + composition_ime_buf, + sizeof (composition_ime_buf), + NULL, NULL); + signal_user_input (); + my_post_msg (&wmsg, hwnd, WM_IME_COMPOSITION, + (WPARAM) &composition_ime_buf, wlen); + } + + goto dflt; + case WM_IME_ENDCOMPOSITION: ignore_ime_char = 0; goto dflt; diff --git a/src/w32term.c b/src/w32term.c index 728f6ce..fd9a3c0 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -5387,6 +5387,15 @@ w32_read_socket (struct terminal *terminal, } break; + 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); + } + break; + case WM_APPCOMMAND: f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); @@ -8392,6 +8401,22 @@ syms_of_w32term (void) API. */); w32_add_wrapped_menu_bar_lines = 1; + 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"); + /* Tell Emacs about this window system. */ Fprovide (Qw32, Qnil); } -- 2.54.0