mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2026-02-04 14:10:27 +01:00
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:
parent
77c898c3b8
commit
1225bcb0e1
19 changed files with 1270 additions and 1 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
172
src/libei-property.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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 *
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
83
src/libei.c
83
src/libei.c
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
113
src/libei.h
113
src/libei.h
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
150
src/libeis-property.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
13
src/libeis.c
13
src/libeis.c
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
94
src/libeis.h
94
src/libeis.h
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
420
test/test-ei.c
420
test/test-ei.c
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue