Stub mtouch driver

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


mtouch stub

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.so

Once you've compiled the code example, copy it to the /system/lib directory on the target device.

Note:

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 mtouch

Then, execute mtouch as the root user using:

mtouch -c ${ABSOLUTE_PATH_TO_CONFIG_DIR}/mtouch.conf

After 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-winmgr

Execute 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, &param);
    param.sched_priority = dev->priority;
    if (EOK != pthread_attr_setschedparam(&pattr, &param)) {
        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_t instance 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_t instance, and initializes the driver capabilities and flags.

  • Declares an mtouch_driver_funcs_t instance and specifies the implemented functions get_contact_id(), is_contact_down(), get_coords(), and get_down_count(), leaving all other functions unimplemented by specifying NULL.

  • 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 0 and 1, 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 0 and the number of touch points supported, switching between them every five seconds, in sync with is_contact_down().
  • 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.

  • 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.
Page updated: