13.1. Overview

13.1.1. Threading

Keypirinha is a multi-threaded application and plugins methods are called in an asynchronous fashion. While the exposed API is thread-safe, plugin developer should keep in mind that application and plugin state may change while her code is running.

13.1.2. Plugin Lifecycle

13.1.2.1. Loading

When the application starts, it loads the embedded Python interpreter and builds a list of the available packages, omitting the ones that are flagged as disabled in the configuration file.

Once the list of valid packages is built, Keypirinha scans their files and look for *.py scripts. Each one of those scripts is loaded through the Python interpreter and the application performs some introspection to list all the classes derived from keypirinha.Plugin and instantiates them.

The application then calls the keypirinha.Plugin.on_start() method of every loaded plugin so they have the opportunity to do heavy one-time initialization, if any.

For now, this call is immediately followed by a call to keypirinha.Plugin.on_catalog() method. Note that this behavior might be altered in a future version so keypirinha.Plugin.on_catalog() may not always be called at startup time.

13.1.2.2. Reloading

Keypirinha monitors the Live Packages directory to detect any change made to any file of the loaded packages (i.e.: not only the *.py scripts).

If a modification is detected, and unless it concerns only *.ini file(s) (in which case keypirinha.Plugin.on_config_changed() is called), the concerned package will be fully reloaded.

At the beginning of the reloading process, the entire package is first unloaded from the application including its resources and associated items. Also, Python is asked to destroy as much references as possible that link to the concerned Plugins.

It becomes tricky. By design, it is not possible to ensure that any Python object is totally unloaded and that its memory freed. Being it a module or a class instance. Python implements a Garbage Collector to manage its memory allocations and some reference to an object might still exist, after a module has been unloaded, due to inter-dependencies for example. While this works just fine most of the time, race conditions may sometimes occur when, for example, the code of a plugin is running while the application tries to unload it.

To avoid that and to be as flexible as possible, Keypirinha will never force a particular plugin, or more generally a thread, to terminate. Instead, it offers to the Plugins a very cheap API call keypirinha.Plugin.should_terminate(), that allows Plugins to frequently check if they should stop any work and leave the current call.

Warning

It is up to the plugin developer to call the keypirinha.Plugin.should_terminate() method as frequently as possible to check if the current call should be interrupted immediately. Especially at I/O boundaries like file operation, directories scanning, network requests, etc…

To synthesize, it is technically possible that several instances of a same plugin coexist. In that case, Keypirinha will always use and communicate with the last loaded instance so the user wouldn’t even notice. To differentiate several instances of a Plugin, Keypirinha internally gives an incremented ID number to every loaded instance of a Package and its plugins.

13.1.2.3. InstanceID

The InstanceID is an unsigned integer internally created and managed by the application, assigned at runtime to every loaded package and plugin. It is essentially used to ensure plugins are not messing up the catalog.

When a package is loaded for the first time, it receives the InstanceID 1, as well as its plugins. When any change is detected in one or several of its files, the package is reloaded and its InstanceID is incremented.

Note

By extension, every plugin of a package receives the InstanceID of its parent package when it is loaded or reloaded.

13.1.2.4. Messages Workflow

Keypirinha sends messages to its plugins either to notify them about a specific event, or to request them to perform a specific task.

Once a plugin is loaded, its keypirinha.Plugin.on_start() method is called, shortly followed by a call to the keypirinha.Plugin.on_catalog() method (this behavior may change in the future).

When the user starts typing their search and each time search terms have changed, the keypirinha.Plugin.on_suggest() method is called. There is a subtlety here: if no item has been selected yet (i.e. Tab key has not been pressed yet), the message is broadcasted to every loaded package. Otherwise, it is sent only to the parent plugin.

When the user wants to execute the selected item, the keypirinha.Plugin.on_execute() method of the appropriate plugin is called and it is up to it to actually execute the passed item.

These are the main messages that a plugin developer usually wants to implement. Other messages are mainly notifications that helps plugin to be up-to-date with user’s settings and environment.

13.1.3. Getting started

Plugin development should be done in the Live Packages directory.

The best way to get your hands in the dirt is to use the Test package as a new package skeleton, then have a look at the source code of the official packages.

Official packages are all located in the default\Packages\*.keypirinha-package.

In particular:

  • the Apps package shows how to use Keypirinha’s API to scan directories and files.
  • the TaskSwitcher and Winamp packages interact with the Win32 API via the ctypes Python standard module.
  • the ControlPanel and PuTTY packages show how to dig into system’s Registry using the winreg Python standard module.
  • the Calc package imports a third-party bundled Python module.
  • the GoogleTranslate package does online HTTP requests to a web-service.
  • the URL package extract an embedded resource file and parse it.