Linux – HoNoSoFt https://blog.honosoft.com Blog & Roll Thu, 14 Mar 2019 12:36:02 +0000 en-CA hourly 1 https://wordpress.org/?v=6.8.9 https://blog.honosoft.com/wp-content/uploads/2018/06/logo.png Linux – HoNoSoFt https://blog.honosoft.com 32 32 Linux + Dotnet Core – GSettings (LibGio) directly from code https://blog.honosoft.com/2019/03/14/linux-dotnet-core-gsettings-libgio-directly-from-code/?utm_source=rss&utm_medium=rss&utm_campaign=linux-dotnet-core-gsettings-libgio-directly-from-code https://blog.honosoft.com/2019/03/14/linux-dotnet-core-gsettings-libgio-directly-from-code/#comments Thu, 14 Mar 2019 12:36:00 +0000 https://blog.honosoft.com/?p=518 Continue Reading]]> 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.

]]>
https://blog.honosoft.com/2019/03/14/linux-dotnet-core-gsettings-libgio-directly-from-code/feed/ 2
Linux & Dotnet – Read from a device file https://blog.honosoft.com/2019/03/12/linux-dotnet-read-from-a-device-file/?utm_source=rss&utm_medium=rss&utm_campaign=linux-dotnet-read-from-a-device-file https://blog.honosoft.com/2019/03/12/linux-dotnet-read-from-a-device-file/#comments Tue, 12 Mar 2019 15:21:37 +0000 https://blog.honosoft.com/?p=491 Continue Reading]]> As you may know, Dotnet Core now runs within Linux. Let’s explore and play around enjoying the fact we can play in any environment now in Core.

Let’s consider that you have a touch panel where you would like to handle everything by yourself (touch, move, etc.). How in the world would you get the touch event?

Probably, if you never played around that low in the system, you probably don’t know. Interestingly, there’s the HID (Human Interface Devices). Most of the documentation is available if you search from your favorite engine. What you really want to know, is the binary data structure, once you have that, the fun can begin. In the case you can’t get any description anywhere, I guess the best choice is to do some reverse engineering (not recommended).

In this article we will only analyse a touch event generating a lot of binary data. I don’t want to cover the entire touch/move/etc of the binary data.

Let’s take the touch screen “touch” click event (KeyPress/Release)

Find the Screen Input Device

Within Linux system, there’s plenty of command in order to find your devices. The device files are usually located within the /dev/…. folder. In the past, we used to have all the existing device in the world in that folder. Nowadays, we simply have the bare minimum and that’s great. Most of the devices, if not all, are documented in the Linus repository. There’s many way to retrieve more details or at least the input device of your choice (touch panel).

One of those method consist in looking in all the USB (/proc/bus/usb/devices) or Input devices (/proc/bus/input/devices) in your system. The touch panel is usually linked to an “Event[1..5]” device.

An alternative is to configure your display to point towards your X-Server and then use the xinput command. If you fail to execute that command, it’s either not installed or you simply forgot to set your environment variable DISPLAY.

xinput --list

Second alternative. Without installing anything, you can use the device manager and look directly in the database and find your touch screen. The command for such a case is udevadm and of course you are required to run this as a root (sudo).

# Give all the devices available on your system. If you have a lot of
# devices connected, consider making the output redirected into a file.
# e.g.: udevadm info --export-db > myFile.txt
udevadm info --export-db 

An another alternative, just in case, is to simply look in the special folder “/dev/input/by-id” and “/dev/input/by-path“. The first give the currently connected device with input capabilities. So if you connect or disconnect an input device, you should be able to see it there. By doing a “ls -l” you will see which event file it’s using since it’s creating a symbolic link to it. For example in this output the event1 is my touchscreen:

myroot@mymachine:/dev/input/by-path# ls -l
total 0
lrwxrwxrwx 1 nobody nogroup 9 Mar  6 08:54 pci-0000:00:14.0-usb-0:13.1:1.1-event -> ../event1
lrwxrwxrwx 1 nobody nogroup 9 Mar  7 23:55 pci-0000:00:14.0-usb-0:13.4.2:1.0-event-kbd -> ../event2

Normally, from here, you know which device file correspond to your device. You might even have more details if you used the udevadm.

Read the binary content

Devices files are the raw data file. In order to be able to do something, you need to stream it as you would do for a TCP/UDP/File data. The structure of the content is structured following the driver rules. For example, the Keyboard and Mouse and a few other devices are considered as well known devices and are well documented all over the internet. You can even go directly in the Unix source code in order to see the .h file.

Let’s create a simple loop receiving the binary content while you touch the panel and then let’s output that raw data using an hex string.

public void ReadDeviceStream(CancellationToken stoppingToken) 
{
  // Use the device file
  var targetFile = new FileInfo("/dev/inputs/event1");

  // Open a stream
  using (FileStream fs = targetFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  {
    stoppingToken.Register(() => fs?.Close());
    int blockId = 1;

    // A big buffer, for simplicity purpose and to receive the entire touch report. We should use
    // the proper buffer size based on the event size. Note that we could also
    // use the binary reader
    var buffer = new byte[1024];

    // Read until the token gets cancelled
    while (!stoppingToken.IsCancellationRequested && fs.Read(buffer) > 0)
    {
      ShowBinaryContent(blockId, buffer);
      blockId++;
    }
  }
}

public void ShowBinaryContent(int blockId, byte[] buffer)
{
  Console.WriteLine($"Block #{blockId}");
  Console.WriteLine(BitConverter.ToString(bytes)); // Hex format: AB-1D...
  Console.WriteLine(string.Empty);
}

How to interpret the data

The result of the previous small program is displayed here

// Block #1
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-39-00-03-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-35-00-31-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-36-00-E8-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-01-00-4A-01-01-00-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-00-00-31-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-01-00-E8-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-04-00-05-00-00-00-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-00-00-00-00-00-00-00-00

// Block #2
59-9A-74-5C-00-00-00-00-6F-40-05-00-00-00-00-00-03-00-35-00-2C-01-00-00-
59-9A-74-5C-00-00-00-00-6F-40-05-00-00-00-00-00-03-00-00-00-2C-01-00-00-
59-9A-74-5C-00-00-00-00-6F-40-05-00-00-00-00-00-04-00-05-00-10-27-00-00-
59-9A-74-5C-00-00-00-00-6F-40-05-00-00-00-00-00-00-00-00-00-00-00-00-00
            
// Block #3
59-9A-74-5C-00-00-00-00-5D-96-05-00-00-00-00-00-04-00-05-00-20-4E-00-00-
59-9A-74-5C-00-00-00-00-5D-96-05-00-00-00-00-00-00-00-00-00-00-00-00-00

// Block #4
59-9A-74-5C-00-00-00-00-7D-9A-05-00-00-00-00-00-03-00-39-00-FF-FF-FF-FF-
59-9A-74-5C-00-00-00-00-7D-9A-05-00-00-00-00-00-01-00-4A-01-00-00-00-00-
59-9A-74-5C-00-00-00-00-7D-9A-05-00-00-00-00-00-04-00-05-00-30-75-00-00-
59-9A-74-5C-00-00-00-00-7D-9A-05-00-00-00-00-00-00-00-00-00-00-00-00-00

As you can see, we receive many events at the same time and each block contains their own timestamp (few first bytes on each lines). A block here contains more than one instruction. For example, it tells you where you pushed and if it was a push event or release. Let’s focus on the data looking like the Block #1.

public static void Main()
	{
		// Release
		 var binaryStrings = 
		 new [] { "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-39-00-0C-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-35-00-51-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-36-00-93-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-01-00-4A-01-01-00-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-00-00-51-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-01-00-93-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-04-00-05-00-00-00-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-00-00-00-00-00-00-00-00"};

		foreach (var bs in binaryStrings) {
			byte[] data = bs.Split('-').Select(b => Convert.ToByte(b, 16)).ToArray();

			Console.Write($"[Sec: {BitConverter.ToUInt32(data, 0)}]");
			Console.Write($"\t[USec: {BitConverter.ToUInt32(data, 8)}]");
			Console.Write($"\t[Type: {BitConverter.ToUInt16(data, 16)}]");
			Console.Write($"\t[Code: {BitConverter.ToUInt16(data, 18)}]");
			Console.Write($"\t[Value: {BitConverter.ToInt32(data, 20)}]");
			Console.WriteLine();
		}
	}
		/*
Ref: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
Ref: https://www.kernel.org/doc/Documentation/input/event-codes.txt

[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 57 ABS_MT_TRACKING_ID]    [Value: 268]  Unique ID of initiated contact
[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 53 ABS_MT_POSITION_X]    [Value: 337] Center X touch position 
[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 54 ABS_MT_POSITION_Y]    [Value: 403] Center Y touch position
[Sec: 1551171811]    [USec: 619071]    [Type: 1 EV_KEY]    [Code: 330 BTN_TOUCH]    [Value: 1] 
[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 0 ABS_X]    [Value: 337] 
[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 1 ABS_Y]    [Value: 403]
[Sec: 1551171811]    [USec: 619071]    [Type: 4 EV_MSC]    [Code: 5 MSC_TIMESTAMP]    [Value: 0] 
[Sec: 1551171811]    [USec: 619071]    [Type: 0 EV_SYN]    [Code: 0 SYN_REPORT]    [Value: 0] Indicate the batch is completed and we can proceed
		*/

All the documentation about the types, code, etc. is available from source of Linux (https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h).

The structure look like the following (4bit):

(C lang)

struct input_event {
	struct timeval time; // 2 unsigned uint32 or uint64
	unsigned short type; // 4 bit
	unsigned short code; // 4 bit
	unsigned int value;
};
Start PositionTypeName
0UINT32TSec (Timestamp seconds) – E3-00-75-5C + skip 4 bytes in 64bit arch
8UINT32TUsec (Timestamp microseconds) – 3F-72-09 + skip 4 bytes in 64bit arch
16UINT16Type – 4A-01
18UINT16Code – 01-00
20INT32Value – 00-00

The official documentation can be found at https://www.kernel.org/doc/Documentation/input/input.txt under the section 3.2.4 evdev and we can read the following:

The event codes are the same on all architectures and are hardware independent.

https://www.kernel.org/doc/Documentation/input/input.txt

How should we test?

It looks obvious, but in windows you might not be able to test/debug. You need a Linux system (Not subsystem). Since I use a touch panel with Linux, I simply logged the event output while I was experimenting the touch panel. I then replayed that same data while writing UT (TDD). After your TDD passes and that your event gets out as expected, you can then have the desired flow within your application. If you’ve never experimented, know that you can do remote debugging from Visual Studio (or code) to a Linux host. It works well and you might be able to debug more easily.

Existing library to support you

Here we go directly in the raw content, however, there’s a NuGet package that might help you realize what you need. As discussed in the beginning of the article, the HID is the keyword you want to look for, for such development. I haven’t played that much with the following library, but you should definitely use it in case you want to manage the devices from Dotnet. That library is OS agnostics and does not really care if you are on Windows, Linux or MacOS.

The only issue with that library is the lack of documentation. The best is to read the code itself or simply code everything by yourself.

]]>
https://blog.honosoft.com/2019/03/12/linux-dotnet-read-from-a-device-file/feed/ 1