Developing PM123 Plugins

PM123 supports four kinds of plugins: visual, decoder, output and filter. Plugins are Dynamic Linked Libraries (DLLs) which PM123 loads on start up.

Visual plugins are used to peek at the data currently being heard (or not) by the user through the output plugin and visually produce data from it back to the user.

Decoder plugins are used to decode different types of files, tracks or streams the user can play.

Output plugins is the final destination of the decoded data. It can be rerouted to a sound card, to the hard disk or anywhere else appropriate. The data is in standard PCM format.

Filter plugins are chained between the decoder and the output plugin to modify the PCM data before getting to the output plugin.

See also common plugin data types.

Common interface

plugin.h contains the necessary structures for all PM123 plugins. All exported and callback functions must use the calling convention _cdecl.

Before you include any file from the PDK you should define the macro PLUGIN_INTERFACE_LEVEL to the appropriate value matching the interface compatibility of your plugin. Example:

#define PLUGIN_INTERFACE_LEVEL 3
#include <plugin.h>
#include <decoder_plug.h>

Initialization

A plugin must have a function that identifies it as a plugin:

int DLLENTRY plugin_query(PPLUGIN_QUERYPARAM param);
plugin_query is called before any other function and exactly once by PM123. The plugin will then have to fill the variables in the param structure, for example:
param->type = PLUGIN_VISUAL;
/* Identify the plugin as visual plugin. Types can be ORred to
include multiple plugin types in the same DLL. */
param->author = "Matti Meikäläinen";
/* Author of the plugin */
param->desc = "Example plugin";
/* A short description of the plugin */
param->configurable = TRUE;
/* Toggles plugin configurability via PM123 Properties dialog */
param->interface = PLUGIN_INTERFACE_LEVEL;
/* This is the required interface revision level.
* This parameter defaults to 0 which is the same as before this field existed.
* Plugins with Level 0 must not access this field to be compatible with older
* versions of PM123. */
return 0;

A second initialization function, plugin_init, is called by the plugin manager once per plugin after plugin_query. The function is called only from the application's main thread. It should not block for longer nor do I/O operations to keep the GUI responsive.

int DLLENTRY plugin_init(const PLUGIN_CONTEXT* ctx);

The export of this function is optional but level 2 and up plugins most likely will require it. It provides some global entry points of PM123. They might be used to handle asynchronous events or requests. The structure and the functions must not be used after plugin_deninit returned.

/** message function */
void DLLENTRY (*message_display)(MESSAGE_TYPE type, const char* msg);

/** retrieve configuration setting */
int DLLENTRY (*profile_query)(const char* key, void* data, int maxlength);
/** store configuration setting */
int DLLENTRY (*profile_write)(const char* key, const void* buffer, int length);

/** execute remote command
* See the documentation of remote commands for a description. */
const char* DLLENTRY (*exec_command)(const char* cmd);

/** Invalidate object properties
* @param what A bit-vector of INFOTYTE
* @return Bits of what that caused an invalidation. */
int DLLENTRYP(obj_invalidate)(const char* url, int what);
/** Check whether a decoder claims to support this kind of URL and type.
* @param url URL of the object to check
* @param type .type extended attribute or mime type respectively.
* Multiple types may be tab separated.
* @return != 0 -> yes
* @remarks The function does not actually cause any I/O.
* It is not reliable during plugin initialization. */
int DLLENTRYP(obj_supported)(const char* url, const char* type);
/* Allocate dynamic string. Any previous content is discarded first.
* The returned memory can be written up to len bytes until the next
* xstring_* function call on dst. The return value is the same than
* dst->cstr except for constness. */
char* DLLENTRY (*xstring_alloc)(xstring* dst, unsigned int len);
/* Deallocate dynamic string. This will change the pointer to NULL. */
void DLLENTRY (*xstring_free)(volatile xstring* dst);
/* Return the length of a dynamic string */
unsigned DLLENTRY (*xstring_length)(const xstring* src);
/* Compare two xstrings for (binary!) equality. NULL allowed. */
int DLLENTRY (*xstring_equal)(const xstring* src1, const xstring* src2);
/* Reassing dynamic string from C string. Any previous content is discarded first. */
void DLLENTRY (*xstring_assign)(volatile xstring* dst, const char* cstr);
/* Copy dynamic string to another one. Any previous content is discarded first.
* This function will not copy the string itself. It only creates an additional reference to the content. */
void DLLENTRY (*xstring_copy)(volatile xstring* dst, const xstring* src);
/* Strongly thread safe version of xstring_copy. */
void DLLENTRY (*xstring_copy_safe)(volatile xstring* dst, volatile const xstring* src);
/* Append to xstring. The source may also be from a xstring.
* If dst is NULL a new string is created */
void DLLENTRY (*xstring_append)(xstring* dst, const char* cstr);
/* printf into a xstring. Any previous content is discarded first. */
void DLLENTRY (*xstring_sprintf)(volatile xstring* dst, const char* fmt, ...);
void DLLENTRY (*xstring_vsprintf)(volatile xstring* dst, const char* fmt, va_list va);

The exec_command function causes PM123 to execute a remote command as if it were sent to the remote pipe interface. It returns the reply string. The returned storage is valid until the next call to exec_command or until plugin_deinit.  Calls to exec_command must be serialized.
The remote state information like the currently selected playlist is private to the plugin and does not interfere with commands sent to the remote pipe or from another plugin.

query_profile and write_profile are similar to the profile OS/2 API functions PrfQueryProfileData and PrfWriteProfileData. But they read and write to a section dedicated to your plugin the current PM123.INI file which may not be the one in the application folder. Using this functions is recommended over creating an individual profile.
query_profile returns the length of the requested Parameter or -1 on error. Independent of the returned length at most maxlen characters are stored in buffer. If you pass NULL as key, a list of '\0' delimited keys is returned. write_profile returns TRUE on success and FALSE on error.

The xstring_* API functions are used to manipulate dynamic strings of type xstring.

Configuration

If you set param->configurable = TRUE in plugin_query, a configuration dialog should appear when PM123 calls

HWND DLLENTRY plugin_configure(HWND owner, HMODULE module);

where owner is the notebook or player window so that you can "lock" your window on it if you want and where module can be used to load a resource from your DLL. The functions is called only from the application's main thread. It should not block for longer nor do I/O operations to keep the GUI responsive.

The function should return a window handle if and only if the configuration dialog in non-modal, i.e. it is still open when plugin_configure returns. PM123 might then call plugin_configure again with owner = NULLHANDLE to close the dialog. If the configuration dialog is modal you need not to care about that. NULLHANDLE should always be returned.

Note that the return value is new to interface level 3. PM123 uses this value to keep track of open configuration windows. Older PM123 versions ignore the return value.

The export of plugin_ocommand is optional. If the symbol is exported, the plugin can receive remote commands. Note that these commands must not be used to control the primary function of a plugin like to stop decoding a file. It is mainly intended for remote configuration.

void DLLENTRY plugin_command(const char* command, xstring* result, xstring* messages);

The command parameter receives the command passed to the remote interface without the asterisk and plugin name prefix. The handling is plugin specific. result should be assigned with the result of the command. Any valid command should return a value. An empty result should indicate an error. In case of an error at least one appropriate message should be returned in *messages. Each message must fit in a single line terminated with '\n' and it must start with a severity indicator: "E " for errors and "W " for warnings.

Unload

Plugins should deinitialize and destroy their windows and free allocated memory when receiving a

int DLLENTRY plugin_deinit(int unload);

It can also be used to save settings in your INI file for other sort of plugins.

Interface levels

The interface level is used to ensure compatibility of plugins over different versions of PM123. The field defaults to 0 representing the oldest implementation. Larger values reflect changes to the plugin interface. The interface level reflects changes to any of the plugin interfaces, so different levels do not necessarily mean different interfaces of one plugin interface. A change in the interface may be only a modified semantic of a function call or it may be a complete change of the interface with entirely other function names or whatever. See the individual PDK documentation of the desired plugin type to get further information for each plugin type.
A new plugin is not necessarily required to use the most recent interface level.

Overview:
Level PM123 version Visual Decoder Output Filter
0 n/a 1 no longer supported! supported supported, deprecated supported, deprecated
1 ≥ 1.32 1 no longer supported! supported,
same as level 0
supported but deprecated,
same as level 0
supported, deprecated,
same as level 0
2 = 1.40 2 no longer supported!
same as level 1
no longer supported! no longer supported! no longer supported!
3 = 1.41 recent recent recent recent

  1. PM123 before version 1.40 does not check for the compatibility of plugins. When a wrong plugin is used, the application will most likely crash.
  2. The plugin interface of PM123 1.41 never got really stable and therefore is discontinued.

For example a filter plugin will be loaded depending on it's interface level by a PM123 instance with interface level 3 (compile time constant) in the following way:
Interface level
of the plugin
Action taken
3 Loaded native.
2 Error, because level 2 is discontinued.
1 Loaded via a proxy in compatibility mode.
0 Loaded via a proxy in compatibility mode.
Level 0 is identical to level 1 with respect to the filter plugin interface.
>3 Error, because the interface is potentially incompatible and not supported by this PM123 core.