So, I spent some time working on this Enlightenment gadget to auto-rotate the screen by reading the orientation through a DBus proxy kindly provided by IIO-Sensor-Proxy. I was happy to see the screen rotating accordingly to how I positioned my laptop. Sadly, the touch screen input was not rotated yet.
What did it mean? If I rotate my laptop of 90° and start moving my finger on the screen horizontally, the cursor would move vertically on the screen. That was very sad and confusing.
As you can see in the picture below, if I would have moved my finger along the white arrow, the cursor would have moved along the red one. Making the poor feline user even more confused ¯\(ツ)/¯
In practice, what I did was to use XRandR wrapped inside e_randr2.h
to rotate the output. XRandR is conveniently encapsulated by e_randr2.h. Of course, this was only half of what I needed to build a complete, working auto-rotate gadget.
Chapter 2: evdev and libinput
In time, I started to study how input worked under X and came around Evdev and libinput. As you can read from Wikipedia, evdev is a generic input event interface that exposes input events through character devices. Both X and Wayland rely on it although in different ways.
From xorg-xserver==1.16 X obtained support for libinput.
A big thank you to Raster that guided me a lot. Also by trying my gadget on another device and pointing me in the right direction. I start reading more and more about the framework I had to deal with to implement my idea.
Evdev
I started to read more about Evdev. You can find the documentation for it here: https://www.freedesktop.org/software/libevdev/doc/latest/index.html Although I found useful reading the python-evdev documentation at https://readthedocs.org/projects/python-evdev/downloads/pdf/latest/.
So, for evdev, my task was quite simple. Look for a device with the Absolute Axis Information property. Then I could have applied the Transformation matrix to that device.
Libinput
On libinput, the property I need to look for is libinput Calibration Matrix. More information can be found on the official documentation: https://wayland.freedesktop.org/libinput/doc/1.15.0/device-configuration-via-udev.html.
Rotate the input
So, the plan of actions was:
- Identify the right input device on X for the touchscreen, either through evdev or libinput.
- Get the Coordinate Transformation Matrix property
- Apply the transformation matrix to the property above
Easy peasy
Find the right device under X
Raster added support for to interface and get input devices under X in 42e691d9b4de72755747a376655663b1079bb592 by using XIQueryDevice
and XIListProperties
.
Conviniently, he added:
EAPI void ecore_x_input_devices_update(void); /**< @since 1.24 */
EAPI int ecore_x_input_device_num_get(void); /**< @since 1.24 */
EAPI int ecore_x_input_device_id_get(int slot); /**< @since 1.24 */
EAPI const char *ecore_x_input_device_name_get(int slot); /**< @since 1.24 */
EAPI char **ecore_x_input_device_properties_list(int slot, int *num_ret); /**< @since 1.24 */
EAPI void ecore_x_input_device_properties_free(char **list, int num); /**< @since 1.24 */
EAPI void *ecore_x_input_device_property_get(int slot, const char *prop, int *num_ret, Ecore_X_Atom *format_ret, int *unit_size_ret); /**< @since 1.24 */
EAPI void ecore_x_input_device_property_set(int slot, const char *prop, void *data, int num, Ecore_X_Atom format, int unit_size); /**< @since 1.24 */
So, the logic was quite simple. You can see a snapshot just below. The first function _is_device_a_touch_pointer starts by getting the number of devices and looping through them. For each device, it checks if the device is a pointer one. We will come back to this in a glimpse. When the pointer device is found, it checks for the presence of the Coordinate Transformation Matrix to make sure the device is capable of being rotated.
Now, how can we identify our pointer device? The _is_device_a_touch_pointer is here to do exactly this. It looks for either the evdev or libinput property for Absolute Axis Information.
static const char *CTM_name = "Coordinate Transformation Matrix";
int _fetch_X_device_input_number()
{
const char *dev_name = NULL;
char **property_name = NULL;
int dev_num = ecore_x_input_device_num_get();
int dev_number = -1;
for (int dev_counter=0; dev_counter<dev_num; dev_counter++)
{
dev_name = ecore_x_input_device_name_get(dev_counter);
int num_properties;
property_name = ecore_x_input_device_properties_list(dev_counter, &num_properties);
char **iterator = property_name;
int is_correct_device = _is_device_a_touch_pointer(dev_counter, num_properties, iterator);
if (is_correct_device == EINA_FALSE)
continue;
iterator = property_name;
for (int i=0; i<num_properties; i++)
{
if (strcmp(*iterator, CTM_name) == 0)
{
dev_number = dev_counter;
}
iterator++;
}
}
return dev_number;
}
int _is_device_a_touch_pointer(int dev_counter, int num_properties, char **iterator)
{
// Looking for a device with either a libinput property for calibration or the old evdev Axlis labels property.
int is_correct_device = EINA_FALSE;
for (int i=0; i<num_properties; i++)
{
if (strstr(*iterator, "libinput Calibration Matrix") != NULL)
is_correct_device = EINA_TRUE;
if (strstr(*iterator, "Axis Labels") != NULL)
{
int num_ret, unit_size_ret;
Ecore_X_Atom format_ret;
char *result = NULL;
result = ecore_x_input_device_property_get(dev_counter, *iterator, &num_ret, &format_ret, &unit_size_ret);
if (result != NULL) {
// Get the property value, check for "Abs MT Position"
}
is_correct_device = EINA_TRUE;
}
iterator++;
}
return is_correct_device;
}
Final consideration
My gadget starts to look complete now. The next step is to add support for Wayland.
You can watch this recording of how the gadget work and if you would like to contribute, please check out my repo: rafspiny/convertible. Please do not hesitate to contact me if you wish to know more or contribute.