The purpose of this document is to help developers write tablet aware applications for the Apple MacOS X operating system.
This document explains how to get and interpret tablet data from either a Carbon CFM, Carbon Mach-O, or Cocoa/Appkit application. It will also provide many tips and tricks, such as how to create a tablet aware application that works in both MacOS X and the Classic MacOS.

Audience

This document is intended for software engineers adding tablet capabilities to MacOS X applications using either the Carbon or Cocoa frameworks.
 
Carbon: This document is intended for MacOS X developers who are at least familiar with the Carbon API set. The developer should have an understanding of event handlers and be able to install and use them. For more information on Carbon event handlers, please refer to Apple's Inside Carbon: Carbon Event Manager.
 
Cocoa: Cocoa developers should have a basic understanding of the AppKit, NSNotifications and Categories. Advanced tablet usage requires a basic understanding of Carbon events and how to access them. Most of this document is written from the Carbon perspective. Cocoa specific notes will be labeled as such.
 

Glossary

AppKit – A developer framework built using the Cocoa API and Objective-C language.
CFM – Code Fragment Manager. CFM is the native run time environment for Classic MacOS. CFM applications that strictly rely on the Carbon API can also run natively on MacOS X.
Carbon – A MacOS X API set that is based on the Classic MacOS API. Programs written using the Carbon API generally can be compiled to run natively on both MacOS X, and the Classic MacOS. One major notable exception is that all of the Tablet functionality included in the Carbon API only work properly in MacOS X.
Cocoa – An API set based on the NextStep API. Cocoa applications are usually written using the AppKit framework.
Mach-O – The native run time environment for MacOS X. Mach-O applications can use either the Cocoa or Carbon API set and will only run on MacOS X. Though CFM is also a native run time environment for MacOS X, special care must be taken when trying to get Mach-O applications and libraries to communicate with CFM applications and libraries.
SDK – Software Developers Kit. Sample code projects to help third party developers write tablet aware applications.
Transducer – A device that works with the tablet. Ex: Pen, Puck (Mouse), Stroke Pen, Airbrush, 4D Mouse.
 
 

General

Tablet Theory

Tablets provide an application with more accurate and more pieces of data than a mouse can. Perhaps the biggest tablet feature an application can take advantage of is pressure. Stylus like devices that are placed on a tablet will report how hard the user is pressing the device against the tablet. With just this small piece of data, a user can tell an application to vary the thickness of a line being drawn, or the opacity, or color. Perhaps an application can determine how fast to scroll, or how loud to play a note. Wacom tablets can also provide even more pieces of data such as the location of the device between pixels, the tilt of a stylus, the rotation of a mouse and more.
 
There are many different types of devices with different features that can be used on a tablet. Some Wacom tablets can even support having two devices on the tablet at the same time. To help handle all of these different devices, a special type of event is introduced, a proximity event. When a user places an appropriate device close enough to a tablet (not necessarily touching the tablets) to start sending information, the tablet sends a proximity event announcing that a tool has entered proximity. Also when the user moves a device too far away from the tablet, a proximity event is sent announcing that the tool has left proximity. An application should use these events to determine the current tool's abilities, ID, and type. Then, as the application gets data from the tool, it will know how to use it.
 
For example, in a painting application, if the proximity event informs the tablet that the tip of a tool is being used, the application should draw lines. However, if the proximity event informs the tablet that the erasure end of a tool is being used, then the application should erase instead of draw.

Tablet Support In MacOS X

Tablets are full-fledged citizens in MacOS X with their own events defined in CarbonEvents.h. This makes it easy to implement most of the features of the tablet without having to worry about opening / attaching to a driver. Normally, these tablet events are embedded inside of mouse events. Thus, all you have to do is to check if the mouse event has tablet data associated with it. If so, use it. If not, then the user is using a mouse and not a tablet device.
 

Cocoa:
Some of the tablet's features have been added directly to the Mouse events in AppKit. Most of them have not. However, with just a little carbon know how (which we will explain) you can get the Carbon Tablet data from Cocoa/Appkit.

Tablet Events

There are two types of tablet events in MacOS X, proximity and pointer.
 
Proximity events describe what kind of device is being used and are issued whenever a transducer is placed near the tablet, or removed from the tablet. Devices that are in proximity can cause pointer events to occur. Proximity events are important because they tell you what kind of device is being used on a tablet, what the capabilities of that device are, and provides key information that is used to link pointer events with specific proximity events.
 

Note: You must use the proximity event to determine if the tip or erasure end of a pen is in use.

 
Pointer events describe the current state of a transducer that is in proximity and are issued whenever any changes are made to the state of a transducer. Pointer events contain information like the location of the transducer, the pressure and tilt of the device.
 
 

Tablet Data Structures

Proximity Event

Related Data Structures, Enums, & Constants
kEventParamTabletProximityRec      defined in CarbonEvents.h
typeTabletProximityRec                     defined in CarbonEvents.h
kEventParamTabletProximityRec      defined in CarbonEvents.h
typeTabletProximityRec                    defined in CarbonEvents.h

struct TabletProximityRec {               defined in CarbonEvents.h
     UInt16 vendorID;
     UInt16 tabletID;
     UInt16 pointerID;
     UInt16 deviceID;
     UInt16 systemTabletID;
     UInt16 vendorPointerType;
     UInt32 pointerSerialNumber;
     UInt64 uniqueID;
     UInt32 capabilityMask;
     UInt8 pointerType;
     UInt8 enterProximity;
};
typedef struct TabletProximityRec TabletProximityRec;

vendorID

Type: UInt16
 
Notes:
This is the USB Vendor ID of the tablet. For Wacom, the vendor ID is 0x56A. You can generally ignore this field unless you want to decode the vendorPointerType.
 

tabletID

Type: UInt16
 
Notes:
This is the USB Model ID of the tablet. You can generally ignore this field.

pointerID

Type: UInt16
 
Notes:
This is the index of the device on the tablet. Usually this is 0, but for tablets that support multiple concurrent devices (also known as multimode or Dual Tracking), this number will change for the second concurrent device and so on. If your app does not support multiple concurrent devices and the pointerID is not 0, then you may want to make a note of the deviceID in this event and ignore tablet pointer events with this deviceID until you get a proximity event with this deviceID and a pointerID of 0.
 
If your application does support Dual Tracking then it is important to note that only one device should move the cursor. See Advanced Techniques . Dual Track for more information.

deviceID

Type: UInt16
 
Notes:
This is a Special ID that is used to match pointer events with this proximity event. It is the applications responsibility to save any relevant information provided in the proximity event structure for future reference while this device is in proximity. All of the pointer events produced by this device between an enter and leaving proximity events will contain the same deviceID as the one here.

systemTabletID

Type: UInt16
 
Notes:
This is the index of the tablet on the system. If there are multiple tablets connected to a system, this field will tell you which tablet this device is being used on.

vendorPointerType

Type: UInt16
 
Notes:
This is a coded bit field that you can use to determine exactly what type of device is being used. Some bits have special meaning to the hardware, but are not of interest to a developer. These bits are Wacom specific, so you should verify that the vendorID is Wacom's.
 
What is of interest is:
There are 12 bits total. The upper 4 bits, and a couple of bits in the lower four, identify the device. The middle 4 bits are used to differentiate between electrically similar, but physically different devices, such as a general stylus with a different color. So to figure out a specific device type you mask with 0x0F06. For example maybe 0x0812 is a stylus that is black, 0x0802 is the standard stylus included in the box, and 0x0832 may be a stylus with no side switches.
 
The currently supported types are:
General Stylus:     (vendorPointerType & 0x0F06) == 0x0802
Airbrush:               (vendorPointerType & 0x0F06) == 0x0902
General Mouse:   (vendorPointerType & 0x0F06) == 0x0006
Pro Mouse:          (vendorPointerType & 0x0F06) == 0x0004
Rotation Stylus:  (vendorPointerType & 0x0F06) == 0x0804
 
 
Intuos
Standard Stylus   = 0x822
Inking Stylus       = 0x812
Stroke Stylus      = 0x832
Grip Stylus         = 0x842
Airbrush             = 0x912
4D Mouse          = 0x094
Lens Cursor       = 0x096
 
Intuos 2
Standard (Grip) Stylus  = 0x852
Classic Stylus               = 0x822
Inking Stylus                 = 0x812
Stroke Stylus                = 0x832
Designer Stylus            = 0x842
Airbrush                        = 0x912
2D Mouse                     = 0x007
4D Mouse                     = 0x094
Lens Cursor                  = 0x096
 
Intous 3
Standard (Grip) Stylus      = 0x823
Rotation Stylus (Art Pen)  = 0x885
Inking Stylus                     = 0x801
Airbrush                            = 0x913
2 D mouse                        = 0x017
Lens Cursor                      = 0x097
 
Graphire & Graphire 2 & Graphire 3 & Bamboo
Graphire Stylus    = 0x022
Graphire Mouse   = 0x296

pointerSerialNumber

Type: UInt32
 
Notes:
Serial number of the transducer. Each transducer has a unique number within each device type. Therefore it is possible for a puck and a pen to have the name serial number.
 
Wacom's consumer level line of tablets, such as the Graphire, do not support this.
 

uniqueID

Type: UInt64
 
Notes:
Also known as Tool ID. A chip with a unique number is inside every device so every device can be uniquely identified. With Unique ID you can assign a specific tool to a specific device or use it to "sign" a document. You can restrict access to document layers to particular devices and have settings follow a device to other machines.
 
Wacom's consumer level line of tablets, such as the Graphire, do not support this, however, different IDs from such a tablet would signify that a different type of device is being used, so you could customize all the pens of a Graphire tablet one way, and the mice another way
 

Important:
In the Classic MacOS & PC Tablet documentation, Tool ID is often referred to as Device ID. It is important to remember that the MacOS X proxEvent.deviceID is not the same as Tool ID. It is the uniqueID field that should be used to uniquely identify a device.


capabilityMask

Type: UInt32
 
Notes:
This is a Bit field used to determine what capabilities this device has. Use the following masks that are defined in Wacom.h on this field to help determine what the capabilities of this device are.
 

kTransducerDeviceIdBitMask

This bit is set to indicate that the deviceID field of a pointer record will contain valid data. Considering that deviceID is used to link pointer events with proximity events, if a device does not support this, then something is terribly wrong.

kTransducerAbsXBitMask

This bit is set to indicate that the absX field of pointer records with this deviceID contain valid data.

kTransducerAbsYBitMask

This bit is set to indicate that the absY field of pointer records with this deviceID contain valid data.

kTransducerVendor1BitMask

This bit is set to indicate that the vendor1 field of pointer records with this deviceID contain valid data.

kTransducerVendor2BitMask

This bit is set to indicate that the vendor2 field of pointer records with this deviceID contain valid data.

kTransducerVendor3BitMask

This bit is set to indicate that the vendor3 field of pointer records with this deviceID contain valid data.

kTransducerButtonsBitMask

This bit is set to indicate that the buttons field of pointer records with this deviceID contain valid data.

kTransducerTiltXBitMask

This bit is set to indicate that the tiltX field of pointer records with this deviceID contain valid data.

kTransducerTiltYBitMask

This bit is set to indicate that the tiltY field of pointer records with this deviceID contain valid data.

kTransducerAbsZBitMask

This bit is set to indicate that the absZ field of pointer records with this deviceID contain valid data..

kTransducerPressureBitMask

This bit is set to indicate that the pressure field of pointer records with this deviceID contain valid data.

kTransducerTangentialPressureBitMask

This bit is set to indicate that the tangentialPressure field of pointer records with this deviceID contain valid data.

kTransducerOrientInfoBitMask

Deprecated! Don't Use!

kTransducerRotationBitMask

This bit is set to indicate that the rotation field of pointer records with this deviceID contain valid data.

 
 

pointerType

Type: UInt8
 
Notes:
This is the generic type of device.. Pen, Puck or Eraser. The following code snippit is defined in Wacom.h. Compare these enums with the pointerType.
 
typedef enum EPointerType
{            
     EUnknown = 0,     // should never happen
     EPen,             // tip end of a stylus like device
     ECursor,  // any puck like device
     EEraser           // eraser end of a stylus like device
} EPointerType;

enterProximity

Type: UInt8
 
Notes:
Is the device entering proximity? This field indicates whether the transducer is being put near the tablet, or taken away.
non-zero = entering; zero = leaving
 
 

Pointer Event

Related Data Structures, Enums, & Constants
            KEventParamTabletPointRec           defined in CarbonEvents.h
              TypeTabletPointRec                  defined in CarbonEvents.h
 
struct TabletPointRec {             defined in CarbonEvents.h
            SInt32 absX;
            SInt32 absY;
             SInt32 absZ;
            UInt16 buttons;
            UInt16 pressure;
            SInt16 tiltX;
            SInt16 tiltY;
            UInt16 rotation;
            SInt16 tangentialPressure;
            UInt16 deviceID;
            SInt16 vendor1;
            SInt16 vendor2;
            SInt16 vendor3;
};
typedef struct TabletPointRec TabletPointRec;
absX
Type: SInt32
Range: 0 to tablet width in counts. The width counts depend on model and size of tablet.
 
Notes:
This is the X coordinate of the device on the tablet.
If you really want to do the scaling yourself, then use this value (see Advanced Techniques . Scaling), otherwise use the sub-pixel information from the mouse event by getting the kEventParamMouseLocation of typeHIPoint (see Advanced Techniques . Sub Pixels).
 
 

absY

Type: SInt32
Range: 0 to tablet height in counts. The height counts depend on model and size of tablet.
 
Notes:
This is the Y coordinate of the device on the tablet.
If you really want to do the scaling yourself, then use this value (see Advanced Techniques . Scaling), otherwise use the sub-pixel information from the mouse event by getting the kEventParamMouseLocation of typeHIPoint (see Advanced Techniques . Sub Pixels).
 
absZ
Type: SInt32
Range: -1023 to 1023
 
Notes:
This is the Z coordinate of the device on the tablet. This is NOT pressure!
Wacom's Intuos and Intuos 2 4D Mice have a wheel that can report the Z coordinate. A possible use of the 4D Mouse wheel is for zooming or for 3D navigation. Note that the wheel must be set to "Application Defined" in the Wacom Tablet Control Panel for the absZ field to update. All other devices and tablets do not support Z.
 

buttons

Type: UInt16
Range: N/A
 
Notes:
This is a Bit field used to determine the state of the buttons on a transducer. Bit 0 is the first button. A 1 means the button is being pressed. For a stylus, the tip is considered to be the first button. All Wacom tablets and devices support buttons.
 

pressure

Type: UInt16
Range: 0 to (2^16)-1
 
Notes:
This is the scaled pressure that is applied to a transducer.
 
This is probably the most important field. All stylus like transducers and even some mice transducers have pressure. If a transducer is in proximity, but not actually touching the tablet, then the pressure is 0. As the user presses a stylus tip, or eraser, to the tablet, this value will change according the amount of pressure they apply to the stylus. All Wacom tablets support pressure, however, not all devices support pressure!
 

tiltX

Type: SInt16
Range: -((2^15)-1) to (2^15)-1       or         (-32767 to 32767)
 
Notes:
This is scaled tilt amount of a transducer in the X direction. If a stylus like device is held perfectly perpendicular to the tablet, then the tilt value is 0. As the stylus is tilted to the left, the value becomes more and more negative until the pen is parallel to the tablet (-90°).. As the stylus is tilted to the right, the values become more and more positive until the pen is parallel to the tablet (-90°)..
 

tiltY

Type: SInt16
Range: -((2^15)-1) to (2^15)-1       or         (-32767 to 32767)
 
Notes:
This is scaled tilt amount of a transducer in the Y direction. If a stylus like device is held perfectly perpendicular to the tablet, then the tilt value is 0. As the stylus is tilted to the top, the value becomes more and more negative until the pen is parallel to the tablet (-90°). As the stylus is tilted to the bottom, the values become more and more positive until the pen is parallel to the tablet (-90°).
 

Tip: Convert To Polar Coordinates
You can easily convert tiltX and tiltY to polar coordinates with the following code:
 
void ConvertCartesianToPolar(double xCoord, double yCoord,
double *r, double *theta)
{
     if ( r != NULL)
     {
         *r = sqrt( (xCoord * xCoord) + (yCoord * yCoord) );
     }
 
     if ( theta != NULL )
     {
         *theta = atan2( yCoord, xCoord );
     }

}
 

 
Not all Wacom Tablets support tilt. On tablets that do support tilt, only stylus like devices (including the airbrush) have tilt.

rotation

Type: UInt16
Range: 0 to 23040
 
Notes:
This is a fixed-point ( 10.6 format.) representation of device rotation in degrees. That is, the first 10 bits are the integer part, and the last 6 bits are the fractional component. Therefore, to convert to floating point notation, divide the fixed-point rotational value by 2^6 (or 64).
 
rotDegrees = (float)rotation/64.0;
rotRadians = (rotDegrees * PI) / 180.0;
 
Note: Rotation is the angle between the "front" of the transducer and the top of the tablet. This does not mean much for mice that support rotation, however, it is very important for styli. For styli this rotation is different than "twisting" the barrel of the pen. While twisting the barrel of the pen does cause a rotation change, the rotation can change without actually twisting the barrel of the pen. For example, hold the pen upright with side-switch buttons pointed towards the top of the tablet. Now fully tilt the pen to the left. Rotate the pen so that the end is pointing down. Finally, tilt the pen back upright. Notice how the side switches are rotated 90° from where they started and that you never physically twisted the barrel of transducer.

tangentialPressure

Type: SInt16
Range: -((2^15)-1) to (2^15)-1       or         (-32767 to 32767)
 
Notes:
This is another pressure field a device can have. It is also often referred to as barrel pressure. The wheel on the Intuos and Intuos 2 Airbrush tool are the only tools that report tangential pressure.
 
Wacom interprets the tangential pressure as follows. 0 is the neutral position. Movement away from the neutral position can then be either positive or negative. The Airbrush's tangential pressure wheel, however, can only move in the positive direction from the neutral position. (Fingerwheel all the way to the front is neutral.)
 

Tip: Tangential Effect
You may want to scale the tangential pressure to get full effect at full pressure.
 
#define      kTanPressureLevels           32767
 
// This goes in your draw routine
// Do not forget to check the capabilities mask from the enter proximity
// event of this deviceID to make sure that this device has tangential
// pressure.
 
tangentialPressure = pointerEvent.tangentialPressure;
 
tangentialEffect = ( tangentialPressure * 100% ) / kTanPressureLevels;

 
 

deviceID

Type: UInt16
Range: N/A
 
Notes:
This is a Special ID that is used to match this pointer event with a previous proximity event. Refer to the data stored from the associated proximity event to determine the capabilities of this device and to determine if it is a tip, erasure or mouse.

vendor1

Type: SInt16
Range: N/A
 
Notes:
Reserved.

vendor2

Type: SInt16
Range: N/A
 
Notes:
Reserved

Tablet Data Structures . Pointer Event . vendor3

Type: SInt16
Range: N/A
 
Notes:
Reserved.

Tablet Events

Tablet events may get sent to an application in two slightly different ways. Generally, tablet events are embedded inside mouse events because the tablet is also used to control the screen cursor. In some cases, where there is no cursor movement, an application may receive a tablet event by itself. Such tablet events are referred to as "pure tablet events."
 
The best way to get tablet events is to install Carbon Event Handlers for mouse events and for the tablet events. Typically, applications only care about Drag, Up and Down events. You can ignore Mouse Move events unless your application cares about mouse or tablet data when the user is not starting, in the middle of, or ending a drag. There is a very special case, though, right after a mouse event that you should be aware of. To prevent accidental drags when the user is attempting to click, the Wacom driver does not produce mouse drag events until the user has moved at least one pixel. That means that there may be pressure changes and other tablet data that does not come through embedded in a mouse event. For many applications, this is not a problem. However, if this is a problem for your app, the tablet data is being supplied as pure tablet pointer events. If you listen for and get pure tablet pointer events right after a mouse down, consider the mouse location to be the same and only the tablet data has changed. Once the tablet driver starts outputting drag events, it will continue to do so until all the buttons and the pen tip is released. It should also be noted that during a drag, the tablet is outputting a constant stream of events. This means that you may receive events that are exactly like a previous tablet event. The user must have a very steady hand to do this, but it is not uncommon for this to occur either at full pressure or for short intervals.
 
Proximity events are always sent as pure tablet events. This way you don't have to search each mouse event to get the proximity events. Simply install a carbon tablet class, tablet proximity event handler and you will get all proximity events without digging for them. Also, you should install an event handler on the "Monitor Target" in 10.3.x or higher. This will allow you to get Proximity events while your application is in the background. This is important, as the user can change tools, or flip over the pen to the eraser while your app is in the background. Without a handler installed on the monitor event target, you will not be informed of this. Proximity events may also be posted embedded in mouse events. You can ignore these proximity events, but you can not assume that embedded tablet events are always pointer events. Always check the embedded event type in mouse events!
 
The Carbon mouse and tablet event types are defined in CarbonEvents.h. If you do not know how to install Carbon Event Handlers than please refer to Apple's online documentation, Inside Carbon: Carbon Event Manager.

Extracting from Mouse Events

Once you receive a mouse event, you can tell if a tablet has generated it by asking for the kEventParamTabletEventType event parameter. If this parameter exists then it is safe to assume tablet data exists. Based on the event type, you can then get the appropriate tablet data by passing either kEventParamTabletProximityRec or kEventParamTabletPointerRec to GetEventParameter(). The following sample code demonstrates how to extract the embedded tablet data.
 
 
OSStatus MyMouseEventHandler( EventHandlerCallRef inCallRef, EventRef inEvent,
void* userData )
{
UInt32            eventType;
OSStatus  result;
      
result = GetEventParameter( inEvent, kEventParamTabletEventType, typeUInt32, NULL,  sizeof( eventType ), NULL,  &eventType );
if ( result != noErr )
{
     // not generated with a tablet
     // Treat this event like it came from a standard mouse.
     return eventNotHandledErr;
}
      
switch ( eventType )
{
     case kEventTabletProximity:
     {
         TabletProximityRec proxEvent;
             
         result = GetEventParameter(inEvent, kEventParamTabletProximityRec, typeTabletProximityRec, NULL, sizeof( TabletProximityRec ), NULL, &proxEvent);          

        if ( result == noErr )
        {
              // proxEvent now contains the embedded tablet proximity event
              // Do something with it here.
        }
        else
        {
              // Oh No!
              // Bad result from GetEventParameter(), getting
              // kEventParamTabletProximityRec
              assert(0);
              }
              break;
             
              case kEventTabletPoint:
              {
              TabletPointerRec pointerEvent;
                     
              result = GetEventParameter(inEvent,
kEventParamTabletPointerRec,
typeTabletPointerRec,
NULL,
sizeof( TabletPointerRec ),
NULL,
&pointerEvent);      
                     
              if ( result == noErr )
              {
              // pointerEvent now contains the embedded tablet
// pointer event
       // Do Something with it here.
              }
              else
              {
              // Oh No!
              // Bad result from GetEventParameter(), getting
// kEventParamTabletPointerRec
                      assert(0);
              }
              }
              break;
             
              default:
              {
// Oh No! How'd this happen
              // Unknown event type
 
              assert( 0 );
              }
              break;
       }
 
       return noErr;
}
 

Tip:
A really easy way to handle embedded proximity events is just to ignore them. It will be duplicate information from the pure tablet proximity event. You do have a Carbon event handler for pure tablet proximity events right?

 
Caution:
Do not depend on WaitNextEvent() to return mouse event records. Old style event records do not contain embedded tablet information. You must use Carbon Event Refs. If you must, replace WNE with RecieveNextEvent(). ReceiveNext event will return a Carbon Event Ref, and you can convert that to an old style Event Record if you need to for further processing.
 

Tracking Loops

When your application receives a mouse down event, you will probably want to go into some sort of tracking loop until your application receives a mouse up event. You must be careful how you write your tracking loop or you will find that your application's mouse and tablet event handlers do not get called.
 
Due to the symmetric multi-tasking of MacOS X, Apple suggests that you no longer use the old event routines like StillDown. To this end, Apple has added two new mouse tracking routines, TrackMouseLocation() and TrackMouseLocationWithOptions(). These two new API calls do not return the mouse event, nor do they call your mouse or tablet event handlers. The embedded tablet events get eaten by these calls, so do NOT use TrackMouseLocation() or TrackMouseLocationWithOptions().
 

        • Do NOT use TrackMouseLocation() or TrackMouseLocationWithOptions() ****

 
The solution is to use the new RecieveNextEvent() and roll your own TrackMouseLocation. There are 3 important things that ReceiveNextEvent() (RNE) does for Tablet Aware application developers. First is that RNE blocks for the specified time if there are no events to get. Second, while RNE is blocked, any timers you may have running still fire. And finally, RNE returns the event as a Carbon EventRef so that you can get to all the mouse information and the embedded tablet event.
 

Sample Code: WacomTrackMouseReturnEvent
//////////////////////////////////////////////////////////////////////////////
/*
*     WacomTrackMouseReturnEvent

  • Once entered this function waits for a mouse event or tablet event. When
  • one of these events occur, the function returns the EventRef containing
  • the mouse or tablet event to the caller. It is the caller's responsibility
  • to dispose of the event. While there is no activity, the current event loop
  • is run, effectively blocking the current thread (save for any timers that
  • fire). This helps to minimize CPU usage when there is nothing going on.
  • IMPORTANT:
  • It is the caller's responsibility to dispose of the returned event using
  • ReleaseEvent( eventRef );
    */
    OSStatus WacomTrackMouseReturnEvent(EventRef *eventRef)
    {
    <ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="edc98c7e-8df5-4b03-b27d-c7cff5b9a33e"><ac:plain-text-body><![CDATA[const EventTypeSpec     events[ 6 ] = {
    ]]></ac:plain-text-body></ac:structured-macro>
    { kEventClassMouse, kEventMouseDown },
    { kEventClassMouse, kEventMouseUp },
    { kEventClassMouse, kEventMouseMoved },
    { kEventClassMouse, kEventMouseDragged },
    { kEventClassTablet, kEventTabletPointer },
    { kEventClassTablet, kEventTabletProximity } };
    OSStatus         result = noErr;
     
    // Check make sure eventRef exists. Avoid crashing.
    if(eventRef == NULL)
    {
    return paramErr;
    }
    // Look for tablet & mouse events.
    result = ReceiveNextEvent( 6,
    events,
    kEventDurationForever,
    true,
    eventRef );
    return result;
    }
     

Sample Code: WacomTrackMouseLocationUsingHandlers
typedef UInt16 WacomTrackingResult;
enum {
kWacomTrackingMouseDown = 1,
kWacomTrackingMouseUp = 2,
kWacomTrackingMouseExited = 3,
kWacomTrackingMouseEntered = 4,
kWacomTrackingMouseDragged = 5,
kWacomTrackingKeyModifiersChanged = 6,
kWacomTrackingUserCancelled = 7,
kWacomTrackingTimedOut = 8,
kWacomPureTabletEvent = 9,
};
 
//////////////////////////////////////////////////////////////////////////////
/*

  • WacomTrackMouseLocationUsingHandlers
  • This function is similar to Apple's TrackMouseLocation. The difference is
  • that this function will call your Carbon Event Handlers for the mouse and
  • tablet events. Also, the mouse location is returned in a HIPoint that is
  • local to inPort as opposed to a Point (typeQDPoint).
  • inPort can be NULL, if so the current port is used.
    */
    OSStatus WacomTrackMouseLocationUsingHandlers(GrafPtr inPort,
    HIPoint * outPt,
    WacomTrackingResult * outResult)
    {
    // List of events we want to track
    <ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="fc64ca67-7404-4826-a57f-5b0e10a3c926"><ac:plain-text-body><![CDATA[const EventTypeSpec     events[ 5 ] = {
    ]]></ac:plain-text-body></ac:structured-macro>
    { kEventClassMouse, kEventMouseDown },
    { kEventClassMouse, kEventMouseUp },
    { kEventClassMouse, kEventMouseDragged },
    { kEventClassTablet, kEventTabletPointer },
    { kEventClassTablet, kEventTabletProximity } };
     
    // Function Variables
    EventRef  eventRef;
    GrafPtr          oldPort;
    GrafPtr          curPort;
    HIPoint          locHIPoint;
    HIPoint          deltaHIPoint;
    Point            aQDPoint;
    Rect             portRect;
    OSStatus  result = noErr;
     
    *outResult = kWacomTrackingTimedOut;
     
    // Look for tablet & mouse events.
    result = ReceiveNextEvent( 5,
    events,
    kEventDurationForever,
    true,
    &eventRef );
    if ( result == noErr )
    {
    // Initially guess at result type.
    if (GetEventClass(eventRef) == kEventClassMouse)
    {
    switch (GetEventKind(eventRef))
    {
    case kEventMouseDown:
    *outResult = kWacomTrackingMouseDown;
    break;
     
    case kEventMouseUp:
    *outResult = kWacomTrackingMouseUp;
    break;
     
    case kEventMouseDragged:
    *outResult = kWacomTrackingMouseDragged;
    break;
     
    default:
    break;
    }
    }
    else
    {
    // If this is not a mouse event then it must be a tablet event
    // in which case the outPt will not be valid.
    *outResult = kWacomPureTabletEvent;
    }
     
    // Make sure outPt is not NULL or else a crash will occur
    if (outPt != NULL)
    {
    GetPort(&oldPort);
     
    // Set the current grafPort to inPort. If inPort == NULL then use
    // the current grafPort.
    if ( inPort != NULL )
    {
    SetPort(inPort);
    curPort = inPort;
    }
    else
    {
    curPort = oldPort;
    }
     
    // Get the mouse location of the event as a HIPoint, so that we
    // get sub-pixel information. If this is not a mouse event (ie
    // tablet event) then GetEventParameter will return an error and
    // we ignore trying to return the mouse loc.
    if(noErr == GetEventParameter(eventRef,
    kEventParamMouseLocation,
    typeHIPoint, NULL,
    sizeof(HIPoint), NULL,
    (void *)&locHIPoint))
    {
    // Must convert the point from Global to Local coordinates
    // but we must be careful and maintain the sub-pixel information.
    aQDPoint.h = locHIPoint.x;
    aQDPoint.v = locHIPoint.y;
     
    GlobalToLocal(&aQDPoint);
     
    locHIPoint.x = (locHIPoint.x - trunc(locHIPoint.x)) +
    (double)aQDPoint.h;
    locHIPoint.y = (locHIPoint.y - trunc(locHIPoint.y)) +
    (double)aQDPoint.v;
     
    // return the local mouse location, however, if outPt is NULL,
    // we do not want to crash. But, we still need the current mouse
    // location to determine if this really should be called an enter
    // or exit result.
    if ( outPt != NULL )
    {
    *outPt = locHIPoint;
    }
     
    // If we can get a mouse delta then we can determine the mouse
    // location of the mouse event before this one and can thus check
    // if this is an enter or exit or plain drag event.
    // GetEventParameter will return an error if this is not a mouse
    // move or drag. But in such a case, the track result should not
    // be Enter or Exit and was thus handled above properly.
    if(noErr == GetEventParameter(eventRef,
    kEventParamMouseDelta,
    typeHIPoint, NULL,
    sizeof(HIPoint), NULL,
    (void *)&deltaHIPoint))
    {
    Boolean oldPointInBounds;
    Boolean newPointInBounds;
     
    GetPortBounds(curPort, &portRect);
     
    // Check to see if the mouse loc for this event is inside
    // the window's bounds.
    newPointInBounds = MacPtInRect(aQDPoint, &portRect);
     
    // Calculate the previous mouse location using the delta info
    aQDPoint.h = (locHIPoint.x - deltaHIPoint.x);
    aQDPoint.v = (locHIPoint.y - deltaHIPoint.y);
     
    // Check to see if the previous mouse loc for this event is
    // inside the window's bounds.
    oldPointInBounds = MacPtInRect(aQDPoint, &portRect);
     
    // If both mouse locations are inside the window, or both mouse
    // locations are outside the window then the tracking result is
    // neither Enter nor Exit. The initial tracking guess was
    // correct.
    if ( oldPointInBounds != newPointInBounds)
    {
    // One mouse location is in the window and the other
    // is outside the window. If the current mouse loc is in
    // the window then this must be an Enter event, else it
    // must be an Exit event.
    if (newPointInBounds)
    {
    *outResult = kWacomTrackingMouseEntered;
    }
    else
    {
    *outResult = kWacomTrackingMouseExited;
    }
    }
     
    }
    }
     
    // Don't forget to set the current port back to what it was before
    // we changed it.
    SetPort(oldPort);
    }
     
    // If there was an event, invoke the application's event handlers for it.
    SendEventToEventTarget ( eventRef, GetEventDispatcherTarget() );
     
    // Since we pulled the event off the queue, we have to release it.
    ReleaseEvent( eventRef );
    }
     
    return result;
    }
     

Cocoa Notes

The AppKit framework's mouse event already has a scaled pressure field and sub-pixel information. The pressure field is a float value scaled from 0.0 to 1.0. The sub-pixel information is stored as the fractional parts of the float x and y mouse locations.
 
In 10.4, Apple has added native methods to NSEvent that allows you to get to the embedded, or pure, tablet data. If your Cocoa application is 10.4 only, please review NSEvent.h for the method names.
 
However, if your apps run on pre 10.3.x or less, and you want any of the other tablet data besides pressure and sub-pixel data, then you must do some trickery. First you need to extend the NSEvent class by creating a Category that allows you to get to the underlying Carbon EventRef. Then you can you can use the above information to extract the embedded tablet information.
 

Sample Code: This is how to create a Category to extend NSEvent to get at the Carbon Event Ref
 
// Tablet Events.h
#import <Cocoa/Cocoa.h>
@interface NSEvent ( TabletEvents )

  • (void *)eventRef;
    @end
     
    // Tablet Events.m
    #import "TabletEvents.h"
    @implementation NSEvent (TabletEvents)
  • (void *)eventRef
    {
    return _eventRef;
    }
    @end









 
 
By default, Cocoa will either ignore pure tablet proximity events (10.3.x or less) or send the proximity event to the first responder it can (10.4+). Neither of these cases are the best solution as the object that needs to know about proximity may not get the event. What you should do is subclass NSApplication and override the sendEvent method. If you get any tablet class, tablet proximity events, then extract it and have your NSApplication subclass broadcast the proximity event as a NSNotification and then all interested objects will receive it. If not, then send the event along on it's way via \[super sendEvent:theEvent\];. Do not forget to change the Principal Class in the Target Settings, Application Settings tab, Cocoa Specific sub section, to your NSApplication subclass. This forces Cocoa to create an instance of your subclass instead of NSApplication when the program is launched.
 
Cocoa still does not have a way to natively listen for background events (as of 10.4.x). This means that your Cocoa application will miss important proximity events while your app is in the background. For example, if the user is erasing in your application and clicks in the Finder, flips the pen over, does some stuff and clicks back into your Cocoa application, your cocoa application will still erase because it did not get the proximity event that told the system the pen was flipped back over. You can get around this in 10.3.x by installing your own Carbon event handlers on the monitor target in your NSApplication subclass. This can get a little nuts if you are only running on 10.4 and using the native Cocoa tablet events because you have to deal with 2 proximity event styles. This isn't terribly hard, but it is a pain. If it really bothers you, let Apple know by filing a bug report at [http://bugreporter.apple.com|http://bugreporter.apple.com].
 





Tip: (for 10.3.x or less)
The Cocoa Complex sample project has code that does most of the Carbon stuff for you.
 
TabletEvents.h - Extends the NSEvent class by creating a Category that allows you to get to the extended Tablet Event data such as Tilt and DeviceID from an NSEvent object.
 
TabletApplication.h - A subclass of NSApplication that will properly broadcast a proximity event as a Notification.

 
 


Advanced Techniques (a)
 
 

Sub Pixels

Wacom tablets are very precise, high-resolution devices. It is possible to determine where the cursor should be on the screen at fractional pixel levels. That is, you can tell how far between two pixels the cursor is supposed to be. The distance away from the reported pixel location of the cursor and the actual location of the cursor is called the sub pixel information.
 
Sub pixel information is particularly helpful if the document is of a higher resolution than the screen. You can use the sub-pixel information to get a cursor location that is much closer to your documents resolution than the screen can possibly be.
 
You can get the size of the tablet and do the scaling yourself, but Apple has modified the mouse event to make it easy for you. When you extract the mouse location from a mouse carbon event, you would generally ask for a typeQDPoint. Instead, you should ask for a typeHIPoint. HIPoints use float values instead of integer values and the sub pixel information is stored in the fractional part of float number. A HIPoint is defined in CarbonEvents.h as follows;
 
/*