Linux + Dotnet Core – GSettings (LibGio) directly from code

Linux + Dotnet Core – GSettings (LibGio) directly from code

2019-03-14 2 By Nordes

Some people like to execute shell commands for LibGio – GSettings using NodeJs (child_process) or Dotnet (Process). Personally, I prefer to use directly the C/C++ API from the compiled binaries. It helps to keep the performance and avoid spawning new process instances for no good reason.

In this article, we will talk about how to call the libgio (GSettings especially) from our Dotnet application. Obviously, this will work only from Linux having the library. People familiar with Linux/Gnome GUI already knows about the libgio by using C or C++, but what about using C#?

In case you want to know more about libgio, I recommend you to go on the Gnome Developer site and as extra resource you can also go on their blog post about the topic (First Step with GSettings). I will repeat myself later, but, the GSettings links configuration to your session using the DBUS session. If you don’t have any session, you will be able to read the default value, but not to modify them.

What are we going to experiment?

We will simply try to get the GSettings and set a new value. It means that we will be using only 3 API’s our of many. The full name of the library being used is libgio-2.0.so and those 3 API’s are:

As you can see, the GSettings is a database containing the key-value pairs of strongly typed documents. What is really under the hood for the schema is XML files. The XML is then compiled and the system will be using binary data instead of XML. The normal emplacement for the schemas is in /usr/share/glib-2.0/schemas. Some details about how to create your own schema (Python) can be found on that blog. In case you don’t want to have to specify the path on each request on your custom schema, simply update it by specifying the path directly in the schema element.

In the case you want to test using the command line, you can use the gsettings –help. In case you play in your subsystem (Windows10), you might want to install libglib2.0-bine and dconf-gsettings-backend.

Example of gsettings file for your application

The example bellow, is purely fictional, but if you wish you can look to existing schema available in /usr/share/glib-2.0/schemas/. If the path is part of the schema element, you won’t need to write the path at every request. Also note that the GSettings set won’t work if your dbus is not started for your user. The dbus bridge your settings with your current session.

<schemalist>
  <enum id="com.honosoft.sample.enum">
    <value nick="off" value="1"/>
    <value nick="warming" value="2"/>
    <value nick="on" value="3"/>
  </enum>

  <schema path="/usr/share/glib-2.0/schemas/" id="com.honosoft.sample">
 
    <key type="b" name="my-flag-is-active">
      <default>false</default>
      <summary>I am a boolean flag</summary>
      <description>A description of your boolean</description>
    </key>
    <key type="i" name="threshold">
        <range min="1" max="100"/>
        <default>50</default>
        <summary>Some threshold</summary>
        <description>Some threshold from 1 to 100 about something</description>
    </key>
 
    <key name="list-prime-numbers" type="ai">
      <default>[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43]</default>
      <summary>Some numbers</summary>
      <description>
        List of numbers
      </description>
    </key>
 
    <key name="list-my-pets" type="as">
        <default>['Captain', 'Scrooge', 'Mana', 'Saturn', 'Brutal']</default>
        <summary>Some array of strings</summary>
        <description>List my pet(s) name</description>
    </key>
 
    <key name="current-state" enum="com.honosoft.sample.enum">
        <default>'off'</default>
        <summary>display a state of my application</summary>
        <description>Using the enum allow only a few values pre-defined</description>
    </key>

    <child name="best-book" schema="com.honosoft.sample.book"/>
  </schema>
 
  <schema path="/usr/share/glib-2.0/schemas/" id="com.honosoft.sample.book">
      <key name="title" type="s">
          <default>'Clean Code'</default>
          <summary>Define the book title</summary>
      </key>
      <key name="author" type="s">
          <default>'Robert C. Martin'</default>
          <summary>Define the author name</summary>
      </key>
  </schema>
</schemalist>

After putting your file in the /usr/share/glib-2.0/schemas/ , you need to compile it. To compile and make it available, you have to execute glib-compile-schemas /usr/share/glib-2.0/schemas/ After, you will be able to access the key-value-pair. Note that the command exists simply for debugging purpose. More details can be found on that article.

Sample to retrieve an array of string

How do we call such library from C# (Dotnet)

It’s not as complex as you might think. There’s some gotcha, and if you have C/C++ knowledge it will help you to understand those (such as void pointers).

In Dotnet, we have the attribute DllImports which consists of doing the link between the “dll” (or compiled C/C++ .so) and the framework. The best practice for that would be to create a layer of abstraction over the layer mapping such commands. Here’s the 3 command that we will be using mapped in C# using the attribute:

[DllImport("libgio-2.0.so", EntryPoint = "g_settings_new")]
public static extern IntPtr New(string schema);

[DllImport("libgio-2.0.so", EntryPoint = "g_settings_get_int")]
public static extern int GetInt(IntPtr settings, string key);

[DllImport("libgio-2.0.so", EntryPoint = "g_settings_set_int")]
public static extern bool SetInt(IntPtr settings, string key, int value);

As you can see, the IntPtr is a reference to the GSettings schema.

Let’s experiment the whole process

  1. Request to the user what schema to retrieve
  2. Request to the user what key (int key) to retrieve from the schema
  3. Get the int value from the schema + key
  4. Request the user to give a new value
  5. Set or store the new value
class Program
{
    static void Main(string[] args)
    {
        Console.Write("Schema to look: ");
        var gsettingsSchema = Console.ReadLine();
        Console.Write("Key to look: ");
        var gsettingsKey = Console.ReadLine();

        var gsettings = DemoBindings.GSettings.New(gsettingsSchema);
        var result = DemoBindings.Bindings.GSettings.GetInt(gsettings, gsettingsKey);
        Console.WriteLine($"Value is: {result}");
        Console.Write("Please give me a new value to set: ");
        var newValue = Console.ReadLine();

        var intValue = int.Parse(newValue); // No validation, but you should ;)
        var success = DemoBindings.GSettings.SetInt(gsettings, gsettingsKey, intValue);
        Console.WriteLine($"The new value was set with {success ? string.Empty : "no "} success")
        Console.ReadKey();
    }
}

What does it mean for you?

In the eventually you would write an application that runs in the background triggering some event on the display, you could take advantage of the applications settings (listen even to those) and then maybe trigger some actions. For example:

  • Toggle the display of a virtual keyboard on a touch screen.
  • Get the current session details (windows)

I hope you’ve enjoyed, even if this is a really short blog.