Files
allhaileris afb81b8278
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
init
2026-02-16 15:50:16 +03:00

757 lines
21 KiB
C

/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
/*
* nimf-xim.c
* This file is part of Nimf.
*
* Copyright (C) 2015-2019 Hodong Kim <cogniti@gmail.com>
*
* Nimf is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nimf is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; If not, see <http://www.gnu.org/licenses/>.
*/
#include "nimf-xim.h"
#include "IMdkit/i18nMethod.h"
#include "IMdkit/i18nX.h"
#include "IMdkit/XimFunc.h"
G_DEFINE_DYNAMIC_TYPE (NimfXim, nimf_xim, NIMF_TYPE_SERVICE);
static void
nimf_xim_change_engine (NimfService *service,
const gchar *engine_id,
const gchar *method_id)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfXim *xim = NIMF_XIM (service);
NimfServiceIC *ic;
ic = g_hash_table_lookup (xim->ics,
GUINT_TO_POINTER (xim->last_focused_icid));
if (ic)
nimf_service_ic_change_engine (ic, engine_id, method_id);
}
static void
nimf_xim_change_engine_by_id (NimfService *service,
const gchar *engine_id)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfXim *xim = NIMF_XIM (service);
NimfServiceIC *ic;
ic = g_hash_table_lookup (xim->ics,
GUINT_TO_POINTER (xim->last_focused_icid));
if (ic)
nimf_service_ic_change_engine_by_id (ic, engine_id);
}
static int nimf_xim_set_ic_values (NimfXim *xim,
IMChangeICStruct *data)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfServiceIC *ic;
NimfXimIC *xic;
CARD16 i;
ic = g_hash_table_lookup (xim->ics, GUINT_TO_POINTER (data->icid));
xic = NIMF_XIM_IC (ic);
for (i = 0; i < data->ic_attr_num; i++)
{
if (!g_strcmp0 (XNInputStyle, data->ic_attr[i].name))
{
xic->input_style = *(CARD32*) data->ic_attr[i].value;
nimf_service_ic_set_use_preedit (ic, !!(xic->input_style & XIMPreeditCallbacks));
}
else if (!g_strcmp0 (XNClientWindow, data->ic_attr[i].name))
{
xic->client_window = *(Window *) data->ic_attr[i].value;
}
else if (!g_strcmp0 (XNFocusWindow, data->ic_attr[i].name))
{
xic->focus_window = *(Window *) data->ic_attr[i].value;
}
else
{
g_warning (G_STRLOC ": %s %s", G_STRFUNC, data->ic_attr[i].name);
}
}
for (i = 0; i < data->preedit_attr_num; i++)
{
if (g_strcmp0 (XNPreeditState, data->preedit_attr[i].name) == 0)
{
XIMPreeditState state = *(XIMPreeditState *) data->preedit_attr[i].value;
switch (state)
{
case XIMPreeditEnable:
nimf_service_ic_set_use_preedit (ic, TRUE);
break;
case XIMPreeditDisable:
nimf_service_ic_set_use_preedit (ic, FALSE);
break;
case XIMPreeditUnKnown:
break;
default:
g_warning (G_STRLOC ": %s: XIMPreeditState: %ld is ignored",
G_STRFUNC, state);
break;
}
}
else if (g_strcmp0 (XNSpotLocation, data->preedit_attr[i].name) == 0)
{
nimf_xim_ic_set_cursor_location (xic,
((XPoint *) data->preedit_attr[i].value)->x,
((XPoint *) data->preedit_attr[i].value)->y);
NimfServer *server = nimf_server_get_default ();
NimfPreeditable *preeditable = nimf_server_get_preeditable (server);
if (nimf_preeditable_is_visible (preeditable))
nimf_preeditable_show (preeditable);
}
else
{
g_critical (G_STRLOC ": %s: %s is ignored",
G_STRFUNC, data->preedit_attr[i].name);
}
}
for (i = 0; i < data->status_attr_num; i++)
{
g_critical (G_STRLOC ": %s: %s is ignored",
G_STRFUNC, data->status_attr[i].name);
}
return 1;
}
static int nimf_xim_create_ic (NimfXim *xim,
IMChangeICStruct *data)
{
g_debug (G_STRLOC ": %s, data->connect_id: %d", G_STRFUNC, data->connect_id);
NimfXimIC *xic;
xic = g_hash_table_lookup (xim->ics, GUINT_TO_POINTER (data->icid));
if (!xic)
{
guint16 icid;
do
icid = xim->next_icid++;
while (icid == 0 ||
g_hash_table_contains (xim->ics, GUINT_TO_POINTER (icid)));
xic = nimf_xim_ic_new (xim, data->connect_id, icid);
g_hash_table_insert (xim->ics, GUINT_TO_POINTER (icid), xic);
data->icid = icid;
g_debug (G_STRLOC ": icid = %d", data->icid);
}
nimf_xim_set_ic_values (xim, data);
return 1;
}
static int nimf_xim_destroy_ic (NimfXim *xim,
IMDestroyICStruct *data)
{
g_debug (G_STRLOC ": %s, data->icid = %d", G_STRFUNC, data->icid);
return g_hash_table_remove (xim->ics, GUINT_TO_POINTER (data->icid));
}
static int nimf_xim_get_ic_values (NimfXim *xim,
IMChangeICStruct *data)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfServiceIC *ic;
ic = g_hash_table_lookup (xim->ics, GUINT_TO_POINTER (data->icid));
CARD16 i;
for (i = 0; i < data->ic_attr_num; i++)
{
if (g_strcmp0 (XNFilterEvents, data->ic_attr[i].name) == 0)
{
data->ic_attr[i].value_length = sizeof (CARD32);
data->ic_attr[i].value = g_malloc (sizeof (CARD32));
*(CARD32 *) data->ic_attr[i].value = KeyPressMask | KeyReleaseMask;
}
else
g_critical (G_STRLOC ": %s: %s is ignored",
G_STRFUNC, data->ic_attr[i].name);
}
for (i = 0; i < data->preedit_attr_num; i++)
{
if (g_strcmp0 (XNPreeditState, data->preedit_attr[i].name) == 0)
{
data->preedit_attr[i].value_length = sizeof (XIMPreeditState);
data->preedit_attr[i].value = g_malloc (sizeof (XIMPreeditState));
if (nimf_service_ic_get_use_preedit (ic))
*(XIMPreeditState *) data->preedit_attr[i].value = XIMPreeditEnable;
else
*(XIMPreeditState *) data->preedit_attr[i].value = XIMPreeditDisable;
}
else
g_critical (G_STRLOC ": %s: %s is ignored",
G_STRFUNC, data->preedit_attr[i].name);
}
for (i = 0; i < data->status_attr_num; i++)
g_critical (G_STRLOC ": %s: %s is ignored",
G_STRFUNC, data->status_attr[i].name);
return 1;
}
static int nimf_xim_forward_event (NimfXim *xim,
IMForwardEventStruct *data)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
XKeyEvent *xevent;
NimfEvent *event;
gboolean retval;
KeySym keysym;
unsigned int consumed;
NimfModifierType state;
xevent = (XKeyEvent*) &(data->event);
event = nimf_event_new (NIMF_EVENT_NOTHING);
if (xevent->type == KeyPress)
event->key.type = NIMF_EVENT_KEY_PRESS;
else
event->key.type = NIMF_EVENT_KEY_RELEASE;
event->key.state = (NimfModifierType) xevent->state;
event->key.keyval = NIMF_KEY_VoidSymbol;
event->key.hardware_keycode = xevent->keycode;
XkbLookupKeySym (xim->display,
event->key.hardware_keycode,
event->key.state,
&consumed, &keysym);
event->key.keyval = (guint) keysym;
state = event->key.state & ~consumed;
event->key.state |= (NimfModifierType) state;
NimfServiceIC *ic;
ic = g_hash_table_lookup (xim->ics, GUINT_TO_POINTER (data->icid));
retval = nimf_service_ic_filter_event (ic, event);
nimf_event_free (event);
if (G_UNLIKELY (!retval))
return xi18n_forwardEvent (xim, (XPointer) data);
return 1;
}
static const gchar *
nimf_xim_get_id (NimfService *service)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
g_return_val_if_fail (NIMF_IS_SERVICE (service), NULL);
return NIMF_XIM (service)->id;
}
static int nimf_xim_set_ic_focus (NimfXim *xim,
IMChangeFocusStruct *data)
{
NimfServiceIC *ic;
NimfXimIC *xic;
ic = g_hash_table_lookup (xim->ics, GUINT_TO_POINTER (data->icid));
xic = NIMF_XIM_IC (ic);
g_debug (G_STRLOC ": %s, icid = %d, connection id = %d",
G_STRFUNC, data->icid, xic->icid);
nimf_service_ic_focus_in (ic);
xim->last_focused_icid = xic->icid;
if (xic->input_style & XIMPreeditNothing)
nimf_xim_ic_set_cursor_location (xic, -1, -1);
return 1;
}
static int nimf_xim_unset_ic_focus (NimfXim *xim,
IMChangeFocusStruct *data)
{
NimfServiceIC *ic;
ic = g_hash_table_lookup (xim->ics, GUINT_TO_POINTER (data->icid));
g_debug (G_STRLOC ": %s, icid = %d", G_STRFUNC, data->icid);
nimf_service_ic_focus_out (ic);
return 1;
}
static int nimf_xim_reset_ic (NimfXim *xim,
IMResetICStruct *data)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfServiceIC *ic;
ic = g_hash_table_lookup (xim->ics, GUINT_TO_POINTER (data->icid));
nimf_service_ic_reset (ic);
return 1;
}
/* FIXME */
int
on_incoming_message (NimfXim *xim,
IMProtocol *data)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
g_return_val_if_fail (data != NULL, True);
int retval;
switch (data->major_code)
{
case XIM_OPEN:
g_debug (G_STRLOC ": XIM_OPEN: connect_id: %u", data->imopen.connect_id);
retval = 1;
break;
case XIM_CLOSE:
g_debug (G_STRLOC ": XIM_CLOSE: connect_id: %u",
data->imclose.connect_id);
retval = 1;
break;
case XIM_PREEDIT_START_REPLY:
g_debug (G_STRLOC ": XIM_PREEDIT_START_REPLY");
retval = 1;
break;
case XIM_CREATE_IC:
retval = nimf_xim_create_ic (xim, &data->changeic);
break;
case XIM_DESTROY_IC:
retval = nimf_xim_destroy_ic (xim, &data->destroyic);
break;
case XIM_SET_IC_VALUES:
retval = nimf_xim_set_ic_values (xim, &data->changeic);
break;
case XIM_GET_IC_VALUES:
retval = nimf_xim_get_ic_values (xim, &data->changeic);
break;
case XIM_FORWARD_EVENT:
retval = nimf_xim_forward_event (xim, &data->forwardevent);
break;
case XIM_SET_IC_FOCUS:
retval = nimf_xim_set_ic_focus (xim, &data->changefocus);
break;
case XIM_UNSET_IC_FOCUS:
retval = nimf_xim_unset_ic_focus (xim, &data->changefocus);
break;
case XIM_RESET_IC:
retval = nimf_xim_reset_ic (xim, &data->resetic);
break;
default:
g_warning (G_STRLOC ": %s: major op code %d not handled", G_STRFUNC,
data->major_code);
retval = 0;
break;
}
return retval;
}
static int
on_xerror (Display *display,
XErrorEvent *error)
{
gchar buf[64];
XGetErrorText (display, error->error_code, buf, 63);
g_warning (G_STRLOC ": %s: %s", G_STRFUNC, buf);
g_warning ("type: %d", error->type);
g_warning ("display name: %s", DisplayString (error->display));
g_warning ("serial: %lu", error->serial);
g_warning ("error_code: %d", error->error_code);
g_warning ("request_code: %d", error->request_code);
g_warning ("minor_code: %d", error->minor_code);
g_warning ("resourceid: %lu", error->resourceid);
return 1;
}
typedef struct
{
GSource source;
NimfXim *xim;
GPollFD poll_fd;
} NimfXEventSource;
static gboolean nimf_xevent_source_prepare (GSource *source,
gint *timeout)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
Display *display = ((NimfXEventSource *) source)->xim->display;
*timeout = -1;
return XPending (display) > 0;
}
static gboolean nimf_xevent_source_check (GSource *source)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfXEventSource *display_source = (NimfXEventSource *) source;
if (display_source->poll_fd.revents & G_IO_IN)
return XPending (display_source->xim->display) > 0;
else
return FALSE;
}
static gboolean
nimf_xevent_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfXim *xim = ((NimfXEventSource*) source)->xim;
XEvent event;
while (XPending (xim->display) > 0)
{
XNextEvent (xim->display, &event);
if (!XFilterEvent (&event, None))
{
switch (event.type)
{
case SelectionRequest:
WaitXSelectionRequest (xim, &event);
break;
case ClientMessage:
{
XClientMessageEvent cme = *(XClientMessageEvent *) &event;
if (cme.message_type == xim->_xconnect)
ReadXConnectMessage (xim, (XClientMessageEvent *) &event);
else if (cme.message_type == xim->_protocol)
WaitXIMProtocol (xim, &event);
else
g_warning (G_STRLOC ": %s: ClientMessage type: %ld not handled",
G_STRFUNC, cme.message_type);
}
break;
case MappingNotify:
g_message (G_STRLOC ": %s: MappingNotify", G_STRFUNC);
break;
default:
g_warning (G_STRLOC ": %s: event type: %d not filtered",
G_STRFUNC, event.type);
break;
}
}
}
return TRUE;
}
static void nimf_xevent_source_finalize (GSource *source)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
}
static GSourceFuncs event_funcs = {
nimf_xevent_source_prepare,
nimf_xevent_source_check,
nimf_xevent_source_dispatch,
nimf_xevent_source_finalize
};
static GSource *nimf_xevent_source_new (NimfXim *xim)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
GSource *source;
NimfXEventSource *xevent_source;
source = g_source_new (&event_funcs, sizeof (NimfXEventSource));
xevent_source = (NimfXEventSource *) source;
xevent_source->xim = xim;
xevent_source->poll_fd.fd = ConnectionNumber (xevent_source->xim->display);
xevent_source->poll_fd.events = G_IO_IN;
g_source_add_poll (source, &xevent_source->poll_fd);
g_source_set_priority (source, G_PRIORITY_DEFAULT);
g_source_set_can_recurse (source, TRUE);
return source;
}
static gboolean nimf_xim_is_active (NimfService *service)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
return NIMF_XIM (service)->active;
}
static gboolean nimf_xim_start (NimfService *service)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfXim *xim = NIMF_XIM (service);
if (xim->active)
return TRUE;
xim->display = XOpenDisplay (NULL);
if (xim->display == NULL)
{
g_warning (G_STRLOC ": %s: Can't open display", G_STRFUNC);
return FALSE;
}
/*
* https://www.x.org/releases/X11R7.7/doc/libX11/libX11/libX11.html
* https://www.x.org/releases/X11R7.7/doc/libX11/XIM/xim.html
*
* The preedit category defines what type of support is provided by the input
* method for preedit information.
*
* XIMPreeditArea (as known as off-the-spot)
*
* The client application provides display windows for the pre-edit data to the
* input method which displays into them directly.
* If chosen, the input method would require the client to provide some area
* values for it to do its preediting. Refer to XIC values XNArea and
* XNAreaNeeded.
*
* XIMPreeditCallbacks (as known as on-the-spot)
*
* The client application is directed by the IM Server to display all pre-edit
* data at the site of text insertion. The client registers callbacks invoked by
* the input method during pre-editing.
* If chosen, the input method would require the client to define the set of
* preedit callbacks. Refer to XIC values XNPreeditStartCallback,
* XNPreeditDoneCallback, XNPreeditDrawCallback, and XNPreeditCaretCallback.
*
* XIMPreeditPosition (as known as over-the-spot)
*
* The input method displays pre-edit data in a window which it brings up
* directly over the text insertion position.
* If chosen, the input method would require the client to provide positional
* values. Refer to XIC values XNSpotLocation and XNFocusWindow.
*
* XIMPreeditNothing (as known as root-window)
*
* The input method displays all pre-edit data in a separate area of the screen
* in a window specific to the input method.
* If chosen, the input method can function without any preedit values.
*
* XIMPreeditNone none
*
* The input method does not provide any preedit feedback. Any preedit value is
* ignored. This style is mutually exclusive with the other preedit styles.
*
*
* The status category defines what type of support is provided by the input
* method for status information.
*
* XIMStatusArea
*
* The input method requires the client to provide some area values for it to do
* its status feedback. See XNArea and XNAreaNeeded.
*
* XIMStatusCallbacks
*
* The input method requires the client to define the set of status callbacks,
* XNStatusStartCallback, XNStatusDoneCallback, and XNStatusDrawCallback.
*
* XIMStatusNothing
*
* The input method can function without any status values.
*
* XIMStatusNone
*
* The input method does not provide any status feedback. If chosen, any status
* value is ignored. This style is mutually exclusive with the other status
* styles.
*/
xim->im_styles.count_styles = 6;
xim->im_styles.supported_styles = g_malloc (sizeof (XIMStyle) * xim->im_styles.count_styles);
/* on-the-spot */
xim->im_styles.supported_styles[0] = XIMPreeditCallbacks | XIMStatusNothing;
xim->im_styles.supported_styles[1] = XIMPreeditCallbacks | XIMStatusNone;
/* over-the-spot */
xim->im_styles.supported_styles[2] = XIMPreeditPosition | XIMStatusNothing;
xim->im_styles.supported_styles[3] = XIMPreeditPosition | XIMStatusNone;
/* root-window */
xim->im_styles.supported_styles[4] = XIMPreeditNothing | XIMStatusNothing;
xim->im_styles.supported_styles[5] = XIMPreeditNothing | XIMStatusNone;
xim->im_event_mask = KeyPressMask | KeyReleaseMask;
XSetWindowAttributes attrs;
attrs.event_mask = xim->im_event_mask;
attrs.override_redirect = True;
xim->im_window = XCreateWindow (xim->display, /* Display *display */
DefaultRootWindow (xim->display), /* Window parent */
0, 0, /* int x, y */
1, 1, /* unsigned int width, height */
0, /* unsigned int border_width */
0, /* int depth */
InputOutput, /* unsigned int class */
CopyFromParent, /* Visual *visual */
CWOverrideRedirect | CWEventMask, /* unsigned long valuemask */
&attrs); /* XSetWindowAttributes *attributes */
if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
xim->byte_order = 'l';
else
xim->byte_order = 'B';
_Xi18nInitAttrList (xim);
_Xi18nInitExtension (xim);
if (!xi18n_openIM (xim, xim->im_window))
{
XDestroyWindow (xim->display, xim->im_window);
XCloseDisplay (xim->display);
xim->im_window = 0;
xim->display = NULL;
g_warning (G_STRLOC ": %s: XIM is not started.", G_STRFUNC);
return FALSE;
}
xim->xevent_source = nimf_xevent_source_new (xim);
g_source_attach (xim->xevent_source, NULL);
XSetErrorHandler (on_xerror);
xim->active = TRUE;
return TRUE;
}
static void nimf_xim_stop (NimfService *service)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfXim *xim = NIMF_XIM (service);
if (!xim->active)
return;
if (xim->xevent_source)
{
g_source_destroy (xim->xevent_source);
g_source_unref (xim->xevent_source);
}
if (xim->im_window)
{
XDestroyWindow (xim->display, xim->im_window);
xim->im_window = 0;
}
g_free (xim->im_styles.supported_styles);
xi18n_closeIM (xim);
if (xim->display)
{
XCloseDisplay (xim->display);
xim->display = NULL;
}
xim->active = FALSE;
}
static void
nimf_xim_init (NimfXim *xim)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
xim->id = g_strdup ("nimf-xim");
xim->ics = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
(GDestroyNotify) g_object_unref);
}
static void nimf_xim_finalize (GObject *object)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
NimfService *service = NIMF_SERVICE (object);
NimfXim *xim = NIMF_XIM (object);
if (nimf_xim_is_active (service))
nimf_xim_stop (service);
g_hash_table_unref (xim->ics);
g_free (xim->id);
G_OBJECT_CLASS (nimf_xim_parent_class)->finalize (object);
}
static void
nimf_xim_class_init (NimfXimClass *class)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
GObjectClass *object_class = G_OBJECT_CLASS (class);
NimfServiceClass *service_class = NIMF_SERVICE_CLASS (class);
service_class->get_id = nimf_xim_get_id;
service_class->start = nimf_xim_start;
service_class->stop = nimf_xim_stop;
service_class->is_active = nimf_xim_is_active;
service_class->change_engine = nimf_xim_change_engine;
service_class->change_engine_by_id = nimf_xim_change_engine_by_id;
object_class->finalize = nimf_xim_finalize;
}
static void
nimf_xim_class_finalize (NimfXimClass *class)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
}
void module_register_type (GTypeModule *type_module)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
nimf_xim_register_type (type_module);
}
GType module_get_type ()
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
return nimf_xim_get_type ();
}