[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
master ae4ff4f25f 1/2: Support input method ``text conversion'' on X Win
From: |
Po Lu |
Subject: |
master ae4ff4f25f 1/2: Support input method ``text conversion'' on X Windows |
Date: |
Sun, 12 Feb 2023 06:56:52 -0500 (EST) |
branch: master
commit ae4ff4f25fbf704446f8f38d8e818f223b79042b
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>
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.
---
configure.ac | 6 ++
etc/NEWS | 7 ++
src/emacs.c | 3 +-
src/frame.c | 2 +-
src/insdel.c | 38 ++++++++
src/lisp.h | 6 ++
src/window.c | 15 +++
src/xfns.c | 303 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/xterm.c | 41 +++++++-
9 files changed, 407 insertions(+), 14 deletions(-)
diff --git a/configure.ac b/configure.ac
index fc17dbd831..7bb0df88cb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
diff --git a/etc/NEWS b/etc/NEWS
index e0175bacfd..d3eafaadf1 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -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
diff --git a/src/emacs.c b/src/emacs.c
index 214e2e2a29..282e2f4810 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -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
diff --git a/src/frame.c b/src/frame.c
index 983424b0be..2cea96d4a3 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -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)))
diff --git a/src/insdel.c b/src/insdel.c
index e459d0cfa1..b65a3fbd80 100644
--- a/src/insdel.c
+++ b/src/insdel.c
@@ -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. */
diff --git a/src/lisp.h b/src/lisp.h
index 0bc400ba78..cacd318c26 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -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)
diff --git a/src/window.c b/src/window.c
index 6201a6f4a3..9334f922f8 100644
--- a/src/window.c
+++ b/src/window.c
@@ -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. */
diff --git a/src/xfns.c b/src/xfns.c
index 3a12921146..9e004f6a67 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -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);
diff --git a/src/xterm.c b/src/xterm.c
index 1325d923be..5feaa4aef0 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -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)