[SET pageicon help] [SET title SlimServer Plugins] [SET techinfo 1] [INCLUDE 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::Display::Animation::showBriefly() instead of showBriefly()).

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;
        Slim::Display::Animation::bumpUp($client);
     },
     'down' => sub  {
        my $client = shift;
        Slim::Display::Animation::bumpDown($client);
     },
     'left' => sub  {
        my $client = shift;
        Slim::Buttons::Common::popModeRight($client);
     },
     'right' => sub  {
        my $client = shift;
        Slim::Display::Animation::bumpRight($client);
     },
     '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);
        Slim::Display::Animation::showBriefly($client, $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:

Slim::Display::Animation::showBriefly($client, $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 &lbrc;
   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' => &lbrc;
         'validate' => \&Slim::Web::Setup::validateInt
         ,'validateArgs' => [1,undef,1]
      &rbrc;
      'plugin-pluginName-pref-two' => &lbrc;
         'validate' => \&Slim::Web::Setup::validateTrueFalse  
         ,'options' => &lbrc;
            '0' => string('SETUP_PLUGIN-PLUGINNAME_PREFTWO_NO)
            ,'1' => string('SETUP_PLUGIN-PLUGINNAME_PREFTWO_YES)
         &rbrc;
      &rbrc;,
   );	
   return (\%setupGroup,\%setupPrefs);
&rbrc;

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.


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() &lbrc;
	Slim::Buttons::Common::addSaver('SCREENSAVER.saverModeName',
		getScreensaverFunctions(),
		\&setScreensaverMode,
		\&leaveScreensaverMode,
		string('PLUGIN_SCREENSAVER_NAME'));
&rbrc;
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;
		Slim::Display::Animation::bumpUp($client);
	},
	'down' => sub  {
	    my $client = shift;
		Slim::Display::Animation::bumpDown($client);
	},
	'left' => sub  {
		my $client = shift;
		Slim::Buttons::Common::popModeRight($client);
	},
	'right' => sub  {
		my $client = shift;
		Slim::Display::Animation::bumpRight($client);
	},
	'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);
		Slim::Display::Animation::showBriefly($client, $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;
[INCLUDE helpfooter.html]