~hp/fakeyd

db402b80f3d4c426ef37ebabbfaa9ae7346cd666 — Hanspeter Portner 2 months ago 19980a0
Run Lua code in a separate thread
3 files changed, 176 insertions(+), 63 deletions(-)

M meson.build
M meson_options.txt
M src/fakeyd.c
M meson.build => meson.build +4 -1
@@ 10,12 10,15 @@ project('fakeyd', 'c', default_options : [

version = get_option('version')
build_tests = get_option('build-tests')
varchunk = subproject('varchunk')

reuse = find_program('reuse', required : false)

add_project_arguments('-DFAKEYD_VERSION="'+version+'"', language : 'c')
add_project_arguments('-D_GNU_SOURCE', language : 'c')

thread_dep = dependency('threads')
varchunk_dep = varchunk.get_variable('varchunk')
input_dep = dependency('libinput', version : '>=1.6.0')
udev_dep = dependency('libudev')
evdev_dep = dependency('libevdev', version : '>=1.5.0')


@@ 24,7 27,7 @@ if not lua_dep.found()
  lua_dep = dependency('lua5.4')
endif

deps = [ input_dep, udev_dep, evdev_dep, lua_dep ]
deps = [ thread_dep, varchunk_dep, input_dep, udev_dep, evdev_dep, lua_dep ]
srcs = [
  join_paths('src', 'fakeyd.c'),
  join_paths('src', 'event.c'),

M meson_options.txt => meson_options.txt +1 -1
@@ 2,4 2,4 @@
# SPDX-License-Identifier: CC0-1.0

option('build-tests', type : 'boolean', value : true)
option('version', type : 'string', value : '0.1.31')
option('version', type : 'string', value : '0.1.37')

M src/fakeyd.c => src/fakeyd.c +171 -61
@@ 13,6 13,8 @@
#include <string.h>
#include <syslog.h>
#include <ctype.h>
#include <pthread.h>
#include <semaphore.h>

#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>


@@ 20,6 22,8 @@

#include <fakeyd/fakeyd.h>

#include <varchunk/varchunk.h>

#define INPUTS_MAX 32

typedef enum _out_type_t {


@@ 29,11 33,16 @@ typedef enum _out_type_t {
	OUTPUTS_MAX // sentinel
} out_type_t;

typedef struct _job_t job_t;
typedef struct _inp_t inp_t;
typedef struct _out_t out_t;
typedef struct _map_t map_t;
typedef struct _app_t app_t;

struct _job_t {
	struct libinput_event *ev;
};

struct _inp_t {
	char path [PATH_MAX];
};


@@ 49,6 58,9 @@ struct _app_t {
	lua_State *L;
	const char *cfg;
	size_t num_inputs;
	varchunk_t *rb;
	sem_t sem;
	pthread_t thread;
	inp_t inputs [INPUTS_MAX];
	out_t outputs [OUTPUTS_MAX];
};


@@ 64,7 76,7 @@ struct _map_t {
#define EVENT_MAP(ID) { .name = #ID, .code = LIBINPUT_EVENT_##ID } 
#define TERMINATOR { .name = NULL, .code = 0 } 

static volatile sig_atomic_t done = false;
static atomic_bool done = ATOMIC_VAR_INIT(false);

static const char *output_labels [OUTPUTS_MAX] = {
	[OUT_TYPE_KEYBOARD] = "fakeyd keyboard",


@@ 467,10 479,16 @@ static const map_t event_maps [] = {
	TERMINATOR
};

static bool
_notdone()
{
	return !atomic_load_explicit(&done, memory_order_relaxed);
}

static void
_sigint(int sig __attribute__((unused)))
{
	done = true;
	atomic_store_explicit(&done, true, memory_order_relaxed);
}

static int


@@ 720,8 738,119 @@ _lua_overwrite(lua_State *L)
}

static int
_handle_event(app_t *app, struct libinput_event *ev)
{
	const enum libinput_event_type type = libinput_event_get_type(ev);

	if(lua_getglobal(app->L, "intercept") != LUA_TFUNCTION)
	{
		syslog(LOG_ERR, "[%s] lua: %s", __func__, "'intercept' not a function");
		lua_pop(app->L, 1); // intercept
		libinput_event_destroy(ev);
		return 1;
	}

	switch(type)
	{
		case LIBINPUT_EVENT_DEVICE_ADDED:
			// fall-through
		case LIBINPUT_EVENT_DEVICE_REMOVED:
		{
			struct libinput_event_device_notify *evn =
				libinput_event_get_device_notify_event(ev);

			event_device_notify_new(app->L, evn);
		} break;

		case LIBINPUT_EVENT_KEYBOARD_KEY:
		{
			struct libinput_event_keyboard *evk =
				libinput_event_get_keyboard_event(ev);

			event_keyboard_new(app->L, evk);
		} break;

		case LIBINPUT_EVENT_POINTER_MOTION:
			// fall-through
		case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
			// fall-through
		case LIBINPUT_EVENT_POINTER_BUTTON:
			// fall-through
		case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL:
			// fall-through
		case LIBINPUT_EVENT_POINTER_SCROLL_FINGER:
			// fall-through
		case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS:
		{
			struct libinput_event_pointer *evk =
				libinput_event_get_pointer_event(ev);

			event_pointer_new(app->L, evk);
		} break;

		case LIBINPUT_EVENT_POINTER_AXIS:
			// fall-through
		default:
			lua_pop(app->L, 1); // intercept
			libinput_event_destroy(ev);
			return 1;
	}

	if(lua_pcall(app->L, 1, 0, 0) != LUA_OK)
	{
		syslog(LOG_ERR, "[%s] lua: %s", __func__, lua_tostring(app->L, -1));
		lua_pop(app->L, 1);
	}

	return 0;
}

static void *
_thread(void *data)
{
	app_t *app = data;

	while(_notdone())
	{
		if(sem_wait(&app->sem) != 0)
		{
			syslog(LOG_ERR, "[%s] sem_wait: %s", __func__, strerror(errno));
			_sigint(SIGINT);
			continue;
		}

		const job_t *job;
		size_t len;

		while( (job = varchunk_read_request(app->rb, &len)) )
		{
			_handle_event(app, job->ev);

			varchunk_read_advance(app->rb);
		}
	}

	return NULL;
}

static int
_app_init(app_t *app)
{
	int err;

	if(sem_init(&app->sem, 0, 0) != 0)
	{
		syslog(LOG_ERR, "[%s] sem_init: %s", __func__, strerror(errno));
		return 1;
	}

	app->rb = varchunk_new(2048, true);
	if(!app->rb)
	{
		syslog(LOG_ERR, "[%s] varchunk_new", __func__);
		return 1;
	}

	app->udev = udev_new();
	if(!app->udev)
	{


@@ 891,6 1020,12 @@ _app_init(app_t *app)
		lua_pop(app->L, 1);
		return 1;
	}

	if ( (err = pthread_create(&app->thread, NULL, _thread, app)) != 0)
	{
		syslog(LOG_ERR, "[%s] pthread_create: %s", __func__, strerror(err));
		return 1;
	}
	
	return 0;
}


@@ 898,6 1033,14 @@ _app_init(app_t *app)
static void
_app_deinit(app_t *app)
{
	_sigint(SIGINT);
	sem_post(&app->sem);

	if(app->thread)
	{
		pthread_join(app->thread, NULL);
	}

	if(app->L)
	{
		lua_close(app->L);


@@ 931,14 1074,33 @@ _app_deinit(app_t *app)
		udev_unref(app->udev);
		app->udev = NULL;
	}

	if(app->rb)
	{
		const job_t *job;
		size_t len;

		// drain ringbuffer
		while( (job = varchunk_read_request(app->rb, &len) ) )
		{
			libinput_event_destroy(job->ev);

			varchunk_read_advance(app->rb);
		}

		varchunk_free(app->rb);
		app-> rb = NULL;
	}

	sem_destroy(&app->sem);
}

static void
_app_run(app_t *app)
{
	const int timeout = 300;
	const int timeout = 500; // ms

	while(!done)
	while(_notdone())
	{
		struct pollfd fds = {
			.fd = libinput_get_fd(app->li),


@@ 962,66 1124,14 @@ _app_run(app_t *app)
		struct libinput_event *ev;
		while ( (ev = libinput_get_event(app->li)) )
		{
			const enum libinput_event_type type = libinput_event_get_type(ev);
			job_t *job;

			if(lua_getglobal(app->L, "intercept") != LUA_TFUNCTION)
			if( (job = varchunk_write_request(app->rb, sizeof(job_t))) )
			{
				syslog(LOG_ERR, "[%s] lua: %s", __func__, "'intercept' not a function");
				lua_pop(app->L, 1); // intercept
				libinput_event_destroy(ev);
				continue;
			}

			switch(type)
			{
				case LIBINPUT_EVENT_DEVICE_ADDED:
					// fall-through
				case LIBINPUT_EVENT_DEVICE_REMOVED:
				{
					struct libinput_event_device_notify *evn =
						libinput_event_get_device_notify_event(ev);

					event_device_notify_new(app->L, evn);
				} break;
				job->ev = ev;

				case LIBINPUT_EVENT_KEYBOARD_KEY:
				{
					struct libinput_event_keyboard *evk =
						libinput_event_get_keyboard_event(ev);

					event_keyboard_new(app->L, evk);
				} break;

				case LIBINPUT_EVENT_POINTER_MOTION:
					// fall-through
				case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
					// fall-through
				case LIBINPUT_EVENT_POINTER_BUTTON:
					// fall-through
				case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL:
					// fall-through
				case LIBINPUT_EVENT_POINTER_SCROLL_FINGER:
					// fall-through
				case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS:
				{
					struct libinput_event_pointer *evk =
						libinput_event_get_pointer_event(ev);

					event_pointer_new(app->L, evk);
				} break;

				case LIBINPUT_EVENT_POINTER_AXIS:
					// fall-through
				default:
					lua_pop(app->L, 1); // intercept
					libinput_event_destroy(ev);
					continue;
			}

			if(lua_pcall(app->L, 1, 0, 0) != LUA_OK)
			{
				syslog(LOG_ERR, "[%s] lua: %s", __func__, lua_tostring(app->L, -1));
				lua_pop(app->L, 1);
				varchunk_write_advance(app->rb, sizeof(job_t));
				sem_post(&app->sem);
			}
		}
	}