0.0.2
Copyright © 2006 Peter Marschall
Copyright © 2002 Guillaume Filion
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".
Abstract
This document is a guide to LCDproc written for developers. It covers LCDproc 0.5.x. Users should read the user guide.
Table of Contents
List of Examples
Table of Contents
This document is meant to be a reference for LCDproc developers. It tries to indicate you where to find the relevant information about LCDproc's inner workings.
Please note that this document is still "under construction". If you run into any trouble feel free to write to the LCDproc mailing list. See http://lcdproc.org/mail.php3 for details on how to subscribe to the list.
Therefore you might want to have a look at http://lcdproc.sf.net/docs/, to get the latest version of this document, unless you want to generate it yourself from the docbook files in the CVS).
This document was written for LCDproc 0.5.
In several other places e-mails and other documents have been included in this document. The authors of those are listed below every such document.
Table of Contents
To enable the debug() function on all of the software, just type: ./configure --enable-debug and recompile with 'make'.
To enable the debug() function only in specific files: 1) Configure without enabling debug (that is without --enable-debug) 2) Edit the source file that you want to debug and put the following line at the top, before the #include "report.h" line: #define DEBUG 3) Then recompile with 'make' This way, the global DEBUG macro is off but is locally enabled in certains parts of the software.
The reporting levels have the following meaning.
Reporting Levels
Critical conditions: the program stops right after this. Only use this if the program is actually exited from the current function.
Error conditions: serious problem, program continues. Use this just before you return -1 from a function.
Warning conditions: Something that the user should fix, but the program can continue without a real problem. Ex: Protocol errors from a client.
Major event in the program: (un)loading of driver, client (dis)connect.
Minor event in the program: the activation of a setting, details of a loaded driver, a key reservation, a keypress, a screen switch.
Insignificant event: What function has been called, what subpart of a function is being executed, what was received and sent over the socket, etc.
Levels 4 (maybe) and 5 (certainly) should be reported using the debug function. The code that this function generates will not be in the executable when compiled without debugging. This way memory and CPU cycles are saved.
report.h file defines 3 functions for debugging and reporting:
int set_reporting( | application_name, | |
| new_level, | ||
new_dest); |
char * | application_name; |
int | new_level; |
int | new_dest; |
Returns the content of the byte.
void report( | level, | |
| format, | ||
); |
const int | level; |
const char * | format; |
| ; |
Returns nothing (void).
The format parameter is the same as the one used by printf.
To create a list, do the following:
LinkedList *list; list = LL_new(); if(!list) handle_an_error();
The list can hold any type of data. You will need to typecast your datatype to a "void *", though. So, to add something to the list, the following would be a good way to start:
typedef struct my_data {
char string[16];
int number;
} my_data;
my_data *thingie;
for(something to something else) {
thingie = malloc(sizeof(my_data));
LL_AddNode(list, (void *)thingie); // typecast it to a "void *"
}
For errors, the general convention is that "0" means success, and a negative number means failure. Check LL.c to be sure, though.
To change the data, try this:
thingie = (my_data *)LL_Get(list); // typecast it back to "my_data" thingie->number = another_number;
You don't need to "Put" the data back, but it doesn't hurt anything.
LL_Put(list, (void *)thingie);
However, if you want to point the node's data somewhere else, you'll need to get the current data first, keep track of it, then set the data to a new location:
my_data * old_thingie, new_thingie; old_thingie = (my_data *)LL_Get(list); LL_Put(list, (void *)new_thingie); // Now, do something with old_thingie. (maybe, free it?)
Or, you could just delete the node entirely and then add a new one:
my_data * thingie; thingie = (my_data *)LL_DeleteNode(list); free(thingie); thingie->number = 666; LL_InsertNode(list, (void *)thingie);
To iterate on each list item, try this:
LL_Rewind(list);
do {
my_data = (my_data *)LL_Get(list);
/* ... do something to it ... */
} while(LL_Next(list) == 0);
You can also treat the list like a stack, or a queue. Just use the following functions:
LL_Push() // Regular stack stuff: add, remove, peek, rotate LL_Pop() LL_Top() LL_Roll() LL_Shift() // Other end of the stack (like in perl) LL_Unshift() LL_Look() LL_UnRoll() LL_Enqueue() // Standard queue operations LL_Dequeue()
There are also other goodies, like sorting and searching.
Array-like operations will come later, to allow numerical indexing:
LL_nGet(list, 3); LL_nSwap(list, 6, 13); LL_nPut(list, -4, data); // Puts item at 4th place from the end..
More ideas for later:
LL_MoveNode(list, amount); // Slides a node to another spot in the list -- LL_MoveNode(list, -1); // moves a node back one toward the head
That's about it, for now... Be sure to free the list when you're done!
See LL.c for more detailed descriptions of these functions.
Table of Contents
The LCDproc clients, for example lcdproc, connect over the network to LCDd. In their communication they use a protocol, often refered to as the "widget language". In this chapter the widget language will be discussed.
The essence of talking to LCDd is quite simple. First you will need to connect to the LCDproc port (usually 13666) on the correct IP address (by default localhost). Once you have established the connection you should say "hello", to let LCDd know you are a good guy. It will respond by telling some LCDproc data, like version and screen width and height. Now your session is open and you can start sending 'real' commands.
LCDd can send a number of strings itself. As a response to your commands, it will usually send a "success" string, or a string starting with "huh" in case of any error. See further below for other strings sent by LCDd.
You can test all these commands by opening a TCP/IP connection manually, like with:
telnet localhost 13666
This way, you can check how the various commands work. It's in this case best to have no other clients. If you do have other clients, you will receive "listen" and "ignore" messages that will disturb your typing.
In this section all commands and their parameters are listed, along with the responses you can expect. If you need a space or a special char in a string, you should quote the string with double quotes. If you need to use a double quote, escape it with a backslash. The listing is divided into subsections for
hello
Opens the session with the LCDd server program. This command is required before other commands can be issued. The response will be a string in the format:
connect parameter...
The client should read all parameters it needs and store their values. The following parameters are in use:
version
Indicates the version number of LCDd.
version
Indicates the widget language version number. This number is only changed when the language of a newer version has become incompatible with the previous version.
int
Tells the client the width of the attached display device in characters.
int
Tells the client the height of the attached display device in characters.
int
How many pixels is a character wide (space between character cells not included)
int
How many pixels is a character high (space between character cells not included)
This word is NOT followed by a value ! Hey do we really need this word in the response string ?
client_set -name name
Sets attributes for the current client. The current client is the one from the connection that you send this command on, in other words: yourself.
name is the client's name as visible to a user.
screen_add new_screen_id
Adds a screen to be displayed. The screen will be identified
by the string new_screen_id, which
is used later when manipulating on the screen.
screen_del screen_id
Removes the screen identified by screen_id
from the client's screens.
screen_set screen_id attributes...
Sets attributes for the given screen. The following attributes exist:
name
Sets the screen's name as visible to a user.
int
,
-hgt int
Sets the size of the screen in characters. If unset, the full display size is assumed.
pri_class
Sets the screen's priority. The following priority classes exist:
hiddenThe screen will never be visible
backgroundThe screen is only visible when no normal info screens exists
infonormal info screen, default priority
foregroundan active client
alertThe screen has an important message for the user.
inputThe client is doing interactive input.
inta positive integer that maps to priority classes above according to the mapping given in the table below.
| range | priority |
|---|---|
| 1 - 64 | foreground |
| 65 - 192 | info |
| 193 - ∞ | background |
LCDd will only show screens with the highest priority at that moment.
So when there are three info screens and one
foreground screen,
only the foreground screen will be visible.
Only background, info and
foreground screens will rotate;
higher classes do not rotate because their purpose is not suitable for rotation.
Changes the heartbeat setting for this screen.
If set to open, the default,
the client's heartbeat setting will be used.
Changes the screen's backlight setting.
If iset to the default value open,
the state will be determined by the client's setting.
blink is a moderately striking backlight variation,
flash is very strinking.
value
A screen will be visible for this amount of time every rotation.
The value is in eights of a second.
value
After the screen has been visible for a total of this amount of time,
it will be deleted. The value is in eights of a second.
Currently the client will not be informed of the deletion (TODO?).
Determines the visibility of a cursor.
If on, a cursor will be visible.
Depending on your hardware, this will be a hardware or software cursor.
The specified cursor shape (block or under)
might not be available in which case an other cursor shape will be used instead.
Default is off.
int
,
-cursor_y int
Set the cursor's x and y coordinates respectively.
If not given, the cursor will be set to
the leftmost (-cursor_x) resp.
topmost (-cursor_y) position.
Coordinates are always 1-based.
So the default top-left corner is denoted by (1,1).
widget_add screen_id new_widget_id widgettype [-in frame_id]
Adds a widget to the given screen.
The new_widget_id sets the identifier for this widget.
The optional -in
places the widget into the given frame.
The following widget types exist:
frame_id
string
A simple text.
title
A title bar on top of the screen.
hbar
A horizontal bar.
vbar
A vertical bar.
icon
A predefined or client-defined icon.
scroller
A variation of the string type that scrolls the text horizontally or vertically.
frame
A frame with that can contain widgets itself. In fact a frame displays an other screen in it.
num
A big number. They have a size of 3x4 characters. The special number 10 is a colon, that you can use for a clock. This character is 1x4.
widget_del screen_id widget_id
Deletes the given widget from the screen.
widget_set screen_id widget_id widgettype_specific_parameters
Sets parameters for a widget. Because not all widgets are created equal, the various widget types require different parameters.
string
x y text
Displays text at position
(x,y).
title
text
Uses text as the title to display.
hbar
,
vbar
x y length
Displays a horizontal (hbar) resp.
vertical (vbar) starting at
position (x,y)
that is length pixels wide resp. high.
icon
x y iconname
Displays the icon iconname at
position (x,y).
scroller
left top right bottom direction speed text
Displays a scroller spanning from position
(left,top)
to (right,bottom)
scrolling text in horizontal (h),
vertical (v) or marquee (m) direction
at a speed of speed, which is the number of
movements per rendering stroke (8 times/second).
frame
left top right bottom width height direction speed
Sets up a frame spanning from
(left,top)
to (right,bottom)
that is width columns wide and
height rows high.
It scrolls in either horizontal (h) or
vertical (v) direction at a speed
of speed, which is the number of
movements per rendering stroke (8 times/second).
num
x int
Displays decimal digit int at
the horizontal position x,
which is a normal character x coordinate on the display.
The special value 10 for int
displays a colon.
In this section all commands for creation, modification of menus and for interaction with them are described. Although keys may be used for other tasks they are listed here too.
TODO: example for normal (static) menu structure.
Menus may be even be used for wizards (the user is automatically guided through a number of configuration options) by virtue of the options -next and -prev. Here a complete example:
client_set name Parenttest
# to be entered on escape from test_menu (but overwritten
# for test_{checkbox,ring})
menu_add_item "" ask menu "Leave menus?" -is_hidden true
menu_add_item "ask" ask_yes action "Yes" -next _quit_
menu_add_item "ask" ask_no action "No" -next _close_
menu_add_item "" test menu "Test"
menu_add_item "test" test_action action "Action"
menu_add_item "test" test_checkbox checkbox "Checkbox"
menu_add_item "test" test_ring ring "Ring" -strings "one\ttwo\tthree"
menu_add_item "test" test_slider slider "Slider" -mintext "" -maxtext "" -value "50"
menu_add_item "test" test_numeric numeric "Numeric" -value "42"
menu_add_item "test" test_alpha alpha "Alpha" -value "abc"
menu_add_item "test" test_ip ip "IP" -v6 false -value "192.168.1.1"
menu_add_item "test" test_menu menu "Menu"
menu_add_item "test_menu" test_menu_action action "Submenu's action"
# no successor for menus. Since test_checkbox and test_ring have their
# own predecessors defined the "ask" rule will not work for them
menu_set_item "" test -prev "ask"
menu_set_item "test" test_action -next "test_checkbox"
menu_set_item "test" test_checkbox -next "test_ring" -prev "test_action"
menu_set_item "test" test_ring -next "test_slider" -prev "test_checkbox"
menu_set_item "test" test_slider -next "test_numeric" -prev "test_ring"
menu_set_item "test" test_numeric -next "test_alpha" -prev "test_slider"
menu_set_item "test" test_alpha -next "test_ip" -prev "test_numeric"
menu_set_item "test" test_ip -next "test_menu" -prev "test_alpha"
menu_set_item "test" test_menu_action -next "_close_"
menu_set_main ""
client_add_key [ -exclusively | -shared ] key...
Tells the server that the current client wants to make use of the given key(s). If you reserve the key(s) in shared mode, other clients can still reserve these keys too. If you reserve the key(s) in exclusive mode no other client can reserve them again. Key(s) reserved in shared mode will only be returned when a screen of the current client is active. These keys can be used for interaction with a visible screen (default). Key(s) reserved in exclusive mode will be returned regardless of which screen is active. They can be used to trigger a special feature or to make a screen come to foreground. Note that you cannot reserve a key in exclusive mode when an other client has reserved it in shared mode.
client_del_key key...
Ends the reservation of the given key(s).
menu_add_item menu_id new_item_id type [options]
Adds a new menu item to a menu. The main menu of a client, will be created automatically as soon as the client adds an item. This main menu has an empty id ("") and the name is identical to the name of the client. The options are described under menu_set_item below.
Some menu commands (menu_goto) and options
(-prev, -next) assume that
menu_ids are unique
(at least within a clients menu hierarchy).
menu item types
action
This item should trigger an action. It consists of simple text.
checkbox
Consists of a text and a status indicator. The status can be on (Y), off (N) or gray (o).
ring
Consists of a text and a status indicator. The status can be one of the strings specified for the item.
slider
Is visible as a text. When selected, a screen comes up that shows a slider. You can set the slider using the cursor keys. When Enter is pressed, the menu returns.
numeric
Allows the user to input an integer value. Is visible as a text. When selected, a screen comes up that shows the current numeric value, that you can edit with the cursor keys and Enter. The number is ended by selecting a 'null' input digit. After that the menu returns.
alpha
Is visible as a text. When selected, a screen comes up that shows the current string value, that you can edit with the cursor keys and Enter. The string is ended by selecting a 'null' input character. After that the menu returns.
ip
Allows the user to input an ip number (v4 or v6). When selected, a screen comes up that shows an ip number that can be edited - digit by digit - via left/right (switch digit) and up/down keys (increase/decrease).
menu
This is a submenu. It is visible as a text, with an
appended >. When selected, the submenu becomes the
active menu.
menu_del_item menu_id item_id
Removes a menu item item_id from menu
menu_id. The menu with the special id ""
(i.e. the empty string) is the client's main menu.
menu_set_item menu_id item_id item_specific_options...
Sets parameters for the menu item(s). Each item type knows different parameters.
options for the various menu items
string
The visible text of the item.
If the item currently should not appear in a menu.
successor_id
Sets the menu item to show after hitting the ENTER key when this item is active. This works for all menu item types except menus i.e. also for menu item types without an own screen e.g., checkbox, ring and action.
Special values
_close_
Equivalent to -menu_result close: Close
the menu.
_quit_
Equivalent to -menu_result quit: Quit
the menu system.
_none_
Equivalent to -menu_result none: Keep
the item open.
predecessor_id
Sets the menu item to show after hitting the ESCAPE key when this Item is active. This works for all menu item types i.e. also for menu item types without an own screen e.g., checkbox, ring and action.
If you define a predecessor for e.g., a checkbox and its parent menu too, the menu's predecessor is ignored in favor of the checkboxes one.
This option accepts the same special
values as the -next option.
action
Sets what to do with the menu when this action is selected: none: the menu stays as it is; close: the menu closes and returns to a higher level; quit: quits the menu completely so you can foreground your app.
checkbox
Set the value of the item.
Sets if a grayed checkbox is allowed.
ring
int (0)
Sets the index in the stringlist that is currently selected.
string (empty)
This single string should contain the strings that can be selected. They should be tab-separated (\t).
slider
int (0)
Sets its current value.
string ("")
,
-maxtext string ("")
The texts at the left and right side of the slider.
int (0)
,
-maxvalue int (100)
The minimum and maximum values of the slider.
int (1)
The stepsize of the slider. If you use 0, you can control the movement completely from your client.
numeric
int (0)
Sets its current value.
int (0)
,
-maxvalue int (100)
The minimum and maximum values that are allowed. If one of them is negative, the user will be able to enter negative numbers too.
TODO: floats!
alpha
string ("")
Sets its current value.
string ("")
If used, instead of the typed characters, this character will be visible.
int (0)
,
-maxlength int (10)
Sets the minimum and maximum allowed lengths.
(Dis)allow these groups of characters.
string ("")
The chars in this string are also allowed.
ip
string ("192.168.1.245")
Set the value of the item, e.g. "192.168.1.245" (v4) or ":::ffff:ffff:ffff:ffff:ffff" (v6).
Changes IP version from default v4.
menu
This is a submenu. It is visible as a text, with an
appended '>'. When selected, the submenu becomes the
active menu.
parentid
(Re)sets the parent of this menu. Parentid has to be of type menu. This function does not change any menu (neither the old nor the new parent) since this option is normally used with hidden menus. Otherwise use menu_add/del_item. Applying this option is equivalent to second argument of the menu_goto command.
menu_goto menu_id [parent_id]
Changes current menu to menu_id. Depending on the
configure option --enable-permissive-menu-goto the
client may switch to any (if enabled) or his menus only
(if not enabled).
menu_id
The menu item to go to (any menu type e.g. an action or a menu).
parent_id
Resets the parent of menu_id. This
optional parameter can be used to reuse a menu
from different places (for wizards etc.). Use it
with caution: This may lead to a messy menu
structure in particular due to the fact that the
menus are not changed !
menu_set_main menu_id
Sets the entry point into the menu system. Use this to
make the server menu invisible. Note that you may only set
the menu to your own clients menus unless the configure
option --enable-permissive-menu-goto is used.
(See menuscreens.c for the menu ids of the server menus.)
menu_id
The new main menu, restricted to the client's own menus. Special values:
The client's main menu.
_main_
Resets main to the "real" main menu.
backlight { on | off | toggle | blink | flash }
Sets the client's backlight state.
output { on | off | int }
Sets the general purpose output on some display modules to
this value. Use on to set all outputs to high state,
and off to set all to low state.
The meaning of the integer value depends on your specific device,
usually it is a bit pattern describing the state of each output line.
info
This command provides information about the driver.
noop
This command does nothing and is always successful. Can be useful to be sent at regular intervals to make sure your connection is still alive.
sleep int
Sleep for the given number of seconds. int
must be a positive integer in the range from 1 to 60.
This command is currently ignored on the server side.
LCDd can send messages back to the client. These messages can be directly related to the last command, or generated for some other reason. Because messages can be generated at any moment, the client should read from the connection at regular intervals. A very simple client could simply ignore all received messages. Not reading the messages will cause trouble !
success
This is the reponse to a command in case everything went ok.
huh? error_description
This is the response to a command in case something has gone wrong. The description is not meant to be parsed, it's only meant for the programmer of the client. It might be that your command has only been partially executed, for example if you try to reserve 3 keys, and one fails. Your client might need to undo its actions completely.
listen screen_id
ignore screen_id
The screen with the screen_id given is now
visible on the display (listen) or it is not visible
anymore on the display (ignore).
key key
This message will be sent if there was a keypress that should be delivered to the current client.
menuevent event_type id [value]
The user did something with a client supplied menu. The type of event can be:
select (action)
The item was activated.
update (checkbox, ring, numeric, alpha)
The item was modified by the user, so LCDd sends an updated
value.
plus (slider)
,
minus (slider)
The slider was moved to left (minus)
or right (plus), so
LCDd sends an updated value.
enter
This item has been entered, which means it is currently active on the screen. The client could now for example update the value of the item. If it is a menu, it may be needed to update the values of the items in it too, because they may be visible too.
leave
This item has been left, so it is currenly not the (main) active item anymore.
Multiple messages may be generated by one action of the user.
Table of Contents
This chapter describes the driver API of v0.5 of LCDproc. At time of this writing, this version is not released and some things might be changed.
The API consists of several functions to tell the driver that certains actions should be performed, some data, and several functions to retrieve configuration data from the server.
The API is best descibed by starting with the struct lcd_logical_driver which is defined in server/drivers/lcd.h.
The use of the API has changed from v0.4 to v0.5. The default functions that the server put in the pointers in v0.4 do no longer exist. Instead empty functions are the default. If a driver implements a function, the function will be detected by the server. The driver should at least implement all basic functions like driver_chr and driver_str itself, and should also have defined a number of other symbols for the server.
I will walk through the driver struct here.
typedef struct lcd_logical_driver {
//////// Variables to be provided by the driver module
// The driver loader will look for symbols with these names !
// pointer to a string describing the API version
char *api_version;
// Does this driver require to be in foreground ?
int *stay_in_foreground;// Does this driver require to be in foreground ?
/ Does this driver support multiple instances ?
int *supports_multiple;
// What should alternatively be prepended to the function names ?
char **symbol_prefix;
/*
The programmer should define the following symbols:
char * api_version = API_VERSION; // <-- this symbol is defined by make
int stay_in_foreground = 0; // This driver does not need to be in foreground
int supports_multiple = 0; // This driver does not c$support multiple instances
char *symbol_prefix = "MyDriver_"; // Driver functions start with MyDriver_
And fill these values with the correct values. Upon loading the driver module,
the server will locate these symbols and store pointers to them in the
driver struct.
Because the drivers are loadable, some kind of version checking should be
done. Therefor the server expects the correct version number to be found in
the api_version symbol (a string). For the v0.5 version this should be "0.5".
If the version is incompatible, the driver will not be loaded. The current
API version can always be determined by inserting the compiler define
API_VERSION in the code.
*/
//////// Functions to be provided by the driver module
//// Mandatory functions (necessary for all drivers)
// initialize driver: returns >= 0 on success
int (*init) (Driver *drvthis);
// close driver
void (*close) (Driver *drvthis);
//// Essential output functions (necessary for output drivers)
// get display width / height (in characters; 1-based)
int (*width) (Driver *drvthis);
int (*height) (Driver *drvthis);
// clear screen
void (*clear) (Driver *drvthis);
// flush screen contents to LCD
void (*flush) (Driver *drvthis);
// write string s at position (x,y)
void (*string) (Driver *drvthis, int x, int y, char *str);
// write char c at position (x,y)
void (*chr) (Driver *drvthis, int x, int y, char c);
//// essential input functions (necessary for input drivers)
// get key from driver: returns a string denoting the key pressed
const char *(*get_key) (Driver *drvthis);
//// Extended output functions (optional; core provides alternatives)
// draw a bar from pos (x,y) upward / to the right filling promille of len chars
void (*vbar) (Driver *drvthis, int x, int y, int len, int promille, int options);
void (*hbar) (Driver *drvthis, int x, int y, int len, int promille, int options);
// display (big) number num at horizontal position x
void (*num) (Driver *drvthis, int x, int num);
// set heartbeat state; animate heartbeat
void (*heartbeat) (Driver *drvthis, int state);
// draw named icon at position (x,y)
void (*icon) (Driver *drvthis, int x, int y, int icon);
// set cursor type and move it to position (x,y)
void (*cursor) (Driver *drvthis, int x, int y, int type);
//// User-defined character functions
// set special character / get free characters
// - It is currently unclear how this system should work exactly
// - The set_char function expects a simple block of data with 1 byte for each pixel-line.
// (So that is 8 bytes for a 5x8 char)
void (*set_char) (Driver *drvthis, char ch, unsigned char *dat);
int (*get_free_chars) (Driver *drvthis);
// get width / height of a character cell (in pixels)
// - necessary to provide info about cell size to clients
// - if not defined, the core will provide alternatives returning default values
int (*cellwidth) (Driver *drvthis);
int (*cellheight) (Driver *drvthis);
//// Hardware functions
// get / set the display's contrast
int (*get_contrast) (Driver *drvthis);
int (*set_contrast) (Driver *drvthis, int promille);
// get / set brightness for given backlight state
int (*get_brightness) (Driver *drvthis, int state);
int (*set_brightness) (Driver *drvthis, int state, int promille);
// set backlight state
void (*backlight) (Driver *drvthis, int state);
// set output
void (*output) (Driver *drvthis, int state);
//// Informational functions
// get a string describing the driver and it's features
const char * (*get_info) (Driver *drvthis);
//////// Variables in server core, available for drivers
// name of the driver instance (name of the config file section)
// - do not change from the driver; consider it read-only
// - to be used to access the driver's own section in the config file
char * name;
// pointer to the driver instance's private data
// - filled by the server by calling store_private_ptr()
// - the driver should cast this to it's own private structure pointer
void * private_data;
//////// Functions in server core, available for drivers
// store a pointer to the driver instance's private data
int (*store_private_ptr) (struct lcd_logical_driver * driver, void * private_data);
// Config file functions, cwprovided by the server
// - see configfile.h on how to use these functions
// - as sectionname, always use the driver name: drvthis->name
char (*config_get_bool) (char * sectionname, char * keyname,
int skip, char default_value);
int (*config_get_int) (char * sectionname, char * keyname,
int skip, int default_value);
double (*config_get_float) (char * sectionname, char * keyname,
int skip, double default_value);
char *(*config_get_string) (char * sectionname, char * keyname,
int skip, char * default_value);
// Returns a string in server memory space.
// Copy this string.
int config_has_section (char *sectionname);
int config_has_key (char *sectionname, char *keyname);
// error reporting function
// - see drivers/report.h for details
void (*report) ( const int level, const char *format, .../*args*/ );
// Display properties functions (for drivers that adapt to other loaded drivers)
// - the return the size of another already loaded driver
// - if no driver is loaded yet, the return values will be 0
int (*get_display_width) ();
int (*get_display_height) ();
} Driver;
With the introduction of loadable modules it is necesary to stop using global variables to store a driver's data in. Instead, you should store it in a structure, that you allocate abd store on driver's init. If you don't use this system, but use globals, you get queer results if you run two LCDd daemons on one machine. They will then use the same variables !
In the driver's private structure will probably at least be something like:
typedef struct my_driver_private {
int fd; // file descriptor for the LCD device
int width, height; // dimension of the LCD (in characters, 1-based
int cellwidth, cellheight; // Size of each LCD cell, in pixels
unsigned char *framebuf; // Frame buffer...
} PrivateData;
You allocate and store this structure like this:
PrivateData *p; // Allocate and store private data p = (PrivateData *) malloc(sizeof(PrivateData)); if (p == NULL) return -1; if (drvthis->store_private_ptr( drvthis, p ) < 0) return -1; // initialize private data p->fd = -1; p->cellheight = 8; p->cellwidth = 6; (... continue with the rest of your init routine)
You retrieve this private data pointer by adding the following code to the beginning of your functions:
PrivateData *p = (PrivateData *) drvthis->private_data;
Then you can access your data like:
p->framebuf
int (*init)( | drvthis); |
Driver * | drvthis; |
The init() function. It starts up the LCD, initializes all variables, allocates private data space and stores the pointer by calling store_private_ptr();
void (*close)( | drvthis); |
Driver * | drvthis; |
Shut down the connection with the LCD. Called just before unloading the driver.
int (*width)( | drvthis); |
Driver * | drvthis; |
Get the screen width in characters. The result is 1-based.
int (*height)( | drvthis); |
Driver * | drvthis; |
Get the screen height in character lines. The result is 1-based.
void (*clear)( | drvthis); |
Driver * | drvthis; |
Clear the framebuffer.
void (*flush)( | drvthis); |
Driver * | drvthis; |
Flush the framebuffer to the LCD.
void (*string)( | drvthis, | |
| x, | ||
| y, | ||
str); |
Driver * | drvthis; |
int | x; |
int | y; |
char * | str; |
Place string str into position
(x,y) in the framebuffer.
All coordinates are 1-based, i.e. (1,1) is top left.
The driver should check for overflows, i.e. that the positional parameters
are within the screen's boundaries and cut off the part of the string
that is out of bounds.
void (*chr)( | drvthis, | |
| x, | ||
| y, | ||
c); |
Driver * | drvthis; |
int | x; |
int | y; |
char | c; |
Place a single character c into position
(x,y) in the framebuffer.
The driver should check for overflows, i.e. that the positional parameters
are within the screen's boundaries and ignore the request if
the character is out of bounds.
void (*vbar)( | drvthis, | |
| x, | ||
| y, | ||
| len, | ||
| promille, | ||
options); |
Driver * | drvthis; |
int | x; |
int | y; |
int | len; |
int | promille; |
int | options; |
Draw a vertical bar at position (x,y)
that has maximal length len, where a fraction of
(promille / 1000) is filled.
void (*hbar)( | drvthis, | |
| x, | ||
| y, | ||
| len, | ||
| promille, | ||
options); |
Driver * | drvthis; |
int | x; |
int | y; |
int | len; |
int | promille; |
int | options; |
Draw a horizontal bar at position (x,y)
that has maximal length len, where a fraction of
(promille / 1000) is filled.
void (*num)( | drvthis, | |
| x, | ||
num); |
Driver * | drvthis; |
int | x; |
int | num; |
Display big number num at horizontal position x.
void (*heartbeat)( | drvthis, | |
state); |
Driver * | drvthis; |
int | state; |
Sets the heartbeat to the indicated state: 0=off 1=graph1 2=graph2 HEARTBEAT_ON to say that we want to display/refresh the heartbeat. The driver choose how to do it. See MtxOrb.c
void (*icon)( | drvthis, | |
| x, | ||
| y, | ||
icon); |
Driver * | drvthis; |
int | x; |
int | y; |
int | icon; |
Draw named icon icon at position
(x,y).
void (*cursor)( | drvthis, | |
| x, | ||
| y, | ||
type); |
Driver * | drvthis; |
int | x; |
int | y; |
int | type; |
Move cursor to position (x,y),
setting its type to type.
void (*set_char)( | drvthis, | |
| ch, | ||
dat); |
Driver * | drvthis; |
char | ch; |
unsigned char * | dat; |
The set_char function expects a simple block of data with 1 byte for each pixel-line. (So that is 8 bytes for a 5x8 char)
int (*get_free_chars)( | drvthis); |
Driver * | drvthis; |
int (*cellwidth)( | drvthis); |
Driver * | drvthis; |
Return the width of a character cell in pixels. The result is 1-based.
int (*cellheight)( | drvthis); |
Driver * | drvthis; |
Return the height of a character cell in pixels. The result is 1-based.
int (*get_contrast)( | drvthis); |
Driver * | drvthis; |
Get the contrast value from the driver. The return value is an integer in the range from 0 to 1000. Many displays do not support getting or setting contrast using software.
int (*set_contrast)( | drvthis, | |
promille); |
Driver * | drvthis; |
int | promille; |
Sets the contrast to the given value, which is an integer in the range from 0 to 1000. It is up to the driver to map the logical interval [0, 1000] into the interval that the hardware supports. Many displays do not support software setting of contrast.
int (*get_brightness)( | drvthis, | |
state); |
Driver * | drvthis; |
int | state; |
Get the brightness value from the driver for the given backlight state.
The parameter state determnies which one
is returned.
The return value is an integer in the range from 0 to 1000.
Many displays do not support getting or setting brightness using software.
int (*set_brightness)( | drvthis, | |
| state, | ||
promille); |
Driver * | drvthis; |
int | state; |
int | promille; |
Set the brightness for the given backlight state to the value given. Value must be an integer in the range from 0 to 1000. It is up to the driver to map the logical interval [0, 1000] into the interval that the hardware supports. Many displays do not support software setting of brightness.
void (*backlight)( | drvthis, | |
state); |
Driver * | drvthis; |
int | state; |
Sets the backlight to the given brightness state. Often hardware can only support two values for the backlight: on and off. In that case any value of state > 0 will switch the backlight on.
void (*output)( | drvthis, | |
state); |
Driver * | drvthis; |
int | state; |
Sets the output value. Some displays/wirings have a general purpose output, which can be controlled by calling this function. See the 'output' command in the 'widget language'.
const char *(*get_key)( | drvthis); |
Driver * | drvthis; |
Checks if a key has been pressed on the device. Returns NULL for "no key pressed", or a string describing the pressd key. These characters should match the keypad-layout.
const char *(*get_info)( | drvthis); |
Driver * | drvthis; |
Returns a string describing the driver and its features.
char (*config_get_bool)( | sectionname, | |
| keyname, | ||
| skip, | ||
default_value); |
char * | sectionname; |
char * | keyname; |
int | skip; |
char | default_value; |
Call to server. Retrieve a bool from the config file. Sectionname should be the name of the driver (as in the struct). If the key cannot be found, the default value will be returned. skip should be 0 usually, but if you want to retrieve multiple identical keys, then increase skip to get every next value.
int (*config_get_int)( | sectionname, | |
| keyname, | ||
| skip, | ||
default_value); |
char * | sectionname; |
char * | keyname; |
int | skip; |
int | default_value; |
Call to server. Retrieve an integer from the config file.
double (*config_get_float)( | sectionname, | |
| keyname, | ||
| skip, | ||
default_value); |
char * | sectionname; |
char * | keyname; |
int | skip; |
double | default_value; |
Call to server. Retrieve a float from the config file.
char *(*config_get_string)( | sectionname, | |
| keyname, | ||
| skip, | ||
default); |
char * | sectionname; |
char * | keyname; |
int | skip; |
char * | default; |
Call to server. Retrieve a string from the config file. Fill result with a pointer to some available space. You can fill it with a default value. If the key is found, it will be overwritten with the value from the key. Note that you should always first copy the the returned string. It is in the address space of the server, and will be freed at the next call.
int config_has_section( | sectionname); |
char * | sectionname; |
Returns wether a section exists. Does not need to be called prior to a call to a config_get_* function.
int config_has_key( | sectionname, | |
keyname); |
char * | sectionname; |
char * | keyname; |
Returns the number of times a key exists. Does not need to be called prior to a call to a config_get_* function.
First version, Joris Robijn, 20011016
Table of Contents
LCDproc is meant to be modular, it is relatively easy to add new input and output drivers to LCDproc.
This chapter will explain you the major steps and few gotchas of adding your own driver to LCDproc. Enjoy!
How I Learned to Stop Worrying and Love the Configure Script
It was decided pretty early in LCDproc's life to use GNU autoconf and GNU automake. This allows LCDproc to be ported to several platforms with much less effort. It can be quite daunting to understand how autoconf & automake interact with each others and with your code, but don't be discouraged. We have taken great care in making this as simple as possible for programers to add their own driver to LCDproc. Hopefully, you'll only have to modify two files, one for autoconf and one for automake.
The first thing you need to do is to find a name for your driver, it should be as descriptive as possible; most drivers are named after their respective chipset, for example hd44780, mtc_s16209x, sed1330 and stv5730, others are named after the company that makes that particular LCD display, for example CFontz and MtxOrb. Remember that these names are case sensitive. In this chapter, we'll use myDriver (which is an absolute non-descriptive name).
You need to add your driver to function LCD_DRIVERS_SELECT of file acinclude.m4. This can be done in three steps.
First you need to add your driver name to the list of possible choices in the help screen.
This:
AC_ARG_ENABLE(drivers, [ --enable-drivers=<list> compile driver for LCDs in <list>.] [ drivers may be separated with commas.] [ Possible choices are:] [ bayrad,CFontz,CFontz633,CFontzPacket,curses,CwLnx,] [ glcdlib,glk,hd44780,icp_a106,imon,IOWarrior,irman,] [ joy,lb216,lcdm001,lcterm,lirc,ms6931,mtc_s16209x,] [ MtxOrb,NoritakeVFD,pyramid,sed1330,sed1520,serialVFD,] [ sli,stv5730,svga,t6963,text,tyan,ula200,xosd] [ 'all' compiles all drivers;] [ 'all,!xxx,!yyy' de-selects previously selected drivers], drivers="$enableval",
becomes:
AC_ARG_ENABLE(drivers,
[ --enable-drivers=<list> compile driver for LCDs in <list>.]
[ drivers may be separated with commas.]
[ Possible choices are:]
[ bayrad,CFontz,CFontz633,CFontzPacket,curses,CwLnx,]
[ glcdlib,glk,hd44780,icp_a106,imon,IOWarrior,irman,]
[ joy,lb216,lcdm001,lcterm,lirc,ms6931,mtc_s16209x,]
[ MtxOrb,NoritakeVFD,pyramid,sed1330,sed1520,serialVFD,]
[ sli,stv5730,svga,t6963,text,tyan,ula200,xosd,myDriver]
[ 'all' compiles all drivers;]
[ 'all,!xxx,!yyy' de-selects previously selected drivers],
drivers="$enableval",
Second, you need to add your driver to the list of all drivers.
This:
allDrivers=[bayrad,CFontz,CFontz633,...(big list)...,tyan,ula200,xosd]
becomes:
allDrivers=[bayrad,CFontz,CFontz633,...(big list)...,tyan,ula200,xosd,myDriver]
Then last, you need to add your driver to be big switch-case in this function, see below.
myDriver)
DRIVERS="$DRIVERS myDriver${SO}"
actdrivers=["$actdrivers myDriver"]
;;
If your driver only works in some platform or requires a particular library or header, you can add your autoconf test here. You can see how other drivers do it, but if you're not sure on how to do this, just send an email to the mailing list and we'll make it for you.
Allready half of the job is done! Not to bad, wasn't it? The rest should be just as easy. In this section, you'll be adding your driver to the file server/drivers/Makefile.am. As you can guess, it's the Makefile for the drivers. This can be done in three (or two) simple steps.
First, you need to add your driver to the list of drivers in this file, this list is called EXTRA_PROGRAMS.
This
EXTRA_PROGRAMS = bayrad CFontz ...(big list)... ula200 xosd
becomes
EXTRA_PROGRAMS = bayrad CFontz ...(big list)... ula200 xosd myDriver
This second step is only needed if your driver needs a particular library. If it doesn't, you can skip to step 3.
You basically need to put you driver name followed by _LDADD and egal this to the name of the library that you need. Usually, these library are substituted by a autoconf variable, if you're not comfortable with this, you send an email to the mailing list and we'll set this up for you.
For example, we would put this for our fictional driver
myDriver_LDADD = @SOMESTRANGELIB@
Last but not least, you need to specify which source files should be
associated with your driver. You put your driver name followed by
_SOURCES and egal this to a space separated list
of the source and header files. See below for an example.
myDriver_SOURCES = lcd.h myDriver.c myDriver.h report.h
You're almost done! You only need to check out if you didn't made any mistake. Just run sh autogen.sh to regenerate the configure script and Makefiles, then run ./configure --enable-drivers=myDriver and type make. If your driver compiles without error, then congratulations, you've just added your driver to LCDproc! Remember to submit a patch to the mailing list so that we can add it to the standard distribution, but do not forget the documentation.
If you had an error, just send us an email describing it to the mailing list and we'll try to help you.
Please do not forget to also add the required documentation, so that your driver can be used from others as well.
Extend the LCDproc server's configuration file with a section that holds a standard configuration for your driver together with short descriptions of the options used.
…
## MyDriver for MyDevice ##
[MyDriver]
# Select the output device to use [default: /dev/lcd]
Device=/dev/ttyS0
# Set the display size [default: 20x4]
Size=20x4
…
Append your driver to the list of drivers in docs/LCDd.conf,
the manual page of LCD, so that users can find your driver when doing man LCDd.
… .TP .B ms6931 MSI-6931 displays in 1U rack servers by MSI .TP .B mtc_s16209x MTC_S16209x LCD displays by Microtips Technology Inc .TP .B MtxOrb Matrix Orbital displays (except Matrix Orbital GLK displays) .TP .B MyDriver displays connected using MyDevice .TP .B NoritakeVFD Noritake VFD Device CU20045SCPB-T28A .TP .B pyramid LCD displays from Pyramid (http://www.pyramid.de) .TP .B sed1330 SED1330/SED1335 (aka S1D13300/S1D13305) based graphical displays …
Please add a file myDriver.docbook,
that describes the configuration of your driver and the hard/software needed,
to the directory docs/lcdproc-user/drivers/.
Define a Docbook entity for your driverfile in lcdproc-user.docbook.
…
<!ENTITY ms6931 SYSTEM "drivers/ms6931.docbook">
<!ENTITY mtc_s16209x SYSTEM "drivers/mtc_s16209x.docbook">
<!ENTITY MtxOrb SYSTEM "drivers/mtxorb.docbook">
<!ENTITY MyDriver SYSTEM "drivers/MyDriver.docbook">
<!ENTITY NoritakeVFD SYSTEM "drivers/NoritakeVFD.docbook">
<!ENTITY pylcd SYSTEM "drivers/pylcd.docbook">
<!ENTITY sed1330 SYSTEM "drivers/sed1330.docbook">
…
Table of Contents
LCDproc is meant to be modular, it is relatively easy to add new input and output drivers to LCDproc. Actually, there are a few things that you can do to make your life easier, they are listed here.
This chapter will explain you the major steps and few gotchas of adding your own driver to LCDproc. Enjoy!
Driving an LCD display is not easy; you need to address ports, to send bytes in a certain order, to respect timing, and unfortunaly no two operating system let you do this in the same way. But don't dispair! There's hope! Someone in a galaxy far far away, has allready done the dirty job for you! This dirty job has been put in shared files. These shared files are full cross platform and are automagically configured by the configure script. You only need to include them and use their functions to benefit from them.
These files are provided only for drivers, others are provided for all of LCDproc. These files are located in the shared directory, they have a dedicated chapter in this book.
The file port.h, located in the server/drivers/ directory provide Input/Output and port permissions for the PC compatible parallel port, also known as the LPT port.
Of course, these functions will only work if the computer where LCDproc runs has parallel port!r In these situations, the configure script will see this and disable drivers that need a parallel port.
port.h file defines 6 static inline functions for port I/O:
static inline int port_in( | port); |
unsigned short int | port; |
Returns the content of the byte.
static inline void port_out( | port, | |
val); |
unsigned short int | port; |
unsigned char | val; |
Returns nothing (void).
static inline int port_access( | port); |
unsigned short int | port; |
Returns 0 if successful, -1 if failed.
static inline int port_deny( | port); |
unsigned short int | port; |
Returns 0 if successful, -1 if failed.
static inline int port_access_full( | port, | |
count); |
unsigned short int | port; |
unsigned short int | count; |
Returns 0 if successful, -1 if failed.
static inline int port_deny_full( | port, | |
count); |
unsigned short int | port; |
unsigned short int | count; |
Returns 0 if successful, -1 if failed.
#include "port.h"
/* Get access to these 3 ports:
0x378 (CONTROL),
0x379 (STATUS) and
0x37A (DATA)
*/
if ( -1 == port_access_multiple(0x378,3) ) {
/* Access denied, do something */
}
/* Write a 'A' to the control port */
ort_out(0x378, 'A');
/* Read from the status port */
char status = port_in(0x379);
/* Close the 3 ports */
port_deny_multiple(0x378,3);
adv_bignum.h is the headerfile for libbignum.a
(made from adv_bignum.c) which contains everything needed to show big-numbers,
including the fonts for the different displays.
(All files are located in the server/drivers/ directory.)
There are only a few requirements to the calling driver:
The following functions have to be implemented by the driver:
height()to determine the display's height and thus the maximal height of the big numbers to be displayed.
get_free_chars()to determine the number of user-defineable characters that can be used in the generation of big numbers.
set_char()
to define a character necessary to write a big number.
Of course this is only necessary if there really are user-definieable
characters, i.e. only if get_free_chars() returns
a value greater 0.
chr()to actually write the characters the big numbers consist of.
The display's cellwidth has to be 5
(6 works also in some cases) and the cellheight
7 or 8.
The custom-characters (if any) have to be at character positions
offset+0,
offset+1,
offset+2, ...
offset+
get_free_chars()-1,
offset+
get_free_chars()-1 must be less than 32,
The library determines the correct font, depending on the display size and the number of user-defined characters itself. So it is easy to integrate into the driver.