Add a new text interface for sending keysyms and utf8 text

The text capability allows for two types of events on that interface:
- XKB keysym events, e.g. XK_ssharp (0x00df) with a press/release state
- UTF8 strings

Keysym events are useful for scenarious where the hardware keycode is
unsuitable due to potentially different key mappings on the client and
server side and/or the client just not wanting to worry about those
mappings. For example a client may want to send Ctrl+C instead of
what effectively is now keycodes for what may or may not be a C key.

UTF8 strings take this a step further and provide a full string (with
implementation-defined size limits to avoid OOM). Unlike e.g. the
wayland text input protocols the assumption is here that the
interaction required to generate that string has already been
performed before the final string is sent over the wire.

Closes #73
This commit is contained in:
Peter Hutterer 2025-08-14 16:12:35 +10:00
parent d55da9466c
commit 2cf2425102
32 changed files with 1232 additions and 8 deletions

View file

@ -781,6 +781,7 @@
- "ei_button"
- "ei_keyboard"
- "ei_touchscreen"
- "ei_text"
The interface version is equal or less to the client-supported
version in ei_handshake.interface_version for the respective interface.
@ -1584,4 +1585,109 @@
<arg name="touchid" type="uint32"/>
</event>
</interface>
<interface name="ei_text" version="1">
<description summary="text object">
Interface for text-based requests and events.
This interface is only provided once per device and where a client
requests ei_text.release the interface does not get re-initialized. An
EIS implementation may adjust the behavior of the device (including removing
the device) if the interface is released.
Note that for a client to receive objects of this type, it must announce
support for this interface in ei_handshake.interface_version.
</description>
<!-- ei_text client requests version 1 -->
<request name="release" since="1">
<description summary="text removal request">
Notification that the client is no longer interested in this text interface object.
The EIS implementation will release any resources related to this object and
send the ei_text.destroyed event once complete.
</description>
</request>
<request name="keysym" since="1" context-type="sender">
<description summary="Keysym state change request">
Generate an XKB key sym event.
It is a client bug to send more than one key request for the same keysym
within the same ei_device.frame and the EIS implementation
may ignore either or all keysym state changes and/or disconnect the client.
It is a protocol violation to send this request for a client
of an ei_handshake.context_type other than sender.
It is a protocol violation to send this request in the same frame
as a ei_keyboard.key.
</description>
<arg name="keysym" type="uint32" summary="the key sym"/>
<arg name="state" type="uint32" enum="ei_keyboard.key_state" summary="logical state of the keysym"/>
</request>
<request name="utf8" since="1" context-type="sender">
<description summary="UTF8 text">
Generate a UTF-8 compatible text string.
It is a protocol violation to send this request for a client
of an ei_handshake.context_type other than sender.
It is a protocol violation to send more than one utf8 request in the same
frame.
The order of ei_keyboard.key or ei_text.keysym and ei_text.utf8 if sent
within the same frame is undefined.
</description>
<arg name="text" type="string" summary="the utf-8 compatible text"/>
</request>
<!-- ei_text events version 1 -->
<event name="destroyed" type="destructor" since="1">
<description summary="Text interface object removal notification">
This text interface object has been removed and a client should release all
associated resources.
This ei_text object will be destroyed by the EIS implementation immediately after
after this event is sent and as such the client must not attempt to use
it after that point.
</description>
<arg name="serial" type="uint32" summary="this event's serial number"/>
</event>
<event name="keysym" since="1" context-type="receiver">
<description summary="Keysym state change event">
See the ei_text.keysym request for details.
It is a protocol violation to send this request for a client
of an ei_handshake.context_type other than receiver.
It is a protocol violation to send a keysym down event in the same
frame as a key up event for the same keysym in the same frame.
It is a protocol violation to send this event in the same frame
as a ei_keyboard.key event.
</description>
<arg name="keysym" type="uint32"/>
<arg name="state" type="uint32" enum="ei_keyboard.key_state"/>
</event>
<event name="utf8" since="1" context-type="receiver">
<description summary="UTF8 text event">
See the ei_text.utf8 request for details.
It is a protocol violation to send this request for a client
of an ei_handshake.context_type other than receiver.
It is a protocol violation to send more than one utf8 event in the same
frame.
The order of ei_keyboard.key, ei_text.keysym, and ei_text.utf8 if sent
within the same frame is undefined.
</description>
<arg name="text" type="string" summary="the utf-8 compatible text"/>
</event>
</interface>
</protocol>

View file

@ -90,6 +90,7 @@ ei_device_destroy(struct ei_device *device)
ei_button_unref(device->button);
ei_touchscreen_unref(device->touchscreen);
ei_keyboard_unref(device->keyboard);
ei_text_unref(device->text);
ei_seat_unref(seat);
free(device->name);
free(device->pending_region_mapping_id);
@ -255,13 +256,16 @@ handle_msg_done(struct ei_device *device)
mask_add(device->capabilities, EI_DEVICE_CAP_KEYBOARD);
if (device->touchscreen)
mask_add(device->capabilities, EI_DEVICE_CAP_TOUCH);
if (device->text)
mask_add(device->capabilities, EI_DEVICE_CAP_TEXT);
if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) &&
!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) &&
!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD) &&
!ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH) &&
!ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON) &&
!ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) {
!ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL) &&
!ei_device_has_capability(device, EI_DEVICE_CAP_TEXT)) {
log_debug(ei, "Rejecting device %#" PRIx64 " '%s' with no known capabilities",
ei_device_get_id(device), ei_device_get_name(device));
ei_device_close(device);
@ -273,7 +277,7 @@ handle_msg_done(struct ei_device *device)
ei_queue_device_added_event(device);
ei_device_done(device);
log_debug(ei,
"Added device %#" PRIx64 " '%s' caps: %s%s%s%s%s%s seat: %s",
"Added device %#" PRIx64 " '%s' caps: %s%s%s%s%s%s%s seat: %s",
ei_device_get_id(device), ei_device_get_name(device),
ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) ? "p" : "",
ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) ? "a" : "",
@ -281,6 +285,7 @@ handle_msg_done(struct ei_device *device)
ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH) ? "t" : "",
ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON) ? "b" : "",
ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL) ? "s" : "",
ei_device_has_capability(device, EI_DEVICE_CAP_TEXT) ? "x" : "",
ei_seat_get_name(ei_device_get_seat(device)));
return NULL;
}
@ -466,6 +471,12 @@ handle_msg_interface(struct ei_device *device, object_id_t id, const char *name,
"Duplicate ei_touchscreen interface object on device");
device->touchscreen = ei_touchscreen_new(device, id, version);
} else if (streq(name, EI_TEXT_INTERFACE_NAME)) {
DISCONNECT_IF_INVALID_VERSION(ei, ei_text, id, version);
if (device->text)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_text interface object on device");
device->text = ei_text_new(device, id, version);
} else {
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Unsupported interface '%s' on device", name);
@ -875,7 +886,8 @@ handle_msg_touch_cancel(struct ei_touchscreen *touchscreen, uint32_t touchid)
}
struct ei *ei = ei_device_get_context(device);
if (ei->interface_versions.ei_touchscreen < EI_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) {
if (ei->interface_versions.ei_touchscreen <
EI_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) {
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Touch cancel event for touchscreen version v1");
}
@ -913,6 +925,75 @@ ei_device_get_touchscreen_interface(struct ei_device *device)
return &touchscreen_interface;
}
static struct brei_result *
handle_msg_text_destroy(struct ei_text *text, uint32_t serial)
{
struct ei *ei = ei_text_get_context(text);
ei_update_serial(ei, serial);
struct ei_device *device = ei_text_get_device(text);
ei_text_unref(steal(&device->text));
return NULL;
}
static struct brei_result *
handle_msg_text_keysym(struct ei_text *text, uint32_t keysym, uint32_t state)
{
struct ei_device *device = ei_text_get_device(text);
DISCONNECT_IF_SENDER_CONTEXT(device);
if (!ei_device_has_capability(device, EI_DEVICE_CAP_TEXT)) {
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"keysym event for non-text device");
}
if (device->state == EI_DEVICE_STATE_EMULATING) {
ei_queue_text_keysym_event(device, keysym, !!state);
return NULL;
}
return maybe_error_on_device_state(device, "text keysym");
}
static struct brei_result *
handle_msg_text_utf8(struct ei_text *text, const char *utf8)
{
struct ei_device *device = ei_text_get_device(text);
DISCONNECT_IF_SENDER_CONTEXT(device);
if (!ei_device_has_capability(device, EI_DEVICE_CAP_TEXT)) {
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"utf8 event for non-text device");
}
if (!utf8) {
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"utf8 text is NULL");
}
if (device->state == EI_DEVICE_STATE_EMULATING) {
ei_queue_text_utf8_event(device, utf8);
return NULL;
}
return maybe_error_on_device_state(device, "text utf8");
}
static const struct ei_text_interface text_interface = {
.destroyed = handle_msg_text_destroy,
.keysym = handle_msg_text_keysym,
.utf8 = handle_msg_text_utf8,
};
const struct ei_text_interface *
ei_device_get_text_interface(struct ei_device *device)
{
return &text_interface;
}
struct ei_device *
ei_device_new(struct ei_seat *seat, object_id_t deviceid, uint32_t version)
{
@ -1194,6 +1275,7 @@ ei_device_has_capability(struct ei_device *device,
case EI_DEVICE_CAP_TOUCH:
case EI_DEVICE_CAP_BUTTON:
case EI_DEVICE_CAP_SCROLL:
case EI_DEVICE_CAP_TEXT:
return mask_all(device->capabilities, cap);
}
return false;
@ -1812,6 +1894,89 @@ ei_touch_cancel(struct ei_touch *touch)
ei_send_touch_up(touch->device, touch->tracking_id);
}
static int
ei_send_text_keysym(struct ei_device *device, uint32_t keysym, bool is_press)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
device->send_frame_event = true;
int rc = ei_text_request_keysym(device->text, keysym, is_press);
if (rc)
ei_disconnect(ei);
return rc;
}
static int
ei_send_text_utf8(struct ei_device *device, const char *utf8)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
device->send_frame_event = true;
int rc = ei_text_request_utf8(device->text, utf8);
if (rc)
ei_disconnect(ei);
return rc;
}
_public_ void
ei_device_text_keysym(struct ei_device *device,
uint32_t keysym, bool is_press)
{
if (!ei_device_has_capability(device, EI_DEVICE_CAP_TEXT)) {
log_bug_client(ei_device_get_context(device),
"%s: device is not a text device", __func__);
return;
}
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not emulating", __func__);
return;
}
ei_send_text_keysym(device, keysym, is_press);
}
_public_ void
ei_device_text_utf8(struct ei_device *device, const char *utf8)
{
ei_device_text_utf8_with_length(device, utf8, utf8 ? strlen(utf8) : 0);
}
_public_ void
ei_device_text_utf8_with_length(struct ei_device *device, const char *utf8, size_t length)
{
if (!ei_device_has_capability(device, EI_DEVICE_CAP_TEXT)) {
log_bug_client(ei_device_get_context(device),
"%s: device is not a text device", __func__);
return;
}
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not emulating", __func__);
return;
}
if (length == 0) {
log_bug_client(ei_device_get_context(device),
"%s: empty utf8 string", __func__);
return;
}
char buf[4096] = {0};
memcpy(buf, utf8, min(length, sizeof(buf) - 1));
ei_send_text_utf8(device, buf);
}
_public_ void
ei_device_frame(struct ei_device *device, uint64_t time)
{

View file

@ -31,6 +31,7 @@
#include "brei-shared.h"
#include "libei-pointer.h"
#include "libei-keyboard.h"
#include "libei-text.h"
#include "libei-touchscreen.h"
enum ei_device_state {
@ -65,6 +66,7 @@ struct ei_device {
struct ei_button *button;
struct ei_keyboard *keyboard;
struct ei_touchscreen *touchscreen;
struct ei_text *text;
struct list link;
enum ei_device_state state;
@ -123,6 +125,7 @@ OBJECT_DECLARE_GETTER(ei_device, scroll_interface, const struct ei_scroll_interf
OBJECT_DECLARE_GETTER(ei_device, button_interface, const struct ei_button_interface *);
OBJECT_DECLARE_GETTER(ei_device, keyboard_interface, const struct ei_keyboard_interface *);
OBJECT_DECLARE_GETTER(ei_device, touchscreen_interface, const struct ei_touchscreen_interface *);
OBJECT_DECLARE_GETTER(ei_device, text_interface, const struct ei_text_interface *);
OBJECT_DECLARE_SETTER(ei_device, type, enum ei_device_type);
OBJECT_DECLARE_SETTER(ei_device, name, const char*);
OBJECT_DECLARE_SETTER(ei_device, seat, const char*);

View file

@ -65,6 +65,8 @@ ei_event_type_to_string(enum ei_event_type type)
CASE_RETURN_STRING(EI_EVENT_TOUCH_DOWN);
CASE_RETURN_STRING(EI_EVENT_TOUCH_UP);
CASE_RETURN_STRING(EI_EVENT_TOUCH_MOTION);
CASE_RETURN_STRING(EI_EVENT_TEXT_KEYSYM);
CASE_RETURN_STRING(EI_EVENT_TEXT_UTF8);
}
return NULL;
@ -98,6 +100,10 @@ ei_event_destroy(struct ei_event *event)
case EI_EVENT_TOUCH_DOWN:
case EI_EVENT_TOUCH_UP:
case EI_EVENT_TOUCH_MOTION:
case EI_EVENT_TEXT_KEYSYM:
break;
case EI_EVENT_TEXT_UTF8:
free(steal(&event->text.utf8));
break;
case EI_EVENT_DEVICE_ADDED:
if (ei->interface_versions.ei_device >= EI_DEVICE_REQUEST_READY_SINCE_VERSION)
@ -377,6 +383,30 @@ ei_event_touch_get_is_cancel(struct ei_event *event)
return event->touch.is_cancel;
}
_public_ uint32_t
ei_event_text_get_keysym(struct ei_event *event)
{
require_event_type(event, 0, EI_EVENT_TEXT_KEYSYM);
return event->text.keysym;
}
_public_ bool
ei_event_text_get_keysym_is_press(struct ei_event *event)
{
require_event_type(event, false, EI_EVENT_TEXT_KEYSYM);
return event->text.is_press;
}
_public_ const char *
ei_event_text_get_utf8(struct ei_event *event)
{
require_event_type(event, NULL, EI_EVENT_TEXT_UTF8);
return event->text.utf8;
}
_public_ uint64_t
ei_event_get_time(struct ei_event *event)
{
@ -392,6 +422,8 @@ ei_event_get_time(struct ei_event *event)
EI_EVENT_TOUCH_DOWN,
EI_EVENT_TOUCH_UP,
EI_EVENT_TOUCH_MOTION,
EI_EVENT_TEXT_KEYSYM,
EI_EVENT_TEXT_UTF8,
EI_EVENT_FRAME);
return event->timestamp;

View file

@ -66,6 +66,11 @@ struct ei_event {
double x, y;
bool is_cancel;
} touch;
struct {
uint32_t keysym;
bool is_press;
char *utf8;
} text;
struct {
uint32_t sequence;
} start_emulating;

View file

@ -98,6 +98,7 @@ ei_handshake_initialize(struct ei_handshake *setup, uint32_t version)
ei_handshake_request_interface_version(setup, EI_BUTTON_INTERFACE_NAME, v->ei_button);
ei_handshake_request_interface_version(setup, EI_KEYBOARD_INTERFACE_NAME, v->ei_keyboard);
ei_handshake_request_interface_version(setup, EI_TOUCHSCREEN_INTERFACE_NAME, v->ei_touchscreen);
ei_handshake_request_interface_version(setup, EI_TEXT_INTERFACE_NAME, v->ei_text);
}
ei_handshake_request_finish(setup);
@ -144,6 +145,7 @@ handle_msg_interface_version(struct ei_handshake *setup, const char *name, uint3
else VERSION_UPDATE(ei_button)
else VERSION_UPDATE(ei_keyboard)
else VERSION_UPDATE(ei_touchscreen)
else VERSION_UPDATE(ei_text)
#undef VERSION_UPDATE

View file

@ -49,6 +49,7 @@
#include "libei-region.h"
#include "libei-scroll.h"
#include "libei-seat.h"
#include "libei-text.h"
#include "libei-touchscreen.h"
struct ei_backend_interface {
@ -77,6 +78,7 @@ struct ei_interface_versions {
uint32_t ei_button;
uint32_t ei_keyboard;
uint32_t ei_touchscreen;
uint32_t ei_text;
};
struct ei_unsent {
@ -243,6 +245,13 @@ ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid);
void
ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid);
void
ei_queue_text_keysym_event(struct ei_device *device, uint32_t keysym,
bool is_press);
void
ei_queue_text_utf8_event(struct ei_device *device, const char *utf8);
void
ei_sync_event_send_done(struct ei_event *e);

View file

@ -247,6 +247,8 @@ ei_seat_has_capability(struct ei_seat *seat,
return seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX] != 0;
case EI_DEVICE_CAP_BUTTON:
return seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX] != 0;
case EI_DEVICE_CAP_TEXT:
return seat->capabilities.map[EI_TEXT_INTERFACE_INDEX] != 0;
}
return false;
}
@ -281,6 +283,8 @@ ei_seat_cap_mask(struct ei_seat *seat, enum ei_device_capability cap)
return seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX];
case EI_DEVICE_CAP_SCROLL:
return seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX];
case EI_DEVICE_CAP_TEXT:
return seat->capabilities.map[EI_TEXT_INTERFACE_INDEX];
}
return 0;

87
src/libei-text.c Normal file
View file

@ -0,0 +1,87 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2025 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 <stdbool.h>
#include "util-bits.h"
#include "util-macros.h"
#include "util-mem.h"
#include "util-io.h"
#include "util-strings.h"
#include "util-version.h"
#include "libei-private.h"
#include "ei-proto.h"
static void
ei_text_destroy(struct ei_text *text)
{
struct ei *ei = ei_text_get_context(text);
ei_unregister_object(ei, &text->proto_object);
}
OBJECT_IMPLEMENT_REF(ei_text);
OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_text);
static
OBJECT_IMPLEMENT_CREATE(ei_text);
static
OBJECT_IMPLEMENT_PARENT(ei_text, ei_device);
OBJECT_IMPLEMENT_GETTER_AS_REF(ei_text, proto_object, const struct brei_object*);
struct ei_device *
ei_text_get_device(struct ei_text *text)
{
return ei_text_parent(text);
}
struct ei*
ei_text_get_context(struct ei_text *text)
{
return ei_device_get_context(ei_text_get_device(text));
}
const struct ei_text_interface *
ei_text_get_interface(struct ei_text *text) {
struct ei_device *device = ei_text_get_device(text);
return ei_device_get_text_interface(device);
}
struct ei_text *
ei_text_new(struct ei_device *device, object_id_t id, uint32_t version)
{
struct ei_text *text = ei_text_create(&device->object);
struct ei *ei = ei_device_get_context(device);
text->proto_object.id = id;
text->proto_object.implementation = text;
text->proto_object.interface = &ei_text_proto_interface;
text->proto_object.version = version;
ei_register_object(ei, &text->proto_object);
return text; /* ref owned by caller */
}

49
src/libei-text.h Normal file
View file

@ -0,0 +1,49 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2025 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "util-object.h"
#include "util-list.h"
#include "brei-shared.h"
struct ei;
struct ei_device;
struct ei_text;
/* This is a protocol-only object, not exposed in the API */
struct ei_text {
struct object object;
struct brei_object proto_object;
};
OBJECT_DECLARE_GETTER(ei_text, context, struct ei*);
OBJECT_DECLARE_GETTER(ei_text, device, struct ei_device*);
OBJECT_DECLARE_GETTER(ei_text, proto_object, const struct brei_object*);
OBJECT_DECLARE_GETTER(ei_text, interface, const struct ei_text_interface *);
OBJECT_DECLARE_REF(ei_text);
OBJECT_DECLARE_UNREF(ei_text);
struct ei_text *
ei_text_new(struct ei_device *device, object_id_t id, uint32_t version);

View file

@ -133,6 +133,7 @@ ei_create_context(bool is_sender, void *user_data)
.ei_scroll = VERSION_V(1),
.ei_button = VERSION_V(1),
.ei_keyboard = VERSION_V(1),
.ei_text = VERSION_V(1),
.ei_touchscreen = VERSION_V(2),
};
/* This must be v1 until the server tells us otherwise */
@ -245,6 +246,8 @@ update_event_timestamp(struct ei_event *event, uint64_t time)
case EI_EVENT_TOUCH_DOWN:
case EI_EVENT_TOUCH_UP:
case EI_EVENT_TOUCH_MOTION:
case EI_EVENT_TEXT_KEYSYM:
case EI_EVENT_TEXT_UTF8:
if (event->timestamp != 0) {
log_bug(ei_event_get_context(event),
"Unexpected timestamp for event of type: %s",
@ -280,6 +283,8 @@ queue_event(struct ei *ei, struct ei_event *event)
case EI_EVENT_TOUCH_DOWN:
case EI_EVENT_TOUCH_UP:
case EI_EVENT_TOUCH_MOTION:
case EI_EVENT_TEXT_KEYSYM:
case EI_EVENT_TEXT_UTF8:
prefix = "pending ";
queue = &device->pending_event_queue;
break;
@ -626,6 +631,30 @@ ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid)
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_text_keysym_event(struct ei_device *device, uint32_t keysym,
bool is_press)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_TEXT_KEYSYM;
e->text.keysym = keysym;
e->text.is_press = is_press;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_text_utf8_event(struct ei_device *device, const char *utf8)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_TEXT_UTF8;
e->text.utf8 = xstrdup(utf8);
queue_event(ei_device_get_context(device), e);
}
_public_ void
ei_disconnect(struct ei *ei)
{

View file

@ -300,6 +300,10 @@ enum ei_device_capability {
* The device can send button events
*/
EI_DEVICE_CAP_BUTTON = (1 << 5),
/**
* The device can send text-like data
*/
EI_DEVICE_CAP_TEXT = (1 << 6),
};
/**
@ -591,6 +595,24 @@ enum ei_event_type {
* See ei_device_touch_new() and ei_touch_motion() for the sender context API.
*/
EI_EVENT_TOUCH_MOTION,
/**
* Event for a single keysym logically pressed/released on this device.
* The keysym is an XKB-compatible keysym (not key code!) and may not be
* present on any keymap currently active on any device.
*
* @note This event is only generated on a receiver ei context.
* See ei_device_text_keysym() for the sender context API.
*/
EI_EVENT_TEXT_KEYSYM = 900,
/**
* Event for a UTF-8 compatible text sequence sent by this device.
*
* @note This event is only generated on a receiver ei context.
* See ei_device_text_utf8() for the sender context API.
*/
EI_EVENT_TEXT_UTF8,
};
/**
@ -1845,6 +1867,68 @@ ei_device_scroll_cancel(struct ei_device *device, bool cancel_x, bool cancel_y);
void
ei_device_keyboard_key(struct ei_device *device, uint32_t keycode, bool is_press);
/**
* @ingroup libei-sender
*
* Generate a key event on a device with
* the @ref EI_DEVICE_CAP_TEXT capability.
*
* Keysyms use the XKB-compatible keysyms, see e.g.
* [/usr/include/xkbcommon/xkbcommon-keysyms.h](https://xkbcommon.org/doc/current/xkbcommon-keysyms_8h.html)
* for a list of key syms
*
* Note that this keysym is independent of any keymap and the keysym
* may not exist on any active keymap on any device. For example, a device
* with both @ref EI_DEVICE_CAP_TEXT and @ref EI_DEVICE_CAP_KEYBOARD is not
* limited to sending keysyms only present on the keymap.
*
* This method is only available on an ei sender context.
*
* @param device The EI device
* @param keysym The keysym
* @param is_press true for key down, false for key up
*/
void
ei_device_text_keysym(struct ei_device *device, uint32_t keysym, bool is_press);
/**
* @ingroup libei-sender
*
* Generate a UTF-8 text event on a device with
* the @ref EI_DEVICE_CAP_TEXT capability.
*
* This method is only available on an ei sender context.
* This method is identical to ei_device_text_utf8_with_length()
* but calculates the length of the string.
*
* @param device The EI device
* @param text The zero-terminated UTF-8 compatible text
*
* @see ei_device_text_utf8_with_length
*/
void
ei_device_text_utf8(struct ei_device *device, const char *text);
/**
* @ingroup libei-sender
*
* Generate a UTF-8 text event on a device with
* the @ref EI_DEVICE_CAP_TEXT capability.
*
* This method is only available on an ei sender context.
*
* This method is identical to ei_device_text_utf8()
* but takes an additional length argument.
*
* @param device The EI device
* @param text The zero-terminated UTF-8 compatible text
* @param length The length of text in bytes, excluding the zero bytes
*
* @see ei_device_text_utf8
*/
void
ei_device_text_utf8_with_length(struct ei_device *device, const char *text, size_t length);
/**
* @ingroup libei-sender
*
@ -2220,6 +2304,34 @@ ei_event_touch_get_y(struct ei_event *event);
bool
ei_event_touch_get_is_cancel(struct ei_event *event);
/**
* @ingroup libei-receiver
*
* For an event of type @ref EI_EVENT_TEXT_KEYSYM
* return the XKB-compatible keysym.
*/
uint32_t
ei_event_text_get_keysym(struct ei_event *event);
/**
* @ingroup libei-receiver
*
* For an event of type @ref EI_EVENT_TEXT_KEYSYM
* return true if the event is a logical key down for the
* keysym.
*/
bool
ei_event_text_get_keysym_is_press(struct ei_event *event);
/**
* @ingroup libei-receiver
*
* For an event of type @ref EI_EVENT_TEXT_UTF8
* return the zero-terminated UTF8 string.
*/
const char *
ei_event_text_get_utf8(struct ei_event *event);
/**
* @}
*/

View file

@ -528,6 +528,7 @@ eis_client_new(struct eis *eis, int fd)
.ei_scroll = VERSION_V(1),
.ei_button = VERSION_V(1),
.ei_keyboard = VERSION_V(1),
.ei_text = VERSION_V(1),
.ei_touchscreen = VERSION_V(2),
};
struct source *s = source_new(fd, client_dispatch, client);

View file

@ -52,6 +52,7 @@ struct eis_client_interface_versions {
uint32_t ei_button;
uint32_t ei_keyboard;
uint32_t ei_touchscreen;
uint32_t ei_text;
};
struct eis_client {

View file

@ -149,6 +149,7 @@ eis_device_destroy(struct eis_device *device)
eis_pointer_unref(device->pointer);
eis_touchscreen_unref(device->touchscreen);
eis_keyboard_unref(device->keyboard);
eis_text_unref(device->text);
free(device->name);
}
@ -739,6 +740,73 @@ eis_device_get_touchscreen_interface(struct eis_device *device)
return &touchscreen_interface;
}
static struct brei_result *
client_msg_text_release(struct eis_text *text)
{
struct eis_device *device = eis_text_get_device(text);
eis_text_event_destroyed(device->text,
eis_client_get_next_serial(eis_device_get_client(device)));
eis_text_unref(steal(&device->text));
return NULL;
}
static struct brei_result *
client_msg_text_keysym(struct eis_text *text, uint32_t keysym, uint32_t state)
{
struct eis_device *device = eis_text_get_device(text);
DISCONNECT_IF_RECEIVER_CONTEXT(device);
if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) {
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"keysym event for non-text device");
}
if (device->state == EIS_DEVICE_STATE_EMULATING) {
eis_queue_text_keysym_event(device, keysym, !!state);
return NULL;
}
return maybe_error_on_device_state(device, "text keysym");
}
static struct brei_result *
client_msg_text_utf8(struct eis_text *text, const char *utf8)
{
struct eis_device *device = eis_text_get_device(text);
DISCONNECT_IF_RECEIVER_CONTEXT(device);
if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) {
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"utf8 event for non-text device");
}
if (!utf8) {
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"utf8 text is NULL");
}
if (device->state == EIS_DEVICE_STATE_EMULATING) {
eis_queue_text_utf8_event(device, utf8);
return NULL;
}
return maybe_error_on_device_state(device, "text utf8");
}
static const struct eis_text_interface text_interface = {
.release = client_msg_text_release,
.keysym = client_msg_text_keysym,
.utf8 = client_msg_text_utf8,
};
const struct eis_text_interface *
eis_device_get_text_interface(struct eis_device *device)
{
return &text_interface;
}
_public_ struct eis_device *
eis_seat_new_device(struct eis_seat *seat)
{
@ -809,6 +877,7 @@ eis_device_configure_capability(struct eis_device *device, enum eis_device_capab
case EIS_DEVICE_CAP_TOUCH:
case EIS_DEVICE_CAP_BUTTON:
case EIS_DEVICE_CAP_SCROLL:
case EIS_DEVICE_CAP_TEXT:
mask_add(device->capabilities, cap);
break;
}
@ -935,6 +1004,14 @@ eis_device_add(struct eis_device *device)
if (rc < 0)
goto out;
}
if (eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) {
device->text = eis_text_new(device);
rc = eis_device_event_interface(device, eis_text_get_id(device->text),
EIS_TEXT_INTERFACE_NAME,
eis_text_get_version(device->text));
if (rc < 0)
goto out;
}
rc = eis_device_event_done(device);
if (rc < 0)
@ -991,6 +1068,10 @@ eis_device_remove(struct eis_device *device)
eis_keyboard_event_destroyed(device->keyboard, eis_client_get_next_serial(client));
eis_keyboard_unref(steal(&device->keyboard));
}
if (device->text) {
eis_text_event_destroyed(device->text, eis_client_get_next_serial(client));
eis_text_unref(steal(&device->text));
}
if (device->state != EIS_DEVICE_STATE_NEW)
eis_device_event_destroyed(device, eis_client_get_next_serial(client));
@ -1018,6 +1099,7 @@ eis_device_has_capability(struct eis_device *device,
case EIS_DEVICE_CAP_TOUCH:
case EIS_DEVICE_CAP_BUTTON:
case EIS_DEVICE_CAP_SCROLL:
case EIS_DEVICE_CAP_TEXT:
return mask_all(device->capabilities, cap);
}
return false;
@ -1391,6 +1473,49 @@ eis_touch_cancel(struct eis_touch *touch)
eis_touchscreen_event_up(device->touchscreen, touch->tracking_id);
}
_public_ void
eis_device_text_keysym(struct eis_device *device,
uint32_t keysym, bool is_press)
{
if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) {
log_bug_client(eis_device_get_context(device),
"%s: device is not a text device", __func__);
return;
}
if (device->state != EIS_DEVICE_STATE_EMULATING)
return;
device->send_frame_event = true;
eis_text_event_keysym(device->text, keysym, is_press);
}
_public_ void
eis_device_text_utf8(struct eis_device *device, const char *utf8)
{
return eis_device_text_utf8_with_length(device, utf8, utf8 ? strlen(utf8) : 0);
}
_public_ void
eis_device_text_utf8_with_length(struct eis_device *device, const char *text, size_t length)
{
if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) {
log_bug_client(eis_device_get_context(device),
"%s: device is not a text device", __func__);
return;
}
if (device->state != EIS_DEVICE_STATE_EMULATING)
return;
device->send_frame_event = true;
char buf[4096] = {0};
memcpy(buf, text, min(length, sizeof(buf) - 1));
eis_text_event_utf8(device->text, buf);
}
_public_ void
eis_device_frame(struct eis_device *device, uint64_t time)
{

View file

@ -52,6 +52,7 @@ struct eis_device {
struct eis_button *button;
struct eis_keyboard *keyboard;
struct eis_touchscreen *touchscreen;
struct eis_text *text;
char *name;
enum eis_device_state state;
@ -117,6 +118,7 @@ OBJECT_DECLARE_GETTER(eis_device, scroll_interface, const struct eis_scroll_inte
OBJECT_DECLARE_GETTER(eis_device, button_interface, const struct eis_button_interface *);
OBJECT_DECLARE_GETTER(eis_device, keyboard_interface, const struct eis_keyboard_interface *);
OBJECT_DECLARE_GETTER(eis_device, touchscreen_interface, const struct eis_touchscreen_interface *);
OBJECT_DECLARE_GETTER(eis_device, text_interface, const struct eis_text_interface *);
void
eis_device_set_client_keymap(struct eis_device *device,

View file

@ -57,6 +57,11 @@ eis_event_destroy(struct eis_event *event)
case EIS_EVENT_TOUCH_MOTION:
case EIS_EVENT_TOUCH_UP:
case EIS_EVENT_FRAME:
case EIS_EVENT_TEXT_KEYSYM:
handled = true;
break;
case EIS_EVENT_TEXT_UTF8:
free(steal(&event->text.utf8));
handled = true;
break;
case EIS_EVENT_PONG:
@ -192,6 +197,8 @@ eis_event_get_time(struct eis_event *event)
EIS_EVENT_TOUCH_DOWN,
EIS_EVENT_TOUCH_UP,
EIS_EVENT_TOUCH_MOTION,
EIS_EVENT_TEXT_KEYSYM,
EIS_EVENT_TEXT_UTF8,
EIS_EVENT_FRAME);
return event->timestamp;
@ -217,6 +224,7 @@ eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capabilit
case EIS_DEVICE_CAP_TOUCH:
case EIS_DEVICE_CAP_BUTTON:
case EIS_DEVICE_CAP_SCROLL:
case EIS_DEVICE_CAP_TEXT:
return mask_all(event->bind.capabilities, cap);
}
return false;
@ -430,3 +438,30 @@ eis_event_touch_get_is_cancel(struct eis_event *event)
return event->touch.is_cancel;
}
_public_ uint32_t
eis_event_text_get_keysym(struct eis_event *event)
{
require_event_type(event, 0,
EIS_EVENT_TEXT_KEYSYM);
return event->text.keysym;
}
_public_ bool
eis_event_text_get_keysym_is_press(struct eis_event *event)
{
require_event_type(event, false,
EIS_EVENT_TEXT_KEYSYM);
return event->text.is_press;
}
_public_ const char *
eis_event_text_get_utf8(struct eis_event *event)
{
require_event_type(event, NULL,
EIS_EVENT_TEXT_UTF8);
return event->text.utf8;
}

View file

@ -60,6 +60,11 @@ struct eis_event {
double x, y;
bool is_cancel;
} touch;
struct {
uint32_t keysym;
bool is_press;
char *utf8;
} text;
struct {
uint32_t sequence;
} start_emulating;

View file

@ -237,6 +237,7 @@ client_msg_interface_version(struct eis_handshake *setup, const char *name, uint
VERSION_ENTRY(ei_scroll),
VERSION_ENTRY(ei_keyboard),
VERSION_ENTRY(ei_touchscreen),
VERSION_ENTRY(ei_text),
#undef VERSION_ENTRY
};

View file

@ -51,6 +51,7 @@
#include "libeis-region.h"
#include "libeis-scroll.h"
#include "libeis-seat.h"
#include "libeis-text.h"
#include "libeis-touchscreen.h"
struct eis_backend_interface {
@ -160,6 +161,12 @@ eis_queue_touch_up_event(struct eis_device *device, uint32_t touchid);
void
eis_queue_touch_cancel_event(struct eis_device *device, uint32_t touchid);
void
eis_queue_text_keysym_event(struct eis_device *device, uint32_t keysym, bool is_press);
void
eis_queue_text_utf8_event(struct eis_device *device, const char *utf8);
void
eis_sync_event_send_done(struct eis_event *e);

View file

@ -108,6 +108,8 @@ client_msg_bind(struct eis_seat *seat, uint64_t caps)
capabilities |= EIS_DEVICE_CAP_BUTTON;
if (caps & bit(EIS_SCROLL_INTERFACE_INDEX))
capabilities |= EIS_DEVICE_CAP_SCROLL;
if (caps & bit(EIS_TEXT_INTERFACE_INDEX))
capabilities |= EIS_DEVICE_CAP_TEXT;
eis_seat_bind(seat, capabilities);
@ -221,6 +223,14 @@ eis_seat_add(struct eis_seat *seat)
mask_add(seat->capabilities.proto_mask, mask);
}
if (seat->capabilities.c_mask & EIS_DEVICE_CAP_TEXT &&
client->interface_versions.ei_text > 0) {
uint64_t mask = bit(EIS_TEXT_INTERFACE_INDEX);
eis_seat_event_capability(seat, mask,
EIS_TEXT_INTERFACE_NAME);
mask_add(seat->capabilities.proto_mask, mask);
}
eis_seat_event_done(seat);
}
@ -313,6 +323,7 @@ eis_seat_configure_capability(struct eis_seat *seat,
case EIS_DEVICE_CAP_TOUCH:
case EIS_DEVICE_CAP_BUTTON:
case EIS_DEVICE_CAP_SCROLL:
case EIS_DEVICE_CAP_TEXT:
mask_add(seat->capabilities.c_mask, cap);
break;
}
@ -329,6 +340,7 @@ eis_seat_has_capability(struct eis_seat *seat,
case EIS_DEVICE_CAP_TOUCH:
case EIS_DEVICE_CAP_BUTTON:
case EIS_DEVICE_CAP_SCROLL:
case EIS_DEVICE_CAP_TEXT:
return mask_all(seat->capabilities.c_mask, cap);
}
return false;

107
src/libeis-text.c Normal file
View file

@ -0,0 +1,107 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2025 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 <stdbool.h>
#include "util-bits.h"
#include "util-macros.h"
#include "util-mem.h"
#include "util-io.h"
#include "util-strings.h"
#include "util-version.h"
#include "libeis-private.h"
#include "eis-proto.h"
static void
eis_text_destroy(struct eis_text *text)
{
struct eis_client * client = eis_text_get_client(text);
eis_client_unregister_object(client, &text->proto_object);
}
OBJECT_IMPLEMENT_REF(eis_text);
OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_text);
OBJECT_IMPLEMENT_GETTER_AS_REF(eis_text, proto_object, const struct brei_object *);
static
OBJECT_IMPLEMENT_CREATE(eis_text);
static
OBJECT_IMPLEMENT_PARENT(eis_text, eis_device);
uint32_t
eis_text_get_version(struct eis_text *text)
{
return text->proto_object.version;
}
object_id_t
eis_text_get_id(struct eis_text *text)
{
return text->proto_object.id;
}
struct eis_device *
eis_text_get_device(struct eis_text *text)
{
return eis_text_parent(text);
}
struct eis_client*
eis_text_get_client(struct eis_text *text)
{
return eis_device_get_client(eis_text_get_device(text));
}
struct eis*
eis_text_get_context(struct eis_text *text)
{
struct eis_client *client = eis_text_get_client(text);
return eis_client_get_context(client);
}
const struct eis_text_interface *
eis_text_get_interface(struct eis_text *text) {
return eis_device_get_text_interface(eis_text_get_device(text));
}
struct eis_text *
eis_text_new(struct eis_device *device)
{
struct eis_text *text = eis_text_create(&device->object);
struct eis_client *client = eis_device_get_client(device);
text->proto_object.id = eis_client_get_new_id(client);
text->proto_object.implementation = text;
text->proto_object.interface = &eis_text_proto_interface;
text->proto_object.version = client->interface_versions.ei_text;
list_init(&text->proto_object.link);
eis_client_register_object(client, &text->proto_object);
return text; /* ref owned by caller */
}

51
src/libeis-text.h Normal file
View file

@ -0,0 +1,51 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2025 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "util-object.h"
#include "brei-shared.h"
#include "libeis-client.h"
struct eis;
struct eis_client;
/* This is a protocol-only object, not exposed in the API */
struct eis_text {
struct object object;
struct brei_object proto_object;
};
OBJECT_DECLARE_GETTER(eis_text, context, struct eis *);
OBJECT_DECLARE_GETTER(eis_text, device, struct eis_device *);
OBJECT_DECLARE_GETTER(eis_text, client, struct eis_client *);
OBJECT_DECLARE_GETTER(eis_text, id, object_id_t);
OBJECT_DECLARE_GETTER(eis_text, version, uint32_t);
OBJECT_DECLARE_GETTER(eis_text, proto_object, const struct brei_object *);
OBJECT_DECLARE_GETTER(eis_text, interface, const struct eis_text_interface *);
OBJECT_DECLARE_REF(eis_text);
OBJECT_DECLARE_UNREF(eis_text);
struct eis_text *
eis_text_new(struct eis_device *device);

View file

@ -155,6 +155,8 @@ eis_event_type_to_string(enum eis_event_type type)
CASE_RETURN_STRING(EIS_EVENT_TOUCH_UP);
CASE_RETURN_STRING(EIS_EVENT_TOUCH_MOTION);
CASE_RETURN_STRING(EIS_EVENT_FRAME);
CASE_RETURN_STRING(EIS_EVENT_TEXT_KEYSYM);
CASE_RETURN_STRING(EIS_EVENT_TEXT_UTF8);
}
return NULL;
@ -175,6 +177,8 @@ update_event_timestamp(struct eis_event *event, uint64_t time)
case EIS_EVENT_TOUCH_DOWN:
case EIS_EVENT_TOUCH_UP:
case EIS_EVENT_TOUCH_MOTION:
case EIS_EVENT_TEXT_KEYSYM:
case EIS_EVENT_TEXT_UTF8:
if (event->timestamp != 0) {
log_bug(eis_event_get_context(event),
"Unexpected timestamp for event of type: %s",
@ -211,6 +215,8 @@ eis_queue_event(struct eis_event *event)
case EIS_EVENT_TOUCH_DOWN:
case EIS_EVENT_TOUCH_UP:
case EIS_EVENT_TOUCH_MOTION:
case EIS_EVENT_TEXT_KEYSYM:
case EIS_EVENT_TEXT_UTF8:
prefix = "pending ";
queue = &device->pending_event_queue;
break;
@ -474,6 +480,26 @@ eis_queue_touch_cancel_event(struct eis_device *device, uint32_t touchid)
eis_queue_event(e);
}
void
eis_queue_text_keysym_event(struct eis_device *device, uint32_t keysym,
bool is_press)
{
struct eis_event *e = eis_event_new_for_device(device);
e->type = EIS_EVENT_TEXT_KEYSYM;
e->text.keysym = keysym;
e->text.is_press = is_press;
eis_queue_event(e);
}
void
eis_queue_text_utf8_event(struct eis_device *device, const char *text)
{
struct eis_event *e = eis_event_new_for_device(device);
e->type = EIS_EVENT_TEXT_UTF8;
e->text.utf8 = xstrdup(text);
eis_queue_event(e);
}
_public_ struct eis_event*
eis_get_event(struct eis *eis)
{

View file

@ -226,6 +226,7 @@ enum eis_device_capability {
EIS_DEVICE_CAP_TOUCH = (1 << 3),
EIS_DEVICE_CAP_SCROLL = (1 << 4),
EIS_DEVICE_CAP_BUTTON = (1 << 5),
EIS_DEVICE_CAP_TEXT = (1 << 6),
};
/**
@ -396,6 +397,16 @@ enum eis_event_type {
* properties).
*/
EIS_EVENT_TOUCH_MOTION,
/**
* A key sym logical press or release event
*/
EIS_EVENT_TEXT_KEYSYM = 900,
/**
* A text event event
*/
EIS_EVENT_TEXT_UTF8,
};
/**
@ -1508,6 +1519,30 @@ eis_device_keyboard_send_xkb_modifiers(struct eis_device *device,
uint32_t locked,
uint32_t group);
/**
* @ingroup libeis-receiver
*
* see @ref ei_device_text_keysym
*/
void
eis_device_text_keysym(struct eis_device *device, uint32_t keysym, bool is_press);
/**
* @ingroup libeis-receiver
*
* see @ref ei_device_text_utf8
*/
void
eis_device_text_utf8(struct eis_device *device, const char *text);
/**
* @ingroup libeis-receiver
*
* see @ref ei_device_text_utf8_with_length
*/
void
eis_device_text_utf8_with_length(struct eis_device *device, const char *text, size_t length);
/**
* @ingroup libeis-receiver
*
@ -1869,6 +1904,34 @@ eis_event_touch_get_y(struct eis_event *event);
bool
eis_event_touch_get_is_cancel(struct eis_event *event);
/**
* @ingroup libeis-sender
*
* For an event of type @ref EIS_EVENT_TEXT_KEYSYM
* return the XKB-compatible keysym.
*/
uint32_t
eis_event_text_get_keysym(struct eis_event *event);
/**
* @ingroup libeis-sender
*
* For an event of type @ref EIS_EVENT_TEXT_KEYSYM
* return true if the event is a logical key down for the
* keysym.
*/
bool
eis_event_text_get_keysym_is_press(struct eis_event *event);
/**
* @ingroup libeis-sender
*
* For an event of type @ref EIS_EVENT_TEXT_UTF8
* return the zero-terminated UTF8 string.
*/
const char *
eis_event_text_get_utf8(struct eis_event *event);
/**
* @returns a timestamp for the current time to pass into
* eis_device_frame().

View file

@ -79,6 +79,7 @@ if build_libei
'libei-scroll.c',
'libei-seat.c',
'libei-socket.c',
'libei-text.c',
'libei-touchscreen.c',
) + [brei_proto_headers, ei_proto_headers, ei_proto_sources]
@ -157,6 +158,7 @@ if build_libeis
'libeis-scroll.c',
'libeis-seat.c',
'libeis-socket.c',
'libeis-text.c',
'libeis-touchscreen.c',
'libeis.c',
) + [brei_proto_headers, eis_proto_headers, eis_proto_sources]

View file

@ -1159,6 +1159,8 @@ _peck_dispatch_eis(struct peck *peck, int lineno)
case EIS_EVENT_TOUCH_DOWN:
case EIS_EVENT_TOUCH_UP:
case EIS_EVENT_TOUCH_MOTION:
case EIS_EVENT_TEXT_KEYSYM:
case EIS_EVENT_TEXT_UTF8:
need_frame = true;
break;
}
@ -1320,6 +1322,8 @@ _peck_dispatch_ei(struct peck *peck, int lineno)
case EI_EVENT_TOUCH_DOWN:
case EI_EVENT_TOUCH_UP:
case EI_EVENT_TOUCH_MOTION:
case EI_EVENT_TEXT_KEYSYM:
case EI_EVENT_TEXT_UTF8:
need_frame = true;
break;
}
@ -1656,6 +1660,8 @@ peck_ei_event_type_name(enum ei_event_type type)
CASE_STRING(TOUCH_DOWN);
CASE_STRING(TOUCH_UP);
CASE_STRING(TOUCH_MOTION);
CASE_STRING(TEXT_KEYSYM);
CASE_STRING(TEXT_UTF8);
}
#undef CASE_STRING
assert(!"Unhandled ei event type");
@ -1695,6 +1701,8 @@ peck_eis_event_type_name(enum eis_event_type type)
CASE_STRING(TOUCH_UP);
CASE_STRING(TOUCH_MOTION);
CASE_STRING(FRAME);
CASE_STRING(TEXT_KEYSYM);
CASE_STRING(TEXT_UTF8);
}
#undef CASE_STRING
assert(!"Unhandled EIS event type");

View file

@ -51,6 +51,10 @@
#define libevdev_event_code_get_name(...) ""
#endif
#if HAVE_LIBXKBCOMMON
#include <xkbcommon/xkbcommon.h>
#endif
#include "libei.h"
#include "src/util-macros.h"
@ -227,6 +231,32 @@ print_key_event(struct ei_event *event)
printf(" press: %s\n", press ? "true" : "false");
}
static void
print_keysym_event(struct ei_event *event)
{
print_device(event);
uint32_t keysym = ei_event_text_get_keysym(event);
bool press = ei_event_text_get_keysym_is_press(event);
char buf[128] = {0};
#if HAVE_LIBXKBCOMMON
xkb_keysym_get_name(keysym, buf, sizeof(buf));
#else
snprintf(buf, sizeof(buf), "0x%04x", keysym);
#endif
printf(" keysym: 0x%08x # %s\n", keysym, buf);
printf(" press: %s\n", press ? "true" : "false");
}
static void
print_utf8_event(struct ei_event *event)
{
const char *text = ei_event_text_get_utf8(event);
printf(" utf8: '%s'\n", text);
}
static void
print_touch_event(struct ei_event *event)
{
@ -434,6 +464,12 @@ int main(int argc, char **argv)
case EI_EVENT_KEYBOARD_KEY:
print_key_event(e);
break;
case EI_EVENT_TEXT_KEYSYM:
print_keysym_event(e);
break;
case EI_EVENT_TEXT_UTF8:
print_utf8_event(e);
break;
case EI_EVENT_TOUCH_DOWN:
case EI_EVENT_TOUCH_MOTION:
case EI_EVENT_TOUCH_UP:

View file

@ -51,6 +51,10 @@
#include <xkbcommon/xkbcommon.h>
#endif
#ifndef XK_dead_a
#define XK_dead_a 0xfe80
#endif
#include "libei.h"
#include "src/util-macros.h"
@ -126,7 +130,7 @@ setup_xkb_keymap(struct ei_keymap *keymap)
for (unsigned int evcode = KEY_Q; evcode <= KEY_Y; evcode++) {
char utf8[7];
xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkbstate, evcode + 8);
xkb_keysym_to_utf8(keysym, utf8, sizeof(utf8));
xkb_keysym_get_name(keysym, utf8, sizeof(utf8));
strcat(layout, utf8);
}
@ -300,12 +304,14 @@ int main(int argc, char **argv)
_unref_(ei_device) *kbd = NULL;
_unref_(ei_device) *abs = NULL;
_unref_(ei_device) *touch = NULL;
_unref_(ei_device) *text = NULL;
bool stop = false;
bool have_ptr = false;
bool have_kbd = false;
bool have_abs = false;
bool have_touch = false;
bool have_text = false;
struct ei_seat *default_seat = NULL;
uint32_t sequence = 0;
@ -344,7 +350,9 @@ int main(int argc, char **argv)
EI_DEVICE_CAP_POINTER_ABSOLUTE,
EI_DEVICE_CAP_TOUCH,
EI_DEVICE_CAP_BUTTON,
EI_DEVICE_CAP_SCROLL, NULL);
EI_DEVICE_CAP_SCROLL,
EI_DEVICE_CAP_TEXT,
NULL);
break;
}
case EI_EVENT_SEAT_REMOVED:
@ -376,6 +384,10 @@ int main(int argc, char **argv)
touch = ei_device_ref(device);
handle_regions(device);
}
if (ei_device_has_capability(device, EI_DEVICE_CAP_TEXT)) {
colorprint("New text device: %s\n", ei_device_get_name(device));
text = ei_device_ref(device);
}
}
break;
case EI_EVENT_DEVICE_RESUMED:
@ -403,6 +415,12 @@ int main(int argc, char **argv)
colorprint("Touch device was resumed\n");
have_touch = true;
}
if (ei_event_get_device(e) == text) {
if (!receiver)
ei_device_start_emulating(text, ++sequence);
colorprint("Text device was resumed\n");
have_text = true;
}
break;
case EI_EVENT_DEVICE_PAUSED:
if (ei_event_get_device(e) == ptr) {
@ -421,6 +439,10 @@ int main(int argc, char **argv)
colorprint("Touch device was paused\n");
have_touch = false;
}
if (ei_event_get_device(e) == text) {
colorprint("Text device was paused\n");
have_text = false;
}
break;
case EI_EVENT_DEVICE_REMOVED:
{
@ -498,6 +520,26 @@ int main(int argc, char **argv)
colorprint("touch up %u\n", ei_event_touch_get_id(e));
}
break;
case EI_EVENT_TEXT_KEYSYM:
{
char buf[128];
uint32_t keysym = ei_event_text_get_keysym(e);
#if HAVE_LIBXKBCOMMON
xkb_keysym_to_utf8(keysym, buf, sizeof(buf));
#else
snprintf(buf, sizeof(buf), "0x%04x", keysym);
#endif
colorprint("text keysym %u [%s] (%s)\n",
keysym,
buf,
ei_event_text_get_keysym_is_press(e) ? "press" : "release");
}
break;
case EI_EVENT_TEXT_UTF8:
{
colorprint("text utf8 '%s'\n", ei_event_text_get_utf8(e));
}
break;
case EI_EVENT_SYNC:
{
colorprint("sync\n");
@ -585,6 +627,23 @@ int main(int argc, char **argv)
}
}
if (have_text) {
static int key = 0;
colorprint("sending text event\n");
ei_device_text_keysym(text, XK_dead_a + key, false);
ei_device_frame(text, now);
now += interval;
ei_device_text_keysym(text, XK_dead_a + key, false);
ei_device_frame(text, now);
now += interval;
key = (key + 1) % 6;
colorprint("sending text utf8 event\n");
ei_device_text_utf8(text, "👋 World!");
ei_device_frame(text, now);
now += interval;
}
}
}
@ -597,13 +656,17 @@ int main(int argc, char **argv)
ei_device_close(abs);
if (touch)
ei_device_close(touch);
if (text)
ei_device_close(text);
if (default_seat) {
ei_seat_bind_capabilities(default_seat, EI_DEVICE_CAP_POINTER,
EI_DEVICE_CAP_KEYBOARD,
EI_DEVICE_CAP_POINTER_ABSOLUTE,
EI_DEVICE_CAP_TOUCH,
EI_DEVICE_CAP_BUTTON,
EI_DEVICE_CAP_SCROLL, NULL);
EI_DEVICE_CAP_SCROLL,
EI_DEVICE_CAP_TEXT,
NULL);
ei_seat_unref(default_seat);
}

View file

@ -54,6 +54,10 @@
#include <xkbcommon/xkbcommon.h>
#endif
#ifndef XK_ssharp
#define XK_ssharp 0x00df /* U+00DF LATIN SMALL LETTER SHARP S */
#endif
#include "src/util-color.h"
#include "src/util-mem.h"
#include "src/util-memfile.h"
@ -124,6 +128,7 @@ eis_demo_client_destroy(struct eis_demo_client *democlient)
eis_device_unref(democlient->abs);
eis_device_unref(democlient->kbd);
eis_device_unref(democlient->touchscreen);
eis_device_unref(democlient->text);
}
static
@ -319,6 +324,17 @@ add_device(struct eis_demo_server *server, struct eis_client *client,
case EIS_DEVICE_CAP_SCROLL:
/* Mixed in with pointer/abs - good enough for a demo server */
break;
case EIS_DEVICE_CAP_TEXT:
{
struct eis_device *text = eis_seat_new_device(seat);
eis_device_configure_name(text, "test text device");
eis_device_configure_capability(text, EIS_DEVICE_CAP_TEXT);
colorprint("Creating text device %s for %s\n", eis_device_get_name(text),
eis_client_get_name(client));
eis_device_add(text);
device = steal(&text);
break;
}
}
return device;
@ -371,6 +387,7 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server,
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TOUCH);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_BUTTON);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_SCROLL);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TEXT);
eis_seat_add(seat);
/* Note: we don't have a ref to this seat ourselves anywhere */
break;
@ -436,12 +453,23 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server,
}
}
if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TEXT)) {
if (!democlient->text)
democlient->text = add_device(server, client, seat, EIS_DEVICE_CAP_TEXT);
} else {
if (democlient->text) {
eis_device_remove(democlient->text);
democlient->text = eis_device_unref(democlient->text);
}
}
/* Special "Feature", if all caps are unbound remove the seat.
* This is a demo server after all, so let's demo this. */
if (!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER) &&
!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER_ABSOLUTE) &&
!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD) &&
!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH))
!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH) &&
!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TEXT))
eis_seat_remove(seat);
break;
@ -479,6 +507,9 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server,
if (democlient->touchscreen == device)
democlient->touchscreen = NULL;
if (democlient->text == device)
democlient->text = NULL;
eis_device_unref(device);
}
break;
@ -551,6 +582,27 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server,
colorprint("touch up %u\n", eis_event_touch_get_id(e));
}
break;
case EIS_EVENT_TEXT_KEYSYM:
{
char buf[128] = {0};
uint32_t keysym = eis_event_text_get_keysym(e);
#if HAVE_LIBXKBCOMMON
xkb_keysym_get_name(keysym, buf, sizeof(buf));
#else
snprintf(buf, sizeof(buf), "0x%04x", keysym);
#endif
colorprint("text keysym %u [%s] (%s)\n",
keysym,
buf,
eis_event_text_get_keysym_is_press(e) ? "press" : "release");
}
break;
case EIS_EVENT_TEXT_UTF8:
{
const char *text = eis_event_text_get_utf8(e);
colorprint("text utf8 '%s'\n", text);
}
break;
case EIS_EVENT_FRAME:
{
colorprint("frame timestamp: %" PRIu64 "\n",
@ -744,6 +796,7 @@ int main(int argc, char **argv)
struct eis_device *kbd = democlient->kbd;
struct eis_device *abs = democlient->abs;
struct eis_device *touchscreen = democlient->touchscreen;
struct eis_device *text = democlient->text;
if (ptr) {
colorprint("sending motion event\n");
eis_device_pointer_motion(ptr, -1, 1);
@ -816,6 +869,23 @@ int main(int argc, char **argv)
}
}
if (text) {
static int key = 0;
colorprint("sending text event\n");
eis_device_text_keysym(text, XK_ssharp + key, true); /* KEY_Q */
eis_device_frame(text, now);
now += interval;
eis_device_text_keysym(text, XK_ssharp + key, false); /* KEY_Q */
eis_device_frame(text, now);
now += interval;
key = (key + 1) % 6;
eis_device_text_utf8(text, "👋🏽 World");
eis_device_frame(text, now);
now += interval;
}
}
}

View file

@ -40,6 +40,7 @@ struct eis_demo_client {
struct eis_device *abs;
struct eis_device *touchscreen;
struct eis_touch *touch;
struct eis_device *text;
};
struct eis_demo_server {

View file

@ -29,7 +29,12 @@ if build_libei
executable('ei-debug-events',
'ei-debug-events.c',
dependencies: [dep_libutil, dep_libei, dep_libevdev],
dependencies: [
dep_libutil,
dep_libei,
dep_libevdev,
dep_libxkbcommon
],
include_directories: [inc_builddir],
install: true,
)