liberty-xui: add support for positioned preedit
Alpine 3.24 Success
OpenBSD 7.8 Success

In fact, all X11 XUI applications should now position their input,
or the IME will be placed in the lower left window corner.
This commit is contained in:
2026-08-10 04:54:07 +02:00
parent be66e0e00a
commit 9c0f8df784
+127 -19
View File
@@ -774,12 +774,18 @@ struct xui
bool tui_suspended; ///< TUI is released to a child process
int tui_clip_top; ///< Top row of the current clip region
int tui_clip_bottom; ///< Bottom of the current clip region
int tui_cursor_x; ///< Requested cursor column, or -1
int tui_cursor_y; ///< Requested cursor row, or -1
// X11:
#ifdef LIBERTY_XUI_WANT_X11
XIM x11_im; ///< Input method
XIC x11_ic; ///< Input method context
XFontSet x11_im_fontset; ///< Fonts for positional preediting
XIMStyle x11_im_style; ///< Negotiated input method style
XPoint x11_im_spot; ///< Last reported input caret baseline
unsigned x11_im_spot_set; ///< x11_im_spot has been reported
Display *dpy; ///< X display handle
struct poller_fd x11_event; ///< X11 events on wire
struct poller_idle xpending_event; ///< X11 events possibly in I/O queues
@@ -1059,14 +1065,30 @@ tui_beep (void)
beep ();
}
static void
tui_set_cursor (int x, int y)
{
g_xui.tui_cursor_x = x;
g_xui.tui_cursor_y = y;
}
static void
tui_render (void)
{
curs_set (0);
tui_set_cursor (-1, -1);
erase ();
LIST_FOR_EACH (struct widget, w, g_xui.widgets)
tui_render_widget (w, 0, g_xui.height);
// To enable independent renders.
if (g_xui.tui_cursor_x >= 0 && g_xui.tui_cursor_y >= 0)
{
move (g_xui.tui_cursor_y, g_xui.tui_cursor_x);
curs_set (1);
}
// To enable independent renders, though beware of the cursor.
g_xui.tui_clip_top = 0;
g_xui.tui_clip_bottom = g_xui.height;
}
@@ -1803,6 +1825,34 @@ x11_beep (void)
XFlush (g_xui.dpy);
}
static void
x11_set_cursor (int x, int y)
{
if (!(g_xui.x11_im_style & XIMPreeditPosition))
return;
XPoint spot = { x, y };
if (g_xui.x11_im_spot_set++
&& g_xui.x11_im_spot.x == spot.x && g_xui.x11_im_spot.y == spot.y)
return;
g_xui.x11_im_spot = spot;
g_xui.x11_im_spot_set = 1;
XVaNestedList preedit =
XVaCreateNestedList (0, XNSpotLocation, &spot, NULL);
if (!preedit)
{
print_warning ("failed to set input method spot location");
return;
}
const char *failed =
XSetICValues (g_xui.x11_ic, XNPreeditAttributes, preedit, NULL);
XFree (preedit);
if (failed)
print_warning ("failed to set input method attribute: %s", failed);
}
static void
x11_render (void)
{
@@ -1811,12 +1861,18 @@ x11_render (void)
XRenderFillRectangle (g_xui.dpy, PictOpSrc, g_xui.x11_pixmap_picture,
&x11_default_bg, 0, 0, g_xui.width, g_xui.height);
unsigned spot_set = g_xui.x11_im_spot_set;
LIST_FOR_EACH (struct widget, w, g_xui.widgets)
x11_render_widget (w, NULL);
XRectangle r = { 0, 0, g_xui.width, g_xui.height };
XUnionRectWithRegion (&r, g_xui.x11_clip, g_xui.x11_clip);
poller_idle_set (&g_xui.xpending_event);
// IBus needs to have the spot placed somewhere,
// or it may end up on another display.
if (g_xui.x11_im_spot_set == spot_set)
x11_set_cursor (0, g_xui.height);
}
static void
@@ -1839,6 +1895,8 @@ x11_destroy (void)
if (g_xui.x11_embed_socket)
XDestroyWindow (g_xui.dpy, g_xui.x11_embed_socket);
XDestroyIC (g_xui.x11_ic);
if (g_xui.x11_im_fontset)
XFreeFontSet (g_xui.dpy, g_xui.x11_im_fontset);
XCloseIM (g_xui.x11_im);
XDestroyRegion (g_xui.x11_clip);
XDestroyWindow (g_xui.dpy, g_xui.x11_window);
@@ -2685,6 +2743,71 @@ x11_init_attributes (struct attrs *attrs, size_t attrs_len)
}
}
static XIMStyle
x11_choose_input_style (void)
{
static const XIMStyle preferred[] =
{
XIMPreeditPosition | XIMStatusNothing,
XIMPreeditPosition | XIMStatusNone,
XIMPreeditNothing | XIMStatusNothing,
XIMPreeditNothing | XIMStatusNone,
};
XIMStyles *supported = NULL;
if (XGetIMValues (g_xui.x11_im, XNQueryInputStyle, &supported, NULL)
|| !supported)
exit_fatal ("failed to query input method styles");
XIMStyle result = 0;
for (size_t i = 0; !result && i < N_ELEMENTS (preferred); i++)
for (unsigned j = 0; j < supported->count_styles; j++)
if (preferred[i] == supported->supported_styles[j])
result = preferred[i];
XFree (supported);
if (!result)
exit_fatal ("failed to find a supported input method style");
return result;
}
static XIC
x11_init_input_context (void)
{
if (!(g_xui.x11_im_style & XIMPreeditPosition))
return XCreateIC (g_xui.x11_im,
XNInputStyle, g_xui.x11_im_style,
XNClientWindow, g_xui.x11_window,
NULL);
// All I can say is that IBus doesn't care about these fonts,
// and setting the spot markedly enhances the experience.
char **missing = NULL;
int missing_len = 0;
char *default_string = NULL;
g_xui.x11_im_fontset = XCreateFontSet (g_xui.dpy, "*",
&missing, &missing_len, &default_string);
if (missing)
XFreeStringList (missing);
if (!g_xui.x11_im_fontset)
exit_fatal ("failed to open an input method font set");
XPoint spot = {};
XVaNestedList preedit = XVaCreateNestedList (0,
XNSpotLocation, &spot, XNFontSet, g_xui.x11_im_fontset, NULL);
if (!preedit)
return None;
XIC ic = XCreateIC (g_xui.x11_im,
XNInputStyle, g_xui.x11_im_style,
XNClientWindow, g_xui.x11_window,
XNFocusWindow, g_xui.x11_window,
XNPreeditAttributes, preedit,
NULL);
XFree (preedit);
return ic;
}
static void
x11_init (struct poller *poller, struct attrs *app_attrs, size_t app_attrs_len)
{
@@ -2794,25 +2917,10 @@ x11_init (struct poller *poller, struct attrs *app_attrs, size_t app_attrs_len)
&& setting->type == XDG_XSETTINGS_INTEGER && setting->integer >= 0)
g_xui.x11_double_click_distance = setting->integer;
// TODO: It is possible to do, e.g., on-the-spot.
XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing;
XIMStyles *im_styles = NULL;
bool im_style_found = false;
if (!XGetIMValues (g_xui.x11_im, XNQueryInputStyle, &im_styles, NULL)
&& im_styles)
{
for (unsigned i = 0; i < im_styles->count_styles; i++)
im_style_found |= im_styles->supported_styles[i] == im_style;
XFree (im_styles);
}
if (!im_style_found)
print_warning ("failed to find the desired input method style");
if (!(g_xui.x11_ic = XCreateIC (g_xui.x11_im,
XNInputStyle, im_style,
XNClientWindow, g_xui.x11_window,
NULL)))
g_xui.x11_im_style = x11_choose_input_style ();
g_xui.x11_ic = x11_init_input_context ();
if (!g_xui.x11_ic)
exit_fatal ("failed to open an input context");
XSetICFocus (g_xui.x11_ic);
x11_init_pixmap ();