#include #include #include #include #include #include "item.h" #include "xmlconfig.h" #include "main.h" #include "navit.h" #include "vehicle.h" #include "speech.h" #include "gui.h" #include "debug.h" #include "callback.h" #include "command.h" #include "event.h" #include "navit_nls.h" /* gui.fullscreen=!gui.fullscreen gui.menu() gui.get_data() zoom_in() zoom_out() speech.active=!speech.active osd_configuration=1 osd[@type=="xxx"].active=0;osd[@type=="yyy"].active=0 */ /** * The result, or interim result, of evaluating a saved command. */ struct result { struct attr attr; /**< The attribute. If {@code allocated} is true, it stores an object that was * successfully retrieved. Else it is either a placeholder or a constant value. */ double val; const char *var; /**< If {@code allocated} is false, the name of the object to be resolved. * Else, it is the name of the object successfully retrieved and stored in * {@code attr}, or {@code NULL} if retrieval failed. * Only the first {@code varlen} characters are significant. */ int varlen; /**< Number of significant characters in {@code var} */ const char *attrn; /**< The name of an object that has been resolved but not yet retrieved, * {@code NULL} otherwise. Only the first {@code attrnlen} characters are * significant. */ int attrnlen; /**< Number of significant characters in {@code attrn} */ int allocated; /**< Whether the result has been calculated */ }; struct result_list { struct attr **attrs; }; struct context { struct attr *attr; int error; int skip; const char *expr; struct result res; }; /** * Information about a callback function for a saved command. */ struct command_saved_cb { struct callback *cb; /**< The callback function */ struct attr attr; }; /** * A saved command. */ struct command_saved { struct context ctx; struct result res; char *command; /**< The command string itself **/ struct event_idle *idle_ev; /**< Event to update this command **/ struct callback *idle_cb; struct callback *register_cb; /**< Callback to register all the callbacks **/ struct event_idle *register_ev; /**< Idle event to register all the callbacks **/ struct attr context_attr; /**< The root of the object hierarchy, which will be assumed as * the parent of all unqualified or partially qualified object * references. **/ int num_cbs; /**< Number of entries in {@code cbs} **/ struct command_saved_cb *cbs; /**< List of callbacks for this saved command **/ struct callback *cb; /**< Callback that should be called when we re-evaluate **/ int error; int async; }; enum error { no_error=0, missing_double_quote, missing_opening_parenthesis, missing_closing_parenthesis, missing_closing_brace, missing_colon, missing_semicolon, wrong_type, illegal_number_format, illegal_character, missing_closing_bracket, invalid_type, not_ready, internal, eof_reached }; enum op_type { op_type_binary, op_type_prefix, op_type_suffix }; enum set_type { set_type_symbol, set_type_string, set_type_integer, set_type_float }; static void eval_comma(struct context *ctx, struct result *res); static struct attr ** eval_list(struct context *ctx); /** * @brief Converts an error to human-readable text. * * @param err The error code * * @return A string containing the error description. The caller is responsible for freeing up the string by * calling {@code g_free()} when it is no longer needed. */ char *command_error_to_text(int err) { switch (err) { case no_error: return g_strdup("no_error"); case missing_double_quote: return g_strdup("missing_double_quote"); case missing_opening_parenthesis: return g_strdup("missing_opening_parenthesis"); case missing_closing_parenthesis: return g_strdup("missing_closing_parenthesis"); case missing_closing_brace: return g_strdup("missing_closing_brace"); case missing_colon: return g_strdup("missing_colon"); case missing_semicolon: return g_strdup("missing_semicolon"); case wrong_type: return g_strdup("wrong_type"); case illegal_number_format: return g_strdup("illegal_number_format"); case illegal_character: return g_strdup("illegal_character"); case missing_closing_bracket: return g_strdup("missing_closing_bracket"); case invalid_type: return g_strdup("invalid_type"); case not_ready: return g_strdup("not_ready"); case internal: return g_strdup("internal"); case eof_reached: return g_strdup("eof_reached"); default: return g_strdup("unknown"); } } static void result_free(struct result *res) { if(res->allocated) { attr_free_content(&res->attr); res->allocated=0; } else { res->attr.type=attr_none; res->attr.u.data=NULL; } } static int command_register_callbacks(struct command_saved *cs); static const char *get_op(struct context *ctx, int test, ...) { char *op; const char *ret=NULL; va_list ap; while (*ctx->expr && g_ascii_isspace(*ctx->expr)) { ctx->expr++; } va_start(ap, test); while ((op = va_arg(ap, char *))) { if (!strncmp(ctx->expr, op, strlen(op))) { ret=op; if (! test) ctx->expr+=strlen(op); break; } } va_end(ap); return ret; } /*static int is_int(struct result *res) { return 1; }*/ static int is_double(struct result *res) { return 0; } static void dump(struct result *res) { #if 0 char object[res->varlen+1]; char attribute[res->attrnlen+1]; if (res->var) strncpy(object, res->var, res->varlen); object[res->varlen]='\0'; if (res->attrn) strncpy(attribute, res->attrn, res->attrnlen); attribute[res->attrnlen]='\0'; dbg(lvl_debug,"type:%s", attr_to_name(res->attr.type)); dbg(lvl_debug,"attribute '%s' from '%s'", attribute, object); #endif } static enum attr_type command_attr_type(struct result *res) { char *attrn=g_alloca(sizeof(char)*(res->attrnlen+1)); strncpy(attrn, res->attrn, res->attrnlen); attrn[res->attrnlen]='\0'; return attr_from_name(attrn); } /** * @brief Retrieves an attribute from an object. * * This function will retrieve the first matching attribute by calling the {@code get_attr} method for * the object type. If {@code object} does not refer to a valid object, or the {@code get_attr} method * for the object type could not be defined, the function fails and zero is returned. * * @param ctx The context (ignored) * @param object The object for which the attribute is to be retrieved. * @param attr_type The type of attribute to retrieve * @param ret Points to a {@code struct attr} to which the attribute will be copied * * @return True if a matching attribute was found, false if no matching attribute was found or an error * occurred */ static int command_object_get_attr(struct context *ctx, struct attr *object, enum attr_type attr_type, struct attr *ret) { int r; struct attr dup; struct object_func *func=object_func_lookup(object->type); if (!object->u.data || !func || !func->get_attr) { dbg(lvl_warning, "cannot retrieve attributes from %s (%p), func=%p", attr_to_name(object->type), object->u.data, func) return 0; } r=func->get_attr(object->u.data, attr_type, &dup, NULL); if(r) attr_dup_content(&dup,ret); else dbg(lvl_warning, "%s (%p) has no attribute %s", attr_to_name(object->type), object->u.data, attr_to_name(attr_type)) return r; } static int command_object_add_attr(struct context *ctx, struct attr *object, struct attr *attr) { struct object_func *func=object_func_lookup(object->type); if (!object->u.data || !func || !func->add_attr) return 0; return func->add_attr(object->u.data, attr); } static int command_object_remove_attr(struct context *ctx, struct attr *object, struct attr *attr) { struct object_func *func=object_func_lookup(object->type); if (!object->u.data || !func || !func->remove_attr) return 0; return func->remove_attr(object->u.data, attr); } /** * @brief Retrieves the current value of an attribute and stores it in {@code res}. * * If {@code ctx->skip} is true, the function aborts and no action is taken. * * Before calling this function, object references in {@code res} must be resolved. That is, * {@code res->attr} holds a copy of {@code ctx->attr} and the first {@code res->attrnlen} characters of * {@code res->attrn} correspond to the object name. * * After this function completes, {@code res->allocated} is true, and {@code res->attrn} and * {@code res->attrnlen} are reset. * * If the attribute was successfully retrieved, the first {@code res->varlen} characters of * {@code res->var} correspond to an object name and {@code res->attr} holds the attribute. * * If the attribute could not be retrieved, {@code res->attr.type} is set to {@code attr_none}, and * {@code res->var} and {@code res->varlen} are reset. * * @param ctx The context * @param res The result */ static void command_get_attr(struct context *ctx, struct result *res) { int result; struct result tmp= {{0,},}; enum attr_type attr_type=command_attr_type(res); enum attr_type parent_type = res->attr.type; /* for debugging only */ if (ctx->skip) return; result=command_object_get_attr(ctx, &res->attr, attr_type, &tmp.attr); result_free(res); *res=tmp; res->allocated=1; if (result) { dbg(lvl_debug, "successfully retrieved '%s' from '%s'", attr_to_name(attr_type), attr_to_name(parent_type)); res->var=res->attrn; res->varlen=res->attrnlen; } else { dbg(lvl_warning, "could not retrieve '%s' from '%s'", attr_to_name(attr_type), attr_to_name(parent_type)); result_free(res); res->attr.type=attr_none; res->var=NULL; res->varlen=0; } res->attrn=NULL; res->attrnlen=0; dump(res); } static void command_set_attr(struct context *ctx, struct result *res, struct result *newres) { enum attr_type attr_type=command_attr_type(res); struct object_func *func=object_func_lookup(res->attr.type); if (ctx->skip) return; if (!res->attr.u.data || !func || !func->set_attr) return; if (attr_type == attr_attr_types) { char *attrn=g_alloca(sizeof(char)*(res->attrnlen+1)); struct attr *tmp; strncpy(attrn, res->attrn, res->attrnlen); attrn[res->attrnlen]='\0'; tmp=attr_new_from_text(attrn, newres->attr.u.str); newres->attr.u.data=tmp->u.data; newres->allocated=1; g_free(tmp); } newres->attr.type=attr_type; func->set_attr(res->attr.u.data, &newres->attr); result_free(res); *res=*newres; } /** * @brief Resolves an object reference. * * Prior to calling this function, {@code res} must contain a valid, unresolved object reference: * {@code res->attr.type} must be {@code attr_none}, and the first {@code res->varlen} characters of * {@code res->var} must correspond to an object name. * * After the function returns, {@code res->attr} holds a copy of {@code ctx->attr} and the first * {@code res->attrnlen} characters of {@code res->attrn} correspond to the object name. * {@code res->var} and {@code res->varlen} are reset. * * @param ctx The context * @param res The result */ static void resolve_object(struct context *ctx, struct result *res) { if (res->attr.type == attr_none && res->varlen) { res->attr=*ctx->attr; res->attrn=res->var; res->attrnlen=res->varlen; res->var=NULL; res->varlen=0; } } /** * @brief Resolves and retrieves an object and stores it in {@code res}. * * Prior to calling this function, {@code res} must contain a valid, unresolved object reference: * {@code res->attr.type} must be {@code attr_none}, and the first {@code res->varlen} characters of * {@code res->var} must correspond to an object name. * * If {@code ctx->skip} is true, the object reference will be resolved but the object will not be * retrieved: the first {@code res->attrnlen} characters of {@code res->attrn} correspond to the object * name after the function returns, while {@code res->var} and {@code res->varlen} are reset. * * If {@code ctx->skip} is false, {@code res->allocated} is true after this function completes. The * object is stored in {@code res->attr} if it was successfully retrieved, otherwise {@code res->var} * and {@code res->varlen} are reset. * * @param ctx The context * @param res The result */ static void resolve(struct context *ctx, struct result *res) { resolve_object(ctx, res); if (res->attrn) command_get_attr(ctx, res); } static double get_double(struct context *ctx, struct result *res) { resolve(ctx, res); return res->val; } /** * @brief Returns an integer or bool representation of the result of an expression * * This function evaluates the result of an expression ({@code res->attr}). * * If {@code res->attr} is of a numeric type, its integer part is returned. * * If {@code is_bool} is false and {@code res->attr} is not of a numeric type, 0 is returned. * * If {@code is_bool} is true and {@code res->attr} is of an object or string type, true is returned * for non-null, false otherwise. * * For all other types of {@code res->attr}, 0 (false) is returned. * * @param ctx The context for the expression * @param is_bool If true, return boolean representation, else return integer representation. See description. * @param res The result of the evaluation * * @return The result of the expression, see description. */ static int get_int_bool(struct context *ctx, int is_bool, struct result *res) { resolve(ctx, res); if (res->attr.type == attr_none) return 0; if (res->attr.type >= attr_type_int_begin && res->attr.type <= attr_type_int_end) { return res->attr.u.num; } if (res->attr.type >= attr_type_double_begin && res->attr.type <= attr_type_double_end) { return (int) (*res->attr.u.numd); } if (is_bool && ATTR_IS_OBJECT(res->attr.type)) return res->attr.u.data != NULL; if (is_bool && ATTR_IS_STRING(res->attr.type)) return res->attr.u.data != NULL; dbg(lvl_debug,"bool %d %s",is_bool,attr_to_name(res->attr.type)); ctx->error=wrong_type; return 0; } /** * @brief Returns an integer representation of the result of an expression * * This function is a wrapper around {@code get_int_bool()}. It is equivalent to * {@code get_int_bool(ctx, 0, res)}. See {@code get_int_bool()} for a description. */ static int get_int(struct context *ctx, struct result *res) { return get_int_bool(ctx, 0, res); } /** * @brief Returns a boolean representation of the result of an expression * * This function is a wrapper around {@code get_int_bool()}. It is equivalent to * {@code get_int_bool(ctx, 1, res)}. See {@code get_int_bool()} for a description. */ static int get_bool(struct context *ctx, struct result *res) { return !!get_int_bool(ctx, 1, res); } static char *get_string(struct context *ctx, struct result *res) { resolve(ctx, res); return attr_to_text(&res->attr, NULL, 0); } static void set_double(struct result *res, double val) { result_free(res); res->attr.type=attr_type_double_begin; res->attr.u.numd=&res->val; res->val=val; } static void set_int(struct result *res, int val) { result_free(res); res->attr.type=attr_type_int_begin; res->attr.u.num=val; } static void result_op(struct context *ctx, enum op_type op_type, const char *op, struct result *inout, struct result *in) { if (ctx->skip) return; switch (op_type) { case op_type_prefix: switch ((op[0] << 8) | op[1]) { case ('!' << 8): set_int(inout, !get_bool(ctx, inout)); return; case ('~' << 8): set_int(inout, ~get_int(ctx, inout)); return; } break; case op_type_binary: resolve(ctx, inout); resolve(ctx, in); switch ((op[0] << 8) | op[1]) { case ('=' << 8)|'=': if (inout->attr.type == attr_none || in->attr.type == attr_none) { set_int(inout, 0); } else if (ATTR_IS_STRING(inout->attr.type) && ATTR_IS_STRING(in->attr.type)) { char *s1=get_string(ctx, inout),*s2=get_string(ctx, in); set_int(inout, (!strcmp(s1,s2))); g_free(s1); g_free(s2); } else if (ATTR_IS_OBJECT(inout->attr.type) && ATTR_IS_OBJECT(in->attr.type)) { set_int(inout, inout->attr.u.data == in->attr.u.data); } else set_int(inout, (get_int(ctx, inout) == get_int(ctx, in))); return; case ('!' << 8)|'=': if (inout->attr.type == attr_none || in->attr.type == attr_none) { set_int(inout, 1); } else if (ATTR_IS_STRING(inout->attr.type) && ATTR_IS_STRING(in->attr.type)) { char *s1=get_string(ctx, inout),*s2=get_string(ctx, in); set_int(inout, (!!strcmp(s1,s2))); g_free(s1); g_free(s2); } else if (ATTR_IS_OBJECT(inout->attr.type) && ATTR_IS_OBJECT(in->attr.type)) { set_int(inout, inout->attr.u.data != in->attr.u.data); } else set_int(inout, (get_int(ctx, inout) != get_int(ctx, in))); return; case ('<' << 8): set_int(inout, (get_int(ctx, inout) < get_int(ctx, in))); return; case ('<' << 8)|'=': set_int(inout, (get_int(ctx, inout) <= get_int(ctx, in))); return; case ('>' << 8): set_int(inout, (get_int(ctx, inout) > get_int(ctx, in))); return; case ('>' << 8)|'=': set_int(inout, (get_int(ctx, inout) >= get_int(ctx, in))); return; case ('*' << 8): if (is_double(inout) || is_double(in)) set_double(inout, get_double(ctx, inout) * get_double(ctx, in)); else set_int(inout, get_int(ctx, inout) * get_int(ctx, in)); return; case ('/' << 8): if (is_double(inout) || is_double(in)) set_double(inout, get_double(ctx, inout) * get_double(ctx, in)); else set_int(inout, get_int(ctx, inout) * get_int(ctx, in)); return; case ('%' << 8): set_int(inout, get_int(ctx, inout) % get_int(ctx, in)); return; case ('+' << 8): if (is_double(inout) || is_double(in)) set_double(inout, get_double(ctx, inout) + get_double(ctx, in)); else if (ATTR_IS_STRING(inout->attr.type) && ATTR_IS_STRING(in->attr.type)) { char *str=g_strdup_printf("%s%s",inout->attr.u.str,in->attr.u.str); result_free(inout); inout->attr.type=attr_type_string_begin; inout->attr.u.str=str; inout->allocated=1; } else set_int(inout, get_int(ctx, inout) + get_int(ctx, in)); return; case ('-' << 8): if (is_double(inout) || is_double(in)) set_int(inout, get_int(ctx, inout) - get_int(ctx, in)); else set_double(inout, get_double(ctx, inout) - get_double(ctx, in)); return; case ('&' << 8): set_int(inout, get_int(ctx, inout) & get_int(ctx, in)); return; case ('^' << 8): set_int(inout, get_int(ctx, inout) ^ get_int(ctx, in)); return; case ('|' << 8): set_int(inout, get_int(ctx, inout) | get_int(ctx, in)); return; case (('&' << 8) | '&'): set_int(inout, get_int(ctx, inout) && get_int(ctx, in)); return; case (('|' << 8) | '|'): set_int(inout, get_int(ctx, inout) || get_int(ctx, in)); return; default: break; } default: break; } dbg(lvl_error,"Unknown op %d %s",op_type,op); ctx->error=internal; } static void result_set(struct context *ctx, enum set_type set_type, const char *op, int len, struct result *out) { if (ctx->skip) return; switch (set_type) { case set_type_symbol: out->attr.type=attr_none; out->var=op; out->varlen=len; return; case set_type_integer: out->attr.type=attr_type_int_begin; out->attr.u.num=atoi(ctx->expr); return; case set_type_float: out->val = strtod(ctx->expr, NULL); out->attr.type=attr_type_double_begin; out->attr.u.numd=&out->val; return; case set_type_string: if (len >= 2) { out->attr.type=attr_type_string_begin; out->attr.u.str=g_malloc(len-1); strncpy(out->attr.u.str, op+1, len-2); out->attr.u.str[len-2]='\0'; out->allocated=1; return; } break; default: break; } dbg(lvl_error,"unknown set type %d %s",set_type,op); ctx->error=internal; } /** * @brief Evaluates a value and stores its result. * * This function evaluates the first value in {@code ctx->expr}. A value can be either an object name * (such as {@code vehicle.position_speed}) or a literal value. * * If evaluation is successful, the result is stored in {@code res->attr}. * * If an object name is encountered, the result has an attribute type of {@code attr_none} and the first * {@code res->varlen} characters of {@code res->var} will point to the object name. * * If a literal value is encountered, the result's attribute type is set to the corresponding generic * data type and its value is stored with the attribute. * * After this function returns, {@code ctx->expr} contains the rest of the expression string, which was * not evaluated. Leading spaces before the value will be discarded with the value. * * If {@code ctx->expr}, after eliminating any leading whitespace, does not begin with a valid value, * one of the following errors is stored in {@code ctx->error}: *
    *
  • {@code illegal_number_format} An illegal number format, such as a second decimal dot, was * encountered.
  • *
  • {@code missing_double_quote} A double quote without a matching second double quote was found.
  • *
  • {@code eof_reached} The expression string is empty.
  • *
  • {@code illegal_character} The expression string begins with a character which is illegal in a * value. This may happen when the expression string begins with an operator.
  • *
* * @param ctx The context to evaluate * @param res Points to a {@code struct res} in which the result will be stored */ static void eval_value(struct context *ctx, struct result *res) { const char *op; int dots=0; result_free(res); res->varlen=0; res->var=NULL; res->attrnlen=0; res->attrn=NULL; while (g_ascii_isspace(*(ctx->expr))) { ctx->expr++; } op = ctx->expr; if ((op[0] >= 'a' && op[0] <= 'z') || (op[0] >= 'A' && op[0] <= 'Z') || op[0] == '_') { const char *s=op; for (;;) { while ((op[0] >= 'a' && op[0] <= 'z') || (op[0] >= 'A' && op[0] <= 'Z') || (op[0] >= '0' && op[0] <= '9') || op[0] == '_') { op++; } if (op-s == 3 && !strncmp(s,"new",3) && op[0] == ' ') { op++; } else break; } result_set(ctx, set_type_symbol, ctx->expr, op-ctx->expr, res); ctx->expr=op; return; } if ((op[0] >= '0' && op[0] <= '9') || (op[0] == '.' && op[1] >= '0' && op[1] <= '9') || (op[0] == '-' && op[1] >= '0' && op[1] <= '9') || (op[0] == '-' && op[1] == '.' && op[2] >= '0' && op[2] <= '9')) { while ((op[0] >= '0' && op[0] <= '9') || op[0] == '.' || (res->varlen == 0 && op[0] == '-')) { if (op[0] == '.') dots++; if (dots > 1) { ctx->error=illegal_number_format; return; } op++; } result_set(ctx, dots?set_type_float:set_type_integer, ctx->expr, op-ctx->expr, res); ctx->expr=op; return; } if (op[0] == '"') { int escaped=0; do { if (op[0] == '\\') { escaped=1; if (op[1] == '"') op++; } op++; } while (op[0] && op[0] != '"'); if(!*op) { ctx->error=missing_double_quote; return; } op++; if (escaped) { char *tmpstr=g_malloc(op-ctx->expr+1),*s=tmpstr; op=ctx->expr; do { if (op[0] == '\\') op++; *s++=*op++; } while (op[0] != '"'); *s++=*op++; result_set(ctx, set_type_string, tmpstr, s-tmpstr, res); g_free(tmpstr); } else result_set(ctx, set_type_string, ctx->expr, op-ctx->expr, res); ctx->expr=op; return; } if (!*op) ctx->error=eof_reached; else { /* * If we get here, ctx->expr does not begin with a variable or a literal value. This is not an * error if this function is being called to test if an expression begins with a value. */ dbg(lvl_debug, "character 0x%x is illegal in a value",*op); ctx->error=illegal_character; } } /** * @brief Retrieves the next object reference from an expression. * * This function scans the expression string {@code ctx->expr} for the next object reference. Anything * other than an object reference (whitespace characters, literal values, operators and even illegal * characters) is discarded until either the end of the string is reached or an object reference is * encountered. * * After this function completes successfully, {@code res->attr.type} is {@code attr_none} and the first * {@code res->varlen} characters of {@code res->var} point to the object name. * * Object names retrieved by this function are unqualified, i.e. {@code vehicle.position_speed} will be * retrieved as {@code vehicle} on the first call (return value 2) and {@code position_speed} on the * second call (return value 1). * * @param ctx The context * @param res Points to a {@code struct result} where the result will be stored. * * @return If a complete object name has been retrieved, the return value is 1. If a partial object name * has been retrieved (e.g. {@code vehicle} from {@code vehicle.position_speed}), the return value is 2. * If no object references were found, the return value is 0. */ static int get_next_object(struct context *ctx, struct result *res) { while (*ctx->expr) { res->varlen = 0; ctx->error = 0; eval_value(ctx,res); if ((res->attr.type == attr_none) && (res->varlen > 0)) { if (ctx->expr[0] != '.') { return 1; // 1 means "this is the final object name" } else { return 2; // 2 means "there are more object names following" (e.g. we just hit 'vehicle' in 'vehicle.position_speed' } } if (ctx->error) { // Probably hit an operator ctx->expr++; } } return 0; } static void eval_brace(struct context *ctx, struct result *res) { if (get_op(ctx,0,"(",NULL)) { eval_comma(ctx, res); if (ctx->error) return; if (!get_op(ctx,0,")",NULL)) ctx->error=missing_closing_parenthesis; return; } eval_value(ctx, res); } static void command_call_function(struct context *ctx, struct result *res) { struct attr cbl,**list=NULL; char *function=g_alloca(sizeof(char)*(res->attrnlen+1)); if (res->attrn) strncpy(function, res->attrn, res->attrnlen); function[res->attrnlen]='\0'; dbg(lvl_debug,"function=%s", function); if (ctx->expr[0] != ')') { list=eval_list(ctx); if (ctx->error) { attr_list_free(list); return; } } if (!get_op(ctx,0,")",NULL)) { attr_list_free(list); ctx->error=missing_closing_parenthesis; return; } if (!ctx->skip) { if (!strcmp(function,"_") && list && list[0] && list[0]->type >= attr_type_string_begin && list[0]->type <= attr_type_string_end) { result_free(res); res->attr.type=list[0]->type; res->attr.u.str=g_strdup(navit_nls_gettext(list[0]->u.str)); res->allocated=1; } else if (!strncmp(function,"new ",4)) { enum attr_type attr_type=attr_from_name(function+4); result_free(res); if (ATTR_IS_INT(attr_type)) { if (list && list[0] && ATTR_IS_INT(list[0]->type)) { res->attr.type=attr_type; res->attr.u.num=list[0]->u.num; res->allocated=0; } else { dbg(lvl_error,"don't know how to create int of args"); } } else if (ATTR_IS_STRING(attr_type)) { if (list && list[0] && ATTR_IS_STRING(list[0]->type)) { res->attr.type=attr_type; res->attr.u.str=g_strdup(list[0]->u.str); res->allocated=1; } else { dbg(lvl_error,"don't know how to create string of args"); } } else if (ATTR_IS_OBJECT(attr_type)) { struct object_func *func=object_func_lookup(attr_type); if (func && func->create) { res->attr.type=attr_type; res->attr.u.data=func->create(list[0], list+1); /* Setting allocated to 1 here will make object to be destroyed when last reference is destroyed. So created persistent objects should be stored with set_attr_var command. */ res->allocated=1; } } else { dbg(lvl_error,"don't know how to create %s (%s)",attr_to_name(attr_type),function+4); } } else if (!strcmp(function,"add_attr")) { command_object_add_attr(ctx, &res->attr, list[0]); } else if (!strcmp(function,"remove_attr")) { command_object_remove_attr(ctx, &res->attr, list[0]); } else { if (command_object_get_attr(ctx, &res->attr, attr_callback_list, &cbl)) { int valid =0; struct attr **out=NULL; dbg(lvl_debug,"function call %s from %s",function, attr_to_name(res->attr.type)); callback_list_call_attr_4(cbl.u.callback_list, attr_command, function, list, &out, &valid); if (valid!=1) { dbg(lvl_error, "invalid command ignored: \"%s\"; see http://wiki.navit-project.org/index.php/" "OSD#Navit_commands for valid commands.", function); } if (out && out[0]) { result_free(res); attr_dup_content(out[0], &res->attr); res->allocated=1; attr_list_free(out); } else result_free(res); } else result_free(res); } } attr_list_free(list); res->var=NULL; res->varlen=0; res->attrn=NULL; res->attrnlen=0; } static void eval_postfix(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; const char *op; eval_brace(ctx, res); if (ctx->error) return; for (;;) { if (!(op=get_op(ctx,0,"[","(",".",NULL))) return; if (op[0] == '.') { eval_brace(ctx, &tmp); if (ctx->error) return; resolve(ctx, res); if (ctx->error) return; res->attrn=tmp.var; res->attrnlen=tmp.varlen; dump(res); } else if (op[0] == '[') { resolve_object(ctx, res); if (ctx->error) return; if (get_op(ctx,0,"@",NULL)) { if (!ctx->skip) { struct object_func *obj_func=object_func_lookup(res->attr.type); struct attr_iter *iter; struct attr attr; enum attr_type attr_type=command_attr_type(res); void *obj=res->attr.u.data; if (!obj) { dbg(lvl_error,"no object"); return; } if (!obj_func) { dbg(lvl_error,"no object func"); return; } if (!obj_func->iter_new || !obj_func->iter_destroy) { dbg(lvl_error,"no iter func"); return; } iter = obj_func->iter_new(NULL); result_free(res); res->varlen=0; res->attrn=NULL; while (obj_func->get_attr(obj, attr_type, &attr, iter)) { if (command_evaluate_to_boolean(&attr, ctx->expr, &ctx->error)) { result_free(res); res->attr=attr; } } obj_func->iter_destroy(iter); } if (ctx->error) return; ctx->expr+=command_evaluate_to_length(ctx->expr, &ctx->error); } if (!get_op(ctx,0,"]",NULL)) { ctx->error=missing_closing_bracket; return; } } else if (op[0] == '(') { dbg(lvl_debug,"function call"); resolve_object(ctx, res); command_call_function(ctx, res); } } } static void eval_unary(struct context *ctx, struct result *res) { const char *op; op=get_op(ctx,0,"~","!",NULL); if (op) { eval_unary(ctx, res); if (ctx->error) return; result_op(ctx, op_type_prefix, op, res, NULL); } else eval_postfix(ctx, res); } static void eval_multiplicative(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; const char *op; eval_unary(ctx, res); if (ctx->error) return; for (;;) { if (!(op=get_op(ctx,0,"*","/","%",NULL))) return; eval_unary(ctx, &tmp); if (!ctx->error) result_op(ctx, op_type_binary, op, res, &tmp); result_free(&tmp); if (ctx->error) return; } } static void eval_additive(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; const char *op; eval_multiplicative(ctx, res); if (ctx->error) return; for (;;) { if (!(op=get_op(ctx,0,"-","+",NULL))) return; eval_multiplicative(ctx, &tmp); if (!ctx->error) result_op(ctx, op_type_binary, op, res, &tmp); result_free(&tmp); if (ctx->error) return; } } static void eval_equality(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; const char *op; eval_additive(ctx, res); if (ctx->error) return; for (;;) { if (!(op=get_op(ctx,0,"==","!=","<=",">=","<",">",NULL))) return; eval_additive(ctx, &tmp); if (!ctx->error) result_op(ctx, op_type_binary, op, res, &tmp); result_free(&tmp); if (ctx->error) return; } } static void eval_bitwise_and(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; eval_equality(ctx, res); if (ctx->error) return; for (;;) { if (get_op(ctx,1,"&&",NULL)) return; if (!get_op(ctx,0,"&",NULL)) return; eval_equality(ctx, &tmp); if (!ctx->error) result_op(ctx, op_type_binary, "&", res, &tmp); result_free(&tmp); if (ctx->error) return; } } static void eval_bitwise_xor(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; eval_bitwise_and(ctx, res); if (ctx->error) return; for (;;) { if (!get_op(ctx,0,"^",NULL)) return; eval_bitwise_and(ctx, &tmp); if (!ctx->error) result_op(ctx, op_type_binary, "^", res, &tmp); result_free(&tmp); if (ctx->error) return; } } static void eval_bitwise_or(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; eval_bitwise_xor(ctx, res); if (ctx->error) return; for (;;) { if (get_op(ctx,1,"||",NULL)) return; if (!get_op(ctx,0,"|",NULL)) return; eval_bitwise_xor(ctx, &tmp); if (!ctx->error) result_op(ctx, op_type_binary, "|", res, &tmp); result_free(&tmp); if (ctx->error) return; } } static void eval_logical_and(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; eval_bitwise_or(ctx, res); if (ctx->error) return; for (;;) { if (!get_op(ctx,0,"&&",NULL)) return; eval_bitwise_or(ctx, &tmp); if (!ctx->error) result_op(ctx, op_type_binary, "&&", res, &tmp); result_free(&tmp); if (ctx->error) return; } } static void eval_logical_or(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; eval_logical_and(ctx, res); if (ctx->error) return; for (;;) { if (!get_op(ctx,0,"||",NULL)) return; eval_logical_and(ctx, &tmp); if (!ctx->error) result_op(ctx, op_type_binary, "||", res, &tmp); result_free(&tmp); if (ctx->error) return; } } static void eval_conditional(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; int cond=0; int skip; eval_logical_or(ctx, res); if (ctx->error) return; if (!get_op(ctx,0,"?",NULL)) return; skip=ctx->skip; cond=get_bool(ctx, res); result_free(res); if (ctx->error) return; ctx->skip=!cond || skip; eval_logical_or(ctx, &tmp); ctx->skip=skip; if (ctx->error) { result_free(&tmp); return; } *res=tmp; memset(&tmp,0,sizeof(tmp)); if (!get_op(ctx,0,":",NULL)) { dbg(lvl_debug,"ctxerr"); ctx->error=missing_colon; return; } ctx->skip=cond || skip; eval_logical_or(ctx, &tmp); ctx->skip=skip; if (ctx->error) { result_free(&tmp); return; } if (!cond) { result_free(res); *res=tmp; } else result_free(&tmp); } /* = *= /= %= += -= >>= <<= &= ^= |= */ static void eval_assignment(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; eval_conditional(ctx, res); if (ctx->error) return; if (!get_op(ctx,0,"=",NULL)) return; eval_conditional(ctx, &tmp); if (ctx->error) { result_free(&tmp); return; } resolve(ctx, &tmp); if (ctx->error) { result_free(&tmp); return; } resolve_object(ctx, res); command_set_attr(ctx, res, &tmp); } /* , */ static void eval_comma(struct context *ctx, struct result *res) { struct result tmp= {{0,},}; eval_assignment(ctx, res); if (ctx->error) return; for (;;) { if (!get_op(ctx,0,",",NULL)) return; eval_assignment(ctx, &tmp); if (ctx->error) return; result_free(res); *res=tmp; } } static struct attr ** eval_list(struct context *ctx) { struct result tmp= {{0,},}; struct attr **ret=NULL; for (;;) { eval_assignment(ctx, &tmp); if (ctx->error) { result_free(&tmp); attr_list_free(ret); return NULL; } resolve(ctx, &tmp); ret=attr_generic_add_attr(ret, &tmp.attr); result_free(&tmp); if (!get_op(ctx,0,",",NULL)) return ret; } } #if 0 void command(struct attr *attr, char *expr) { struct result res; struct context ctx; memset(&res, 0, sizeof(res)); memset(&ctx, 0, sizeof(ctx)); ctx.attr=attr; ctx.error=0; ctx.expr=expr; printf("command='%s'\n", expr); eval_comma(&ctx,&res); printf("err=%d %s\n", ctx.error, ctx.expr); dump(&res); printf("***\n"); resolve(&ctx, &res); dump(&res); printf("%s\n", get_string(&ctx, &res)); } #endif static void command_evaluate_to(struct attr *attr, const char *expr, struct context *ctx, struct result *res) { result_free(res); memset(res, 0, sizeof(*res)); memset(ctx, 0, sizeof(*ctx)); ctx->attr=attr; ctx->expr=expr; eval_comma(ctx,res); } enum attr_type command_evaluate_to_attr(struct attr *attr, char *expr, int *error, struct attr *ret) { struct result res= {{0,},}; struct context ctx; command_evaluate_to(attr, expr, &ctx, &res); if (ctx.error) return attr_none; resolve_object(&ctx, &res); *ret=res.attr; dbg(lvl_debug,"type %s",attr_to_name(command_attr_type(&res))); return command_attr_type(&res); } void command_evaluate_to_void(struct attr *attr, char *expr, int *error) { struct result res= {{0,},}; struct context ctx; command_evaluate_to(attr, expr, &ctx, &res); if (!ctx.error) resolve(&ctx, &res); if (error) *error=ctx.error; result_free(&res); } char *command_evaluate_to_string(struct attr *attr, char *expr, int *error) { struct result res= {{0,},}; struct context ctx; char *ret=NULL; command_evaluate_to(attr, expr, &ctx, &res); if (!ctx.error) resolve(&ctx, &res); if (!ctx.error) ret=get_string(&ctx, &res); if (error) *error=ctx.error; result_free(&res); if (ctx.error) return NULL; else return ret; } int command_evaluate_to_int(struct attr *attr, char *expr, int *error) { struct result res= {{0,},}; struct context ctx; int ret=0; command_evaluate_to(attr, expr, &ctx, &res); if (!ctx.error) resolve(&ctx, &res); if (!ctx.error) ret=get_int(&ctx, &res); if (error) *error=ctx.error; result_free(&res); if (ctx.error) return 0; else return ret; } int command_evaluate_to_boolean(struct attr *attr, const char *expr, int *error) { struct result res= {{0,},}; struct context ctx; int ret=0; command_evaluate_to(attr, expr, &ctx, &res); if (!ctx.error) resolve(&ctx, &res); if (!ctx.error) { if (res.attr.type == attr_none) ret=0; else if ((res.attr.type >= attr_type_int_begin && res.attr.type <= attr_type_int_end) || (res.attr.type >= attr_type_double_begin && res.attr.type <= attr_type_double_end)) ret=get_int(&ctx, &res); else ret=res.attr.u.data != NULL; } result_free(&res); if (error) *error=ctx.error; if (ctx.error) return 0; else return ret; } int command_evaluate_to_length(const char *expr, int *error) { struct attr attr; struct result res= {{0,},}; struct context ctx; attr.type=attr_none; attr.u.data=NULL; command_evaluate_to(&attr, expr, &ctx, &res); result_free(&res); if (!ctx.error) return ctx.expr-expr; return 0; } static int command_evaluate_single(struct context *ctx) { struct result res= {{0,},},tmp= {{0,},}; const char *op,*a,*f,*end; enum attr_type attr_type; void *obj; struct object_func *obj_func; struct attr_iter *iter; struct attr attr; int cond=0; int skip=ctx->skip; if (!(op=get_op(ctx,0,"foreach","if","{",NULL))) { eval_comma(ctx,&res); if (ctx->error) return 0; resolve(ctx, &res); result_free(&res); if (ctx->error) return 0; return get_op(ctx,0,";",NULL) != NULL; } switch (op[0]) { case 'f': if (!get_op(ctx,0,"(",NULL)) { ctx->error=missing_opening_parenthesis; return 0; } ctx->skip=1; a=ctx->expr; eval_conditional(ctx, &res); resolve_object(ctx, &res); ctx->skip=skip; if (!get_op(ctx,0,";",NULL)) { ctx->error=missing_semicolon; return 0; } eval_comma(ctx,&res); attr_type=command_attr_type(&res); obj=res.attr.u.data; obj_func=object_func_lookup(res.attr.type); if (!get_op(ctx,0,")",NULL)) { ctx->error=missing_closing_parenthesis; return 0; } f=ctx->expr; ctx->skip=1; if (!command_evaluate_single(ctx)) { ctx->skip=skip; return 0; } ctx->skip=skip; if (ctx->skip) { result_free(&res); return 1; } end=ctx->expr; if (!obj) { dbg(lvl_error,"no object"); return 0; } if (!obj_func) { dbg(lvl_error,"no object func"); return 0; } if (!obj_func->iter_new || !obj_func->iter_destroy) { dbg(lvl_error,"no iter func"); return 0; } iter = obj_func->iter_new(NULL); while (obj_func->get_attr(obj, attr_type, &attr, iter)) { ctx->expr=a; eval_conditional(ctx, &res); resolve_object(ctx, &res); tmp.attr=attr; resolve(ctx, &tmp); if (ctx->error) { result_free(&tmp); return 0; } command_set_attr(ctx, &res, &tmp); result_free(&tmp); ctx->expr=f; if (!command_evaluate_single(ctx)) { obj_func->iter_destroy(iter); return 0; } } obj_func->iter_destroy(iter); ctx->expr=end; return 1; case 'i': if (!get_op(ctx,0,"(",NULL)) { ctx->error=missing_opening_parenthesis; return 0; } eval_comma(ctx,&res); if (!skip && !ctx->error) cond=get_bool(ctx, &res); result_free(&res); if (ctx->error) return 0; if (!get_op(ctx,0,")",NULL)) { ctx->error=missing_closing_parenthesis; return 0; } ctx->skip=!cond || skip; command_evaluate_single(ctx); ctx->skip=skip; if (ctx->error) return 0; if (get_op(ctx,0,"else",NULL)) { ctx->skip=cond || skip; command_evaluate_single(ctx); ctx->skip=skip; if (ctx->error) return 0; } return 1; case '{': while (!get_op(ctx,0,"}",NULL)) { if (!command_evaluate_single(ctx)) return 0; } return 1; default: return 0; } } void command_evaluate(struct attr *attr, const char *expr) { /* Once the eval has started we can't rely anymore on the content of * expr which may be freed when the calling widget is destroyed by a * subsequent command call. Hence the g_strdup. */ char *expr_dup; char *err = NULL; /* Error description */ struct context ctx= {0,}; ctx.attr=attr; ctx.error=0; ctx.expr=expr_dup=g_strdup(expr); for (;;) { if (!command_evaluate_single(&ctx)) break; } if (ctx.error && ctx.error != eof_reached) { char expr[32]; strncpy(expr, ctx.expr, 32); expr[31]='\0'; err = command_error_to_text(ctx.error); dbg(lvl_error, "error %s starting at %s", err, expr); g_free(err); } g_free(expr_dup); } #if 0 void command_interpreter(struct attr *attr) { char buffer[4096]; int size; for (;;) { size=read(0, buffer, 4095); buffer[size]='\0'; if (size) { buffer[size-1]='\0'; } command(attr, buffer); } } #endif static void command_table_call(struct command_table *table, int count, void *data, char *command, struct attr **in, struct attr ***out, int *valid) { int i; for (i = 0 ; i < count ; i++) { if (!strcmp(command,table->command)) { if (valid) *valid=1; table->func(data, command, in, out); } table++; } } void command_add_table_attr(struct command_table *table, int count, void *data, struct attr *attr) { attr->type=attr_callback; attr->u.callback=callback_new_attr_3(callback_cast(command_table_call),attr_command, table, count, data); } void command_add_table(struct callback_list *cbl, struct command_table *table, int count, void *data) { struct attr attr; command_add_table_attr(table, count, data, &attr); callback_list_add(cbl, attr.u.callback); } void command_saved_set_cb (struct command_saved *cs, struct callback *cb) { cs->cb = cb; } /** * @brief Returns an integer representation of the evaluation result of a saved command * * This function is a wrapper around {@code get_int()}. It is equivalent to * {@code get_int(&cs->ctx, &cs->res)}. See {@code get_int()} for a description. */ int command_saved_get_int (struct command_saved *cs) { return get_int(&cs->ctx, &cs->res); } int command_saved_error (struct command_saved *cs) { return cs->error; } /** * @brief Idle function to evaluate a command * * This function is called from an idle loop for asynchronous evaluation but may also be called in-line. * * The result of the evaluation can be retrieved from {@code cs->res} after this function returns. If an * error occurred, it will be stored in {@code cs->error}. * * @param cs The command to evaluate */ static void command_saved_evaluate_idle (struct command_saved *cs) { dbg(lvl_debug, "enter: cs=%p, cs->command=%s", cs, cs->command); // Only run once at a time if (cs->idle_ev) { event_remove_idle(cs->idle_ev); cs->idle_ev = NULL; } command_evaluate_to(&cs->context_attr, cs->command, &cs->ctx, &cs->res); if (!cs->ctx.error) { cs->error = 0; if (cs->cb) { callback_call_1(cs->cb, cs); } } else { cs->error = cs->ctx.error; } } /** * @brief Evaluates a command * * This function examines {@code cs->async} to determine if the command should be evaluated immediately. * If {@code cs->async} is true, an idle event is registered to register the command. Else the command * is evaluated immediately and the result can be retrieved immediately after this function returns. * * See {@link command_saved_evaluate_idle(struct command_saved *)} for details. * * @param cs The command to evaluate */ static void command_saved_evaluate(struct command_saved *cs) { dbg(lvl_debug, "enter: cs=%p, cs->async=%d, cs->command=%s", cs, cs->async, cs->command); if (!cs->async) { command_saved_evaluate_idle(cs); return; } if (cs->idle_ev) { // We're already scheduled for reevaluation return; } if (!cs->idle_cb) { cs->idle_cb = callback_new_1(callback_cast(command_saved_evaluate_idle), cs); } cs->idle_ev = event_add_idle(100, cs->idle_cb); } /** * @brief Recreates all callbacks for a saved command * * @param cs The saved command */ static void command_saved_callbacks_changed(struct command_saved *cs) { // For now, we delete each and every callback and then re-create them int i; struct object_func *func; struct attr attr; dbg(lvl_debug, "enter: cs=%p, cs->async=%d, cs->command=%s", cs, cs->async, cs->command); if (cs->register_ev) { event_remove_idle(cs->register_ev); cs->register_ev = NULL; } attr.type = attr_callback; for (i = 0; i < cs->num_cbs; i++) { func = object_func_lookup(cs->cbs[i].attr.type); if (!func->remove_attr) { dbg(lvl_error, "Could not remove command-evaluation callback because remove_attr is missing for type %i!", cs->cbs[i].attr.type); continue; } attr.u.callback = cs->cbs[i].cb; func->remove_attr(cs->cbs[i].attr.u.data, &attr); callback_destroy(cs->cbs[i].cb); } g_free(cs->cbs); cs->cbs = NULL; cs->num_cbs = 0; // Now, re-create all the callbacks command_register_callbacks(cs); } /** * @brief Registers callbacks for a saved command * * This function registers callbacks for each attribute used in a saved command, causing the command to * be re-evaluated whenever its value might change. * * This function will fail if an object used in the expression could not be resolved. This may happen * during startup if this function is called before all objects have been created. In this case, the * caller should schedule the function to be called again at a later time. * * It will also fail if an error is encountered. This can be determined by examining * {@code cs->ctx.error} after the function returns. * * @param cs The command * * @return True if all callbacks were successfully registered, false if the function failed */ static int command_register_callbacks(struct command_saved *cs) { struct attr prev; /* The parent of the next object which will be retrieved. */ struct attr cb_attr; int status; struct object_func *func; struct callback *cb; int tmpoffset; /* For debugging. Because we work with pointers into the same string instance. * we can figure out offsets by using simple pointer arithmetics. */ dbg(lvl_debug, "enter: cs=%p, cs->async=%d, cs->command=%s", cs, cs->async, cs->command); cs->ctx.expr = cs->command; prev = cs->context_attr; while ((status = get_next_object(&cs->ctx, &cs->res)) != 0) { tmpoffset = cs->res.var - cs->command; cs->ctx.attr = &prev; resolve(&cs->ctx, &cs->res); if (cs->ctx.error) { /* An error occurred while parsing the command */ tmpoffset = cs->ctx.expr - cs->command; dbg(lvl_error, "parsing error: cs=%p, cs->ctx.error=%d", cs, cs->ctx.error); dbg(lvl_error, "\t%s", cs->command); dbg(lvl_error, "\t%*s", tmpoffset + 1, "^"); return 0; } else if (cs->res.attr.type == attr_none) { /* We could not resolve an object, perhaps because it has not been created */ dbg(lvl_error, "could not resolve object in cs=%p:", cs); dbg(lvl_error, "\t%s", cs->command); dbg(lvl_error, "\t%*s", tmpoffset + 1, "^"); return 0; } if (prev.type != attr_none) { func = object_func_lookup(prev.type); if (func->add_attr) { if (status == 2) { // This is not the final attribute name cb = callback_new_attr_1(callback_cast(command_saved_callbacks_changed), cs->res.attr.type, (void*)cs); } else if (status == 1) { // This is the final attribute name cb = callback_new_attr_1(callback_cast(command_saved_evaluate), cs->res.attr.type, (void*)cs); cs->ctx.attr = &cs->context_attr; } else { dbg(lvl_error, "Error: Strange status returned from get_next_object()"); } cs->num_cbs++; cs->cbs = g_realloc(cs->cbs, (sizeof(struct command_saved_cb) * cs->num_cbs)); cs->cbs[cs->num_cbs-1].cb = cb; cs->cbs[cs->num_cbs-1].attr = prev; cb_attr.u.callback = cb; cb_attr.type = attr_callback; func->add_attr(prev.u.data, &cb_attr); } else { dbg(lvl_error, "Could not add callback because add_attr is missing for type %i", prev.type); } } if (status == 2) { prev = cs->res.attr; } else { prev = cs->context_attr; } } command_saved_evaluate_idle(cs); dbg(lvl_debug, "done: cs=%p, cs->command=%s", cs, cs->command); return 1; } /** * @brief Creates a new saved command. * * @param command The command string * @param attr The context attribute for the saved command * @param cb The callback to call whenver the command is re-evaluated * @param async Whether the saved command should be flagged as asynchronous, causing it to be evaluated * in an idle callback */ struct command_saved * command_saved_attr_new(char *command, struct attr *attr, struct callback *cb, int async) { struct command_saved *ret; ret = g_new0(struct command_saved, 1); dbg(lvl_debug, "enter, ret=%p, command=%s", ret, command); ret->command = g_strdup(command); ret->context_attr = *attr; ret->cb = cb; ret->error = not_ready; ret->async = async; if (!command_register_callbacks(ret)) { // We try this as an idle call again dbg(lvl_debug, "could not register callbacks, will retry as an idle call"); ret->register_cb = callback_new_1(callback_cast(command_saved_callbacks_changed), ret); ret->register_ev = event_add_idle(300, ret->register_cb); } return ret; } struct command_saved * command_saved_new(char *command, struct navit *navit, struct callback *cb, int async) { struct attr attr=ATTR_OBJECT(navit, navit); return command_saved_attr_new(command, &attr, cb, async); } void command_saved_destroy(struct command_saved *cs) { g_free(cs->command); g_free(cs); }