ApplicationCommandLine

In Terminix I’m making some improvements to how I’m handling the command-line by moving away from D’s getopts to the GTK method of dealing with it. The primary driver for this is to get the command line sent from the local application to the primary one. To do this, I registered my Options via setMainOptions, added a handler to addOnCommandLine and included the flag HANDLES_COMMAND_LINE when creating the application.

It all worked swimmingly except for one issue, while the primary got the command line just fine, the local instance would just hang and never return. After scratching my head for a bit and asking on the GTK IRC channel, a pointer to a C example gave me the nudge I needed. Looking more closely at the documentation for ApplicationCommandLine, I see this line:

For the case of a remote invocation, the remote process will typically exit when the last reference is dropped on @cmdline.

The issue here is that since D is GC’ed, the ApplicationCommandLine doesn’t get collected in a deterministic fashion. The solution was quite easy, simply destroy it at the end via a scope(exit) block. I’m going to file an issue with GtkD to see if this shouldn’t be made a Scoped reference.

GtkD and Localization

On my todo list for awhile has been adding localization support to Terminix. Unfortunately at this time D does not have an official i18n package nor are there any libraries supporting the GNU gettext which has become a De facto standard for localization over the years. While there are some localization libraries available for D, I really wanted to stick with gettext given the huge eco-system it has in terms of support tooling and websites.

While examining my options, I stumbled on the fact that the GTK GLib library supports gettext and that GtkD has already wrapped them in the glib.Internalization class. After refreshing my knowledge of how gettext works I was able to put together a basic working localization system in about an evenings worth of work. The remainder of the post will describe the steps involved with getting it working.

I knew in Terminix that I would need to support localization, so from day one I had a module gx.i18n.l10n that contained a single function following the gnu gettext pattern as follows:

string _(string text) {
    return text;
}

Whenever something in Terminix would eventually need to be localized, I would simply wrap it in a call to this function. Obviously at this early stage the function does nothing beyond returning the original text. An example of its usage is as follows:

string msg = _("This is a localized string");

For those of you familiar with the usage of gettext in C or other languages this will all seem familiar. For anyone else, in gettext any text you want to localize is wrapped in this function and becomes the key for retrieving a localized version. If no localized version is available, i.e. for a language for which a translation does not yet exist, the key is used as the text that is displayed. in my opinion, this system is much superior to localization systems that require programmers to have to embed artificial keys in the code and is one reason why gettext is so popular.

The next step was to simply incorporate the glib.Internationalization class from GtkD, so I updated my _() method as follows:

string _(string text) {
    return Internationalization.dgettext(_textdomain, text);
}

The textdomain parameter above is used by gettext to differentiate this application from others and will be discussed in more detail later. At this point, the application now supports localization but the real work begins as we need to prepare all of the localization materials that gettext requires. In gettext there are three types of files required for localization:

  • Template. This file, with a .pot extension, is used as the template for the localization. For a given application there is typically only one template file.
  • Portable Object (po). These files, with the extension .po, contain the localization for a specific locale, for example en.po or fr.po. These files are in the same format as the template file.
  • Machine Object (mo). These are the binary files that are used at runtime and are created by the msgfmt utility. These files have a 1:1 mapping with a po file in that each mo file is created from a po file.

Here’s an extract showing what a template/po file looks like:

msgid "Match entire word only"
msgstr "Match entire word only"

msgid "IBeam"
msgstr "IBeam"

msgid "Run command as a login shell"
msgstr "Run command as a login shell"

msgid "Exit the terminal"
msgstr "Exit the terminal"

in the extract above, the msgid is the key while the msgstr is the actual localization and as per above in the template file these will be identical. Additionally for most applications there is no need to provide a localization file for the locale the developer is using since that locale is already embedded in the source code by default.

The challenge at this point was creating the template file, while the gettext program has a utility called xgettext that can extract all of the localized strings from source code, unfortunately D is not one of the languages supported. I thought about creating a version of xgettext using the excellent libdparse, however I opted for a quick and dirty method as Terminix doesn’t have a large amount of strings needing localization.

What I ended up doing is adding some code to my l10n module to capture all the localization requests and then write it out a file when the application terminates. This has the advantage of being relatively easy to do but the disadvantage that you have to exercise the app pretty completely to capture all the strings. For Terminix there were only a few areas I couldn’t exercise easily and for those I simply updated the template file after the fact. Below is the code I used to generate the template file, note the use of the Version specification so this code only gets included in a specific build configuration. I’ve removed some of the comments for the sake of conciseness, you can view the original code in github.

module gx.i18n.l10n;
 
import glib.Internationalization;
 
version (Localize) {
 
    import std.experimental.logger;
    import std.file;
    import std.string;
 
    string[string] messages;
 
    void saveFile(string filename) {
        string output;
        foreach(key,value; messages) {
            if (key.indexOf("%") >= 0) {
                output ~= "#, c-format\n";
            }
            if (key.indexOf("\n") >= 0) {
                string lines;
                foreach(s;key.splitLines()) {
                    lines ~= "\"" ~ s ~ "\"\n"; 
                }
                output ~= ("msgid \"\"\n" ~ lines);
                output ~= ("msgstr \"\"\n" ~ lines ~ "\n");
            } else {
                output ~= "msgid \"" ~ key ~ "\"\n";
                output ~= "msgstr \"" ~ key ~ "\"\n\n";
            }
        }
        write(filename, output);
    } 
}
 
void textdomain(string domain) {
    _textdomain = domain;
}
 
string _(string text) {
    version (Localize) {
        trace("Capturing key " ~ text);
        messages[text] = text;
    }
 
    return Internationalization.dgettext(_textdomain, text);
}
 
private:
string _textdomain;

When capturing text, there are a couple of special cases in gettext to be aware of. The first is that the xgettext utility puts a special comment in front of strings that use C style formatting, i.e. %d or %s. I don’t think this comment is used but I wanted to keep it. The second is that the key for multi-line strings is generated with each line separated. That’s why in the code above you see the check for newline and the splitLines call.

Once the template file is completed we are ready to create our first localization. In my case, I created an English localization as Terminix has some programmatic terms (shortcut identifiers) that were intended to be localized to human friendly language rather then shown directly to the user. Creating the en.po file is just a matter of copying the terminix.pot file to en.po. While gettext has a utility for this, msginit, I just opted to copy it for simplicity.

Once the en.po localization was completed it needs to be compiled into a mo file. In Linux, mo files are stored in usr/share/locale/${LOCALE}/LC_MESSAGES where ${LOCALE} is the standard language/country code. The mo files for the application are named after the textdomain for the application and this is how gettext locates the right file for the application. For example, in Terminix’s case the full path to the English mo file would be usr/share/locale/en/LC_MESSAGES/terminix.mo.

To compile the mo file, simply use the gettext msgfmt utility as follows:

sudo msgfmt en.po -o /usr/share/locale/en/LC_MESSAGES/terminix.mo

Obviously you would want to script the above process as part of creating an installation package, you can see how Terminix does this here.

Using the GAction Framework

In GUI development you typically want to centralize event handlers given that it is pretty common to have multiple GUI elements (button, menu item, etc) invoking the same event. In many frameworks these centralized event handlers are typically referred to as actions. In GTK the action framework is part of GIO and is referred to as GAction. An overview of the framework can be found in the Gnome wiki in this How Do I entry.

Having done Delphi development 15 years ago I had some familiarity with the concept but it still took a bit of time to get up to speed on GActions. While the Gnome wiki provided some basic information, I still found it confusing in some spots so I thought I’d write a bit about my experience using GActions in Terminix as it specifically relates to the D language and the GtkD framework.

First some basics, every GAction has two elements that identify it, a prefix and a name. The prefix is a way to categorize actions and within a prefix the name of the action must be unique. The prefix and the name together, with a period between them, make a detailed action name and this is how you reference actions when binding them to UI elements. So if you have a prefix of “terminal” and a name of “copy” the detailed name would be “terminal.copy”.

Actions are stored in containers which typically implement the interfaces gio.ActionMapIF and gio.ActionGroupIF. Out of the box, GTK implements these interfaces in Application and ApplicationWindow. Actions registered against these objects are automatically assigned the prefix of “app” and “win” respectively. Typically actions registered against the Application object are used in the Gnome application menu and actions registered against ApplicationWindow operate against that window.

An important thing to understand though is you are not limited to registering actions against just Application and ApplicationWindow, you can register actions against any widget in your application using custom prefixes. The key is using the gio.SimpleActionGroup to create your own container to register actions against. You can then bind the SimpleActionGroup to a widget via the method insertActionGroup. An example of this is shown below:

SimpleActionGroup sagTerminalActions = new SimpleActionGroup();
//...create actions and register to the group
insertActionGroup(“terminal”, sagTerminalActions);

Understanding how to create your own action groups gives you a lot of flexibility in organizing your actions. Terminix is an application where the UI is heavily nested and generally I wanted to keep each layer isolated from each other. I didn’t want the Terminal to know too much about the containers it is nested within. Being able to create these custom action groups allows the terminal to have it’s own set of actions without needing a mechanism to bind them to the window.

The other benefit to this approach is that the GIO framework will allow you to have multiple instances of the same action and will determine which instance get’s invoked based on the scope. In Terminix I have multiple terminals each with the same set of “terminal” actions registered. The GAction framework automatically invokes the right action instance based on the terminal that has focus presumably by walking up the widget hierarchy until it finds the first widget with the action it is looking for.

To register an action, you simply create an instance of gio.SimpleAction which is a concrete implementation of the ActionIF interface and the add it to the desired ActionMap. A simple example would look like:

action = new SimpleAction(“copy”, null);
action.addOnActivate(delegate(Variant, SimpleAction) {
vte.copyClipboard();
});
sagTerminalActions.add(action)

Once you have your action, the next step is to bind it to a user interface element. One thing to be aware of is that in GTK there are actually two action frameworks, the one in the gio package (SimpleAction, SimpleActionGroup) and one in the gtk package (Action, ActionGroup). The one in GTK is considered deprecated and it is highly recommended to use the GIO action framework where possible. In general, GTK widgets that work with GIO actions use the method setActionName to specify the target action, the method setActionTargetValue is used by the GTK framework.

To bind the action to a UI element, you simply call the setActionName method with the detailed action name, for example:

button.setActionName(“terminal.copy);

Actions can be represented in menus using a variety of UI elements such as check and radio buttons. This involves declaring that an action has state which is represented by a GLib Variant in the Activate signal. I won’t spend any time on this topic, however for those interested I wrote an actions demo showing how to do this with popovers and it is available in the GtkD repository here.

If you want your action to have an accelerator, i.e. a keyboard shortcut, you need to register the key and the detailed action name with the your global GTK Application instance using the method Application.setAccelsForAction. Here is an example of this:

app.setAccelsForAction(“terminal.copy”, [“c”]);

This concludes the article on GActions, hopefully as you can see this provides a powerful framework for centralizing your event handlers within a GTK application. One final note, for the purposes of this article I’ve been using strings directly for prefixes and names for illustration. Obviously, you should declare these as constants in your code to ensure consistency. If you need to change the name of a prefix or action, having the string declared in one spot makes that job much easier.