mirror of https://github.com/FreeRDP/FreeRDP
Implement pen functionality for X11, including pressure, hover, and eraser
This commit is contained in:
parent
1fc0d5b4b1
commit
9c7a541d9c
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -78,6 +78,9 @@ extern "C"
|
|||
pcRdpeiPen PenBegin;
|
||||
pcRdpeiPen PenUpdate;
|
||||
pcRdpeiPen PenEnd;
|
||||
pcRdpeiPen PenHoverBegin;
|
||||
pcRdpeiPen PenHoverUpdate;
|
||||
pcRdpeiPen PenHoverCancel;
|
||||
|
||||
pcRdpeiSuspendTouch SuspendTouch;
|
||||
pcRdpeiResumeTouch ResumeTouch;
|
||||
|
|
Loading…
Reference in New Issue