从GConf迁移到GSettings

从GConf迁移到GSettings

在开始之前

将单个应用程序及其设置从GConf转换为GSettings可以随时进行。但是,如字体或主题设置这样的桌面级设置通常会影响多个模块。因此,需要考虑所有设置的用户同时转换为GSettings,或者负责配置该设置的程序继续在两个地方都更新值。

查看以前如何处理类似问题始终是个好主意。

概念性差异

从概念上讲,GConf和GSettings相当相似。两者都有插件后端的概念。两者都在模式中保持关于键及其类型的信息。两者都有强制值的概念,这使得您可以实现锁定。

在模式方法上存在一些差异。GConf将模式安装到数据库中,并提供处理模式信息的API(如gconf_client_get_default_from_schema()gconf_value_get_schema()等)。而GSettings假定应用程序知道自己的模式,不提供处理运行时模式信息的API。GSettings在需要随时读取或写入键时对要求模式也更加严格。为了处理出现在GConf无模式条目中的更自由格式信息,GSettings允许模式"可重定位"。

应用程序与其设置交互方式的一个差异是,使用GConf时您与设置树交互(即在读取或写入值时传递给函数的键实际上是带有实际键名为最后一部分的路径。使用GSettings时,您创建一个GSettings对象,该对象有一个隐式的前缀,用于确定设置在哪里存储在全局设置树中,但是在读取或写入值时传递的键只是键名,而不是完整路径。

GConfClient(和GConfBridge)API转换

大多数人通过高级GConfClient API使用GConf。相应的API是GSettings对象。虽然并非每个GConfClient函数都有直接GSettings等效项,但许多都有。

GConfClient GSettings
gconf_client_get_default() 没有直接等效项,而是为使用的模式调用g_settings_new()
gconf_client_set() g_settings_set()
gconf_client_get() g_settings_get()
gconf_client_get_bool() g_settings_get_boolean()
gconf_client_set_bool() g_settings_set_boolean()
gconf_client_get_int() g_settings_get_int()
gconf_client_set_int() g_settings_set_int()
gconf_client_get_float() g_settings_get_double()
gconf_client_set_float() g_settings_set_double()
gconf_client_get_string() g_settings_get_string()
gconf_client_set_string() g_settings_set_string()
gconf_client_get_list() 对于字符串列表,请参阅g_settings_get_strv(),否则请参阅g_settings_get_value()和GVariant API
gconf_client_set_list() 对于字符串列表,请参阅 g_settings_set_strv(),否则请参阅 g_settings_set_value() 和 GVariant API
gconf_entry_get_is_writable() g_settings_is_writable()
gconf_client_notify_add() 不是必需的,"changed" 信号会自动发出
gconf_client_add_dir() 不是必需的,每个 GSettings 实例会自动监视其路径中的所有键
GConfChangeSet g_settings_delay()g_settings_apply()
gconf_client_get_default_from_schema() 没有等效功能,应用程序需要了解其架构
gconf_client_all_entries() 没有等效功能,应用程序需要了解其架构,且 GSettings 不允许无架构条目
gconf_client_get_without_default() 没有等效功能
gconf_bridge_bind_property() g_settings_bind()
gconf_bridge_bind_property_full() g_settings_bind_with_mapping()

GConfBridge 是一个第三方库,它使用 GConf 来将对象属性绑定到特定的配置键。GSettings 本身就提供这项服务。

有时在 GConf 中使用的一种模式,其中设置可以有明确的“值 A”,明确的“值 B”或“使用系统默认值”。在 GConf 中,"使用系统默认值"有时通过取消用户值的方式实现。在 GSettings 中这是不可能的,因为它没有 API 来确定值是否为默认值,并且不允许取消值。在 GSettings 中实现此功能的推荐方式(并且更为清晰)是使用一个单独的“使用系统默认”布尔设置。

更改通知

GConf 需要你调用 gconf_client_add_dir()gconf_client_notify_add() 来获取更改通知。在 GSettings 中,这并非必需;对于每一次更改都会自动发出信号。

每次改变的键都会发出 GSettings::changed 信号。还有一个 GSettings::change-event 信号,如果你需要查看同时改变的多个键的组时,可以处理该信号。

GSettings 还通过 GSettings::writable-changed 信号(和 GSettings::writable-change-event 信号)通知你有关于键的可写性更改。

更改集合

GConf 具有一个更改集合的概念,这些更改可以一次性应用或撤销:GConfChangeSet(GConf 实际上并不以原子方式应用更改,这是它的一个缺点)。

而不是使用单独的对象来表示更改集合,GSettings 具有可以开启的“延迟应用”模式,你可以通过调用 g_settings_delay()GSettings 对象启用该模式。在这种模式下,对 GSettings 对象所做的更改不会被应用——在调用同一对象的 g_settings_get() 时它们仍然可见,但不会应用到其他 GSettings 实例,甚至不会应用到其他进程。

要一次性应用挂起的更改(GSettings 这里做到了原子性),请调用 g_settings_apply()。要撤销挂起的更改,请调用 g_settings_revert() 或简单地丢弃对 GSettings 对象的引用。

架构转换

如果您正在将应用程序从GConf迁移,那么很可能会已经有一个GConf架构。GConf附带了一个命令行工具gsettings-schema-convert,可以帮助将GConf架构转换为等效的GSettings架构。这个工具并不完美,在一些情况下可能需要帮助。

gsettings-schema-convert的使用示例

在下面的desktop_gnome_font_rendering.schemas文件上运行以下命令:

<?xml version="1.0"?>
<gconfschemafile>
    <schemalist>
        <schema>
            <key>/schemas/desktop/gnome/font_rendering/dpi</key>
            <applyto>/desktop/gnome/font_rendering/dpi</applyto>
            <owner>gnome</owner>
            <type>int</type>
            <default>96</default>
            <locale name="C">
                <short>DPI</short>
                <long>The resolution used for converting font sizes to pixel sizes, in dots per inch.</long>
            </locale>
        </schema>
    </schemalist>
</gconfschemafile>

gsettings-schema-convert --gconf --xml --schema-id "org.gnome.font-rendering" --output org.gnome.font-rendering.gschema.xml desktop_gnome_font_rendering.schemas

<schemalist>
  <schema id="org.gnome.font-rendering" path="/desktop/gnome/font_rendering/">
    <key name="dpi" type="i">
      <default>96</default>
      <summary>DPI</summary>
      <description>The resolution used for converting font sizes to pixel sizes, in dots per inch.</description>
    </key>
  </schema>
</schemalist>

生成一个org.gnome.font-rendering.gschema.xml文件,其内容如下:

GSettings架构在运行时通过它们的ID(在XML源文件中指定)进行识别。建议使用类似D-Bus总线名称的点分名称作为架构ID,例如“org.gnome.SessionManager”。在设置是一般性且不特定于一个应用的情况下,ID不应使用大驼峰命名法,例如“org.gnome.font-rendering”。用于XML架构源文件的名称无关紧要,但架构编译器期望文件具有.gschema.xml扩展名。建议直接使用架构ID作为文件名,然后加上此扩展名,例如org.gnome.SessionManager.gschema.xml

架构由glib-compile-schemas实用工具编译成二进制形式。GIO在其pkg-config文件中提供了一个指向架构编译器二进制的glib-compile-schemas变量。

Meson使用架构

您应该使用install_data().gschema.xml文件安装到正确的目录中,例如:

install_data('my.app.gschema.xml', install_dir: get_option('datadir') / 'glib-2.0/schemas')

架构编译是在安装时进行的;如果您正在使用0.57或更新的Meson,您可以使用来自GNOME模块的gnome.post_install()函数。

gnome.post_install(glib_compile_schemas: true)

或者,您可以使用meson.add_install_script()和以下Python脚本:

#!/usr/bin/env python3
# build-aux/compile-schemas.py

import os
import subprocess

install_prefix = os.environ['MESON_INSTALL_PREFIX']
schemadir = os.path.join(install_prefix, 'share', 'glib-2.0', 'schemas')

if not os.environ.get('DESTDIR'):
    print('Compiling gsettings schemas...')
    subprocess.call(['glib-compile-schemas', schemadir])
meson.add_install_script('build-aux/compile-schemas.py')

Autotools使用架构

GLib提供了m4宏以隐藏各种复杂性并降低出错的可能性。

要在Autotools构建中处理架构,首先将以下内容添加到您的configure.ac

GLIB_GSETTINGS

然后将此段添加到您的Makefile.am

# gsettings_SCHEMAS is a list of all the schemas you want to install
gsettings_SCHEMAS = my.app.gschema.xml

# include the appropriate makefile rules for schema handling
@GSETTINGS_RULES@

仅此还不够。您需要说明my.app.gschema.xml文件的来源。如果架构文件是直接与项目tarball一起分发的,那么在EXTRA_DIST中提及其内容是合适的。如果架构文件来源于另一个文件,则您需要为该来源添加适当的规则,并且可能还需要为该规则的使用源文件添加一个条目到EXTRA_DIST

在架构转换中可能出现的一个潜在问题是,GSettings架构中的默认值由GVariant解析器解析。这意味着字符串需要在XML中包含引号。请注意,类型现在是作为GVariant类型字符串指定的。

<type>string</type>
<default>rgb</default>

变为:

<key name="rgba-order" type="s">
  <default>'rgb'</default> <!-- note quotes -->
</key>

另一个潜在的问题是,GConf指定每个键的完整路径,而GSettings架构有一个包含所有键前缀的“path”属性,每个单独的键只有一个简单的名称。因此

<key>/schemas/desktop/gnome/font_rendering/antialiasing</key>

变为:

<schema id="org.gnome.font" path="/desktop/gnome/font_rendering/">
  <key name="antialiasing" type="s">

默认值可以在 GConf 和 GSettings 规约中进行本地化,但 GSettings 使用 gettext 进行本地化。您可以在 gettext-domain 属性中指定要使用的 gettext 域。因此,在将 GConf 中的本地化默认值转换时,

<key>/schemas/apps/my_app/font_size</key>
  <locale name="C">
    <default>18</default>
  </locale>
  <locale name="be">
    <default>24</default>
  </locale>
</key>

变为:

<schema id="..." gettext-domain="your-domain">
 ...
<key name="font-size" type="i">
  <default l10n="messages" context="font_size">18</default>
</key>

GSettings 使用 gettext 对默认值进行翻译。翻译的字符串是恰好出现在 <default> 元素内部的字符串。这包括围绕字符串出现的引号。默认值必须在 <default> 标签中使用 l10n 属性进行标记,该属性应根据所需类别设置为“消息”或“时间”。还可以使用上下文属性指定可选的翻译上下文,如示例所示。这通常建议使用,因为“18”这个字符串没有上下文是很难翻译的。默认值的翻译版本应存储在指定的 gettext-domain 中。在翻译过程中,必须注意确保所有翻译值在语法上都是有效的;在这里犯错误将导致运行时错误。

GSettings 规约为每个键都包含可选的 <summary><description> 元素,它们对应于 GConf 规约中的 <short><long> 元素,并且在 GUI 编辑器中可以使用相同的方式进行,因此您应该对它们使用相同的约定:总结只是一个无标点的简短标签,描述可以是或多个完整句子。如果需要多个段落来描述,段落应通过一个完全空的行分开。

这些字符串的翻译也将通过 gettext 进行处理,因此您应该安排将这些字符串提取到您的 gettext 库中。自 0.19 版本起,gettext 原生支持 GSettings 规约,所以您只需将 XML 规约文件添加到 POTFILES.in 中可翻译文件的列表即可。

GSettings 对键名的限制比 GConf 更严格。GSettings 中的键名最多为 32 个字符长,且只能由小写字母、数字和短划线组成,不允许连续使用短划线。第一个字符不能是数字或短划线,最后一个字符不能是“-”。

如果在过渡期间使用 GConf 作为 GSettings 的后端,您可能希望保持键名与 GConf 中相同,这样就可以保留用户 GConf 数据库中的现有设置。您可以通过在 glib-compile-schemas 规约编译器中使用 --allow-any-name 实现这一点。请注意,此选项仅用于简化应用程序的移植过程,允许应用程序的部分继续访问 GConf,部分使用 GSettings。在您完成应用程序的移植后,必须确保所有键名都有效。

数据转换

GConf 附带一个可以用来帮助过渡到 GSettings API 的后端。要使用它,您需要设置 GSETTINGS_BACKEND 为“gconf”,例如:

g_setenv ("GSETTINGS_BACKEND", "gconf", TRUE);

请注意,此后端纯粹是过渡工具,不应在生产中使用。

GConf 还附带一个名为 gsettings-data-convert 的实用程序,旨在帮助将用户设置从 GConf 迁移到另一个 GSettings 后端。它可以手动运行,但设计为在每次用户登录时自动执行。它会跟踪已执行的数据迁移,并且多次运行是安全的。

要使用此实用程序,您必须在目录 /usr/share/GConf/gsettings 中安装一个keyfile,该文件列出GSettings键和GConf路径,它们相互映射,为每个您想要迁移用户数据的方案。

以下是一个示例

[org.gnome.fonts]
antialiasing = /desktop/gnome/font_rendering/antialiasing
dpi = /desktop/gnome/font_rendering/dpi
hinting = /desktop/gnome/font_rendering/hinting
rgba-order = /desktop/gnome/font_rendering/rgba_order

[apps.myapp:/path/to/myapps/]
some-odd-key1 = /apps/myapp/some_ODD-key1

最后一个键示例说明,为了遵守更严格的GSettings键名规则,可能需要修改键名。当然,这意味着您的应用程序在GSettings中查找设置时必须使用新的键名。

示例中的最后一个组还显示如何处理“可移动”方案的情况,它们没有固定的路径。您可以在组名中指定要使用的路径,路径之间用冒号分隔。

有一些限制:gsettings-data-convert不对值进行任何转换。它也不处理列表字符串或整数之外的其他复杂的GConf类型。

如果您正在使用GConf后端或转换实用程序,不要忘记在您的configure脚本中要求GConf 2.31.1或更高版本。

如果您作为应用程序开发人员,有兴趣手动确保已调用gsettings-data-convert(例如,处理在分发升级期间用户已登录或对于不作为自动启动运行的命令的非XDG桌面环境的情况),您可以在程序初始化期间手动调用它。这并不推荐所有应用程序作者使用 —— 如果这个用例对您来说很重要,这是您的选择。

在内部,gsettings-data-convert使用一个keyfile来跟踪哪些设置已经迁移。以下代码片段将检查该keyfile以查看您的数据转换脚本是否已运行,如果没有,将试图调用工具运行它。您应将其改为适合您应用程序的形式。

static void
ensure_migrated (const gchar *name)
{
  gboolean needed = TRUE;
  GKeyFile *kf;
  gchar **list;
  gsize i, n;

  kf = g_key_file_new ();

  g_key_file_load_from_data_dirs (kf, "gsettings-data-convert",
                                  NULL, G_KEY_FILE_NONE, NULL);
  list = g_key_file_get_string_list (kf, "State", "converted", &n, NULL);

  if (list)
    {
      for (i = 0; i < n; i++)
        if (strcmp (list[i], name) == 0)
          {
            needed = FALSE;
            break;
          }

      g_strfreev (list);
    }

  g_key_file_free (kf);

  if (needed)
    g_spawn_command_line_sync ("gsettings-data-convert",
                               NULL, NULL, NULL, NULL);
}

尽管存在gsettings-data-convert脚本可能以这种方法同时运行多次的可能性,但认为这是安全的。