Add the minimal implementation for a UNIX socket libeis server

This is the minimum framework to support new clients, added devices and
pointer relative motion events. It's missing a bunch of checks and
verification, most of the server hooks aren't there yet, the only
implementation is a UNIX socket and the protocol is plain text (but at least
the last two makes it netcat-compatible).

Protocol is plain text for now and interaction is like this (S is server, C is client):
S: hello
C: connect myclientname
S: connected
C: add 2 4
S: accept 2
C: rel 2 -1 1
C: rel 2 5 4

Where the last two lines are: add device with id 2 and capability mask 0x4,
send a relative pointer motion event for device 2 with coordinates -1/1, then
5/4.

The implementation relies heavily on some abstraction and macros galore, see
the various util-* files. These are largely copied from libinput, with a few
parts removed and a few other parts added.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2020-07-28 14:19:28 +10:00
parent 805ba96271
commit f7a24b2fbd
21 changed files with 2928 additions and 0 deletions

View file

@ -12,9 +12,30 @@ cflags = cppflags + ['-Wmissing-prototypes', '-Wstrict-prototypes']
add_project_arguments(cflags, language : 'c')
add_project_arguments(cppflags, language : 'cpp')
config_h = configuration_data()
config_h.set('_GNU_SOURCE', '1')
lib_util = static_library('util',
'src/util-io.h',
'src/util-list.h',
'src/util-list.c',
'src/util-logger.h',
'src/util-logger.c',
'src/util-macros.h',
'src/util-object.h',
'src/util-sources.h',
'src/util-sources.c',
'src/util-strings.c',
'src/util-strings.h',
)
dep_libutil = declare_dependency(link_with: lib_util)
lib_libei = shared_library('ei',
'src/libei.h',
'src/libei.c',
dependencies: [dep_libutil],
install: true
)
@ -28,8 +49,16 @@ pkgconfig.generate(lib_libei,
lib_libeis = shared_library('eis',
'src/libeis.h',
'src/libeis.c',
'src/libeis-client.c',
'src/libeis-device.c',
'src/libeis-socket.c',
dependencies: [dep_libutil],
install: true
)
dep_libeis = declare_dependency(link_with: lib_libeis,
include_directories: 'src')
pkgconfig.generate(lib_libeis,
filebase: 'libeis',
name : 'libEIS',
@ -37,3 +66,11 @@ pkgconfig.generate(lib_libeis,
version : meson.project_version(),
libraries : lib_libei
)
executable('eis-socket-server',
'tools/eis-socket-server.c',
dependencies: [dep_libeis])
configure_file(output : 'config.h', configuration : config_h)

459
src/libeis-client.c Normal file
View file

@ -0,0 +1,459 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "util-bits.h"
#include "util-io.h"
#include "util-logger.h"
#include "util-macros.h"
#include "util-mem.h"
#include "util-sources.h"
#include "util-strings.h"
#include "libeis-private.h"
/* The message type for the wire format */
enum message_type {
MESSAGE_CONNECT,
MESSAGE_DISCONNECT,
MESSAGE_ADD_DEVICE,
MESSAGE_REMOVE_DEVICE,
MESSAGE_POINTER_REL,
};
struct message {
enum message_type type;
union {
struct message_connect {
char *name;
} connect;
struct message_disconnect {
uint8_t pad; /* no data */
} disconnect;
struct message_add_device {
uint32_t deviceid;
uint32_t capabilities;
} add_device;
struct message_remove_device {
uint32_t deviceid;
} remove_device;
struct message_pointer_rel {
uint32_t deviceid;
int32_t x;
int32_t y;
} pointer_rel;
};
};
static void
message_free(struct message *msg)
{
switch (msg->type) {
case MESSAGE_CONNECT:
free(msg->connect.name);
break;
default:
break;
}
free(msg);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(struct message*, message_free);
static void
eis_client_destroy(struct eis_client *client)
{
free(client->name);
source_unref(client->source);
list_remove(&client->link);
}
OBJECT_DECLARE_CREATE(eis_client);
static
OBJECT_DECLARE_PARENT(eis_client, eis);
_public_
OBJECT_DECLARE_REF(eis_client);
_public_
OBJECT_DECLARE_UNREF(eis_client);
_public_
OBJECT_DECLARE_GETTER(eis_client, name, const char*);
_public_ struct eis*
eis_client_get_context(struct eis_client *client)
{
return eis_client_parent(client);
}
static int
client_send_hello(struct eis_client *client)
{
const char buf[] = "hello\n";
return min(0, xwrite(source_get_fd(client->source), buf, sizeof(buf)));
}
static int
client_send_disconnect(struct eis_client *client)
{
const char buf[] = "disconnected\n";
return min(0, xwrite(source_get_fd(client->source), buf, sizeof(buf)));
}
static int
client_send_connect(struct eis_client *client)
{
const char buf[] = "connected\n";
return min(0, xwrite(source_get_fd(client->source), buf, sizeof(buf)));
}
static int
client_send_accepted(struct eis_client *client, struct eis_device *device)
{
char buf[64];
if (!xsnprintf(buf, sizeof(buf), "accept %d\n", device->id))
return -ENOMEM;
return min(0, xwrite(source_get_fd(client->source), buf, strlen(buf)));
}
_public_ void
eis_client_connect(struct eis_client *client)
{
switch(client->state) {
case EIS_CLIENT_STATE_CONNECTING:
break;
default:
return;
}
int rc = client_send_connect(client);
if (rc) {
eis_client_disconnect(client);
} else {
client->state = EIS_CLIENT_STATE_CONNECTED;
}
}
_public_ void
eis_client_disconnect(struct eis_client *client)
{
switch(client->state) {
case EIS_CLIENT_STATE_DISCONNECTED:
/* Client already disconnected? don't bother sending an
* event */
return;
case EIS_CLIENT_STATE_CONNECTING:
case EIS_CLIENT_STATE_CONNECTED:
eis_queue_disconnect_event(client);
/* fallthrough */
case EIS_CLIENT_STATE_HELLO:
client_send_disconnect(client);
client->state = EIS_CLIENT_STATE_DISCONNECTED;
source_unref(client->source);
break;
}
}
static int
client_new_device(struct eis_client *client,
uint32_t id, uint32_t capabilities)
{
/* Check for duplicate IDs */
struct eis_device *d;
list_for_each(d, &client->devices, link) {
if (d->id == id)
return -EINVAL;
}
if (capabilities == 0 ||
capabilities & ~(bit(EIS_DEVICE_CAP_POINTER) |
bit(EIS_DEVICE_CAP_POINTER_ABSOLUTE) |
bit(EIS_DEVICE_CAP_KEYBOARD) |
bit(EIS_DEVICE_CAP_TOUCH)))
return -EINVAL;
struct eis_device *device = eis_device_new(client, id, capabilities);
list_append(&client->devices, &device->link);
log_debug(eis_client_parent(client), "New device %d caps: %#x\n",
id, capabilities);
eis_queue_added_event(device);
return 0;
}
static int
client_pointer_rel(struct eis_client *client, uint32_t deviceid,
int32_t x, int32_t y)
{
struct eis_device *device;
list_for_each(device, &client->devices, link) {
if (device->id == deviceid) {
return eis_device_pointer_rel(device, x, y);
}
}
return -EINVAL;
}
static int
client_hello_handle_msg(struct eis_client *client, struct message *msg)
{
int rc = 0;
switch (msg->type) {
case MESSAGE_CONNECT:
eis_queue_connect_event(client);
client->name = steal(&msg->connect.name);
client->state = EIS_CLIENT_STATE_CONNECTING;
break;
case MESSAGE_DISCONNECT:
rc = -ECANCELED;
break;
case MESSAGE_ADD_DEVICE:
case MESSAGE_REMOVE_DEVICE:
case MESSAGE_POINTER_REL:
rc = -EPROTO;
break;
}
return rc;
}
static int
client_connecting_handle_msg(struct eis_client *client, const struct message *msg)
{
int rc = 0;
switch (msg->type) {
case MESSAGE_CONNECT:
rc = -EPROTO;
break;
case MESSAGE_DISCONNECT:
rc = -ECANCELED;
break;
case MESSAGE_ADD_DEVICE:
case MESSAGE_REMOVE_DEVICE:
case MESSAGE_POINTER_REL:
rc = -EPROTO;
break;
}
return rc;
}
static int
client_connected_handle_msg(struct eis_client *client,
const struct message *msg)
{
int rc = 0;
switch (msg->type) {
case MESSAGE_CONNECT:
rc = -EPROTO;
break;
case MESSAGE_DISCONNECT:
client->state = EIS_CLIENT_STATE_DISCONNECTED;
rc = -ECANCELED;
break;
case MESSAGE_ADD_DEVICE:
rc = client_new_device(client, msg->add_device.deviceid,
msg->add_device.capabilities);
break;
case MESSAGE_REMOVE_DEVICE:
/* FIXME: remove device */
break;
case MESSAGE_POINTER_REL:
rc = client_pointer_rel(client, msg->pointer_rel.deviceid,
msg->pointer_rel.x, msg->pointer_rel.y);
break;
}
return rc;
}
static struct message *
client_parse_message(const char *data_in, size_t len)
{
_cleanup_(message_freep) struct message *msg = xalloc(sizeof(*msg));
_cleanup_free_ char *data = strstrip(data_in, "\n");
_cleanup_(strv_freep) char **tokens = strv_from_string(data, " ");
if (streq(tokens[0], "connect")) {
if (!tokens[1])
goto error;
char *name = tokens[1];
*msg = (struct message) {
.type = MESSAGE_CONNECT,
.connect.name = xstrdup(name),
};
} else if (streq(tokens[0], "disconnect")) {
*msg = (struct message) {
.type = MESSAGE_DISCONNECT,
};
} else if (streq(tokens[0], "add")) {
if (!tokens[1] || !tokens[2])
goto error;
uint32_t deviceid;
if (!xatou(tokens[1], &deviceid))
goto error;
uint32_t capabilities;
if (!xatou(tokens[2], &capabilities))
goto error;
*msg = (struct message) {
.type = MESSAGE_ADD_DEVICE,
.add_device.deviceid = deviceid,
.add_device.capabilities = capabilities,
};
} else if (streq(tokens[0], "remove")) {
if (!tokens[1])
goto error;
uint32_t deviceid;
if (!xatou(tokens[1], &deviceid))
goto error;
*msg = (struct message) {
.type = MESSAGE_REMOVE_DEVICE,
.add_device.deviceid = deviceid,
};
} else if (streq(tokens[0], "rel")) {
if (!tokens[1] || !tokens[2] || !tokens[3])
return NULL;
uint32_t deviceid;
if (!xatou(tokens[1], &deviceid))
goto error;
int x, y;
if (!xatoi(tokens[2], &x) || !xatoi(tokens[3], &y))
goto error;
*msg = (struct message) {
.type = MESSAGE_POINTER_REL,
.pointer_rel.deviceid = deviceid,
.pointer_rel.x = x,
.pointer_rel.y = y,
};
} else {
goto error;
}
return steal(&msg);
error:
return NULL;
}
static void
client_dispatch(struct source *source, void *data)
{
struct eis_client *client = data;
enum eis_client_state old_state = client->state;
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(64);
int rc = iobuf_append_from_fd(buf, source_get_fd(source));
if (rc == -EAGAIN)
return;
else if (rc < 0)
goto error;
_cleanup_(message_freep) struct message *msg =
client_parse_message(iobuf_data(buf), iobuf_len(buf));
if (!msg) {
rc = -EBADMSG;
goto error;
}
switch (client->state) {
case EIS_CLIENT_STATE_HELLO:
rc = client_hello_handle_msg(client, msg);
break;
case EIS_CLIENT_STATE_CONNECTING:
/* Client is waiting for us, shouldn't send anything
* but disconnect */
rc = client_connecting_handle_msg(client, msg);
break;
case EIS_CLIENT_STATE_CONNECTED:
rc = client_connected_handle_msg(client, msg);
break;
case EIS_CLIENT_STATE_DISCONNECTED:
abort();
}
error:
if (rc < 0)
eis_client_disconnect(client);
static const char *client_states[] = {
"HELLO",
"CONNECTING",
"CONNECTED",
"DISCONNECTED",
};
if (rc)
log_warn(eis_client_parent(client), "Client error: %s\n",
strerror(-rc));
log_debug(eis_client_parent(client), "Client dispatch: %s -> %s\n",
client_states[old_state],
client_states[client->state]);
}
struct eis_client *
eis_client_new(struct eis *eis, int fd)
{
static uint32_t client_id;
struct eis_client *client = eis_client_create(&eis->object);
client->id = ++client_id;
list_init(&client->devices);
list_append(&eis->clients, &client->link);
client->source = source_add_autoclose(eis->sink, fd,
client_dispatch, client);
client->state = EIS_CLIENT_STATE_HELLO;
int rc = client_send_hello(client);
if (rc != 0)
client = eis_client_unref(client);
return client;
}
void
eis_client_connect_device(struct eis_client *client, struct eis_device *device)
{
client_send_accepted(client, device);
}

102
src/libeis-device.c Normal file
View file

@ -0,0 +1,102 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include "util-macros.h"
#include "util-bits.h"
#include "libeis-private.h"
static void
eis_device_destroy(struct eis_device *device)
{
}
OBJECT_DECLARE_REF(eis_device);
OBJECT_DECLARE_UNREF(eis_device);
OBJECT_DECLARE_CREATE(eis_device);
static
OBJECT_DECLARE_PARENT(eis_device, eis_client);
_public_ struct eis_client *
eis_device_get_client(struct eis_device *device)
{
return eis_device_parent(device);
}
struct eis_device *
eis_device_new(struct eis_client *client,
uint32_t id,
uint32_t capabilities)
{
struct eis_device *device = eis_device_create(&client->object);
device->capabilities = capabilities;
device->id = id;
device->state = EIS_DEVICE_STATE_NEW;
return device;
}
_public_
bool
eis_device_has_capability(struct eis_device *device,
enum eis_device_capability cap)
{
switch (cap) {
case EIS_DEVICE_CAP_POINTER:
case EIS_DEVICE_CAP_POINTER_ABSOLUTE:
case EIS_DEVICE_CAP_KEYBOARD:
case EIS_DEVICE_CAP_TOUCH:
return flag_is_set(device->capabilities, cap);
}
return false;
}
int
eis_device_pointer_rel(struct eis_device *device,
int x, int y)
{
if (!eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER))
return -EINVAL;
if (device->state != EIS_DEVICE_STATE_ACCEPTED)
return -EINVAL;
eis_queue_pointer_rel_event(device, x, y);
return 0;
}
_public_ void
eis_device_connect(struct eis_device *device)
{
if (device->state != EIS_DEVICE_STATE_NEW)
return;
device->state = EIS_DEVICE_STATE_ACCEPTED;
eis_client_connect_device(eis_device_get_client(device), device);
}

133
src/libeis-private.h Normal file
View file

@ -0,0 +1,133 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "util-object.h"
#include "libeis.h"
#include "util-list.h"
#include "util-sources.h"
struct eis_backend {
void (*destroy)(struct eis *eis);
};
struct eis {
struct object object;
void *userdata;
struct logger *logger;
struct sink *sink;
struct list clients;
struct eis_backend backend;
struct list event_queue;
};
enum eis_client_state {
EIS_CLIENT_STATE_HELLO, /* just connected, server sent hello */
EIS_CLIENT_STATE_CONNECTING, /* client requested connect */
EIS_CLIENT_STATE_CONNECTED, /* server has sent connect */
EIS_CLIENT_STATE_DISCONNECTED,
};
struct eis_client {
struct object object;
struct list link;
struct source *source;
uint32_t id;
enum eis_client_state state;
char *name;
struct list devices;
};
enum eis_device_state {
EIS_DEVICE_STATE_NEW,
EIS_DEVICE_STATE_ACCEPTED,
};
struct eis_device {
struct object object;
struct list link;
uint32_t id;
enum eis_device_state state;
uint32_t capabilities;
};
struct eis_event {
struct object object;
enum eis_event_type type;
struct list link;
struct eis_client *client;
struct eis_device *device;
};
struct eis_event_client {
struct eis_event base;
};
struct eis_event_pointer {
struct eis_event base;
int x, y; /* relative motion */
};
void
eis_init_object(struct eis *eis, struct object *parent);
int
eis_init(struct eis *eis);
struct eis_client *
eis_client_new(struct eis *eis, int fd);
struct eis*
eis_client_get_context(struct eis_client *client);
void
eis_client_connect_device(struct eis_client *client,
struct eis_device *device);
struct eis_device *
eis_device_new(struct eis_client *client,
uint32_t id,
uint32_t capabilities);
int
eis_device_pointer_rel(struct eis_device *device,
int x, int y);
void
eis_queue_connect_event(struct eis_client *client);
void
eis_queue_disconnect_event(struct eis_client *client);
void
eis_queue_added_event(struct eis_device *device);
void
eis_queue_removed_event(struct eis_device *device);
void
eis_queue_pointer_rel_event(struct eis_device *device, int x, int y);

127
src/libeis-socket.c Normal file
View file

@ -0,0 +1,127 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "util-logger.h"
#include "util-mem.h"
#include "util-macros.h"
#include "util-sources.h"
#include "util-strings.h"
#include "libeis.h"
#include "libeis-private.h"
struct eis_socket {
struct eis base;
struct source *listener;
};
static inline struct eis_socket *
eis_socket(struct eis *eis)
{
return container_of(eis, struct eis_socket, base);
}
static void
socket_destroy(struct eis *eis)
{
}
const struct eis_backend backend = {
.destroy = socket_destroy,
};
_public_ struct eis *
eis_socket_new_context(void *userdata)
{
struct eis_socket *ctx = xalloc(sizeof *ctx);
eis_init_object(&ctx->base, NULL);
ctx->base.userdata = userdata;
ctx->base.backend = backend;
return &ctx->base;
}
static void
listener_dispatch(struct source *source, void *data)
{
struct eis_socket *socket = data;
log_debug(&socket->base, "New client connection waiting\n");
int fd = accept4(source_get_fd(source), NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
if (fd == -1)
return;
eis_client_new(&socket->base, fd);
}
_public_ int
eis_socket_init(struct eis *eis, const char *socketpath)
{
assert(eis);
assert(socketpath);
assert(socketpath[0] != '\0');
int rc = eis_init(eis);
if (rc)
return -rc;
_cleanup_free_ char *path;
if (socketpath[0] == '/') {
path = xstrdup(socketpath);
} else {
const char *xdg = getenv("XDG_RUNTIME_DIR");
if (!xdg)
return -ENOTDIR;
path = xaprintf("%s/%s", xdg, socketpath);
}
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = {0},
};
if (!xsnprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path))
return -EINVAL;
int sockfd = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0);
if (sockfd == -1)
return -errno;
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1)
return -errno;
if (listen(sockfd, 2) == -1)
return -errno;
struct eis_socket *socket = eis_socket(eis);
source_add_autoclose(eis->sink, sockfd, listener_dispatch, socket);
return 0;
}

View file

@ -21,4 +21,220 @@
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "util-logger.h"
#include "util-macros.h"
#include "util-object.h"
#include "util-sources.h"
#include "util-strings.h"
#include "libeis.h"
#include "libeis-private.h"
static struct eis_event* eis_event_ref(struct eis_event *event);
static void
eis_event_destroy(struct eis_event *event)
{
switch (event->type) {
case EIS_EVENT_CLIENT_CONNECT:
case EIS_EVENT_CLIENT_DISCONNECT:
case EIS_EVENT_DEVICE_ADDED:
case EIS_EVENT_DEVICE_REMOVED:
break;
default:
abort(); /* not yet implemented */
}
eis_device_unref(event->device);
eis_client_unref(event->client);
}
static
OBJECT_DECLARE_INIT(eis_event);
/* this one is not public */
OBJECT_DECLARE_REF(eis_event);
_public_
OBJECT_DECLARE_UNREF(eis_event);
_public_
OBJECT_DECLARE_GETTER(eis_event, type, enum eis_event_type);
_public_
OBJECT_DECLARE_GETTER(eis_event, client, struct eis_client*);
_public_
OBJECT_DECLARE_GETTER(eis_event, device, struct eis_device*);
/* FIXME: these should contain error checks but for now this will do */
_public_
OBJECT_DECLARE_GETTER(eis_event_pointer, x, int);
_public_
OBJECT_DECLARE_GETTER(eis_event_pointer, y, int);
_public_ struct eis_event_pointer *
eis_event_get_pointer_event(struct eis_event *e)
{
switch (e->type) {
case EIS_EVENT_NONE:
case EIS_EVENT_CLIENT_CONNECT:
case EIS_EVENT_CLIENT_DISCONNECT:
case EIS_EVENT_DEVICE_ADDED:
case EIS_EVENT_DEVICE_REMOVED:
case EIS_EVENT_REQUEST_CAPABILITY:
case EIS_EVENT_CANCEL_CAPABILITY:
break;
case EIS_EVENT_POINTER_MOTION:
case EIS_EVENT_POINTER_MOTION_ABSOLUTE:
case EIS_EVENT_POINTER_BUTTON:
case EIS_EVENT_POINTER_SCROLL:
case EIS_EVENT_POINTER_SCROLL_DISCRETE:
return container_of(e, struct eis_event_pointer, base);
case EIS_EVENT_KEYBOARD_KEY:
case EIS_EVENT_TOUCH_DOWN:
case EIS_EVENT_TOUCH_UP:
case EIS_EVENT_TOUCH_MOTION:
break;
}
return NULL;
}
static void
eis_destroy(struct eis *eis)
{
eis->logger = logger_unref(eis->logger);
if (eis->backend.destroy)
eis->backend.destroy(eis);
sink_unref(eis->sink);
}
OBJECT_DECLARE_INIT(eis);
_public_
OBJECT_DECLARE_REF(eis);
_public_
OBJECT_DECLARE_UNREF(eis);
#define _cleanup_eis_ _cleanup_(eis_cleanup)
_public_
OBJECT_DECLARE_SETTER(eis, userdata, void *);
OBJECT_DECLARE_GETTER(eis, userdata, void *);
int
eis_init(struct eis *eis)
{
list_init(&eis->clients);
list_init(&eis->event_queue);
eis->logger = logger_new(eis);
logger_set_priority(eis->logger, LOGGER_DEBUG);
eis->sink = sink_new();
if (!eis->sink)
return -EMFILE;
return 0;
}
_public_ int
eis_get_fd(struct eis *eis)
{
return sink_get_fd(eis->sink);
}
_public_ void
eis_dispatch(struct eis *eis)
{
sink_dispatch(eis->sink);
}
static void
eis_queue_event(struct eis *eis, struct eis_event *event)
{
list_append(&eis->event_queue, &event->link);
}
void
eis_queue_connect_event(struct eis_client *client)
{
struct eis *eis = eis_client_get_context(client);
struct eis_event_client *e = xalloc(sizeof(*e));
eis_event_init_object(&e->base, &eis->object);
e->base.client = eis_client_ref(client);
e->base.type = EIS_EVENT_CLIENT_CONNECT;
eis_queue_event(eis, &e->base);
}
void
eis_queue_disconnect_event(struct eis_client *client)
{
struct eis *eis = eis_client_get_context(client);
struct eis_event_client *e = xalloc(sizeof(*e));
eis_event_init_object(&e->base, &eis->object);
e->base.type = EIS_EVENT_CLIENT_DISCONNECT;
e->base.client = eis_client_ref(client);
eis_queue_event(eis, &e->base);
}
void
eis_queue_added_event(struct eis_device *device)
{
struct eis_client *client = eis_device_get_client(device);
struct eis *eis = eis_client_get_context(client);
struct eis_event_client *e = xalloc(sizeof(*e));
eis_event_init_object(&e->base, &eis->object);
e->base.type = EIS_EVENT_DEVICE_ADDED;
e->base.client = eis_client_ref(client);
e->base.device = eis_device_ref(device);
eis_queue_event(eis, &e->base);
}
void
eis_queue_removed_event(struct eis_device *device)
{
struct eis_client *client = eis_device_get_client(device);
struct eis *eis = eis_client_get_context(client);
struct eis_event_client *e = xalloc(sizeof(*e));
eis_event_init_object(&e->base, &eis->object);
e->base.type = EIS_EVENT_DEVICE_REMOVED;
e->base.client = eis_client_ref(client);
e->base.device = eis_device_ref(device);
eis_queue_event(eis, &e->base);
}
void
eis_queue_pointer_rel_event(struct eis_device *device, int x, int y)
{
struct eis_client *client = eis_device_get_client(device);
struct eis *eis = eis_client_get_context(client);
struct eis_event_pointer *e = xalloc(sizeof(*e));
eis_event_init_object(&e->base, &eis->object);
e->base.type = EIS_EVENT_POINTER_MOTION;
e->base.client = eis_client_ref(client);
e->base.device = eis_device_ref(device);
e->x = x;
e->y = y;
eis_queue_event(eis, &e->base);
}
_public_ struct eis_event*
eis_get_event(struct eis *eis)
{
if (list_empty(&eis->event_queue))
return NULL;
struct eis_event *e = list_first_entry(&eis->event_queue, e, link);
list_remove(&e->link);
return eis_event_ref(e);
}

View file

@ -136,6 +136,7 @@ eis_portal_init(struct eis *ctx);
int
eis_dbus_init(struct eis *ctx);
struct eis *
eis_socket_new_context(void *userdata);
@ -199,6 +200,9 @@ eis_event_get_type(struct eis_event *event);
struct eis_client *
eis_event_get_client(struct eis_event *event);
struct eis_client *
eis_device_get_client(struct eis_device *device);
struct eis_device *
eis_device_ref(struct eis_device *device);
@ -379,6 +383,9 @@ eis_device_keyboard_set_keymap(struct eis_device *device,
struct eis_device *
eis_event_get_device(struct eis_event *event);
struct eis_event_pointer *
eis_event_get_pointer_event(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_POINTER_MOTION return the x movement
* in 1/1000th of a logical pixel.

102
src/util-bits.h Normal file
View file

@ -0,0 +1,102 @@
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2013-2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#define bit(x_) (1UL << (x_))
#define NBITS(b) (b * 8)
#define LONG_BITS (sizeof(long) * 8)
#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
#define NCHARS(x) ((size_t)(((x) + 7) / 8))
#define flag_is_set(mask_, b_) (!!((mask_) & bit(b_)))
/* This bitfield helper implementation is taken from from libevdev-util.h,
* except that it has been modified to work with arrays of unsigned chars
*/
static inline bool
bit_is_set(const unsigned char *array, int bit)
{
return !!(array[bit / 8] & (1 << (bit % 8)));
}
static inline void
set_bit(unsigned char *array, int bit)
{
array[bit / 8] |= (1 << (bit % 8));
}
static inline void
clear_bit(unsigned char *array, int bit)
{
array[bit / 8] &= ~(1 << (bit % 8));
}
static inline bool
long_bit_is_set(const unsigned long *array, int bit)
{
return !!(array[bit / LONG_BITS] & (1ULL << (bit % LONG_BITS)));
}
static inline void
long_set_bit(unsigned long *array, int bit)
{
array[bit / LONG_BITS] |= (1ULL << (bit % LONG_BITS));
}
static inline void
long_clear_bit(unsigned long *array, int bit)
{
array[bit / LONG_BITS] &= ~(1ULL << (bit % LONG_BITS));
}
static inline void
long_set_bit_state(unsigned long *array, int bit, int state)
{
if (state)
long_set_bit(array, bit);
else
long_clear_bit(array, bit);
}
static inline bool
long_any_bit_set(unsigned long *array, size_t size)
{
unsigned long i;
assert(size > 0);
for (i = 0; i < size; i++)
if (array[i] != 0)
return true;
return false;
}

158
src/util-io.h Normal file
View file

@ -0,0 +1,158 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
static inline int
xclose(int fd) {
if (fd != -1) close(fd);
return -1;
}
static inline int
xread(int fd, void *buf, size_t count)
{
int rc = read(fd, buf, count);
return rc == -1 ? -errno : rc;
}
static inline int
xwrite(int fd, const void *buf, size_t count)
{
int rc = write(fd, buf, count);
return rc == -1 ? -errno : rc;
}
struct iobuf {
size_t sz;
size_t len;
char *data;
};
static inline struct iobuf *
iobuf_new(size_t size)
{
struct iobuf *buf = malloc(sizeof(*buf));
char *data = malloc(size);
assert(buf);
assert(data);
*buf = (struct iobuf) {
.sz = size,
.len = 0,
.data = data,
};
return buf;
}
static inline void
iobuf_resize(struct iobuf *buf, size_t to_size)
{
char *newdata = realloc(buf->data, to_size);
assert(newdata);
buf->data = newdata;
buf->sz = to_size;
}
static inline size_t
iobuf_len(struct iobuf *buf)
{
return buf->len;
}
static inline const char *
iobuf_data(struct iobuf *buf)
{
return buf->data;
}
static inline char *
iobuf_take_data(struct iobuf *buf)
{
char *data = buf->data;
buf->data = NULL;
buf->len = 0;
iobuf_resize(buf, buf->sz);
return data;
}
static inline void
iobuf_append(struct iobuf *buf, const char *data, size_t len)
{
if (buf->len + len > buf->sz) {
size_t newsize = buf->len + len;
iobuf_resize(buf, newsize);
}
memcpy(buf->data + buf->len, data, len);
}
static inline int
iobuf_append_from_fd(struct iobuf *buf, int fd)
{
char data[1024];
size_t nread = 0;
ssize_t rc;
do {
rc = xread(fd, data, sizeof(data));
if (rc <= 0) {
if (rc == -EAGAIN)
return nread;
return rc;
}
iobuf_append(buf, data, rc);
nread += rc;
} while (rc == sizeof(buf));
return nread;
}
static inline struct iobuf *
iobuf_free(struct iobuf *buf)
{
if (buf) {
free(buf->data);
buf->sz = 0;
buf->len = 0;
buf->data = NULL;
free(buf);
}
return NULL;
}
static inline void _iobuf_cleanup(struct iobuf **buf) { iobuf_free(*buf); }
#define _cleanup_iobuf_ __attribute__((cleanup(_iobuf_cleanup)))

88
src/util-list.c Normal file
View file

@ -0,0 +1,88 @@
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2013-2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <stddef.h>
#include <stdbool.h>
#include "util-list.h"
void
list_init(struct list *list)
{
list->prev = list;
list->next = list;
}
void
list_insert(struct list *list, struct list *elm)
{
assert((list->next != NULL && list->prev != NULL) ||
!"list->next|prev is NULL, possibly missing list_init()");
assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) ||
!"elm->next|prev is not NULL, list node used twice?");
elm->prev = list;
elm->next = list->next;
list->next = elm;
elm->next->prev = elm;
}
void
list_append(struct list *list, struct list *elm)
{
assert((list->next != NULL && list->prev != NULL) ||
!"list->next|prev is NULL, possibly missing list_init()");
assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) ||
!"elm->next|prev is not NULL, list node used twice?");
elm->next = list;
elm->prev = list->prev;
list->prev = elm;
elm->prev->next = elm;
}
void
list_remove(struct list *elm)
{
assert((elm->next != NULL && elm->prev != NULL) ||
!"list->next|prev is NULL, possibly missing list_init()");
elm->prev->next = elm->next;
elm->next->prev = elm->prev;
elm->next = NULL;
elm->prev = NULL;
}
bool
list_empty(const struct list *list)
{
assert((list->next != NULL && list->prev != NULL) ||
!"list->next|prev is NULL, possibly missing list_init()");
return list->next == list;
}

67
src/util-list.h Normal file
View file

@ -0,0 +1,67 @@
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2013-2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <stdbool.h>
#include <stddef.h>
/*
* This list data structure is a verbatim copy from wayland-util.h from the
* Wayland project; except that wl_ prefix has been removed.
*/
struct list {
struct list *prev;
struct list *next;
};
void list_init(struct list *list);
void list_insert(struct list *list, struct list *elm);
void list_append(struct list *list, struct list *elm);
void list_remove(struct list *elm);
bool list_empty(const struct list *list);
#define container_of(ptr, type, member) \
(__typeof__(type) *)((char *)(ptr) - \
offsetof(__typeof__(type), member))
#define list_first_entry(head, pos, member) \
container_of((head)->next, __typeof__(*pos), member)
#define list_for_each(pos, head, member) \
for (pos = 0, pos = list_first_entry(head, pos, member); \
&pos->member != (head); \
pos = list_first_entry(&pos->member, pos, member))
#define list_for_each_safe(pos, tmp, head, member) \
for (pos = 0, tmp = 0, \
pos = list_first_entry(head, pos, member), \
tmp = list_first_entry(&pos->member, tmp, member); \
&pos->member != (head); \
pos = tmp, \
tmp = list_first_entry(&pos->member, tmp, member))

104
src/util-logger.c Normal file
View file

@ -0,0 +1,104 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <stdbool.h>
#include <stdio.h>
#include "util-logger.h"
#include "util-object.h"
struct logger {
struct object object;
enum logger_priority priority;
logger_log_func_t handler;
void *user_data;
};
_printf_(3, 0)
static void
logger_default_log_func(struct logger *logger,
enum logger_priority priority,
const char *format, va_list args)
{
const char *prefix;
switch(priority) {
case LOGGER_DEBUG: prefix = "debug"; break;
case LOGGER_INFO: prefix = "info"; break;
case LOGGER_WARN: prefix = "warn"; break;
case LOGGER_ERROR: prefix = "error"; break;
default: prefix="<invalid priority>"; break;
}
fprintf(stderr, "%s: ", prefix);
vfprintf(stderr, format, args);
}
void
log_msg_va(struct logger *logger,
enum logger_priority priority,
const char *format,
va_list args)
{
if (logger->handler && logger->priority <= priority)
logger->handler(logger, priority, format, args);
}
void
log_msg(struct logger *logger,
enum logger_priority priority,
const char *format, ...)
{
va_list args;
va_start(args, format);
log_msg_va(logger, priority, format, args);
va_end(args);
}
static void
logger_destroy(struct logger* logger)
{
/* nothing to do */
}
OBJECT_DECLARE_CREATE(logger);
OBJECT_DECLARE_UNREF(logger);
OBJECT_DECLARE_SETTER(logger, priority, enum logger_priority);
OBJECT_DECLARE_GETTER(logger, priority, enum logger_priority);
OBJECT_DECLARE_SETTER(logger, user_data, void *);
OBJECT_DECLARE_GETTER(logger, user_data, void *);
OBJECT_DECLARE_SETTER(logger, handler, logger_log_func_t);
struct logger *
logger_new(void *user_data)
{
struct logger *logger = logger_create(NULL);
logger->user_data = user_data;
logger->priority = LOGGER_WARN;
logger->handler = logger_default_log_func;
return logger;
}

87
src/util-logger.h Normal file
View file

@ -0,0 +1,87 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <stdarg.h>
#include "util-macros.h"
struct logger;
enum logger_priority {
LOGGER_DEBUG = 20,
LOGGER_INFO = 30,
LOGGER_WARN = 40,
LOGGER_ERROR = 50,
};
typedef void (*logger_log_func_t)(struct logger *libinput,
enum logger_priority priority,
const char *format, va_list args);
void
log_msg(struct logger *logger,
enum logger_priority priority,
const char *format, ...);
void
log_msg_va(struct logger *logger,
enum logger_priority priority,
const char *format,
va_list args);
/* log helpers. The struct T_ needs to have a field called 'logger' */
#define log_debug(T_, ...) \
log_msg((T_)->logger, LOGGER_DEBUG, __VA_ARGS__)
#define log_info(T_, ...) \
log_msg((T_)->logger, LOGGER_INFO, __VA_ARGS__)
#define log_warn(T_, ...) \
log_msg((T_)->logger, LOGGER_WARN, __VA_ARGS__)
#define log_error(T_, ...) \
log_msg((T_)->logger, LOGGER_ERROR, __VA_ARGS__)
struct logger *
logger_new(void *user_data);
struct logger *
logger_unref(struct logger *logger);
void *
logger_get_user_data(struct logger *logger);
void
logger_set_user_data(struct logger *logger,
void *user_data);
enum logger_priority
logger_get_priority(struct logger *logger);
void
logger_set_priority(struct logger *logger,
enum logger_priority priority);
void
logger_set_handler(struct logger *logger,
logger_log_func_t handler);

60
src/util-macros.h Normal file
View file

@ -0,0 +1,60 @@
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2013-2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <unistd.h>
#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
#define ARRAY_FOR_EACH(_arr, _elem) \
for (size_t _i = 0; _i < ARRAY_LENGTH(_arr) && (_elem = &_arr[_i]); _i++)
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define ANSI_HIGHLIGHT "\x1B[0;1;39m"
#define ANSI_RED "\x1B[0;31m"
#define ANSI_GREEN "\x1B[0;32m"
#define ANSI_YELLOW "\x1B[0;33m"
#define ANSI_BLUE "\x1B[0;34m"
#define ANSI_MAGENTA "\x1B[0;35m"
#define ANSI_CYAN "\x1B[0;36m"
#define ANSI_BRIGHT_RED "\x1B[0;31;1m"
#define ANSI_BRIGHT_GREEN "\x1B[0;32;1m"
#define ANSI_BRIGHT_YELLOW "\x1B[0;33;1m"
#define ANSI_BRIGHT_BLUE "\x1B[0;34;1m"
#define ANSI_BRIGHT_MAGENTA "\x1B[0;35;1m"
#define ANSI_BRIGHT_CYAN "\x1B[0;36;1m"
#define ANSI_NORMAL "\x1B[0m"
#define ANSI_UP "\x1B[%dA"
#define ANSI_DOWN "\x1B[%dB"
#define ANSI_RIGHT "\x1B[%dC"
#define ANSI_LEFT "\x1B[%dD"
#define _public_ __attribute__((visibility("default")))
#define _printf_(_a, _b) __attribute__((format (printf, _a, _b)))

92
src/util-mem.h Normal file
View file

@ -0,0 +1,92 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <assert.h>
#include <stdlib.h>
/**
* Use: _cleanup_(somefunction) struct foo *bar;
*/
#define _cleanup_(_x) __attribute__((cleanup(_x)))
static inline void _free_ptr_(void *p) { free(*(void**)p); }
/**
* Use: _cleanup_free_ char *data;
*/
#define _cleanup_free_ _cleanup_(_free_ptr_)
/**
* Use:
* DEFINE_TRIVIAL_CLEANUP_FUNC(struct foo, foo_unref)
* _cleanup_(foo_unrefp) struct foo *bar;
*/
#define DEFINE_TRIVIAL_CLEANUP_FUNC(_type, _func) \
static inline void _func##p(_type *_p) { \
if (*_p) \
_func(*_p); \
} \
struct __useless_struct_to_allow_trailing_semicolon__
static inline void*
_steal(void *ptr) {
void **original = (void**)ptr;
void *swapped = *original;
*original = NULL;
return swapped;
}
/**
* Resets the pointer content and resets the data to NULL.
* This circumvents _cleanup_ handling for that pointer.
* Use:
* _cleanup_free_ char *data = malloc();
* return steal(&data);
*
*/
#define steal(ptr_) \
(typeof(*ptr_))_steal(ptr_)
/**
* Never-failing calloc with a size limit check.
*/
static inline void *
xalloc(size_t size)
{
void *p;
/* We never need to alloc anything more than 1,5 MB so we can assume
* if we ever get above that something's going wrong */
if (size > 1536 * 1024)
assert(!"bug: internal malloc size limit exceeded");
p = calloc(1, size);
if (!p)
abort();
return p;
}

175
src/util-object.h Normal file
View file

@ -0,0 +1,175 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/**
* This is an abstraction layer for ref-counted objects with an optional
* parent. It cuts down on boilerplate by providing a bunch of macros
* that generate the various required functions.
* Limitation: the object must be the first item in the parent struct.
*/
#pragma once
#include "config.h"
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
struct object;
/**
* Function called when the last reference is unref'd. Clean up **internal**
* state of the object, the object itself is freed by the generated
* functions.
*/
typedef void (*object_destroy_func_t)(struct object *object);
/* internal implementation, do not use directly */
struct object {
struct object *parent; /* may be NULL */
uint32_t refcount;
object_destroy_func_t destroy;
};
/* internal implementation, do not call directly */
static inline void
object_init(struct object *object,
struct object *parent,
object_destroy_func_t destroy)
{
object->refcount = 1;
object->destroy = destroy;
object->parent = parent;
}
/* internal implementation, do not call directly */
static inline void
object_destroy(struct object *object)
{
if (object->destroy)
object->destroy(object);
free(object);
}
/* internal implementation, do not call directly */
static inline void *
object_ref(struct object *object)
{
assert(object->refcount >= 1);
++object->refcount;
return object;
}
/* internal implementation, do not call directly */
static inline void *
object_unref(struct object *object)
{
assert(object->refcount >= 1);
if (--object->refcount == 0)
object_destroy(object);
return NULL;
}
/**
* For a type of "foo", generate
* struct foo *foo_ref(struct foo *f);
*/
#define OBJECT_DECLARE_REF(type_) \
struct type_ * type_##_ref(struct type_ *obj) { \
object_ref(&obj->object); \
return obj; \
}
/**
* For a type of "foo", generate
* struct foo *foo_unref(struct foo *f);
* The function always returns NULL, when the last reference is removed the
* object is freed.
*
* This macro also defines a cleanup function, use with
* __attribute__((cleanup(foo_cleanup))
*/
#define OBJECT_DECLARE_UNREF(type_) \
struct type_ * type_##_unref(struct type_ *obj) { \
if (!obj) return NULL; \
return object_unref(&obj->object); \
} \
static inline void type_##_cleanup(struct type_ **p_) { \
if (*p_) type_##_unref(*p_); \
} \
struct __useless_struct_to_allow_trailing_semicolon__
/**
* For a type for "foo", generate
* void foo_init_object(struct foo *f)
* which sets up the *object* part of the foo struct. Use this where
* allocation through foo_create isn't suitable.
*/
#define OBJECT_DECLARE_INIT(type_) \
void type_##_init_object(struct type_ *t, struct object *parent) { \
assert((intptr_t)t == (intptr_t)&t->object && "field 'object' must be first one in the struct"); \
object_init(&t->object, parent, (object_destroy_func_t)type_##_destroy); \
}
/**
* For a type for "foo", generate
* struct foo *foo_create(struct object *parent)
* which returns an callocated and initialized foo struct.
*/
#define OBJECT_DECLARE_CREATE(type_) \
static inline struct type_ * type_##_create(struct object *parent) { \
struct type_ *t = calloc(1, sizeof *t); \
assert((intptr_t)t == (intptr_t)&t->object && "field 'object' must be first one in the struct"); \
assert(t != NULL); \
object_init(&t->object, parent, (object_destroy_func_t)type_##_destroy); \
return t; \
}
/**
* For a type "foo" with parent type "bar", generate
* struct bar* foo_parent(struct foo *foo)
*/
#define OBJECT_DECLARE_PARENT(type_, parent_type_) \
struct parent_type_ * type_##_parent(struct type_ *o) { \
return container_of(o->object.parent, struct parent_type_, object); \
}
/**
* Generate a simple getter function for the given type, field and return
* type.
*/
#define OBJECT_DECLARE_GETTER(type_, field_, rtype_) \
rtype_ type_##_get_##field_(struct type_ *obj) { \
return obj->field_; \
}
/**
* Generate a simple setter function for the given type, field and return
* type.
*/
#define OBJECT_DECLARE_SETTER(type_, field_, rtype_) \
void type_##_set_##field_(struct type_ *obj, rtype_ val_) { \
obj->field_ = (val_); \
}

189
src/util-sources.c Normal file
View file

@ -0,0 +1,189 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <sys/epoll.h>
#include "util-object.h"
#include "util-io.h"
#include "util-list.h"
#include "util-sources.h"
struct sink {
struct object object;
int epollfd;
struct list sources;
struct list sources_removed;
};
enum source_close_behavior {
SOURCE_CLOSE_ON_REMOVE = 1, /* default */
SOURCE_CLOSE_ON_DESTROY,
SOURCE_CLOSE_NEVER,
};
struct source {
struct object object;
struct sink *sink;
struct list link; /* sink.sources or sink.sources_removed */
source_dispatch_t dispatch;
void *user_data;
enum source_close_behavior close_behavior;
int fd;
};
OBJECT_DECLARE_UNREF(source);
OBJECT_DECLARE_GETTER(source, fd, int);
OBJECT_DECLARE_GETTER(source, user_data, void*);
OBJECT_DECLARE_SETTER(source, user_data, void*);
/**
* Remove the source, closing the fd. The source is tagged as removed and
* will be removed whenever sink_dispatch() finishes (or is called next).
*/
void
source_remove(struct source *source)
{
if (source->fd != -1) {
epoll_ctl(source->sink->epollfd, EPOLL_CTL_DEL, source->fd, NULL);
if (source->close_behavior == SOURCE_CLOSE_ON_REMOVE)
source->fd = xclose(source->fd);
}
list_remove(&source->link);
list_append(&source->sink->sources_removed, &source->link);
}
/* Ignore, use source_unref() */
static void
source_destroy(struct source *source)
{
if (source->fd != -1) {
source_remove(source);
if (source->close_behavior == SOURCE_CLOSE_ON_DESTROY)
source->fd = xclose(source->fd);
}
}
OBJECT_DECLARE_CREATE(source);
static inline struct source *
source_add(struct sink *sink, int sourcefd,
source_dispatch_t dispatch,
void *user_data,
enum source_close_behavior close_behavior)
{
struct source *source = source_create(NULL);
source->dispatch = dispatch;
source->user_data = user_data;
source->fd = sourcefd;
source->sink = sink;
source->close_behavior = close_behavior;
struct epoll_event e = {
.events = EPOLLIN,
.data.ptr = source,
};
if (epoll_ctl(sink->epollfd, EPOLL_CTL_ADD, sourcefd, &e) < 0) {
free(source);
return NULL;
}
list_append(&sink->sources, &source->link);
return source;
}
struct source *
source_add_autoclose(struct sink *sink, int sourcefd,
source_dispatch_t dispatch,
void *user_data)
{
return source_add(sink, sourcefd, dispatch, user_data, SOURCE_CLOSE_ON_REMOVE);
}
static void
sink_destroy(struct sink *sink)
{
struct source *s, *tmp;
list_for_each_safe(s, tmp, &sink->sources, link) {
source_remove(s);
}
list_for_each_safe(s, tmp, &sink->sources_removed, link) {
source_unref(s);
}
xclose(sink->epollfd);
}
OBJECT_DECLARE_UNREF(sink);
OBJECT_DECLARE_CREATE(sink);
int
sink_get_fd(struct sink *sink)
{
return sink->epollfd;
}
struct sink *
sink_new(void)
{
int fd = epoll_create1(EPOLL_CLOEXEC);
if (fd < 0)
return NULL;
struct sink *sink = sink_create(NULL);
sink->epollfd = fd;
list_init(&sink->sources);
list_init(&sink->sources_removed);
return sink;
}
int
sink_dispatch(struct sink *sink)
{
struct epoll_event ep[32];
int count = epoll_wait(sink->epollfd, ep, sizeof(ep)/sizeof(ep[0]), 0);
if (count < 0)
return -errno;
for (int i = 0; i < count; ++i) {
struct source *source = ep[i].data.ptr;
if (source->fd == -1)
continue;
source->dispatch(source, source->user_data);
}
struct source *s, *tmp;
list_for_each_safe(s, tmp, &sink->sources_removed, link) {
source_unref(s);
}
return 0;
}

83
src/util-sources.h Normal file
View file

@ -0,0 +1,83 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/**
* Source/sink objects.
*/
#pragma once
#include "config.h"
struct source;
struct sink;
/**
* Callback invoked when the source has data available. userdata is the data
* provided to source_add()
*/
typedef void (*source_dispatch_t)(struct source *source, void *user_data);
/**
* Remove source from its sink without destroying it.
*/
void source_remove(struct source *source);
/**
* Unref source. When the last reference is dropped, the source is removed
* from the sink and resources are released.
*/
struct source *
source_unref(struct source *source);
int
source_get_fd(struct source *source);
void *
source_get_user_data(struct source *source);
void
source_set_user_data(struct source *source, void *user_data);
/**
* Add a new source that closes the connected fd on removal of the source.
*/
struct source *
source_add_autoclose(struct sink *sink, int sourcefd,
source_dispatch_t dispatch,
void *user_data);
struct sink *
sink_new(void);
struct sink *
sink_unref(struct sink *sink);
int
sink_dispatch(struct sink *sink);
/**
* The epollfd to monitor for this sink.
*/
int
sink_get_fd(struct sink *sink);

157
src/util-strings.c Normal file
View file

@ -0,0 +1,157 @@
/*
* Copyright © 2008 Kristian Høgsberg
* Copyright © 2013-2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include "util-strings.h"
/**
* Return the next word in a string pointed to by state before the first
* separator character. Call repeatedly to tokenize a whole string.
*
* @param state Current state
* @param len String length of the word returned
* @param separators List of separator characters
*
* @return The first word in *state, NOT null-terminated
*/
static const char *
next_word(const char **state, size_t *len, const char *separators)
{
const char *next = *state;
size_t l;
if (!*next)
return NULL;
next += strspn(next, separators);
if (!*next) {
*state = next;
return NULL;
}
l = strcspn(next, separators);
*state = next + l;
*len = l;
return next;
}
/**
* Return a null-terminated string array with the tokens in the input
* string, e.g. "one two\tthree" with a separator list of " \t" will return
* an array [ "one", "two", "three", NULL ].
*
* Use strv_free() to free the array.
*
* @param in Input string
* @param separators List of separator characters
*
* @return A null-terminated string array or NULL on errors
*/
char **
strv_from_string(const char *in, const char *separators)
{
const char *s, *word;
char **strv = NULL;
int nelems = 0, idx;
size_t l;
assert(in != NULL);
s = in;
while (next_word(&s, &l, separators) != NULL)
nelems++;
if (nelems == 0)
return NULL;
nelems++; /* NULL-terminated */
strv = xalloc(nelems * sizeof *strv);
idx = 0;
s = in;
while ((word = next_word(&s, &l, separators)) != NULL) {
char *copy = strndup(word, l);
if (!copy) {
strv_free(strv);
return NULL;
}
strv[idx++] = copy;
}
return strv;
}
/**
* Return a newly allocated string with all elements joined by the
* joiner, same as Python's string.join() basically.
* A strv of ["one", "two", "three", NULL] with a joiner of ", " results
* in "one, two, three".
*
* An empty strv ([NULL]) returns NULL, same for passing NULL as either
* argument.
*
* @param strv Input string arrray
* @param joiner Joiner between the elements in the final string
*
* @return A null-terminated string joining all elements
*/
char *
strv_join(char **strv, const char *joiner)
{
char **s;
char *str;
size_t slen = 0;
size_t count = 0;
if (!strv || !joiner)
return NULL;
if (strv[0] == NULL)
return NULL;
for (s = strv, count = 0; *s; s++, count++) {
slen += strlen(*s);
}
assert(slen < 1000);
assert(strlen(joiner) < 1000);
assert(count > 0);
assert(count < 100);
slen += (count - 1) * strlen(joiner);
str = xalloc(slen + 1); /* trailing \0 */
for (s = strv; *s; s++) {
strcat(str, *s);
--count;
if (count > 0)
strcat(str, joiner);
}
return str;
}

370
src/util-strings.h Normal file
View file

@ -0,0 +1,370 @@
/*
* Copyright © 2008 Kristian Høgsberg
* Copyright © 2013-2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdarg.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef HAVE_XLOCALE_H
#include <xlocale.h>
#endif
#include "util-macros.h"
#include "util-mem.h"
#define streq(s1, s2) (strcmp((s1), (s2)) == 0)
#define strneq(s1, s2, n) (strncmp((s1), (s2), (n)) == 0)
/**
* strdup guaranteed to succeed. If the input string is NULL, the output
* string is NULL. If the input string is a string pointer, we strdup or
* abort on failure.
*/
static inline char*
xstrdup(const char *str)
{
char *s;
if (!str)
return NULL;
s = strdup(str);
if (!s)
abort();
return s;
}
static inline bool
_printf_(3, 4)
xsnprintf(char *buf, size_t sz, const char *format, ...)
{
va_list ap;
int rc;
va_start(ap, format);
rc = vsnprintf(buf, sz, format, ap);
va_end(ap);
return rc >= 0 && (size_t)rc < sz;
}
static inline char *
_printf_(1, 0)
xvasprintf(const char *fmt, va_list args)
{
char *str;
int len;
len = vasprintf(&str, fmt, args);
if (len == -1)
return NULL;
return str;
}
/**
* A version of asprintf that returns the allocated string or NULL on error.
*/
static inline char *
_printf_(1, 2)
xaprintf(const char *fmt, ...)
{
va_list args;
char *str;
va_start(args, fmt);
str = xvasprintf(fmt, args);
va_end(args);
return str;
}
static inline bool
xatoi_base(const char *str, int *val, int base)
{
char *endptr;
long v;
assert(base == 10 || base == 16 || base == 8);
errno = 0;
v = strtol(str, &endptr, base);
if (errno > 0)
return false;
if (str == endptr)
return false;
if (*str != '\0' && *endptr != '\0')
return false;
if (v > INT_MAX || v < INT_MIN)
return false;
*val = v;
return true;
}
static inline bool
xatoi(const char *str, int *val)
{
return xatoi_base(str, val, 10);
}
static inline bool
xatou_base(const char *str, unsigned int *val, int base)
{
char *endptr;
unsigned long v;
assert(base == 10 || base == 16 || base == 8);
errno = 0;
v = strtoul(str, &endptr, base);
if (errno > 0)
return false;
if (str == endptr)
return false;
if (*str != '\0' && *endptr != '\0')
return false;
if ((long)v < 0)
return false;
*val = v;
return true;
}
static inline bool
xatou(const char *str, unsigned int *val)
{
return xatou_base(str, val, 10);
}
static inline bool
xatod(const char *str, double *val)
{
char *endptr;
double v;
#ifdef HAVE_LOCALE_H
locale_t c_locale;
#endif
size_t slen = strlen(str);
/* We don't have a use-case where we want to accept hex for a double
* or any of the other values strtod can parse */
for (size_t i = 0; i < slen; i++) {
char c = str[i];
if (isdigit(c))
continue;
switch(c) {
case '+':
case '-':
case '.':
break;
default:
return false;
}
}
#ifdef HAVE_LOCALE_H
/* Create a "C" locale to force strtod to use '.' as separator */
c_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
if (c_locale == (locale_t)0)
return false;
errno = 0;
v = strtod_l(str, &endptr, c_locale);
freelocale(c_locale);
#else
/* No locale support in provided libc, assume it already uses '.' */
errno = 0;
v = strtod(str, &endptr);
#endif
if (errno > 0)
return false;
if (str == endptr)
return false;
if (*str != '\0' && *endptr != '\0')
return false;
if (v != 0.0 && !isnormal(v))
return false;
*val = v;
return true;
}
char **strv_from_string(const char *string, const char *separator);
char *strv_join(char **strv, const char *separator);
static inline void
strv_free(char **strv) {
char **s = strv;
if (!strv)
return;
while (*s != NULL) {
free(*s);
*s = (char*)0x1; /* detect use-after-free */
s++;
}
free (strv);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(char **, strv_free);
struct key_value_str{
char *key;
char *value;
};
struct key_value_double {
double key;
double value;
};
static inline ssize_t
kv_double_from_string(const char *string,
const char *pair_separator,
const char *kv_separator,
struct key_value_double **result_out)
{
char **pairs;
char **pair;
struct key_value_double *result = NULL;
ssize_t npairs = 0;
unsigned int idx = 0;
if (!pair_separator || pair_separator[0] == '\0' ||
!kv_separator || kv_separator[0] == '\0')
return -1;
pairs = strv_from_string(string, pair_separator);
if (!pairs)
return -1;
for (pair = pairs; *pair; pair++)
npairs++;
if (npairs == 0)
goto error;
result = xalloc(npairs * sizeof *result);
for (pair = pairs; *pair; pair++) {
char **kv = strv_from_string(*pair, kv_separator);
double k, v;
if (!kv || !kv[0] || !kv[1] || kv[2] ||
!xatod(kv[0], &k) ||
!xatod(kv[1], &v)) {
strv_free(kv);
goto error;
}
result[idx].key = k;
result[idx].value = v;
idx++;
strv_free(kv);
}
strv_free(pairs);
*result_out = result;
return npairs;
error:
strv_free(pairs);
free(result);
return -1;
}
/**
* Strip any of the characters in what from the beginning and end of the
* input string.
*
* @return a newly allocated string with none of "what" at the beginning or
* end of string
*/
static inline char *
strstrip(const char *input, const char *what)
{
char *str, *last;
str = xstrdup(&input[strspn(input, what)]);
last = str;
for (char *c = str; *c != '\0'; c++) {
if (!strchr(what, *c))
last = c + 1;
}
*last = '\0';
return str;
}
/**
* Return true if str ends in suffix, false otherwise. If the suffix is the
* empty string, strendswith() always returns false.
*/
static inline bool
strendswith(const char *str, const char *suffix)
{
size_t slen = strlen(str);
size_t suffixlen = strlen(suffix);
size_t offset;
if (slen == 0 || suffixlen == 0 || suffixlen > slen)
return false;
offset = slen - suffixlen;
return strneq(&str[offset], suffix, suffixlen);
}
static inline bool
strstartswith(const char *str, const char *prefix)
{
size_t prefixlen = strlen(prefix);
return prefixlen > 0 ? strneq(str, prefix, strlen(prefix)) : false;
}

115
tools/eis-socket-server.c Normal file
View file

@ -0,0 +1,115 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include "libeis.h"
#include "src/util-mem.h"
#include "src/util-strings.h"
int main(int argc, char **argv)
{
const char SOCKETNAME[] = "eis.socket";
struct eis *eis = eis_socket_new_context(NULL);
assert(eis);
const char *xdgdir = getenv("XDG_RUNTIME_DIR");
assert(xdgdir != NULL);
_cleanup_free_ char *socketpath = xaprintf("%s/%s", xdgdir, SOCKETNAME);
unlink(socketpath);
/* This should be handled by libeis */
signal(SIGPIPE, SIG_IGN);
int rc = eis_socket_init(eis, SOCKETNAME);
if (rc != 0) {
fprintf(stderr, "init failed: %s\n", strerror(errno));
return 1;
}
struct pollfd fds = {
.fd = eis_get_fd(eis),
.events = POLLIN,
.revents = 0,
};
while (poll(&fds, 1, -1) > -1) {
eis_dispatch(eis);
struct eis_event *e = eis_get_event(eis);
if (!e)
continue;
switch(eis_event_get_type(e)) {
case EIS_EVENT_CLIENT_CONNECT:
{
struct eis_client *client = eis_event_get_client(e);
printf("server: new client: %s\n", eis_client_get_name(client));
/* insert sophisticated authentication here */
eis_client_connect(client);
printf("server: accepting client\n");
break;
}
case EIS_EVENT_CLIENT_DISCONNECT:
{
struct eis_client *client = eis_event_get_client(e);
printf("server: client %s disconnected\n", eis_client_get_name(client));
eis_client_disconnect(client);
break;
}
case EIS_EVENT_DEVICE_ADDED:
{
struct eis_device *device = eis_event_get_device(e);
printf("server: new device, caps:%s%s%s%s\n",
eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER) ? " ptr" : "",
eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD) ? " kbd" : "",
eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE) ? " abs" : "",
eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH) ? " touch" : "");
/* insert sophisticated device checks here */
eis_device_connect(device);
break;
}
case EIS_EVENT_POINTER_MOTION:
{
struct eis_event_pointer *p = eis_event_get_pointer_event(e);
printf("server: motion by %d/%d\n",
eis_event_pointer_get_x(p),
eis_event_pointer_get_y(p));
}
break;
default:
abort();
}
eis_event_unref(e);
}
return 0;
}