Add a property API for generic key/value exchanges

There is data that libei and the EIS implementation will want to
exchange that is not covered by the immediate API.

To avoid having to add APIs for all of these, let's provide a generic
property API that both server and client can use to exchange this info.

The property API provides read/write/delete permissions but those only
apply to the client, not the server. The idea is that a server can
create (or restrict) properties that the client can read but not modify
and/or delete. A special-case are properties filled in automatically by
libei: ei.application.pid and ei.application.cmdline. These could be
used by e.g. the portal implementation to match permissions.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2021-08-24 14:49:39 +10:00
parent 77c898c3b8
commit 1225bcb0e1
19 changed files with 1270 additions and 1 deletions

View file

@ -50,6 +50,7 @@ src_libei = [
'src/libei-seat.c',
'src/libei-socket.c',
'src/libei-fd.c',
'src/libei-property.c',
'src/libei-proto.h',
'src/libei-proto.c',
'src/libei-region.c',
@ -102,6 +103,7 @@ src_libeis = [
'src/libeis-device.c',
'src/libeis-event.c',
'src/libeis-log.c',
'src/libeis-property.c',
'src/libeis-region.c',
'src/libeis-seat.c',
'src/libeis-socket.c',

View file

@ -166,6 +166,12 @@ message Frame {
uint32 deviceid = 1;
}
message Property {
string name = 1;
string value = 2;
uint32 permissions = 3;
};
message ClientMessage {
oneof msg {
Connect connect = 1;
@ -189,6 +195,7 @@ message ClientMessage {
Frame frame = 19;
StartEmulating start_emulating = 20;
StopEmulating stop_emulating = 21;
Property property = 22;
}
}
@ -269,6 +276,7 @@ message ServerMessage {
DeviceResumed device_resumed = 11;
DevicePaused device_paused = 12;
KeyboardModifiers keyboard_modifiers = 13;
Property property = 14;
}
}

View file

@ -48,6 +48,7 @@ ei_event_type_to_string(enum ei_event_type type)
CASE_RETURN_STRING(EI_EVENT_DEVICE_PAUSED);
CASE_RETURN_STRING(EI_EVENT_DEVICE_RESUMED);
CASE_RETURN_STRING(EI_EVENT_KEYBOARD_MODIFIERS);
CASE_RETURN_STRING(EI_EVENT_PROPERTY);
}
assert(!"Unhandled event type");
@ -67,6 +68,10 @@ ei_event_destroy(struct ei_event *event)
case EI_EVENT_DEVICE_RESUMED:
case EI_EVENT_KEYBOARD_MODIFIERS:
break;
case EI_EVENT_PROPERTY:
free(event->prop.name);
free(event->prop.value);
break;
}
ei_device_unref(event->device);
ei_seat_unref(event->seat);
@ -155,3 +160,31 @@ ei_event_keyboard_get_xkb_group(struct ei_event *event)
return event->modifiers.group;
}
_public_ const char *
ei_event_property_get_name(struct ei_event *event)
{
require_event_type(event, NULL,
EI_EVENT_PROPERTY);
return event->prop.name;
}
_public_ const char *
ei_event_property_get_value(struct ei_event *event)
{
require_event_type(event, NULL,
EI_EVENT_PROPERTY);
return event->prop.value;
}
_public_ uint32_t
ei_event_property_get_permissions(struct ei_event *event)
{
require_event_type(event, 0,
EI_EVENT_PROPERTY);
return event->prop.permissions;
}

View file

@ -58,6 +58,8 @@ struct ei {
struct list seats;
char *name;
struct list properties;
struct {
ei_log_handler handler;
enum ei_log_priority priority;
@ -173,9 +175,34 @@ struct ei_event {
struct ei_seat *seat; /* NULL if device is non-NULL */
struct ei_device *device;
struct ei_xkb_modifiers modifiers;
union {
struct ei_xkb_modifiers modifiers;
struct {
char *name;
char *value;
uint32_t permissions;
} prop;
};
};
struct ei_property {
struct object object;
struct list link;
char *name;
char *value;
uint32_t permissions;
};
struct ei_property *
ei_property_unref(struct ei_property *prop);
struct ei_property *
ei_find_property(struct ei *ei, const char *name);
int
ei_property_update(struct ei *ei, const char *name,
const char *value, uint32_t permissions);
struct ei_event *
ei_event_new(struct ei *ei);
@ -204,6 +231,9 @@ ei_seat_remove(struct ei_seat *seat);
void
ei_seat_drop(struct ei_seat *seat);
int
ei_send_property(struct ei *ei, const char *name, const char *value, uint32_t permissions);
int
ei_send_seat_bind(struct ei_seat *seat, uint32_t capabilities);

172
src/libei-property.c Normal file
View file

@ -0,0 +1,172 @@
/*
* Copyright © 2021 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-strings.h"
#include "util-object.h"
#include "libei-private.h"
static void
ei_property_destroy(struct ei_property *prop)
{
list_remove(&prop->link);
free(prop->name);
free(prop->value);
}
static
OBJECT_IMPLEMENT_CREATE(ei_property);
OBJECT_IMPLEMENT_UNREF(ei_property);
#define _PERM(p_, mask_) (((p_) & (mask_)) == (mask_))
#define PERM_R(p_) _PERM((p_)->permissions, EI_PROPERTY_PERM_READ)
#define PERM_RW(p_) _PERM((p_)->permissions, EI_PROPERTY_PERM_READ|EI_PROPERTY_PERM_WRITE)
#define PERM_RD(p_) _PERM((p_)->permissions, EI_PROPERTY_PERM_READ|EI_PROPERTY_PERM_DELETE)
struct ei_property *
ei_find_property(struct ei *ei, const char *name)
{
struct ei_property *prop;
list_for_each(prop, &ei->properties, link) {
if (streq(prop->name, name)) {
return prop;
}
}
return NULL;
}
static struct ei_property *
ei_property_new(struct ei *ei, const char *name,
const char *value, uint32_t permissions)
{
struct ei_property *prop = ei_property_create(NULL);
prop->name = xstrdup(name);
prop->value = xstrdup(value);
prop->permissions = permissions & EI_PROPERTY_PERM_ALL;
/* Initial ref is owned by this list */
list_append(&ei->properties, &prop->link);
return prop;
}
static int
ei_property_delete(struct ei *ei, struct ei_property *prop)
{
if (!PERM_RD(prop))
return -EACCES;
free(prop->value);
prop->value = NULL;
ei_property_unref(prop);
return 0;
}
static int
ei_property_change(struct ei *ei, struct ei_property *prop,
const char *value, uint32_t permissions)
{
if (!PERM_RW(prop))
return -EACCES;
if (prop->value != value) {
free(prop->value);
prop->value = xstrdup(value);
}
prop->permissions = permissions;
return 0;
}
int
ei_property_update(struct ei *ei, const char *name,
const char *value, uint32_t permissions)
{
struct ei_property *prop = ei_find_property(ei, name);
int rc = 0;
if (!prop) {
ei_property_new(ei, name, value, permissions);
} else if (value) {
rc = ei_property_change(ei, prop, value, permissions);
} else {
rc = ei_property_delete(ei, prop);
}
return rc;
}
_public_ int
ei_property_set_with_permissions(struct ei *ei, const char *name, const char *value,
uint32_t permissions)
{
if (strstartswith(name, "ei.") || strstartswith(name, "eis."))
return -EACCES;
struct ei_property *prop = ei_find_property(ei, name);
if (prop && permissions && (prop->permissions | permissions) != prop->permissions)
return -EPERM;
int rc = ei_property_update(ei, name, value, permissions);
if (rc == 0)
ei_send_property(ei, name, value, permissions);
return rc;
}
_public_ int
ei_property_set(struct ei *ei, const char *name, const char *value)
{
uint32_t permissions = EI_PROPERTY_PERM_ALL;
struct ei_property *prop = ei_find_property(ei, name);
if (prop)
permissions = prop->permissions;
return ei_property_set_with_permissions(ei, name, value, permissions);
}
_public_ uint32_t
ei_property_get_permissions(struct ei *ei, const char *name)
{
struct ei_property *prop = ei_find_property(ei, name);
return (prop && PERM_R(prop)) ? prop->permissions : 0;
}
_public_ const char *
ei_property_get(struct ei *ei, const char *name)
{
struct ei_property *prop = ei_find_property(ei, name);
return (prop && PERM_R(prop)) ? prop->value : NULL;
}

View file

@ -125,6 +125,12 @@ ei_proto_handle_message(struct ei *ei,
rc = call(device_paused, ei,
proto->device_paused->deviceid);
break;
case SERVER_MESSAGE__MSG_PROPERTY:
rc = call(property, ei,
proto->property->name,
proto->property->value[0] ? proto->property->value : NULL,
proto->property->permissions);
break;
default:
rc = -EBADMSG;
break;
@ -167,6 +173,7 @@ log_wire_message(struct ei *ei, const ClientMessage *msg, int error)
MSG_STRING_CASE(CONFIGURE_NAME);
MSG_STRING_CASE(CONFIGURE_CAPS);
MSG_STRING_CASE(FRAME);
MSG_STRING_CASE(PROPERTY);
}
if (message == NULL)
assert(!"Unimplemented message type");
@ -443,6 +450,18 @@ ei_proto_send_frame(struct ei_device *device)
return ei_proto_send_msg(ei_device_get_context(device), &msg);
}
static int
ei_proto_send_property(struct ei *ei, const char *name, const char *value, uint32_t permissions)
{
prepare_msg(PROPERTY, Property, property);
property.name = (char*)name;
property.value = value ? (char*)value : "";
property.permissions = permissions;
return ei_proto_send_msg(ei, &msg);
}
static const struct ei_proto_requests requests = {
.connect = ei_proto_send_connect,
.connect_done = ei_proto_send_connect_done,
@ -464,6 +483,7 @@ static const struct ei_proto_requests requests = {
.touch_motion = ei_proto_send_touch_motion,
.touch_up = ei_proto_send_touch_up,
.frame = ei_proto_send_frame,
.property = ei_proto_send_property,
};
const struct ei_proto_requests *

View file

@ -54,6 +54,9 @@ struct ei_proto_interface {
int (*keyboard_modifiers)(struct ei *ei, uint32_t deviceid,
uint32_t depressed, uint32_t latched,
uint32_t locked, uint32_t group);
int (*property)(struct ei *ei,
const char *name, const char *value,
uint32_t permissions);
};
struct ei_proto_requests {
@ -80,6 +83,9 @@ struct ei_proto_requests {
uint32_t tid, double x, double y);
int (*touch_up)(struct ei_device *device, uint32_t tid);
int (*frame)(struct ei_device *device);
int (*property)(struct ei *ei,
const char *name, const char *value,
uint32_t permissions);
};
int

View file

@ -24,10 +24,12 @@
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "util-io.h"
#include "util-macros.h"
#include "util-object.h"
@ -80,6 +82,11 @@ ei_destroy(struct ei *ei)
ei->backend = NULL;
sink_unref(ei->sink);
free(ei->name);
struct ei_property *prop;
list_for_each_safe(prop, &ei->properties, link) {
ei_property_unref(prop);
}
}
static
@ -96,6 +103,33 @@ OBJECT_IMPLEMENT_GETTER(ei, user_data, void *);
DEFINE_UNREF_CLEANUP_FUNC(ei_device);
DEFINE_UNREF_CLEANUP_FUNC(ei_region);
static void
set_prop_cmdline(struct ei *ei)
{
char path[PATH_MAX];
xsnprintf(path, sizeof(path), "/proc/%u/cmdline", getpid());
_cleanup_close_ int fd = open(path, O_RDONLY);
if (fd < 0)
return;
int len;
if ((len = read(fd, path, sizeof(path) - 1)) < 0)
return;
path[len] = '\0';
ei_property_update(ei, "ei.application.cmdline", path, EI_PROPERTY_PERM_NONE);
}
static void
set_prop_pid(struct ei *ei)
{
char pid[64];
xsnprintf(pid, sizeof(pid), "%u", getpid());
ei_property_update(ei, "ei.application.pid", pid, EI_PROPERTY_PERM_NONE);
}
_public_ struct ei *
ei_new(void *user_data)
{
@ -105,6 +139,7 @@ ei_new(void *user_data)
ei->requests = ei_proto_get_requests();
list_init(&ei->event_queue);
list_init(&ei->seats);
list_init(&ei->properties);
ei_log_set_handler(ei, NULL);
ei_log_set_priority(ei, EI_LOG_PRIORITY_INFO);
@ -115,6 +150,9 @@ ei_new(void *user_data)
ei->user_data = user_data;
ei->backend = NULL;
set_prop_pid(ei);
set_prop_cmdline(ei);
return steal(&ei);
}
@ -277,6 +315,19 @@ queue_keyboard_modifiers_event(struct ei_device *device,
queue_event(ei, e);
}
static void
queue_property_event(struct ei *ei, const char *name,
const char *value, uint32_t permissions)
{
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_PROPERTY;
e->prop.name = xstrdup(name);
e->prop.value = xstrdup(value);
e->prop.permissions = permissions;
queue_event(ei, e);
}
void
ei_disconnect(struct ei *ei)
{
@ -465,6 +516,17 @@ handle_msg_keyboard_modifiers(struct ei *ei, uint32_t deviceid,
return 0;
}
static int
handle_msg_property(struct ei *ei, const char *name, const char *value,
uint32_t permissions)
{
int rc = ei_property_update(ei, name, value, permissions);
if (rc == 0)
queue_property_event(ei, name, value, permissions);
return 0;
}
static int
handle_msg_device_removed(struct ei *ei, uint32_t deviceid)
{
@ -507,6 +569,19 @@ handle_msg_device_paused(struct ei *ei, uint32_t deviceid)
return 0;
}
int
ei_send_property(struct ei *ei, const char *name, const char *value, uint32_t permissions)
{
/* properties before CONNECTED_DONE are handled in a custom way */
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->property(ei, name, value, permissions);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_close_device(struct ei_device *device)
{
@ -798,6 +873,7 @@ static const struct ei_proto_interface intf_state_connected = {
.device_keymap = handle_msg_device_keymap,
.device_done = handle_msg_device_added_done,
.keyboard_modifiers = handle_msg_keyboard_modifiers,
.property = handle_msg_property,
};
static const struct ei_proto_interface *interfaces[] = {
@ -858,6 +934,13 @@ ei_set_connection(struct ei *ei, int fd)
ei->source = source_ref(source);
ei->state = EI_STATE_BACKEND;
rc = ei->requests->connect(ei);
struct ei_property *prop;
list_for_each_safe(prop, &ei->properties, link) {
if (rc == 0)
rc = ei->requests->property(ei, prop->name, prop->value, prop->permissions);
}
if (rc == 0) {
rc = ei->requests->connect_done(ei);
}

View file

@ -142,6 +142,22 @@ enum ei_device_capability {
EI_DEVICE_CAP_TOUCH,
};
/**
* @enum ei_property_permission
*
* A set of masks for operations permitted on properties. Note that property
* permissions only affect the libei client, the server has full access to the
* properties at any time.
*/
enum ei_property_permission {
EI_PROPERTY_PERM_NONE = 0,
EI_PROPERTY_PERM_READ = (1 << 0),
EI_PROPERTY_PERM_WRITE = (1 << 1),
EI_PROPERTY_PERM_DELETE = (1 << 2),
EI_PROPERTY_PERM_ALL = (EI_PROPERTY_PERM_READ|EI_PROPERTY_PERM_WRITE|EI_PROPERTY_PERM_DELETE),
};
/**
* @enum ei_keymap_type
*
@ -179,6 +195,17 @@ enum ei_event_type {
*/
EI_EVENT_DISCONNECT,
/**
* The server has added, removed, or changed a property.
*
* This event is not generated for properties without the @ref
* EI_PROPERTY_PERM_READ set.
*
* libei guarantees that a @ref EI_EVENT_DISCONNECT is provided to
* the caller even where the server does not send one.
*/
EI_EVENT_PROPERTY,
/**
* The server has added a seat available to this client.
*
@ -329,6 +356,59 @@ ei_log_get_priority(const struct ei *ei);
void
ei_configure_name(struct ei * ei, const char *name);
/**
* See ei_property_set_with_permissions(), but the permissions are
* left as-is. If the property does not exist, it is created with permissions
* @ref EI_PROPERTY_PERM_ALL.
*/
int
ei_property_set(struct ei *ei, const char *property, const char *value);
/**
* Change, create or delete a property.
*
* - If the property does not yet exist, it is created with the given @ref
* ei_property_permission.
* - If the property exists and @a value is not NULL and the @ref
* EI_PROPERTY_WRITE permission is set on the property, the value is changed
* to the new value and the property's permissions are updated to the given
* permission.
* - If the property exists and @a value is NULL and the @ref
* EI_PROPERTY_WRITE and @ref EI_PROPERTY_DELETE permission is set on the
* property, the property is deleted.
*
* Note that property permissions only apply to the libei client, they do not
* apply to the EIS server which can read, modify and delete any property
* regardless of permissions.
*
* Permissions can only be reduced, not increased and the permissions applied
* to the property are always the binary AND of @a permissions and the
* existing permissions for this property (if any).
*
* @return 0 on success or a negative errno on failure
* @retval -EACCESS The client does not have sufficient permissions for this
* operation
* @retval -EPERM The permissions mask is too loose
*/
int
ei_property_set_with_permissions(struct ei *ei, const char *property, const char *value,
uint32_t permission);
/**
* Return the permissions mask for the given property. If the property does
* not exist or does not have the @ref EI_PROPERTY_READ permission,
* this function returns zero.
*/
uint32_t
ei_property_get_permissions(struct ei *ei, const char *property);
/**
* Return the value of the given property or NULL if the property is not set
* or does not have the @ref EI_PROPERTY_READ permission.
*/
const char *
ei_property_get(struct ei *ei, const char *property);
/**
* Set this ei context to use the socket backend. The ei context will
* connect to the socket at the given path and initiate the conversation
@ -1169,6 +1249,39 @@ ei_touch_get_device(struct ei_touch *touch);
struct ei_seat *
ei_event_get_seat(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_PROPERTY, get the property name
* that has changed.
*/
const char *
ei_event_property_get_name(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_PROPERTY, get the new property
* value (at the time of the event).
*
* A property value of NULL indicates the property has been deleted.
*
* Note that the property value as seen in the event and the property value on
* the ei context itself may differ. The latter always reflects the current
* state of the property while the event contains the value of the property
* at the time of the event.
*/
const char *
ei_event_property_get_value(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_PROPERTY, get the permissions of the
* property (at the time of the event).
*
* Note that the property permissions as seen in the event and the property
* permissiosn on the ei context itself may differ. The latter always reflects
* the current state of the property while the event contains the permissions of
* the property at the time of the event.
*/
uint32_t
ei_event_property_get_permissions(struct ei_event *event);
/**
* @}
*/

View file

@ -47,6 +47,11 @@ eis_client_destroy(struct eis_client *client)
source_remove(client->source);
source_unref(client->source);
list_remove(&client->link);
struct eis_property *prop;
list_for_each_safe(prop, &client->properties, link) {
eis_property_unref(prop);
}
}
static
@ -549,8 +554,36 @@ client_msg_disconnect(struct eis_client *client)
return -ECANCELED;
}
static int
client_msg_property(struct eis_client *client,
const char *name, const char *value,
uint32_t permissions)
{
struct eis_property *prop = eis_property_find(client, name);
/* The client cannot increase property permissions */
if (prop)
permissions = prop->permissions & permissions;
eis_property_update(client, name, value, permissions);
return 0;
}
static int
client_msg_property_with_event(struct eis_client *client,
const char *name, const char *value,
uint32_t permissions)
{
int rc = client_msg_property(client, name, value, permissions);
if (rc == 0)
eis_queue_property_event(client, name, value, permissions);
return rc;
}
static const struct eis_proto_interface intf_state_new = {
.connect = client_msg_connect,
.property = client_msg_property,
.connect_done = client_msg_connect_done,
.disconnect = client_msg_disconnect,
.configure_name = client_msg_configure_name,
@ -566,6 +599,7 @@ static const struct eis_proto_interface intf_state_connecting = {
static const struct eis_proto_interface intf_state_connected = {
.disconnect = client_msg_disconnect,
.property = client_msg_property_with_event,
.bind_seat = client_msg_bind_seat,
.unbind_seat = client_msg_unbind_seat,
.close_device = client_msg_close_device,
@ -676,6 +710,7 @@ eis_client_new(struct eis *eis, int fd)
client->id = ++client_id;
list_init(&client->seats);
list_init(&client->seats_pending);
list_init(&client->properties);
struct source *s = source_new(fd, client_dispatch, client);
int rc = sink_add_source(eis->sink, s);

View file

@ -58,6 +58,11 @@ eis_event_destroy(struct eis_event *event)
case EIS_EVENT_FRAME:
handled = true;
break;
case EIS_EVENT_CLIENT_PROPERTY:
handled = true;
free(event->prop.name);
free(event->prop.value);
break;
}
if (!handled)
@ -367,3 +372,31 @@ eis_event_touch_get_y(struct eis_event *event)
return event->touch.y;
}
_public_ const char *
eis_event_property_get_name(struct eis_event *event)
{
require_event_type(event, NULL,
EIS_EVENT_CLIENT_PROPERTY);
return event->prop.name;
}
_public_ const char *
eis_event_property_get_value(struct eis_event *event)
{
require_event_type(event, NULL,
EIS_EVENT_CLIENT_PROPERTY);
return event->prop.value;
}
_public_ uint32_t
eis_event_property_get_permissions(struct eis_event *event)
{
require_event_type(event, 0,
EIS_EVENT_CLIENT_PROPERTY);
return event->prop.permissions;
}

View file

@ -71,9 +71,12 @@ struct eis_client {
enum eis_client_state state;
char *name;
struct list seats;
struct list seats_pending;
struct list properties;
struct {
enum {
CLIENT_CAP_POLICY_ALLOW,
@ -170,6 +173,11 @@ struct eis_event {
uint32_t touchid;
double x, y;
} touch;
struct {
char *name;
char *value;
uint32_t permissions;
} prop;
};
};
@ -190,6 +198,24 @@ struct eis_xkb_modifiers {
uint32_t group;
};
struct eis_property {
struct object object;
struct list link;
char *name;
char *value;
uint32_t permissions;
};
struct eis_property *
eis_property_unref(struct eis_property *prop);
void
eis_property_update(struct eis_client *client, const char *name,
const char *value, uint32_t permissions);
struct eis_property *
eis_property_find(struct eis_client *client, const char *name);
void
eis_init_object(struct eis *eis, struct object *parent);
@ -313,6 +339,10 @@ eis_queue_connect_event(struct eis_client *client);
void
eis_queue_disconnect_event(struct eis_client *client);
void
eis_queue_property_event(struct eis_client *client, const char *name,
const char *value, uint32_t permissions);
void
eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities);

150
src/libeis-property.c Normal file
View file

@ -0,0 +1,150 @@
/*
* Copyright © 2021 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-strings.h"
#include "util-object.h"
#include "libeis-private.h"
#include "libeis-proto.h"
static void
eis_property_destroy(struct eis_property *prop)
{
list_remove(&prop->link);
free(prop->name);
free(prop->value);
}
static
OBJECT_IMPLEMENT_CREATE(eis_property);
OBJECT_IMPLEMENT_UNREF(eis_property);
struct eis_property *
eis_property_find(struct eis_client *client, const char *name)
{
struct eis_property *prop;
list_for_each(prop, &client->properties, link) {
if (streq(prop->name, name)) {
return prop;
}
}
return NULL;
}
static struct eis_property *
eis_client_property_new(struct eis_client *client, const char *name,
const char *value, uint32_t permissions)
{
assert(name);
assert(value);
struct eis_property *prop = eis_property_create(NULL);
prop->name = xstrdup(name);
prop->value = xstrdup(value);
prop->permissions = permissions & EIS_PROPERTY_PERM_ALL;
/* initial ref is owned by this list */
list_append(&client->properties, &prop->link);
return prop;
}
static void
eis_property_delete(struct eis_client *client, struct eis_property *prop)
{
eis_property_unref(prop);
}
static void
eis_property_change(struct eis_client *client, struct eis_property *prop,
const char *value, uint32_t permissions)
{
if (prop->value != value) {
free(prop->value);
prop->value = xstrdup(value);
}
prop->permissions = permissions;
}
void
eis_property_update(struct eis_client *client, const char *name,
const char *value, uint32_t permissions)
{
struct eis_property *prop = eis_property_find(client, name);
if (!prop) {
eis_client_property_new(client, name, value, permissions);
} else if (value) {
eis_property_change(client, prop, value, permissions);
} else {
eis_property_delete(client, prop);
}
}
_public_ void
eis_client_property_set_with_permissions(struct eis_client *client, const char *name,
const char *value, uint32_t permissions)
{
if (strstartswith(name, "ei.") || strstartswith(name, "eis."))
return;
eis_property_update(client, name, value, permissions);
/* We should have a check to see if anything actually changed, but meh */
struct eis *eis = eis_client_get_context(client);
eis->requests->property(client, name, value, permissions);
}
_public_ void
eis_client_property_set(struct eis_client *client, const char *name, const char *value)
{
uint32_t permissions = EIS_PROPERTY_PERM_ALL;
struct eis_property *prop = eis_property_find(client, name);
if (prop)
permissions = prop->permissions;
return eis_client_property_set_with_permissions(client, name, value, permissions);
}
_public_ uint32_t
eis_client_property_get_permissions(struct eis_client *client, const char *name)
{
struct eis_property *prop = eis_property_find(client, name);
return prop ? prop->permissions : 0;
}
_public_ const char *
eis_client_property_get(struct eis_client *client, const char *name)
{
struct eis_property *prop = eis_property_find(client, name);
return prop ? prop->value : NULL;
}

View file

@ -57,6 +57,7 @@ log_wire_message(struct eis *eis, const ServerMessage *msg)
MSG_STRING_CASE(DEVICE_RESUMED);
MSG_STRING_CASE(DEVICE_PAUSED);
MSG_STRING_CASE(KEYBOARD_MODIFIERS);
MSG_STRING_CASE(PROPERTY);
break;
default:
assert(!"Unimplemented message type");
@ -248,6 +249,20 @@ eis_proto_send_device_resumed(struct eis_device *device)
return eis_proto_send_msg(eis_device_get_client(device), &msg);
}
static int
eis_proto_send_property(struct eis_client *client, const char *name,
const char *value, uint32_t permissions)
{
prepare_msg(PROPERTY, Property, property);
property.name = (char*)name;
property.value = (char*)value;
property.permissions = permissions;
return eis_proto_send_msg(client, &msg);
}
static const struct eis_proto_requests requests = {
.disconnected = eis_proto_send_disconnected,
.connected = eis_proto_send_connected,
@ -261,6 +276,7 @@ static const struct eis_proto_requests requests = {
.device_done = eis_proto_send_device_done,
.device_region = eis_proto_send_device_region,
.keyboard_modifiers = eis_proto_send_keyboard_modifiers,
.property = eis_proto_send_property,
};
const struct eis_proto_requests *
@ -398,6 +414,11 @@ eis_proto_handle_message(struct eis_client *client,
case CLIENT_MESSAGE__MSG_FRAME:
rc = call(frame, client, proto->frame->deviceid);
break;
case CLIENT_MESSAGE__MSG_PROPERTY:
rc = call(property, client, proto->property->name,
proto->property->value[0] ? proto->property->value : NULL,
proto->property->permissions);
break;
default:
rc = -EBADMSG;
break;

View file

@ -60,6 +60,8 @@ struct eis_proto_interface {
bool policy_is_allow,
uint32_t allow, uint32_t deny);
int (*frame) (struct eis_client *client, uint32_t deviceid);
int (*property)(struct eis_client *client, const char *name,
const char *value, uint32_t permissions);
};
struct eis_proto_requests {
@ -77,6 +79,8 @@ struct eis_proto_requests {
const struct eis_region *region);
int (*keyboard_modifiers)(struct eis_device *device,
const struct eis_xkb_modifiers *mods);
int (*property)(struct eis_client *client, const char *name,
const char *value, uint32_t permissions);
};
int

View file

@ -105,6 +105,7 @@ eis_event_type_to_string(enum eis_event_type type)
switch(type) {
CASE_RETURN_STRING(EIS_EVENT_CLIENT_CONNECT);
CASE_RETURN_STRING(EIS_EVENT_CLIENT_DISCONNECT);
CASE_RETURN_STRING(EIS_EVENT_CLIENT_PROPERTY);
CASE_RETURN_STRING(EIS_EVENT_SEAT_BIND);
CASE_RETURN_STRING(EIS_EVENT_SEAT_UNBIND);
CASE_RETURN_STRING(EIS_EVENT_DEVICE_CLOSED);
@ -154,6 +155,18 @@ eis_queue_disconnect_event(struct eis_client *client)
eis_queue_event(e);
}
void
eis_queue_property_event(struct eis_client *client, const char *name, const char *value,
uint32_t permissions)
{
struct eis_event *e = eis_event_new_for_client(client);
e->type = EIS_EVENT_CLIENT_PROPERTY;
e->prop.name = xstrdup(name);
e->prop.value = xstrdup(value);
e->prop.permissions = permissions;
eis_queue_event(e);
}
void
eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities)
{

View file

@ -82,6 +82,22 @@ enum eis_device_capability {
EIS_DEVICE_CAP_TOUCH,
};
/**
* @enum eis_property_permission
*
* A set of masks for operations permitted on properties. Note that property
* permissions only affect the libei client, the server has full access to the
* properties at any time.
*/
enum eis_property_permission {
EIS_PROPERTY_PERM_NONE = 0,
EIS_PROPERTY_PERM_READ = (1 << 0),
EIS_PROPERTY_PERM_WRITE = (1 << 1),
EIS_PROPERTY_PERM_DELETE = (1 << 2),
EIS_PROPERTY_PERM_ALL = (EIS_PROPERTY_PERM_READ|EIS_PROPERTY_PERM_WRITE|EIS_PROPERTY_PERM_DELETE),
};
enum eis_keymap_type {
EIS_KEYMAP_TYPE_XKB = 1,
};
@ -100,6 +116,11 @@ enum eis_event_type {
*/
EIS_EVENT_CLIENT_DISCONNECT,
/**
* A client property has been added, changed, or deleted.
*/
EIS_EVENT_CLIENT_PROPERTY,
/**
* The client wants to bind to a seat. Devices associated with this
* seat should be sent to the client.
@ -220,6 +241,56 @@ eis_get_user_data(struct eis *eis);
void
eis_set_user_data(struct eis *eis, void *user_data);
/**
* See eis_client_property_set_with_permissions(), but the permissions are
* left as-is. If the property does not exist, it is created with permissions
* @ref EIS_PROPERTY_PERM_ALL.
*/
void
eis_client_property_set(struct eis_client *client, const char *property, const char *value);
/**
* Change, create or delete a property.
*
* - If the property does not yet exist, it is created with the given @ref
* eis_property_permission.
* - If the property exists and @a value is not NULL, the value is changed to
* the new value.
* - If the property exists and @a value is NULL, the property is deleted.
*
* If the property is not deleted, the permissions are updated to the given @a
* permissions.
*
* Properties should use a name in the form "namespace.name" or
* "namespace.name.subname". Two namespaces are reserved for use by this
* library: "eis.*" and "ei.*". Attempting to change a property within that
* namespace will be silently ignored.
*/
void
eis_client_property_set_with_permissions(struct eis_client *client,
const char *property, const char *value,
uint32_t permission);
/**
* Return the permissions mask for the given property. If the property does
* not exist, this function returns zero.
*/
uint32_t
eis_client_property_get_permissions(struct eis_client *client, const char *propert);
/**
* Return the value of the given property or NULL if the property is not set.
*
* The following property names are automatically set by libei:
* - "ei.application.pid" - the PID of the libei process
* - "ei.application.cmdline" - The first element of /proc/<pid>/cmdline of
* the libei process
*
*/
const char *
eis_client_property_get(struct eis_client *client, const char *property);
/**
* Initialize the context with a UNIX socket name.
* If the path does not start with / it is relative to $XDG_RUNTIME_DIR.
@ -416,6 +487,29 @@ eis_event_get_type(struct eis_event *event);
struct eis_client *
eis_event_get_client(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_CLIENT_PROPERTY, get the property name
* that has changed.
*/
const char *
eis_event_property_get_name(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_CLIENT_PROPERTY, get the new property
* value (at the time of the event).
*
* A property value of NULL indicates the property has been deleted.
*/
const char *
eis_event_property_get_value(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_CLIENT_PROPERTY, get the permissions
* of the property (at the time of the event).
*/
uint32_t
eis_event_property_get_permissions(struct eis_event *event);
struct eis_seat *
eis_event_get_seat(struct eis_event *event);

View file

@ -1012,6 +1012,7 @@ peck_ei_event_type_name(enum ei_event_type type)
CASE_STRING(DEVICE_PAUSED);
CASE_STRING(DEVICE_RESUMED);
CASE_STRING(KEYBOARD_MODIFIERS);
CASE_STRING(PROPERTY);
}
#undef CASE_STRING
assert(!"Unhandled ei event type");
@ -1032,6 +1033,7 @@ peck_eis_event_type_name(enum eis_event_type type)
switch (type) {
CASE_STRING(CLIENT_CONNECT);
CASE_STRING(CLIENT_DISCONNECT);
CASE_STRING(CLIENT_PROPERTY);
CASE_STRING(SEAT_BIND);
CASE_STRING(SEAT_UNBIND);
CASE_STRING(DEVICE_CLOSED);

View file

@ -23,7 +23,11 @@
#include "config.h"
#include <unistd.h>
#include <fcntl.h>
#include "util-munit.h"
#include "util-strings.h"
#include "eierpecken.h"
DEFINE_UNREF_CLEANUP_FUNC(peck);
@ -45,6 +49,422 @@ MUNIT_TEST(test_ei_ref_unref)
return MUNIT_OK;
}
MUNIT_TEST(test_ei_initial_properties)
{
_unref_(peck) *peck = peck_new();
char pid[64];
char cmdline[PATH_MAX];
xsnprintf(pid, sizeof(pid), "%u", getpid());
xsnprintf(cmdline, sizeof(cmdline), "/proc/%u/cmdline", getpid());
int fd = open(cmdline, O_RDONLY);
munit_assert_int(fd, >=, 0);
int len = read(fd, cmdline, sizeof(cmdline) - 1);
munit_assert_int(len, >=, 0);
cmdline[len] = '\0';
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
peck_dispatch_until_stable(peck);
with_server(peck) {
_unref_(eis_event) *connect =
peck_eis_next_event(eis, EIS_EVENT_CLIENT_CONNECT);
struct eis_client *client = eis_event_get_client(connect);
const char *pidprop = eis_client_property_get(client, "ei.application.pid");
munit_assert_ptr_not_null(pidprop);
munit_assert_string_equal(pidprop, pid);
const char *cmdprop = eis_client_property_get(client, "ei.application.cmdline");
munit_assert_ptr_not_null(cmdprop);
munit_assert_string_equal(cmdprop, cmdline);
}
return MUNIT_OK;
}
MUNIT_TEST(test_ei_properties)
{
_unref_(peck) *peck = peck_new();
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_dispatch_until_stable(peck);
with_client(peck) {
ei_property_set(ei, "foo.bar", "value");
}
peck_dispatch_until_stable(peck);
with_server(peck) {
_unref_(eis_event) *e =
peck_eis_next_event(eis, EIS_EVENT_CLIENT_PROPERTY);
const char *name = eis_event_property_get_name(e);
const char *value = eis_event_property_get_value(e);
munit_assert_string_equal(name, "foo.bar");
munit_assert_string_equal(value, "value");
}
with_client(peck) {
int rc = ei_property_set(ei, "foo.bar", "newval");
munit_assert_int(rc, ==, 0);
}
peck_dispatch_until_stable(peck);
with_server(peck) {
_unref_(eis_event) *e =
peck_eis_next_event(eis, EIS_EVENT_CLIENT_PROPERTY);
const char *name = eis_event_property_get_name(e);
const char *value = eis_event_property_get_value(e);
munit_assert_string_equal(name, "foo.bar");
munit_assert_string_equal(value, "newval");
}
with_client(peck) {
int rc = ei_property_set(ei, "foo.bar", NULL);
munit_assert_int(rc, ==, 0);
}
peck_dispatch_until_stable(peck);
with_server(peck) {
_unref_(eis_event) *e =
peck_eis_next_event(eis, EIS_EVENT_CLIENT_PROPERTY);
const char *name = eis_event_property_get_name(e);
const char *value = eis_event_property_get_value(e);
munit_assert_string_equal(name, "foo.bar");
munit_assert_ptr_null(value);
}
return MUNIT_OK;
}
MUNIT_TEST(test_ei_properties_reserved)
{
_unref_(peck) *peck = peck_new();
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_dispatch_until_stable(peck);
with_client(peck) {
int rc;
rc = ei_property_set(ei, "ei.bar", "value");
munit_assert_int(rc, ==, -EACCES);
rc = ei_property_set(ei, "eis.bar", "value");
munit_assert_int(rc, ==, -EACCES);
}
peck_dispatch_until_stable(peck);
with_server(peck) {
peck_assert_no_eis_events(eis);
}
peck_dispatch_until_stable(peck);
return MUNIT_OK;
}
MUNIT_TEST(test_ei_properties_permissions)
{
_unref_(peck) *peck = peck_new();
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_dispatch_until_stable(peck);
with_client(peck) {
ei_property_set_with_permissions(ei, "cannot.delete", "v1",
EI_PROPERTY_PERM_READ|EI_PROPERTY_PERM_WRITE);
ei_property_set_with_permissions(ei, "cannot.write", "v2",
EI_PROPERTY_PERM_READ|EI_PROPERTY_PERM_DELETE);
ei_property_set_with_permissions(ei, "cannot.read", "v3",
EI_PROPERTY_PERM_DELETE|EI_PROPERTY_PERM_WRITE);
const char *value;
int rc;
/* can't delete but we can write to it */
value = ei_property_get(ei, "cannot.delete");
munit_assert_string_equal(value, "v1");
rc = ei_property_set(ei, "cannot.delete", NULL);
munit_assert_int(rc, ==, -EACCES);
value = ei_property_get(ei, "cannot.delete");
munit_assert_string_equal(value, "v1");
rc = ei_property_set(ei, "cannot.delete", "newv1");
munit_assert_int(rc, ==, 0);
value = ei_property_get(ei, "cannot.delete");
munit_assert_string_equal(value, "newv1");
/* can't modify it but we can delete it */
value = ei_property_get(ei, "cannot.write");
munit_assert_string_equal(value, "v2");
rc = ei_property_set(ei, "cannot.write", "newv2");
munit_assert_int(rc, ==, -EACCES);
value = ei_property_get(ei, "cannot.write");
munit_assert_string_equal(value, "v2");
rc = ei_property_set(ei, "cannot.write", NULL);
munit_assert_int(rc, ==, 0);
value = ei_property_get(ei, "cannot.write");
munit_assert_ptr_equal(value, NULL);
value = ei_property_get(ei, "cannot.read");
munit_assert_ptr_equal(value, NULL);
rc = ei_property_set(ei, "cannot.read", "newv3");
munit_assert_int(rc, ==, -EACCES);
value = ei_property_get(ei, "cannot.read");
munit_assert_ptr_equal(value, NULL);
rc = ei_property_set(ei, "cannot.read", NULL);
munit_assert_int(rc, ==, -EACCES);
}
return MUNIT_OK;
}
MUNIT_TEST(test_ei_properties_permissions_drop)
{
_unref_(peck) *peck = peck_new();
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_dispatch_until_stable(peck);
with_client(peck) {
int rc;
ei_property_set_with_permissions(ei, "test.perms", "v1",
EI_PROPERTY_PERM_READ|EI_PROPERTY_PERM_WRITE|EI_PROPERTY_PERM_DELETE);
/* downgrading permissions, can't delete anymore */
rc = ei_property_set_with_permissions(ei, "test.perms", "v1",
EI_PROPERTY_PERM_READ|EI_PROPERTY_PERM_WRITE);
munit_assert_int(rc, ==, 0);
rc = ei_property_set(ei, "test.perms", NULL);
munit_assert_int(rc, ==, -EACCES);
/* upgrading permissions should fail */
rc = ei_property_set_with_permissions(ei, "test.perms", "v1",
EI_PROPERTY_PERM_READ|EI_PROPERTY_PERM_WRITE|EI_PROPERTY_PERM_DELETE);
munit_assert_int(rc, ==, -EPERM);
/* Drop the write permission */
rc = ei_property_set_with_permissions(ei, "test.perms", "v1",
EI_PROPERTY_PERM_READ);
munit_assert_int(rc, ==, 0);
rc = ei_property_set(ei, "test.perms", "new value");
munit_assert_int(rc, ==, -EACCES);
/* We need write permissions to change the permissions */
rc = ei_property_set_with_permissions(ei, "test.perms", "v1",
EI_PROPERTY_PERM_NONE);
munit_assert_int(rc, ==, -EACCES);
}
return MUNIT_OK;
}
MUNIT_TEST(test_ei_properties_events)
{
_unref_(peck) *peck = peck_new();
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_dispatch_until_stable(peck);
with_client(peck) {
ei_property_set(ei, "test.events", "v1");
ei_property_set(ei, "test.events", "v2");
}
peck_dispatch_until_stable(peck);
with_server(peck) {
_unref_(eis_event) *e1 =
peck_eis_next_event(eis, EIS_EVENT_CLIENT_PROPERTY);
const char *name = eis_event_property_get_name(e1);
const char *value = eis_event_property_get_value(e1);
munit_assert_string_equal(name, "test.events");
munit_assert_string_equal(value, "v1");
struct eis_client *client = eis_event_get_client(e1);
const char *propval = eis_client_property_get(client, "test.events");
/*
* This probably needs fixing: because we processed the second
* event already, the actual property value is different to
* our current event value.
*/
munit_assert_string_equal(propval, "v2");
_unref_(eis_event) *e2 =
peck_eis_next_event(eis, EIS_EVENT_CLIENT_PROPERTY);
name = eis_event_property_get_name(e2);
value = eis_event_property_get_value(e2);
munit_assert_string_equal(name, "test.events");
munit_assert_string_equal(value, "v2");
}
/* delete it */
with_client(peck) {
ei_property_set(ei, "test.events", "v3");
ei_property_set(ei, "test.events", NULL);
}
peck_dispatch_until_stable(peck);
with_server(peck) {
_unref_(eis_event) *e3 =
peck_eis_next_event(eis, EIS_EVENT_CLIENT_PROPERTY);
const char *name = eis_event_property_get_name(e3);
const char *value = eis_event_property_get_value(e3);
munit_assert_string_equal(name, "test.events");
munit_assert_string_equal(value, "v3");
struct eis_client *client = eis_event_get_client(e3);
const char *propval = eis_client_property_get(client, "test.events");
/*
* This probably needs fixing: because we processed the second
* event already, the actual property value is different to
* our current event value.
*/
munit_assert_ptr_null(propval);
_unref_(eis_event) *e4 =
peck_eis_next_event(eis, EIS_EVENT_CLIENT_PROPERTY);
name = eis_event_property_get_name(e4);
value = eis_event_property_get_value(e4);
munit_assert_string_equal(name, "test.events");
munit_assert_ptr_null(value);
}
return MUNIT_OK;
}
MUNIT_TEST(test_ei_properties_events_server)
{
_unref_(peck) *peck = peck_new();
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_dispatch_until_stable(peck);
with_server(peck) {
struct eis_client *client = peck_eis_get_default_client(peck);
eis_client_property_set(client, "test.events", "v1");
eis_client_property_set(client, "test.events", "v2");
}
peck_dispatch_until_stable(peck);
with_client(peck) {
_unref_(ei_event) *e1 =
peck_ei_next_event(ei, EI_EVENT_PROPERTY);
const char *name = ei_event_property_get_name(e1);
const char *value = ei_event_property_get_value(e1);
munit_assert_string_equal(name, "test.events");
munit_assert_string_equal(value, "v1");
const char *propval = ei_property_get(ei, "test.events");
/*
* This probably needs fixing: because we processed the second
* event already, the actual property value is different to
* our current event value.
*/
munit_assert_string_equal(propval, "v2");
_unref_(ei_event) *e2 =
peck_ei_next_event(ei, EI_EVENT_PROPERTY);
name = ei_event_property_get_name(e2);
value = ei_event_property_get_value(e2);
munit_assert_string_equal(name, "test.events");
munit_assert_string_equal(value, "v2");
}
/* delete it */
with_server(peck) {
struct eis_client *client = peck_eis_get_default_client(peck);
eis_client_property_set(client, "test.events", "v3");
eis_client_property_set(client, "test.events", NULL);
}
peck_dispatch_until_stable(peck);
with_client(peck) {
_unref_(ei_event) *e3 =
peck_ei_next_event(ei, EI_EVENT_PROPERTY);
const char *name = ei_event_property_get_name(e3);
const char *value = ei_event_property_get_value(e3);
munit_assert_string_equal(name, "test.events");
munit_assert_string_equal(value, "v3");
const char *propval = ei_property_get(ei, "test.events");
/*
* This probably needs fixing: because we processed the second
* event already, the actual property value is different to
* our current event value.
*/
munit_assert_ptr_null(propval);
_unref_(ei_event) *e4 =
peck_ei_next_event(ei, EI_EVENT_PROPERTY);
name = ei_event_property_get_name(e4);
value = ei_event_property_get_value(e4);
munit_assert_string_equal(name, "test.events");
munit_assert_ptr_null(value);
}
return MUNIT_OK;
}
MUNIT_TEST(test_ei_properties_events_pingpong)
{
_unref_(peck) *peck = peck_new();
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_dispatch_until_stable(peck);
with_server(peck) {
struct eis_client *client = peck_eis_get_default_client(peck);
eis_client_property_set(client, "test.events", "ping");
}
peck_dispatch_until_stable(peck);
with_client(peck) {
_unref_(ei_event) *e =
peck_ei_next_event(ei, EI_EVENT_PROPERTY);
const char *name = ei_event_property_get_name(e);
const char *value = ei_event_property_get_value(e);
munit_assert_string_equal(name, "test.events");
munit_assert_string_equal(value, "ping");
const char *propval = ei_property_get(ei, "test.events");
munit_assert_string_equal(propval, "ping");
ei_property_set(ei, "test.events", "pong");
propval = ei_property_get(ei, "test.events");
munit_assert_string_equal(propval, "pong");
}
peck_dispatch_until_stable(peck);
with_server(peck) {
_unref_(eis_event) *e =
peck_eis_next_event(eis, EIS_EVENT_CLIENT_PROPERTY);
const char *name = eis_event_property_get_name(e);
const char *value = eis_event_property_get_value(e);
munit_assert_string_equal(name, "test.events");
munit_assert_string_equal(value, "pong");
struct eis_client *client = eis_event_get_client(e);
const char *propval = eis_client_property_get(client, "test.events");
munit_assert_string_equal(propval, "pong");
}
return MUNIT_OK;
}
MUNIT_TEST(test_ei_disconnect_immediately)
{
_unref_(peck) *peck = peck_new();