/*
* 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