Scripting Plug-ins

Sections on this Page

Adobe Photoshop 4.0 introduced a new palette and subsequent set of commands and callbacks: the Actions palette, and the Descriptor callback suite. The Actions palette is the user-interface and hub for the scripting system for Adobe Photoshop. Adobe Photoshop 5.0 extends the Actions structure to include automation plug-ins that can access all scriptable Photoshop commands.

Actions allow commands in Photoshop to be recorded in a form that is easy for an end user to read and edit. Actions are similar to AppleScript and AppleEvents but are cross platform and designed to support both AppleScript on the Macintosh and OLE Automation on Windows.

Actions extend the plug-in API to allow Import, Export, Filter, Format and Selection plug-in modules to be fully recordable and automated.

Scripting on Windows with OLE
While the scripting system operates consistently across both Windows and Macintosh platforms, in Photoshop 4.0 and 5.0, additional OLE automation has been added. See OLE Automation Programming Guide.

AppleScript and AppleEvents recommended reading
Since Actions are based on AppleScript and AppleEvents, we recommend the following materials for preliminary reading:

  • Inside Macintosh: Interapplication Communication (Addison-Wesley, 1993);
  • "Apple Event Objects and You" (Richard Clark, develop, issue 10);
  • "Better Apple Event Coding Through Objects" (Eric M. Berdahl, develop, issue 11);
  • "Designing Scriptability" (Cal Simone, develop, issue 21);
  • Series: "According to Script" (Cal Simone, develop, issues 22-25);

See MacTech develop Article Archives.

All the plug-in module examples that support scripting have been updated. Detailed code-related information is available in each separate module example and in PIActions.h.

Photoshop 5.0 Automation plug-ins
See Photoshop Actions Event Guide for implementation details on Adobe Photoshop 5.0 Automation plug-in modules.

Scripting Basics

For a plug-in to be scripting-aware, or able to record scripting parameters and be automated by them, it requires the addition of two basic mechanisms:

  • Terminology resource. A terminology resource maps the keys to human readable text, providing additional type information for values. For instance, key keyLuminance ('Lmnc') and its value typeInteger may be mapped to the human readable text "luminance value". This is accompanied by the HasTerminology PiPL property, which points the scripting system to the terminology resource.
  • Descriptors. A descriptor is a pair of data in the form of [<key> <value>] that describes the property of an object or the parameter of an event.

Implementation Order

We recommend you convert existing plug-ins to scripting-aware plug-ins by following this scripting implementation order:

  1. Look at your user interfaces and describe the parameters as human-readable text;
  2. Create a terminology resource for your plug-in;
  3. Add the HasTerminology PiPL property;
  4. Update your plug-in code to record scripting events and objects;
  5. Update your plug-in code to be automated by (play back) scripting events and objects.

Scripting Caveats

The scripting system has been designed specifically to drive plug-ins in a way that is transparent to the existing operation of the host. This means that there is no way to know whether your plug-in is being driven by the scripting system or an end-user. You should treat all operations as consistently as possible.

The scripting system always hands the plug-in a descriptor at every selector call.

If the plug-in uses a descriptor that was handed to it by the host, and it hands back a new descriptor, the plug-in is responsible for deleting the old descriptor. All the examples do this through the set of utility routines in PIUtilities.

If the plug-in doesn’t use the descriptor handed to it by the host, the plug-in can hand it back and it will be deleted automatically.

If the plug-in doesn’t use the descriptor handed to it by the host, but the plug-in hands back NULL, then the plug-in is responsible for deleting the descriptor the host handed it.

Creating a Terminology Resource

A terminology resource is used to specify the mapping from a descriptor to human readable text. The format of the terminology resource is identical to an AppleEvent terminology resource. CNVTPIPL.EXE on Windows understands this resource and converts it accordingly. All the example plug-ins have a rez entry in the plugInName.r file for an 'aete' resource.

For further information on the 'aete' resource, see "Making Sense of 'aete' Resources" (Richard McGath, MacTech, Volume 11, issue 7), found in the MacTech Article Archives.

To let Photoshop know that the terminology resource is present, a PiPL property is added, HasTerminology ('hstm'), which contains the class ID, event ID, and terminology resource ID for your plug-in. See Scripting-specific PiPL Properties.

The terminology resource is a complex structure designed by Apple to cover numerous scripting situations that are not required by Photoshop. By that nature, the structure is more complicated than it needs to be to describe plug-ins. However, it was chosen because Apple plans to support it both now and in the future, and it allows you to increase the scope of your plug-in by being AppleEvent- and AppleScript-savvy. See AppleScript Compatibility.

Basic Terminology Resource Format

resource 'aete' (0)
{   // aete version and language specifiers
    { // suite descriptor 
        { // filter/selection/color picker descriptor 
            { // any parameters 
                // additional parameters 
            }
        },
        { // import/export/format descriptors 
            { // properties. First property defines inheritance. 
                // any properties 
            },
            { // elements. Not supported for plug-ins. 
            },
            // class descriptions for other classes used as parameters or properties 
        },
        { // comparison ops. Not currently supported. 
        },
        { // any enumerations 
            {
                // additional values for enumeration 
            },
            // any additional enumerations 
            // variant types are a special enumeration: 
            {
                // additional types for variant 
            },
            // any additional variants 
            // class and reference types are a special enumeration: 
            {
            },
            // any additional class or reference types 
        }
    }
}

Whether your plug-in is a filter, or one of the others, each section of the terminology resource must be present (even if it is blank "{},").

Detailed Terminology Resource Example

resource 'aete' (0)
{
    1, 0, english, roman,                   // aete version and language specifiers
    { // suite descriptor below         
        "suite name",                       // name of suite
        "description",                      // optional description of suite
        'stID',                             // suite ID, must be unique 4-char code
        1,                                  // suite code, must be 1
        1,                                  // suite level, must be 1
        { // filter/selection/color picker descriptor below 
            "plug-in name",                 // name of plug-in, must be unique
            "description",                  // optional description of filter
            'clID',                         // class ID, must be unique 
                                            // 4-char code or suite ID
            'evID',                         // event ID, must be unique 4-char 
                                            //code within class (may be suite ID)
            NO_REPLY,                       // never a reply
            IMAGE_DIRECT_PARAMETER          // direct parameter
            { // any parameters below 
            "parameter name",               // name of parameter
            'kyID',                         // parameter key ID. 
            'tyID',                         // parameter type ID. 
            flagsTypeParameter,             // parameter flags. 
            // additional parameters here 
            }
        },
        { // import/export/format descriptors below 
            "plug-in name",                 // name of plug-in, must be unique
            'clID',                         // class ID, must be unique 
                                            // 4-char code or suite ID
            "description",                  // optional description of plug-in
            { // properties below. First property defines inheritance. 
                "<Inheritance>",            // required
                keyInherits,                // required
                classInherited,             // parent class: Format, Import, or Export
                "",                         
                flagsSingleProperty,
                // any properties below 
                "property name",            // name of property
                'kyID',                     // property key ID. 
                'tyID',                     // property type ID. 
                "description",              // optional description
                flagsTypeProperty,          // property flags. 
            },                              
            { // elements. Not supported for plug-ins. 
            },
            // class descriptions for other classes used as parameters or properties 
        },
        { // comparison ops. Not currently supported. 
        },
        { // any enumerations below 
            'enID',                         // enumeration ID
            {                               
                "enumerated name",          // first value name
                'e1ID',                     // first value ID
                "description",              // optional description of first value
                // additional values for this enumeration 
            },
            // any additional enumerations 
            // variant types are a special enumeration: 
            '#vID',                         // variant ID (must begin with "#")
            {                               
                "type name",                // first type name
                'v1ID',                     // first type ID
                "",
                // additional types for variant 
            },
            // any additional variants 
            // class and reference types are a special enumeration: 
            '#tID',                         // enumeration ID (must begin with "#")
            {                               
                "type name",                // name of type
                't1ID',                     // type ID. Either typeClass or 
                                            // typeObjectReference.
                ""
            },
            // any additional class or reference types 
        }
    }
}

Nomenclature

The user terms in the terminology resource should be all lower case with the exception of proper names and acronyms. Photoshop will capitalize terms appropriately.

Parameters and Properties

The terminology resource has two basic ways to describe information a Photoshop plug-in needs to provide scripting.

For Filter, Selection and Color Picker plug-ins, parameters generally provide keys for an event (see Filter, Selection, and Color Picker Events). They usually describe input that a plug-in receives from the user through the plug-in user interface.

For Import, Export and Format plug-ins, input from the user is structured through class definitions, which are objects that a command acts on. See Classes and the Terminology Resource and Import, Export, and Format Objects. Properties provide information about a class.

In the terminology resource, both parameters and properties are defined by four basic pieces of information:

  • A name, a string that provides the parameter or property name that appears in the Actions Palette.
  • A key, a four character unique identifier for the property or parameter. You can define keys for your plug-in, or use the Photoshop pre-defined keys.
  • A type, the data type for the parameter, e.g. typeInteger. See Basic Value Types.
  • A set of flags, which provide information about how the property or parameter is used. See Parameter and Property Flags.

Parameter and Property Flags

Parameter and property flags provide information about how parameters and properties are used.

Parameter flags indicate,for example, whether the parameter takes single or multiple values, or whether its value comes from an enumeration. See the Dissolve.r file in Dissolve filter sample code for use of parameter flags in a PiPL resource.

Similarly, property flags indicate, for example, whether an object can have one or more of a particular property, or whether its value comes from an enumeration. See the GradientImport.r file in GradientImport sample code for use of property flags in a PiPL resource.

The Photoshop SDK predefines flags for parameters and properties, which make coding of the 'aete' resource somewhat simpler. See parameter flags and property flags.

The flags for properties are the same for parameters, except there is not a flag for optional properties. Properties can be optional by putting "optional" at the beginning of the description field.

Note:
The AppleScript Editor doesn’t display parameter type list correctly. In order for the dictionary to read correctly, the description field for the type should begin with the word "list".

Classes and the Terminology Resource

For Import, Export, and Format plug-ins, the first property in the terminology resource must indicate inheritance ("<Inheritance>") from the base class for the plug-in. For example, Import plug-ins need to inherit from the base class classImport. Additional classes may be defined as templates for parameters or properties.

The Photoshop SDK provides a number of "predefined classes" that are available for use in the terminology resource. A useful subset of those classes is shown in the table below. Use these classes when they are appropriate, but you can define new classes in the terminology resource, if necessary. See the GradientImport.r file in GradientImport sample code for an example of defining a class (classMultiImportStruct) in a terminology resource.

A Selection of Useful Predefined Classes
Name Code Description/keys
classImport 'Impr' Class for Import modules.
classExport 'Expr' Class for Export modules.
classFormat 'Fmt ' Class for Format modules.
classColor 'Clr ' Class for color classes.
classRGBColor 'RGBC' keyRed, keyGreen, keyBlue
classCMYKColor 'CMYC' keyCyan, keyMagenta, keyYellow, keyBlack.
classUnspecifiedColor 'UnsC' Unspecified.
classGrayscale 'Grsc' keyGray
classBookColor 'BkCl' Book color.
classLabColor 'LbCl' keyLuminance, keyA, keyB.
classHSBColor 'HSBC' keyHue, keySaturation, keyBrightness.
classPoint 'Pnt ' keyHorizontal, keyVertical.

Class Inheritance

The Inheritance property ("<Inheritance>") can also be used to specify a hierarchy of types. Inheritance is used by defining a base class with the first property configured with:

  • Name = the name of the class;
  • Type = class type.

Class types are defined by creating a special enumeration.

For example, the class "RGB color" is defined based on the class "color". If the class color is specified as a parameter or property type, then any of its subclasses are acceptable. Here is a definition for the class color:

    { /* suite descriptor below */
        "color",                        // class name
        classColor,                     // class ID for Color 'Clr '
        "",                             // no description
        {
            "color",                    // color property (special for base class)
            keyColor,                   // property ID for Color 'Clr '
            typeClassColor,             // type this class
            "",                         // no description
            flagsEnumeratedParameter    // "type" is special enumeration
        },
        { /* no elements */
        }
        ...
    }

The class RGB color is defined:

    "RGB color",    // class name
    classRGBColor,  // class ID 'RGBC'
    "", // no description
        {
            "<Inheritance>",            // define inheritance
            keyInherits,                // property ID for inheritance 'Clr '
            classColor,                 // from parent class "color"
            "",                         // no description
            flagsSingleParameter        // single parameter

            "red",                      // red property
            keyRed,                     // property ID for Red 'Rd  '
            typeFloat,                  // value type "float"
            "",                         // no description
            flagsSingleParameter        // single parameter

            "green",                    // green property
            keyGreen,                   // property ID for Green 'Grn '
            typeFloat,                  // value type "float"
            "",                         // no description
            flagsSingleParameter        // single parameter

            "blue",                     // blue property
            keyBlue,                    // property ID for Blue 'Bl  '
            typeFloat,                  // value type "float"
            "",                         // no description
            flagsSingleParameter        // single parameter
        },
        { /* no elements */
        }

Enumerated Types

Enumerated types are used in the standard fashion to create a type that can have one or a set of values. See Dissolve.r in the Dissolve example for an example of creating an enumeration in a PiPL resource.

Note:
For the enumerated value IDs, as tempting as it may be, don’t use simple indexes, use four-character types.

For example, an enumerated type for quality with the values of low, medium, high, and maximum is defined:

    typeQuality,    // type ID for Quality 'Qlty'
    {
        "low",      // "low" value
        enumLow,    
        "", 

        "medium",   // "medium" value
        enumMedium, 
        "", 

        "high",     // "high" value
        enumHigh,   
        "", 

        "maximum",  // "maximum" value
        enumMaximum,    
        "", 
    }

Variant types

Enumerated types are also used to specify variant types for parameters and properties.

Note:
The first character of a variant type ID must be "#".

If you have a parameter that can take text or an integer, it is defined:

    "specifier",        // parameter name
    keySpecifier,       // parameter ID
    typeTextInteger,    // text or integer
    "index or name",    // short description
    flagsEnumeratedParameter

Where typeTextInteger is defined as "#tin".

In the PiPL resource file, the type typeTextInteger is defined an enumeration:

typeTextInteger,        // type ID (variant types must begin with "#")
    {
        "string",       // name of first type (AppleScript name)
        typeText,   
        "", 

        "integer",      // name of second type
        typeInteger,    
        ""  
    }

Enumerations and object reference types Enumeration variants can also be used to specify object reference types and class types. From the example of the class color, typeClassColor is defined:

    typeClassColor,     // type ID (variant types must begin with "#")
    {
        "type color",   // name of type
        typeClass,      // generic type reference
        "", 
    }

Lists and the Terminology Resource

All types can be used as lists for parameters and properties. All items in a list must be of the same type. To specify a list in the terminology resource use the flagsListParameter or flagsListProperty.

Descriptors

Because the Actions palette provides an alternate, text-based user interface to Adobe Photoshop, textual script commands need to map intuitively to the graphical user interface. The way to start developing a scripting interface for your plug-in is to look carefully at the options provided in your dialogs, and then describe them in writing.

For some options, such as checkboxes and popup menus, this is fairly straight-forward. For others, such as showing placement of an object graphically, this is more difficult.

All scripting commands are described with the following form within the Actions palette. This form provides a descriptor for the event.

event [target] [<key> <value>]
Scripting command syntax
Name Description
event Command being executed.
target Item being acted upon.
key Parameter key.
value Parameter value type. See Basic Value Types

Basic value types
Name Code Description
typeInteger 'long' int32
typeFloat 'doub' IEEE 64 bit double
typeBoolean 'bool' TRUE or FALSE.
typeText 'TEXT' Block of any number of readable characters.
typeAlias 'alis' Macintosh file system path.
typePaths 'Pth ' Windows file system path.
typePlatformFilePath 'alis' or 'Pth ' typeAlias for Macintosh, typePath for Windows.
Special value types
Name Code Description
typeEnumeration Enumeration declared in the terminology resource.
typeClass 'char' Used in terminology resource for class type specifier.
typeObjectReference 'indx' Refers to Photoshop object, such as channel or layer. See Type typeObjectReference.
classClass Enumerated class. See Predefined Classes.

Filter, Selection, and Color Picker Events

Filter, Selection, and Color Picker scripting is described as "scripting events". These events have a descriptor syntax in the Actions palette with the following form:

filter [target] [<key> <value>]

Such as:

gaussian blur layer 1 radius 5
Scripting event command syntax
Name Description (with example)
filter Menu name of the filter plug-in, or similar. ("gaussian blur")
target Portion of the document to apply filter. ("layer 1")
key Parameter key. ("radius")
value Parameter value. ("5")

Import, Export, and Format Objects

Import, Export, and Format scripting is described as "scripting objects". These objects have a descriptor syntax in the Actions palette with the following form:

command [target] [as type | object | string] [in file | folder]

Such as:

save document "bright banana" as "Shareware"
save document 1 as Photoshop EPS
save as Photoshop EPS { preview 8 bit TIFF }
Scripting object command syntax
Name Description (with example)
command Operation: "save" or "open". ("save")
target Document. ("document 1")
as type = type of class ("TIFF")
object = object of class ("Photoshop EPS")
string = name of non-scriptable format ("Shareware")
in Location to save/load the file.

In the example "save as Photoshop EPS { preview 8 bit TIFF }" the target document is saved with the "Photoshop EPS" format with the parameter: key "preview", value "8 bit TIFF".

Note:
This form is a little different then AppleScript. In AppleScript, the "save as Photoshop EPS" example would appear as: save as {format: Photoshop EPS, preview: 8 bit TIFF}

In Photoshop the object’s class is implied by the object passed since the scripting mechanism has a stronger type system than AppleScript.

Save as object

To save as an object, the nomenclature is save as classFormat, where classFormat, is generally a class type with parameters, such as JPEG, PDF, or EPS:

save as { class: JPEG, quality: 3 }
save as JPEG with properties { quality: 5 }

Save as type

Saving as a type takes the form of save as typeClassFormat, which is always a specific type, with no parameters:

save as JPEG
save as EPS

Type typeObjectReference

The type typeObjectReference is used to refer to an external object, such as a channel or layer. Plug-ins cannot access these objects directly but can use object references to refer to elements that are accessible through other means.

When using the Action Descriptor Suite, the read and write functions GetReference and PutReference use the structure PIActionReference for keys that have type typeObjectReference. The PIActionReference structure itself is read and written using the Action Reference Suite. When using the Deprecated Standard Descriptor Suite, the read and write functions GetSimpleReferenceProc and PutSimpleReferenceProc use the structure PIDescriptorSimpleReference for keys that have type typeObjectReference.

An object reference can refer to an object which is either an element or a property of another object. Elements may be referred to by name or index. Plug-ins can only refer to elements or properties of the immediate target, due to the one-dimensional nature of PIDescriptorSimpleReference. For example, your plug-in may specify:

channel 1

But it cannot specify:

channel 1 of layer 2

If a plug-in attempts to read a complex object reference (for instance, one containing other references) the host attempts to simplify the reference; if it can’t, it returns an error.

Scripting Parameters

Once you’ve added a terminology resource and you’ve edited the HasTerminology PiPL property (see the Cross Application Plug-in Development Resource Guide), your plug-in is considered scripting-aware.

At every selector call, the host passes the plug-in a descriptor structure through the Descriptor Suite portion of the parameter block: PIDescriptorParameters, pointed to by the field descriptorParameters. This structure contains fields that provide flags for playback and recording, as well as the descriptor field, a set of key/value pairs that provide the plug-in with the key/value portion of the descriptor information displayed on the Actions Palette.

The plug-in can access the data structure in the descriptor field directly using the Deprecated Standard Descriptor Suite, or can convert the PIDescriptorHandle structure to a PIActionDescriptor structure by using the PSActionDescriptorProcs::HandleToDescriptor and PSActionDescriptorProcs::AsHandle functions. Once converted to a PIActionDescriptor, the plug-in can use the Action Descriptor Suite to manipulate the descriptor.

See the Hidden sample for an example of converting the PIDescriptorHandle structure to a PIActionDescriptor, and using the Action Descriptor Suite to access the descriptor.

Recording

Building a descriptor

If your plug-in has no options, PIDescriptorParameters::descriptor may be set to NULL.

To build a descriptor using the Action Descriptor Suite:

  1. Call PSActionDescriptorProcs::Make to create a new PIActionDescriptor token, such as actionDescriptor.
  2. Call various PSActionDescriptorProcs Put routines such as PutInteger, PutFloat, etc., to add key/value pairs to actionDescriptor. The keys and value types must correspond to those in your terminology resource.
  3. Check if the PIDescriptorParameters::descriptor structure is NULL. Each plug-in has a pointer to PIDescriptorParameters in its parameter block through the field descriptorParameters. If it is not NULL, dispose of it using the Dispose function in the Handle Suite Callbacks.
  4. Call PSActionDescriptorProcs::AsHandle with the actionDescriptor token, which converts the PIActionDescriptor structure into a PIDescriptorHandle.
  5. Place the PIDescriptorHandle into the PIDescriptorParameters::descriptor field. The host disposes of it when finished.
  6. Free the actionDescriptor using PSActionDescriptorProcs::Free.
  7. Store your recording information into PIDescriptorParameters::recordInfo. See Dialog Record Options.

The following example of building a descriptor using the Action Descriptor Suite is from the WriteScriptParamters function in the Hidden sample code.

    PIActionDescriptor actionDescriptor = NULL;
    
    // Create the action descriptor
    HERROR(sPSActionDescriptor->Make(&actionDescriptor));

    // Put the descriptor key/value pairs into the descriptor
    HERROR(sPSActionDescriptor->PutEnumerated(actionDescriptor, 
                                         keyResult, 
                                         typeResult, 
                                         enumResult));

    // See if we need to clear the handle of an existing descriptor
    if (filterRecord->descriptorParameters != NULL && 
        filterRecord->descriptorParameters->descriptor != NULL &&
        filterRecord->handleProcs != NULL)
    {
        HandleProcs * handleProcs = filterRecord->handleProcs;
        handleProcs->disposeProc(filterRecord->descriptorParameters->descriptor);
        filterRecord->descriptorParameters->descriptor = NULL;
    }

    // Convert the Action Descriptor to a PIDescriptorHandle, and return
    // it in the descriptor parameters structure.
    HERROR(sPSActionDescriptor->AsHandle(actionDescriptor, 
                        &filterRecord->descriptorParameters->descriptor));

    // Free the action descriptor
    if (actionDescriptor != NULL) sPSActionDescriptor->Free(actionDescriptor);

To build a descriptor using the Deprecated Standard Descriptor Suite:

  1. Call WriteDescriptorProcs::openWriteDescriptorProc which returns a PIWriteDescriptor token, such as writeToken.
  2. Call various WriteDescriptorProcs Put routines such as PutIntegerProc, PutFloatProc, etc., to add key/value pairs to writeToken. The keys and value types must correspond to those in your terminology resource.
  3. Call CloseWriteDescriptorProc with writeToken, which creates a PIDescriptorHandle.
  4. Place the PIDescriptorHandle into the PIDescriptorParameters::descriptor field. The host disposes of it when finished.
  5. Store your recording information into PIDescriptorParameters::recordInfo. See Dialog Record Options.

Recording Error Handling

If an error occurs during or after PIWriteDescriptor, then writeToken and the new PIDescriptorHandle should be disposed of using DisposePIHandleProc from the Handle Suite Callbacks.

Recording Classes

Plug-ins can declare classes to use as templates for structures. Classes declared by plug-ins cannot contain elements, but can use inheritance. Objects of a particular class are created by defining a descriptor and adding the key/value pairs for the properties. The root property of the base class is not added to the descriptor.

Playback

Reading a descriptor
If a plug-in has no options, or is not scripting-aware, PIDescriptorParameters::descriptor will be NULL.

To read a descriptor using the Action Descriptor Suite:

  1. Get the PIDescriptorHandle from PIDescriptorParameters::descriptor. Each plug-in has a pointer to PIDescriptorParameters in its parameter block through the field descriptorParameters.
  2. Convert the PIDescriptorHandle into a PIActionDescriptor (actionDescriptor) by calling PSActionDescriptorProcs::HandleToDescriptor.
  3. Make paired calls to PSActionDescriptorProcs::HasKey followed by the appropriate Get routine, to determine whether the key exists in the actionDescriptor, and if so, to return the data.
  4. Free the actionDescriptor by calling PSActionDescriptorProcs::Free.
  5. Update your parameters and show your dialog, depending on PIDescriptorParameters::playInfo. See Play Dialog Options.

The following example of reading a descriptor using the Action Descriptor Suite is from the ReadScriptParamters function in the Propetizer sample code.

        // Get the descriptor handle from the parameter block
        PIDescriptorHandle descHandle = NULL;
        descHandle = filterRecord->descriptorParameters->descriptor;
        
        popDialog = !(filterRecord->descriptorParameters->playInfo == plugInDialogSilent);

        // Convert the descriptor handle into an Action Descriptor
        PIActionDescriptor actionDescriptor = NULL;
        sPSActionDescriptor->HandleToDescriptor(descHandle, &actionDescriptor);

        Boolean hasKey;
        uint32 length;
    
        // Call HasKey for each key we expect, followed by the appropriate Get routine.
        error = sPSActionDescriptor->HasKey(actionDescriptor, keyMyNudgeH, &hasKey);
        if (!error && hasKey)
            sPSActionDescriptor->GetFloat(actionDescriptor, keyMyNudgeH, &fBigNudgeH);

        // ...

        error = sPSActionDescriptor->HasKey(actionDescriptor, keyMyWatch, &hasKey);
        if (!error && hasKey)
            sPSActionDescriptor->GetInteger(actionDescriptor, keyMyWatch, &fWatchSuspension);

        // Free the action descriptor
        if (actionDescriptor != NULL) sPSActionDescriptor->Free(actionDescriptor);
    }

To read a descriptor using the Deprecated Standard Descriptor Suite:

  1. Call ReadDescriptorProcs::openReadDescriptorProc with two parameters: the PIDescriptorHandle in descriptor, and a NULL-terminated array of expected key IDs. It returns a PIReadDescriptor token, such as readToken.
    Note:
    descriptorKeyIDArray must be NULL-terminated, or the automatic array processor starts to read and write past the array, writing over other data and likely crashing the host.
  2. Make repeated calls to ReadDescriptorProcs::GetKeyProc, which returns information about the current key in the readToken. GetKeyProc returns FALSE when there are no more keys.
  3. Make the appropriate call to the Get routine responding to the key and type, such as GetIntegerProc, GetFloatProc, etc.
  4. Call ReadDescriptorProcs::closeReadDescriptorProc with readToken, which disposes of readToken and returns any errors that occurred during GetKeyProc. (See Sticky Errors.)
  5. Dispose of the PIDescriptorHandle pointed to in descriptor by calling DisposePIHandleProc from the Handle Suite Callbacks. You may keep the descriptor for use, such as a parameter handle, but the descriptor field should still be set to NULL.
  6. Set the descriptor field to NULL.
  7. Check for and manage any errors (see Playback error handling.)
  8. Update your parameters and show your dialog, depending on playInfo. See Play Dialog Options.

Playback error handling

Because a descriptor can be built by other software, don’t assume that your keys will come in order, be of the proper type, or all be present. This is only relevant for plug-ins using the Deprecated Standard Descriptor Suite, in which keys are retrieved in order from the descriptor using the GetKey function. For the Action Descriptor Suite, keys are retrieved in any order by simply requesting them by name, once you have verified the key exists in the descriptor by using the HasKey function.

Coerced Parameters

If a Get call is made for the wrong type, paramErr will be returned unless the type could be coerced, in which case the value will be returned with the coercedParam error.

If an error occurs and not plugInDialogSilent, then the plug-in may:

  • Present a dialog and return a positive error value, or
  • Return a negative error value and the host displays a standard message.

Sticky Errors

Errors that occur in Get routines and GetKeyProc are sticky, meaning that an error keeps getting returned until another more drastic error supercedes it. This way the plug-in can check for any major errors after it has read all the data.

Playback errors returned
Name Description
NULL No error.
coercedParam Coerced data to requested type.
paramErr Error with parameters passed or data
does not match requesting proc.
errWrongPlatformFilePath Mismatch between typeAlias (Macintosh) and
typePath (Windows) request.

DescriptorKeyIDArray

When using the Deprecated Standard Descriptor Suite, during the repeated calls to GetKeyProc, DescriptorKeyIDArray, passed to OpenReadDescriptorProc, is updated automatically. As each key is found in GetKeyProc, the corresponding key in descriptorKeyIDArray is set to typeNull='null'. Keys still in the array after you’re done reading all the data indicate keys that were not passed in the descriptor and you will need to coerce them or request them from the user (if not plugInDialogSilent).

AppleScript Compatibility

The Photoshop scripting system was made with AppleScript compatibility as one of its primary goals. This explains the reliance on some of the more (seemingly needless) complex structures such as the dictionary resource. The reliance on the dictionary resource, and the structure of the key name and ID pairs, maps directly to AppleScript. There are some important details to watch out for.

AppleScript maintains a global name space, which means if your plug-in is going to be AppleScript compatible, your key name and ID pairs must be completely unique. For example, if you defined:

    "red",                  // red property
    myRed,                  // my unique property ID for Red
    typeFloat,              // value type "float"
    "",                     // no description
    flagsSingleParameter    // single parameter

    "green",                // green property
    myGreen,                // my unique property ID for Green
    typeFloat,              // value type "float"
    "",                     // no description
    flagsSingleParameter    // single parameter

    "blue",                 // blue property
    myBlue,                 // my unique property ID for Blue
    typeFloat,              // value type "float"
    "",                     // no description
    flagsSingleParameter    // single parameter

You would ruin "red", "green", and "blue" for anyone else who attempted to use it, as it would now map to your unique keys (or whoever got their dictionary registered before yours.) In this case, you must use unique textual names as well, such as:

    "AdobeSDK red",         // unique red property name
    myRed,                  // my unique property ID for Red
    typeFloat,              // value type "float"
    "",                     // no description
    flagsSingleParameter    // single parameter

In that case, future requests would take the form of:

tell "Adobe SDK dissolve"
    set AdobeSDK red of AdobeSDK Dissolve to 65535, 0, 0
end tell

This way is safe and makes sure you don’t conflict with anything else. When in doubt, make the name and ID unique, or use the predefined values. Those are always available and are mapped to your plug-in through your dictionary resource automatically.

Registration and Unique Name Spaces

When trying to determine unique key name and ID spaces, you must follow these rules:

  • All ID’s starting with an uppercase letter are reserved by Adobe.
  • All ID’s that are all uppercase are reserved by Apple.
  • All ID’s that are all lowercase are reserved by Apple.
  • This leaves all ID’s that begin with a lowercase letter and have at least one uppercase letter for you and other plug-in developers.

Since the scripting system is based on unique IDs and AppleScript, and works the same between Mac OS and Windows, this means that if you wish to register a unique ID you must use the Apple filename ID registration web page, whether Windows or Mac OS based. The web page is at http://dev.info.apple.com/cftype/main.html.

Common Adobe Plug-in ID Good Will Format

For all plug-in developers wishing to allocate a block of IDs (which is common to want to do, for sets of plug-ins needing unique variables, etc.) register your plug-in type as a variant, with the first three characters following the basic rules for ID creation, and a last character of "#". This registers all 255 permutations of your ID. For example,

  • 'sdK#' reserves sdKx, where x is any character, allowing keys such as 'sdK*', 'sdK0', 'sdKA', or 'sdKz', and
  • 'gAr#' reserves gArx, where x is any character, allowing keys such as 'gAr$', 'gAr8', 'gArG', or 'gArr'.

Remember, if you’re registering a block of name space, that the first three characters must follow the ID rules: they must start with a lowercase character, and at least one character must be uppercase.

Note:
This registration method is not supported by Apple. As a matter of fact, Apple explicitly states that one cannot reserve more than one ID at a time. If we all follow the same rule, however, it will work just fine until another solution becomes apparent.

When you log onto the registration web page to check your unique ID, you must check for 'nam#' where nam is your three-digit ID. If you check and register any four digit ID, without searching for your three-digit ID + "#", then you will probably stomp someone else's name space.

This name space registration method is only useful if we all agree do follow it.

Ignoring AppleScript

If you’ve decided that forward compatibility with future AppleScript features is not a major concern, you can disable any AppleScript-savvy features and make your plug-in only Photoshop-specific. By doing this, you may ignore any requirements for unique key name and ID pairs. To do this, add a unique ID string to your HasTerminology resource.

For example, the following entry in the PiPL resource file ignores AppleScript:

        HasTerminology
        {
            plugInClassID,                  // Class ID
            plugInEventID,                  // Event ID
            ResourceID,                     // AETE ID
            // Unique string or empty for AppleScript compliant:
            "98b5a608-46ce-11d3-bd6b-0060b0a13dc4"
        }

But the following entry indicates the plug-in is AppleScript compatible:

        HasTerminology
        {
            plugInClassID,                  // Class ID
            plugInEventID,                  // Event ID
            ResourceID,                     // AETE ID
            // Unique string or empty for AppleScript compliant:
            ""
        }

For more information, see Scripting-specific PiPL Properties, and the PITerminology structure.

AppleEvents

In Photoshop 4.0 and 5.0, besides the standard AppleEvents, there is one additional AppleEvent call that is supported by the host: do script.

You can call into Photoshop with the do script command to have it play any currently loaded script in the Actions palette.

tell application "Photoshop 4.0"
    do script "MyAction"
end tell