Stub mtouch driver
This stub mtouch sample driver demonstrates how to integrate with the mtouch framework and is shown in the diagram below:

The stub sample generates touch events at a rate of 4 events per second with values based on the current time. You can compile the mtouch-stub.c code example below using:
qcc -V gcc_ntoaarch64le -shared mtouch-stub.c -Wl,-hlibmtouch-stub.so.1 -o libmtouch-stub.soOnce you've compiled the code example, copy it to the /system/lib directory on the target device.
If you aren't using an RPi4, or other ARM-based device, you may need to specify different target using the -V option of qcc.
To execute the driver, you need to specify it in a configuration file and copy the file to the target device, as per the following example:
begin mtouch
driver = stub
options = count=3,delay=1000,period=1000,priority=31
end mtouchThen, execute mtouch as the root user using:
mtouch -c ${ABSOLUTE_PATH_TO_CONFIG_DIR}/mtouch.confAfter executing mtouch, the system displays an error on the line containing libmtouch-calib[ERROR]. This occurs if you don't specify the scaling config in your configuration. However, this error is recovered by using default scaling mode in the following line:
$ slog2info -b mtouch
...
... mtouch[INFO]: Parsing config file: /data/home/root/mtouch.conf
... touch_display[INFO]: loading mtouch module stub...
... libinputevents[INFO]: Attaching mtouch driver
... touch_display[INFO]: loaded mtouch module stub...
... libmtouch-calib[ERROR]: read_scaling_conf: Failed to open scaling config file: /etc/system/config/scaling.conf
... mtouch[INFO]: Note: read_scaling_conf failed. Defaulting to scaling mode.
... libinputevents[INFO]: Changing mtouch coordinate mapping
... libinputevents[INFO]: Opening mtouch device #0
... mtouch[INFO]: starting module 0 with event queuing
... mtouch[INFO]: attached to mtouch0
... mtouch[INFO]: no transition to run-time policies
$Ensure that there is no program running that prevents creating screen context, for example a screen manager. If there is, then slay it (using root user):
$ pidin Ar
...
987171 fullscreen-winmgr
...
$ slay fullscreen-winmgrExecute the events utility as root user to see the generated events:
$ events
...
SCREEN_EVENT_CREATE(device=0x33fdd4f2d0, pid=0, id=0)
...
SCREEN_EVENT_MTOUCH_TOUCH(window=0x33fdd4f550, port id=1, touch id=0, sequence=0, pos=[0,0], size=[1,1], source pos=[0,0], source size=[1,1], orientation=0, pressure=1)
SCREEN_EVENT_MTOUCH_TOUCH(window=0x33fdd4f550, port id=1, touch id=1, sequence=1, pos=[0,0], size=[1,1], source pos=[0,0], source size=[1,1], orientation=0, pressure=1)
SCREEN_EVENT_MTOUCH_TOUCH(window=0x33fdd4f550, port id=1, touch id=2, sequence=2, pos=[0,0], size=[1,1], source pos=[0,0], source size=[1,1], orientation=0, pressure=1)
SCREEN_EVENT_MTOUCH_RELEASE(window=0x33fdd4f550, port id=1, touch id=0, sequence=3, pos=[0,0], size=[1,1], source pos=[0,0], source size=[1,1], orientation=0, pressure=1)
SCREEN_EVENT_MTOUCH_RELEASE(window=0x33fdd4f550, port id=1, touch id=1, sequence=4, pos=[0,0], size=[1,1], source pos=[0,0], source size=[1,1], orientation=0, pressure=1)
SCREEN_EVENT_MTOUCH_RELEASE(window=0x33fdd4f550, port id=1, touch id=2, sequence=5, pos=[0,0], size=[1,1], source pos=[0,0], source size=[1,1], orientation=0, pressure=1)
...Since the configuration options specified count=3, notice that the touch events are generated in groups of three, with touch IDs 0, 1, and 2.
mtouch-stub.c
#include <pthread.h>
#include <string.h>
#include "input/mtouch_driver.h"
#include "input/mtouch_log.h"
#include "input/parseopts.h"
#define DEVICE_NAME "stub"
typedef struct {
struct mtouch_device* device_handle;
pthread_t hardware_handler_thread;
unsigned touch_count;
unsigned delay_ms;
unsigned period_ms;
unsigned priority;
unsigned width;
unsigned height;
} stub_device_t;
static void* hardware_init(stub_device_t* dev);
static void hardware_fini(stub_device_t* dev);
static void* hardware_handler(void* dev);
void* mtouch_driver_init(const char* options);
void mtouch_driver_fini(void* dev);
static int set_option(const char* option, const char* value, void* arg);
static int get_contact_id(void* packet, _uint8 digit_idx, _uint32* contact_id, void* arg);
static int get_coords(void* packet, _uint8 digit_idx, _int32* x, _int32* y, void* arg);
static int get_down_count(void* packet, _uint32* down_count, void* arg);
static int is_contact_down(void* packet, _uint8 digit_idx, int* valid, void* arg);
stub_device_t stub_device = {
.device_handle = NULL,
.touch_count = 2,
.delay_ms = 1000u,
.period_ms = 250u,
.priority = 21u,
.width = 1920u,
.height = 1080u,
};
mtouch_driver_params_t driver_params = {
.capabilities = MTOUCH_CAPABILITIES_CONTACT_ID |
MTOUCH_CAPABILITIES_COORDS |
MTOUCH_CAPABILITIES_CONTACT_COUNT,
.flags = 0,
};
mtouch_driver_funcs_t driver_funcs = {
.get_contact_id = get_contact_id,
.is_contact_down = is_contact_down,
.get_coords = get_coords,
.get_down_count = get_down_count,
.get_touch_width = NULL,
.get_touch_height = NULL,
.get_touch_orientation = NULL,
.get_touch_pressure = NULL,
.get_seq_id = NULL,
.set_event_rate = NULL,
.get_contact_type = NULL,
.get_select = NULL,
};
static int get_contact_id(void* packet, _uint8 digit_idx, _uint32* contact_id, void* arg) {
*contact_id = digit_idx;
return EOK;
}
static int is_contact_down(void* packet, _uint8 digit_idx, int* valid, void* arg) {
*valid = (time(NULL) / 5) % 2;
return EOK;
}
static int get_coords(void* packet, _uint8 digit_idx, _int32* x, _int32* y, void* arg) {
stub_device_t * device = arg;
*x = 10 * digit_idx % (device->width + 1);
*y = 10 * digit_idx % (device->height + 1);
return EOK;
}
static int get_down_count(void* packet, _uint32* down_count, void* arg) {
stub_device_t * device = arg;
int is_down = 0;
is_contact_down(NULL, 0, &is_down, NULL);
*down_count = device->touch_count * is_down;
return EOK;
}
static void* hardware_init(stub_device_t* dev) {
pthread_attr_t pattr;
sched_param_t param;
pthread_attr_init(&pattr);
pthread_attr_getschedparam(&pattr, ¶m);
param.sched_priority = dev->priority;
if (EOK != pthread_attr_setschedparam(&pattr, ¶m)) {
mtouch_warn(DEVICE_NAME, "Failed to set scheduler params");
}
if (EOK != pthread_attr_setinheritsched(&pattr, PTHREAD_EXPLICIT_SCHED)) {
mtouch_warn(DEVICE_NAME, "Failed to set scheduler inherit attribute");
}
if (EOK != pthread_create(&dev->hardware_handler_thread, &pattr, hardware_handler, dev)) {
mtouch_error(DEVICE_NAME, "Failed to create the generator thread");
return NULL;
}
if (EOK != pthread_setname_np(dev->hardware_handler_thread, "hardware_handler")) {
mtouch_warn(DEVICE_NAME, "Failed to set name of the generator thread");
}
return dev;
}
static void hardware_fini(stub_device_t* dev) {
if (EOK != pthread_cancel(dev->hardware_handler_thread)) {
mtouch_error(DEVICE_NAME, "Failed to cancel the generator thread");
} else if (EOK != pthread_join(dev->hardware_handler_thread, NULL)) {
mtouch_error(DEVICE_NAME, "Failed to join the generator thread");
}
}
static void* hardware_handler(void* dev) {
stub_device_t* device = dev;
nanosleep((struct timespec[]){{
.tv_sec = device->delay_ms / 1000u,
.tv_nsec = (device->delay_ms % 1000u) * 1000000u,
}}, NULL);
while (1) {
mtouch_driver_process_packet(device->device_handle, NULL, device, MTOUCH_PARSER_FLAG_NONE);
nanosleep((struct timespec[]){{
.tv_sec = device->period_ms / 1000u,
.tv_nsec = (device->period_ms % 1000u) * 1000000u,
}}, NULL);
}
return NULL;
}
void* mtouch_driver_init(const char* options) {
input_parseopts(options, set_option, &stub_device);
driver_params.max_touchpoints = stub_device.touch_count;
driver_params.width = stub_device.width;
driver_params.height = stub_device.height;
if (NULL == hardware_init(&stub_device)) {
mtouch_error(DEVICE_NAME, "Failed to initialize stub hardware.");
goto mtouch_driver_init_error;
}
stub_device.device_handle = mtouch_driver_attach(&driver_params, &driver_funcs);
if (NULL == stub_device.device_handle) {
mtouch_error(DEVICE_NAME, "Failed to connect to libinputevents");
goto mtouch_driver_init_error;
}
return &stub_device;
mtouch_driver_init_error:
mtouch_driver_detach(stub_device.device_handle);
stub_device.device_handle = NULL;
return NULL;
}
void mtouch_driver_fini(void* dev) {
stub_device_t* device = dev;
hardware_fini(device);
if (device->device_handle) {
mtouch_driver_detach(device->device_handle);
device->device_handle = NULL;
}
}
static int set_option(const char* option, const char* value, void* arg) {
stub_device_t * device = (stub_device_t *)arg;
if (!strcmp(option, "count")) {
input_parse_unsigned(option, value, &device->touch_count);
} else if (!strcmp(option, "delay")) {
input_parse_unsigned(option, value, &device->delay_ms);
} else if (!strcmp(option, "period")) {
input_parse_unsigned(option, value, &device->period_ms);
} else if (!strcmp(option, "priority")) {
input_parse_unsigned(option, value, &device->priority);
} else if (!strcmp(option, "width")) {
input_parse_unsigned(option, value, &device->width);
} else if (!strcmp(option, "height")) {
input_parse_unsigned(option, value, &device->height);
} else {
mtouch_warn(DEVICE_NAME, "Ignoring unknown option %s=%s", option, value);
}
return EOK;
}This driver:
Defines a stub device-specific structure,
stub_device_t.Declares a
stub_device_tinstance and initializes it with default values. This can also be done in hardware_init() or mtouch_driver_init(), but is left here for visibility.Declares an
mtouch_driver_params_tinstance, and initializes the driver capabilities and flags.Declares an
mtouch_driver_funcs_tinstance and specifies the implemented functions get_contact_id(), is_contact_down(), get_coords(), and get_down_count(), leaving all other functions unimplemented by specifyingNULL.Implements the required callback functions get_contact_id(), is_contact_down(), and get_coords().
get_contact_id() returns an ID that is the same as the digit index.
is_contact_down() alternatively returns
0and1, switching between them every five seconds.get_coords() returns x and y coordinates with a value equal to 10 times the digit index (wrapped if maximum x or y is reached).
Implements the single optional function get_down_count() to support the MTOUCH_CAPABILITIES_CONTACT_COUNT capability.
- get_down_count() alternatively returns
0and the number of touch points supported, switching between them every five seconds, in sync with is_contact_down().
- get_down_count() alternatively returns
Implements hardware-specific functions hardware_init(), hardware_fini(), and hardware_handler().
- Since the stub driver doesn't actually interact with any hardware, hardware_init() creates a new thread that executes hardware_handler(), which after a delay, loops, calling mtouch_driver_process_packet() to simulate touch events and sleeping for a period of time, until cancelled by hardware_fini().
Implements the required mtouch_driver_init() function, which:
Calls input_parseopts() to parse the options found in the configuration file and to process them using the set_option() function.
Sets driver parameters based on the parsed options.
Calls hardware_init() to initialize the stub hardware.
Calls mtouch_driver_attach() to attach the driver to the Input Events Framework.
Returns pointer to the initialized device or NULL on error.
Implements the required mtouch_driver_fini() function, which calls hardware_fini() to cleanup the stub hardware.
- Calls mtouch_driver_detach() to detach the driver from the Input Events framework.
Implements set_option() function used by input_parseopts() to process the options from the configuration file.
- Based on the passed in option the function sets specific driver options to value or displays an error.
