From f7a24b2fbd2bf7dd0b663fe300bd3a32e591de1c Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 28 Jul 2020 14:19:28 +1000 Subject: [PATCH] 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 --- meson.build | 37 +++ src/libeis-client.c | 459 ++++++++++++++++++++++++++++++++++++++ src/libeis-device.c | 102 +++++++++ src/libeis-private.h | 133 +++++++++++ src/libeis-socket.c | 127 +++++++++++ src/libeis.c | 216 ++++++++++++++++++ src/libeis.h | 7 + src/util-bits.h | 102 +++++++++ src/util-io.h | 158 +++++++++++++ src/util-list.c | 88 ++++++++ src/util-list.h | 67 ++++++ src/util-logger.c | 104 +++++++++ src/util-logger.h | 87 ++++++++ src/util-macros.h | 60 +++++ src/util-mem.h | 92 ++++++++ src/util-object.h | 175 +++++++++++++++ src/util-sources.c | 189 ++++++++++++++++ src/util-sources.h | 83 +++++++ src/util-strings.c | 157 +++++++++++++ src/util-strings.h | 370 ++++++++++++++++++++++++++++++ tools/eis-socket-server.c | 115 ++++++++++ 21 files changed, 2928 insertions(+) create mode 100644 src/libeis-client.c create mode 100644 src/libeis-device.c create mode 100644 src/libeis-private.h create mode 100644 src/libeis-socket.c create mode 100644 src/util-bits.h create mode 100644 src/util-io.h create mode 100644 src/util-list.c create mode 100644 src/util-list.h create mode 100644 src/util-logger.c create mode 100644 src/util-logger.h create mode 100644 src/util-macros.h create mode 100644 src/util-mem.h create mode 100644 src/util-object.h create mode 100644 src/util-sources.c create mode 100644 src/util-sources.h create mode 100644 src/util-strings.c create mode 100644 src/util-strings.h create mode 100644 tools/eis-socket-server.c diff --git a/meson.build b/meson.build index 62b0244..afea523 100644 --- a/meson.build +++ b/meson.build @@ -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) diff --git a/src/libeis-client.c b/src/libeis-client.c new file mode 100644 index 0000000..601e143 --- /dev/null +++ b/src/libeis-client.c @@ -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 +#include +#include +#include + +#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); +} diff --git a/src/libeis-device.c b/src/libeis-device.c new file mode 100644 index 0000000..feba668 --- /dev/null +++ b/src/libeis-device.c @@ -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 + +#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); +} diff --git a/src/libeis-private.h b/src/libeis-private.h new file mode 100644 index 0000000..197024c --- /dev/null +++ b/src/libeis-private.h @@ -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); diff --git a/src/libeis-socket.c b/src/libeis-socket.c new file mode 100644 index 0000000..6907c2a --- /dev/null +++ b/src/libeis-socket.c @@ -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 +#include +#include + +#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; +} diff --git a/src/libeis.c b/src/libeis.c index 2eaae5f..35441ce 100644 --- a/src/libeis.c +++ b/src/libeis.c @@ -21,4 +21,220 @@ * DEALINGS IN THE SOFTWARE. */ +#include "config.h" + +#include +#include +#include +#include + +#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); +} diff --git a/src/libeis.h b/src/libeis.h index b84b1d2..a66a124 100644 --- a/src/libeis.h +++ b/src/libeis.h @@ -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. diff --git a/src/util-bits.h b/src/util-bits.h new file mode 100644 index 0000000..13e73f7 --- /dev/null +++ b/src/util-bits.h @@ -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 +#include +#include + +#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; +} diff --git a/src/util-io.h b/src/util-io.h new file mode 100644 index 0000000..71e6e88 --- /dev/null +++ b/src/util-io.h @@ -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 +#include +#include +#include + +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))) diff --git a/src/util-list.c b/src/util-list.c new file mode 100644 index 0000000..45fed45 --- /dev/null +++ b/src/util-list.c @@ -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 +#include +#include + +#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; +} diff --git a/src/util-list.h b/src/util-list.h new file mode 100644 index 0000000..292665a --- /dev/null +++ b/src/util-list.h @@ -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 +#include + +/* + * 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)) diff --git a/src/util-logger.c b/src/util-logger.c new file mode 100644 index 0000000..5e594d3 --- /dev/null +++ b/src/util-logger.c @@ -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 +#include + +#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=""; 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; +} diff --git a/src/util-logger.h b/src/util-logger.h new file mode 100644 index 0000000..3b969bc --- /dev/null +++ b/src/util-logger.h @@ -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 + +#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); diff --git a/src/util-macros.h b/src/util-macros.h new file mode 100644 index 0000000..fd8fb90 --- /dev/null +++ b/src/util-macros.h @@ -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 + +#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))) diff --git a/src/util-mem.h b/src/util-mem.h new file mode 100644 index 0000000..62f5899 --- /dev/null +++ b/src/util-mem.h @@ -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 +#include + +/** + * 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; +} + diff --git a/src/util-object.h b/src/util-object.h new file mode 100644 index 0000000..b128489 --- /dev/null +++ b/src/util-object.h @@ -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 +#include +#include + +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_); \ +} diff --git a/src/util-sources.c b/src/util-sources.c new file mode 100644 index 0000000..44abe51 --- /dev/null +++ b/src/util-sources.c @@ -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 +#include +#include + +#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; +} diff --git a/src/util-sources.h b/src/util-sources.h new file mode 100644 index 0000000..03683be --- /dev/null +++ b/src/util-sources.h @@ -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); diff --git a/src/util-strings.c b/src/util-strings.c new file mode 100644 index 0000000..d2c6de7 --- /dev/null +++ b/src/util-strings.c @@ -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; +} diff --git a/src/util-strings.h b/src/util-strings.h new file mode 100644 index 0000000..35462db --- /dev/null +++ b/src/util-strings.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif +#ifdef HAVE_XLOCALE_H +#include +#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; +} diff --git a/tools/eis-socket-server.c b/tools/eis-socket-server.c new file mode 100644 index 0000000..e914cdc --- /dev/null +++ b/tools/eis-socket-server.c @@ -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 +#include +#include +#include +#include +#include + +#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; +}