# InstallerPluginSample

- `Download example`

# Install plugin sample

## 1. What is an Installer Plugin?

An Installer Plugin extends the user experience of the Installation process by allowing developers to insert additional custom steps in the installation process of their software packages. In the sample code provided here, a registration pane is built that asks users for registration information before installing.

Note that informed end-users can remove your package’s Plugins directory and/or change your package’s InstallerSections.plist file to remove custom plugins from its flow. So plugins cannot serve as effective gatekeepers to prevent installation.

## 2. Using the Sample Installer Plugin

1. Take a pre-existing package (or create a new one with PackageMaker.)

   The sample package `InstallerPluginsTestPackage.pkg` is provided for your test use.

2. Create a `Plugins` directory in that Package bundle: `InstallerPluginsTestPackage.pkg/Contents/Plugins`

3. Run the `setup.py` in `Registration`

4. Copy the created bundle created into the Plugins directory of your package.

5. Copy the `InstallerSections.plist` file in this directory into the Plugins directory of your package. The `InstallerSections.plist` defines the order that the Installer section pane will be presented to the user, and where the Registration pane will appear (following the `License` pane.)

6. Open the package

   It will run with the additional custom Registration plugin (and will ask if it is al right to run additional code before opening).

## 3. Creating Your Own Custom Installer Plugin

### Getting started

- Use the provided plugin as your starting point for development.

- Custom Installer Plugins must be written in Cocoa. They may not be written in Carbon or Java, but can be written in Python (obviously).

- Review the InstallerPlugins.framework APIs.

- Copy and update the provided `InstallerSections.plist` file to include your plugin(s), and place them in the appropriate location in the Install sequence.

- Contact the Mac OS X Installation Technology Engineering team with questions by subscribing to the installer-dev mailing list (email contact information is provided in the ‘Notes’ section.)

### Code Flow in the Plugin

- The plugin’s entry point to is the method `didEnterPane_(pane)`.

- When the user clicks the “Continue” button, method `shouldExitPane_(dir)` is called. Return False from this method to prevent the user from leaving your plugin page.

## 4. Notes

Plugins are only supported in Mac OS X Tiger (v10.4) and later.

## Sources

### setup.py

    """
    This is dummy file, the real setup.py
    is in ``Registration``
    """

### Registration/RegistrationPane.py

    import Cocoa
    import InstallerPlugins
    import objc

    # Important: be aware that informed end-users can remove your package's
    # Plugins directory and/or change your package's InstallerSections.plist file
    # to remove custom plugins from its flow.
    # So plugins cannot serve as effective gatekeepers to prevent installation.

    class RegistrationPane(InstallerPlugins.InstallerPane):
        uiFirstNameField = objc.IBOutlet()
        uiLastNameField = objc.IBOutlet()
        uiOrganizationField = objc.IBOutlet()
        uiSerialNumberField = objc.IBOutlet()

        def _entriesAreValid(self):
            """
            test all textfields to if they all have at least one character in them
            """
            if (
                len(self.uiFirstNameField.stringValue()) == 0
                or len(self.uiLastNameField.stringValue()) == 0
                or len(self.uiOrganizationField.stringValue()) == 0
                or len(self.uiSerialNumberField.stringValue()) == 0
            ):
                return False

            return True

        def _serialNumberIsValid(self):
            """
            perform a simple string compare to validate the serial number
            entered by the user
            """
            return self.uiSerialNumberField.stringValue() == "123-456-789"

        def _updateNextButtonState(self):
            """
            enable the 'Continue' button if '_entriesAreValid' returns 'True'
            """
            self.setNextEnabled_(self._entriesAreValid())

        def _localizedStringForKey_(self, key):
            """
            localization helper method:  This pulls localized strings from the
            plugin's bundle
            """
            return Cocoa.NSBundle.bundleForClass_(
                type(self)
            ).localizedStringForKey_value_table_(key, "", None)

        def title(self):
            """
            return the title of this pane
            """
            return self._localizedStringForKey_("Title")

        def didEnterPane_(self, direction):
            """
            pane's entry point: code called when user enters this pane
            """
            Cocoa.NSLog("DIDENTER")
            self._updateNextButtonState()

        def shouldExitPane_(self, direction):
            """
            called when user clicks "Continue" -- return value indicates
            if application should exit pane
            """
            if (
                direction == InstallerPlugins.InstallerDirectionForward
                and not self._serialNumberIsValid()
            ):
                self._updateNextButtonState()

                Cocoa.NSBeginInformationalAlertSheet(
                    None,
                    self._localizedStringForKey_("OK_BUTTON"),
                    None,
                    None,
                    self.uiFirstNameField.window(),
                    None,
                    None,
                    None,
                    0,
                    self._localizedStringForKey_("InvalidSerialNumberAlertMessage"),
                )
                return False

            return True

        def controlTextDidChange_(self, notification):
            """
            updates the state of the next button when the contents of the
            delegate textfields change
            """
            self._updateNextButtonState()

### Registration/setup.py

    """
    Script for building the example.

    Usage:
        python3 setup.py py2app
    """

    from setuptools import setup

    plist = {
        "InstallerSectionTitle": "PyRegistration",
        "NSMainNibFile": "Registration",
        "NSPrincipalClass": "InstallerSection",
        "CFBundleIdentifier": "com.MyCompany.Installer.example.Registration",
    }

    setup(
        name="Registration",
        plugin=["RegistrationPane.py"],
        data_files=["English.lproj"],
        options={"py2app": {"extension": ".bundle", "plist": plist}},
        setup_requires=[
            "py2app",
            "pyobjc-framework-Cocoa",
            "pyobjc-framework-InstallerPlugins",
        ],
    )