Support input method ``text conversion'' on X Windows

* configure.ac (HAVE_TEXT_CONVERSION): Define on X.
* etc/NEWS: Announce new change.
* src/emacs.c (main): Always call init_xterm.
* src/frame.c (do_switch_frame): Use `fset_selected_window'.
* src/insdel.c (struct safe_del_range_context): New structure.
(safe_del_range_1, safe_del_range_2, safe_del_range): New
functions.
* src/lisp.h: Export new functions.
* src/window.c (run_window_change_functions): Report selected
window and buffer changes so that the input method can be reset.
* src/xfns.c (XICCallback, Xxic_preedit_caret_callback)
(Xxic_preedit_done_callback, Xxic_preedit_start_callback)
(Xxic_preedit_draw_callback): Fix coding style.
(Xxic_string_conversion_callback): New callback.
(create_frame_xic): Register string conversion callback.
(struct x_xim_text_conversion_data): New field `size'.
(x_encode_xim_text_1, x_encode_xim_text): New functions.
(xic_string_conversion_callback): New function.
* src/xterm.c (x_reset_conversion): New function.
(text_conversion_interface): New variable.
(init_xterm): Initialize text conversion interface.
This commit is contained in:
Po Lu 2023-02-12 19:55:28 +08:00
parent 50140585a2
commit ae4ff4f25f
9 changed files with 407 additions and 14 deletions

View file

@ -6502,6 +6502,12 @@ if test "$window_system" != "none"; then
AC_DEFINE([POLL_FOR_INPUT], [1],
[Define if you poll periodically to detect C-g.])
WINDOW_SYSTEM_OBJ="fontset.o fringe.o image.o"
if test "$window_system" = "x11"; then
AC_DEFINE([HAVE_TEXT_CONVERSION], [1],
[Define if the window system has text conversion support.])
WINDOW_SYSTEM_OBJ="$WINDOW_SYSTEM_OBJ textconv.o"
fi
fi
AC_SUBST([WINDOW_SYSTEM_OBJ])

View file

@ -59,6 +59,13 @@ This allows the user to customize the prompt that is appended by
* Editing Changes in Emacs 30.1
---
** On X, Emacs now supports input methods which perform "string conversion".
This means an input method can now ask Emacs to delete text
surrounding point and replace it with something else, as well as query
Emacs for surrounding text. If your input method allows you to "undo"
mistaken compositions, this will now work as well.
---
** New command 'kill-matching-buffers-no-ask'.
This works like 'kill-matching-buffers', but without asking for

View file

@ -2447,7 +2447,8 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
#ifdef HAVE_DBUS
init_dbusbind ();
#endif
#if defined(USE_GTK) && !defined(HAVE_PGTK)
#ifdef HAVE_X_WINDOWS
init_xterm ();
#endif

View file

@ -1526,7 +1526,7 @@ do_switch_frame (Lisp_Object frame, int for_deletion, Lisp_Object norecord)
if (f->select_mini_window_flag
&& !NILP (Fminibufferp (XWINDOW (f->minibuffer_window)->contents, Qt)))
f->selected_window = f->minibuffer_window;
fset_selected_window (f, f->minibuffer_window);
f->select_mini_window_flag = false;
if (! FRAME_MINIBUF_ONLY_P (XFRAME (selected_frame)))

View file

@ -1715,6 +1715,44 @@ del_range (ptrdiff_t from, ptrdiff_t to)
del_range_1 (from, to, 1, 0);
}
struct safe_del_range_context
{
/* From and to positions. */
ptrdiff_t from, to;
};
static Lisp_Object
safe_del_range_1 (void *ptr)
{
struct safe_del_range_context *context;
context = ptr;
del_range (context->from, context->to);
return Qnil;
}
static Lisp_Object
safe_del_range_2 (enum nonlocal_exit type, Lisp_Object value)
{
return Qt;
}
/* Like del_range; however, catch all non-local exits. Value is 0 if
the buffer contents were really deleted. Otherwise, it is 1. */
int
safe_del_range (ptrdiff_t from, ptrdiff_t to)
{
struct safe_del_range_context context;
context.from = from;
context.to = to;
return !NILP (internal_catch_all (safe_del_range_1,
&context,
safe_del_range_2));
}
/* Like del_range; PREPARE says whether to call prepare_to_modify_buffer.
RET_STRING says to return the deleted text. */

View file

@ -4116,6 +4116,7 @@ extern void del_range_byte (ptrdiff_t, ptrdiff_t);
extern void del_range_both (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, bool);
extern Lisp_Object del_range_2 (ptrdiff_t, ptrdiff_t,
ptrdiff_t, ptrdiff_t, bool);
extern int safe_del_range (ptrdiff_t, ptrdiff_t);
extern void modify_text (ptrdiff_t, ptrdiff_t);
extern void prepare_to_modify_buffer (ptrdiff_t, ptrdiff_t, ptrdiff_t *);
extern void prepare_to_modify_buffer_1 (ptrdiff_t, ptrdiff_t, ptrdiff_t *);
@ -5212,6 +5213,11 @@ extern void syms_of_profiler (void);
extern char *emacs_root_dir (void);
#endif /* DOS_NT */
#ifdef HAVE_TEXT_CONVERSION
/* Defined in textconv.c. */
extern void report_selected_window_change (struct frame *);
#endif
#ifdef HAVE_NATIVE_COMP
INLINE bool
SUBR_NATIVE_COMPILEDP (Lisp_Object a)

View file

@ -3856,6 +3856,9 @@ run_window_change_functions_1 (Lisp_Object symbol, Lisp_Object buffer,
*
* This function does not save and restore match data. Any functions
* it calls are responsible for doing that themselves.
*
* Additionally, report changes to each frame's selected window to the
* input method in textconv.c.
*/
void
run_window_change_functions (void)
@ -4015,6 +4018,18 @@ run_window_change_functions (void)
run_window_change_functions_1
(Qwindow_selection_change_functions, Qnil, frame);
#if defined HAVE_TEXT_CONVERSION
/* If the buffer or selected window has changed, also reset the
input method composition state. */
if ((frame_selected_window_change || frame_buffer_change)
&& FRAME_LIVE_P (f)
&& FRAME_WINDOW_P (f))
report_selected_window_change (f);
#endif
/* A frame has changed state when a size or buffer change
occurred, its selected window has changed, when it was
(de-)selected or its window state change flag was set. */

View file

@ -37,6 +37,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "termhooks.h"
#include "font.h"
#ifdef HAVE_X_I18N
#include "textconv.h"
#endif
#include <sys/types.h>
#include <sys/stat.h>
@ -2671,24 +2675,50 @@ append_wm_protocols (struct x_display_info *dpyinfo,
#ifdef HAVE_X_I18N
static void xic_preedit_draw_callback (XIC, XPointer, XIMPreeditDrawCallbackStruct *);
static void xic_preedit_caret_callback (XIC, XPointer, XIMPreeditCaretCallbackStruct *);
static void xic_preedit_draw_callback (XIC, XPointer,
XIMPreeditDrawCallbackStruct *);
static void xic_preedit_caret_callback (XIC, XPointer,
XIMPreeditCaretCallbackStruct *);
static void xic_preedit_done_callback (XIC, XPointer, XPointer);
static int xic_preedit_start_callback (XIC, XPointer, XPointer);
static void xic_string_conversion_callback (XIC, XPointer,
XIMStringConversionCallbackStruct *);
#ifndef HAVE_XICCALLBACK_CALLBACK
#define XICCallback XIMCallback
#define XICProc XIMProc
#endif
static XIMCallback Xxic_preedit_draw_callback = { NULL,
(XIMProc) xic_preedit_draw_callback };
static XIMCallback Xxic_preedit_caret_callback = { NULL,
(XIMProc) xic_preedit_caret_callback };
static XIMCallback Xxic_preedit_done_callback = { NULL,
(XIMProc) xic_preedit_done_callback };
static XICCallback Xxic_preedit_start_callback = { NULL,
(XICProc) xic_preedit_start_callback };
static XIMCallback Xxic_preedit_draw_callback =
{
NULL,
(XIMProc) xic_preedit_draw_callback,
};
static XIMCallback Xxic_preedit_caret_callback =
{
NULL,
(XIMProc) xic_preedit_caret_callback,
};
static XIMCallback Xxic_preedit_done_callback =
{
NULL,
(XIMProc) xic_preedit_done_callback,
};
static XICCallback Xxic_preedit_start_callback =
{
NULL,
(XICProc) xic_preedit_start_callback,
};
static XIMCallback Xxic_string_conversion_callback =
{
/* This is actually an XICCallback! */
NULL,
(XIMProc) xic_string_conversion_callback,
};
#if defined HAVE_X_WINDOWS && defined USE_X_TOOLKIT
/* Create an X fontset on frame F with base font name BASE_FONTNAME. */
@ -3094,6 +3124,8 @@ create_frame_xic (struct frame *f)
XNFocusWindow, FRAME_X_WINDOW (f),
XNStatusAttributes, status_attr,
XNPreeditAttributes, preedit_attr,
XNStringConversionCallback,
&Xxic_string_conversion_callback,
NULL);
else if (preedit_attr)
xic = XCreateIC (xim,
@ -3101,6 +3133,8 @@ create_frame_xic (struct frame *f)
XNClientWindow, FRAME_X_WINDOW (f),
XNFocusWindow, FRAME_X_WINDOW (f),
XNPreeditAttributes, preedit_attr,
XNStringConversionCallback,
&Xxic_string_conversion_callback,
NULL);
else if (status_attr)
xic = XCreateIC (xim,
@ -3108,12 +3142,16 @@ create_frame_xic (struct frame *f)
XNClientWindow, FRAME_X_WINDOW (f),
XNFocusWindow, FRAME_X_WINDOW (f),
XNStatusAttributes, status_attr,
XNStringConversionCallback,
&Xxic_string_conversion_callback,
NULL);
else
xic = XCreateIC (xim,
XNInputStyle, xic_style,
XNClientWindow, FRAME_X_WINDOW (f),
XNFocusWindow, FRAME_X_WINDOW (f),
XNStringConversionCallback,
&Xxic_string_conversion_callback,
NULL);
if (!xic)
@ -3377,6 +3415,7 @@ struct x_xim_text_conversion_data
struct coding_system *coding;
char *source;
struct x_display_info *dpyinfo;
size_t size;
};
static Lisp_Object
@ -3411,6 +3450,38 @@ x_xim_text_to_utf8_unix_1 (ptrdiff_t nargs, Lisp_Object *args)
return Qnil;
}
static Lisp_Object
x_encode_xim_text_1 (ptrdiff_t nargs, Lisp_Object *args)
{
struct x_xim_text_conversion_data *data;
ptrdiff_t nbytes;
Lisp_Object coding_system;
data = xmint_pointer (args[0]);
if (SYMBOLP (Vx_input_coding_system))
coding_system = Vx_input_coding_system;
else if (!NILP (data->dpyinfo->xim_coding))
coding_system = data->dpyinfo->xim_coding;
else
coding_system = Vlocale_coding_system;
nbytes = data->size;
data->coding->destination = NULL;
setup_coding_system (coding_system, data->coding);
data->coding->mode |= (CODING_MODE_LAST_BLOCK
| CODING_MODE_SAFE_ENCODING);
data->coding->source = (const unsigned char *) data->source;
data->coding->dst_bytes = 2048;
data->coding->destination = xmalloc (2048);
encode_coding_object (data->coding, Qnil, 0, 0,
nbytes, nbytes, Qnil);
return Qnil;
}
static Lisp_Object
x_xim_text_to_utf8_unix_2 (Lisp_Object val, ptrdiff_t nargs,
Lisp_Object *args)
@ -3468,6 +3539,46 @@ x_xim_text_to_utf8_unix (struct x_display_info *dpyinfo,
return (char *) coding.destination;
}
/* Convert SIZE bytes of the specified text from Emacs's internal
coding system to the input method coding system. Return the
result, its byte length in *LENGTH, and its character length in
*CHARS, or NULL.
The string returned is not NULL terminated. */
static char *
x_encode_xim_text (struct x_display_info *dpyinfo, char *text,
size_t size, ptrdiff_t *length,
ptrdiff_t *chars)
{
struct coding_system coding;
struct x_xim_text_conversion_data data;
Lisp_Object arg;
bool was_waiting_for_input_p;
data.coding = &coding;
data.source = text;
data.dpyinfo = dpyinfo;
data.size = size;
was_waiting_for_input_p = waiting_for_input;
/* Otherwise Fsignal will crash. */
waiting_for_input = false;
arg = make_mint_ptr (&data);
internal_condition_case_n (x_encode_xim_text_1, 1, &arg,
Qt, x_xim_text_to_utf8_unix_2);
waiting_for_input = was_waiting_for_input_p;
if (length)
*length = coding.produced;
if (chars)
*chars = coding.produced_char;
return (char *) coding.destination;
}
static void
xic_preedit_draw_callback (XIC xic, XPointer client_data,
XIMPreeditDrawCallbackStruct *call_data)
@ -3664,6 +3775,128 @@ xic_set_xfontset (struct frame *f, const char *base_fontname)
FRAME_XIC_FONTSET (f) = xfs;
}
/* String conversion support. See textconv.c for more details. */
static void
xic_string_conversion_callback (XIC ic, XPointer client_data,
XIMStringConversionCallbackStruct *call_data)
{
struct textconv_callback_struct request;
ptrdiff_t length;
struct frame *f;
int rc;
/* Find the frame associated with this IC. */
f = x_xic_to_frame (ic);
if (!f)
goto failure;
/* Fill in CALL_DATA as early as possible. */
call_data->text->feedback = NULL;
call_data->text->encoding_is_wchar = False;
/* Now translate the conversion request to the format understood by
textconv.c. */
request.position = call_data->position;
switch (call_data->direction)
{
case XIMForwardChar:
request.direction = TEXTCONV_FORWARD_CHAR;
break;
case XIMBackwardChar:
request.direction = TEXTCONV_BACKWARD_CHAR;
break;
case XIMForwardWord:
request.direction = TEXTCONV_FORWARD_WORD;
break;
case XIMBackwardWord:
request.direction = TEXTCONV_BACKWARD_WORD;
break;
case XIMCaretUp:
request.direction = TEXTCONV_CARET_UP;
break;
case XIMCaretDown:
request.direction = TEXTCONV_CARET_DOWN;
break;
case XIMNextLine:
request.direction = TEXTCONV_NEXT_LINE;
break;
case XIMPreviousLine:
request.direction = TEXTCONV_PREVIOUS_LINE;
break;
case XIMLineStart:
request.direction = TEXTCONV_LINE_START;
break;
case XIMLineEnd:
request.direction = TEXTCONV_LINE_END;
break;
case XIMAbsolutePosition:
request.direction = TEXTCONV_ABSOLUTE_POSITION;
break;
default:
goto failure;
}
/* factor is signed in call_data but is actually a CARD16. */
request.factor = call_data->factor;
if (call_data->operation == XIMStringConversionSubstitution)
request.operation = TEXTCONV_SUBSTITUTION;
else
request.operation = TEXTCONV_RETRIEVAL;
/* Now perform the string conversion. */
rc = textconv_query (f, &request);
if (rc)
{
xfree (request.text.text);
goto failure;
}
/* Encode the text in the locale coding system and give it back to
the input method. */
request.text.text = NULL;
call_data->text->string.mbs
= x_encode_xim_text (FRAME_DISPLAY_INFO (f),
request.text.text,
request.text.bytes, NULL,
&length);
call_data->text->length = length;
/* Free the encoded text. This is always set to something
valid. */
xfree (request.text.text);
/* Detect failure. */
if (!call_data->text->string.mbs)
goto failure;
return;
failure:
/* Return a string of length 0 using the C library malloc. This
assumes XFree is able to free data allocated with our malloc
wrapper. */
call_data->text->length = 0;
call_data->text->string.mbs = malloc (0);
}
#endif /* HAVE_X_I18N */
@ -9771,6 +10004,53 @@ This should be called from a variable watcher for `x-gtk-use-native-input'. */)
return Qnil;
}
#if 0
DEFUN ("x-test-string-conversion", Fx_test_string_conversion,
Sx_test_string_conversion, 5, 5, 0,
doc: /* Perform tests on the XIM string conversion support. */)
(Lisp_Object frame, Lisp_Object position,
Lisp_Object direction, Lisp_Object operation, Lisp_Object factor)
{
struct frame *f;
XIMStringConversionCallbackStruct call_data;
XIMStringConversionText text;
f = decode_window_system_frame (frame);
if (!FRAME_XIC (f))
error ("No XIC on FRAME!");
CHECK_FIXNUM (position);
CHECK_FIXNUM (direction);
CHECK_FIXNUM (operation);
CHECK_FIXNUM (factor);
/* xic_string_conversion_callback (XIC ic, XPointer client_data,
XIMStringConversionCallbackStruct *call_data) */
call_data.position = XFIXNUM (position);
call_data.direction = XFIXNUM (direction);
call_data.operation = XFIXNUM (operation);
call_data.factor = XFIXNUM (factor);
call_data.text = &text;
block_input ();
xic_string_conversion_callback (FRAME_XIC (f), NULL,
&call_data);
unblock_input ();
/* Place a breakpoint here to inspect TEXT! */
while (1)
maybe_quit ();
return Qnil;
}
#endif
/***********************************************************************
Initialization
@ -10217,6 +10497,9 @@ eliminated in future versions of Emacs. */);
defsubr (&Sx_display_set_last_user_time);
defsubr (&Sx_translate_coordinates);
defsubr (&Sx_get_modifier_masks);
#if 0
defsubr (&Sx_test_string_conversion);
#endif
tip_timer = Qnil;
staticpro (&tip_timer);

View file

@ -636,6 +636,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "xterm.h"
#include <X11/cursorfont.h>
#ifdef HAVE_X_I18N
#include "textconv.h"
#endif
#ifdef USE_XCB
#include <xcb/xproto.h>
#include <xcb/xcb.h>
@ -31506,7 +31510,37 @@ x_initialize (void)
XSetIOErrorHandler (x_io_error_quitter);
}
#ifdef USE_GTK
#ifdef HAVE_X_I18N
/* Notice that a change has occured on F that requires its input
method state to be reset. */
static void
x_reset_conversion (struct frame *f)
{
char *string;
if (FRAME_XIC (f))
{
string = XmbResetIC (FRAME_XIC (f));
/* string is actually any string that was being composed at the
time of the reset. */
if (string)
XFree (string);
}
}
/* Interface used to control input method ``text conversion''. */
static struct textconv_interface text_conversion_interface =
{
x_reset_conversion,
};
#endif
void
init_xterm (void)
{
@ -31520,8 +31554,11 @@ init_xterm (void)
gdk_disable_multidevice ();
#endif
#endif
}
#ifdef HAVE_X_I18N
register_texconv_interface (&text_conversion_interface);
#endif
}
void
mark_xterm (void)