Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

Carbon
            Pasteboards:Enhanced Data Sharing

Carbon pasteboards allow applications to share data at runtime. By supporting different types of data, pasteboards take the familiar concept of the Clipboard to a new level.

Using the techniques discussed in this article, you can easily add pasteboard support to an application, including copy and paste as well as drag and drop. Setting up a service is slightly more involved, but once you handle the required Carbon Events then you can fine-tune any translation or other capabilities your application provides.

If you have used the Carbon Scrap Manager, then the Pasteboard Manager API will feel comfortable, yet provide your application with better control over the copy and paste process. Pasteboards also provide enhanced drag and drop capability when used with the Drag Manager. If you have used Cocoa Pasteboards under AppKit, then you will notice many similarities to Carbon Pasteboards.

Evolution of Pasteboards

The Carbon Scrap Manager provides a single-item Clipboard for interapplication communication (IAC). The Clipboard has historically been the mechanism for live transfer of data between Carbon (and previously, Classic) applications. Because the Scrap Manager only supports one item at a time, there is neither a history nor a set of items that can be transferred.

The Carbon Drag Manager provides more interactivity than the Scrap Manager by permitting the user to control the placement of pasted items using the mouse. In Mac OS X v10.3 Panther the Drag Manager API was updated to use pasteboards in drag operations.

Cocoa pasteboards (embodied in the NSPasteboard class) are similar to the Scrap and Drag Managers in that all three support multiple data types. However, NSPasteboard does not support multiple items.

Benefits

Adding pasteboard support to your Carbon application results in the following benefits:

  1. A pasteboard may contain multiple items. Your application can iterate over the pasteboard contents looking for a particular item by type, index, content, etc.
  2. A single data transfer model, unifying the APIs for copy and paste, drag and drop, and services.
  3. Cocoa compatibility allows Carbon and Cocoa applications to share data through use of Uniform Type Identifiers (discussed in the section Pasteboard Terms).

Carbon services may also make use of pasteboards. A service is a faceless process that provides a specific capability to applications or other services. This article provides an example of a service that handles text copying between applications, including Carbon and Cocoa.

System-Provided Pasteboards

System pasteboards exist in two familiar settings:

  1. The Clipboard is available to Carbon applications, and is designated for copy and paste operations. If your application has limited data exchange needs, the Clipboard's behavior might be sufficient. The examples in this article use the Clipboard.

  2. The system Find utility accepts search terms from other sources, and the Find pasteboard is also available to applications, as demonstrated by one of the applications in this article.

Pasteboard Terms

A pasteboard reference, type PasteboardRef, is the primary data structure for distinguishing between pasteboards. As with other Carbon data types, the pasteboard reference is opaque and you must use functions to access its fields, such as its name and contained items.

Pasteboards may support a variety of data flavors. This allows pasteboards to support multiple data types, distinguished from each other through the use of Uniform Type Identifiers, which map types in a hierarchical manner and allow for equality checking and, especially, a representation or display format. UTIs use a reverse domain name scheme. For example, com.adobe.pdf identifies the Portable Document Format flavor. LaunchServices/UTType.h has a short primer on the subject.

Pasteboards may support multiple items. You can traverse the set of items in a pasteboard by index and identifier. For each item you can inspect and change its properties.

A promise is an agreement between the provider of data and a pasteboard (the user of that data). The provider agrees to make the data available at a later time in the desired format. This is useful for large data elements that may take time to assemble, and for deferring memory allocation for large elements. The user of the data (the pasteboard) must call-in promises prior to making use of the data. This forces the provider to send the data. A promise is implemented as a callback, and registered using PasteboardSetPromiseKeeper. The operating system automatically invokes the callback when the promise is called-in.

A pasteboard consists of a global instance or resource and one or more local references to that instance. This allows multiple interface elements (such as windows) in an application to work with a local copy of the pasteboard. You need to synchronize the local reference with the global instance at certain times: any changes to the global resource will then be reflected in the local reference. The examples in this article synchronize during reads from and writes to the pasteboard.

In the sample code discussed in this article, Carbon Events provide a message-passing structure for interacting with pasteboards. This is not a requirement, and there are no pasteboard-specific events. This technique is shown as a convenience. Refer to the documents at the end of this article for more information about the Carbon Event model.

A Simple Copy and Paste Example

This first example illustrates how to copy and paste PDF data using pasteboards. The Carbon Event handler fragment in Listing 1 responds to the Copy and Paste menu item events, invoking functions that add data to a pasteboard and retrieve data from a pasteboard, respectively.

Listing 1: Handling Copy and Paste Events

    // ...
    switch (command.commandID)
    {
        case kHICommandCopy:
        // for now, only demonstrate how to put the current document content  
        // as 'pdf' on the clip board
        {
            err = AddWindowContentToPasteboardAsPDF( GetPasteboard(), docStP );
        }
        break;

        case kHICommandPaste:
        // for now, only demonstrate how to paste in a 'pdf' from the clip board
        {
            CFDataRef pdfData;
            if ( PasteboardContainsPDF( GetPasteboard(), &pdfData ) )
            {
                AttachPDFToWindow( pdfData, docStP ); // this will retain the 
                                                      // pdfData in docStP
                CFRelease( pdfData );
            }
            else
            {
                fprintf( stderr, "kHICommandPaste: Paste menu item should have been 
                    disabled!\n" );
            }
        }
        break;
    // Handle additional events...

Each of the item handlers calls the accessor function shown in Listing 2 to retrieve the static pasteboard reference to the system Clipboard. If you are not familiar with the modifier static, it marks a variable as requiring declaration and initialization only once, even inside a function (where variables are typically local in scope). Once a static variable's value is set, the variable retains its value for the life of the program or until explicitly reassigned. In this function, the declaration and assignment of sPasteboard occurs only the first time this function gets called.

Listing 2: Retrieving the Static Pasteboard Reference

PasteboardRef GetPasteboard( void )
{
    static PasteboardRef sPasteboard = NULL;

    if ( sPasteboard == NULL )
    {
        PasteboardCreate( kPasteboardClipboard, &sPasteboard );
    }

    return sPasteboard;
}

The constant kPasteboardClipboard refers to the system Clipboard, defined in HIServices/Pasteboard.h as a string:

#define kPasteboardClipboard            CFSTR( "com.apple.pasteboard.clipboard" )

The Clipboard pasteboard is reserved for copy and paste operations. If your application requires other operations, you should create your own pasteboard(s).

Copying To a Pasteboard

AddWindowContentToPasteboardAsPDF (Listing 3) transfers data to the pasteboard in response to kHICommandCopy. After first clearing the pasteboard, this function invokes a set of custom callbacks to populate the pdfData variable. (This population step is not shown. You may find it more convenient to create the data in a separate function, and then pass a pointer to that data in to a function similar to this one.) AddWindowContentToPasteboardAsPDF then calls PasteboardPutItemFlavor to copy pdfData to the pasteboard item whose ID is 1. Note that this is not necessarily the pasteboard item at index 1. The UTI identifying the flavor of this item is com.adobe.pdf.

The require variants are assertion macros that check specific conditions and jump to one of the labeled locations in this function on failure.

Listing 3: Copying PDF Data to the Pasteboard

static OSStatus AddWindowContentToPasteboardAsPDF( PasteboardRef pasteboard, 
    const DocStorage* docStP )
{
    OSStatus                 err = noErr;
    CGRect                   docRect = CGRectMake(0, 0, docStP->docSize.h, 
                                 docStP->docSize.v);
    CFDataRef                pdfData = CFDataCreateMutable( kCFAllocatorDefault, 0 );
    CGContextRef             pdfContext;
    CGDataConsumerRef        consumer;
    CGDataConsumerCallbacks  cfDataCallbacks = { MyCFDataPutBytes, MyCFDataRelease };
    
    // We need to clear the pasteboard of it's current contents so that this 
    // application can own it and add it's own data.
    err = PasteboardClear( pasteboard );
    require_noerr( err, PasteboardClear_FAILED );
    
    // snip: write the actual bytes to pdfData

    err = PasteboardPutItemFlavor( pasteboard, ( PasteboardItemID )1,
                        CFSTR( "com.adobe.pdf" ), pdfData, 0 );
    require_noerr( err, PasteboardPutItemFlavor_FAILED );
    
CGPDFContextCreate_FAILED:
PasteboardPutItemFlavor_FAILED:
    // snip: handle failures

PasteboardClear_FAILED:
    return err;
}

Copying From a Pasteboard

Pasting, or more accurately "copying from the pasteboard", is a two-step process in this example (refer back to the event handler). The first call, PasteboardContainsPDF, checks whether there is PDF data on the pasteboard. If so, copy the data to the destination by calling AttachPDFToWindow. Otherwise print an error message.

The function PasteboardContainsPDF in Listing 4 iterates over the pasteboard contents looking for the first PDF item. Note the comparison against the UTI "com.adobe.pdf" as the desired flavor. If found, the associated data is copied to the CFDataRef pointer passed in the second argument. If you look at the API documentation for PasteboardCopyItemFlavorData, you will find that the client using the CFDataRef is responsible for releasing the pointer memory.

You may consider iterating over the pasteboard contents to be a waste of cycles if you only intend to support one item and one flavor on your pasteboard. For small or simple applications that may be true. However, most applications do not remain small or simple over time. Including this loop may make modifications easier in the future. For additional flexibility, move the loop into a standalone function that returns the nth item for a given flavor, passing the flavor as a parameter. Then your pasteboard will support the return of arbitrary items and flavors without requiring you to rewrite much if any of the pasteboard traversal code.

Note that the outer loop, which iterates over the pasteboard items, is 1-based. This aspect of the Pasteboard Manager API retains compatibility with the old Drag Manager API, which was 1-based. However, the inner loop, which iterates over the CFArrayRef contents, is 0-based.

Listing 4: Copying PDF Data from the Pasteboard

static Boolean PasteboardContainsPDF( PasteboardRef inPasteboard, CFDataRef* pdfData )
{
    Boolean              gotPDF      = false;
    OSStatus             err         = noErr;
    PasteboardSyncFlags  syncFlags   = PasteboardSynchronize( inPasteboard );
    ItemCount            itemCount;
    UInt32               itemIndex;
    
    if ( syncFlags & kPasteboardModified )
    {
        fprintf( stderr, "Pasteboard modified\n" );
    }
    
    // Count the number of items on the pasteboard so we can iterate through them.
    err = PasteboardGetItemCount( inPasteboard, &itemCount );
    require_noerr( err, PasteboardGetItemCount_FAILED );
    
    for ( itemIndex = 1; itemIndex <= itemCount; ++itemIndex )
    {
        PasteboardItemID    itemID;
        CFArrayRef          flavorTypeArray;
        CFIndex             flavorCount;
        CFIndex             flavorIndex;
        
        // Every item is identified by a unique value.
        err = PasteboardGetItemIdentifier( inPasteboard, itemIndex, &itemID );
        require_noerr( err, PasteboardGetItemIdentifier_FAILED );
        
        // The flavorTypeArray is a CFType and we'll need to call CFRelease on it 
        // later.
        err = PasteboardCopyItemFlavors( inPasteboard, itemID, &flavorTypeArray );
        require_noerr( err, PasteboardCopyItemFlavors_FAILED );
        
        flavorCount = CFArrayGetCount( flavorTypeArray );
        
        for ( flavorIndex = 0; flavorIndex < flavorCount; ++flavorIndex )
        {
            CFStringRef           flavorType;
            CFComparisonResult    comparisonResult;
            
            // grab the flavorType so we can extract it's flags and data
            flavorType = ( CFStringRef )CFArrayGetValueAtIndex( flavorTypeArray, 
                flavorIndex );

            // If it's a pdf, get it and return true
            comparisonResult = UTTypeConformsTo( flavorType, CFSTR( "com.adobe.pdf" ) );
            if ( comparisonResult == kCFCompareEqualTo )
            {
                if ( pdfData != NULL )
                {
                    err = PasteboardCopyItemFlavorData( inPasteboard, itemID, 
                        flavorType, pdfData );
                    require_noerr( err, PasteboardCopyItemFlavorData_FAILED );
                }

                // Exit this loop after finding the PDF flavor in this item.
                // The break eliminates unnecessary iterations.
                // However, if other items contain PDF data they will
                // overwrite the first one found.
                gotPDF = true;
                break;
            }
                        
PasteboardCopyItemFlavorData_FAILED:
            ;
        }
        
        CFRelease( flavorTypeArray );

PasteboardCopyItemFlavors_FAILED:
PasteboardGetItemIdentifier_FAILED:
        ;
    }
    
PasteboardGetItemCount_FAILED:    
    return gotPDF;
}

The function AttachPDFToWindow, shown in Listing 5, associates the passed-in data with the second argument, a DocStorage pointer. The DocStorage type is defined elsewhere and is a custom data structure, not a window as suggested by the function name. DocStorage may or may not be able to draw the PDF data directly, which is why this function name uses the verb attach rather than paste.

If the DocStorage pointer already has a reference to existing data, it releases that reference before adding a reference to the new data. CFRetain and CFRelease are part of the reference counting mechanism that allows the operating system to automatically free the memory associated with an object when no more active references to that object exist. Note the complementary use of CFRetain and CFRelease when operating on the CFDataRef argument. Always use the same API for reference counting and for memory allocation and deallocation, otherwise unexpected behavior may occur.

Listing 5: Associating Data with a Window

static void AttachPDFToWindow( CFDataRef pdfData, DocStorage* docStP )
{
    if ( docStP->pdfData != NULL )
    {
        CFRelease( docStP->pdfData );
        docStP->pdfData = NULL;
    }

    docStP->pdfData = CFRetain( pdfData );
}

Advanced Pasteboard Manipulation

The next example is the Pasteboard Peeker sample code, available on the ADC website. Pasteboard Peeker runs as an application that also provides a text manipulation service. It makes available in the Services menu an item named Pasteboard Peeker Service that copies text to a pasteboard, then displays that text in multiple flavors in a HITextView object (or Text View).

Figure 1 shows the contents of the pasteboard after copying text containing an HTML tag.

Pasteboard text content

Figure 1: Pasteboard Content

To experiment, first run the Pasteboard Peeker application. In the text window, highlight a portion of the default text (or enter something new), and select Edit > Copy, then Edit > Paste. Pasteboard Peeker displays the selected text in several forms. The main window contains a HITextView object that displays the pasteboard content (one item) in multiple flavors as well as converted to uppercase and lowercase. You can also paste text from other applications. In addition, Pasteboard Peeker supports the revised drag and drop model (new in Mac OS X v10.3 Panther) that relies on pasteboards.

Pasteboard Related Events

Listing 6 shows the Pasteboard Peeker event handler, which supports pasteboard related events, including Copy, Paste, dragging and dropping, and several custom events. This event handler is broken into sections responsible for specific event classes and event IDs. Note the hierarchical relationship between class and ID.

The first handled event class is kEventClassCommand, which includes events generated by menu commands. This function responds to the events kHICommandCopy, kHICommandPaste, and several custom commands, including converting selected text to uppercase, converting to lowercase, and displaying the pasteboard contents in the Text View. All command IDs are of type OSType, a 32-bit integer commonly displayed as a four character sequence, such as 'UPPR', 'LOWR', and so on. Figure 2 shows how this sequence maps to the Command field in the Menu Item Info view for the uppercase translation item in Interface Builder.

Uppercase menu item info

Figure 2: Uppercase Menu Item Info

The second class of supported event is kEventClassControl. The only supported event in that class is kEventControlTrack, which occurs as a result of dragging text in or from the Text View. The function handling this event illustrates the Panther approach to initiating a drag, using the Pasteboard Manager API.

The third supported event class is kEventClassService. This class defines the events that help distinguish the service provided by this application.

All of the system-defined event constant definitions (those beginning with a 'k') discussed here are found in HIToolbox/CarbonEvents.h. The commandIDs specified as four-character literals are custom to this application.

The TXNObject type used here is the text object associated with the HITextView in the application's window. In this article the distinction is blurred: the text object is referred to as "the Text View", and for practical purposes it is the focus of the text display. For a better description of the distinction between HITextView and TXNObject refer to the Multilingual Text Engine Reference.

Listing 6: A Carbon Event Handler that Supports Multiple Pasteboard Related Events

OSStatus EventHandler( EventHandlerCallRef inHandlerCallRef, EventRef inEvent, 
    void *inUserData )
{
    OSStatus    err = eventNotHandledErr;
    EventClass  eventClass = GetEventClass( inEvent );
    EventKind   eventKind = GetEventKind( inEvent );
    TXNObject   txnObject = ( TXNObject )inUserData;
    
    switch( eventClass )
    {
        case kEventClassCommand:
        {
            HICommand command;
            
            require_noerr( GetEventParameter( inEvent, kEventParamDirectObject, 
                typeHICommand, NULL, sizeof( command ), NULL, &command ) , 
                CantGetHICommand );
            
            switch( command.commandID )
            {
                case kHICommandCopy: // override the text view's copy support
                {
                    err = AddDataToPasteboard( gClipboard, txnObject, true );
                }
                break;
                
                case kHICommandPaste: // override the text view's paste support
                {
                    err = GetDataFromPasteboard( gClipboard, txnObject );
                }
                break;
                
                case 'UPPR': // handle the request for uppercase translation
                {
                    err = RequestTextTranslation( 
                        CFSTR( "com.apple.pasteboardpeeker.uppercasetext" ), 
                        txnObject );
                }
                break;
                
                case 'LOWR': // handle the request for lowercase translation
                {
                    err = RequestTextTranslation( 
                        CFSTR( "com.apple.pasteboardpeeker.lowercasetext" ),
                        txnObject );
                }
                break;
                
                case 'FSEL': // add selected text to the find pasteboard
                {
                    err = AddDataToPasteboard( gFindPasteboard, txnObject, false );
                }
                break;
                
                case 'FIND': // show the find pasteboard contents
                {
                    err = GetDataFromPasteboard( gFindPasteboard, txnObject );
                }
                break;
            }
        }
        break;
        
        case kEventClassControl:
        {
            switch( eventKind )
            {
                case kEventControlTrack: // initiate a drag
                {
                    err = HandleDragInitiation( inEvent, txnObject );
                }
                break;
            }
        }
        break;
        
        case kEventClassService:
        {
            switch( eventKind )
            {
                case kEventServiceGetTypes: // advertise servicable types
                {
                    err = HandleGetTypes( inEvent, txnObject );
                }
                break;
                
                case kEventServiceCopy: // copy data from the text object to the 
                                        // service pasteboard
                {
                    err = HandleServiceCopy( inEvent, txnObject );
                }
                break;
                
                case kEventServicePaste: // paste data from the service pasteboard 
                                         // to the text object
                {
                    err = HandleServicePaste( inEvent, txnObject );
                }
                break;
                
                case kEventServicePerform: // handle service and translation 
                                           // requests
                {
                    err = HandlePerformService( inEvent, txnObject );
                }
                break;
            }
        }
        break;
    }
    
CantGetHICommand:
    
    return err;
}

The remainder of this article discusses the individual event functions.

Inspecting Pasteboard Contents

PasteboardPeeker displays its contents in a Text View inside a window. Most pasteboards will not need this capability, but it is useful for understanding how a pasteboard stores data and for inspecting flavors and content. Much of the function GetDataFromPasteboard in Listing 7 involves formatting data for display. This discussion covers the entire function, but your pasteboards may not need all this capability.

GetDataFromPasteboard is called from several places in the program. This function takes as arguments a reference to a pasteboard and a text object. If you follow the calling chain back up several levels to main you will see that this text object is the Text View. During installation of the event handler the Text View was specified as the void * inUserData parameter.

    TXNObject            txnObject = NULL;
    ...
    err = InstallControlEventHandler( textView, EventHandler, 
        GetEventTypeCount( eventList ), eventList, txnObject, NULL );

The TXNObject type is defined in HIToolbox/MacTextEditor.h.

The call to PasteboardSynchronize forces the local pasteboard reference to sync with the global pasteboard. The inline comments in the source code state that this is not a typical use of synchronization. Most applications should check the pasteboard upon receiving the kEventAppActivated event, and adjust the Paste menu item accordingly.

Following synchronization, this function sets the initial content for the Text View to the pasteboard reference value and item count. The function then iterates over each item in the pasteboard, retrieving and appending to the Text View content the index and ID values. Note that the loop index is 1-based: this conforms to the old Drag Manager API, and should simplify porting to the new pasteboard model. Next, for each flavor of the item, the function appends the UTI, the flavor as a pasteboard flavor type (if defined) or an OSType, and an appropriate representation of the text.

Listing 7: Displaying Pasteboard Data

OSStatus GetDataFromPasteboard( PasteboardRef inPasteboard, TXNObject inTXNObject )
{
    OSStatus             err = noErr;
    PasteboardSyncFlags  syncFlags;
    ItemCount            itemCount;
    char                 pasteboardText[ 64 ];
    
    // Check to see whether the pasteboard has been updated. This isn't strictly 
    // necessary because the creation routine syncs automatically. Typically the  
    // sync of the pasteboard would be checked when the application is brought  
    // forward to see if there is any tasty information on the pasteboard. If so,  
    // the paste menu item should be enabled. For PasteboardPeeker, we accept 
    // all data so the paste menu item is always enabled.
    // For instance, the commented out require statement would only allow data to be 
    // retrieved from the pasteboard if it had been modified since the last time we 
    // looked.
    syncFlags = PasteboardSynchronize( inPasteboard );
    //require_action( syncFlags&kPasteboardModified, PasteboardOutOfSync, 
            err = badPasteboardSyncErr );
    
    // Count the number of items on the pasteboard so we can iterate through them.
    err = PasteboardGetItemCount( inPasteboard, &itemCount );
    require_noerr( err, CantGetPasteboardItemCount );
    
    // We need to clear the text view and add the pasteboard and item count 
    // information.
    sprintf( pasteboardText, "PasteboardRef: %d  ItemCount: %d\n", 
        ( int )inPasteboard, ( int )itemCount );
    TXNSetData( inTXNObject, kTXNTextData, pasteboardText, strlen( pasteboardText ), 
        kTXNStartOffset, kTXNEndOffset );
    
    for( UInt32 itemIndex = 1; itemIndex <= itemCount; itemIndex++ )
    {
        PasteboardItemID  itemID;
        CFArrayRef        flavorTypeArray;
        CFIndex           flavorCount;
        char              itemText[ 64 ];
        
        // Every item is identified by a unique value.
        err = PasteboardGetItemIdentifier( inPasteboard, itemIndex, &itemID );
        require_noerr( err, CantGetPasteboardItemIdentifier );
        
        // Now lets add the item index, identifier and flavor count information 
        // to the text view.
        sprintf( itemText, "   Index: %d  item ID: %d\n", (int)itemIndex, 
            (int)itemID );
        TXNSetData( inTXNObject, kTXNTextData, itemText, strlen( itemText ), 
            kTXNEndOffset, kTXNEndOffset );
        
        // The item's flavor types are retreived as an array which we are responsible 
        // for releasing later. It's important to take into account all flavors, 
        // their flags and the context the data will be used when deciding which 
        // flavor ought to be used. The flavor type array is a CFType and we'll need 
        // to call CFRelease on it later.
        err = PasteboardCopyItemFlavors( inPasteboard, itemID, &flavorTypeArray );
        require_noerr( err, CantCopyPasteboardItemFlavors );
        
        // Count the number of flavors in the item so we can iterate through them.
        flavorCount = CFArrayGetCount( flavorTypeArray );
        
        for( CFIndex flavorIndex = 0; flavorIndex <= flavorCount; flavorIndex++ )
        {
            CFStringRef            flavorType, nsPboardFlavorType, osTypeFlavorType;
            PasteboardFlavorFlags  flavorFlags;
            CFDataRef              flavorData;
            CFIndex                flavorDataSize;
            char                   flavorTypeStr[ 128 ], nsPboardFlavorTypeStr[ 64 ], 
                                         osTypeFlavorTypeStr[ 5 ];
            char                   flavorText[ 256 ];
            
            if ( flavorIndex < flavorCount )
                // grab the flavor name so we can extract it's flags and data
                flavorType = ( CFStringRef )CFArrayGetValueAtIndex( flavorTypeArray, 
                    flavorIndex );
            else
                // go through these steps one extra time to look for our  
                // request only flavor
                flavorType = CFSTR( "com.apple.pasteboardpeeker.requestonly" );
            
            // Getting the flavor flags gives us insight to the nature of the flavor. 
            // If your application doesn't want to access 
            err = PasteboardGetItemFlavorFlags( inPasteboard, itemID, flavorType, 
                &flavorFlags );
            require_noerr( err, CantGetItemFlavorFlags );
            
            // Having looked at the item's flavors and their flags we've settled on 
            // the data we want to reteive.  Because we're copying the flavor data 
            // we'll need to dispose of it via CFRelease when we no longer need it.
            err = PasteboardCopyItemFlavorData( inPasteboard, itemID, flavorType, 
                &flavorData );
            require_noerr( err, CantCopyFlavorData );
            
            flavorDataSize = CFDataGetLength( flavorData );
            
            nsPboardFlavorType = UTTypeCopyPreferredTagWithClass( flavorType, 
                kUTTagClassNSPboardType );
            
            osTypeFlavorType = UTTypeCopyPreferredTagWithClass( flavorType, 
                kUTTagClassOSType );
            
            // Now that we have the flavor, flags, and data we need to format it 
            // nicely for the text view.
            CFStringGetCString( flavorType, flavorTypeStr, 128, 
                kCFStringEncodingMacRoman );
            
            if ( nsPboardFlavorType != NULL )
                CFStringGetCString( nsPboardFlavorType, nsPboardFlavorTypeStr, 64, 
                    kCFStringEncodingMacRoman );
            else
                nsPboardFlavorTypeStr[0] = 0;
            
            if ( osTypeFlavorType != NULL )
                CFStringGetCString( osTypeFlavorType, osTypeFlavorTypeStr, 5, 
                    kCFStringEncodingMacRoman );
            else
                osTypeFlavorTypeStr[0] = 0;
            
            sprintf( flavorText, 
                    "      \"%.80s\"\n      \"%s\"\n      '%s' %c%c%c%c%c%c %d  ",
                    flavorTypeStr, nsPboardFlavorTypeStr, osTypeFlavorTypeStr,
                    ( flavorFlags&kPasteboardFlavorPromised ) ? 'P' : '_',
                    ( flavorFlags&kPasteboardFlavorSystemTranslated ) ? 'T' : '_',
                    ( flavorFlags&kPasteboardFlavorRequestOnly ) ? 'r' : '_',
                    ( flavorFlags&kPasteboardFlavorNotSaved ) ? 'n' : '_',
                    ( flavorFlags&kPasteboardFlavorSenderTranslated ) ? 't' : '_',
                    ( flavorFlags&kPasteboardFlavorSenderOnly ) ? 's' : '_',
                    ( int )flavorDataSize );
            TXNSetData( inTXNObject, kTXNTextData, flavorText, strlen( flavorText ), 
                kTXNEndOffset, kTXNEndOffset );
            
            if ( nsPboardFlavorType != NULL )
                CFRelease( nsPboardFlavorType );
            
            if ( osTypeFlavorType != NULL )
                CFRelease( osTypeFlavorType );
            
            flavorDataSize = (flavorDataSize<80) ? flavorDataSize : 80;

            for( short dataIndex = 0; dataIndex <= flavorDataSize; dataIndex++ )
            {
                char byte = *(CFDataGetBytePtr( flavorData ) + dataIndex);
                
                flavorText[dataIndex] = ( byte > 40 ) ? byte : ' ';
            }

            flavorText[ flavorDataSize ] = '\n';
            flavorText[ flavorDataSize + 1 ] = '\n';
            TXNSetData( inTXNObject, kTXNTextData, flavorText, flavorDataSize+2, 
                kTXNEndOffset, kTXNEndOffset );
            
CantCopyFlavorData:
CantGetItemFlavorFlags:
            
            // Don't report the error if we couldn't find the request only flavor.
            if ( ( err == badPasteboardFlavorErr ) && ( flavorIndex == flavorCount ) )
                err = noErr;
        }
        
CantCopyPasteboardItemFlavors:
CantGetPasteboardItemIdentifier:
        ;
    }
    
CantGetPasteboardItemCount:
///PasteboardOutOfSync:
    
    return err;
}

Keeping Promises

A promise is a guarantee that data will be provided to a pasteboard at a later time. AddDataToPasteboard (discussed next) contains a call to PasteboardSetPromiseKeeper, which sets a promise on the pasteboard. The implementation of a promise is a callback of type PasteboardPromiseKeeperProcPtr. Its arguments include:

  • the local PasteboardRef to which the data should be written
  • the identifier of the pasteboard item in which to place the data
  • the flavor as a UTI in CFStringRef form. This is not used to format the data, but rather to inform the pasteboard item of the type being added.
  • the programmer-defined context passed to PasteboardSetPromiseKeeper. This example passes the Text View from the application window as the context.

The callback places the data of the appropriate flavor in the requested item on the pasteboard. Note that the CFDataRef gets released after use. The pasteboard copies the data, so the original reference is not required. Since the data was allocated dynamically using CFDataCreate, you should release the memory using CFRelease, which decrements the reference count of the memory object. Not releasing will ultimately result in a memory leak; releasing or directly freeing memory using a different API (such as free) will have unpredictable and possibly bad results.

Listing 8: Providing Data through a Promise Keeper Callback

OSStatus PromiseKeeper( PasteboardRef inPasteboard, PasteboardItemID inItem, 
    CFStringRef inFlavorType, void *inContext )
{
    OSStatus   err = noErr;
    CFDataRef  promisedData = NULL;
    
    // create the promised flavor data
    promisedData = CFDataCreate( kCFAllocatorDefault, (UInt8*)"Promised data", 14 );
    require_action( promisedData != NULL, CantCreatePromisedData, err = memFullErr );
    
    // fulfill the promised data request for item and flavor type
    err = PasteboardPutItemFlavor( inPasteboard, inItem, inFlavorType, promisedData, 0 );
    CFRelease( promisedData );
    require_noerr( err, CantFulfillPromise );
    
CantFulfillPromise:
CantCreatePromisedData:
        
    return err;
}

Copying To a Pasteboard, Revisited

This example copies data to a pasteboard in a slightly different manner than the first example. The function AddDataToPasteboard sets a promise keeper callback for the Text View. The implied agreement is that when the Text View requires data the callback will make data available. The operating system automatically invokes the callback.

This function also checks the current selection range in the Text View. The selected text is placed into pasteboard item 1 with a UTI of com.apple.traditional-mac-plain-text. This forms the basis for the new content of the Text View. The boolean argument addExtraTypes determines whether two additional, arbitrary flavors are added to the pasteboard item. The flavor flags kPasteboardPromisedData and kPasteboardFlavorRequestOnly are defined and discussed in HIServices/Pasteboard.h. kPasteboardPromisedData marks a promised piece of data, as discussed in the previous section. kPasteboardFlavorRequestOnly is a privacy marker: the receiver of the data must explicitly request this flavor. The UTIs are unique to this application.

Note the call to DisposeHandle to dispose of the memory (of type Handle *) allocated by TXNGetDataEncoded. This is the recommended approach for memory returned by that particular function. Note that TXNGetDataEncoded has been deprecated in favor of TXNWriteRangeToCFData. See HIToolbox/MacTextEditor.h for details.

Listing 9: Copying to a Pasteboard

OSStatus AddDataToPasteboard( PasteboardRef inPasteboard, TXNObject inTXNObject, 
    bool addExtraTypes )
{
    OSStatus             err = noErr;
    PasteboardSyncFlags  syncFlags;
    TXNOffset            start, end;
    Handle               dataHandle;
    CFDataRef            textData = NULL, requestOnlyData = NULL;
    
    // We need to clear the pasteboard of it's current contents so that this 
    // application can own it and add it's own data.
    err = PasteboardClear( inPasteboard );
    require_noerr( err, CantClearPasteboard );
    
    // Check to see whether the pasteboard is owned by this client. This isn't 
    // really necessary since we just cleared it, but it shows off an added feature 
    // of the PasteboardSynchronize API.  If we were a plug-in in a larger 
    // application we could find out if someone in the application has cleared
    // a pasteboard we care about. If so, we would know that we could go ahead
    // and add data.
    syncFlags = PasteboardSynchronize( inPasteboard );
    require_action( !( syncFlags&kPasteboardModified ), PasteboardNotSynchedAfterClear, 
        err = badPasteboardSyncErr );
    require_action( ( syncFlags&kPasteboardClientIsOwner ), ClientNotPasteboardOwner, 
        err = notPasteboardOwnerErr );
    
    // add a promise keeper to the pasteboard before making a promise
    err = PasteboardSetPromiseKeeper( inPasteboard, PromiseKeeper, inTXNObject );
    require_noerr( err, CantSetPromiseKeeper );
    
    // get the selection from the text view and the data within it
    TXNGetSelection( inTXNObject, &start, &end );
    err = TXNGetDataEncoded( inTXNObject, start, end, &dataHandle, kTXNTextData );
    require_noerr( err, CantGetDataFromTextObject );
    
    // allocate data based on the size of the selection
    textData = CFDataCreate( kCFAllocatorSystemDefault, (UInt8*)*dataHandle, 
        end-start );
    DisposeHandle( dataHandle );
    require_action( textData != NULL, CantCreateTextData, err = memFullErr );
    
    // add text data to the pasteboard
    err = PasteboardPutItemFlavor( inPasteboard, (PasteboardItemID)1,
        CFSTR("com.apple.traditional-mac-plain-text"), textData, 0 );
    require_noerr( err, CantPutTextData );
    
    if ( addExtraTypes )
    {
        // add some promised data
        err = PasteboardPutItemFlavor( inPasteboard, ( PasteboardItemID )1,
            CFSTR( "com.apple.pasteboardpeeker.promised" ), 
            kPasteboardPromisedData, 0 );
        require_noerr( err, CantPutPromisedData );
        
        // add some data that is available upon explicit request only
        requestOnlyData = CFDataCreate( kCFAllocatorDefault, 
            ( UInt8 * )"Request only data", 18 );
        require_action( requestOnlyData != NULL, CantCreateRequestOnlyData, 
            err = memFullErr );
        
        err = PasteboardPutItemFlavor( inPasteboard, ( PasteboardItemID )1,
            CFSTR( "com.apple.pasteboardpeeker.requestonly" ), requestOnlyData,
            kPasteboardFlavorRequestOnly );
        CFRelease( requestOnlyData );
        require_noerr( err, CantPutPasteboardRequestOnly );
    }
    
CantPutPasteboardRequestOnly:
CantCreateRequestOnlyData:
CantPutPromisedData:
CantPutTextData:
CantCreateTextData:
CantGetDataFromTextObject:
CantSetPromiseKeeper:
ClientNotPasteboardOwner:
PasteboardNotSynchedAfterClear:
CantClearPasteboard:
    
    return err;
}

Drag and Drop

Supporting drag and drop requires assignment of callbacks for drag tracking and drop handling. Listing 10 shows this setup in main. Note that tracking occurs within the HITextView, while dropping targets the text object associated with the HITextView.

Listing 10: Setting Up Drag and Drop Handlers

    err = InstallTrackingHandler( DragTrackingHandler, window, textView );
    require_noerr( err, CantInstallDragTrackingHandler );
    
    err = InstallReceiveHandler( DragReceiveHandler, window, txnObject );
    require_noerr( err, CantInstallDragReceiveHandler );

The tracking handler in Listing 11 highlights the HITextView when the drag enters and unhighlights when the drag leaves. A more complete approach would be to check the drag pasteboard contents and determine if the item being dragged corresponds to one of the types supported by this application. Call GetDragPasteboard to get the PasteboardRef; see HIToolbox/Drag.h for more information.

Listing 11: Tracking a Drag

OSErr DragTrackingHandler( DragTrackingMessage inMessage, WindowRef inWindow, 
    void *inUserData, DragRef inDrag )
{
    switch( inMessage )
    {
        // we accept all drags so hilight whenever any drag tracks over our window
        case kDragTrackingEnterWindow:
        {
            DragAttributes attributes;
            
            GetDragAttributes( inDrag, &attributes );
            
            // only show the drag hilight if the drag has left the sender window per 
            // HI spec
            if ( attributes & kDragHasLeftSenderWindow )
            {
                HIViewRef    textView = ( HIViewRef )inUserData;
                HIRect       textFrame;
                RgnHandle    hiliteRgn = NewRgn();
                
                // get the text view's frame ...
                HIViewGetFrame( textView, &textFrame );
                
                // ... and convert it into a region for ShowDragHilite
                HIShapeRef textShape = HIShapeCreateWithRect( &textFrame );
                HIShapeGetAsQDRgn( textShape, hiliteRgn );
                CFRelease( textShape );
                
                // add the drag hilight to the inside of the text view
                ShowDragHilite( inDrag, hiliteRgn, true );
                
                DisposeRgn( hiliteRgn );
            }
        }

        break;
        
        case kDragTrackingLeaveWindow: // hide the drag hilight when leaving 
                                       // the window
        {
            HideDragHilite( inDrag );
        }

        break;
    }
    
    return noErr;
}

The application event handler responds to drag events (kEventControlTrack) initiated in the Text View. After checking for a valid selection and waiting for the mouse to move, this function sets up a new pasteboard for the drag. Passing kPasteboardUniqueName, which is defined as NULL in Pasteboard.h, forces a new pasteboard to be created. Next, the selected text is added to the pasteboard, which is then associated with a DragRef. The DragRef is used to track the drag. When the user drops the selection, then the receive handler function will be called. Recall that DragReceiveHandler was installed in main as the receive handler, and will be invoked automatically.

Listing 12: Initiating a Drag

OSStatus HandleDragInitiation( EventRef inEvent, TXNObject inTXNObject )
{
    OSStatus    err = eventNotHandledErr;
    EventRecord convertedEvent;
    HIPoint     eventPoint;
    TXNOffset   start, end, offset;
    
    require_noerr( GetEventParameter( inEvent, kEventParamMouseLocation, typeHIPoint, 
        NULL, sizeof( eventPoint ), NULL, &eventPoint ), CantGetHIPoint );
    
    require_noerr( TXNHIPointToOffset( inTXNObject, &eventPoint, &offset ), 
        CantGetTXNOffsetForPoint );
    
    TXNGetSelection( inTXNObject, &start, &end );
    
    ConvertEventRefToEventRecord( inEvent, &convertedEvent );
    
    // create a drag if the mouse is tracking within a text selection
    if ( start <= offset && offset <= end && WaitMouseMoved( convertedEvent.where ) )
    {
        PasteboardRef   pasteboard;
        DragRef         drag;
        Point           trackPoint;
        RgnHandle       theRegion, insetRegion;
        
        // Create a uniquely named pasteboard for use with the drag.  Because we're 
        // creating the memory for the pasteboard we must release it later.
        err = PasteboardCreate( kPasteboardUniqueName, &pasteboard );
        require_noerr( err, CantCreatePasteboardForDrag );
        
        // add data to the drag pasteboard using the generic mechanism
        err = AddDataToPasteboard( pasteboard, inTXNObject, true );
        require_noerr( err, CantAddDataToTheDragPasteboard );
        
        // Create a new drag with an already existing pasteboard.  Alternately, we 
        // could have used the pasteboard generated with a call to NewDrag but this 
        // demonstrates a little more flexibility with the new APIs.
        err = NewDragWithPasteboard( pasteboard, &drag );
        require_noerr( err, CantCreateDrag );
        
        trackPoint.h = convertedEvent.where.h;
        trackPoint.v = convertedEvent.where.v;
        LocalToGlobal( &trackPoint );
        
        theRegion = NewRgn();
        insetRegion = NewRgn();
        SetRectRgn( theRegion, 
                    convertedEvent.where.h - 10, convertedEvent.where.v - 5,
                    convertedEvent.where.h + 10, convertedEvent.where.v + 5 );
        CopyRgn( theRegion, insetRegion );
        InsetRgn( insetRegion, 1, 1 );
        DiffRgn( theRegion, insetRegion, theRegion );
        DisposeRgn( insetRegion );
        
        // perform the drag
        err = TrackDrag( drag, &convertedEvent, theRegion );
        require_noerr( err, CantTrackDrag );
        
CantTrackDrag:
        
        DisposeRgn( theRegion );
        check_noerr( DisposeDrag( drag ) );
        
CantCreateDrag:
CantAddDataToTheDragPasteboard:
        
        CFRelease( pasteboard );
    }
    
CantCreatePasteboardForDrag:
CantGetTXNOffsetForPoint:
CantGetHIPoint:
    
    return err;
}

The drag receive handler in Listing 13 obtains the PasteboardRef and then calls the GetDataFromPasteboard function discussed earlier.

Listing 13: Handling a Drop

OSErr DragReceiveHandler( WindowRef inWindow, void *inUserData, DragRef inDrag )
{
    OSStatus        err = noErr;
    PasteboardRef   pasteboard;
    TXNObject       txnObject = ( TXNObject )inUserData;
    
    // Grab the pasteboard from the drag.  Releaseing of the pasteboard will be 
    // handled by the Drag Manager.
    err = GetDragPasteboard( inDrag, &pasteboard );
    require_noerr( err, CantGetDragPasteboard );
    
    // just take the pasteboard and hand it off to the common pasteboard code
    err = GetDataFromPasteboard( pasteboard, txnObject );
    
CantGetDragPasteboard:
    
    return err;
}

A Pasteboard Text Translation Service

A service needs to respond to specific service events. This application includes functions that handle the three events (kEventServiceGetTypes, kEventServiceCopy, and kEventServicePaste) for a service user, and the kEventServicePerform event for a service provider. See Introduction to Application Services for more information.

The first returns a list of the OSTypes that this service handles for both copy and paste. The values are placed in the arrays passed in the respective event parameters. Here only the 'TEXT' type is supported.

Listing14: Returning the OSTypes Supported by this Service

OSStatus HandleGetTypes( EventRef inEvent, TXNObject inTXNObject )
{
    OSStatus            err = noErr;
    TXNOffset           start, end;
    CFMutableArrayRef   copyTypes, pasteTypes;
    CFStringRef         type = CreateTypeStringWithOSType( 'TEXT' );
    
    TXNGetSelection( inTXNObject, &start, &end );
    
    // only add copy types if text is selected
    if ( start < end )
    {
        err = GetEventParameter( inEvent, kEventParamServiceCopyTypes, 
            typeCFMutableArrayRef, NULL, sizeof( copyTypes ), NULL, &copyTypes );
        require_noerr( err, CantGetCopyTypes );
        
        CFArrayAppendValue( copyTypes, type );
    }
    
    err = GetEventParameter( inEvent, kEventParamServicePasteTypes, 
        typeCFMutableArrayRef, NULL, sizeof( pasteTypes ), NULL, &pasteTypes );
    require_noerr( err, CantGetPasteTypes );
    
    CFArrayAppendValue( pasteTypes, type );
    
CantGetPasteTypes:
CantGetCopyTypes:
    
    return err;
}

The service copy and paste events use the earlier functions that write to and read from the pasteboard.

Listing 15: Handling Copy and Paste Service Events

OSStatus HandleServiceCopy( EventRef inEvent, TXNObject inTXNObject )
{
    OSStatus        err = noErr;
    PasteboardRef   pasteboard;
    
    err = GetEventParameter( inEvent, kEventParamPasteboardRef, typePasteboardRef, 
        NULL, sizeof( pasteboard ), NULL, &pasteboard );
    require_noerr( err, CantGetServicePasteboard );
    
    err = AddDataToPasteboard( pasteboard, inTXNObject, false );
    
CantGetServicePasteboard:
    
    return err;
}

OSStatus HandleServicePaste( EventRef inEvent, TXNObject inTXNObject )
{
    OSStatus        err = noErr;
    PasteboardRef   pasteboard;
    
    err = GetEventParameter( inEvent, kEventParamPasteboardRef, typePasteboardRef, 
        NULL, sizeof( pasteboard ), NULL, &pasteboard );
    require_noerr( err, CantGetServicePasteboard );
    
    err = GetDataFromPasteboard( pasteboard, inTXNObject );
    
CantGetServicePasteboard:
    
    return err;
}

HandlePerformService responds to the final service event, kEventServicePerform. This event must be handled by service providers (as opposed to service users). The action to be taken is based on the serviceName extracted from the event.

The first action handled in Listing 16 simply places the existing pasteboard data in the Text View. This occurs in response to the user selecting the NSMenuItem "Pasteboard Peeker Service" in the Services menu, which passes a NSMessage value of "PeekerService". This menu item and message are defined in the project's Info.plist.

If Service > Pasteboard Peeker Service was not selected, then the request must be for a conversion to uppercase or lowercase. The two NSFilter items in the Info.plist determine which of these will occur: one maps to "UpperCaseTranslation", the other to "LowerCaseTranslation". The converted text is placed back on the pasteboard. Note the use of CFDataGetBytePtr and CFDataGetMutableBytePtr to "cast" the source and destination data references, the latter to a mutable array.

The inline comments describe the translation process a bit further.

Listing 16: Responding to Translation Requests

// HandlePerformService
//
//    PasteboardPeeker provides two data translations from 
//    "com.apple.traditional-mac-plain-text" to 
//    "com.apple.pasteboardpeeker.uppercasetext" and 
//    "com.apple.pasteboardpeeker.lowercasetext".
//
//    To provide these translations, PasteboardPeeker must be a filter service. 
//    This means adding a set of NSServices entries in the info.plist (NSFilter, 
//    NSSendTypes, NSReturnTypes, and NSSupportsData/FileTranslations) describing
//    the translations provided.  When a translation is requested, the filter 
//    service will be launched and sent a kEventServicePerform message.  
//    This routine handles that message.

OSStatus HandlePerformService( EventRef inEvent, TXNObject inTXNObject )
{
    OSStatus            err = noErr;
    CFStringRef         serviceName, returnType;
    PasteboardRef       pasteboard;
    PasteboardItemID    item;
    CFDataRef           sourceData;
    CFIndex             sourceSize;
    CFMutableDataRef    returnData = NULL;
    const UInt8*        sourceBytes;
    UInt8*              returnBytes;
    
    // Find out what service is being requested of us.  The services  we support
    // are defined in the applicaiton's plist.
    err = GetEventParameter( inEvent, kEventParamServiceMessageName, typeCFStringRef, 
        NULL, sizeof( CFStringRef ), NULL, &serviceName );
    require_noerr( err, CantGetServiceName );
    
    // Get ahold of the pasteboard containing the data being sent to us.  Don't
    // release it as it was created for us, not by us.  Later we'll clear it
    // and add our translated data to it.
    err = GetEventParameter( inEvent, kEventParamPasteboardRef, typePasteboardRef, 
        NULL, sizeof( PasteboardRef ), NULL, &pasteboard );
    require_noerr( err, CantGetPasteboardRef );
    
    // perform peeker service
    if ( CFStringCompare( serviceName, CFSTR( "PeekerService" ), 0 ) == 
        kCFCompareEqualTo )
    {
        GetDataFromPasteboard( pasteboard, inTXNObject );
    }
    else // handle the translation services
    {
        // Get ahold of the data to be translated. We only support translation of
        // "com.apple.traditional-mac-plain-text" so if it doesn't exist in the
        // first item we must fail.
        err = PasteboardGetItemIdentifier( pasteboard, 1, &item );
        require_noerr( err, CantGetItemIdentifier );
        
        err = PasteboardCopyItemFlavorData( pasteboard, item,
            CFSTR( "com.apple.traditional-mac-plain-text" ), &sourceData );
        require_noerr( err, CantGetSourceData );
        
        sourceSize = CFDataGetLength( sourceData );
        
        // create the translation's return data storage
        returnData = CFDataCreateMutable( kCFAllocatorDefault, sourceSize );
        require_action( returnData != NULL, CantCreateReturnData, err = memFullErr );
        
        sourceBytes = CFDataGetBytePtr( sourceData );
        returnBytes = CFDataGetMutableBytePtr( returnData );
        CFDataSetLength( returnData, sourceSize );
        
        // perform the uppercase translation
        if ( CFStringCompare( serviceName, CFSTR( "UpperCaseTranslation" ), 0 ) == 
            kCFCompareEqualTo )
        {
            returnType = CFSTR( "com.apple.pasteboardpeeker.uppercasetext" );
            
            for( CFIndex i = 0; i< sourcesize; i++ )
                returnbytes[i] = ( UInt8 )toupper( sourceBytes[ i ] );
        }
        else // perform the lowercase translation
        {
            returnType = CFSTR( "com.apple.pasteboardpeeker.lowercasetext" );
            
            for( CFIndex i = 0; i< sourceSize; i++ )
                returnBytes[ i ] = ( UInt8 )tolower( sourceBytes[ i ] );
        }
        
        // clear the pasteboard so we can add our translated return data
        err = PasteboardClear( pasteboard );
        require_noerr( err, CantClearPasteboard );
        
        // add the translated data
        err = PasteboardPutItemFlavor( pasteboard, item, returnType, returnData, 0 );
        require_noerr( err, CantAddTranslatedData );
        
CantAddTranslatedData:
CantClearPasteboard:
        
        CFRelease( returnData );
        
CantCreateReturnData:
        
        CFRelease( sourceData );
    }

CantGetSourceData:
CantGetItemIdentifier:    
CantGetPasteboardRef:
CantGetServiceName:
    
    return err;
}

For More Information

Many header files document their respective APIs very thoroughly. The tricky part can be determining which framework to look in:

Carbon.framework

  • HIToolbox/CarbonEvents.h contains information about the kEventClassService suite of events, and receiving and providing services.
  • HIToolbox/Drag.h discusses the new pasteboard support in the Drag Manager.

ApplicationServices.framework

  • LaunchServices/UTType.h has information on Uniform Type Identifiers and the programming interface.
  • HIServices/Pasteboard.h contains function, data type, and constant information.
  • HIServices/TranslationServices.h explains more about providing a translation service and the API for requesting translations.

Updated: 2005-04-29