X11: support multiple tablet devices.

Previously we would try to guess what the main tablet device is, but this is
error prone. Now we keep a list of X11 devices and try to match events to
them. On the Blender side there are still some limitations in regards to using
multiple devices at the same time, but this should improve things already.

Fixes T59645.
This commit is contained in:
Brecht Van Lommel 2018-12-27 15:22:20 +01:00
parent 2c0c1f494d
commit 29feb215f4
Notes: blender-bot 2023-02-14 04:23:01 +01:00
Referenced by issue #59645, The recent changes broke the pressure and tilt of the tablet pen
3 changed files with 146 additions and 177 deletions

View File

@ -228,9 +228,6 @@ GHOST_SystemX11(
}
#endif /* USE_XINPUT_HOTPLUG */
/* initialize incase X11 fails to load */
memset(&m_xtablet, 0, sizeof(m_xtablet));
refreshXInputDevices();
#endif /* WITH_X11_XINPUT */
}
@ -245,12 +242,8 @@ GHOST_SystemX11::
#endif
#ifdef WITH_X11_XINPUT
/* close tablet devices */
if (m_xtablet.StylusDevice)
XCloseDevice(m_display, m_xtablet.StylusDevice);
if (m_xtablet.EraserDevice)
XCloseDevice(m_display, m_xtablet.EraserDevice);
/* Close tablet devices. */
clearXInputDevices();
#endif /* WITH_X11_XINPUT */
if (m_xkb_descr) {
@ -670,17 +663,6 @@ processEvents(
}
#ifdef WITH_X11_XINPUT
/* set currently using tablet mode (stylus or eraser) depending on device ID */
static void setTabletMode(GHOST_SystemX11 *system, GHOST_WindowX11 *window, XID deviceid)
{
if (deviceid == system->GetXTablet().StylusID)
window->GetTabletData()->Active = GHOST_kTabletModeStylus;
else if (deviceid == system->GetXTablet().EraserID)
window->GetTabletData()->Active = GHOST_kTabletModeEraser;
}
#endif /* WITH_X11_XINPUT */
#ifdef WITH_X11_XINPUT
static bool checkTabletProximity(Display *display, XDevice *device)
{
@ -778,9 +760,15 @@ GHOST_SystemX11::processEvent(XEvent *xe)
* but for now enough parts of the code are checking 'Active'
* - campbell */
if (window->GetTabletData()->Active != GHOST_kTabletModeNone) {
if (checkTabletProximity(xe->xany.display, m_xtablet.StylusDevice) == false &&
checkTabletProximity(xe->xany.display, m_xtablet.EraserDevice) == false)
{
bool any_proximity = false;
for (GHOST_TabletX11& xtablet: m_xtablets) {
if (checkTabletProximity(xe->xany.display, xtablet.Device)) {
any_proximity = true;
}
}
if (!any_proximity) {
// printf("proximity disable\n");
window->GetTabletData()->Active = GHOST_kTabletModeNone;
}
@ -1374,59 +1362,64 @@ GHOST_SystemX11::processEvent(XEvent *xe)
default:
{
#ifdef WITH_X11_XINPUT
if (xe->type == m_xtablet.MotionEvent ||
xe->type == m_xtablet.MotionEventEraser ||
xe->type == m_xtablet.PressEvent ||
xe->type == m_xtablet.PressEventEraser)
{
XDeviceMotionEvent *data = (XDeviceMotionEvent *)xe;
const unsigned char axis_first = data->first_axis;
const unsigned char axes_end = axis_first + data->axes_count; /* after the last */
int axis_value;
for (GHOST_TabletX11& xtablet: m_xtablets) {
if (xe->type == xtablet.MotionEvent || xe->type == xtablet.PressEvent) {
XDeviceMotionEvent *data = (XDeviceMotionEvent *)xe;
if (data->deviceid != xtablet.ID) {
continue;
}
/* stroke might begin without leading ProxyIn event,
* this happens when window is opened when stylus is already hovering
* around tablet surface */
setTabletMode(this, window, data->deviceid);
const unsigned char axis_first = data->first_axis;
const unsigned char axes_end = axis_first + data->axes_count; /* after the last */
int axis_value;
/* Note: This event might be generated with incomplete dataset (don't exactly know why, looks like in
* some cases, if the value does not change, it is not included in subsequent XDeviceMotionEvent
* events). So we have to check which values this event actually contains!
*/
/* stroke might begin without leading ProxyIn event,
* this happens when window is opened when stylus is already hovering
* around tablet surface */
window->GetTabletData()->Active = xtablet.mode;
/* Note: This event might be generated with incomplete dataset (don't exactly know why, looks like in
* some cases, if the value does not change, it is not included in subsequent XDeviceMotionEvent
* events). So we have to check which values this event actually contains!
*/
#define AXIS_VALUE_GET(axis, val) \
((axis_first <= axis && axes_end > axis) && ((void)(val = data->axis_data[axis - axis_first]), true))
if (AXIS_VALUE_GET(2, axis_value)) {
window->GetTabletData()->Pressure = axis_value / ((float)m_xtablet.PressureLevels);
}
if (AXIS_VALUE_GET(2, axis_value)) {
window->GetTabletData()->Pressure = axis_value / ((float)xtablet.PressureLevels);
}
/* the (short) cast and the & 0xffff is bizarre and unexplained anywhere,
* but I got garbage data without it. Found it in the xidump.c source --matt
*
* The '& 0xffff' just truncates the value to its two lowest bytes, this probably means
* some drivers do not properly set the whole int value? Since we convert to float afterward,
* I don't think we need to cast to short here, but do not have a device to check this. --mont29
*/
if (AXIS_VALUE_GET(3, axis_value)) {
window->GetTabletData()->Xtilt = (short)(axis_value & 0xffff) /
((float)m_xtablet.XtiltLevels);
}
if (AXIS_VALUE_GET(4, axis_value)) {
window->GetTabletData()->Ytilt = (short)(axis_value & 0xffff) /
((float)m_xtablet.YtiltLevels);
}
/* the (short) cast and the & 0xffff is bizarre and unexplained anywhere,
* but I got garbage data without it. Found it in the xidump.c source --matt
*
* The '& 0xffff' just truncates the value to its two lowest bytes, this probably means
* some drivers do not properly set the whole int value? Since we convert to float afterward,
* I don't think we need to cast to short here, but do not have a device to check this. --mont29
*/
if (AXIS_VALUE_GET(3, axis_value)) {
window->GetTabletData()->Xtilt = (short)(axis_value & 0xffff) /
((float)xtablet.XtiltLevels);
}
if (AXIS_VALUE_GET(4, axis_value)) {
window->GetTabletData()->Ytilt = (short)(axis_value & 0xffff) /
((float)xtablet.YtiltLevels);
}
#undef AXIS_VALUE_GET
}
else if (xe->type == m_xtablet.ProxInEvent) {
XProximityNotifyEvent *data = (XProximityNotifyEvent *)xe;
}
else if (xe->type == xtablet.ProxInEvent) {
XProximityNotifyEvent *data = (XProximityNotifyEvent *)xe;
if (data->deviceid != xtablet.ID) {
continue;
}
setTabletMode(this, window, data->deviceid);
}
else if (xe->type == m_xtablet.ProxOutEvent) {
window->GetTabletData()->Active = GHOST_kTabletModeNone;
window->GetTabletData()->Active = xtablet.mode;
}
else if (xe->type == xtablet.ProxOutEvent) {
window->GetTabletData()->Active = GHOST_kTabletModeNone;
}
}
#endif // WITH_X11_XINPUT
break;
@ -2206,6 +2199,20 @@ static GHOST_TTabletMode tablet_mode_from_name(const char *name, const char *typ
NULL
};
static const char* type_blacklist[] = {
"pad",
"cursor",
"touch",
NULL
};
/* Skip some known unsupported types. */
for (i=0; type_blacklist[i] != NULL; i++) {
if (type && (strcasecmp(type, type_blacklist[i]) == 0)) {
return GHOST_kTabletModeNone;
}
}
/* First check device type to avoid cases where name is "Pen and Eraser" and type is "ERASER" */
for (i=0; tablet_stylus_whitelist[i] != NULL; i++) {
if (type && match_token(type, tablet_stylus_whitelist[i])) {
@ -2232,16 +2239,8 @@ static GHOST_TTabletMode tablet_mode_from_name(const char *name, const char *typ
void GHOST_SystemX11::refreshXInputDevices()
{
if (m_xinput_version.present) {
if (m_xtablet.StylusDevice) {
XCloseDevice(m_display, m_xtablet.StylusDevice);
m_xtablet.StylusDevice = NULL;
}
if (m_xtablet.EraserDevice) {
XCloseDevice(m_display, m_xtablet.EraserDevice);
m_xtablet.EraserDevice = NULL;
}
/* Close tablet devices. */
clearXInputDevices();
/* Install our error handler to override Xlib's termination behavior */
GHOST_X11_ERROR_HANDLERS_OVERRIDE(handler_store);
@ -2253,75 +2252,51 @@ void GHOST_SystemX11::refreshXInputDevices()
for (int i = 0; i < device_count; ++i) {
char *device_type = device_info[i].type ? XGetAtomName(m_display, device_info[i].type) : NULL;
// printf("Tablet type:'%s', name:'%s', index:%d\n", device_type, device_info[i].name, i);
GHOST_TTabletMode tablet_mode = tablet_mode_from_name(device_info[i].name, device_type);
if ((m_xtablet.StylusDevice == NULL) &&
((tablet_mode == GHOST_kTabletModeStylus) && (device_info[i].type != m_atom.TABLET)))
/* for libinput to work reliable, only lookup ValuatorClass in Tablet type:'STYLUS' */
{
// printf("\tfound stylus\n");
m_xtablet.StylusID = device_info[i].id;
m_xtablet.StylusDevice = XOpenDevice(m_display, m_xtablet.StylusID);
if (m_xtablet.StylusDevice != NULL) {
/* Find how many pressure levels tablet has */
XAnyClassPtr ici = device_info[i].inputclassinfo;
bool found_valuator_class = false;
for (int j = 0; j < m_xtablet.StylusDevice->num_classes; ++j) {
if (ici->c_class == ValuatorClass) {
// printf("\t\tfound ValuatorClass\n");
XValuatorInfo *xvi = (XValuatorInfo *)ici;
m_xtablet.PressureLevels = xvi->axes[2].max_value;
if (xvi->num_axes > 3) {
/* this is assuming that the tablet has the same tilt resolution in both
* positive and negative directions. It would be rather weird if it didn't.. */
m_xtablet.XtiltLevels = xvi->axes[3].max_value;
m_xtablet.YtiltLevels = xvi->axes[4].max_value;
}
else {
m_xtablet.XtiltLevels = 0;
m_xtablet.YtiltLevels = 0;
}
found_valuator_class = true;
break;
}
ici = (XAnyClassPtr)(((char *)ici) + ici->length);
}
if (!found_valuator_class) {
/* In case our name matching detects a device that
* isn't actually a stylus. For example there can
* be "XPPEN Tablet" and "XPPEN Tablet Pen", but
* only the latter is a stylus. */
XCloseDevice(m_display, m_xtablet.StylusDevice);
m_xtablet.StylusDevice = NULL;
m_xtablet.StylusID = 0;
}
}
else {
m_xtablet.StylusID = 0;
}
}
else if ((m_xtablet.EraserDevice == NULL) &&
(tablet_mode == GHOST_kTabletModeEraser))
{
// printf("\tfound eraser\n");
m_xtablet.EraserID = device_info[i].id;
m_xtablet.EraserDevice = XOpenDevice(m_display, m_xtablet.EraserID);
if (m_xtablet.EraserDevice == NULL) m_xtablet.EraserID = 0;
}
// printf("Tablet type:'%s', name:'%s', index:%d\n", device_type, device_info[i].name, i);
if (device_type) {
XFree((void *)device_type);
}
if (!(tablet_mode == GHOST_kTabletModeStylus || tablet_mode == GHOST_kTabletModeEraser)) {
continue;
}
GHOST_TabletX11 xtablet = {tablet_mode};
xtablet.ID = device_info[i].id;
xtablet.Device = XOpenDevice(m_display, xtablet.ID);
if (xtablet.Device != NULL) {
/* Find how many pressure levels tablet has */
XAnyClassPtr ici = device_info[i].inputclassinfo;
for (int j = 0; j < xtablet.Device->num_classes; ++j) {
if (ici->c_class == ValuatorClass) {
XValuatorInfo *xvi = (XValuatorInfo *)ici;
xtablet.PressureLevels = xvi->axes[2].max_value;
if (xvi->num_axes > 3) {
/* this is assuming that the tablet has the same tilt resolution in both
* positive and negative directions. It would be rather weird if it didn't.. */
xtablet.XtiltLevels = xvi->axes[3].max_value;
xtablet.YtiltLevels = xvi->axes[4].max_value;
}
else {
xtablet.XtiltLevels = 0;
xtablet.YtiltLevels = 0;
}
break;
}
ici = (XAnyClassPtr)(((char *)ici) + ici->length);
}
m_xtablets.push_back(xtablet);
}
}
XFreeDeviceList(device_info);
@ -2331,4 +2306,14 @@ void GHOST_SystemX11::refreshXInputDevices()
}
}
void GHOST_SystemX11::clearXInputDevices()
{
for (GHOST_TabletX11& xtablet: m_xtablets) {
if (xtablet.Device)
XCloseDevice(m_display, xtablet.Device);
}
m_xtablets.clear();
}
#endif /* WITH_X11_XINPUT */

View File

@ -289,28 +289,22 @@ public:
#ifdef WITH_X11_XINPUT
typedef struct GHOST_TabletX11 {
XDevice *StylusDevice;
XDevice *EraserDevice;
XID StylusID, EraserID;
GHOST_TTabletMode mode;
XDevice *Device;
XID ID;
int MotionEvent;
int ProxInEvent;
int ProxOutEvent;
int PressEvent;
int MotionEventEraser;
int ProxInEventEraser;
int ProxOutEventEraser;
int PressEventEraser;
int PressureLevels;
int XtiltLevels, YtiltLevels;
} GHOST_TabletX11;
GHOST_TabletX11 &GetXTablet()
std::vector<GHOST_TabletX11> &GetXTablets()
{
return m_xtablet;
return m_xtablets;
}
#endif // WITH_X11_XINPUT
@ -364,7 +358,7 @@ private:
#ifdef WITH_X11_XINPUT
/* Tablet devices */
GHOST_TabletX11 m_xtablet;
std::vector<GHOST_TabletX11> m_xtablets;
#endif
/// The vector of windows that need to be updated.
@ -394,6 +388,7 @@ private:
#endif
#ifdef WITH_X11_XINPUT
void clearXInputDevices();
void refreshXInputDevices();
#endif

View File

@ -648,37 +648,26 @@ bool GHOST_WindowX11::createX11_XIC()
void GHOST_WindowX11::refreshXInputDevices()
{
if (m_system->m_xinput_version.present) {
GHOST_SystemX11::GHOST_TabletX11 &xtablet = m_system->GetXTablet();
XEventClass xevents[8], ev;
int dcount = 0;
std::vector<XEventClass> xevents;
/* With modern XInput (xlib 1.6.2 at least and/or evdev 2.9.0) and some 'no-name' tablets
* like 'UC-LOGIC Tablet WP5540U', we also need to 'select' ButtonPress for motion event,
* otherwise we do not get any tablet motion event once pen is pressed... See T43367.
*/
for (GHOST_SystemX11::GHOST_TabletX11& xtablet: m_system->GetXTablets()) {
/* With modern XInput (xlib 1.6.2 at least and/or evdev 2.9.0) and some 'no-name' tablets
* like 'UC-LOGIC Tablet WP5540U', we also need to 'select' ButtonPress for motion event,
* otherwise we do not get any tablet motion event once pen is pressed... See T43367.
*/
XEventClass ev;
if (xtablet.StylusDevice) {
DeviceMotionNotify(xtablet.StylusDevice, xtablet.MotionEvent, ev);
if (ev) xevents[dcount++] = ev;
DeviceButtonPress(xtablet.StylusDevice, xtablet.PressEvent, ev);
if (ev) xevents[dcount++] = ev;
ProximityIn(xtablet.StylusDevice, xtablet.ProxInEvent, ev);
if (ev) xevents[dcount++] = ev;
ProximityOut(xtablet.StylusDevice, xtablet.ProxOutEvent, ev);
if (ev) xevents[dcount++] = ev;
}
if (xtablet.EraserDevice) {
DeviceMotionNotify(xtablet.EraserDevice, xtablet.MotionEventEraser, ev);
if (ev) xevents[dcount++] = ev;
DeviceButtonPress(xtablet.EraserDevice, xtablet.PressEventEraser, ev);
if (ev) xevents[dcount++] = ev;
ProximityIn(xtablet.EraserDevice, xtablet.ProxInEventEraser, ev);
if (ev) xevents[dcount++] = ev;
ProximityOut(xtablet.EraserDevice, xtablet.ProxOutEventEraser, ev);
if (ev) xevents[dcount++] = ev;
DeviceMotionNotify(xtablet.Device, xtablet.MotionEvent, ev);
if (ev) xevents.push_back(ev);
DeviceButtonPress(xtablet.Device, xtablet.PressEvent, ev);
if (ev) xevents.push_back(ev);
ProximityIn(xtablet.Device, xtablet.ProxInEvent, ev);
if (ev) xevents.push_back(ev);
ProximityOut(xtablet.Device, xtablet.ProxOutEvent, ev);
if (ev) xevents.push_back(ev);
}
XSelectExtensionEvent(m_display, m_window, xevents, dcount);
XSelectExtensionEvent(m_display, m_window, xevents.data(), (int)xevents.size());
}
}