/* GKRELLMWireless
| Copyright (C) 2000-2002 Sjoerd Simons
|
| Author: Sjoerd Simons sjoerd@gkrellm.luon.net
| Latest versions might be found at: http://gkrellm.luon.net
|
| This program is free software which I release under the GNU General Public
| License. You may redistribute and/or modify this program under the terms
| of that license as published by the Free Software Foundation; either
| version 2 of the License, or (at your option) any later version.
|
| This program 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.
|
| To get a copy of the GNU General Puplic License, write to the
| Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "wireless.h"
static GkrellmMonitor *monitor;
static gint panel_style_id;
static wcard_t *cards = NULL;
static GtkWidget *PanelBox;
static void reset_panel(int create);
wcard_t *
new_wcard(gchar *interface,int found, int flags) {
/* creates an new wcard entry for interface, and sets the flags if it's not
a newly found card */
wcard_t *newcard;
wcard_t *card;
newcard = malloc(sizeof(wcard_t));
newcard->next = NULL;
newcard->ifname = strdup(interface);
newcard->level_panel = NULL;
newcard->link_panel = NULL;
newcard->noise_panel = NULL;
if (found) newcard->flags = (0 | SHOW | SHOW_LINK | SHOW_LEVEL | SHOW_NOISE);
else /* a card wich is not detected, but from a config file */
newcard->flags = flags & ~ACTIVE;
if (cards == NULL) cards = newcard;
else {
for (card = cards ; card->next != NULL; card = card->next);
card->next = newcard;
}
return newcard;
}
void
del_wcard(wcard_t *card) {
/* called when a card isn't active anymore, sets it's entry to */
/* uninitialized */
card->flags &= (~ACTIVE);
reset_panel(0);
}
wcard_t *found_wcard(gchar *interface) {
/* called by the system specific find_wcard functions */
/* for each wireless card it had found */
/* adds/modifies an wireless card entry for it */
/* returns the entry, so it's system specific entry can be edited */
wcard_t *card;
for (card = cards ; card != NULL ; card = card->next) {
if (strcmp(card->ifname,interface)) {
/* wrong entry */
continue;
}
if (card->flags & ACTIVE) {
/* already showing this one */
return NULL;
} else {
card->flags |= ACTIVE;
return card;
}
/* found the entry */
}
/* never saw this card before, creating new entry */
card = new_wcard(interface,1,0);
card->flags |= ACTIVE;
gkrellm_config_modified();
return card;
}
/* system specific stuff */
#if defined(__FreeBSD__) || defined(__NetBSD__)
/* FreeBSD & NetBSD specific */
#if defined(__FreeBSD__) && __FreeBSD_version >= 600034
static gint
find_scan_card(void) {
gint ret = FALSE;
struct ifaddrs *res = NULL, *ifa = NULL;
struct ifmediareq ifmr;
int s;
if (getifaddrs(&res) != 0)
return ret;
if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
freeifaddrs(res);
return ret;
}
for (ifa = res; ifa; ifa = ifa->ifa_next) {
memset(&ifmr, 0, sizeof(ifmr));
strncpy(ifmr.ifm_name, ifa->ifa_name, sizeof(ifmr.ifm_name));
if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
continue;
if (!(ifmr.ifm_status & IFM_AVALID))
continue;
if (IFM_TYPE(ifmr.ifm_active) != IFM_IEEE80211)
continue;
if (found_wcard(ifa->ifa_name) != NULL)
ret = TRUE;
}
close(s);
freeifaddrs(res);
return ret;
}
#else
static int
find_wi_card(void) {
/* possible interfaces */
char interfaces[][4] = {"wi0","wi1","wi2"};
/* wireless info request struct */
struct wi_req wreq;
/* interface request struct */
struct ifreq ifr;
int s,i,ret;
ret = FALSE;
/* open a socket for ioctl's */
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return ret;
/* clean out the wreq struct */
memset(&wreq, 0, sizeof(wreq));
wreq.wi_len = WI_MAX_DATALEN;
/* we want to know the quality */
wreq.wi_type = WI_RID_COMMS_QUALITY;
for (i = 0 ; i < 3 ; i++ ) {
memset(&ifr,0, sizeof(ifr));
/* ask information about a certain interface */
strcpy(ifr.ifr_name,interfaces[i]);
ifr.ifr_data = (caddr_t)&wreq;
/* get information about this interface, if it excists it's an card */
if (ioctl(s, SIOCGWAVELAN, &ifr) == -1) continue;
if(found_wcard(interfaces[i]) != NULL) ret = TRUE;
}
close(s);
return ret;
}
#if !defined(__NetBSD__)
static gint
find_an_card(void) {
char interfaces[][4] = {"an0","an1","an2"};
int i,s,ret ;
struct ifreq ifr;
struct an_req areq;
ret = FALSE;
/* open a socket for ioctl's */
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return ret;
for (i = 0 ; i < 3 ; i++ ) {
memset(&ifr,0, sizeof(ifr));
/* ask information about a certain interface */
strcpy(ifr.ifr_name,interfaces[i]);
areq.an_len = AN_MAX_DATALEN;
areq.an_type = AN_RID_READ_CACHE;
ifr.ifr_data = (caddr_t)&areq;
if (ioctl(s,SIOCGAIRONET,&ifr) == -1) continue;
if(found_wcard(interfaces[i]) != NULL) ret = TRUE;
}
close(s);
return ret;
}
#endif /* !defined(__NetBSD__) */
#endif /* defined(__FreeBSD__) && __FreeBSD_version >= 600034 */
static gint
find_wlancard(void) {
gint ret = FALSE;
#if defined(__FreeBSD__) && __FreeBSD_version >= 600034
ret = find_scan_card();
#else
ret = find_wi_card();
#if !defined(__NetBSD__)
ret = find_an_card() || ret;
#endif /* !defined(__NetBSD__) */
#endif /* defined(__FreeBSD__) && __FreeBSD_version >= 600034 */
return ret;
}
#if defined(__FreeBSD__) && __FreeBSD_version >= 600034
static int
get_scan_link_quality(wcard_t *card, float *quality, float *level, float *noise) {
int s;
uint8_t buf[24 * 1024];
struct ieee80211req ireq;
struct ieee80211req_scan_result *sr;
/* open a socket for ioctl's */
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return FALSE;
memset(&ireq, 0, sizeof(ireq));
strncpy(ireq.i_name, card->ifname, sizeof(ireq.i_name));
ireq.i_type = IEEE80211_IOC_SCAN_RESULTS;
ireq.i_data = buf;
ireq.i_len = sizeof(buf);
if (ioctl(s, SIOCG80211, &ireq) < 0) {
close(s);
return FALSE;
}
close(s);
if (ireq.i_len < sizeof(struct ieee80211req_scan_result))
return FALSE;
sr = (struct ieee80211req_scan_result *) buf;
*quality = sr->isr_intval;
*level = sr->isr_rssi;
*noise = sr->isr_noise;
return TRUE;
}
#endif /* defined(__FreeBSD__) && __FreeBSD_version >= 600034 */
#if !defined(__FreeBSD__) || __FreeBSD_version < 700052
static int
get_wi_link_quality(wcard_t *card, float *quality, float *level, float *noise) {
/* wireless info request struct */
struct wi_req wreq;
/* interface request struct */
struct ifreq ifr;
int s;
/* open a socket for ioctl's */
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return FALSE;
/* clean out the wreq struct */
memset(&wreq, 0, sizeof(wreq));
wreq.wi_len = WI_MAX_DATALEN;
/* we want to know the quality */
wreq.wi_type = WI_RID_COMMS_QUALITY;
memset(&ifr,0, sizeof(ifr));
/* ask information about a certain interface */
strcpy(ifr.ifr_name,card->ifname);
ifr.ifr_data = (caddr_t)&wreq;
/* get information about this interface */
if (ioctl(s, SIOCGWAVELAN, &ifr) == -1) {
close(s);
return FALSE;
}
close(s);
*quality = wreq.wi_val[0];
*level = wreq.wi_val[1];
*noise = wreq.wi_val[2];
return TRUE;
}
#endif /* !defined(__FreeBSD__) || __FreeBSD_version < 700052 */
#if !defined(__NetBSD__)
static int
get_an_link_quality(wcard_t *card, float *quality, float *level, float *noise) {
int nr,s;
struct ifreq ifr;
struct an_req areq;
struct an_sigcache *sc;
/* open a socket for ioctl's */
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return FALSE;
memset(&ifr,0, sizeof(ifr));
memset(&areq,0, sizeof(areq));
/* ask information about a certain interface */
strcpy(ifr.ifr_name,card->ifname);
areq.an_len = AN_MAX_DATALEN;
areq.an_type = AN_RID_READ_CACHE;
ifr.ifr_data = (caddr_t)&areq;
if (ioctl(s,SIOCGAIRONET,&ifr) == -1) {
close(s);
return FALSE;
}
close(s);
/* anval excists out of a integer which represents the number of sigcaches*/
/* followed by the sigcaches */
nr = (int) *areq.an_val; /* number of signal caches */
if (nr == 0) return FALSE;
/* we use the first sigcache for statistics, which seems to work ok for me */
sc = (struct an_sigcache *) ((char *) &areq.an_val + sizeof(int));
*quality = sc->quality;
*level = sc->signal;
*noise = sc->noise;
return TRUE;
}
#endif /* !defined(__NetBSD__) */
static int
get_link_quality(wcard_t *card, float *quality, float *level, float *noise) {
#if defined(__FreeBSD__) && __FreeBSD_version >= 600034
if (strncmp(card->ifname, "an", 2) == 0 && isnumber(card->ifname[2]))
return get_an_link_quality(card,quality,level,noise);
#if !defined(__FreeBSD__) || __FreeBSD_version < 700052
else if (strncmp(card->ifname, "wi", 2) == 0 && isnumber(card->ifname[2]))
return get_wi_link_quality(card,quality,level,noise);
#endif /* !defined(__FreeBSD__) || __FreeBSD_version < 700052 */
return get_scan_link_quality(card,quality,level,noise);
#else
switch (card->ifname[0]) {
#if !defined(__NetBSD__)
case 'a': /* an card */
return get_an_link_quality(card,quality,level,noise);
#endif /* !defined(__NetBSD__) */
case 'w': /* wi card */
return get_wi_link_quality(card,quality,level,noise);
}
return FALSE;
#endif
}
#endif
#ifdef __linux__
/* Linux Specific*/
#define WIRELESS "/proc/net/wireless"
static gint
find_wlancard(void) {
FILE *procfile;
int ret = FALSE;
char iface[5],procread[256], *c;
if ((procfile = fopen(WIRELESS,"r")) == NULL) return FALSE;
/* 2 lines header */
fgets(procread,sizeof(procread),procfile);
fgets(procread,sizeof(procread),procfile);
while(fgets(procread,sizeof(procread),procfile) != NULL) {
sscanf(procread,"%s: %*s %*f %*f %*f %*d %*d %*d",
iface);
c = strstr(iface,":");
*c = '\0';
if(found_wcard(iface) != NULL) ret = TRUE;
}
fclose(procfile);
return ret;
}
float get_next_fl(char **string) {
char *c;
float ret;
c = *string;
while (!isdigit(*c) && *c != '\0') c++;
ret = atof(*string);
while(!isspace(*c) && *c != '\0') c++;
*string = c;
return ret;
}
static int
get_link_quality(wcard_t *card, float *link, float *level, float *noise) {
FILE *procfile;
char procread[256], *c;
if ((procfile = fopen(WIRELESS,"r")) != NULL) {
/* 2 lines header */
fgets(procread,sizeof(procread),procfile);
fgets(procread,sizeof(procread),procfile);
/* search for our card */
while (fgets(procread,sizeof(procread),procfile) != NULL) {
c = procread;
while (isspace(*c)) c++;
if (!strncmp(c,card->ifname,strlen(card->ifname))) {
while (!isspace(*c) && *c != '\0') c++;
get_next_fl(&c); /* status thingie */
*link = get_next_fl(&c);
*level = get_next_fl(&c);
*noise = get_next_fl(&c);
fclose(procfile);
return TRUE;
}
}
fclose(procfile);
}
del_wcard(card);
return FALSE;
}
#endif
/* end of system specific code */
static gint
panel_expose_event(GtkWidget *widget, GdkEventExpose *event, GkrellmPanel *panel) {
gdk_draw_pixmap(widget->window,
widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
panel->pixmap, event->area.x, event->area.y, event->area.x, event->area.y,
event->area.width, event->area.height);
return FALSE;
}
static void
del_panel(GkrellmPanel *panel) {
if (panel == NULL) return;
gkrellm_destroy_decal_list(panel);
gkrellm_destroy_krell_list(panel);
gkrellm_panel_destroy(panel);
gkrellm_pack_side_frames();
}
static void
create_panel(GkrellmPanel **rpanel, GkrellmDecal **text, int fullscale,int create) {
GkrellmKrell *k;
GkrellmStyle *style;
GkrellmPiximage *krell_image;
int first_create = 0 ;
GkrellmPanel *panel = *rpanel;
if (panel == NULL) {
panel = gkrellm_panel_new0();
first_create = 1;
} else {
gkrellm_destroy_decal_list(panel);
gkrellm_destroy_krell_list(panel);
}
style = gkrellm_meter_style(panel_style_id);
krell_image = gkrellm_krell_meter_piximage(panel_style_id);
k = gkrellm_create_krell(panel, krell_image, style);
gkrellm_set_krell_full_scale(k, fullscale, 1);
panel->textstyle = gkrellm_meter_textstyle(panel_style_id);
*text = gkrellm_create_decal_text(panel,"0"
,panel->textstyle,style,-1,-1,-1);
gkrellm_panel_configure(panel,NULL,style);
gkrellm_panel_create(PanelBox, monitor, panel);
if (first_create || create) {
g_signal_connect(GTK_OBJECT(panel->drawing_area), "expose_event",
G_CALLBACK(panel_expose_event),panel);
}
gkrellm_draw_decal_text(panel,*text,"wireless",-10);
gkrellm_draw_panel_layers(panel);
gkrellm_pack_side_frames();
*rpanel = panel;
}
static void
reset_panel(int create) {
/* (re)create all the needed panels */
/* if create then gkrellm is newly build (theme switch or whatever) */
/* else it's called by the plugin itself (new card, changed config) */
wcard_t *card;
for (card = cards ; card != NULL ; card = card->next ) {
if (!(card->flags & ACTIVE) || !(card->flags & SHOW)) {
del_panel(card->level_panel); card->level_panel = NULL;
del_panel(card->link_panel); card->link_panel = NULL;
del_panel(card->noise_panel); card->noise_panel = NULL;
continue;
}
if (card->flags & SHOW_LINK)
create_panel(&(card->link_panel),&(card->link_text),LINKQ_MAX,create);
else { del_panel(card->link_panel); card->link_panel = NULL; }
if (card->flags & SHOW_LEVEL)
create_panel(&(card->level_panel),&(card->level_text),LEVEL_MAX,create);
else { del_panel(card->level_panel); card->level_panel = NULL; }
if (card->flags & SHOW_NOISE)
create_panel(&(card->noise_panel),&(card->noise_text),NOISE_MAX,create);
else { del_panel(card->noise_panel); card->noise_panel = NULL; }
}
}
static void
update_panel(GkrellmPanel *panel, GkrellmDecal *decal
, char *header , char *ifname,float amount) {
GkrellmKrell *k;
char text[50];
if (panel == NULL) return;
snprintf(text,sizeof(text),"%s: %.0f %s", ifname, amount, header);
k = KRELL(panel);
k->previous = 0;
gkrellm_update_krell(panel,k,amount);
gkrellm_draw_decal_text(panel,decal,text,amount);
gkrellm_draw_panel_layers(panel);
}
static void
update_plugin (void) {
wcard_t *card;
float link = 0, level = 0, noise =0;
if (GK.second_tick) {
for (card = cards; card != NULL; card = card->next) {
if (!(card->flags & ACTIVE) || !(card->flags & SHOW)) continue;
/* get_quality_info: get info in a system dependant way */
if (!get_link_quality(card,&link,&level,&noise)) return;
update_panel(card->level_panel,card->level_text
,"Level",card->ifname,level);
update_panel(card->link_panel,card->link_text
,"Link",card->ifname,link);
update_panel(card->noise_panel,card->noise_text
,"Noise",card->ifname,noise);
}
}
if (GK.two_second_tick) {
if (find_wlancard() == TRUE) {
reset_panel(0);
}
}
}
static void
create_plugin(GtkWidget *vbox, gint first_create) {
if (first_create) {
PanelBox = vbox;
/* first time it's created, only save panelbox here */
} else {
reset_panel(1); /* rebuild the panels */
}
}
static void
save_plugin_config (FILE *f) {
wcard_t *card;
for (card = cards; card != NULL ; card = card->next) {
fprintf(f,"%s %s %d\n",PLUGIN_CONFIG_KEYWORD,card->ifname,card->flags);
}
}
static void
load_plugin_config(gchar *arg) {
gchar ifname[10];
int flags;
if (sscanf(arg,"%s %d\n",ifname,&flags) == 2) {
new_wcard(ifname,0,flags);
}
}
/* the config panel stuff and callbacs */
static void
button_toggled(GtkWidget *widget, wcard_t *card, int value) {
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
card->cflags |= value;
else
card->cflags &= (~value);
}
static void
cb_show_button_toggled(GtkWidget *widget, wcard_t *card) {
button_toggled(widget,card,SHOW);
}
static void
cb_link_button_toggled(GtkWidget *widget, wcard_t *card) {
button_toggled(widget,card,SHOW_LINK);
}
static void
cb_level_button_toggled(GtkWidget *widget, wcard_t *card) {
button_toggled(widget,card,SHOW_LEVEL);
}
static void
cb_noise_button_toggled(GtkWidget *widget, wcard_t *card) {
button_toggled(widget,card,SHOW_NOISE);
}
static void
create_toggle_button(char *name,int active, wcard_t *card,
GtkWidget *box, GtkSignalFunc func) {
GtkWidget *button;
button = gtk_check_button_new_with_label(name);
gtk_box_pack_start(GTK_BOX(box),button,FALSE,TRUE,3);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),active);
g_signal_connect(G_OBJECT(button),"toggled",
G_CALLBACK(func),card);
}
static void
create_device_tab(GtkWidget *notebook,wcard_t *card) {
GtkWidget *label;
GtkWidget *frame;
GtkWidget *vbox;
GtkWidget *separator;
label = gtk_label_new(card->ifname);
frame = gtk_frame_new(NULL);
gtk_notebook_append_page(GTK_NOTEBOOK(notebook),frame,label);
vbox = gtk_vbox_new(FALSE,0);
gtk_container_add(GTK_CONTAINER(frame),vbox);
create_toggle_button("Show this Interface" ,card->flags & SHOW, card,
vbox, (GtkSignalFunc) cb_show_button_toggled);
separator = gtk_hseparator_new();
gtk_box_pack_start(GTK_BOX(vbox),separator,FALSE,TRUE,3);
create_toggle_button("Show link quality" ,card->flags & SHOW_LINK, card,
vbox, (GtkSignalFunc) cb_link_button_toggled);
create_toggle_button("Show signal level" ,card->flags & SHOW_LEVEL, card,
vbox, (GtkSignalFunc) cb_level_button_toggled);
create_toggle_button("Show noise level" ,card->flags & SHOW_NOISE, card,
vbox, (GtkSignalFunc) cb_noise_button_toggled);
}
static void
create_devices_tab(GtkWidget *notebook) {
wcard_t *card;
for (card = cards ; card != NULL; card = card->next) {
create_device_tab(notebook, card);
card->cflags = card->flags;
}
}
static void
create_help_text(GtkWidget *text) {
gchar *info_text[] =
{
"<b>This plugin allows you to monitor the quality of a wireless lan card \n",
"<b>\nConfiguration:\n",
"Every detected wireless interface will have ",
"one config tab with the following options:\n",
"<b>\tShow this interface: ",
"\n\tShow information about the interface\n",
"<b>\tShow link quality:\n",
"\tShow the link quality of this interface\n",
"<b>\tShow signal level:\n",
"\tShow the signal level of this interface\n",
"<b>\tShow noise level:\n",
"\tShow the noise level of this interface\n"
};
gkrellm_gtk_text_view_append_strings(text,info_text,
sizeof(info_text)/sizeof(gchar *));
}
static void
create_info_tab(GtkWidget *notebook) {
GtkWidget *frame;
GtkWidget *scrolled;
GtkWidget *text;
GtkWidget *page;
frame = gtk_frame_new(NULL);
gtk_container_border_width(GTK_CONTAINER(frame),3);
scrolled = gtk_scrolled_window_new(NULL,NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
gtk_container_add(GTK_CONTAINER(frame),scrolled);
page = gkrellm_gtk_notebook_page(notebook,"Info");
text = gkrellm_gtk_scrolled_text_view(page,NULL,
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
create_help_text(text);
}
static void
create_about_tab(GtkWidget *notebook) {
gchar *plugin_about_text;
GtkWidget *text;
GtkWidget *label;
plugin_about_text = g_strdup_printf(
"GkrellMWireless %d.%d%s\n" \
"GKrellM Wireless Plugin\n\n" \
"Copyright (C) 2000-2001 Sjoerd Simons\n" \
"sjoerd@luon.net\n" \
"http://gkrellm.luon.net \n\n" \
"Released under the GNU Public Licence",
WIRELESS_MAJOR_VERSION,WIRELESS_MINOR_VERSION,WIRELESS_EXTRA_VERSION);
text = gtk_label_new(plugin_about_text);
label = gtk_label_new("About");
gtk_notebook_append_page(GTK_NOTEBOOK(notebook),text,label);
g_free(plugin_about_text);
}
static void
create_plugin_config(GtkWidget *tab) {
GtkWidget *notebook;
notebook = gtk_notebook_new();
gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook),GTK_POS_TOP);
gtk_box_pack_start(GTK_BOX(tab),notebook,TRUE,TRUE,0);
create_devices_tab(notebook);
create_info_tab(notebook);
create_about_tab(notebook);
}
static void
apply_plugin_config(void) {
wcard_t *card;
for (card = cards ; card != NULL ; card = card->next) {
card->flags = card->cflags;
}
reset_panel(0);
}
/* end of config panel stuff */
static GkrellmMonitor wireless_meter = {
"Wireless", /* Name, for config tab. */
0, /* Id, 0 if a plugin */
create_plugin, /* The create function */
update_plugin, /* The update function */
create_plugin_config, /* The config tab create function */
apply_plugin_config, /* Apply the config function */
save_plugin_config, /* Save user config */
load_plugin_config, /* Load user config */
PLUGIN_CONFIG_KEYWORD, /* config keyword */
NULL, /* Undefined 2 */
NULL, /* Undefined 1 */
NULL, /* Undefined 0 */
MON_FS, /* Insert plugin before this monitor. Choose */
/* MON_CLOCK, MON_CPU, MON_PROC, MON_DISK, */
/* MON_INET, MON_NET, MON_FS, MON_MAIL, */
/* MON_APM, or MON_UPTIME */
/* Choose wisely, others have to live with it. */
NULL, /* Handle if a plugin, filled in by GKrellM */
NULL /* path if a plugin, filled in by GKrellM */
};
/* All GKrellM plugins must have one global routine named init_plugin()
| which returns a pointer to a filled in monitor structure.
*/
GkrellmMonitor *
gkrellm_init_plugin(void) {
panel_style_id = gkrellm_add_meter_style(&wireless_meter,"wireless");
monitor = &wireless_meter;
return monitor;
}
syntax highlighted by Code2HTML, v. 0.9.1