/* * menuitem.c * This file is part of LCDd, the lcdproc server. * * This file is released under the GNU General Public License. Refer to the * COPYING file distributed with this package. * * Copyright (c) 1999, William Ferrell, Scott Scriven * 2002, Joris Robijn * 2004, F5 Networks, Inc. - IP-address input * 2005, Peter Marschall - error checks, ... * * * Handles a menuitem and all actions that can be performed on it. */ #include #include #include #include #include "shared/report.h" #include "menuitem.h" #include "menuscreens.h" #include "menu.h" #include "drivers.h" /* this is needed for verify_ipv4 and verify_ipv6. */ #include "sock.h" #define MAX_NUMERIC_LEN 40 char *error_strs[] = {"", "Out of range", "Too long", "Too short", "Invalid Address"}; char *menuitemtypenames[] = {"menu", "action", "checkbox", "ring", "slider", "numeric", "alpha", "ip"}; char *menueventtypenames[] = {"select", "update", "plus", "minus", "enter", "leave"}; void menuitem_destroy_action (MenuItem *item); void menuitem_destroy_checkbox (MenuItem *item); void menuitem_destroy_ring (MenuItem *item); void menuitem_destroy_slider (MenuItem *item); void menuitem_destroy_numeric (MenuItem *item); void menuitem_destroy_alpha (MenuItem *item); void menuitem_destroy_ip (MenuItem *item); void menuitem_reset_numeric (MenuItem *item); void menuitem_reset_alpha (MenuItem *item); void menuitem_reset_ip (MenuItem *item); void menuitem_rebuild_screen_slider (MenuItem *item, Screen *s); void menuitem_rebuild_screen_numeric (MenuItem *item, Screen *s); void menuitem_rebuild_screen_alpha (MenuItem *item, Screen *s); void menuitem_rebuild_screen_ip (MenuItem *item, Screen *s); void menuitem_update_screen_slider (MenuItem *item, Screen *s); void menuitem_update_screen_numeric (MenuItem *item, Screen *s); void menuitem_update_screen_alpha (MenuItem *item, Screen *s); void menuitem_update_screen_ip (MenuItem *item, Screen *s); MenuResult menuitem_process_input_slider (MenuItem *item, MenuToken token, char * key, bool extended); MenuResult menuitem_process_input_numeric (MenuItem *item, MenuToken token, char * key, bool extended); MenuResult menuitem_process_input_alpha (MenuItem *item, MenuToken token, char * key, bool extended); MenuResult menuitem_process_input_ip (MenuItem *item, MenuToken token, char * key, bool extended); /* information about string representation of IP addresses */ typedef struct { int maxlen; // max length of the string represenation; char sep; // separators between numeric strings int base; // numeric base int width; // width of each numeric string int limit; // upper limit of each numeric string int posValue[5]; // digit-value of the digits at the approp. position in the numeric string char format[5]; // printf-format string to print a numeric string int (*verify)(const char *); // verify function: returns 1 = OK, 0 = error char dummy[16]; // dummy value (if provided value is no IP address) } IpSstringProperties; const IpSstringProperties IPinfo[] = { // IPv4: 15 char long '.'-separated sequence of 3-digit decimal strings in range 000 - 255 { 15, '.', 10, 3, 255, { 100, 10, 1, 0, 0 }, "%03d", verify_ipv4, "0.0.0.0" }, // IPv6: 39 char long ':'-separated sequence of 4-digit hex strings in range 0000 - ffff { 39, ':', 16, 4, 65535, { 4096, 256, 16, 1, 0 }, "%04x", verify_ipv6, "0:0:0:0:0:0:0:0" } }; /******** MENU UTILITY FUNCTIONS ********/ /** returns default_result if predecessor_id is NULL or the respective value * otherwise. */ MenuResult menuitem_predecessor2menuresult(char *predecessor_id, MenuResult default_result) { if (predecessor_id == NULL) return default_result; if (strcmp("_quit_", predecessor_id) == 0) return MENURESULT_QUIT; else if (strcmp("_close_", predecessor_id) == 0) return MENURESULT_CLOSE; else if (strcmp("_none_", predecessor_id) == 0) return MENURESULT_NONE; else return MENURESULT_PREDECESSOR; } /** returns default_result if successor_id is NULL or the respective value * otherwise. */ MenuResult menuitem_successor2menuresult(char *successor_id, MenuResult default_result) { if (successor_id == NULL) return default_result; if (strcmp("_quit_", successor_id) == 0) return MENURESULT_QUIT; else if (strcmp("_close_", successor_id) == 0) return MENURESULT_CLOSE; else if (strcmp("_none_", successor_id) == 0) return MENURESULT_NONE; else return MENURESULT_SUCCESSOR; } /** Returns the MenuItem with the specified id if found or NULL. The search * for itemid is restricted to the client's menus if the preprocessor macro * LCDPROC_PERMISSIVE_MENU_GOTO is *not* set. */ MenuItem *menuitem_search(char *menu_id, Client *client) { # ifdef LCDPROC_PERMISSIVE_MENU_GOTO MenuItem *top = main_menu; # else MenuItem *top = client->menu; # endif /* LCDPROC_PERMISSIVE_MENU_GOTO */ return menu_find_item (top, menu_id, true); } /******** FUNCTION TABLES ********/ /* Tables with functions to call for all different item types */ void (*destructor_table[NUM_ITEMTYPES] ) (MenuItem *item) = { menu_destroy, NULL, NULL, menuitem_destroy_ring, menuitem_destroy_slider, menuitem_destroy_numeric, menuitem_destroy_alpha, menuitem_destroy_ip }; void (*reset_table[NUM_ITEMTYPES] ) (MenuItem *item) = { menu_reset, NULL, NULL, NULL, NULL, menuitem_reset_numeric, menuitem_reset_alpha, menuitem_reset_ip }; void (*build_screen_table[NUM_ITEMTYPES] ) (MenuItem *item, Screen *s) = { menu_build_screen, NULL, NULL, NULL, menuitem_rebuild_screen_slider, menuitem_rebuild_screen_numeric, menuitem_rebuild_screen_alpha, menuitem_rebuild_screen_ip }; void (*update_screen_table[NUM_ITEMTYPES] ) (MenuItem *item, Screen *s) = { menu_update_screen, NULL, NULL, NULL, menuitem_update_screen_slider, menuitem_update_screen_numeric, menuitem_update_screen_alpha, menuitem_update_screen_ip }; MenuResult (*process_input_table[NUM_ITEMTYPES] ) (MenuItem *item, MenuToken token, char *key, bool extended) = { menu_process_input, NULL, NULL, NULL, menuitem_process_input_slider, menuitem_process_input_numeric, menuitem_process_input_alpha, menuitem_process_input_ip }; /******** METHODS ********/ MenuItem *menuitem_create (MenuItemType type, char *id, MenuEventFunc(*event_func), char *text, Client *client) { MenuItem *new_item; debug (RPT_DEBUG, "%s( type=%d, id=\"%s\", event_func=%p, text=\"%s\" )", __FUNCTION__, type, id, event_func, text); if ((id == NULL) || (text == NULL)) { // report (RPT_ERR, "%s: illegal id or text", __FUNCTION__); return NULL; } /* Allocate space and fill struct */ new_item = malloc (sizeof(MenuItem)); if (!new_item) { report (RPT_ERR, "%s: Could not allocate memory", __FUNCTION__); return NULL; } new_item->type = type; new_item->id = strdup (id); if (!new_item->id) { report (RPT_ERR, "%s: Could not allocate memory", __FUNCTION__); free (new_item); return NULL; } new_item->successor_id = NULL; new_item->predecessor_id = NULL; new_item->parent = NULL; new_item->event_func = event_func; new_item->text = strdup (text); if (!new_item->text) { report (RPT_ERR, "%s: Could not allocate memory", __FUNCTION__); free (new_item->id); free (new_item); return NULL; } new_item->client = client; new_item->is_hidden = false; /* Clear the type specific data part */ memset ( &(new_item->data), '\0', sizeof(new_item->data)); return new_item; } // fixme: the menu_result arg is obsoleted (use char* successor_id) MenuItem *menuitem_create_action (char *id, MenuEventFunc(*event_func), char *text, Client *client, MenuResult menu_result) { MenuItem *new_item; debug (RPT_DEBUG, "%s( id=[%s], event_func=%p, text=\"%s\", close_menu=%d )", __FUNCTION__, id, event_func, text, menu_result); new_item = menuitem_create (MENUITEM_ACTION, id, event_func, text, client); if (new_item != NULL) { switch (menu_result) { case MENURESULT_NONE: new_item->successor_id = strdup("_none_"); break; case MENURESULT_CLOSE: new_item->successor_id = strdup("_close_"); break; case MENURESULT_QUIT: new_item->successor_id = strdup("_quit_"); break; default: assert(!"unexpected MENURESULT"); } } return new_item; } MenuItem *menuitem_create_checkbox (char *id, MenuEventFunc(*event_func), char *text, Client *client, bool allow_gray, bool value) { MenuItem *new_item; debug (RPT_DEBUG, "%s( id=[%s], event_func=%p, text=\"%s\", allow_gray=%d, value=%d )", __FUNCTION__, id, event_func, text, allow_gray, value); new_item = menuitem_create (MENUITEM_CHECKBOX, id, event_func, text, client); if (new_item != NULL) { new_item->data.checkbox.allow_gray = allow_gray; new_item->data.checkbox.value = value; } return new_item; } MenuItem *menuitem_create_ring (char *id, MenuEventFunc(*event_func), char *text, Client *client, char *strings, short value) { MenuItem *new_item; debug (RPT_DEBUG, "%s( id=[%s], event_func=%p, text=\"%s\", strings=\"%s\", value=%d )", __FUNCTION__, id, event_func, text, strings, value); new_item = menuitem_create (MENUITEM_RING, id, event_func, text, client); if (new_item != NULL) { new_item->data.ring.strings = tablist2linkedlist (strings); new_item->data.ring.value = value; } return new_item; } MenuItem *menuitem_create_slider (char *id, MenuEventFunc(*event_func), char *text, Client *client, char *mintext, char *maxtext, int minvalue, int maxvalue, int stepsize, int value) { MenuItem *new_item; debug (RPT_DEBUG, "%s( id=[%s], event_func=%p, text=\"%s\", mintext=\"%s\", maxtext=\"%s\", minvalue=%d, maxvalue=%d, stepsize=%d, value=%d )", __FUNCTION__, id, event_func, text, mintext, maxtext, minvalue, maxvalue, stepsize, value); new_item = menuitem_create (MENUITEM_SLIDER, id, event_func, text, client); if (new_item != NULL) { new_item->data.slider.mintext = strdup (mintext); new_item->data.slider.maxtext = strdup (maxtext); new_item->data.slider.minvalue = minvalue; new_item->data.slider.maxvalue = maxvalue; new_item->data.slider.stepsize = stepsize; new_item->data.slider.value = value; } return new_item; } MenuItem *menuitem_create_numeric (char *id, MenuEventFunc(*event_func), char *text, Client *client, int minvalue, int maxvalue, int value) { MenuItem *new_item; debug (RPT_DEBUG, "%s( id=[%s], event_func=%p, text=\"%s\", minvalue=%d, maxvalue=%d, value=%d )", __FUNCTION__, id, event_func, text, minvalue, minvalue, value); new_item = menuitem_create (MENUITEM_NUMERIC, id, event_func, text, client); if (new_item != NULL) { new_item->data.numeric.maxvalue = maxvalue; new_item->data.numeric.minvalue = minvalue; new_item->data.numeric.edit_str = malloc (MAX_NUMERIC_LEN); new_item->data.numeric.value = value; } return new_item; } MenuItem *menuitem_create_alpha (char *id, MenuEventFunc(*event_func), char *text, Client *client, char password_char, short minlength, short maxlength, bool allow_caps, bool allow_noncaps, bool allow_numbers, char *allowed_extra, char *value) { MenuItem *new_item; debug (RPT_DEBUG, "%s( id=\"%s\", event_func=%p, text=\"%s\", password_char=%d, maxlength=%d, value=\"%s\" )", __FUNCTION__, id, event_func, text, password_char, maxlength, value); new_item = menuitem_create (MENUITEM_ALPHA, id, event_func, text, client); if (new_item != NULL) { new_item->data.alpha.password_char = password_char; new_item->data.alpha.minlength = minlength; new_item->data.alpha.maxlength = maxlength; new_item->data.alpha.allow_caps = allow_caps; new_item->data.alpha.allow_noncaps = allow_noncaps; new_item->data.alpha.allow_numbers = allow_numbers; new_item->data.alpha.allowed_extra = strdup (allowed_extra); new_item->data.alpha.value = malloc (maxlength + 1); if (new_item->data.alpha.value != NULL) { strncpy (new_item->data.alpha.value, value, maxlength); new_item->data.alpha.value[maxlength] = 0; } new_item->data.alpha.edit_str = malloc (maxlength + 1); } return new_item; } MenuItem *menuitem_create_ip (char *id, MenuEventFunc(*event_func), char *text, Client *client, bool v6, char *value) { MenuItem *new_item; const IpSstringProperties *ipinfo; debug (RPT_DEBUG, "%s( id=\"%s\", event_func=%p, text=\"%s\", v6=%d, value=\"%s\" )", __FUNCTION__, id, event_func, text, v6, value); new_item = menuitem_create (MENUITEM_IP, id, event_func, text, client); new_item->data.ip.v6 = v6; ipinfo = (v6) ? &IPinfo[1] : &IPinfo[0]; new_item->data.ip.maxlength = ipinfo->maxlen;; new_item->data.ip.value = malloc (new_item->data.ip.maxlength + 1); strncpy(new_item->data.ip.value, value, new_item->data.ip.maxlength); new_item->data.ip.value[new_item->data.ip.maxlength] = '\0'; if (ipinfo->verify != NULL) { char *start = new_item->data.ip.value; while (start != NULL) { char *skip = start; while ((*skip == ' ') || ((*skip == '0') && (skip[1] != ipinfo->sep) && (skip[1] != '\0'))) skip++; memccpy(start, skip, '\0', new_item->data.ip.maxlength + 1); skip = strchr(start, ipinfo->sep); start = (skip != NULL) ? (skip + 1) : NULL; } if (!ipinfo->verify(new_item->data.ip.value)) { report(RPT_WARNING, "%s(id=\"%s\") ip address not verified: \"%s\"", __FUNCTION__, id, value); strncpy(new_item->data.ip.value, ipinfo->dummy, new_item->data.ip.maxlength); new_item->data.ip.value[new_item->data.ip.maxlength] = '\0'; } } new_item->data.ip.edit_str = malloc (new_item->data.ip.maxlength + 1); return new_item; } void menuitem_destroy (MenuItem *item) { debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); if (item != NULL) { void (*destructor) (MenuItem *); /* First destroy type specific data */ destructor = destructor_table[item->type]; if (destructor) destructor (item); /* Following strings should always be allocated */ free (item->text); free (item->id); /* And finally...*/ free (item); } } void menuitem_destroy_ring (MenuItem *item) { debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); if (item != NULL) { char * s; /* deallocate the strings */ for (s = LL_GetFirst(item->data.ring.strings); s != NULL; s = LL_GetNext(item->data.ring.strings)) { free (s); } /* and the list */ LL_Destroy (item->data.ring.strings); } } void menuitem_destroy_slider (MenuItem *item) { debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); if (item != NULL) { /* These strings should always be allocated */ free (item->data.slider.mintext); free (item->data.slider.maxtext); } } void menuitem_destroy_numeric (MenuItem *item) { debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); if (item != NULL) { /* This string should always be allocated */ free (item->data.numeric.edit_str); } } void menuitem_destroy_alpha (MenuItem *item) { debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); if (item != NULL) { /* These strings should always be allocated */ free (item->data.alpha.allowed_extra); free (item->data.alpha.value); free (item->data.alpha.edit_str); } } void menuitem_destroy_ip (MenuItem *item) { debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); /* These strings should always be allocated */ free (item->data.ip.value); free (item->data.ip.edit_str); } /******** MENU ITEM RESET FUNCTIONS ********/ void menuitem_reset (MenuItem *item) { debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); if (item != NULL) { void (*func) (MenuItem *); /* First destroy type specific data */ func = reset_table[item->type]; if (func) func (item); } } void menuitem_reset_numeric (MenuItem *item) { debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); if (item != NULL) { item->data.numeric.edit_pos = 0; item->data.numeric.edit_offs = 0; memset (item->data.numeric.edit_str, '\0', MAX_NUMERIC_LEN); if (item->data.numeric.minvalue < 0) { snprintf (item->data.numeric.edit_str, MAX_NUMERIC_LEN, "%+d", item->data.numeric.value); } else { snprintf (item->data.numeric.edit_str, MAX_NUMERIC_LEN, "%d", item->data.numeric.value); } } } void menuitem_reset_alpha (MenuItem *item) { debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); if (item != NULL) { item->data.alpha.edit_pos = 0; item->data.alpha.edit_offs = 0; memset (item->data.alpha.edit_str, '\0', item->data.alpha.maxlength+1); strcpy (item->data.alpha.edit_str, item->data.alpha.value); } } void menuitem_reset_ip (MenuItem *item) { char *start = item->data.ip.value; const IpSstringProperties *ipinfo = (item->data.ip.v6) ? &IPinfo[1] : &IPinfo[0]; debug (RPT_DEBUG, "%s( item=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)")); item->data.ip.edit_pos = 0; item->data.ip.edit_offs = 0; memset (item->data.ip.edit_str, '\0', item->data.ip.maxlength+1); // normalize IP address string to e.g. 010.002.250.002 / 0001:0203:0405:0607:0809:0a0b:0c0d:0e0f while (start != NULL) { char *end; char tmpstr[5]; int num = (int) strtol(start, (char **) NULL, ipinfo->base); snprintf(tmpstr, 5, ipinfo->format, num); strcat(item->data.ip.edit_str, tmpstr); end = strchr(start, ipinfo->sep); start = (end != NULL) ? (end + 1) : NULL; if (start != NULL) { tmpstr[0] = ipinfo->sep; tmpstr[1] = '\0'; strcat(item->data.ip.edit_str, tmpstr); } } } /******** MENU SCREEN BUILD FUNCTIONS ********/ void menuitem_rebuild_screen (MenuItem *item, Screen *s) { Widget * w; void (*build_screen) (MenuItem *item, Screen *s); debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if (!display_props) { /* Nothing to build if no display size is known */ report (RPT_ERR, "%s: display size unknown", __FUNCTION__ ); return; } if (s != NULL) { /* First remove all widgets from the screen */ while ( (w = screen_getfirst_widget(s)) != NULL) { /* We know these widgets don't have subwidgets, so we can * easily remove them */ screen_remove_widget (s, w); widget_destroy (w); } if (item != NULL) { /* Call type specific screen building function */ build_screen = build_screen_table [item->type]; if (build_screen) { build_screen (item, s); } else { report (RPT_ERR, "%s: given menuitem cannot be active", __FUNCTION__); return; } /* Also always call update_screen */ menuitem_update_screen (item, s); } } } void menuitem_rebuild_screen_slider (MenuItem *item, Screen *s) { Widget * w; debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if ((item == NULL) || (s == NULL)) return; if (display_props->height >= 2 ) { /* Only add a title if enough space... */ w = widget_create ("text", WID_STRING, s); screen_add_widget (s, w); w->text = strdup(item->text); w->x = 1; w->y = 1; } w = widget_create ("bar", WID_HBAR, s); screen_add_widget (s, w); w->width = display_props->width; if (display_props->height > 2 ) { /* This is option 1: we have enought space, so the bar and * min/max texts can be on separate lines. */ w->x = 2; w->y = display_props->height / 2 + 1; w->width = display_props->width - 2; } w = widget_create ("min", WID_STRING, s); screen_add_widget (s, w); w->text = NULL; w->x = 1; if (display_props->height > 2 ) { w->y = display_props->height / 2 + 2; } else { w->y = display_props->height / 2 + 1; } w = widget_create ("max", WID_STRING, s); screen_add_widget (s, w); w->text = NULL; w->x = 1; if (display_props->height > 2 ) { w->y = display_props->height / 2 + 2; } else { w->y = display_props->height / 2 + 1; } } void menuitem_rebuild_screen_numeric (MenuItem *item, Screen *s) { Widget * w; debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if ((item == NULL) || (s == NULL)) return; if (display_props->height >= 2 ) { /* Only add a title if enough space... */ w = widget_create ("text", WID_STRING, s); screen_add_widget (s, w); w->text = strdup(item->text); w->x = 1; w->y = 1; } w = widget_create ("value", WID_STRING, s); screen_add_widget (s, w); w->text = malloc (MAX_NUMERIC_LEN); w->x = 2; w->y = display_props->height / 2 + 1; /* Only display error string if enough space... */ if (display_props->height > 2 ) { w = widget_create ("error", WID_STRING, s); screen_add_widget (s, w); w->text = strdup(""); w->x = 1; w->y = display_props->height; } } void menuitem_rebuild_screen_alpha (MenuItem *item, Screen *s) { Widget * w; debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if ((item == NULL) || (s == NULL)) return; if (display_props->height >= 2 ) { /* Only add a title if enough space... */ w = widget_create ("text", WID_STRING, s); screen_add_widget (s, w); w->text = strdup(item->text); w->x = 1; w->y = 1; } w = widget_create ("value", WID_STRING, s); screen_add_widget (s, w); w->text = malloc (item->data.alpha.maxlength+1); w->x = 2; w->y = display_props->height / 2 + 1; /* Only display error string if enough space... */ if (display_props->height > 2 ) { w = widget_create ("error", WID_STRING, s); screen_add_widget (s, w); w->text = strdup(""); w->x = 1; w->y = display_props->height; } } void menuitem_rebuild_screen_ip (MenuItem *item, Screen *s) { Widget * w; debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if ((item == NULL) || (s == NULL)) return; if (display_props->height >= 2 ) { /* Only add a title if enough space... */ w = widget_create ("text", WID_STRING, s); screen_add_widget (s, w); w->text = strdup(item->text); w->x = 1; w->y = 1; } w = widget_create ("value", WID_STRING, s); screen_add_widget (s, w); w->text = malloc (item->data.ip.maxlength+1); w->x = 2; w->y = display_props->height / 2 + 1; /* Only display error string if enough space... */ if (display_props->height > 2 ) { w = widget_create ("error", WID_STRING, s); screen_add_widget (s, w); w->text = strdup(""); w->x = 1; w->y = display_props->height; } } /******** MENU SCREEN UPDATE FUNCTIONS ********/ void menuitem_update_screen (MenuItem *item, Screen *s) { void (*update_screen) (MenuItem *item, Screen *s); debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if ((item == NULL) || (s == NULL)) return; /* Disable the cursor by default */ s->cursor = CURSOR_OFF; /* Call type specific screen building function */ update_screen = update_screen_table [item->type]; if (update_screen) { update_screen (item, s); } else { report (RPT_ERR, "%s: given menuitem cannot be active", __FUNCTION__); return; } } void menuitem_update_screen_slider (MenuItem *item, Screen *s) { Widget * w; int min_len, max_len; debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if ((item == NULL) || (s == NULL)) return; /* Calculate the bar position and length by filling buffers */ min_len = strlen (item->data.slider.mintext); max_len = strlen (item->data.slider.maxtext); /* And adjust the data */ w = screen_find_widget (s, "bar"); if (display_props->height <= 2 ) { /* This is option 2: we're tight on lines, so we put the bar * and min/max texts on the same line. */ w->x = 1 + min_len; w->y = display_props->height; w->width = display_props->width - min_len - max_len; } /* FUTURE: w->promille = 1000 * (item->data.slider.value - item->data.slider.minvalue) / (item->data.slider.maxvalue - item->data.slider.minvalue) */; w->length = w->width * display_props->cellwidth * (item->data.slider.value - item->data.slider.minvalue) / (item->data.slider.maxvalue - item->data.slider.minvalue); w = screen_find_widget (s, "min"); if (w->text) free (w->text); w->text = strdup(item->data.slider.mintext); w = screen_find_widget (s, "max"); if (w->text) free (w->text); w->x = 1 + display_props->width - max_len; w->text = strdup(item->data.slider.maxtext); } void menuitem_update_screen_numeric (MenuItem *item, Screen *s) { Widget * w; debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if ((item == NULL) || (s == NULL)) return; w = screen_find_widget (s, "value"); strcpy (w->text, item->data.numeric.edit_str + item->data.numeric.edit_offs); s->cursor = CURSOR_DEFAULT_ON; s->cursor_x = w->x + item->data.numeric.edit_pos - item->data.numeric.edit_offs; s->cursor_y = w->y; /* Only display error string if enough space... */ if (display_props->height > 2 ) { w = screen_find_widget (s, "error"); free (w->text); w->text = strdup (error_strs[item->data.numeric.error_code]); } } void menuitem_update_screen_alpha (MenuItem *item, Screen *s) { Widget * w; debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if ((item == NULL) || (s == NULL)) return; w = screen_find_widget (s, "value"); if (item->data.alpha.password_char == '\0') { strcpy (w->text, item->data.alpha.edit_str + item->data.alpha.edit_offs); } else { int len = strlen(item->data.alpha.edit_str) - item->data.alpha.edit_offs; memset (w->text, item->data.alpha.password_char, len); w->text[len] = '\0'; } s->cursor = CURSOR_DEFAULT_ON; s->cursor_x = w->x + item->data.alpha.edit_pos - item->data.alpha.edit_offs; s->cursor_y = w->y; /* Only display error string if enough space... */ if (display_props->height > 2 ) { w = screen_find_widget (s, "error"); free (w->text); w->text = strdup (error_strs[item->data.alpha.error_code]); } } void menuitem_update_screen_ip (MenuItem *item, Screen *s) { Widget * w; debug (RPT_DEBUG, "%s( item=[%s], screen=[%s] )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), ((s != NULL) ? s->id : "(null)")); if ((item == NULL) || (s == NULL)) return; w = screen_find_widget (s, "value"); if (w != NULL) strcpy (w->text, item->data.ip.edit_str + item->data.ip.edit_offs); s->cursor = CURSOR_DEFAULT_ON; s->cursor_x = w->x + item->data.ip.edit_pos - item->data.ip.edit_offs; s->cursor_y = w->y; /* Only display error string if enough space... */ if (display_props->height > 2 ) { w = screen_find_widget (s, "error"); free (w->text); w->text = strdup (error_strs[item->data.ip.error_code]); } } /******** MENU SCREEN INPUT HANDLING FUNCTIONS ********/ MenuResult menuitem_process_input (MenuItem *item, MenuToken token, char * key, bool extended) { MenuResult (*process_input) (MenuItem *item, MenuToken token, char * key, bool extended); debug (RPT_DEBUG, "%s( item=[%s], token=%d, key=\"%s\" )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), token, key); if (item == NULL) return MENURESULT_ERROR; /* Call type specific screen building function */ process_input = process_input_table [item->type]; if (process_input ) { return process_input (item, token, key, extended); } else { report (RPT_ERR, "%s: given menuitem cannot be active", __FUNCTION__); return MENURESULT_ERROR; } } MenuResult menuitem_process_input_slider (MenuItem *item, MenuToken token, char * key, bool extended) { debug (RPT_DEBUG, "%s( item=[%s], token=%d, key=\"%s\" )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), token, key); if (item == NULL) return MENURESULT_ERROR; switch (token) { case MENUTOKEN_MENU: return menuitem_predecessor2menuresult( item->predecessor_id, MENURESULT_CLOSE); case MENUTOKEN_ENTER: return menuitem_successor2menuresult( item->successor_id, MENURESULT_CLOSE); case MENUTOKEN_UP: case MENUTOKEN_RIGHT: item->data.slider.value = min (item->data.slider.maxvalue, item->data.slider.value + item->data.slider.stepsize); if (item->event_func) item->event_func (item, MENUEVENT_PLUS); return MENURESULT_NONE; case MENUTOKEN_DOWN: case MENUTOKEN_LEFT: item->data.slider.value = max (item->data.slider.minvalue, item->data.slider.value - item->data.slider.stepsize); if (item->event_func) item->event_func (item, MENUEVENT_MINUS); return MENURESULT_NONE; case MENUTOKEN_OTHER: default: break; } return MENURESULT_ERROR; } MenuResult menuitem_process_input_numeric (MenuItem *item, MenuToken token, char * key, bool extended) { char buf1[MAX_NUMERIC_LEN]; char buf2[MAX_NUMERIC_LEN]; int max_len; debug (RPT_DEBUG, "%s( item=[%s], token=%d, key=\"%s\" )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), token, key); if (item != NULL) { /* To make life easy... */ char *str = item->data.numeric.edit_str; int pos = item->data.numeric.edit_pos; int allow_signed = (item->data.numeric.minvalue < 0); char *format_str = (allow_signed) ? "%+d" : "%d"; snprintf (buf1, MAX_NUMERIC_LEN, format_str, item->data.numeric.minvalue); snprintf (buf2, MAX_NUMERIC_LEN, format_str, item->data.numeric.maxvalue); max_len = max (strlen(buf1), strlen(buf2)); /* Clear the error */ item->data.numeric.error_code = 0; switch (token) { case MENUTOKEN_MENU: if (pos == 0) { return menuitem_predecessor2menuresult( item->predecessor_id, MENURESULT_CLOSE); } else { /* Reset data */ menuitem_reset_numeric(item); } return MENURESULT_NONE; case MENUTOKEN_ENTER: if ((extended) || (str[pos] == '\0')) { int value; /* The user completed his input */ /* ...scan it */ if (sscanf (str, "%d", &value) != 1) { return MENURESULT_ERROR; } /* Test the value */ if (value < item->data.numeric.minvalue || value > item->data.numeric.maxvalue) { /* Out of range ! * We can't exit this screen now */ item->data.numeric.error_code = 1; item->data.numeric.edit_pos = 0; item->data.numeric.edit_offs = 0; return MENURESULT_NONE; } /* OK, store value */ item->data.numeric.value = value; /* Inform client */ if (item->event_func) item->event_func (item, MENUEVENT_UPDATE); return menuitem_successor2menuresult( item->successor_id, MENURESULT_CLOSE); } else { /* The user wants to go to next digit */ if (pos < max_len) { item->data.numeric.edit_pos++; if (pos >= display_props->width - 2) item->data.numeric.edit_offs++; } } return MENURESULT_NONE; case MENUTOKEN_UP: if (pos >= max_len) { /* We're not allowed to add anything anymore */ item->data.numeric.error_code = 2; item->data.numeric.edit_pos = 0; item->data.numeric.edit_offs = 0; return MENURESULT_NONE; } if (allow_signed && pos == 0) { /* make negative */ str[0] = (str[0] == '-') ? '+' : '-'; } else { if (str[pos] >= '0' && str[pos] < '9') { str[pos] ++; } else if (str[pos] == '9') { str[pos] = '\0'; } else if (str[pos] == '\0') { str[pos] = '0'; } } return MENURESULT_NONE; case MENUTOKEN_DOWN: if (pos >= max_len) { /* We're not allowed to add anything anymore */ item->data.numeric.error_code = 2; item->data.numeric.edit_pos = 0; item->data.numeric.edit_offs = 0; return MENURESULT_NONE; } if (allow_signed && pos == 0) { /* make negative */ str[0] = (str[0] == '-') ? '+' : '-'; } else { if (str[pos] > '0' && str[pos] <= '9') { str[pos] --; } else if (str[pos] == '0') { str[pos] = '\0'; } else if (str[pos] == '\0') { str[pos] = '9'; } } return MENURESULT_NONE; case MENUTOKEN_RIGHT: /* The user wants to go to next digit */ if (str[pos] != '\0' && pos < max_len) { item->data.numeric.edit_pos++; if (pos >= display_props->width - 2) item->data.numeric.edit_offs++; } return MENURESULT_NONE; case MENUTOKEN_LEFT: /* The user wants to go to back a digit */ if (pos > 0) { item->data.numeric.edit_pos--; if (item->data.numeric.edit_offs > item->data.numeric.edit_pos) item->data.numeric.edit_offs = item->data.numeric.edit_pos; } return MENURESULT_NONE; case MENUTOKEN_OTHER: if (pos >= max_len) { /* We're not allowed to add anything anymore */ item->data.numeric.error_code = 2; item->data.numeric.edit_pos = 0; item->data.numeric.edit_offs = 0; return MENURESULT_NONE; } /* process numeric keys */ if ((strlen(key) == 1) && isdigit(key[0])) { str[pos] = key[0]; item->data.numeric.edit_pos++; if (pos >= display_props->width - 2) item->data.numeric.edit_offs++; } return MENURESULT_NONE; } } return MENURESULT_ERROR; } MenuResult menuitem_process_input_alpha (MenuItem *item, MenuToken token, char * key, bool extended) { char * p; static char * chars = NULL; debug (RPT_DEBUG, "%s( item=[%s], token=%d, key=\"%s\" )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), token, key); if (item != NULL) { /* To make life easy... */ char *str = item->data.alpha.edit_str; int pos = item->data.alpha.edit_pos; /* Create list of allowed chars */ chars = realloc (chars, 26 + 26 + 10 + strlen(item->data.alpha.allowed_extra) + 1); chars[0] = '\0'; /* clear string */ if (item->data.alpha.allow_caps) strcat (chars, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); if (item->data.alpha.allow_noncaps) strcat (chars, "abcdefghijklmnopqrstuvwxyz"); if (item->data.alpha.allow_numbers) strcat (chars, "0123456789"); strcat (chars, item->data.alpha.allowed_extra); /* Clear the error */ item->data.alpha.error_code = 0; switch (token) { case MENUTOKEN_MENU: if (pos == 0) { return menuitem_predecessor2menuresult( item->predecessor_id, MENURESULT_CLOSE); } else { /* Reset data */ menuitem_reset_alpha(item); } return MENURESULT_NONE; case MENUTOKEN_ENTER: if ((extended) || (str[item->data.alpha.edit_pos] == '\0')) { /* The user completed his input */ /* It's not too short ? */ if (strlen (item->data.alpha.edit_str) < item->data.alpha.minlength) { item->data.alpha.error_code = 3; return MENURESULT_NONE; } /* Store value */ strcpy (item->data.alpha.value, item->data.alpha.edit_str); /* Inform client */ if (item->event_func) item->event_func (item, MENUEVENT_UPDATE); return menuitem_successor2menuresult( item->successor_id, MENURESULT_CLOSE); } else { /* The user wants to go to next digit */ if (pos < item->data.alpha.maxlength) { item->data.alpha.edit_pos++; if (pos >= display_props->width - 2) item->data.alpha.edit_offs++; } } return MENURESULT_NONE; case MENUTOKEN_UP: if (pos >= item->data.alpha.maxlength) { /* We're not allowed to add anything anymore */ item->data.alpha.error_code = 2; item->data.alpha.edit_pos = 0; item->data.alpha.edit_offs = 0; return MENURESULT_NONE; } if (str[pos] == '\0') { /* User goes past EOL */ str[pos] = chars[0]; } else { /* We should have a symbol from our list */ p = strchr (chars, str[pos]); if (p != NULL) { str[pos] = * (++p); /* next symbol on list */ /* Might be '\0' now */ } else { str[pos] = '\0'; } } return MENURESULT_NONE; case MENUTOKEN_DOWN: if (pos >= item->data.alpha.maxlength) { /* We're not allowed to add anything anymore */ item->data.alpha.error_code = 2; item->data.alpha.edit_pos = 0; item->data.alpha.edit_offs = 0; return MENURESULT_NONE; } if (str[pos] == '\0') { /* User goes past EOL */ str[pos] = chars[strlen(chars)-1]; } else { /* We should have a symbol from our list */ p = strchr (chars, str[pos]); if ((p != NULL) && (p != chars)) { str[pos] = * (--p); /* previous symbol on list */ } else { str[pos] = '\0'; } } return MENURESULT_NONE; case MENUTOKEN_RIGHT: /* The user wants to go to next digit */ if (str[item->data.alpha.edit_pos] != '\0' && pos < item->data.alpha.maxlength - 1) { item->data.alpha.edit_pos++; if (pos >= display_props->width - 2) item->data.alpha.edit_offs++; } return MENURESULT_NONE; case MENUTOKEN_LEFT: /* The user wants to go to back a digit */ if (pos > 0) { item->data.alpha.edit_pos--; if (item->data.alpha.edit_offs > item->data.alpha.edit_pos) item->data.alpha.edit_offs = item->data.alpha.edit_pos; } return MENURESULT_NONE; case MENUTOKEN_OTHER: if (pos >= item->data.alpha.maxlength) { /* We're not allowed to add anything anymore */ item->data.alpha.error_code = 2; item->data.alpha.edit_pos = 0; item->data.alpha.edit_offs = 0; return MENURESULT_NONE; } /* process other keys */ if ((strlen(key) == 1) && (key[0] >= ' ') && (strchr(chars, key[0]) != NULL)) { str[pos] = key[0]; item->data.alpha.edit_pos++; if (pos >= display_props->width - 2) item->data.alpha.edit_offs++; } return MENURESULT_NONE; } } return MENURESULT_ERROR; } MenuResult menuitem_process_input_ip (MenuItem *item, MenuToken token, char * key, bool extended) { /* To make life easy... */ char *str = item->data.ip.edit_str; char numstr[5]; int num; int pos = item->data.ip.edit_pos; const IpSstringProperties *ipinfo = (item->data.ip.v6) ? &IPinfo[1] : &IPinfo[0]; debug (RPT_DEBUG, "%s( item=[%s], token=%d, key=\"%s\" )", __FUNCTION__, ((item != NULL) ? item->id : "(null)"), token, key); /* Clear the error */ item->data.ip.error_code = 0; switch (token) { case MENUTOKEN_MENU: if (pos == 0) { return menuitem_predecessor2menuresult( item->predecessor_id, MENURESULT_CLOSE); } else { /* Reset data */ menuitem_reset_ip(item); } return MENURESULT_NONE; case MENUTOKEN_ENTER: if (extended || (pos >= item->data.ip.maxlength - 1)) { // remove the leading spaces/zeros in each octet-representing string char tmp[40]; // 40 = max. length of IPv4 & IPv6 addresses incl. '\0' char *start = tmp; memccpy(tmp, str, '\0', sizeof(tmp)); while (start != NULL) { char *skip = start; while ((*skip == ' ') || ((*skip == '0') && (skip[1] != ipinfo->sep) && (skip[1] != '\0'))) skip++; memccpy(start, skip, '\0', item->data.ip.maxlength + 1); skip = strchr(start, ipinfo->sep); start = (skip != NULL) ? (skip + 1) : NULL; } // check IP address entered if ((ipinfo->verify != NULL) && (!ipinfo->verify(tmp))) { report(RPT_WARNING, "%s(id=\"%s\") ip address not verified: \"%s\"", __FUNCTION__, item->id, tmp); item->data.ip.error_code = 4; return MENURESULT_NONE; } // store value strcpy (item->data.ip.value, tmp); // Inform client if (item->event_func) item->event_func (item, MENUEVENT_UPDATE); return menuitem_successor2menuresult( item->successor_id, MENURESULT_CLOSE); } else { item->data.ip.edit_pos++; if (str[item->data.ip.edit_pos] == ipinfo->sep) item->data.ip.edit_pos++; while (item->data.ip.edit_pos - item->data.ip.edit_offs > display_props->width - 2) item->data.ip.edit_offs++; return MENURESULT_NONE; } case MENUTOKEN_UP: // convert string starting at the beginning / previous dot into a number num = (int) strtol(&str[pos - (pos % (ipinfo->width + 1))], (char **) NULL, ipinfo->base); // increase the number depending on the position num += ipinfo->posValue[(pos - (pos / (ipinfo->width + 1))) % ipinfo->width]; // wrap around upper limit if (num > ipinfo->limit) num = 0; snprintf(numstr, 5, ipinfo->format, num); memcpy(&str[pos - (pos % (ipinfo->width + 1))], numstr, ipinfo->width); return MENURESULT_NONE; case MENUTOKEN_DOWN: // convert string starting at the beginning / previous dot into a number num = (int) strtol(&str[pos - (pos % (ipinfo->width + 1))], (char **) NULL, ipinfo->base); // decrease the number depending on the position num -= ipinfo->posValue[(pos - (pos / (ipinfo->width + 1))) % ipinfo->width]; // wrap around lower limit 0 if (num < 0) num = ipinfo->limit; snprintf(numstr, 5, ipinfo->format, num); memcpy(&str[pos - (pos % (ipinfo->width + 1))], numstr, ipinfo->width); return MENURESULT_NONE; case MENUTOKEN_RIGHT: if (pos < item->data.ip.maxlength - 1) { item->data.ip.edit_pos++; if (str[item->data.ip.edit_pos] == ipinfo->sep) item->data.ip.edit_pos++; while (item->data.ip.edit_pos - item->data.ip.edit_offs > display_props->width - 2) item->data.ip.edit_offs++; } return MENURESULT_NONE; case MENUTOKEN_LEFT: /* The user wants to go to back a digit */ if (pos > 0) { item->data.ip.edit_pos--; if (str[item->data.ip.edit_pos] == ipinfo->sep) item->data.ip.edit_pos--; if (item->data.ip.edit_offs > item->data.ip.edit_pos) item->data.ip.edit_offs = item->data.ip.edit_pos; } return MENURESULT_NONE; case MENUTOKEN_OTHER: /* process other keys */ if ((strlen(key) == 1) && ((item->data.ip.v6) ? isxdigit(key[0]) : isdigit(key[0]))) { str[pos] = tolower(key[0]); if (pos < item->data.ip.maxlength - 1) { item->data.ip.edit_pos++; if (str[item->data.ip.edit_pos] == ipinfo->sep) item->data.ip.edit_pos++; while (item->data.ip.edit_pos - item->data.ip.edit_offs > display_props->width - 2) item->data.ip.edit_offs++; } } return MENURESULT_NONE; } return MENURESULT_ERROR; } /** * get the Client that owns item. extracted from menu_commands_handler(). */ Client * menuitem_get_client(MenuItem * item) { return item->client; } LinkedList * tablist2linkedlist (char *strings) { LinkedList * list; list = LL_new(); /* Parse strings */ if (strings != NULL) { char *p = strings; char *tabptr, *new_s; while ((tabptr = strchr(p, '\t')) != NULL) { int len = (int)(tabptr - p); /* Alloc and copy substring */ new_s = malloc (len + 1); strncpy (new_s, p, len); new_s[len] = 0; LL_Push (list, new_s); /* Go to next string */ p = tabptr + 1; } /* Add last string */ new_s = strdup (p); LL_Push (list, new_s); } return list; } MenuItemType menuitem_typename_to_type (char *name) { if (name != NULL) { MenuItemType type; for (type = 0; type < NUM_ITEMTYPES; type ++) { if (strcmp (menuitemtypenames[type], name) == 0) { return type; } } } return -1; } char *menuitem_type_to_typename (MenuItemType type) { return ((type >= 0 && type < NUM_ITEMTYPES) ? menuitemtypenames[type] : NULL); } MenuEventType menuitem_eventtypename_to_eventtype (char *name) { if (name != NULL) { MenuEventType type; for (type = 0; type < NUM_EVENTTYPES; type ++) { if (strcmp (menueventtypenames[type], name) == 0) { return type; } } } return -1; } char *menuitem_eventtype_to_eventtypename (MenuEventType type) { return ((type >= 0 && type < NUM_EVENTTYPES) ? menueventtypenames[type] : NULL); }