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
andWinamp
packages interact with the Win32 API via thectypes
Python standard module.the
ControlPanel
andPuTTY
packages show how to dig into system’s Registry using thewinreg
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.