/* * Copyright (c) 2019 Clementine Computing LLC. * * This file is part of PopuFare. * * PopuFare is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * PopuFare 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with PopuFare. If not, see . * */ #include #include #include #include #include #include #include #include #include #include "../common/common_defs.h" #include "../commhub/commhub.h" #include "../commhub/client_utils.h" #include "fbutil.h" #include "expat-2.0.1/lib/expat.h" #include "menu.h" char *nodetype_words[NUM_NODETYPES] = {"driver_ui", "menu", "action", "widget", "class"}; char *action_words[NUM_ACTIONS] = {"callrule", "setvar", "append", "backspace", "setmenu", "login", "logout", "setpaddle", "prevstop", "nextstop", "seteqnum", "geteqnum", "clearmessages", "shellcall"}; char *widget_words[NUM_WIDGETS] = {"init", "button", "statusbar", "messages", "input", "text", "box", "filled_box", "line", "fullstatus"}; char *font_words[NUM_FONTS] = {"small", "medium", "large"}; extern int diu_fd; extern int hub_fd; extern int token_diag_serial; extern char token_diag_string[LINE_BUFFER_SIZE]; //--------------------------------------------------------------- // This structure is used to keep a buffer of messages to the driver //--------------------------------------------------------------- typedef struct diu_message_line_struct { pixel bgcolor; pixel textcolor; int num; char message[DIU_MESSAGE_CHARS]; } diu_message_line; diu_message_line diu_messages[DIU_MESSAGE_LINES] = {{0}}; int diu_message_head = 0; int diu_message_num = 0; //-------------------------------------------------------------- void clear_diu_messages() { diu_message_head = 0; diu_message_num = 0; memset(diu_messages, 0, sizeof(diu_messages)); } void add_diu_message(pixel bgcolor, pixel textcolor, char *message) { diu_messages[diu_message_head].bgcolor = bgcolor; diu_messages[diu_message_head].textcolor = textcolor; strncpy(diu_messages[diu_message_head].message, message, DIU_MESSAGE_CHARS - 1); diu_messages[diu_message_head].message[DIU_MESSAGE_CHARS - 1] = '\0'; diu_messages[diu_message_head++].num = diu_message_num++; if(diu_message_head >= DIU_MESSAGE_LINES) { diu_message_head = 0; } // printf("Head = %d, msg = %s\n", diu_message_head, message); } void replace_diu_message(pixel bgcolor, pixel textcolor, char *message) { int tmp_head = diu_message_head - 1; if(tmp_head < 0) { tmp_head = DIU_MESSAGE_LINES - 1; } diu_messages[tmp_head].bgcolor = bgcolor; diu_messages[tmp_head].textcolor = textcolor; strncpy(diu_messages[tmp_head].message, message, DIU_MESSAGE_CHARS - 1); diu_messages[tmp_head].message[DIU_MESSAGE_CHARS - 1] = '\0'; diu_messages[tmp_head].num = diu_message_num++; } //This function does an inefficient linear search for an attribute value on a menu node //It is rarely used since most common widget and menu parameters are pre-parsed at load time //inline char *get_attr_val(menunode *p, char *attr) char *get_attr_val(menunode *p, char *attr) { int i; if(!p || ! attr) { return NULL; } for(i = 0; i < p->keys; i++) { if(!strcasecmp(p->key[i], attr)) { return p->value[i]; } } return NULL; } //inline int attr_match(menunode *p, char *attr, char *value) int attr_match(menunode *p, char *attr, char *value) { char *val = get_attr_val(p, attr); if(!val || !value) { return 0; } return !strcasecmp(val, value); } time_t paddle_req_timeout = 0; set_paddle_req paddle_req = {0}; char *paddle_ok_menu = ""; char *paddle_fail_menu = ""; driver_status my_driver_status={0}; int update_driver_status = 0; //This function searches the driver file and checks a driver, pin pair returning 0 on success //or -1 on failure. int driver_login(int drivernum, char *pinnum) { FILE *f; char name[DRIVER_NAME_LEN]={0}; char pin[64]; char line[LINE_BUFFER_SIZE]={0}; char *inc_line; struct message_record outgoing_msg; int drv; int retval; if(!drivernum || !pinnum) { return -1; } f=fopen(DRIVERS_FILE,"rb"); //open the driver file if(!f) { format_log_message(&outgoing_msg, LOGLEVEL_ERROR, "Cannot open %s\n", DRIVERS_FILE); send_message(hub_fd,&outgoing_msg); return -1; //fail if we can't read it } while(!feof(f)) //while we haven't hit EOF { if(!fgets(line,sizeof(line)-1,f)) //read it line by line break; line[sizeof(line)-1] = '\0'; //terminating each line safely inc_line = line; while(1) //and ignoring any leading whitespace { char c = *inc_line; if( (c == ' ') ||(c == '\t') ||(c == '\r') ||(c == '\n') ) { inc_line++; continue; } break; } if( (inc_line[0] == '#') || (inc_line[0] == '\0')) //and ignoring blank/comment lines continue; name[0]='\0'; pin[0]='\0'; retval=sscanf(inc_line,"%d %63[0123456789] %63[^\r\n]",&drv,pin,name); //parse the line format (drivernum pin_num drivername) if(retval >= 2) //if we get at least a driver number and pin number { if(drv != drivernum) //check to see if we've found a match continue; if(!strcmp(pinnum, pin)) //if the PIN matches { my_driver_status.logged_in_driver = drivernum; strncpy(my_driver_status.driver_name, name, DRIVER_NAME_LEN); my_driver_status.driver_name[DRIVER_NAME_LEN - 1] = '\0'; my_driver_status.equip_num = get_equip_num(); format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Successful login for driver %d\n", drivernum); send_message(hub_fd,&outgoing_msg); update_driver_status |= 1; fclose(f); return 0; } else { format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Failed login for driver %d\n", drivernum); send_message(hub_fd,&outgoing_msg); fclose(f); return -1; } } } format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Failed login for unknown driver %d\n", drivernum); send_message(hub_fd,&outgoing_msg); fclose(f); return -1; } //Log a driver out and cancel any pending set paddle request int driver_logout() { struct message_record outgoing_msg; if(my_driver_status.logged_in_driver) { format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Driver logout for driver %d\n", my_driver_status.logged_in_driver); send_message(hub_fd,&outgoing_msg); } my_driver_status.logged_in_driver = 0; my_driver_status.driver_name[0] = '\0'; paddle_req_timeout = 0; update_driver_status |= 1; return 0; } //This gets the value of a menu variable by name. Again not the fastest way, but it is simple //and these operations don't occur very often static char *menuvar_getval(menutree *mt, char *ident) { menuvar *p; if(!mt || !ident) { return NULL; } p = mt->menuvars; while(p) { if(!strcasecmp(p->ident, ident)) { return p->value; } p = p->next; } return NULL; } static int menuvar_setval(menutree *mt, char *ident, char *val, int limit) { menuvar *p, *q; if(!mt || !ident) { return -1; } if(val == NULL) { val = ""; } if( (limit < 1) || (limit >= MENUVAR_VALUELEN) ) { limit = MENUVAR_VALUELEN - 1; } q = NULL; p = mt->menuvars; while(p) { if(!strcasecmp(p->ident, ident)) { break; } q = p; p = p->next; } if(p == NULL) { p = (menuvar *)malloc(sizeof(menuvar)); if(p == NULL) { return -1; } p->next = NULL; strncpy(p->ident, ident, MENUVAR_IDENTLEN); p->ident[limit] = '\0'; if(q) { q->next = p; } else { mt->menuvars = p; } } strncpy(p->value, val, limit); p->value[limit]='\0'; return 0; } static int menuvar_append(menutree *mt, char *ident, char *val, int limit) { char temp[MENUVAR_VALUELEN] = {0}; char *getret; if(!mt || !ident) { return -1; } if(val == NULL) { val = ""; } getret = menuvar_getval(mt, ident); if(getret == NULL) { getret = ""; } snprintf(temp, MENUVAR_IDENTLEN, "%s%s", getret, val); return menuvar_setval(mt, ident, temp, limit); } static int menuvar_backspace(menutree *mt, char *ident) { char *getret; int n; if(!mt || !ident) { return -1; } //NOTE: ---- We are counting on the fact that menuvar_getvalue returns a pointer to the ACTUAL string getret = menuvar_getval(mt, ident); if(getret == NULL) { return -1; } n = strlen(getret); if(n > 0) { getret[--n] = '\0'; //so we can modify it in this ridiculous way... } return 0; } //This function appends a node to the tail of child list of the node at the top of the stack static int append_node(menutree *mt, menunode *p) { if(mt->stack[mt->sp].n == NULL) { mt->head = p; return 0; } if(mt->stack[mt->sp].n->subtree == NULL) { mt->stack[mt->sp].n->subtree = p; } else { if(mt->stack[mt->sp].n->subtree->next == NULL) { mt->stack[mt->sp].n->subtree->next = p; mt->stack[mt->sp].tail = p; } else { mt->stack[mt->sp].tail->next = p; mt->stack[mt->sp].tail = p; } } return 0; } //This pushes the menu stack and sets the parser to expect the noted type of node static int push_menustack(menutree *mt, menunode *p, menunode_type t) { if(mt->sp < (MENU_STACK_DEPTH - 1)) { mt->sp++; mt->stack[mt->sp].n = p; mt->stack[mt->sp].expect = t; mt->stack[mt->sp].tail = NULL; return 0; } return -1; } //This function takes the attribute list in the libExpat style and allocates a local copy to live //in the menu node (unfolding the alternate index key/value scheme in the process) static int gather_args(menunode *p, const char **args) { int count = 0; int i; if(p == NULL) return -1; if(args == NULL) return 0; count = 0; while(args[count * 2]) { count++; } p->keys = count; if(count == 0) { return 0; } p->key = (char **)malloc(sizeof(char *) * count); if(p->key == NULL) { p->keys = 0; fprintf(stderr,"Cannot allocate memory for key list!\n"); return -1; } p->value = (char **)malloc(sizeof(char *) * count); if(p->key == NULL) { free(p->key); p->key = NULL; p->keys = 0; fprintf(stderr,"Cannot allocate memory for value list!\n"); return -1; } for(i=0; i < count; i++) { p->key[i] = strdup(args[i * 2]); if(p->key[i] == NULL) { int j; for(j=0; j < i; j++) { free(p->key[j]); free(p->value[j]); } free(p->key); free(p->value); p->key = NULL; p->value = NULL; p->keys = 0; fprintf(stderr,"Cannot allocate memory for key!\n"); return -1; } p->value[i] = strdup(args[(i * 2) + 1]); if(p->value[i] == NULL) { int j; for(j=0; j < i; j++) { free(p->key[j]); free(p->value[j]); } free(p->key[i]); free(p->key); free(p->value); p->key = NULL; p->value = NULL; p->keys = 0; fprintf(stderr,"Cannot allocate memory for value!\n"); return -1; } } return 0; } static menunode *alloc_menunode(menunode_type t) { menunode *r; r = (menunode *)malloc(sizeof(menunode)); if(r == NULL) { return NULL; } memset(r, 0, sizeof(menunode)); r->type = t; return r; } #define SET_WIDGET_BINARY_FLAG(str, flag) \ switch((str)[0]) \ { \ case '1': \ case 'Y': \ case 'y': \ r->wid.flags |= (flag); \ break; \ \ case '0': \ case 'N': \ case 'n': \ r->wid.flags &= ~(flag); \ break; \ \ default: \ break; \ } \ //---------------------------------------------- //Go and look through a list of text attributes and extract common ones into packed binary data //structures for quick widget drawing at runtime... static int extract_common_widget_attributes(menunode *r) { if(!r) return -1; int j,k; for(j = 0; j < r->keys; j++) { if(!strcasecmp(r->key[j], "x")) { r->wid.x = atoi(r->value[j]); } else if(!strcasecmp(r->key[j], "y")) { r->wid.y = atoi(r->value[j]); } else if(!strcasecmp(r->key[j], "width") || !strcasecmp(r->key[j], "w")) { r->wid.width = atoi(r->value[j]); } else if(!strcasecmp(r->key[j], "height") || !strcasecmp(r->key[j], "h")) { r->wid.height = atoi(r->value[j]); } else if(!strcasecmp(r->key[j], "font")) { for(k=0; k < NUM_FONTS; k++) { if(!strcasecmp(r->value[j], font_words[k])) { r->wid.font = k; break; } } } else if(!strcasecmp(r->key[j], "label") || !strcasecmp(r->key[j], "text")) { r->wid.text = r->value[j]; } else if(!strcasecmp(r->key[j], "color")) { r->wid.color = string_to_color(r->value[j]); } else if(!strcasecmp(r->key[j], "bgcolor")) { r->wid.bgcolor = string_to_color(r->value[j]); } else if(!strcasecmp(r->key[j], "textcolor")) { r->wid.textcolor = string_to_color(r->value[j]); } else if(!strcasecmp(r->key[j], "hotcolor")) { r->wid.hotcolor = string_to_color(r->value[j]); } else if(!strcasecmp(r->key[j], "disabled")) { SET_WIDGET_BINARY_FLAG(r->value[j], WF_DISABLED); } else if(!strcasecmp(r->key[j], "hidden")) { SET_WIDGET_BINARY_FLAG(r->value[j], WF_DONTDRAW); } else if(!strcasecmp(r->key[j], "shadow")) { SET_WIDGET_BINARY_FLAG(r->value[j], WF_SHADOW); } else if(!strcasecmp(r->key[j], "password")) { SET_WIDGET_BINARY_FLAG(r->value[j], WF_PASSWORD); } } return 0; } //Do the same for actions static int extract_common_action_attributes(menunode *r) { if(!r) return -1; int j; for(j = 0; j < r->keys; j++) { if(!strcasecmp(r->key[j], "limit")) { r->act.limit = atoi(r->value[j]); } else if(!strcasecmp(r->key[j], "var") || !strcasecmp(r->key[j], "variable")) { r->act.var = r->value[j]; } else if(!strcasecmp(r->key[j], "value")) { r->act.value = r->value[j]; } else if(!strcasecmp(r->key[j], "name")) { r->act.name = r->value[j]; } } return 0; } static void XMLCALL start_tag(void *data, const char *element, const char **attrib) { menutree *mt = (menutree *)data; menunode *r = NULL; if(mt->user_error) return; int i; // printf("[%d] BEGIN %s\n", mt->sp, element); if(mt->parse_flags & PARSE_IN_CLASS_TAG) { fprintf(stderr, "%d: tag may not have any children!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; return; } switch(mt->stack[mt->sp].expect) { case NODE_ROOT: if(!strcasecmp(element, "driver_ui")) { r = alloc_menunode(NODE_ROOT); if(r) { if(gather_args(r, attrib)) { free(r); fprintf(stderr, "%d: Error loading attributes!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; return; } append_node(mt, r); push_menustack(mt, r, NODE_MENU); } } else { fprintf(stderr, "%d: Expecting root tag of \n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; } break; case NODE_MENU: if(!strcasecmp(element, "menu")) { r = alloc_menunode(NODE_MENU); if(r) { if(gather_args(r, attrib)) { free(r); fprintf(stderr, "%d: Error loading attributes!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; return; } append_node(mt, r); push_menustack(mt, r, NODE_WIDGET); } } else if(!strcasecmp(element, "class")) { r = alloc_menunode(NODE_CLASS); if(r) { if(gather_args(r, attrib)) { free(r); fprintf(stderr, "%d: Error loading attributes!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; return; } extract_common_widget_attributes(r); r->next = mt->classlist; mt->classlist = r; mt->parse_flags |= PARSE_IN_CLASS_TAG; return; } } else { fprintf(stderr, "%d: Expecting or tag!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; } break; case NODE_ACTION: for(i = 0; i < NUM_ACTIONS; i++) { if(!strcasecmp(element, action_words[i])) { r = alloc_menunode(NODE_ACTION); if(r) { if(gather_args(r, attrib)) { free(r); fprintf(stderr, "%d: Error loading attributes!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; return; } r->act.type = i; extract_common_action_attributes(r); append_node(mt, r); push_menustack(mt, r, NODE_ACTION); return; } else { fprintf(stderr, "%d: Out of memory!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; return; } } } fprintf(stderr, "%d: Expecting action tag!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; break; case NODE_WIDGET: for(i = 0; i < NUM_WIDGETS; i++) { if(!strcasecmp(element, widget_words[i])) { r = alloc_menunode(NODE_WIDGET); if(r) { int j; if(gather_args(r, attrib)) { free(r); fprintf(stderr, "%d: Error loading attributes!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; return; } //Scan the attribute list for a widget class specifier to pick defaults from for(j = 0; j < r->keys; j++) { if(!strcasecmp(r->key[j], "class")) //if we have a class specifier { menunode *pp = mt->classlist; //start looking through the class pist while(pp) { if(attr_match(pp, "name", r->value[j])) //If the name of this class matches our class request { //copy the attribues from the class to our widget memcpy(&r->wid, &pp->wid, sizeof(struct widget_struct)); break; //break the while(pp) } pp = pp->next; } if(pp == NULL) //if we exhausted the list but found no match, complain bitterly... { fprintf(stderr, "%d: Widget invokes unknown class \"%s\"!\n", (int)XML_GetCurrentLineNumber(mt->parser), r->value[j]); mt->user_error = 1; } break; } } //Now that the widget class data have been copied, it's safe to override the type to our actual type. r->wid.type = i; extract_common_widget_attributes(r); append_node(mt, r); push_menustack(mt, r, NODE_ACTION); return; } else { fprintf(stderr, "%d: Out of memory!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; return; } } } fprintf(stderr, "%d: Expecting widget tag!\n", (int)XML_GetCurrentLineNumber(mt->parser)); mt->user_error = 1; break; default: break; } } static void XMLCALL end_tag(void *data, const char *element) { menutree *mt = (menutree *)data; if(mt->user_error) return; // printf("[%d] END %s\n", mt->sp, element); if(!strcasecmp(element,"class")) //if this is the tail end of a tag, clear the in_class flag { mt->parse_flags &= ~PARSE_IN_CLASS_TAG; } else //otherwise, pop the menu stack { mt->sp--; } } void free_menunode(menunode *r) { int i; if(r->subtree) { free_menunode(r->subtree); } if(r->next) { free_menunode(r->next); } if(r->key) { for(i=0; i < r->keys; i++) { if(r->key[i]) { free(r->key[i]); } } free(r->key); } if(r->value) { for(i=0; i < r->keys; i++) { if(r->value[i]) { free(r->value[i]); } } free(r->value); } free(r); } void free_menuvars(menuvar *v) { menuvar *p, *q; p = v; q = NULL; while(p) { q = p; p = p->next; free(q); } } void free_menutree(menutree *mt) { if(!mt) return; driver_logout(); if(mt->head) { free_menunode(mt->head); } if(mt->classlist) { free_menunode(mt->classlist); } if(mt->menuvars) { free_menuvars(mt->menuvars); } free(mt); } menutree *load_menutree(char *fname) { menutree *mt = NULL; FILE *f = NULL; char chunk[XML_CHUNK_SIZE] = {0}; int len = 0; int done = 0; XML_Parser p = NULL; if(!fname) return NULL; f = fopen(fname, "rb"); if(!f) { fprintf(stderr, "Cannot read menutree file %s!\n", fname); return NULL; } p = XML_ParserCreate(NULL); if(!p) { fprintf(stderr,"No memory for parser!\n"); fclose(f); return NULL; } XML_SetElementHandler(p, start_tag, end_tag); mt = (menutree *)malloc(sizeof(menutree)); if(!mt) { fprintf(stderr, "Cannot allocate memory for menutree!\n"); fclose(f); XML_ParserFree(p); return NULL; } memset(mt, 0, sizeof(menutree)); XML_SetUserData(p, (void *)mt); mt->parser = p; while(!done) { len = fread(chunk, 1, XML_CHUNK_SIZE, f); if(feof(f)) { done = 1; } if( XML_Parse(p, chunk, len, done) == XML_STATUS_ERROR ) { fprintf(stderr, "Parse error at line %d: %s\n", (int)XML_GetCurrentLineNumber(p), XML_ErrorString(XML_GetErrorCode(p))); fclose(f); XML_ParserFree(p); free_menutree(mt); return NULL; } if(mt->user_error) { fclose(f); XML_ParserFree(p); free_menutree(mt); return NULL; } } fclose(f); XML_ParserFree(p); mt->parser = NULL; return mt; } void print_menu_tree_inner(menunode *p, int indent) { int i; for(i=0; i < indent; i++) { printf(" "); } printf("[BEGIN %s]", nodetype_words[p->type]); switch(p->type) { case NODE_ACTION: printf(" (%s)", action_words[p->act.type]); break; case NODE_WIDGET: printf(" (%s)", widget_words[p->wid.type]); break; default: break; } for(i = 0; i < p->keys; i++) { printf(" %s=\"%s\"", p->key[i], p->value[i]); } printf("\n"); if(p->subtree) { print_menu_tree_inner(p->subtree, indent + 1); } for(i=0; i < indent; i++) { printf(" "); } printf("[END %s]\n", nodetype_words[p->type]); if(p->next) { print_menu_tree_inner(p->next, indent); } } void print_widget_class_list(menunode *classes) { menunode *p = classes; int i; while(p) { if(p->type != NODE_CLASS) { printf("\tExpecting widget class...\n"); p = p->next; continue; } printf("\t"); for(i = 0; i < p->keys; i++) { printf(" %s=\"%s\"", p->key[i], p->value[i]); } printf("\n"); p = p->next; } } void print_menu_tree(menutree *mt) { printf("Widget Classes:\n"); print_widget_class_list(mt->classlist); printf("\nMenu Definitions:\n"); print_menu_tree_inner(mt->head, 0); } void do_menu_action(menutree *mt, menunode *act); int setmenu(menutree *mt, char *name) { menunode *p; if(!mt) return -1; p = mt->head->subtree; while(p) { if(attr_match(p, "name", name)) { mt->current = p; p = p->subtree; while(p) { if( (p->type == NODE_WIDGET) && (p->wid.type == WIDGET_MENUINIT) ) { do_menu_action(mt, p->subtree); } p = p->next; } return 0; } p = p->next; } return -1; } int init_menutree(menutree *mt) { char *start_menu; if(!mt) { return -1; } if(!mt->head) { return -1; } if(!mt->head->subtree) { return -1; } start_menu = get_attr_val(mt->head, "startup"); if(setmenu(mt, start_menu)) { mt->current = mt->head->subtree; } return 0; } int make_paddle_request(int paddle, char *okmenu, char *failmenu) { struct message_record outgoing_msg; paddle_req.result = 0; paddle_req.request = paddle; paddle_req_timeout = time(NULL) + PADDLE_REQ_TIMEOUT; paddle_ok_menu = okmenu; paddle_fail_menu = failmenu; prepare_message(&outgoing_msg, MAILBOX_SET_PADDLE, &paddle_req, sizeof(paddle_req)); return send_message(hub_fd, &outgoing_msg); } int check_paddle_request(menutree *mt) { struct message_record outgoing_msg; if(paddle_req_timeout != 0) { if(paddle_req.request == paddle_req.result) { format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Set Paddle %d\n", paddle_req.request); send_message(hub_fd, &outgoing_msg); paddle_req_timeout = 0; setmenu(mt, paddle_ok_menu); return 1; } else if( (paddle_req.result < 0) || (paddle_req_timeout < time(NULL)) ) { format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Failed to set Paddle %d\n", paddle_req.request); send_message(hub_fd, &outgoing_msg); paddle_req.result = 0; paddle_req.request = 0; paddle_req_timeout = 0; setmenu(mt, paddle_fail_menu); return 1; } } return 0; } void action_nextstop() { struct message_record outgoing_msg; prepare_message(&outgoing_msg, MAILBOX_NEXT_STOP, "", 0); send_message(hub_fd, &outgoing_msg); } void action_prevstop() { struct message_record outgoing_msg; prepare_message(&outgoing_msg, MAILBOX_PREV_STOP, "", 0); send_message(hub_fd, &outgoing_msg); } int draw_menu(menutree *mt) { menunode *p; if(!mt->current) { return -1; } p = mt->current->subtree; int x, y; int xx, yy; int tmp_flags; set_rawcolor(FBCOLOR_WHITE); cls(); while(p) { if(p->type != NODE_WIDGET) { fprintf(stderr, "Expecting Widget!\n"); print_menu_tree_inner(p, 2); p = p->next; continue; } if(p->wid.flags & WF_DONTDRAW) { p = p->next; continue; } tmp_flags = p->wid.flags; if(p == mt->pressed) { tmp_flags = mt->pressed_flags; } x = p->wid.x * GRID; y = p->wid.y * GRID; xx = (p->wid.x + p->wid.width) * GRID; yy = (p->wid.y + p->wid.height) * GRID; switch(p->wid.type) { case WIDGET_BUTTON: if(tmp_flags & WF_SHADOW) { select_blend_mode(BLENDMODE_LIGHT); set_rawcolor(FBCOLOR_BLACK | FBCOLOR_TRANSLUCENT); if(tmp_flags & WF_PRESSED) { set_pos(x + SHADOW_OFFSET_DOWN, y + SHADOW_OFFSET_DOWN); box_fill(xx + SHADOW_OFFSET_DOWN, yy + SHADOW_OFFSET_DOWN); } else { set_pos(x + SHADOW_OFFSET_UP, y + SHADOW_OFFSET_UP); box_fill(xx + SHADOW_OFFSET_UP, yy + SHADOW_OFFSET_UP); } select_blend_mode(BLENDMODE_DARK); } if(tmp_flags & WF_PRESSED) { set_rawcolor(p->wid.hotcolor); } else { set_rawcolor(p->wid.color); } set_pos(x, y); box_fill(xx, yy); set_rawcolor(FBCOLOR_BLACK); set_pos(x, y); box(xx, yy); if(p->wid.text) { set_clip_rect(x + 1, y + 1, xx, yy); int strw, strh; int dx, dy; set_font(p->wid.font); font_cell_size(&strw, &strh); strw *= strlen(p->wid.text); dx = ((xx - x) - strw) / 2; dy = ((yy - y) - strh) / 2; set_pos(x + dx, y + dy); set_rawcolor(p->wid.textcolor); print_string(p->wid.text, 0); reset_clip_rect(); } break; case WIDGET_TEXT: { char *label = (p->wid.text)?p->wid.text:""; set_pos(x, y); set_font(p->wid.font); set_rawcolor(p->wid.textcolor); print_string(label, 0); } break; case WIDGET_BOX: set_pos(x, y); set_rawcolor(p->wid.color); box(xx, yy); break; case WIDGET_FILLEDBOX: set_pos(x, y); set_rawcolor(p->wid.color); box_fill(xx, yy); break; case WIDGET_LINE: set_pos(x, y); set_rawcolor(p->wid.color); line_to(xx, yy); break; case WIDGET_MESSAGES: { int charw, charh; int numlines; int idx; int dy; int numchars; int splitchars; char tmptext[DIU_MESSAGE_CHARS]; set_rawcolor(p->wid.color); set_pos(x, y); box_fill(xx, yy); set_rawcolor(FBCOLOR_BLACK); set_pos(x, y); box(xx, yy); set_clip_rect(x + 1, y + 1, xx, yy); set_font(p->wid.font); font_cell_size(&charw, &charh); numlines = ((p->wid.height * GRID) - 2) / charh; numchars = ((p->wid.width * GRID) - 2) / charw; splitchars = numchars - 5; idx = diu_message_head - 1; if(idx < 0) idx += DIU_MESSAGE_LINES; dy = (yy - 1) - charh; while(numlines) { // printf("dy = %d, idx = %d, numlines = %d\n", dy, idx, numlines); if(diu_messages[idx].message[0]) { set_rawcolor(diu_messages[idx].bgcolor); set_pos(x+1, dy); box_fill(xx - 1, dy + charh); set_pos(x+1, dy); set_rawcolor(diu_messages[idx].textcolor); snprintf(tmptext, DIU_MESSAGE_CHARS, "%-*.*s (%02d)", splitchars, splitchars, diu_messages[idx].message, diu_messages[idx].num); print_string(tmptext, 0); // printf("Printed \"%s\" at %d,%d\n", tmptext, x+1, dy); } dy -= charh; numlines--; idx--; if(idx < 0) idx += DIU_MESSAGE_LINES; } reset_clip_rect(); } break; case WIDGET_INPUT: if(tmp_flags & WF_SHADOW) { select_blend_mode(BLENDMODE_LIGHT); set_rawcolor(FBCOLOR_BLACK | FBCOLOR_TRANSLUCENT); if(tmp_flags & WF_PRESSED) { set_pos(x + SHADOW_OFFSET_DOWN, y + SHADOW_OFFSET_DOWN); box_fill(xx + SHADOW_OFFSET_DOWN, yy + SHADOW_OFFSET_DOWN); } else { set_pos(x + SHADOW_OFFSET_UP, y + SHADOW_OFFSET_UP); box_fill(xx + SHADOW_OFFSET_UP, yy + SHADOW_OFFSET_UP); } select_blend_mode(BLENDMODE_DARK); } set_rawcolor(p->wid.color); set_pos(x, y); box_fill(xx, yy); set_rawcolor(FBCOLOR_BLACK); set_pos(x, y); box(xx, yy); { char *varname; char *content; char *label; int strw, strh; int dx, dy; label = (p->wid.text)?p->wid.text:""; varname = get_attr_val(p, "var"); if(varname == NULL) { fprintf(stderr, "Input box lacks content variable!\n"); fflush(stderr); content = ""; } else { content = menuvar_getval(mt, varname); if(content == NULL) { content = ""; } } set_clip_rect(x + 1, y + 1, xx, yy); set_font(p->wid.font); font_cell_size(&dx, &strh); strw = dx * strlen(label); dy = ((yy - y) - strh) / 2; set_pos(x + dx, y + dy); set_rawcolor(p->wid.textcolor); print_string(label, 0); dx += strw; if(p->wid.flags & WF_PASSWORD) { int n; int i; char crapval[MENUVAR_VALUELEN] = {0}; n = strlen(content); if(n >= MENUVAR_VALUELEN) { n = MENUVAR_VALUELEN - 1; } for(i=0; i < n; i++) { crapval[i]='*'; } set_pos(x + dx, y + dy); print_string(crapval, 0); } else { set_pos(x + dx, y + dy); print_string(content, 0); } reset_clip_rect(); } break; case WIDGET_STATUSBAR: { char statline[DIU_MESSAGE_CHARS]; char date_str[32]; time_t t; struct tm tm; switch(pass_stat.flush_status) { case FLUSH_STATUS_DOWNLOAD: snprintf(statline, DIU_MESSAGE_CHARS, "DB Flush Downloading %d%%", pass_stat.progress_indicator); break; case FLUSH_STATUS_APPLY: snprintf(statline, DIU_MESSAGE_CHARS, "DB Flush Applying %d%%", pass_stat.progress_indicator); break; case FLUSH_STATUS_WRITE: snprintf(statline, DIU_MESSAGE_CHARS, "Saving New Database. Please Wait."); break; default: /* snprintf(statline, DIU_MESSAGE_CHARS, "Rte %d Trip %d Stop %d GPS %-3s Tun %-3s", stop_stat.route, stop_stat.trip, stop_stat.stop, gps_stat.gps_good?"YES":"NO", tunnel_is_up()?"YES":"NO"); */ t = time(NULL); localtime_r(&t, &tm); strftime(date_str, 32, "%Y-%m-%d", &tm); snprintf(statline, DIU_MESSAGE_CHARS, "Rt %d Trp %d Stop %d GPS %-3s Tun %-3s %s", stop_stat.route, stop_stat.trip, stop_stat.stop, gps_stat.gps_good?"YES":"NO", tunnel_is_up()?"YES":"NO", date_str); break; } set_pos(x, y); set_rawcolor(p->wid.color); box_fill(xx,yy); set_clip_rect(x + 1, y + 1, xx, yy); set_pos(x, y); set_rawcolor(p->wid.textcolor); set_font(p->wid.font); print_string(statline, 0); reset_clip_rect(); } break; #define MAX_PKGS (16) case WIDGET_FULLSTATUS: { int i; int npkgs; FILE *f; char statline[DIU_MESSAGE_CHARS]; package_signature pkgs[MAX_PKGS]; int dy = 0, dx = 0; int cellwidth, cellheight; struct tm pkgtime; set_font(p->wid.font); set_rawcolor(FBCOLOR_BLACK); set_pos(x, y); box(xx,yy); set_clip_rect(x + 1, y + 1, xx, yy); set_pos(x, y); font_cell_size(&cellwidth, &cellheight); dx = cellwidth >> 1; dy = cellheight >> 1; snprintf(statline, DIU_MESSAGE_CHARS, "Tunnel: %-3s GPRS: %-3s Eq# %d #Msg: %d", tunnel_is_up()?"YES":"NO", gprs_is_up()?"YES":"NO", get_equip_num(), bill_stat.unsynced_messages); set_pos(x + dx, y + dy); print_string(statline, 0); dy += cellheight; snprintf(statline, DIU_MESSAGE_CHARS, "Last Token Read (%02d): \"%s\"", token_diag_serial % 100, token_diag_string); set_pos(x + dx, y + dy); print_string(statline, 0); dy += cellheight; if(gps_stat.gps_good) { snprintf(statline, DIU_MESSAGE_CHARS, "GPS: nSat = %d Loc = (%11.4f,%11.4f)", gps_stat.num_sats, gps_stat.lat, gps_stat.lon); } else { snprintf(statline, DIU_MESSAGE_CHARS, "GPS: NO / Stale"); } set_pos(x + dx, y + dy); print_string(statline, 0); dy += cellheight; dy += cellheight; snprintf(statline, DIU_MESSAGE_CHARS, "Package Version Installed"); set_pos(x + dx, y + dy); print_string(statline, 0); dy += cellheight; npkgs = find_packages(pkgs, MAX_PKGS); for(i=0; i < npkgs; i++) { localtime_r(&pkgs[i].installed, &pkgtime); snprintf(statline, DIU_MESSAGE_CHARS, "%-8s %-16s %02d/%02d/%02d %02d:%02d:%02d", pkgs[i].pkgname, pkgs[i].pkgver, pkgtime.tm_mon + 1, pkgtime.tm_mday, pkgtime.tm_year + 1900, pkgtime.tm_hour, pkgtime.tm_min, pkgtime.tm_sec); set_pos(x + dx, y + dy); print_string(statline, 0); dy += cellheight; } f = fopen("/tmp/net_ids", "rb"); if(f) { dy += cellheight; while(fgets(statline, DIU_MESSAGE_CHARS, f)) { set_pos(x + dx, y + dy); strip_crlf(statline); print_string(statline, 0); dy += cellheight; } fclose(f); } reset_clip_rect(); } break; default: break; } p = p->next; } present_framebuffer(); return 0; } void do_menu_action(menutree *mt, menunode *act) { menunode *p = act; while(p) { if(p->type != NODE_ACTION) { fprintf(stderr, "Expecting Action!\n"); print_menu_tree_inner(p, 2); p = p->next; break; } // fprintf(stderr,"Action: %d\n", p->act.type); // fflush(stderr); switch(p->act.type) { case ACTION_SETMENU: if(p->act.name) { setmenu(mt, p->act.name); } else { fprintf(stderr, "setmenu expects name=\"...\"\n"); } break; case ACTION_SETVAR: if(p->act.var == NULL) { fprintf(stderr, "setvar expects var=\"...\"\n"); } else { menuvar_setval(mt, p->act.var, p->act.value, p->act.limit); } break; case ACTION_APPEND: if(p->act.var == NULL) { fprintf(stderr, "append expects var=\"...\"\n"); } else { menuvar_append(mt, p->act.var, p->act.value, p->act.limit); } break; case ACTION_BKSPACE: if(p->act.var == NULL) { fprintf(stderr, "backspace expects var=\"...\"\n"); } else { menuvar_backspace(mt, p->act.var); } break; case ACTION_LOGIN: { char *driver_var, *pin_var, *okmenu, *failmenu; char *driver_val, *pin_val; driver_var = get_attr_val(p, "drivervar"); pin_var = get_attr_val(p, "pinvar"); okmenu = get_attr_val(p, "okmenu"); failmenu = get_attr_val(p, "failmenu"); if(!driver_var || !pin_var || !okmenu || !failmenu) { fprintf(stderr, "login expects parameters: drivervar, pinvar, okmenu, failmenu\n"); } else { driver_val = menuvar_getval(mt, driver_var); pin_val = menuvar_getval(mt, pin_var); if(!driver_val || !pin_val) { setmenu(mt, failmenu); return; } else { if(driver_login(atoi(driver_val), pin_val)) { setmenu(mt, failmenu); return; } else { //If this is the "magic" driver name, go to admin mode menu if(!strcmp(my_driver_status.driver_name, MAGIC_DRIVER_NAME)) { //(named by the #define'd value of MACIC_DRIVER_MENU) setmenu(mt, MACIC_DRIVER_MENU); } else { //otherwise, go the the config-file specified OK menu setmenu(mt, okmenu); } return; } } } } break; case ACTION_SETPADDLE: { char *paddle_var, *okmenu, *failmenu; char *paddle_val; paddle_var = get_attr_val(p, "paddlevar"); okmenu = get_attr_val(p, "okmenu"); failmenu = get_attr_val(p, "failmenu"); if(!paddle_var || !okmenu || !failmenu) { fprintf(stderr, "setpaddle expects parameters: paddlevar, okmenu, failmenu\n"); } else { paddle_val = menuvar_getval(mt, paddle_var); if(!paddle_val || (atoi(paddle_val) <= 0) ) { setmenu(mt, failmenu); return; } else { make_paddle_request(atoi(paddle_val), okmenu, failmenu); } } } break; case ACTION_PREVSTOP: action_prevstop(); break; case ACTION_NEXTSTOP: action_nextstop(); break; case ACTION_LOGOUT: driver_logout(); break; case ACTION_RULE: { struct message_record outgoing; driver_rulecall foo = {{0}}; char *rule_var, *rule_val; char *param_var, *param_val; rule_var = get_attr_val(p, "rulevar"); rule_val = get_attr_val(p, "rule"); if(rule_val) { if(rule_var) { fprintf(stderr, "callrule expects rulevar OR rule, not both!\n"); break; } } else { if(rule_var) { rule_val = menuvar_getval(mt, rule_var); if(!rule_val) { fprintf(stderr, "callrule expects non-null value in variable %s specified by rulevar!\n", rule_var); break; } } else { fprintf(stderr, "callrule expects rulevar OR rule!\n"); break; } } param_var = get_attr_val(p, "paramvar"); param_val = get_attr_val(p, "param"); if(param_val) { if(param_var) { fprintf(stderr, "callrule expects paramvar OR param, not both!\n"); break; } } else { if(param_var) { param_val = menuvar_getval(mt, param_var); if(!param_val) { param_val = ""; } } else { fprintf(stderr, "callrule expects paramvar OR param!\n"); break; } } if(hub_fd >= 0) { strncpy(foo.rulename, rule_val, DRIVER_RULECALL_LEN - 1); strncpy(foo.ruleparam, param_val, DRIVER_RULECALL_LEN - 1); prepare_message(&outgoing, MAILBOX_RULE_CALL, &foo, sizeof(foo)); send_message(hub_fd, &outgoing); } } break; case ACTION_SETEQNUM: { char *eqval; int retval; if(p->act.var) { eqval = menuvar_getval(mt, p->act.var); if(eqval) { retval = set_equip_num(strtol(eqval, NULL, 10)); if(retval) { fprintf(stderr, "Failed to set Equipment Number to %s\n", eqval); } else { system("/mnt/data2/bin/showmessage -b 'Restarting...'"); sleep(1); system("/sbin/shutdown -r now"); sleep(30); } } } else { fprintf(stderr, "Action seteqnum expects: var\n"); } } break; case ACTION_GETEQNUM: { char tmp[MENUVAR_VALUELEN]; int retval; retval = get_equip_num(); snprintf(tmp, MENUVAR_VALUELEN, "%d", retval); if(p->act.var) { menuvar_setval(mt, p->act.var, tmp, p->act.limit); } else { fprintf(stderr, "Action geteqnum expects: var\n"); } } break; case ACTION_CLRMSGS: { clear_diu_messages(); } break; case ACTION_SHELLCALL: { char tmp[SHELLCALL_BUFFER_SIZE] = {0}; char varname[MENUVAR_IDENTLEN] = {0}; char *cmd_val = NULL; char *var_val = NULL; char *okmenu = NULL; char *failmenu = NULL; char c; int in_idx = 0; int out_idx = 0; int fmt_mode = 0; int fmt_idx = 0; int ret = 0; int fail_flag = 0; //Gather the command line template before we run it through formatting cmd_val = get_attr_val(p, "cmdline"); //Bitch loudly and give up if we don't get it... if(!cmd_val) { fprintf(stderr, "Action shellcall requires: cmdline\n"); break; } okmenu = get_attr_val(p, "okmenu"); failmenu = get_attr_val(p, "failmenu"); //While we still have format string to process and buffer space to put the result while( (c = cmd_val[in_idx]) && (out_idx < (SHELLCALL_BUFFER_SIZE - 1)) ) { if(fmt_mode) //------------------------------------------ FORMAT MODE { if(c == SHELLCALL_VAR_ESC) //and we just got an escape character to terminate the variable name { if(fmt_idx > 0) //if we have a non-zero length variable name { varname[fmt_idx] = '\0'; //nul terminate it if( (var_val = menuvar_getval(mt, varname)) ) //and look it up { //if we find it, tack its value onto our output buffer out_idx += snprintf(tmp + out_idx, SHELLCALL_BUFFER_SIZE - 1, "%s", var_val); } else //if we don't find it, mark failure and break out { fail_flag = 1; fprintf(stderr,"Action shellcall aborted: variable ~%s~ not defined\n", varname); break; } } else //If we have a zero-length variable name, that just means we want the escape character { tmp[out_idx++] = SHELLCALL_VAR_ESC; } fmt_mode = 0; //Either way, if we were in format mode and got the escape, we're in normal mode now } else //if we are in format mode and processing a normal character { if(fmt_idx == (MENUVAR_IDENTLEN - 1)) //make sure we have room for the identifier { fail_flag = 1; varname[MENUVAR_IDENTLEN - 1] = '\0'; fprintf(stderr,"Action shellcall aborted: variable name too long ~%s...\n", varname); break; } varname[fmt_idx++] = c; //then store the character to the end... } } else //------------------------------------------ NORMAL MODE { if(c == SHELLCALL_VAR_ESC) //if we're in normal mode and encounter an escape { fmt_mode = 1; //set escape mode fmt_idx = 0; //reset variable name index } else { tmp[out_idx++] = c; //copy the character verbatim } } in_idx++; //no matter what, we want to advance to the next format string character } if(fail_flag) //if we hit a failure during format expansion, bail now... { break; } if(fmt_mode) { varname[fmt_idx] = '\0'; fprintf(stderr, "Action shellcall aborted: unterminated variable expansion ~%s...\n", varname); break; } //If we've overflowed and constructed a partially formed command line, play it safe and give up now before //we blow up the world and poison everybody with radioactive waste. if(out_idx >= SHELLCALL_BUFFER_SIZE) { fprintf(stderr, "Action shellcall aborted: assembled command exceeds %d character limit!\n", SHELLCALL_BUFFER_SIZE); break; } tmp[out_idx] = '\0'; //Slap the terminating nul on the newly minted shell script. Wo0t! //Here goes nothing, we make our call to system() here to invoke the shell... ret = system(tmp); if( WEXITSTATUS(ret) == 0 ) //If the shell return value is 0 (true / success in bash-ese) { if(okmenu) //and the config file specifies a menu to jump to on success { setmenu(mt, okmenu); //then by all means do so... } } else //otherwise, it's the same drill with failure { //now with 30% MORE FAIL! if(failmenu) { setmenu(mt, failmenu); } } } break; default: fprintf(stderr, "Menu Action %s Currently Unimplemented...\n", action_words[p->act.type]); break; } p = p->next; } } int process_pen(menutree *mt, int x, int y, int dwn) { int return_code = 0; menunode *p; menunode *pp = mt->pressed; int tx,ty,txx,tyy; if(!mt->current) { mt->pressed = NULL; mt->pressed_flags = 0; mt->down = 0; mt->down_x = 0; mt->down_y = 0; return -1; } p = mt->current->subtree; if(dwn) //if the pen is down { mt->pressed = NULL; mt->pressed_flags = 0; if(!mt->down) //and this is news... { mt->down = 1; //record a pen down event mt->down_x = x; mt->down_y = y; } while(p) { if(p->type != NODE_WIDGET) { fprintf(stderr, "Expecting Widget!\n"); print_menu_tree_inner(p, 2); p = p->next; continue; } switch(p->wid.type) { case WIDGET_BUTTON: break; default: p = p->next; continue; } if(p->wid.flags & WF_DISABLED) { p = p->next; continue; } tx = p->wid.x * GRID; ty = p->wid.y * GRID; txx = tx + p->wid.width * GRID; tyy = ty + p->wid.height * GRID; // printf("%d %d %d %d\n", p->wid.x, p->wid.y, p->wid.width, p->wid.height); // printf("(%d,%d)-(%d,%d) %s / (%d,%d) (%d,%d)\n", tx, ty, txx, tyy, p->wid.text, x, y, mt->down_x, mt->down_y); if( (x > tx) && (x < txx) && (y > ty) && (y < tyy) && (mt->down_x > tx) && (mt->down_x < txx) && (mt->down_y > ty) && (mt->down_y < tyy) ) { mt->pressed = p; mt->pressed_flags = p->wid.flags | WF_PRESSED; } p = p->next; } } else { if(mt->down) { while(p) { if(p->type != NODE_WIDGET) { fprintf(stderr, "Expecting Widget!\n"); print_menu_tree_inner(p, 2); p = p->next; continue; } if(p == mt->pressed) break; p = p->next; } if(p != NULL) { // printf("Button: %s\n", p->wid.text); DIU_BUTTON_BEEP(diu_fd); do_menu_action(mt, p->subtree); } } else { //take no action on a redundant pen up } mt->pressed = NULL; mt->pressed_flags = 0; mt->down = 0; mt->down_x = 0; mt->down_y = 0; } if(pp != mt->pressed) { return_code |= 1; } return return_code; }