只针对4.5.0版本,以及这文章是我去翻GTK源码查调用过程的记录,文字组织没有任何分段或者排版。
GTK文本输入部件和输入法的关系,是通过GtkIMMulticontext关联的,所以需要理解这个 问题就需要知道GtkIMMulticontext的执行过程。
在GtkIMMulticontext被创建的时候,默认的输入法就被设置好了(其实并不是),可以用以下的 关键代码来验证这一点:
GtkIMContext* im = gtk_im_multicontext_new();
g_print(gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(im)));
上述代码输出的内容是
gtk-im-context-simple
这是GTK内部默认的输入法,输入Ctrl+Shift+u20ac可以得到字符“€”。所以问题进一步 缩小为理解gtk_im_multicontext_new的内部过程。
以下是我找到的GTK实现源码
GtkIMContext *
gtk_im_multicontext_new (void)
{
return g_object_new (GTK_TYPE_IM_MULTICONTEXT, NULL);
}
同时有
#define GTK_TYPE_IM_MULTICONTEXT (gtk_im_multicontext_get_type ())
线索到此断了,逻辑不是在gtk_im_multicontext_get_type上。所以我换个 方向,去找这个
const char *
gtk_im_multicontext_get_context_id (GtkIMMulticontext *context)
{
GtkIMMulticontextPrivate *priv = context->priv;
g_return_val_if_fail (GTK_IS_IM_MULTICONTEXT (context), NULL);
if (priv->context_id == NULL)
gtk_im_multicontext_get_delegate (context);
return priv->context_id;
}
于是问题变成了寻找gtk_im_multicontext_get_delegate的实现。就找到了这个
static GtkIMContext *
gtk_im_multicontext_get_delegate (GtkIMMulticontext *multicontext)
{
GtkIMMulticontextPrivate *priv = multicontext->priv;
if (!priv->delegate)
{
GtkIMContext *delegate;
g_free (priv->context_id);
priv->context_id = g_strdup (get_effective_context_id (multicontext));
delegate = _gtk_im_module_create (priv->context_id);
if (delegate)
{
gtk_im_multicontext_set_delegate (multicontext, delegate, FALSE);
g_object_unref (delegate);
}
}
return priv->delegate;
}
于是,需要找get_effective_context_id的源码。
static const char *
get_effective_context_id (GtkIMMulticontext *multicontext)
{
GtkIMMulticontextPrivate *priv = multicontext->priv;
GdkDisplay *display;
if (priv->context_id_aux)
return priv->context_id_aux;
if (priv->client_widget)
display = gtk_widget_get_display (priv->client_widget);
else
display = gdk_display_get_default ();
return _gtk_im_module_get_default_context_id (display);
}
获取了display之后找_gtk_im_module_get_default_context_id:
/**
* _gtk_im_module_get_default_context_id:
* @display: The display to look up the module for
*
* Return the context_id of the best IM context type
* for the given window.
*
* Returns: the context ID (will never be %NULL)
*/
const char *
_gtk_im_module_get_default_context_id (GdkDisplay *display)
{
const char *context_id = NULL;
const char *envvar;
GtkSettings *settings;
GIOExtensionPoint *ep;
GList *l;
char *tmp;
envvar = g_getenv ("GTK_IM_MODULE");
if (envvar)
{
char **immodules;
immodules = g_strsplit (envvar, ":", 0);
context_id = lookup_immodule (display, immodules);
g_strfreev (immodules);
if (context_id)
return context_id;
}
/* Check if the certain immodule is set in XSETTINGS. */
settings = gtk_settings_get_for_display (display);
g_object_get (G_OBJECT (settings), "gtk-im-module", &tmp, NULL);
if (tmp)
{
char **immodules;
immodules = g_strsplit (tmp, ":", 0);
context_id = lookup_immodule (display, immodules);
g_strfreev (immodules);
g_free (tmp);
if (context_id)
return context_id;
}
ep = g_io_extension_point_lookup (GTK_IM_MODULE_EXTENSION_POINT_NAME);
for (l = g_io_extension_point_get_extensions (ep); l; l = l->next)
{
GIOExtension *ext = l->data;
context_id = g_io_extension_get_name (ext);
if (match_backend (display, context_id))
return context_id;
}
g_error ("GTK was run without any IM module being present. This must not happen.");
return SIMPLE_ID;
}
其相关常量定义为
#define GTK_IM_MODULE_EXTENSION_POINT_NAME "gtk-im-module"
#define SIMPLE_ID "gtk-im-context-simple"
所以,GTK判断应用什么输入法的核心逻辑是:获取环境变量GTK_IM_MODULE并用lookup_immodule 查找模块,成功则结束,否则进入下一阶段;通过XSETTINGS规范查找配置,然后也是通过 lookup_immodule查找模块,成功则返回否则继续;通过GIO的扩展点gtk-im-module 名称查找模块,成功找到这个名称则返回,否则就返回默认的gtk-im-context-simple。
那lookup_immodule如何运作呢?
static const char *
lookup_immodule (GdkDisplay *display,
char **immodules_list)
{
guint i;
for (i = 0; immodules_list[i]; i++)
{
if (!match_backend (display, immodules_list[i]))
continue;
if (g_strcmp0 (immodules_list[i], SIMPLE_ID) == 0)
return SIMPLE_ID;
else if (g_strcmp0 (immodules_list[i], NONE_ID) == 0)
return NONE_ID;
else
{
GIOExtensionPoint *ep;
GIOExtension *ext;
ep = g_io_extension_point_lookup (GTK_IM_MODULE_EXTENSION_POINT_NAME);
ext = g_io_extension_point_get_extension_by_name (ep, immodules_list[i]);
if (ext)
return g_io_extension_get_name (ext);
}
}
return NULL;
}
因此,match_backend成功后还是通过gtk-im-module名称的GIO扩展点来加载模块。
这个match_backend是什么呢?如下
static gboolean
match_backend (GdkDisplay *display,
const char *context_id)
{
if (g_strcmp0 (context_id, "wayland") == 0)
{
#ifdef GDK_WINDOWING_WAYLAND
return GDK_IS_WAYLAND_DISPLAY (display) &&
gdk_wayland_display_query_registry (display,
"zwp_text_input_manager_v3");
#else
return FALSE;
#endif
}
if (g_strcmp0 (context_id, "broadway") == 0)
#ifdef GDK_WINDOWING_BROADWAY
return GDK_IS_BROADWAY_DISPLAY (display);
#else
return FALSE;
#endif
if (g_strcmp0 (context_id, "ime") == 0)
#ifdef GDK_WINDOWING_WIN32
return GDK_IS_WIN32_DISPLAY (display);
#else
return FALSE;
#endif
if (g_strcmp0 (context_id, "quartz") == 0)
#ifdef GDK_WINDOWING_MACOS
return GDK_IS_MACOS_DISPLAY (display);
#else
return FALSE;
#endif
return TRUE;
}
看实现估计是检查当前的显示服务,和输入法关系相对不大。
回到上面的gtk_im_multicontext_get_context_id的实现可以看出,这里设置了 相关的内容
if (priv->context_id == NULL)
gtk_im_multicontext_get_delegate (context);
这里的gtk_im_multicontext_get_delegate是在下面这个位置设置context属性的
delegate = _gtk_im_module_create (priv->context_id);
if (delegate)
{
gtk_im_multicontext_set_delegate (multicontext, delegate, FALSE);
g_object_unref (delegate);
}
}
根据函数字面的意思看出,这个_gtk_im_module_create是创建输入法模块的, 它的实现是
/**
* _gtk_im_module_create:
* @context_id: the context ID for the context type to create
*
* Create an IM context of a type specified by the string
* ID @context_id.
*
* Returns: a newly created input context of or @context_id, or
* if that could not be created, a newly created `GtkIMContextSimple`
*/
GtkIMContext *
_gtk_im_module_create (const char *context_id)
{
GIOExtensionPoint *ep;
GIOExtension *ext;
GType type;
GtkIMContext *context = NULL;
if (strcmp (context_id, NONE_ID) == 0)
return NULL;
ep = g_io_extension_point_lookup (GTK_IM_MODULE_EXTENSION_POINT_NAME);
ext = g_io_extension_point_get_extension_by_name (ep, context_id);
if (ext)
{
type = g_io_extension_get_type (ext);
context = g_object_new (type, NULL);
}
return context;
}
因此,GTK低层确实都是通过GIO的扩展点机制,用g_io_extension_point_lookup 和g_io_extension_point_get_extension_by_name查找输入法模块。第三方 输入法模块的实现需要名为“gtk-im-module”的“GIOExtension”扩展点, 扩展的名称需要和环境变量GTK_IM_MODULE保持一致。
当然,继续深入,GIO又是靠什么机制和输入法模块进行通信?按照GIO文档上的图,下面这张
图片显示,GIO内部是通过DBus实现的进程间通信。