本周 GTK+ 进展 – 34

在 GNOME 3.24 发布后经过相当长的休息,我们终于回来了。很抱歉让大家久等了!

在过去的一周里,GTK+ 的主分支有 103 次提交,新增了 2355 行代码,删除了 5482 行代码。

计划和状态
  • GTK+ 路线图可在 wiki 上找到。
  • Matthias Clasen 发布了 GTK+ 3.91.0,这是将导致 3.92 版本的开发周期的第一个快照。这仍然是通向 API 稳定的 4.0 的开发周期的一部分。
  • Timm Bäder 正在他的 drawing 分支 上工作,该分支旨在将 CSS 小工具的所有内部使用替换为真正的窗口小部件。有关更多信息,请参阅此博客上的 这篇文章
值得注意的更改

在主分支上

  • Carlos Garnacho 合并了他的 event-delivery 分支,该分支将事件处理从 GDK 窗口层次结构移动到 GTK 窗口小部件层次结构;这是朝着删除顶级窗口之外的所有 GdkWindow 实例迈出的第一步,最终将改进输入处理。
修复的错误
  • 745289 wayland:在连接错误时不使用 g_error()
参与其中

有兴趣参与 GTK+ 的开发吗?请查看新手错误列表并加入 irc.gnome.org 上的 IRC 频道 #gtk+。

容器的秘密

我最近花了一些时间追踪 GTK+ 容器和绘图的一个问题。这是我发现的。

CSS 绘图

在 GTK+ 3.22 中,大多数(如果不是全部)容器都支持具有多个背景和边框层的完整 CSS 绘图模型。例如,GtkFrame 现在就是这样绘制其框架的。但是,通常只排列其子元素的容器(例如 GtkBox)可以绘制背景和边框。可能性是无限的!

例如,我们可以使用 GtkBox 在列表框和标签周围放置一个框架,使标签在视觉上看起来像是列表的一部分。您甚至可以使用一些 CSS 使其色彩缤纷且有趣,例如

box.frame {
 border: 5px solid magenta;
}

分配和调整大小

传统上,GTK+ 中的大多数容器都不执行自己的任何绘制,而只是排列其子元素,因此当其大小更改时,它们实际上不需要进行完整的重绘 - 重绘子元素就足够了。这就是 gtk_container_set_reallocate_redraws() 的用途。在 GTK+ 3 中,它默认为 FALSE,因为我们不想在分配发生变化时冒着添加过多重绘的风险。

您可以看出事情的发展方向:如果我使用删除按钮从配料列表中删除黄油和盐,则列表的分配(以及周围的框的分配)将缩小,并且会出现重绘问题。

解决方案

如果您计划使普通布局容器绘制背景或边框,请确保将正确的窗口小部件(在本例中为有趣框的父级)的 reallocate-redraws 设置为 TRUE。

gtk_container_reallocate_redraws (GTK_CONTAINER (parent), TRUE);

请注意,gtk_container_reallocate_redraws() 在 GTK+ 3.22 中已弃用,因为我们将在 GTK+ 4 中将其删除并自动执行正确的操作。但这不应该阻止您使用它来解决此问题。

另一种(也许更好)的替代方法是使用旨在绘制边框的容器,例如 GtkFrame。

日志记录等

不久前,GLib 获得了一个新的“结构化日志记录”工具。与此同时,它还获得了将日志写入 systemd 日志的支持。显然,GLib 中的日志记录变得更加复杂,并且可能会让人感到困惑。

本文试图澄清一些问题。

结构化与非结构化

传统的 GLib 日志记录工具是 g_message()g_debug() 等宏,它们最终会调用 g_log() 函数,然后使用已通过 g_log_set_handler() 设置的日志处理程序来执行实际写入。您可以将任何您喜欢的信息放入您的日志中,但它必须全部格式化为单个字符串,即消息。

g_debug ("You have %d eggs", 12 + 2);

g_log (G_LOG_DOMAIN,
       G_LOG_LEVEL_DEBUG,
       "You have %d eggs", 12 + 2);

使用新的结构化日志记录工具,您可以调用 g_log_structured(),然后使用日志写入器函数来执行写入。到目前为止,这与较旧的日志记录工具非常相似。结构化日志的优点在于,您可以将多个字段放入日志中,而无需将其全部格式化为字符串。相反,您传递一个日志字段数组,它们是键值对。

g_log_structured (G_LOG_DOMAIN,
                  G_LOG_LEVEL_DEBUG,
                  "CODE_FILE", "mysource.c",
                  "CODE_LINE", 312,
                  "MESSSAGE_ID", "06d4df59e6c24647bfe69d2c27ef0b4e",
                  "MESSAGE", "You have %d eggs", 12 + 2);

这里的 CODE_FILECODE_LINEMESSAGE_ID 只是“标准”字段的示例。您也可以创建自己的字段。请注意,您仍然可以对 MESSAGE 字段使用 printf 样式的格式化。

因此,GLib 现在有两个单独的日志记录工具。为了使事情变得更有趣,我们允许您将 g_message()g_debug() 等包装宏重定向为在底层使用 g_log_structured() 而不是 g_log()。为此,请在包含 glib.h 之前定义 G_LOG_USE_STRUCTURED 宏。

为什么这很有用?一方面,它可以省去您替换所有 g_debug() 的麻烦,并且仍然可以让您利用结构化日志记录的一些优势 - 当以这种方式使用时,传统的宏将为日志域、代码文件和行以及其他一些字段使用单独的字段,这有助于在生成的日志中进行过滤和搜索,尤其是在使用 systemd 日志时。

另一个优点是,您可以使用单个后端(日志写入器函数)来控制旧的和新的日志记录调用最终的位置。

我的所有日志都去哪了?

结构化日志记录通常与 systemd 日志关联。因此,人们期望 g_log_structured() 输出到日志中也就不足为奇了。对于服务,或者当您从桌面图标启动应用程序时,这确实非常有用。但是,如果您从终端运行它,您可能会希望看到它的输出在那里。

为了满足这些相互竞争的需求,GLib 默认的日志写入器函数会尝试变得智能。如果它检测到 stderr 被重定向到 journald 套接字,那么它会将其结构化输出写入日志。否则,它会格式化一条消息并将其写入 stderr

GNOME Shell 和 DBus 都会在启动应用程序或服务时安排将 stderr 重定向到日志。显式将 stderr 重定向到日志的一种方法是在 systemd-cat 下运行您的应用程序

systemd-cat my-app-that-logs

如果您确定希望您的日志始终进入日志,您可以告诉 GLib 使用执行此操作的日志写入器

g_log_set_writer_func (g_log_writer_journald, NULL, NULL)

超越默认值

即使 GLib 默认提供的日志写入器函数应该满足许多需求,您可能仍然需要编写自己的日志写入器函数。在这种情况下,GLib 有许多有用的函数可以帮助您,例如 g_log_writer_format_fields()g_log_writer_is_journald()g_log_writer_supports_color()

日志记录愉快!

参考

  • Philipps 关于结构化日志记录的 演讲
  • GLib 日志记录 文档
  • Systemd 日志 文档

使用 GSettings 的第一步

我们有时会在 #gtk+ 中收到有关 GSettings 的问题,以及是否在“简单”情况下使用此 API 是一个好主意。我认为答案非常明确:是,本文试图解释原因。

好处

GSettings 的一个优点是它是一个高级 API,具有各种本机配置系统的后端。因此,如果您有一天将您的应用程序移植到 OS X 或 Windows,您的应用程序将自动使用预期的平台 API 来存储其设置(Windows 上的注册表和 OS X 上的 plists)。

即使您的应用程序永远不会移植到这些平台,在 Linux 上使用的 dconf 后端也具有强大的功能,例如配置文件和锁,这些功能允许系统管理员配置您的应用程序,而无需您担心。

不幸的是,GSettings 的文档使其看起来比实际复杂,因为它并没有真正试图隐藏可供您使用的强大功能(配置文件、供应商覆盖、翻译的默认值、复杂类型、绑定等)。

因此,这里有一个关于 GSettings 的第一步指南,适用于一个简单的情况。

开始使用

让我们从最简单的设置开始:一个布尔值。

最大的障碍是 GSettings 坚持要有一个模式,该模式定义每个键的数据类型和默认值。

<schemalist>
  <schema path="/org/gnome/recipes/"       
         id="org.gnome.Recipes">
    <key type="b" name="use-metric">
      <default>true</default>
      <summary>Prefer metric units</summary>
      <description>
        This key determines whether values
        such as temperatures or weights will
        be displayed in metric units.
      </description>
    </key>
  </schema>
</schemalist>

需要安装模式(这样做的好处是,诸如 dconf-editor 之类的工具可以使用模式信息)。如果您使用的是 autotools,则可以使用宏支持此功能。只需添加

GLIB_GSETTINGS

到您的 configure.ac 中,以及

gsettings_SCHEMAS = org.gnome.Recipes.gschema.xml
@GSETTINGS_RULES@

到 Makefile.am 中。meson 的设置类似。

现在我们已经定义了我们的键,我们可以像这样获取它的值

s = g_settings_new ("org.gnome.Recipes");
if (g_settings_get_boolean (s, "use-metric"))
  g_print ("Using metric units");

我们可以像这样设置它

s = g_settings_new ("org.gnome.Recipes");g_settings_set_boolean (s, "use-metric", TRUE);

使用 GSettings 处理其他基本类型(例如整数、浮点数、字符串等)非常相似。只需使用适当的 getter 和 setter。

您可能想知道我们在这里创建的设置对象。在需要时创建它们是完全可以的。如果您愿意,也可以创建一个全局单例,但除非您想监视设置的更改,否则没有实际需要这样做。

下一步:复杂类型

除了基本类型之外,您还可以使用 GVariant 类型系统的全部功能来存储复杂类型。例如,如果您需要存储有关平面中圆形的信息,您可以将其存储为类型为(ddd)的三元组,存储圆心的 x、y 坐标和半径。

要在代码中处理具有复杂类型的设置,请使用 g_settings_get() 和 g_settings_set(),它们以 GVariant 的形式返回和接受值。

下一步:可重定位的模式

如果您的应用程序使用帐户,您可能需要查看可重定位的模式。当您需要同一配置的多个实例(单独存储)时,您需要使用可重定位的模式。一个典型的例子是帐户:您的应用程序允许创建多个帐户,并且每个帐户都关联有相同的配置信息。

GSettings 通过省略模式中的路径来处理这种情况

<schemalist>
  <schema id="org.gnome.Recipes.User">
    <key type="b" name="use-metric">
      <default>true</default>
    </key>
  </schema>
</schemalist>

相反,我们在创建 GSettings 对象时需要指定一个路径

s = g_settings_new_with_path ("org.gnome.Recipes.User",
                              "/org/gnome/Recipes/Account/mclasen");
if (g_settings_get_boolean (s, "use-metric"))
  g_print ("User mclasen is using metric units");

由您来设计一个模式,将您的帐户映射到唯一的路径。

绊脚石

使用 GSettings 时,需要注意一些事项。其中之一是 GLib 需要能够在运行时找到已编译的模式。当您在不安装的情况下从构建目录运行应用程序时,这可能会成为一个问题。要处理这种情况,您可以设置 GSETTINGS_SCHEMA_DIR 环境变量来告诉 GLib 在哪里查找已编译的模式

GSETTINGS_SCHEMA_DIR=build/data ./build/src/gnome-recipes

另一个绊脚石是 GSettings 以序列化的 GVariant 形式读取 XML 中的默认值。对于常见的字符串情况,这可能会有点令人惊讶,因为这意味着我们需要在字符串周围加上引号

<default>'celsius'</default>

但这些都是小问题,一旦您了解了它们,就可以很容易地避免。