* Tablet Pressure support in GHOST

This is 'ported' from Nicholas Bishop's sculpting GSoC tree. I'm bringing it
over now so a) it can be there for when lukep does his GHOST refactor b) it's
something that GHOST should have anyway, particularly now there's interest in
painting tools and c) it's missing support in Windows, so hopefully now some
enterprising Windows coder can add that more easily in the main bf tree.

Right now X11 and Mac OS X are supported. I added and can maintain the Mac OS X
part, but I'm not familiar with the Xinput stuff, which Nicholas wrote. Both
X11 and Mac are collecting active device and pressure, and Mac is also
collecting x and y tilt data. Up to coders how they want to use this info! :)

Although the data's coming in, I haven't actually made this do anything. I
thought it best to leave it to brecht to figure out what he wants to do with the
painting stuff, and I wonder what other interesting uses there could be for it
(proportional edit?). I'll write implementation details in a separate mail to
the committers list.
This commit is contained in:
Matt Ebb 2006-08-03 12:23:00 +00:00
parent 454fceb5f5
commit c85de34c26
12 changed files with 239 additions and 13 deletions

@ -607,6 +607,13 @@ extern GHOST_TSuccess GHOST_ActivateWindowDrawingContext(GHOST_WindowHandle wind
*/
extern GHOST_TSuccess GHOST_InvalidateWindow(GHOST_WindowHandle windowhandle);
/**
* Returns the status of the tablet
* @param windowhandle The handle to the window
* @return Status of tablet
*/
extern const GHOST_TabletData *GHOST_GetTabletData(GHOST_WindowHandle windowhandle);
/**
* Access to rectangle width.
* @param rectanglehandle The handle to the rectangle
@ -751,7 +758,6 @@ extern void GHOST_SetRectangleCenter(GHOST_RectangleHandle rectanglehandle,
*/
extern GHOST_TSuccess GHOST_ClipRectangle(GHOST_RectangleHandle rectanglehandle,
GHOST_RectangleHandle anotherrectanglehandle);
#ifdef __cplusplus
}
#endif

@ -201,6 +201,12 @@ public:
*/
virtual void setUserData(const GHOST_TUserDataPtr userData) = 0;
/**
* Returns the tablet data (pressure etc).
* @return The tablet data (pressure etc).
*/
virtual const GHOST_TabletData* GetTabletData() = 0;
/***************************************************************************************
** Cursor management functionality
***************************************************************************************/

@ -55,6 +55,13 @@ typedef enum
GHOST_kSuccess
} GHOST_TSuccess;
typedef struct GHOST_TabletData {
char Active; /* 0=None, 1=Stylus, 2=Eraser */
float Pressure;
float Xtilt;
float Ytilt;
} GHOST_TabletData;
typedef enum {
GHOST_kNotVisible = 0,

@ -649,6 +649,11 @@ GHOST_TSuccess GHOST_InvalidateWindow(GHOST_WindowHandle windowhandle)
}
extern const GHOST_TabletData* GHOST_GetTabletData(GHOST_WindowHandle windowhandle)
{
return ((GHOST_IWindow*)windowhandle)->GetTabletData();
}
GHOST_TInt32 GHOST_GetWidthRectangle(GHOST_RectangleHandle rectanglehandle)
{
@ -795,6 +800,3 @@ GHOST_TSuccess GHOST_ClipRectangle(GHOST_RectangleHandle rectanglehandle,
return result;
}

@ -688,6 +688,79 @@ OSStatus GHOST_SystemCarbon::handleWindowEvent(EventRef event)
return err;
}
OSStatus GHOST_SystemCarbon::handleTabletEvent(EventRef event)
{
GHOST_IWindow* window = m_windowManager->getActiveWindow();
TabletPointRec tabletPointRecord;
TabletProximityRec tabletProximityRecord;
UInt32 anInt32;
GHOST_TabletData& ct=((GHOST_WindowCarbon*)window)->GetCarbonTabletData();
OSStatus err = eventNotHandledErr;
ct.Pressure = 0;
ct.Xtilt = 0;
ct.Ytilt = 0;
// is there an embedded tablet event inside this mouse event?
if(noErr == GetEventParameter(event, kEventParamTabletEventType, typeUInt32, NULL, sizeof(UInt32), NULL, (void *)&anInt32))
{
// yes there is one!
// Embedded tablet events can either be a proximity or pointer event.
if(anInt32 == kEventTabletPoint)
{
//GHOST_PRINT("Embedded pointer event!\n");
// Extract the tablet Pointer Event. If there is no Tablet Pointer data
// in this event, then this call will return an error. Just ignore the
// error and go on. This can occur when a proximity event is embedded in
// a mouse event and you did not check the mouse event to see which type
// of tablet event was embedded.
if(noErr == GetEventParameter(event, kEventParamTabletPointRec,
typeTabletPointRec, NULL,
sizeof(TabletPointRec),
NULL, (void *)&tabletPointRecord))
{
ct.Pressure = tabletPointRecord.pressure / 65535.0f;
ct.Xtilt = tabletPointRecord.tiltX / 32767.0f; /* can be positive or negative */
ct.Ytilt = tabletPointRecord.tiltY / 32767.0f; /* can be positive or negative */
}
} else {
//GHOST_PRINT("Embedded proximity event\n");
// Extract the Tablet Proximity record from the event.
if(noErr == GetEventParameter(event, kEventParamTabletProximityRec,
typeTabletProximityRec, NULL,
sizeof(TabletProximityRec),
NULL, (void *)&tabletProximityRecord))
{
if (tabletProximityRecord.enterProximity) {
//pointer is entering tablet area proximity
switch(tabletProximityRecord.pointerType)
{
case 1: /* stylus */
ct.Active = 1;
break;
case 2: /* puck, not supported so far */
ct.Active = 0;
break;
case 3: /* eraser */
ct.Active = 2;
break;
default:
ct.Active = 0;
break;
}
} else {
// pointer is leaving - return to mouse
ct.Active = 0;
}
}
}
err = noErr;
}
}
OSStatus GHOST_SystemCarbon::handleMouseEvent(EventRef event)
{
OSStatus err = eventNotHandledErr;
@ -708,6 +781,9 @@ OSStatus GHOST_SystemCarbon::handleMouseEvent(EventRef event)
/* Window still gets mouse up after command-H */
if (m_windowManager->getActiveWindow()) {
// handle any tablet events that may have come with the mouse event (optional)
handleTabletEvent(event);
::GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button);
pushEvent(new GHOST_EventButton(getMilliSeconds(), type, window, convertButton(button)));
err = noErr;
@ -716,15 +792,19 @@ OSStatus GHOST_SystemCarbon::handleMouseEvent(EventRef event)
break;
case kEventMouseMoved:
case kEventMouseDragged:
Point mousePos;
case kEventMouseDragged: {
Point mousePos;
if (window) {
//handle any tablet events that may have come with the mouse event (optional)
handleTabletEvent(event);
::GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &mousePos);
pushEvent(new GHOST_EventCursor(getMilliSeconds(), GHOST_kEventCursorMove, window, mousePos.h, mousePos.v));
err = noErr;
}
break;
}
break;
}
case kEventMouseWheelMoved:
{
OSStatus status;

@ -182,6 +182,13 @@ protected:
*/
virtual GHOST_TSuccess exit();
/**
* Handles a tablet event.
* @param event A Mac event.
* @return Indication whether the event was handled.
*/
OSStatus handleTabletEvent(EventRef event);
/**
* Handles a mouse event.
* @param event A Mac event.

@ -338,7 +338,7 @@ processEvent(
if (!window) {
return;
}
switch (xe->type) {
case Expose:
{
@ -357,6 +357,7 @@ processEvent(
}
break;
}
case MotionNotify:
{
XMotionEvent &xme = xe->xmotion;
@ -506,8 +507,23 @@ processEvent(
case ReparentNotify:
break;
default:
default: {
if(xe->type == window->GetXTablet().MotionEvent) {
XDeviceMotionEvent* data = (XDeviceMotionEvent*)xe;
window->GetXTablet().CommonData.Pressure= data->axis_data[2]/((float)window->GetXTablet().PressureLevels);
}
else if(xe->type == window->GetXTablet().ProxInEvent) {
XProximityNotifyEvent* data = (XProximityNotifyEvent*)xe;
if(data->deviceid == window->GetXTablet().StylusID)
window->GetXTablet().CommonData.Active= 1;
else if(data->deviceid == window->GetXTablet().EraserID)
window->GetXTablet().CommonData.Active= 2;
}
else if(xe->type == window->GetXTablet().ProxOutEvent)
window->GetXTablet().CommonData.Active= 0;
break;
}
}
if (g_event) {

@ -185,6 +185,8 @@ GHOST_WindowCarbon::GHOST_WindowCarbon(
setDrawingContextType(GHOST_kDrawingContextTypeOpenGL);;installDrawingContext(GHOST_kDrawingContextTypeOpenGL);
updateDrawingContext();
activateDrawingContext();
m_tablet.Active = 0;
}
}

@ -213,6 +213,11 @@ public:
virtual short getMac_windowState();
const GHOST_TabletData* GetTabletData()
{ return &m_tablet; }
GHOST_TabletData& GetCarbonTabletData()
{ return m_tablet; }
protected:
/**
* Tries to install a rendering context in this window.
@ -276,6 +281,8 @@ protected:
static AGLContext s_firstaglCtx;
Cursor* m_customCursor;
GHOST_TabletData m_tablet;
/** When running in full-screen this tells whether to refresh the window. */
bool m_fullScreenDirty;

@ -216,7 +216,8 @@ public:
*/
void loadCursor(bool visible, GHOST_TStandardCursor cursorShape) const;
const GHOST_TabletData* GetTabletData()
{ return NULL; }
protected:
/**
* Tries to install a rendering context in this window.

@ -193,7 +193,9 @@ GHOST_WindowX11(
XFree(xclasshint);
setTitle(title);
initXInputDevices();
// now set up the rendering context.
if (installDrawingContext(type) == GHOST_kSuccess) {
m_valid_setup = true;
@ -206,6 +208,67 @@ GHOST_WindowX11(
XFlush(m_display);
}
void GHOST_WindowX11::initXInputDevices()
{
XExtensionVersion *version = XGetExtensionVersion(m_display, INAME);
if(version && (version != (XExtensionVersion*)NoSuchExtension)) {
if(version->present) {
int device_count;
XDeviceInfo* device_info = XListInputDevices(m_display, &device_count);
m_xtablet.StylusDevice = 0;
m_xtablet.EraserDevice = 0;
m_xtablet.CommonData.Active= 0;
for(int i=0; i<device_count; ++i) {
if(!strcmp(device_info[i].name, "stylus")) {
m_xtablet.StylusID= device_info[i].id;
m_xtablet.StylusDevice = XOpenDevice(m_display, m_xtablet.StylusID);
/* Find how many pressure levels tablet has */
XAnyClassPtr ici = device_info[i].inputclassinfo;
for(int j=0; j<m_xtablet.StylusDevice->num_classes; ++j) {
if(ici->c_class==ValuatorClass) {
XValuatorInfo* xvi = (XValuatorInfo*)ici;
m_xtablet.PressureLevels = xvi->axes[2].max_value;
break;
}
ici = (XAnyClassPtr)(((char *)ici) + ici->length);
}
}
if(!strcmp(device_info[i].name, "eraser")) {
m_xtablet.EraserID= device_info[i].id;
m_xtablet.EraserDevice = XOpenDevice(m_display, m_xtablet.EraserID);
}
}
XFreeDeviceList(device_info);
XEventClass xevents[10], ev;
int dcount = 0;
if(m_xtablet.StylusDevice) {
DeviceMotionNotify(m_xtablet.StylusDevice, m_xtablet.MotionEvent, ev);
if(ev) xevents[dcount++] = ev;
ProximityIn(m_xtablet.StylusDevice, m_xtablet.ProxInEvent, ev);
if(ev) xevents[dcount++] = ev;
ProximityOut(m_xtablet.StylusDevice, m_xtablet.ProxOutEvent, ev);
if(ev) xevents[dcount++] = ev;
}
if(m_xtablet.EraserDevice) {
DeviceMotionNotify(m_xtablet.EraserDevice, m_xtablet.MotionEvent, ev);
if(ev) xevents[dcount++] = ev;
ProximityIn(m_xtablet.EraserDevice, m_xtablet.ProxInEvent, ev);
if(ev) xevents[dcount++] = ev;
ProximityOut(m_xtablet.EraserDevice, m_xtablet.ProxOutEvent, ev);
if(ev) xevents[dcount++] = ev;
}
XSelectExtensionEvent(m_display, m_window, xevents, dcount);
}
XFree(version);
}
}
Window
GHOST_WindowX11::
getXWindow(

@ -39,6 +39,8 @@
#include "GHOST_Window.h"
#include <X11/Xlib.h>
#include <GL/glx.h>
// For tablets
#include <X11/extensions/XInput.h>
#include <map>
@ -188,6 +190,28 @@ public:
getXWindow(
);
class XTablet
{
public:
GHOST_TabletData CommonData;
XDevice* StylusDevice;
XDevice* EraserDevice;
XID StylusID, EraserID;
int MotionEvent;
int ProxInEvent;
int ProxOutEvent;
int PressureLevels;
};
XTablet& GetXTablet()
{ return m_xtablet; }
const GHOST_TabletData* GetTabletData()
{ return &m_xtablet.CommonData; }
protected:
/**
* Tries to install a rendering context in this window.
@ -272,6 +296,8 @@ private :
Cursor
getEmptyCursor(
);
void initXInputDevices();
GLXContext m_context;
Window m_window;
@ -298,6 +324,9 @@ private :
/** Cache of XC_* ID's to XCursor structures */
std::map<unsigned int, Cursor> m_standard_cursors;
/* Tablet devices */
XTablet m_xtablet;
};