Implement pen functionality for X11, including pressure, hover, and eraser

This commit is contained in:
digitalsignalperson 2023-06-20 23:57:47 -07:00 committed by akallabeth
parent 1fc0d5b4b1
commit 9c7a541d9c
4 changed files with 237 additions and 26 deletions

View File

@ -318,7 +318,7 @@ static UINT rdpei_send_pen_event_pdu(GENERIC_CHANNEL_CALLBACK* callback, UINT32
{
if ((status = rdpei_write_pen_frame(s, &frames[x])))
{
WLog_ERR(TAG, "rdpei_write_touch_frame failed with error %" PRIu32 "!", status);
WLog_ERR(TAG, "rdpei_write_pen_frame failed with error %" PRIu32 "!", status);
Stream_Free(s, TRUE);
return status;
}
@ -404,7 +404,7 @@ static UINT rdpei_add_pen_frame(RdpeiClientContext* context)
penContacts[penFrame.contactCount++] = contact->data;
}
if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_UP)
if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_CANCELED)
{
contact->externalId = 0;
contact->active = FALSE;
@ -1177,18 +1177,16 @@ static UINT rdpei_add_pen(RdpeiClientContext* context, INT32 externalId,
}
static UINT rdpei_pen_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags,
UINT32 fieldFlags, INT32 x, INT32 y, va_list ap)
UINT32 fieldFlags, INT32 x, INT32 y, BOOL begin, va_list ap)
{
RDPINPUT_PEN_CONTACT_POINT* contactPoint;
RDPEI_PLUGIN* rdpei;
BOOL begin;
UINT error = CHANNEL_RC_OK;
if (!context || !context->handle)
return ERROR_INTERNAL_ERROR;
rdpei = (RDPEI_PLUGIN*)context->handle;
begin = contactFlags & RDPINPUT_CONTACT_FLAG_DOWN;
EnterCriticalSection(&rdpei->lock);
contactPoint = rdpei_pen_contact(rdpei, externalId, !begin);
@ -1234,7 +1232,7 @@ static UINT rdpei_pen_begin(RdpeiClientContext* context, INT32 externalId, UINT3
error = rdpei_pen_process(context, externalId,
RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
RDPINPUT_CONTACT_FLAG_INCONTACT,
fieldFlags, x, y, ap);
fieldFlags, x, y, FALSE, ap);
va_end(ap);
return error;
@ -1255,7 +1253,7 @@ static UINT rdpei_pen_update(RdpeiClientContext* context, INT32 externalId, UINT
error = rdpei_pen_process(context, externalId,
RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
RDPINPUT_CONTACT_FLAG_INCONTACT,
fieldFlags, x, y, ap);
fieldFlags, x, y, FALSE, ap);
va_end(ap);
return error;
}
@ -1270,34 +1268,71 @@ static UINT rdpei_pen_end(RdpeiClientContext* context, INT32 externalId, UINT32
{
UINT error;
va_list ap;
va_start(ap, y);
error = rdpei_pen_process(context, externalId,
RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
RDPINPUT_CONTACT_FLAG_INCONTACT,
fieldFlags, x, y, ap);
RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_INRANGE, fieldFlags,
x, y, FALSE, ap);
va_end(ap);
if (error == CHANNEL_RC_OK)
{
va_start(ap, y);
error =
rdpei_pen_process(context, externalId, RDPINPUT_CONTACT_FLAG_UP, fieldFlags, x, y, ap);
va_end(ap);
}
return error;
}
static UINT rdpei_pen_cancel(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
INT32 x, INT32 y, ...)
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT rdpei_pen_hover_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
INT32 x, INT32 y, ...)
{
UINT error;
va_list ap;
va_start(ap, y);
error = rdpei_pen_process(context, externalId,
RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_CANCELED, fieldFlags,
x, y, ap);
RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE,
fieldFlags, x, y, TRUE, ap);
va_end(ap);
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT rdpei_pen_hover_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
INT32 x, INT32 y, ...)
{
UINT error;
va_list ap;
va_start(ap, y);
error = rdpei_pen_process(context, externalId,
RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE,
fieldFlags, x, y, FALSE, ap);
va_end(ap);
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT rdpei_pen_hover_cancel(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
INT32 x, INT32 y, ...)
{
UINT error;
va_list ap;
va_start(ap, y);
error = rdpei_pen_process(context, externalId,
RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_CANCELED,
fieldFlags, x, y, FALSE, ap);
va_end(ap);
return error;
}
@ -1308,7 +1343,7 @@ static UINT rdpei_pen_raw_event(RdpeiClientContext* context, INT32 externalId, U
va_list ap;
va_start(ap, y);
error = rdpei_pen_process(context, externalId, contactFlags, fieldFlags, x, y, ap);
error = rdpei_pen_process(context, externalId, contactFlags, fieldFlags, FALSE, x, y, ap);
va_end(ap);
return error;
}
@ -1357,7 +1392,9 @@ static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdp
context->PenBegin = rdpei_pen_begin;
context->PenUpdate = rdpei_pen_update;
context->PenEnd = rdpei_pen_end;
context->PenCancel = rdpei_pen_cancel;
context->PenHoverBegin = rdpei_pen_hover_begin;
context->PenHoverUpdate = rdpei_pen_hover_update;
context->PenHoverCancel = rdpei_pen_hover_cancel;
context->PenRawEvent = rdpei_pen_raw_event;
rdpei->context = context;

View File

@ -79,6 +79,7 @@ static BOOL register_input_events(xfContext* xfc, Window window)
WINPR_ASSERT(settings);
XIDeviceInfo* info = XIQueryDevice(xfc->display, XIAllDevices, &ndevices);
xfc->num_pens = 0;
for (int i = 0; i < MIN(ndevices, 64); i++)
{
@ -125,8 +126,46 @@ static BOOL register_input_events(xfContext* xfc, Window window)
XISetMask(masks[nmasks], XI_ButtonRelease);
XISetMask(masks[nmasks], XI_Motion);
used = TRUE;
break;
}
case XIValuatorClass:
{
const XIValuatorClassInfo* t = (const XIValuatorClassInfo*)class;
char* name = t->label ? XGetAtomName(xfc->display, t->label) : NULL;
WLog_DBG(TAG, "%s device (id: %d) valuator %d label %s range %f - %f",
dev->name, dev->deviceid, t->number, name ? name : "None", t->min,
t->max);
// if (strstr(name, "Pressure"))
if (t->number ==
2) // other implementations always go by index. would name be better?
{
double max_pressure = t->max;
if (strstr(dev->name, "Stylus Pen") || strstr(dev->name, "Pen Pen"))
{
xfc->pens[xfc->num_pens].deviceid = dev->deviceid;
xfc->pens[xfc->num_pens].is_eraser = FALSE;
xfc->pens[xfc->num_pens].max_pressure = max_pressure;
xfc->pens[xfc->num_pens].hovering = FALSE;
xfc->pens[xfc->num_pens].pressed = FALSE;
xfc->num_pens++;
WLog_DBG(TAG, "registered pen");
}
else if (strstr(dev->name, "Stylus Eraser") ||
strstr(dev->name, "Pen Eraser"))
{
xfc->pens[xfc->num_pens].deviceid = dev->deviceid;
xfc->pens[xfc->num_pens].is_eraser = TRUE;
xfc->pens[xfc->num_pens].max_pressure = max_pressure;
xfc->pens[xfc->num_pens].hovering = FALSE;
xfc->pens[xfc->num_pens].pressed = FALSE;
xfc->num_pens++;
WLog_DBG(TAG, "registered eraser");
}
}
break;
}
break;
default:
break;
}
@ -620,6 +659,101 @@ static int xf_input_touch_remote(xfContext* xfc, XIDeviceEvent* event, int evtyp
return 0;
}
static int xf_input_pen_remote(xfContext* xfc, XIDeviceEvent* event, int evtype, int pen_index)
{
int x, y;
int contactId;
RdpeiClientContext* rdpei = xfc->common.rdpei;
if (!rdpei)
return 0;
xf_input_hide_cursor(xfc);
x = (int)event->event_x;
y = (int)event->event_y;
xf_event_adjust_coordinates(xfc, &x, &y);
double pressure = 0.0;
double* val = event->valuators.values;
for (int i = 0; i < MIN(event->valuators.mask_len * 8, 3); i++)
{
if (XIMaskIsSet(event->valuators.mask, i))
{
double value = *val++;
if (i == 2)
pressure = value;
}
}
WLog_DBG(TAG, "pen pressure %f", pressure);
// [MS-RDPEI] 2.2.3.7.1.1: This value MUST be normalized in the range 0x00000000 to 0x00000400
// (1024), inclusive
pressure = pressure / xfc->pens[pen_index].max_pressure * 1024;
const UINT32 fieldFlags =
RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT | RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT;
const UINT32 penFlags = xfc->pens[pen_index].is_eraser ? RDPINPUT_PEN_FLAG_INVERTED : 0;
const UINT32 penPressure = (UINT32)pressure;
switch (evtype)
{
case XI_ButtonPress:
WLog_DBG(TAG, "Pen press %d", pen_index);
xfc->pens[pen_index].hovering = FALSE;
xfc->pens[pen_index].pressed = TRUE;
rdpei->PenBegin(rdpei, pen_index, fieldFlags, x, y, penFlags, penPressure);
break;
case XI_Motion:
if (xfc->pens[pen_index].pressed)
{
WLog_DBG(TAG, "Pen update %d", pen_index);
rdpei->PenUpdate(rdpei, pen_index, fieldFlags, x, y, penFlags, penPressure);
}
else if (xfc->pens[pen_index].hovering)
{
WLog_DBG(TAG, "Pen hover update %d", pen_index);
rdpei->PenHoverUpdate(rdpei, pen_index, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
penFlags);
}
else if (xfc->pens[pen_index].last_x != x && xfc->pens[pen_index].last_y != y)
{
WLog_DBG(TAG, "Pen hover begin %d", pen_index);
xfc->pens[pen_index].hovering = TRUE;
rdpei->PenHoverBegin(rdpei, pen_index, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
penFlags);
}
break;
case XI_ButtonRelease:
WLog_DBG(TAG, "Pen release %d", pen_index);
xfc->pens[pen_index].pressed = FALSE;
rdpei->PenUpdate(rdpei, pen_index, fieldFlags, x, y, penFlags, penPressure);
rdpei->PenEnd(rdpei, pen_index, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y, penFlags);
rdpei->PenHoverCancel(rdpei, pen_index, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
penFlags);
break;
default:
break;
}
xfc->pens[pen_index].last_x = x;
xfc->pens[pen_index].last_y = y;
return 0;
}
static int xf_input_pens_unhover(xfContext* xfc)
{
RdpeiClientContext* rdpei = xfc->common.rdpei;
if (!rdpei)
return 0;
for (int i = 0; i < xfc->num_pens; i++)
{
if (xfc->pens[i].hovering)
{
WLog_DBG(TAG, "unhover pen %d", i);
xfc->pens[i].hovering = FALSE;
rdpei->PenHoverCancel(rdpei, i, 0, xfc->pens[i].last_x, xfc->pens[i].last_y);
}
}
}
int xf_input_event(xfContext* xfc, const XEvent* xevent, XIDeviceEvent* event, int evtype)
{
const rdpSettings* settings;
@ -711,12 +845,34 @@ static int xf_input_handle_event_remote(xfContext* xfc, const XEvent* event)
switch (cookie.cc->evtype)
{
case XI_TouchBegin:
xf_input_pens_unhover(xfc);
case XI_TouchUpdate:
case XI_TouchEnd:
xf_input_touch_remote(xfc, cookie.cc->data, cookie.cc->evtype);
break;
case XI_ButtonPress:
case XI_Motion:
case XI_ButtonRelease:
{
WLog_DBG(TAG, "checking for pen");
XIDeviceEvent* deviceEvent = (XIDeviceEvent*)cookie.cc->data;
int deviceid = deviceEvent->deviceid;
bool used = FALSE;
for (int i = 0; i < xfc->num_pens; i++)
{
if (xfc->pens[i].deviceid == deviceid)
{
WLog_DBG(TAG, "pen found, is_eraser=%d", xfc->pens[i].is_eraser);
xf_input_pen_remote(xfc, cookie.cc->data, cookie.cc->evtype, i);
used = TRUE;
break;
}
}
if (used)
break;
}
default:
xf_input_pens_unhover(xfc);
xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
break;
}

View File

@ -124,6 +124,7 @@ typedef struct
#if defined(WITH_XI)
#define MAX_CONTACTS 20
#define MAX_PENS 4
typedef struct touch_contact
{
@ -135,6 +136,18 @@ typedef struct touch_contact
double last_y;
} touchContact;
typedef struct pen_device
{
int deviceid;
BOOL is_eraser;
double max_pressure;
int hovering;
int pressed;
int last_x;
int last_y;
} penDevice;
#endif
struct xf_context
@ -290,6 +303,8 @@ struct xf_context
double z_vector;
double px_vector;
double py_vector;
penDevice pens[MAX_PENS];
int num_pens;
#endif
BOOL xi_rawevent;
BOOL xi_event;

View File

@ -78,6 +78,9 @@ extern "C"
pcRdpeiPen PenBegin;
pcRdpeiPen PenUpdate;
pcRdpeiPen PenEnd;
pcRdpeiPen PenHoverBegin;
pcRdpeiPen PenHoverUpdate;
pcRdpeiPen PenHoverCancel;
pcRdpeiSuspendTouch SuspendTouch;
pcRdpeiResumeTouch ResumeTouch;