* Tablet Pressure support in GHOST
authorMatt Ebb <matt@mke3.net>
Thu, 3 Aug 2006 12:23:00 +0000 (12:23 +0000)
committerMatt Ebb <matt@mke3.net>
Thu, 3 Aug 2006 12:23:00 +0000 (12:23 +0000)
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.

12 files changed:
intern/ghost/GHOST_C-api.h
intern/ghost/GHOST_IWindow.h
intern/ghost/GHOST_Types.h
intern/ghost/intern/GHOST_C-api.cpp
intern/ghost/intern/GHOST_SystemCarbon.cpp
intern/ghost/intern/GHOST_SystemCarbon.h
intern/ghost/intern/GHOST_SystemX11.cpp
intern/ghost/intern/GHOST_WindowCarbon.cpp
intern/ghost/intern/GHOST_WindowCarbon.h
intern/ghost/intern/GHOST_WindowWin32.h
intern/ghost/intern/GHOST_WindowX11.cpp
intern/ghost/intern/GHOST_WindowX11.h

index ba22074a62039b8409505ea53ef209854e4ad721..4c4094409dd62835f6e7c43595c63e72f85c74d2 100644 (file)
@@ -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
index 903d1e4498da24c566326eb54c849bf4ba0b7226..5f6bbe553c60e244bf4c6ceeac36e48bbb8bfddd 100644 (file)
@@ -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
         ***************************************************************************************/
index 8e439cfc9e3a450b59548726e039c6422f91a89b..4abecce50c144381709c8a261a8b174288f8a054 100644 (file)
@@ -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,
index 6f8ddd858ef7b54bba361c2cf6058fdcd2ac7e5c..fbb4cca91e0a94cf197c14cdffa882ff374b4eb7 100644 (file)
@@ -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;
 }
-
-
-
index ef9e91ec3df06447a1066a85f77c6e5ec1d10e3e..9e790154eb9bf6ffd99ff15d1dc2b265219396ea 100644 (file)
@@ -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;
index 740006a335fa1662850447a1a89b260871106911..93022aa78ff479b17e687815d6da3461a4cf950a 100644 (file)
@@ -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.
index e6d1962958a2401005097d368c5b051f4d4a6ed8..68bdd1a09a30c1758960c81f6fde30a0d200f3a2 100755 (executable)
@@ -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) {
index 30c88ccd777c140d192cde27d78618b6df55fc13..4484ec54da6f7302f3eb6bfdab3cf91ebd10609f 100644 (file)
@@ -185,6 +185,8 @@ GHOST_WindowCarbon::GHOST_WindowCarbon(
         setDrawingContextType(GHOST_kDrawingContextTypeOpenGL);;installDrawingContext(GHOST_kDrawingContextTypeOpenGL);
         updateDrawingContext();
         activateDrawingContext();        
+
+       m_tablet.Active = 0;
     }
 }
 
index 582979e62483fd70a36ea98b81928bf14e7fc066..5b81470a030064eab5780b77f3925c0f361e2194 100644 (file)
@@ -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;
index b96e0f401ea1f799535d6334b66fc3a515c59102..5a0ff3e2052296d66c89e0f2c60f45186915151e 100644 (file)
@@ -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.
index cfdc173907466dbdea5db30d3a6d972588121c7d..87798b02ff4909c1e9bfe754b48597611c599eb2 100755 (executable)
@@ -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(
index d8b5f61697e6ab68377a26da635909e94804cf9f..863644da09545c5218178ad6a842d760f32904dc 100755 (executable)
@@ -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;
 };