Skip to main content

Basics

Overview

This page provides the developer with a basic overview of how to make requests of the macOS Wacom tablet driver.

Target Platforms

macOS-5based systems.

Target Tablets

Wacom tablets supported by the installed tablet driver.

Communicating with the Tablet Driver

All communication with the tablet driver should be done with Apple Events. The target of these Apple Events should be the TabletDriver application.

Generic Communication with the Tablet Driver

The Generic set of Apple Events allows you to programmatically look at settings in the tablet driver. Although you can modify settings with these events, these changes would affect the global operation of the tablet. In general, you should not change these settings. The most common use of these events would be to get the version number of the driver, the number of tablets, and the size of each tablet.

Application Specific Communication with the Tablet Driver

The Application Specific Apple Events are composed of a Wacom Apple Event SendTabletEvent and a Context class.

#define cTabletEvent     'TblE'
#define kAEWacomSuite 'Wacm'
#define eSendTabletEvent 'WSnd'
#define eEventProximity 'WePx'
#define eEventPointer 'WePt'
#define kDefaultTimeOut 15 //Timeout value in ticks Approx 1/4 second


//////////////////////////////////////////////////////////////////////////////
// resendLastTabletEventOfType:
//
// Purpose: Send an Apple Event to the tablet driver to resend an event.
//
// Parameters: tabletEventType - eEventProximity, eEventPointer
//
+ (void) resendLastTabletEventOfType:(DescType)tabletEventType
{
NSAppleEventDescriptor *event = nil;
NSAppleEventDescriptor *reply = nil;
// create the apple event for object count
event = [NSAppleEventDescriptor appleEventWithEventClass:kAEWacomSuite
eventID:eSendTabletEvent
targetDescriptor:[self driverAppleEventTarget]
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];

// set object class to tablet to indicate that we want tablet count
[event setDescriptor:[NSAppleEventDescriptor descriptorWithEnumCode:tabletEventType]
forKeyword:keyAEData];


// send the event
reply = [event sendExpectingReplyWithPriority:kAEHighPriority
andTimeout:kTabletDriverAETimeout];
}

Context events are how some of the more advanced techniques are accomplished, such as automatic scaling, disconnecting the tablet events from the mouse, and more. Your application can ask the tablet driver to create one or more contexts per tablet and then modify the properties of these contexts as you see fit. These contexts are specific to the instance of your application that created them. Keep in mind that your contexts are only valid while your application is in the foreground. Your contexts will cease to function while your application is in the background. When your application returns as the foreground application, your contexts will return as well. When your application quits, the contexts go away permanently. (Though please be nice and destroy any created contexts before quitting.) When your application is re-launched, it must create its contexts all over again.

To create a context:

  • send to the tablet driver an Apple Event of class / type {kAECoreSuite, kAECreateElement} with the keyAEObjectClass Param of the Apple Event filled with a DescType of cContext and the keyAEInsertHere Param filled with an object specifier of the index of the tablet (cTablet) you want to create a context for.

To set and get properties of a context, send to the tablet driver an Apple Event of one of the following classes / types:

  • kAECore
  • kAESetData
  • kAEGetData

This should be set with the keyDirect Apple Event Parameter filled with an object specifier of the context's (cContext) uniqueID (formUniqueID) and the property's (cProperty) ID (formPropertyID of type DescType).

Context PropertyValueTypeDescription
pContextMapScreenArea'Smap'typeQDRectangleThis is the area of the Desktop, in pixels, that this context's tablet area will map to.
pContextMapTabletOutputArea'Tomp'typeLongRectangleThis is the rectangle that this context will map the Tablet Input Area to. (i.e. the location of the pen on the tablet input area will be scaled to this range and the result is returned in the absX, and absY tablet pointer event fields).
pContextMapTabletInputArea'Tmap'typeLongRectangleThis is the area of the tablet, in tablet counts, that this context is valid for. When a pen is in this rectangle on the tablet, it will obey the rules of this context.
pContextMovesSystemCursor'Mvsc'typeBooleanShould events generated from this context move the system cursor? If false, then all tablet events will be sent as pure tablet events.
pcontextenabled'Cenb'typeBooleanIs this context enabled? You can enable and disable the contexts you create at any time.

To destroy a context, send to the tablet driver an Apple Event of class / Type {kAECore, kAEDelete} with the keyDirect Apple Event Parameter filled with an object specifier of the context's (cContext) uniqueID (formUniqueID).

Tip:

The WacomTabletDriver class located in the Tablet Mapping sample code contains functions to Create, Destroy, Read, and Modify Contexts and their attributes. Their use is even demonstrated with the “Constrain to Window” menu command.

Scaling

To get the tablet driver to scale its output to a specific section on the screen, create a context and modify the pContextMapScreenArea attribute to a rectangle on the screen.

To get the tablet driver to scale it's absX, and absY data, create a context and modify the pContextMapTabletOutputArea to the output rectangle you would like.

Note that changing the output rectangle does not modify the screen area that the tablet driver moves the cursor and vice versa. So, for example, you can modify the pContextMapScreenArea of your context to force the cursor to remain inside your window. Then you can modify the pContextMapTabletOutputArea of the same context so that the absolute range is 0 – 1000 in both axes. Why would you want to do this? I don't know, but you can!

Overriding Tablet Controls

WacomTabletDriver provides a set of Apple Events that enable applications to take control of tablet controls. There are three types of tablet controls: ExpressKeys, TouchStrip, and TouchRing. Each control has one or more functions associated with it. Do not make an assumption of the number of controls of a specific tablet or the number of functions associated with a control. Always use the APIs to query for the information.

An application needs to do the following to override tablet controls:

  1. Create a context for the tablet of interest.
  2. Register with the distributed notification center to receive the overridden controls' data from user actions.
  3. Query for number of controls by control type (ExpressKeys, TouchStrip, or TouchRing).
  4. Query for number of functions of each control.
  5. Enumerate the functions to find out which are available for override.
  6. Set override flag for a control function that's available.
  7. Handle the control data notifications to implement functionality that the application desires for the control function.
  8. Must destroy the context upon the application's termination or when the application is done with it.

To create an override context for a tablet, send to the tablet driver an Apple Event of class / type {kAECoreSuite, kAECreateElement} with the keyAEObjectClass Param of the Apple Event filled with a DescType of cContext, the keyAEInsertHere Param filled with an object specifier of the index of the tablet (cWTDTablet) and the keyASPrepositionFor Param filled with a DescType of pContextTypeBlank.

To destroy a context, send to the tablet driver an Apple Event of class / Type {kAECore, kAEDelete} with the keyDirect Apple Event Parameter filled with an object specifier of the context's (cContext) uniqueID (formUniqueID).

Cocoa Sample Code:

#define kWacomDriverSig             'WaCM' 
#define cContext 'CTxt'
#define pContextTypeBlank 'Blnk'


///////////////////////////////////////////////////////////////////////////////
// Helper function that returns Apple Event descriptor of the tablet driver.
//
+ (NSAppleEventDescriptor *)driverAppleEventTarget
{
OSType tdSig = kWacomDriverSig;
return [NSAppleEventDescriptor descriptorWithDescriptorType:typeApplSignature length:sizeof(tdSig)];
}
///////////////////////////////////////////////////////////////////////////////
// Create a blank context for the tablet with index tabletIndex
//
+ (UInt32)makeContextForTablet:(UInt32)tabletIndex
{
UInt32 context = 0;
NSAppleEventDescriptor *response = nil;
NSAppleEventDescriptor *event = [NSAppleEventDescriptor
appleEventWithEventClass:kAECoreSuite
eventID:kAECreateElement
targetDescriptor:[self driverAppleEventTarget]
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];

[event setDescriptor:[NSAppleEventDescriptor descriptorWithTypeCode:cContext]
forKeyword:keyAEObjectClass];

[event setDescriptor:[NSAppleEventDescriptor descriptorForObjectOfType:cWTDTablet
withKey:[NSAppleEventDescriptor descriptorWithUInt32:tabletIndex]
ofForm:formAbsolutePosition]
forKeyword:keyAEInsertHere];

[event setDescriptor:[NSAppleEventDescriptor descriptorWithTypeCode:pContextTypeBlank]
forKeyword:keyASPrepositionFor];

response = [event sendExpectingReplyWithPriority:kAEHighPriority
andTimeout:kWtcTabletDriverAETimeout];

context = [[response descriptorForKeyword:keyDirectObject] int32Value];

return context;
}

///////////////////////////////////////////////////////////////////////////////
// Destroy context
//
+ (void)destroyContext:(UInt32)context
{
NSAppleEventDescriptor *event = [NSAppleEventDescriptor
appleEventWithEventClass:kAECoreSuite
eventID:kAEDelete
targetDescriptor:[self driverAppleEventTarget]
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];

[event setDescriptor:[NSAppleEventDescriptor descriptorForObjectOfType:cContext
withKey:[NSAppleEventDescriptor descriptorWithUInt32:context]
ofForm:formUniqueID]
forKeyword:keyDirectObject];

[event sendWithPriority:kAEHighPriority andTimeout:kWtcTabletDriverAETimeout];
}

To Get or Set properties of a control function, send to the tablet driver an Apple Event of one of the following classes / types:

  • kAECore
  • kAESetData
  • kAEGetData

This should be set with the keyDirect Apple Event Parameter that specifies the override context, index of the control and function to override. The structure of the descriptor filled looks like this:

send to the tablet driver an Apple Event

cContext

--- cWTDExpressKey (cWTDTouchRing, or cWTDTouchStrip)

--- cWTDControlFunction

The table below lists the properties that an application can Get and/or Set for a tablet control or its functions.

PropertyValueTypeDescriptionNotes
pFunctionAvailable'FunA'typeBooleanThis indicates whether the control function is available for an application to override.Read only.
pControlMinValue'CMin'typeUInt32The minimum value of the control.Read only. Function index ignored.
pControlMaxValue'CMax'typeUInt32The maximum value of the controlRead only. Function index ignored.
pControlLocation'CLoc'typeUInt32The physical location of the control on the tablet.Read only. Function index ignored. 0 - left; 1 - right; 2 – top; 3 – bottom.
pOverrideFlag'OvrF'typeBooleanGet or set the override flag for a control function.Read/Write
Cocoa Sample Code:

#define kInavlidAppleEventIndex 0

#define cContext 'CTxt'
#define cWTDTouchRing 'WRnG'
#define cWTDExpressKey 'WExK'
#define cWTDTouchStrip 'WTcS'
#define cWTDControlFunction 'WCtF'

#define pFunctionAvailable 'FunA'
#define pOverrideFlag 'OvrF'
#define pOverrideName 'ONme'

typedef enum eAETabletControlType
{
eAETouchRing = 0,
eAETouchStrip,
eAEExpressKey
} eAETabletControlType;

///////////////////////////////////////////////////////////////////////////////
// Helper function for translating tablet control type to Apple Event class.
//
+ (DescType)descTypeFromControlType:(SInt32)controlType
{
if (controlType == eAETouchStrip)
{
return cWTDTouchStrip;
}
else if (controlType == eAEExpressKey)
{
return cWTDExpressKey;
}
return cWTDTouchRing;
}

///////////////////////////////////////////////////////////////////////////////
// Helper function for querying the tablet driver for a tablet control property.
//
+ (NSAppleEventDescriptor*)dataForAttribute:(DescType)attribute ofType:(DescType)dataType
ofFunction:(UInt32)function ofControl:(UInt32)control
ofContext:(UInt32)context forControlType:(SInt32)controlType
{
NSAppleEventDescriptor *reply = nil;
NSAppleEventDescriptor *event = [NSAppleEventDescriptor
appleEventWithEventClass:kAECoreSuite
eventID:kAEGetData
targetDescriptor:[self driverAppleEventTarget]
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];

// context descriptor
NSAppleEventDescriptor *contextDesc = [NSAppleEventDescriptor
descriptorForObjectOfType:cContext
withKey:[NSAppleEventDescriptor descriptorWithUInt32:context]
ofForm:formUniqueID];

// control descriptor
NSAppleEventDescriptor *controlDesc = [NSAppleEventDescriptor
descriptorForObjectOfType:[self descTypeFromControlType:controlType]
withKey:[NSAppleEventDescriptor descriptorWithUInt32:control]
ofForm:formAbsolutePosition from:contextDesc];

// function descriptor
NSAppleEventDescriptor *functionDesc = (kInavlidAppleEventIndex != function) ?
[NSAppleEventDescriptor
descriptorForObjectOfType:cWTDControlFunction
withKey:[NSAppleEventDescriptor descriptorWithUInt32:function]
ofForm:formAbsolutePosition from:controlDesc] : nil;

// attribute descriptor
NSAppleEventDescriptor *attribDesc = [NSAppleEventDescriptor
descriptorForObjectOfType:formPropertyID
withKey:[NSAppleEventDescriptor descriptorWithTypeCode:attribute]
ofForm:formPropertyID from:functionDesc ? functionDesc : controlDesc];

[event setDescriptor:attribDesc forKeyword:keyDirectObject];

[event setDescriptor:[NSAppleEventDescriptor descriptorWithTypeCode:dataType]
forKeyword:keyAERequestedType];

// send
reply = [event sendExpectingReplyWithPriority:kAEHighPriority
andTimeout:kWtcTabletDriverAETimeout];

return [reply descriptorForKeyword:keyDirectObject];
}

///////////////////////////////////////////////////////////////////////////////
// Helper function for setting a tablet control property.
//
+ (BOOL)setBytes:(void*)bytes ofSize:(UInt32)size ofType:(DescType)dataType
forAttribute:(DescType)attribute
ofFunction:(UInt32)function ofControl:(UInt32)control
ofContext:(UInt32)context forControlType:(SInt32)controlType
{
OSErr err = noErr;
NSAppleEventDescriptor *data = nil;
NSAppleEventDescriptor *event = [NSAppleEventDescriptor
appleEventWithEventClass:kAECoreSuite
eventID:kAESetData
targetDescriptor:[self driverAppleEventTarget]
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];

// context descriptor
NSAppleEventDescriptor *contextDesc = [NSAppleEventDescriptor
descriptorForObjectOfType:cContext
withKey:[NSAppleEventDescriptor descriptorWithUInt32:context]
ofForm:formUniqueID];

// control descriptor
NSAppleEventDescriptor *controlDesc = [NSAppleEventDescriptor
descriptorForObjectOfType:[self descTypeFromControlType:controlType]
withKey:[NSAppleEventDescriptor descriptorWithUInt32:control]
ofForm:formAbsolutePosition from:contextDesc];

// function descriptor
NSAppleEventDescriptor *functionDesc = (kInavlidAppleEventIndex != function) ?
[NSAppleEventDescriptor
descriptorForObjectOfType:cWTDControlFunction
withKey:[NSAppleEventDescriptor descriptorWithUInt32:function]
ofForm:formAbsolutePosition from:controlDesc] : nil;

// attribute descriptor
NSAppleEventDescriptor *attribDesc = [NSAppleEventDescriptor
descriptorForObjectOfType:formPropertyID
withKey:[NSAppleEventDescriptor descriptorWithTypeCode:attribute]
ofForm:formPropertyID
from:functionDesc ? functionDesc : controlDesc];

[event setDescriptor:attribDesc forKeyword:keyDirectObject];

[event setDescriptor:[NSAppleEventDescriptor descriptorWithTypeCode:dataType]
forKeyword:keyAERequestedType];

data = [NSAppleEventDescriptor descriptorWithDescriptorType:dataType
bytes:bytes length:size];

[event setDescriptor:data forKeyword:keyAEData];

// send
err = [event sendWithPriority:kAEHighPriority andTimeout:kWtcTabletDriverAETimeout];

return (err == noErr);
}

///////////////////////////////////////////////////////////////////////////////
// Override touch ring of a tablet associated with the input context.
//
(void)takeControlOfRingInContext:(UInt32)context
withRingCount:(UInt32)ringCount andFunctionCount:(UInt32)functionCount
{
UInt32 controlIndex;
for (controlIndex = 1; controlIndex <= ringCount; controlIndex++)
{
UInt32 functionIndex;
for (functionIndex = 1; functionIndex <= functionCount; functionIndex++)
{
// check if this function is available for override
NSAppleEventDescriptor *aeResponse = [[self class]
dataForAttribute:pFunctionAvailable
ofType:typeBoolean
ofFunction:functionIndex
ofControl:controlIndex
ofContext:context
forControlType:eAETouchRing];
if ([aeResponse booleanValue] != 0)
{
// this ring function is available for override
// send apple event to override
Boolean overrideFlag = 1;
BOOL success = [[self class] setBytes:&overrideFlag
ofSize:sizeof(overrideFlag) ofType:typeBoolean
forAttribute:pOverrideFlag
ofFunction:functionIndex
ofControl:controlIndex
ofContext:context
forControlType:eAETouchRing];
if (!success)
{
NSLog(@"Failed to override Ring#%d Function#%d.",
controlIndex, functionIndex);
}
}
}
}
}

An application that overrides tablet controls needs to register with the distributed notification center to receive overridden controls' data when user performs an action on the controls. The user info dictionary of the notifications contains the value of the control and information that indicates which tablet, control, and function the data is for:

  1. Tablet index - 1-based index for the tablet of interest.
  2. Control type - the type of the tablet control.
  3. Control index - 1-based index for the tablet control.
  4. Function index - 1-based index for the currently active function of the control.
  5. Control value - current value of the control.

Cocoa Sample Code:

#define kWacomNotificationObject           "com.wacom.tabletdriver.hardware" 
#define kWacomTabletControlNotification "com.wacom.tabletdriver.hardware.controldata"

#define kTabletNumberKey "Tablet Number" // 1-based system tablet index
#define kControlTypeKey "Control Type" // eAETouchRing, eAETouchStrip, or eAEExpressKey
#define kControlNumberKey "Control Number" // 1-based index
#define kFunctionNumberKey "Function Number" // 1-based index
#define kControlValueKey "Control Value" // current control value

///////////////////////////////////////////////////////////////////////////////
// Register observer for control data notificaations.
//
(void)observeContext:(UInt32)context
{
// NOTE: suspend the notifications when the application is in the background
// since the overrides are effective only when the application is in the foreground
if (context != 0)
{
[[NSDistributedNotificationCenter notificationCenterForType:NSLocalNotificationCenterType]
addObserver:self
selector:@selector(tabletControlData
name:@kWacomTabletControlNotification
object:@kWacomNotificationObject
suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
}
}

///////////////////////////////////////////////////////////////////////////////
// This is where we handle tablet control data notification.
//
(void)tabletControlData:(NSNotification*)note
{
// extract control data from the notification
NSDictionary *userInfo = [note userInfo];
SInt32 controlType = [[userInfo objectForKey:@kControlTypeKey] longValue];
UInt32 tabletIndex = [[dict objectForKey:@kTabletNumberKey] unsignedLongValue];
UInt32 controlIndex = [[dict objectForKey:@kControlNumberKey] unsignedLongValue];
UInt32 functionIndex = [[dict objectForKey:@kFunctionNumberKey] unsignedLongValue];
UInt32 controlValue = [[dict objectForKey:@kControlValueKey] unsignedLongValue];

// do something with the data
// ...
}