[% pageicon = 'help' %] [% pagetitle = 'SlimServer Plugins' %] [% techinfo = '1' %] [% PROCESS helpheader.html %]

#### Introduction

This document illustrates the basic framework upon which to build a Plugin or module compatible with the SlimServer software. The SlimServer provides a method to load custom modules at startup which are then made available to the user via remote control menus. Each plugin serves as an area within SlimServer remote menu system, available from within the Plugins menu. Plugin files are stored in the "Plugins" folder which is in the same folder as the SlimServer software. The Plugin interface can provide a very powerful control and has complete access to the functionality of the SlimServer. As with the rest of the SlimServer, plugin modules are created using the Perl language.

#### Perl Module Basics

Here are a couple of basic calls that should be made at the beginning of the Plugin code:

    use strict;

This tells Perl to do some useful error checking to help avoid the use of undeclared variables, or ambiguous references to functions. All references must be fully qualified (ie Slim::Buttons::Common::popMode() instead of popMode()).

    package Plugins::pluginname;

Where `pluginname` is the name of the module (which should be the same as the filename, without the .pm extension.) This defines the 'namespace' for the module. It essentially sets up the module as an entity of its own, within the server environment. This allows you to then refer to the functions and variables within this plugin by the full reference Plugins::pluginname::function.

#### SlimServer Plugin Hooks

Each level in the SlimServer menu hierarchy is considered a "mode" by the SlimServer. Each time the server is started, it reads the Plugins directory for modules. Each is added, by name, into a list. Each plugin is given a mode, and a set of functions that are defined within that module. The plugins can each be enabled or disabled at will from the web interface. The plugin module itself must define a mode and functions in a form that the server is expecting. This comes from two functions: sub setMode() and sub getFunctions(). A third subroutine, sub getDisplayName() is required to provide the name that the server should display for the plugin. Simple examples of these three functions are below. We will now build a template based on these main functions.

> Example 1

>       sub setMode() {
>          my $client = shift;
>          $client->lines(\&lines);
>       }

>       sub getFunctions() {
>          return \%functions;
>       }

>       sub getDisplayName() { return "My Plugin Name")}

#### Modes

The remote buttons are context sensitive, meaning that they can serve different roles when the SlimServer is in different modes. The setMode() subroutine defines this mode. Within this subroutine, there must be a definition for $client->lines, which is the text to be displayed on the Slim client while in this plugin mode. Typically, this is labeled just as above, though it can be any name you wish. The lines subroutine, or other name if you have chosen one, returns two strings. One for each line of the display. They can be updated at any time from any point in the plugin module by using the line:

>     $client->update();

Also included in the setMode subroutine are any commands or functions that must be run each time the plugin is called. This may be loading an array for a menu system, default settings, or running any number of other subroutines that are needed for the operation of the plugin.

You may also want to add extra modes under your plugin. There is a function call that tells the server to add your new mode with its setMode and getFunctions references.

>     Slim::Buttons::Common::addMode('newmodename', getNewModeFunctions(), \&::PluginName::setNewMode);

and then you can include the following in your plugin to hook-in to your new mode:

>     sub setNewMode() {
>        my $client = shift;
>        $client->lines(\&newModeLines);
>     }

>     sub getNewModeFunctions() {
>        return \%newModeFunctions;
>     }

#### Functions

The SlimServer handles all commands from the remote control for each mode by creating a hash table of functions, each button having a subroutine associated with it. The subroutine getFunctions() returns a reference to this hash, and can be any label you wish, although typically %functions is used. The call to point to this list is shown above in example 1. The function list, should look something like this example taken from rescan.pm, which is included with the SlimServer:

> Example 2

>        my %functions = (
>          'up' => sub  {
>             my $client = shift;
>             $client->bumpUp();
>          },
>          'down' => sub  {
>             my $client = shift;
>             $client->bumpDown();
>          },
>          'left' => sub  {
>             my $client = shift;
>             Slim::Buttons::Common::popModeRight($client);
>          },
>          'right' => sub  {
>             my $client = shift;
>             $client->bumpRight();
>          },
>          'play' => sub {
>             my $client = shift;
>             my @pargs=('rescan');
>             my ($line1, $line2) = (string('PLUGIN_RESCAN_MUSIC_LIBRARY'), string('PLUGIN_RESCAN_RESCANNING'));
>             Slim::Control::Command::execute($client, \@pargs, undef, undef);
>             $client->showBriefly(, $line1, $line2);
>          }
>       );

Each remote button (eg. 'play') points to a subroutine to be performed each time that button is pressed. In the case above, pressing play sets up local variables, starts a rescan of the entire library, then shows two lines on the display for a short time to tell the user that the rescan has been started. The line "my $client = shift;" is very important here to keep track of the player status, and to pass on in server function calls such as:

>     $client->showBriefly(, $line1, $line2);

Examples of remote control functions include: 'up','down','play,'add' (REC button),'left','right','numberScroll' and 'stop' The full button to function map is found in the Default.map file, which is in the IR directory under the SlimServer directory.

#### Lines

The lines subroutine returns the text that the Slim Client will display while using your plugin. The setMode() function creates the reference for the lines subroutine, and your lines subroutine name must match that. Each mode may have its own set of lines, and each is named after the reference created in each setMode. The input for this function is the current client information, and the return is two strings for main display and optionally a pair of strings which may need to be overlayed on top of the first pair, right justified. The lines are sent to the display at any time using the command:

>     $client->update();

The simple line subroutine, below, is taken from Rescan.pm.

> Example 3

>     sub lines {
>        my ($line1, $line2);
>        $line1 = string('PLUGIN_RESCAN_MUSIC_LIBRARY');
>        $line2 = string('PLUGIN_RESCAN_PRESS_PLAY');
>        return ($line1, $line2);
>     };

#### Settings

SlimServer Plugins have the option to include settings to be accessed from the web interface through the plugins settings page. To do this, include a function called "setupGroup" which returns a hash reference to the Group parameters, and another to the preference parameters. These two hash may contain any or all of the following keys:

##### Group hash ref keys

PrefOrder
  list of prefs to appear in group, in order
PrefsInTable
  set if prefs should appear in a table
GroupHead
  Name of the group, usually set to string('SETUP_GROUP+$groupname'), no default
GroupDesc
  Description of the group, usually set to string('SETUP_GROUP_$groupname_DESC'), no default
GroupLine
  set if an <hr> should appear after the group
GroupSub
  set if a submit button should appear after the group
Suppress_PrefHead
  set to prevent the heading of preferences in the group from showing
Suppress_PrefDesc
  set to prevent the descriptions of preferences in the group from showing
Suppress_PrefLine
  set to prevent <hr>'s from appearing after preferences in the group
Suppress_PrefSub
  set to prevent submit buttons from appearing after preferences in the group

##### Prefs hash's' keys

onChange
  sub ref taking $client, $changeref, $paramref, $pageref, $key, $ind as parameters, fired when a preference changes
isArray
  exists if setting is an array type preference
arrayAddExtra
  number of extra null entries to append to end of array
arrayDeleteNull
  indicates whether to delete undef and '' from array
arrayDeleteValue
  value signifying a null entry in the array
arrayCurrentPref
  name of preference denoting current of array
arrayBasicValue
  value to add to array if all entries are removed
arrayMax
  largest index in array (only needed for items which are not actually prefs)
validate
  reference to validation function (will be passed value to validate) (default validateAcceptAll)
validateArgs
  array of arguments to be passed to validation function (in addition to value) (no default)
options
  hash of value => text pairs to be used in building a list (no default)
optionSort
  controls sort order of the options, one of K (key), KR (key reversed), V (value) VR (value reversed) - (default K)
dontSet
  flag to suppress actually changing the preference
currentValue
  sub ref taking $client,$key,$ind as parameters, returns current value of preference. Only needed for preferences which don't use Slim::Utils::Prefs
noWarning
  flag to suppress change information
externalValue
  sub ref taking $client,$value, $key as parameters, used to map an internal value to an external one
PrefName
  friendly name of the preference (defaults to string('SETUP_$prefname')
PrefDesc
  long description of the preference (defaults to string('SETUP_$prefname_DESC')
PrefChoose
  label to use for input of the preference (defaults to string('SETUP_$prefname_CHOOSE')
PrefSize
  size to use for text box of input (choices are 'small','medium', and 'large', default 'small'). Actual size to use is determined by the setup_input_txt.html template (in EN skin values are 10,20,40)
ChangeButton
  Text to display on the submit button within the input for this preference. Defaults to string('CHANGE')
inputTemplate
  template to use for the input of the preference (defaults to setup_input_sel.html for preferences with 'options', setup_input_txt.html otherwise)
changeIntro
  template for the change introductory text (defaults to 'string('SETUP_NEW_VALUE') string('SETUP_prefname'):') for array prefs the default is 'string('SETUP_NEW_VALUE') string('SETUP_prefname') %s:' sprintf'd with array index
changeMsg
  template for change value (defaults to %s), this will be sprintf'd to stick in the value
changeAddlText
  template for any additional text to display after a change (default '')
rejectIntro
  template for the rejection introductory text (defaults to 'string('SETUP_NEW_VALUE') string('SETUP_prefname') string('SETUP_REJECTED'):')(sprintf'd with array index for array settings) for array prefs the default is 'string('SETUP_NEW_VALUE') string('SETUP_prefname') %s string('SETUP_REJECTED'):' sprintf'd with array index
rejectMsg
  template for rejected value message (defaults to 'string('SETUP_BAD_VALUE')%s'), this will be sprintf'd to stick in the value
rejectAddlText
  template for any additional text to display after a rejection (default '')

The default values are used for keys which do not exist() for a particular preference for most preferences the only values to set will be 'validate', 'validateArgs', and 'options'

##### Strings

Settings use strings extensively, for many values it defaults to a certain combination of the preference name with other characters. For this reason it is important to follow the naming convention when adding strings for preferences in your plugins. In the following $groupname is replaced by the actual key used in the Groups hash and $prefname by the key from the Prefs hash.

SETUP_GROUP_$groupname
  the name of the group, such as would be used in a menu to select the group or in the heading of the group in the web page. Should be < 40 chars
SETUP_GROUP_$groupname_DESC
  the long description of the group, used in the web page, so length unimportant
SETUP_$prefname
  the friendly name of the preference, such as would be used in a menu to select the preference or in the heading of the preference in the web page. Should be < 40 chars. Also used when the preference changes and no change intro message was specified.
SETUP_$prefname_DESC
  the long description of the preference, used in the web page, so length unimportant
SETUP_$prefname_CHOOSE
  the label used for presentation of the input for the preference
SETUP_$prefname_OK
  the change intro message to use when the preference changes

> Example 4

>     sub setupGroup {
>        my %setupGroup = (
>           PrefOrder =>
>           ['plugin-pluginName-pref-one','plugin-pluginName-pref-two']
>           ,GroupHead => string('SETUP_GROUP_PLUGIN_PLUGINNAME')
>           ,GroupDesc => string('SETUP_GROUP_PLUGIN_PLUGINNAME_DESC')
>           ,GroupLine => 1
>           ,GroupSub => 1
>           ,Suppress_PrefSub => 1
>           ,Suppress_PrefLine => 1
>        );
>        my %setupPrefs = (
>           'plugin-pluginName-pref-one' => {
>              'validate' => \&Slim::Web::Setup::validateInt
>              ,'validateArgs' => [1,undef,1]
>           }
>           'plugin-pluginName-pref-two' => {
>              'validate' => \&Slim::Web::Setup::validateTrueFalse
>              ,'options' => {
>                 '0' => string('SETUP_PLUGIN-PLUGINNAME_PREFTWO_NO)
>                 ,'1' => string('SETUP_PLUGIN-PLUGINNAME_PREFTWO_YES)
>              }
>           },
>        );
>        return (\%setupGroup,\%setupPrefs);
>     }

#### Strings

The plugin API also allows you to add in your own localization. The format of the strings list follows the same format as the strings.txt file used by the SlimServer for localization. The function strings() can be used within the plugin to extract a strings for the user's specified language. Defining the strings is done as follows:

> Example 5

>     sub strings() { return '
>     PLUGIN_STRING_ONE
>     <tab> EN <tab> English version of line one
>     <tab> FR <tab> Version française de la ligne une
>     '};

and to use your strings in your module, you make the call to strings in any place where you would use a text string. For example, referring to the getDisplayName() call from Example 1:

>     sub getDisplayName() { return "My Plugin Name" }

can be changed to

>     sub getDisplayName() { return strings('PLUGIN_NAME') }

where your strings function contains:

>     sub strings() { return '
>     PLUGIN_NAME
>     <tab> EN <tab> My Plugin Name
>     '};

One special note, the format of the strings list is very strict. The whitespace must be a tab, not spaces, which is why the tabs are shown above.

#### Web Interface

SlimServer Plugins can expose their own web interface, accessible through the main server's web pages. To do this, include a function called "webPages" which returns a list of two values: a hash reference to handler functions for the plugin's pages, and a string representing the path of the plugin's index page.

The handler function hash contains keys that represent regular expression values. These regular expressions match URL paths that will be handled by the corresponding page handler. For example, the regular expression "index\.(?:htm|xml)" will match the URL paths "index.html", "index.htm" or "index.xml".

The index page string represents the URL path for a link that will be included in the "Plugins" section of the SlimServer index page. When exposed through the SlimServer's web interface, all plugin web pages are prepended with the URL path http://<host>/plugins/<pluginname>/. However, neither the regular expression keys nor the index URL path need to take this prefix into account.

> Example 6

>     sub webPages {
>         my %pages = (
>             "index\.(?:htm|xml)" => \&handleIndex,
>     	"enable\.(?:htm|xml)" => \&handleEnable,
>         );

>         return (\%pages, "index.html");
>     }

Plugins also have the option of adding links to other sections of the SlimServer index page. Specifically, links can be added to the "browse", "search", "settings", "plugins" and "help" sections. Note that not all skins may support every section. Links can be added using the Slim::Web::Pages::addLinks() method. The method takes two parameters: the category to which links should be added, and a reference to a hash that maps link text to a URL path. In this case, the URL path must include the "plugins/<pluginname>/" prefix.

>     Slim::Web::Pages::addLinks("search", {
>         string('PLUGIN_MYPLUGIN_SEARCH_LINK') => "plugins/MyPlugin/search.html",
>     );

#### ScreenSavers

You can turn any plugin into a custom screensaver by registering your plugin as a screensaver. The plugin should be used to give the user any settings options or to set the screensaver as active or reset to default. You can register the screensaver using the following subroutine:

    sub screenSaver() {
    	Slim::Buttons::Common::addSaver('SCREENSAVER.saverModeName',
    		getScreensaverFunctions(),
    		\&setScreensaverMode,
    		\&leaveScreensaverMode,
    		string('PLUGIN_SCREENSAVER_NAME'));
    }

SCREENSAVER.saverModeName
  Change this to provide a unique name for your screensaver. Using the prefix "SCREENSAVER" will indicate to the server that your mode is of the screensaver class, and match the IR mappings from the [screensaver] section of Default.map
getScreensaverFunctions()
  This subroutine, just like getFunctions points to the function has for the screensaver mode.
setScreensaverMode
  subroutine just like setMode to initialize anything you need for the screensaver mode.
leaveScreensaverMode
  subroutine to execute before the mode exits
string('SCREENSAVER_NAME')
  SCREENSAVER_NAME should be change to a unique identifier for your plugin name. This can be the same as your plugin, or something different. This allows for future nationalisation for the other languages supported by SlimServer.

#### Summary

Using the existing plugins as examples (one appears below) and this document as an explanation of what each section of the plugin can do, you should now be able to start working on a plugin of your own. Remember that this is only a framework to allow you to hook into the SlimServer. There are always many other ways to implement the features of a plugin. As long as your provide the lines from the examples above, the server will try to work with your Plugin. The rest can be just about anything you want, including using any of functions and subroutines within the SlimServer. Remember, there's more than one way to do it.

Happy Coding!

#### NOTE:

Plugins made for server version before 5.0 need to be updated using whack.pl, included in the tools directory of the CVS version of server 5.0 and higher. Syntax for this command is:

>     whack.pl myplugin.pm...

This will rewrite myplugin.pm (and any other specified files), leaving a copy of the script in myplugin.pm.old, to use the new module layout.

#### Appendix: Sample Plugin

    # Rescan.pm by Andrew Hedges (andrew@hedges.me.uk) October 2002
    #
    # This code is derived from code with the following copyright message:
    #
    # SlimServer Copyright (C) 2001 Sean Adams, Slim Devices Inc.
    # This program is free software; you can redistribute it and/or
    # modify it under the terms of the GNU General Public License,
    # version 2.
    use strict;

    ###########################################
    ### Section 1. Change these as required ###
    ###########################################

    package Plugins::Rescan;

    use Slim::Player::Playlist;
    use Slim::Utils::Strings qw (string);

    sub getDisplayName() (return string('PLUGIN_RESCAN_MUSIC_LIBRARY'));

    sub strings() {  return '
    PLUGIN_RESCAN_MUSIC_LIBRARY
    	EN	Rescan Music Library

    PLUGIN_RESCAN_RESCANNING
    	EN	Server now rescanning...

    PLUGIN_RESCAN_PRESS_PLAY
    	EN	Press PLAY to rescan your music folder
    '};

    ##################################################
    ### Section 2. Your variables and code go here ###
    ##################################################

    sub setMode() {
    	my $client = shift;
    	$client->lines(\&lines);
    }

    sub enabled {
    	my $client = shift;
    	return !Slim::Music::iTunes::useiTunesLibrary();
    }

    my %functions = (
    	'up' => sub {
    		my $client = shift;
    		$client->bumpUp();
    	},
    	'down' => sub {
    	    my $client = shift;
    		$client->bumpDown();
    	},
    	'left' => sub {
    		my $client = shift;
    		Slim::Buttons::Common::popModeRight($client);
    	},
    	'right' => sub {
    		my $client = shift;
    		$client->bumpRight();
    	},
    	'play' => sub {
    		my $client = shift;
    		my @pargs=('rescan');
    		my ($line1, $line2) = (string('PLUGIN_RESCAN_MUSIC_LIBRARY'), string('PLUGIN_RESCAN_RESCANNING'));
    		Slim::Control::Command::execute($client, \@pargs, undef, undef);
    		$client->showBriefly(, $line1, $line2);
    	}
    );

    sub lines {
    	my ($line1, $line2);
    	$line1 = string('PLUGIN_RESCAN_MUSIC_LIBRARY');
    	$line2 = string('PLUGIN_RESCAN_PRESS_PLAY');
    	return ($line1, $line2);
    }

    ################################################
    ### End of Section 2.                        ###
    ################################################

    sub getFunctions() {
    	return \%functions;
    }

    1;

[% PROCESS helpfooter.html %]