Container secrets

I recently spent some time tracking down a problem with GTK+ containers and drawing. Here is what I found out.

CSS drawing

In GTK+ 3.22, most, if not all, containers support the full CSS drawing model with multiple layers of backgrounds and borders. This is how, for example, GtkFrame draws its frame nowadays. But also containers that normally only arrange their children, such as GtkBox, can draw backgrounds and borders. The possibilities are endless!

For example, we can use a GtkBox to put a frame around a list box and a label, to make the label visually appear as part of the list. You can even make it colorful and fun, using some CSS like:

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

Allocation and resizing

Traditionally, most containers in GTK+ are not doing any drawing of their own and just arrange their children, and thus there is no real need for them to do a full redraw when their size changes – it is enough to redraw the children. This is what gtk_container_set_reallocate_redraws() is about. And it defaults to FALSE in GTK+ 3, since we did not want to risk adding excessive redraws  whenever allocations change.

You can see where this is going: If I use the delete button to remove Butter and Salt from the list of ingredients, the allocation of the list, and thus of the box around it, will shrink, and we get a redraw problem.

The solution

If you plan to make plain layout containers draw backgrounds or borders, make sure to set reallocate-redraws to TRUE for the right widgets (in this case, the parent of the fun box).

gtk_container_reallocate_redraws (GTK_CONTAINER (parent), TRUE);

Note that gtk_container_reallocate_redraws() is deprecated in GTK+ 3.22, since we will get rid of it in GTK+ 4 and do the right thing automatically. But that shouldn’t stop you from using it to fix this issue.

Another (and maybe better) alternative is to use a container that is meant to draw a border, such as GtkFrame.

Logging and more

A while ago, GLib gained a new facility for ‘structured logging’. At the same time, it also gained support for writing logs to the systemd journal. Clearly, logging in GLib got a bit more complicated, and it can be a bit confusing.

This article is an attempt to clarify things.

Structured or not

The traditional GLib logging facilities are the g_message(), g_debug(), etc macros, which eventually call the g_log() function, which then uses the log handler that has been set with g_log_set_handler() to do the actual writing. You can put any information you like into your logs, but it has to all be formatted as a single string, the message.

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

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

With the new structured logging facilities, you call g_log_structured(), which then uses a log writer function to do the writing. So far, this is very similar to the older logging facility. The advantage of structured logs is that you can put multiple fields into the log without resorting to formatting it all into a string. Instead, you pass an array of log fields, which are key-value pairs.

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_FILE, CODE_LINE and MESSAGE_ID here are just examples for “standard” fields. You can also invent your own fields. Note that you can still use printf-style formatting for the MESSAGE field.

So GLib has two separate logging facilities now. To make matters a bit more interesting, we allow you to redirect the g_message(), g_debug(), etc wrapper macros to use g_log_structured() instead of g_log() under the covers. To do so, define the G_LOG_USE_STRUCTURED macro before including glib.h.

Why is this useful? For one thing, it saves you the trouble of replacing all your g_debug() and still lets you take some advantage of the structured logging – when used in this fashion, the traditional macros use separate fields for the log domain, the code file and line and some other fields, which can be helpful for filtering and searching in the resulting logs, in particular with the systemd journal.

Another advantage is that you can use a single backend, the log writer function, to control where both old and new logging calls end up.

Where have all my logs gone ?

Structured logging is frequently associated with the systemd journal. So, it is not surprising that people expect the g_log_structured() output to go the journal. And that is a really useful thing for services, or when you are launching an application e.g. from a desktop icon. But if you run it from the terminal, you probably expect to see its output right there.

To satisfy these competing demands, the GLib default log writer function tries to be smart. If it detects that stderr is redirected to the journald socket, then it writes its structured output to the journal. Otherwise, it formats a message and writes it to stderr.

Both GNOME Shell and DBus arrange for stderr to be redirected to the journal when they start apps or services. A way to explicitly redirect stderr to the journal is to run your application under systemd-cat:

systemd-cat my-app-that-logs

If you are sure that you want your logs to always go to the journal, you can tell GLib to use a log writer that does that:

g_log_set_writer_func (g_log_writer_journald, NULL, NULL)

Beyond the default

Even though the log writer functions that GLib provides by default should serve many needs, you might need to write your own. In that case, GLib has a number of useful functions that can help you, such as g_log_writer_format_fields(), g_log_writer_is_journald() or g_log_writer_supports_color().

Happy logging!

References

First steps with GSettings

We sometimes get questions about GSettings in #gtk+, and whether it is a good idea to use this API for ‘simple’ cases. The answer is a very clear Yes, in my opinion, and this article is trying to explain why.

Benefits

One of the nice things about GSettings is that it is a high-level API with backends for various native configuration systems. So, if you ever get around to porting your application to OS X or Windows, your application will automatically use the expected platform API to store its settings (the registry on Windows, and plists on OS X).

And even if your application will never be ported to those platforms, the dconf backend that is used on Linux has powerful features such as profiles and locks that let system administrators configure your application without you having to worry about it.

The documentation for GSettings unfortunately makes it appear more complicated than it is, since it doesn’t really try to hide the powerful features that are available to you (profiles, vendor overrides, translated defaults, complex types, bindings, etc).

So, here is a guide to first steps with GSettings, in a simple case.

Getting started

Lets get started with the simplest possible setting: a boolean.

The biggest hurdle to overcome is that GSettings insists on having a schema, which defines the datatype and default value for each key.

<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>

Schemas need to be installed (the advantage of this is that tools such as dconf-editor can use the schema information). If you are using autotools, this is supported with macros. Just add

GLIB_GSETTINGS

to your configure.ac, and

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

to Makefile.am. The setup with meson is similar.

Now that we’ve defined our key, we can obtain its value like this:

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

and we can set it like this:

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

Using GSettings for other basic types such as integers, floating point numbers, strings, etc. is very similar. Just use the appropriate getters and setters.

You may be wondering about the settings objects that we are creating here. It is fine to just create these whenever you need them. You can also create a global singleton if you prefer, but there is not real need to do so unless you want to monitor the setting for changes.

Next Steps: complex types

Beyond basic types, you have the full power of the GVariant type system available to store complex types. For example, if you need to store information about a circle in the plane, you could store it as a triple of type (ddd) storing the x, y coordinates of the center and the radius.

To handle settings with complex types in your code, use g_settings_get() and g_settings_set(), which return and accept the value in the form of a GVariant.

Next Steps: relocatable schemas

If your application uses accounts, you may want to look at relocatable schemas. A relocatable schema is what you need when you need multiple instances of the same configuration, stored separately. A typical example for this is accounts: your application allows to create more than one, and each of them has the same kind of configuration information associated with it.

GSettings handles this by omitting the path in the schema:

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

Instead, we need to specify a path when we create the GSettings object:

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");

It is up to you to come up with a schema to map your accounts to unique paths.

Stumbling blocks

There are a few things to be aware of when using GSettings. One is that GLib needs to be able to find the compiled schema at runtime. This can be an issue when running your application out of the build directory without installing it. To handle this situation, you can set the GSETTINGS_SCHEMA_DIR environment variable to tell GLib where to find the compiled schema:

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

Another stumbling block is that GSettings reads the default values in the XML in the form of a serialized GVariant. This can be a bit surprising for the common case of a string, since it means that we need to put quotes around the string:

<default>'celsius'</default>

But these are minor issues, and easily avoided once you know about them.