GTK是如何选择输入法的

只针对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的API文档

当然,继续深入,GIO又是靠什么机制和输入法模块进行通信?按照GIO文档上的图,下面这张

GIO运作图
图片显示,GIO内部是通过DBus实现的进程间通信。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值