aboutsummaryrefslogtreecommitdiffstats
path: root/README
blob: 3d236e82e5ff5afe23531809700e0a0e9c750cd2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#
# $Id: README,v 1.23 2002/04/29 11:00:26 reinelt Exp $
#

This is the README file for lcd4linux


INTRODUCTION 

lcd4linux is a small program that reads various information from the kernel
(and from other subsystems, especially ISDN) and displays them on a LCD or
other display device.

It supports displaying text values and different types of bars: Horizontal and
vertical bars, logarithmic bars, split bars (two independent bars in one row).


USAGE

lcd4linux -h  
  print version number and a small help text, then exit

lcd4linux -l 
  list available drivers

#ifdef USE_OLD_UDELAY
lcd4linux -d
  calibrate delay loop (necessary for some drivers)
#endif

lcd4linux [-c key=val] [-F] [-f config-file] [-o output] [-q] [-v]
  run lcd4linux 
  overwrite entries from the config-file with '-c'
  do not fork and detach with '-F'
  use configuration from 'config-file' instead of /etc/lcd4linux.conf
  write picture to 'output' (raster driver only)
  suppress startup splash screen with '-q'
  generate info messages with '-v'
  generate debugging messages with '-vv'
  debug socket traffic too, with '-vvv'
  
  
DIAGNOSTICS

  lcd4linux on foreground writes (depending on level) to stdout or stderr.
  The Text-Driver has ist's own diagnostics window.
  
  Started in the background (the default), lcd4linux uses your syslog daemon
  for logging. Facility is USER, levels are ERR, INFO and DEBUG.


SUPPORTED DISPLAYS

* Matrox Orbital: <http://www.matrixorbital.com>
	
  "LCD0821": 2 lines by  8 characters
  "LCD1621": 2 lines by 16 characters
  "LCD2021": 2 lines by 20 characters
  "LCD2041": 4 lines by 20 characters (tested)
  "LCD4021": 2 lines by 40 characters


* CrystalFontz <http://www.crystalfontz.com>

  any of 626, 632, 634 and 636.


* HD44780-based displays

  This driver supports display modules based on the Hitachi HD44780 chip,
  connected to a parallel port. These displays are made by different 
  manufactures, and come in various sizes.

* Beckmann+Egle mini-terminal

  thanks to Helmut A. Bender <hab@arcanum.inka.de>


* Mitsubishi M50530

  I got a real cool display (8 lines by 24 characters!) from 
  Udo Altmann (udo.altmann@web.de) Thanks again!
  Unfortunately, the driver is not finished yet.


* Toshiba T6963

  I got another real cool display (240x128 pixel!) from 
  Carsten Nau (info@cnau.de). Thanks again!
  Unfortunately, the driver is not finished yet.


* PalmOrb

  I was told that lcd4linux works fine with PalmOrb, a small program that 
  emulates a Matrox Orbital display on the Palm Pilot.


* X11
  thanks to Herbert Rosmanith <herp@wildsau.idv.uni-linz.ac.at> a driver
  for the X Window System is available. It supports any size at any
  resolution. A very small XLCD4Linux-Window can even swallow on the KDE
  Panel!


* Raster formats:
  a generic raster driver (which is used by the X11-driver, too) is availiable,
  it supports:
     PPM (portable pixmap)
     PNG (with libgd)

* Text:
  This is a ncurses based text driver, mainly intended for debugging.

* other displays: lcd4linux and especially the display driver code is very
  modular, so it should be quite easy to write a driver for any display. See
  README.driver for details. Contributors are welcome!!!


CONFIGURATION

The configuration file (default: /etc/lcd4linux.conf) has a very simple
format: Every line consists of a key and a value, seperated by whitespace
(blanks or tabs). Values can contain whitespace, and can be enclosed in
single or double quotes. A key must not contain whitespace. Keys are NOT
case-sensitive. Order doesn't matter. Empty lines and all text on a line
after a '#' will be ignored. If you want to use '#' in a value (think of
X11-colors), you have to quote it with a backslash.

NOTE:

Because of security reasons the config file is assured to be:
   * - file is a normal file (or /dev/null)
   * - file owner is owner of program
   * - file is not accessible by group
   * - file is not accessible by other
   
So if you run lcd4linux as root, /etc/lcd4linux has to be:
  chmod 600
  chown root.root


The configuration file contains information for different modules of
lcd4linux:

Global options:

  tick: time in milliseconds between bar updates
  tack: time in milliseconds between text updates (text can be updated less 
        often than bars, so you get a smooth bar display and readable text)
  tau:  time constant (in milliseconds) for damping function (not used by now)

Data-specific options:

  overload: load average threshold and bar scaling. The '%L' token (see below)
            displays a '!' instead of a blank if the current load average 
	    exceeds this value. 
            load bars are scaled by this value (load=overload gives 100%)
  sensor1: path to the 1st temperature file 
           (e.g. /proc/sys/dev/sensors/w83781d-isa-0290/temp1)
           it is important that you use the isa sensors, because the i2c 
	   sensors are very slow!
  sensor1_min: temperature where the corresponding bar starts
  sensor1_max: temperature where bar ends
  sensor[2..9], -_min, -_max: entries for the 2nd to 9th temperature sensor
  
  exec:
     x1 ... x9: command to execute, PATH=/usr/local/bin:/usr/bin:/bin
                $X1 ... $X8 is result of command 1..8 in environment
     Tick_x1 .. 9 delay in ticks (overrides delay_x)
     Delay_x1 ..9 delay in seconds (default 1)
     Max_x1 ..9 max value for bars (default 100)
     Min_x1 ..9 min value for bars (default 0)
     
  battery: Battwarning 10 (default 10)
  
  Mailbox:
     The option string may be a plain mbox file or a pop3/imap4/nntp server
     string with the following format:
     
       pop3:[user[:pass]@]machine[:port]
       imap4:[user[:pass]@]machine[:port][/dir]
       nntp:[user[:pass]@]machine[:port][/dir]
       
     Port defaults to 110 and 143 respectively.
     If /dir is not given, INBOX is assumed.
     If dir is given for nntp: it should be a valid group name
       with '.' separating items
     If dir is not given for nntp: all/unread news of subscribed groups
     from Newsrc are calcualted.
       
     Delay_e1..e9 delay in seconds for querying the MailboxN (default 5)  
     Newsrc ... path/name of your .newsrc file containing subscribed news
     
     Note: authorization on newsservers is untested.
     Note: user and pass may not contain a '/' with above syntax, I hope
           that's ok.
   
  SetiDir: directory where seti@home stores its data files

 
Driver-specific options:
  
  Display: the name of a display model (see "supported displays" above)
  every driver has its own configuration options (e.g. 'Port', 'Speed', ...)
  see README.<Drivername> for details!
  
  
Display options:
  
  row1: Text to display in row 1
  row[2-max]: Text to display in other rows

  The text to be displayed can contain specific directives, which will be
  replaced by the appropriate values, or will create bars:

  '\nnn` will write the ASCII-character nnn (octal)
  '%<token>' will be replaced by the value of <token>
  '%%' will write a '%'
  '%$' will write a '$'

  '$<direction><length><token>[+<token>] will create a bar with the
  specified direction and length (in characters) with the value of <token>.
  If the driver supports dual bars, you can specify the second value with
  '+<token>'. <direction> can be 'l' (left), 'r' (right), 'u' (up) or 'd'
  (down). If you specify the direction in upper case, a logarithmic bar will
  be created. note that the space occupied by a bar always grows from left
  to right or from top to bottom, regardless of the direction!
  
  '$t<height><token>,<width> will create a time series bar. The data are 
  displayed like '$u', but are shifted every second 1 pixel to the 
  left. Currently only displays based on the pixel-driver support this 
  bar type.

Tokens:

  'o'  operating system name ('Linux')
  'v'  operating system release ('2.0.38')
  'p'  processor ('i686')
  'r'  total amount of memory installed (MB)
  'mt' total memory from /proc/meminfo (kB)
  'mu' used memory (kB)  
  'mf' free memory (kB)
  'ms' shared memory (kB)
  'mb' buffers (kB)
  'mc' page cache (kB)  
  'ma' application memory (kB) = used - buffer - cache
  'l1' load average for the past 1 minute
  'l2' load average for the past 5 minutes
  'l3' load average for the past 15 minutes
  'L'  '!' if load > overload (from config)   
  'cu' percentage of CPU in user mode
  'cn' percentage of CPU in niced tasks
  'cs' percentage of CPU in system mode
  'cb' percentage of CPU busy (=100-idle)
  'ci' percentage of CPU idle
  'dr' disk blocks read
  'dw' disk blocks written
  'dt' disk blocks total (read+write)
  'dm' disk blocks max (read, write)
  'nr' network bytes received
  'nw' network bytes transmitted
  'nt' network bytes total (receive+transmit)
  'nm' network bytes max (receive, transmit)
  'ii' ISDN bytes received
  'io' ISDN bytes sent
  'it' ISDN bytes total (received+send)
  'im' ISDN bytes max (received, send)
  'ic' ISDN connected (0=offline, 1=online)
  'ti' PPP bytes received
  'to' PPP bytes sent
  'tt' PPP bytes total (received+send)
  'tm' PPP bytes max (received, send)
  's1' temperature of sensor 1
  's2' temperature of sensor 2 (up to s9)
  'bp' battery percentage (APM by now)
  'bs' battery status ('=' = online, '+' = charging, '-' discharging)
  'bd' battery duration in s{econds}, m{ins}, h{ours} or d{ays}
  'hc' seti@home % completed
  'ht' seti@home time spent on workunit
  'e*' mails in mailbox 1-9, total mail
  'u*' mails in mailbox 1-9, unseen mail
  'x*' output of command 1-9
  
Please have a look at lcd4linux.conf.sample, where you can find examples
of all options and there usage.
: 5px; padding-right: 5px; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/* $Id$
 * $URL$
 *
 * mpd informations v0.82
 *
 * Copyright (C) 2006 Stefan Kuhne <sk-privat@gmx.net>
 * Copyright (C) 2007 Robert Buchholz <rbu@gentoo.org>
 * Copyright (C) 2008 Michael Vogt <michu@neophob.com>
 * Copyright (C) 2006 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
 *
 * This file is part of LCD4Linux.
 *
 * LCD4Linux is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * LCD4Linux is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* 
 * changelog v0.5 (20.11.2007):
 *   changed: 	mpd::artist(), mpd::title(), mpd::album()
 *              init code, call only if a function is used.
 *   removed: 	"old style" functions, duplicate code
 *   fixed: 	strdup() mem leaks
 *   added:	getstate, function which return player status: play/pause/stop
 *		getVolume, funtcion which returns the mpd volume
 *		getFilename, return current filename
 *
 * 
 * changelog v0.6 (05.12.2007):
 *  changed:    -connection handling
 *              -verbose "NO TAG" messages
 *              -init code
 *  added:      -added password support (MPD_PASSWORD env variable)
 *              -mpd::getSongsInDb - how many songs are in the mpd db?
 *              -mpd::getMpdUptime - uptime of mpd
 *              -mpd::getMpdPlayTime - playtime of mpd
 *              -mpd::getMpdDbPlayTime - playtime of all songs in mpd db
 *              -basic error handler..
 *		-mpd configuration in lcd4linux.conf
 *		-formatTime* - use those functions to format time values
 *  removed:    -reprand method
 *
 *
 * changelog v0.7 (14.12.2007):
 *  changed:	-connection handling improved, do not disconnect/reconnect for each query
 *               -> uses less ressources
 *
 * changelog v0.8 (30.01.2008):
 *  changed:  -libmpd is not needed anymore, use libmpdclient.c instead
 *  fixed:    -getMpdUptime()
 *            -getMpdPlaytime()
 *            -type definition
 *  added:    -mpd::getSamplerateHz
 *            -getAudioChannels
 *
 * changelog v0.83 (26.07.2008):
 *  added:    -mpd::cmd* commands
 *
 */

/*

TODO: 
 -what happens if the db is updating? 

*/

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>

#include "debug.h"
#include "plugin.h"
#include "cfg.h"
/* struct timeval */
#include <sys/time.h>

/* source: http://www.musicpd.org/libmpdclient.shtml */
#include "libmpdclient.h"

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif

#define TIMEOUT_IN_S 10
#define ERROR_DISPLAY 5

/* current song */

static int l_totalTimeSec;
static int l_elapsedTimeSec;
static int l_bitRate;
static int l_repeatEnabled;
static int l_randomEnabled;
static int l_state;
static int l_volume;
static int l_numberOfSongs;
static unsigned long l_uptime;
static unsigned long l_playTime;
static unsigned long l_dbPlayTime;
static int l_playlistLength;
/* pos in playlist */
static int l_currentSongPos;
static unsigned int l_sampleRate;
static int l_channels;

static mpd_Song *currentSong;

/* connection information */
static char host[255];
static char pw[255];
static int iport;
static int plugin_enabled;
static int waittime;
struct timeval timestamp;

static mpd_Connection *conn;
static char Section[] = "Plugin:MPD";
static int errorcnt = 0;


static int configure_mpd(void)
{
    static int configured = 0;

    char *s;

    if (configured != 0)
	return configured;

    /* read enabled */
    if (cfg_number(Section, "enabled", 0, 0, 1, &plugin_enabled) < 1) {
	plugin_enabled = 0;
    }

    if (plugin_enabled != 1) {
	info("[MPD] WARNING: Plugin is not enabled! (set 'enabled 1' to enable this plugin)");
	configured = 1;
	return configured;
    }

    /* read server */
    s = cfg_get(Section, "server", "localhost");
    if (*s == '\0') {
	info("[MPD] empty '%s.server' entry from %s, assuming 'localhost'", Section, cfg_source());
	strcpy(host, "localhost");
    } else
	strcpy(host, s);

    free(s);

    /* read port */
    if (cfg_number(Section, "port", 6600, 1, 65536, &iport) < 1) {
	info("[MPD] no '%s.port' entry from %s using MPD's default", Section, cfg_source());
    }

    /* read minUpdateTime in ms */
    if (cfg_number(Section, "minUpdateTime", 500, 1, 10000, &waittime) < 1) {
	info("[MPD] no '%s.minUpdateTime' entry from %s using MPD's default", Section, cfg_source());
    }


    /* read password */
    s = cfg_get(Section, "password", "");
    if (*s == '\0') {
	info("[MPD] empty '%s.password' entry in %s, assuming none", Section, cfg_source());
	memset(pw, 0, sizeof(pw));
    } else {
	strcpy(pw, s);
	free(s);
    }

    debug("[MPD] connection detail: [%s:%d]", host, iport);
    configured = 1;
    return configured;
}


static int mpd_update()
{
    int ret = -1;
    struct timeval now;

    /* reread every 1000 msec only */
    gettimeofday(&now, NULL);
    int timedelta = (now.tv_sec - timestamp.tv_sec) * 1000 + (now.tv_usec - timestamp.tv_usec) / 1000;

    if (timedelta < waittime) {
	/* debug("[MPD] waittime not reached...\n"); */
	return 1;
    }

    /* check if configured */
    if (configure_mpd() < 0) {
	return -1;
    }
    /* check if connected */
    if (conn == NULL || conn->error) {
	if (conn) {
	    if (errorcnt < ERROR_DISPLAY)
		debug("[MPD] Error: [%s], try to reconnect to [%s]:[%i]\n", conn->errorStr, host, iport);
	    mpd_closeConnection(conn);
	} else
	    debug("[MPD] initialize connect to [%s]:[%i]\n", host, iport);

	conn = mpd_newConnection(host, iport, TIMEOUT_IN_S);
	if (conn->error) {
	    if (errorcnt < ERROR_DISPLAY)
		error("[MPD] connection failed, give up...");
	    if (errorcnt == ERROR_DISPLAY)
		error("[MPD] stop logging, until connection is fixed!");
	    errorcnt++;
	    gettimeofday(&timestamp, NULL);
	    return -1;
	}
	errorcnt = 0;
	debug("[MPD] connection fixed...");
    }

    mpd_Status *status = NULL;
    mpd_Stats *stats = NULL;
    mpd_InfoEntity *entity;

    mpd_sendCommandListOkBegin(conn);
    mpd_sendStatsCommand(conn);

    if (conn->error) {
	error("[MPD] error: %s", conn->errorStr);
	return -1;
    }

    mpd_sendStatusCommand(conn);
    mpd_sendCurrentSongCommand(conn);
    mpd_sendCommandListEnd(conn);

    stats = mpd_getStats(conn);
    if (stats == NULL) {
	error("[MPD] error mpd_getStats: %s", conn->errorStr);
	goto cleanup;
    }

    mpd_nextListOkCommand(conn);
    if ((status = mpd_getStatus(conn)) == NULL) {
	error("[MPD] error mpd_nextListOkCommand: %s", conn->errorStr);
	goto cleanup;
    }

    mpd_nextListOkCommand(conn);
    while ((entity = mpd_getNextInfoEntity(conn))) {
	mpd_Song *song = entity->info.song;

	if (entity->type != MPD_INFO_ENTITY_TYPE_SONG) {
	    mpd_freeInfoEntity(entity);
	    continue;
	}
	if (currentSong != NULL)
	    mpd_freeSong(currentSong);

	currentSong = mpd_songDup(song);
	mpd_freeInfoEntity(entity);
    }

    l_elapsedTimeSec = status->elapsedTime;
    l_totalTimeSec = status->totalTime;
    l_repeatEnabled = status->repeat;
    l_randomEnabled = status->random;
    l_bitRate = status->bitRate;
    l_state = status->state;
    l_volume = status->volume;
    l_playlistLength = status->playlistLength;
    l_currentSongPos = status->song + 1;
    l_sampleRate = status->sampleRate;
    l_channels = status->channels;

    l_numberOfSongs = stats->numberOfSongs;
    l_uptime = stats->uptime;
    l_playTime = stats->playTime;
    l_dbPlayTime = stats->dbPlayTime;


    /* sanity checks */
    if (l_volume < 0 || l_volume > 100)
	l_volume = 0;

    if (l_bitRate < 0)
	l_bitRate = 0;

    if (l_elapsedTimeSec > l_totalTimeSec || l_elapsedTimeSec < 0)
	l_elapsedTimeSec = 0;
    ret = 0;

  cleanup:
    if (stats != NULL)
	mpd_freeStats(stats);

    if (status != NULL)
	mpd_freeStatus(status);

    if (conn->error) {
	error("[MPD] error: %s", conn->errorStr);
	return -1;
    }

    mpd_finishCommand(conn);
    if (conn->error) {
	error("[MPD] error mpd_finishCommand: %s", conn->errorStr);
	return -1;
    }

    gettimeofday(&timestamp, NULL);
    return ret;
}


static void elapsedTimeSec(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_elapsedTimeSec;
    SetResult(&result, R_NUMBER, &d);
}


static void totalTimeSec(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_totalTimeSec;
    SetResult(&result, R_NUMBER, &d);
}

static void bitRate(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_bitRate;
    SetResult(&result, R_NUMBER, &d);
}


static void getRepeatInt(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_repeatEnabled;
    SetResult(&result, R_NUMBER, &d);
}


static void getRandomInt(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_randomEnabled;
    SetResult(&result, R_NUMBER, &d);
}

/* if no tag is availabe, use filename */
static void getArtist(RESULT * result)
{
    mpd_update();
    if (currentSong != NULL) {
	if (currentSong->artist != NULL) {
	    SetResult(&result, R_STRING, currentSong->artist);
	} else {
	    if (currentSong->file != NULL)
		SetResult(&result, R_STRING, currentSong->file);
	    else
		SetResult(&result, R_STRING, "");
	}
    } else
	SetResult(&result, R_STRING, "");

}

static void getTitle(RESULT * result)
{
    mpd_update();
    if (currentSong != NULL) {
	if (currentSong->title != NULL) {
	    SetResult(&result, R_STRING, currentSong->title);
	} else
	    SetResult(&result, R_STRING, "");
    } else
	SetResult(&result, R_STRING, "");

}

static void getAlbum(RESULT * result)
{
    mpd_update();
    if (currentSong != NULL) {
	if (currentSong->album != NULL)
	    SetResult(&result, R_STRING, currentSong->album);
	else
	    SetResult(&result, R_STRING, "");
    } else
	SetResult(&result, R_STRING, "");
}

static void getFilename(RESULT * result)
{
    mpd_update();
    if (currentSong != NULL) {
	if (currentSong->file != NULL)
	    SetResult(&result, R_STRING, currentSong->file);
	else
	    SetResult(&result, R_STRING, "");
    } else
	SetResult(&result, R_STRING, "");

}

/*  
    return player state:
	0=unknown
	1=play
	2=pause
	3=stop
*/
static void getStateInt(RESULT * result)
{
    double ret;

    mpd_update();

    switch (l_state) {
    case MPD_STATUS_STATE_PLAY:
	ret = 1;
	break;
    case MPD_STATUS_STATE_PAUSE:
	ret = 2;
	break;
    case MPD_STATUS_STATE_STOP:
	ret = 3;
	break;
    default:
	ret = 0;
	break;
    }

    SetResult(&result, R_NUMBER, &ret);
}


static void getVolume(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_volume;
    /* return 0..100 or < 0 when failed */
    SetResult(&result, R_NUMBER, &d);
}

/* return the # of songs in the mpd db .. */
static void getSongsInDb(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_numberOfSongs;
    SetResult(&result, R_NUMBER, &d);
}

static void getMpdUptime(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_uptime;
    SetResult(&result, R_NUMBER, &d);
}

static void getMpdPlayTime(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_playTime;
    SetResult(&result, R_NUMBER, &d);
}

static void getMpdDbPlayTime(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_dbPlayTime;
    SetResult(&result, R_NUMBER, &d);
}

static void getMpdPlaylistLength(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_playlistLength;
    SetResult(&result, R_NUMBER, &d);
}

static void getCurrentSongPos(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_currentSongPos;
    SetResult(&result, R_NUMBER, &d);
}

static void getAudioChannels(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_channels;
    SetResult(&result, R_NUMBER, &d);
}

static void getSamplerateHz(RESULT * result)
{
    double d;
    mpd_update();
    d = (double) l_sampleRate;
    SetResult(&result, R_NUMBER, &d);
}


static void nextSong()
{
    mpd_update();
    if (currentSong != NULL) {
	mpd_sendNextCommand(conn);
	mpd_finishCommand(conn);
	if (conn->error) {
	    error("[MPD] error mpd_finishCommand: %s", conn->errorStr);
	}
    }
}

static void prevSong()
{
    mpd_update();
    if (currentSong != NULL) {
	mpd_sendPrevCommand(conn);
	mpd_finishCommand(conn);
	if (conn->error) {
	    error("[MPD] error mpd_finishCommand: %s", conn->errorStr);
	}
    }
}

static void stopSong()
{
    mpd_update();
    if (currentSong != NULL) {
	mpd_sendStopCommand(conn);
	mpd_finishCommand(conn);
	if (conn->error) {
	    error("[MPD] error mpd_finishCommand: %s", conn->errorStr);
	}
    }
}

static void pauseSong()
{
    mpd_update();
    if (currentSong != NULL) {
	if (l_state == MPD_STATUS_STATE_PAUSE) {
	    mpd_sendPauseCommand(conn, 0);
	} else {
	    mpd_sendPauseCommand(conn, 1);
	}

	mpd_finishCommand(conn);
	if (conn->error) {
	    error("[MPD] error mpd_finishCommand: %s", conn->errorStr);
	}
    }
}

static void volUp()
{
    mpd_update();
    if (currentSong != NULL) {
	l_volume += 5;
	if (l_volume > 100)
	    l_volume = 100;
	mpd_sendSetvolCommand(conn, l_volume);
	mpd_finishCommand(conn);
	if (conn->error) {
	    error("[MPD] error mpd_finishCommand: %s", conn->errorStr);
	}
    }
}

static void volDown()
{
    mpd_update();
    if (currentSong != NULL) {
	if (l_volume > 5)
	    l_volume -= 5;
	else
	    l_volume = 0;
	mpd_sendSetvolCommand(conn, l_volume);
	mpd_finishCommand(conn);
	if (conn->error) {
	    error("[MPD] error mpd_finishCommand: %s", conn->errorStr);
	}
    }
}

static void toggleRepeat()
{
    mpd_update();
    if (currentSong != NULL) {

	l_repeatEnabled = !l_repeatEnabled;
	mpd_sendRepeatCommand(conn, l_repeatEnabled);

	mpd_finishCommand(conn);
	if (conn->error) {
	    error("[MPD] error mpd_finishCommand: %s", conn->errorStr);
	}
    }
}


static void toggleRandom()
{
    mpd_update();
    if (currentSong != NULL) {

	l_randomEnabled = !l_randomEnabled;
	mpd_sendRandomCommand(conn, l_randomEnabled);

	mpd_finishCommand(conn);
	if (conn->error) {
	    error("[MPD] error mpd_finishCommand: %s", conn->errorStr);
	}
    }
}


static void formatTimeMMSS(RESULT * result, RESULT * param)
{
    long sec;
    char myTime[6] = " ";

    sec = R2N(param);

    if ((sec >= 0) && (sec < 6000)) {
	const int minutes = (int) (sec / 60);
	const int seconds = (int) (sec % 60);
	sprintf(myTime, "%02d:%02d", minutes, seconds);
    } else if (sec >= 6000) {
	strcpy(myTime, "LONG");
    } else
	strcpy(myTime, "ERROR");

    /* store result */
    SetResult(&result, R_STRING, myTime);
}

static void formatTimeDDHHMM(RESULT * result, RESULT * param)
{
    long sec;
    char myTime[16] = " ";

    sec = R2N(param);

    if (sec >= 0) {
	int days = sec / 86400;
	int hours = (sec % 86400) / 3600;
	int minutes = (sec % 3600) / 60;
	sprintf(myTime, "%dd%02dh%02dm", days, hours, minutes);	/* 3d 12:33h */
    } else
	strcpy(myTime, "ERROR");

    /* store result */
    SetResult(&result, R_STRING, myTime);
}



int plugin_init_mpd(void)
{
    int check;
    debug("[MPD] v0.83, check lcd4linux configuration file...");

    check = configure_mpd();
    if (plugin_enabled != 1)
	return 0;

    if (check)
	debug("[MPD] configured!");
    else
	debug("[MPD] error, NOT configured!");

    /* when mpd dies, do NOT exit application, ignore it! */
    signal(SIGPIPE, SIG_IGN);
    gettimeofday(&timestamp, NULL);

    AddFunction("mpd::artist", 0, getArtist);
    AddFunction("mpd::title", 0, getTitle);
    AddFunction("mpd::album", 0, getAlbum);
    AddFunction("mpd::file", 0, getFilename);
    AddFunction("mpd::totalTimeSec", 0, totalTimeSec);
    AddFunction("mpd::elapsedTimeSec", 0, elapsedTimeSec);
    AddFunction("mpd::bitRate", 0, bitRate);
    AddFunction("mpd::getSamplerateHz", 0, getSamplerateHz);
    AddFunction("mpd::getAudioChannels", 0, getAudioChannels);
    AddFunction("mpd::getRepeatInt", 0, getRepeatInt);
    AddFunction("mpd::getRandomInt", 0, getRandomInt);
    AddFunction("mpd::getStateInt", 0, getStateInt);
    AddFunction("mpd::getVolume", 0, getVolume);
    AddFunction("mpd::getSongsInDb", 0, getSongsInDb);
    AddFunction("mpd::getMpdUptime", 0, getMpdUptime);
    AddFunction("mpd::getMpdPlayTime", 0, getMpdPlayTime);
    AddFunction("mpd::getMpdDbPlayTime", 0, getMpdDbPlayTime);
    AddFunction("mpd::getMpdPlaylistLength", 0, getMpdPlaylistLength);
    AddFunction("mpd::getMpdPlaylistGetCurrentId", 0, getCurrentSongPos);

    AddFunction("mpd::cmdNextSong", 0, nextSong);
    AddFunction("mpd::cmdPrevSong", 0, prevSong);
    AddFunction("mpd::cmdStopSong", 0, stopSong);
    AddFunction("mpd::cmdTogglePauseSong", 0, pauseSong);
    AddFunction("mpd::cmdVolUp", 0, volUp);
    AddFunction("mpd::cmdVolDown", 0, volDown);
    AddFunction("mpd::cmdToggleRandom", 0, toggleRandom);
    AddFunction("mpd::cmdToggleRepeat", 0, toggleRepeat);

    AddFunction("mpd::formatTimeMMSS", 1, formatTimeMMSS);
    AddFunction("mpd::formatTimeDDHHMM", 1, formatTimeDDHHMM);

    return 0;
}


void plugin_exit_mpd(void)
{
    if (plugin_enabled == 1) {
	debug("[MPD] disconnect from mpd");
	if (currentSong != NULL)
	    mpd_freeSong(currentSong);
	mpd_closeConnection(conn);
    }
}