From adae1f62934311b371701ac6228b3c87f1698e0b Mon Sep 17 00:00:00 2001 From: Marc Payne Date: Sat, 9 Jan 2016 10:19:48 -0700 Subject: [PATCH] Refactor the exec callback system The main purpose of this commit is to ensure that exec callbacks are registered at startup (or config reload), so that they have a chance to run on the first update interval. Much of the code in exec.cc got consolidated, making it easier to follow. Several checks for NULL pointers should eliminate some obscure segfaults I noticed, as well. During the refactoring, I implemented height and width arguments for execbar and execgauge. The functionality was already there, but wasn't being used. All this is accompanied by updates to the man page and plenty of code comments. --- doc/config_settings.xml | 34 +++-- doc/variables.xml | 145 ++++++++++++------- src/core.cc | 95 +++++++------ src/exec.cc | 304 ++++++++++++++++++++++++---------------- src/exec.h | 51 ++++++- src/text_object.h | 28 +++- 6 files changed, 417 insertions(+), 240 deletions(-) diff --git a/doc/config_settings.xml b/doc/config_settings.xml index f0d91c54..461593e8 100644 --- a/doc/config_settings.xml +++ b/doc/config_settings.xml @@ -99,8 +99,8 @@ - Specify a default height for bars. This is particularly useful for execbar and - execibar as they do not take size arguments. + Specify a default height for bars. If not specified, + the default value is 6. @@ -109,8 +109,13 @@ - Specify a default width for bars. This is particularly useful for execbar and - execibar as they do not take size arguments. + Specify a default width for bars. If not specified, + the default value is 0, which causes the bar to expand to + fit the width of your Conky window. If you set + out_to_console = true, the text version of the bar will + actually have no width and you will need to set a + sensible default or set the height and width of each bar + individually. @@ -128,8 +133,8 @@ - Specify a default height for gauges. This is particularly useful for execgauge - and execigauge as they do not take size arguments + Specify a default height for gauges. If not specified, + the default value is 25. @@ -138,8 +143,8 @@ - Specify a default width for gauges. This is particularly useful for execgauge - and execigauge as they do not take size arguments + Specify a default width for gauges. If not specified, + the default value is 40. @@ -148,8 +153,8 @@ - Specify a default height for graphs. This is particularly useful for execgraph - and execigraph as they do not take size arguments + Specify a default height for graphs. If not specified, + the default value is 25. @@ -158,8 +163,13 @@ - Specify a default width for graphs. This is particularly useful for execgraph - and execigraph as they do not take size arguments + Specify a default width for graphs. If not specified, + the default value is 0, which causes the graph to expand to + fit the width of your Conky window. If you set + out_to_console = true, the text version of the graph will + actually have no width and you will need to set a + sensible default or set the height and width of each graph + individually. diff --git a/doc/variables.xml b/doc/variables.xml index e847ae64..1c859486 100644 --- a/doc/variables.xml +++ b/doc/variables.xml @@ -1118,9 +1118,9 @@ Executes a shell command and displays the output - in conky. warning: this takes a lot more resources than - other variables. I'd recommend coding wanted behaviour in C - and posting a patch. + in conky. Warning: this takes a lot more resources than + other variables. I'd recommend coding wanted behaviour in C/C++ + and posting a patch. @@ -1128,12 +1128,14 @@ + - Same as exec, except if the first value return is - a value between 0-100, it will use that number for a bar. - The size for bars can be controlled via the - default_bar_size config setting. + Same as exec, except if the first value returned is + a value between 0-100, it will use that number to draw a + horizontal bar. The height and width parameters are optional, + and default to the default_bar_height and default_bar_width + config settings, respectively. @@ -1141,12 +1143,15 @@ + - Same as exec, except if the first value returned - is a value between 0-100, it will use that number for a - gauge. The size for gauges can be controlled via the - default_gauge_size config setting. + Same as exec, except if the first value returned is + a value between 0-100, it will use that number to draw a + round gauge (much like a vehicle speedometer). The height and + width parameters are optional, and default to the + default_gauge_height and default_gauge_width config settings, + respectively. @@ -1154,33 +1159,61 @@ - + + + + + + + - Same as execbar, but graphs values. Uses a - logaritmic scale when the log option (-l switch) is given - (to see small numbers). Values still have to be between 0 - and 100. The size for graphs can be controlled via the - default_graph_size config setting. Takes the switch '-t' to - use a temperature gradient, which makes the gradient values - change depending on the amplitude of a particular graph - value (try it and see). If -t or -l is your first argument, - you may need to preceed it by a space (' '). You may also use - double-quotes around the exec argument should you need to execute a - command with spaces. For example, ${execgraph "date +'%S'"} to execute - `date +'%S'` and graph the result. Without quotes, it would simply - print the result of `date`. - + + Draws a horizontally scrolling graph with values + from 0-100 plotted on the vertical axis. All parameters + following the command are optional. Gradient colors can + be specified as hexadecimal values with no 0x or # + prefix. Use the -t switch to enable a temperature + gradient, so that small values are "cold" with color 1 + and large values are "hot" with color 2. Without the -t + switch, the colors produce a horizontal gradient + spanning the width of the graph. The scale parameter + defines the maximum value of the graph. Use the -l + switch to enable a logarithmic scale, which helps to see + small values. The default size for graphs can be + controlled via the default_graph_height and + default_graph_width config settings. + + If you need to execute a command with spaces, you + have a couple options: 1) put your command into a + separate file, such as ~/bin/myscript.sh, and use that + as your execgraph command. Remember to make your script + executable! 2) Wrap your command in double-quotes. If + you go for this option, do not try to pass the extra + parameters for height, width, etc., as the results may + be unexpected. + + Option 1 is preferred. In the following example, we + set up execgraph to display seconds (0-59) on a graph that + is 50px high and 200px wide, using a temperature gradient + with colors ranging from red for small values (FF0000) to + yellow for large values (FFFF00). We set the scale to + 60. + + ${execgraph ~/seconds.sh 50,200 FF0000 + FFFF00 60 -t} + - + + - Same as exec but with specific interval. Interval - can't be less than update_interval in configuration. See - also $texeci + Same as exec, but with a specific interval in + seconds. The interval can't be less than the update_interval + in your configuration. See also $texeci. @@ -1188,9 +1221,11 @@ - + + + - Same as execbar, except with an interval + Same as execbar, but with an interval. @@ -1198,10 +1233,11 @@ - + + + - Same as execgauge, but takes an interval arg and - gauges values. + Same as execgauge, but with an interval. @@ -1209,11 +1245,16 @@ - + + + + + + + + - Same as execgraph, but takes an interval arg and - graphs values. If -t or -l is your first argument, you may - need to preceed it by a space (' '). + Same as execgraph, but with an interval. @@ -1224,8 +1265,8 @@ Executes a shell command and displays the output - in conky. warning: this takes a lot more resources than - other variables. I'd recommend coding wanted behaviour in C + in conky. Warning: this takes a lot more resources than + other variables. I'd recommend coding wanted behaviour in C/C++ and posting a patch. This differs from $exec in that it parses the output of the command, so you can insert things like ${color red}hi!${color} in your script and have it @@ -1235,7 +1276,7 @@ like $execi within an $execp statement, it will functionally run at the same interval that the $execp statement runs, as it is created and destroyed at every - interval. + interval. @@ -1243,12 +1284,12 @@ - + + - Same as execp but with specific interval. - Interval can't be less than update_interval in - configuration. Note that the output from the $execpi - command is still parsed and evaluated at every interval. + Same as execp, but with an interval. Note that + the output from the $execpi command is still parsed + and evaluated at every interval. @@ -3769,19 +3810,20 @@ - + + Runs a command at an interval inside a thread and displays the output. Same as $execi, except the command is run inside a thread. Use this if you have a slow script to keep Conky updating. You should make the interval slightly - longer then the time it takes your script to execute. For + longer than the time it takes your script to execute. For example, if you have a script that take 5 seconds to execute, you should make the interval at least 6 seconds. See also $execi. This object will clean up the thread when it is destroyed, so it can safely be used in a nested fashion, though it may not produce the desired behaviour if - used this way. + used this way. @@ -3789,7 +3831,8 @@ - + + Same as execpi, except the command is run inside a thread. diff --git a/src/core.cc b/src/core.cc index 56eb8207..4ffa2295 100644 --- a/src/core.cc +++ b/src/core.cc @@ -742,69 +742,79 @@ struct text_object *construct_text_object(char *s, const char *arg, scan_no_update(obj, arg); obj->callbacks.print = &print_no_update; obj->callbacks.free = &free_no_update; - END OBJ(exec, 0) - scan_exec_arg(obj, arg); + END OBJ_ARG(exec, 0, "exec needs arguments: ") + scan_exec_arg(obj, arg, EF_EXEC); obj->parse = false; obj->thread = false; + register_exec(obj); obj->callbacks.print = &print_exec; obj->callbacks.free = &free_exec; - END OBJ(execp, 0) - scan_exec_arg(obj, arg); + END OBJ_ARG(execi, 0, "execi needs arguments: ") + scan_exec_arg(obj, arg, EF_EXECI); + obj->parse = false; + obj->thread = false; + register_execi(obj); + obj->callbacks.print = &print_exec; + obj->callbacks.free = &free_execi; + END OBJ_ARG(execp, 0, "execp needs arguments: ") + scan_exec_arg(obj, arg, EF_EXEC); obj->parse = true; obj->thread = false; + register_exec(obj); obj->callbacks.print = &print_exec; obj->callbacks.free = &free_exec; - END OBJ(execbar, 0) - scan_exec_arg(obj, arg); + END OBJ_ARG(execpi, 0, "execpi needs arguments: ") + scan_exec_arg(obj, arg, EF_EXECI); + obj->parse = true; + obj->thread = false; + register_execi(obj); + obj->callbacks.print = &print_exec; + obj->callbacks.free = &free_execi; + END OBJ_ARG(execbar, 0, "execbar needs arguments: [height],[width] ") + scan_exec_arg(obj, arg, EF_EXEC | EF_BAR); + register_exec(obj); obj->callbacks.barval = &execbarval; obj->callbacks.free = &free_exec; - END OBJ(execgauge, 0) - scan_exec_arg(obj, arg); + END OBJ_ARG(execibar, 0, "execibar needs arguments: [height],[width] ") + scan_exec_arg(obj, arg, EF_EXECI | EF_BAR); + register_execi(obj); + obj->callbacks.barval = &execbarval; + obj->callbacks.free = &free_execi; +#ifdef BUILD_X11 + END OBJ_ARG(execgauge, 0, "execgauge needs arguments: [height],[width] ") + scan_exec_arg(obj, arg, EF_EXEC | EF_GAUGE); + register_exec(obj); obj->callbacks.gaugeval = &execbarval; obj->callbacks.free = &free_exec; -#ifdef BUILD_X11 - END OBJ(execgraph, 0) - scan_execgraph_arg(obj, arg); + END OBJ_ARG(execigauge, 0, "execigauge needs arguments: [height],[width] ") + scan_exec_arg(obj, arg, EF_EXECI | EF_GAUGE); + register_execi(obj); + obj->callbacks.gaugeval = &execbarval; + obj->callbacks.free = &free_execi; + END OBJ_ARG(execgraph, 0, "execgraph needs arguments: [height],[width] [color1] [color2] [scale] [-t|-l]") + scan_exec_arg(obj, arg, EF_EXEC | EF_GRAPH); + register_exec(obj); obj->callbacks.graphval = &execbarval; obj->callbacks.free = &free_exec; -#endif /* BUILD_X11 */ - END OBJ_ARG(execibar, 0, "execibar needs arguments") - scan_execi_bar_arg(obj, arg); - obj->callbacks.barval = &execi_barval; - obj->callbacks.free = &free_execi; -#ifdef BUILD_X11 - END OBJ_ARG(execigraph, 0, "execigraph needs arguments") - scan_execgraph_arg(obj, arg); - obj->callbacks.graphval = &execi_barval; - obj->callbacks.free = &free_execi; - END OBJ_ARG(execigauge, 0, "execigauge needs arguments") - scan_execi_gauge_arg(obj, arg); - obj->callbacks.gaugeval = &execi_barval; + END OBJ_ARG(execigraph, 0, "execigraph needs arguments: [height],[width] [color1] [color2] [scale] [-t|-l]") + scan_exec_arg(obj, arg, EF_EXECI | EF_GRAPH); + register_execi(obj); + obj->callbacks.graphval = &execbarval; obj->callbacks.free = &free_execi; #endif /* BUILD_X11 */ - END OBJ_ARG(execi, 0, "execi needs arguments") - scan_execi_arg(obj, arg); - obj->parse = false; - obj->thread = false; - obj->callbacks.print = &print_execi; - obj->callbacks.free = &free_execi; - END OBJ_ARG(execpi, 0, "execpi needs arguments") - scan_execi_arg(obj, arg); - obj->parse = true; - obj->thread = false; - obj->callbacks.print = &print_execi; - obj->callbacks.free = &free_execi; - END OBJ_ARG(texeci, 0, "texeci needs arguments") - scan_execi_arg(obj, arg); + END OBJ_ARG(texeci, 0, "texeci needs arguments: ") + scan_exec_arg(obj, arg, EF_EXECI); obj->parse = false; obj->thread = true; - obj->callbacks.print = &print_execi; + register_execi(obj); + obj->callbacks.print = &print_exec; obj->callbacks.free = &free_execi; - END OBJ_ARG(texecpi, 0, "texecpi needs arguments") - scan_execi_arg(obj, arg); + END OBJ_ARG(texecpi, 0, "texecpi needs arguments: ") + scan_exec_arg(obj, arg, EF_EXECI); obj->parse = true; obj->thread = true; - obj->callbacks.print = &print_execi; + register_execi(obj); + obj->callbacks.print = &print_exec; obj->callbacks.free = &free_execi; END OBJ(fs_bar, &update_fs_stats) init_fs_bar(obj, arg); @@ -2071,7 +2081,6 @@ void free_text_objects(struct text_object *root) if(root && root->prev) { for (obj = root->prev; obj; obj = root->prev) { root->prev = obj->prev; - if (obj->callbacks.free) { (*obj->callbacks.free)(obj); } diff --git a/src/exec.cc b/src/exec.cc index 42c25a06..aab210e0 100644 --- a/src/exec.cc +++ b/src/exec.cc @@ -30,30 +30,17 @@ #include "conky.h" #include "core.h" +#include "exec.h" #include "logging.h" #include "specials.h" #include "text_object.h" +#include "update-cb.hh" +#include +#include #include #include #include #include -#include -#include -#include "update-cb.hh" - -namespace { - class exec_cb: public conky::callback { - typedef conky::callback Base; - - protected: - virtual void work(); - - public: - exec_cb(uint32_t period, bool wait, const std::string &cmd) - : Base(period, wait, Base::Tuple(cmd)) - {} - }; -} struct execi_data { float interval; @@ -70,14 +57,14 @@ static FILE* pid_popen(const char *command, const char *mode, pid_t *child) { //by running pipe after the strcmp's we make sure that we don't have to create a pipe //and close the ends if mode is something illegal - if(strcmp(mode, "r") == 0) { - if(pipe(ends) != 0) { + if (strcmp(mode, "r") == 0) { + if (pipe(ends) != 0) { return NULL; } parentend = ends[0]; childend = ends[1]; - } else if(strcmp(mode, "w") == 0) { - if(pipe(ends) != 0) { + } else if (strcmp(mode, "w") == 0) { + if (pipe(ends) != 0) { return NULL; } parentend = ends[1]; @@ -85,17 +72,18 @@ static FILE* pid_popen(const char *command, const char *mode, pid_t *child) { } else { return NULL; } + *child = fork(); - if(*child == -1) { + if (*child == -1) { close(parentend); close(childend); return NULL; - } else if(*child > 0) { + } else if (*child > 0) { close(childend); waitpid(*child, NULL, 0); } else { //don't read from both stdin and pipe or write to both stdout and pipe - if(childend == ends[0]) { + if (childend == ends[0]) { close(0); } else { close(1); @@ -110,9 +98,21 @@ static FILE* pid_popen(const char *command, const char *mode, pid_t *child) { execl("/bin/sh", "sh", "-c", command, (char *) NULL); _exit(EXIT_FAILURE); //child should die here, (normally execl will take care of this but it can fail) } + return fdopen(parentend, mode); } +/** + * Executes a command and stores the result + * + * This function is called automatically, either once every update + * interval, or at specific intervals in the case of execi commands. + * conky::run_all_callbacks() handles this. In order for this magic to + * happen, we must register a callback with conky::register_cb() + * and store it somewhere, such as obj->exec_handle. To retrieve the + * results, use the stored callback to call get_result_copy(), which + * returns a std::string. + */ void exec_cb::work() { pid_t childpid; @@ -120,37 +120,45 @@ void exec_cb::work() std::shared_ptr fp; char b[0x1000]; - if(FILE *t = pid_popen(std::get<0>(tuple).c_str(), "r", &childpid)) + if (FILE *t = pid_popen(std::get<0>(tuple).c_str(), "r", &childpid)) fp.reset(t, fclose); else return; - while(!feof(fp.get()) && !ferror(fp.get())) { + while (!feof(fp.get()) && !ferror(fp.get())) { int length = fread(b, 1, sizeof b, fp.get()); buf.append(b, length); } - if(*buf.rbegin() == '\n') + if (*buf.rbegin() == '\n') buf.resize(buf.size()-1); std::lock_guard l(result_mutex); result = buf; } + //remove backspaced chars, example: "dog^H^H^Hcat" becomes "cat" //string has to end with \0 and it's length should fit in a int #define BACKSPACE 8 static void remove_deleted_chars(char *string){ int i = 0; - while(string[i] != 0){ - if(string[i] == BACKSPACE){ - if(i != 0){ + while (string[i] != 0) { + if (string[i] == BACKSPACE) { + if (i != 0) { strcpy( &(string[i-1]), &(string[i+1]) ); i--; - }else strcpy( &(string[i]), &(string[i+1]) ); //necessary for ^H's at the start of a string - }else i++; + } else strcpy( &(string[i]), &(string[i+1]) ); //necessary for ^H's at the start of a string + } else i++; } } +/** + * Parses command output to find a number between 0.0 and 100.0. + * Used by ${exec[i]{bar,gauge,graph}}. + * + * @param[in] buf output of a command executed by an exec_cb object + * @return number between 0.0 and 100.0 + */ static inline double get_barnum(const char *buf) { double barnum; @@ -165,125 +173,177 @@ static inline double get_barnum(const char *buf) "therefore it will be ignored"); return 0.0; } + return barnum; } -void scan_exec_arg(struct text_object *obj, const char *arg) -{ - /* XXX: do real bar parsing here */ - scan_bar(obj, "", 100); - obj->data.s = strndup(arg ? arg : "", text_buffer_size.get(*state)); -} - -void scan_execi_arg(struct text_object *obj, const char *arg) -{ - struct execi_data *ed; - int n; - - ed = new execi_data; - - if (sscanf(arg, "%f %n", &ed->interval, &n) <= 0) { - NORM_ERR("${execi* command}"); - delete ed; - return; - } - ed->cmd = strndup(arg + n, text_buffer_size.get(*state)); - obj->data.opaque = ed; -} - -void scan_execi_bar_arg(struct text_object *obj, const char *arg) -{ - /* XXX: do real bar parsing here */ - scan_bar(obj, "", 100); - scan_execi_arg(obj, arg); -} - -#ifdef BUILD_X11 -void scan_execi_gauge_arg(struct text_object *obj, const char *arg) -{ - /* XXX: do real gauge parsing here */ - scan_gauge(obj, "", 100); - scan_execi_arg(obj, arg); -} - -void scan_execgraph_arg(struct text_object *obj, const char *arg) -{ - struct execi_data *ed; - char *buf; - - ed = new execi_data; - memset(ed, 0, sizeof(struct execi_data)); - - buf = scan_graph(obj, arg, 100); - if (!buf) { - NORM_ERR("missing command argument to execgraph object"); - return; - } - ed->cmd = buf; - obj->data.opaque = ed; -} -#endif /* BUILD_X11 */ - +/** + * Store command output in p. For execp objects, we process the output + * in case it contains special commands like ${color} + * + * @param[in] buffer the output of a command + * @param[in] obj text_object that specifies whether or not to parse + * @param[out] p the string in which we store command output + * @param[in] p_max_size the maximum size of p... + */ void fill_p(const char *buffer, struct text_object *obj, char *p, int p_max_size) { - if(obj->parse == true) { + if (obj->parse == true) { evaluate(buffer, p, p_max_size); - } else snprintf(p, p_max_size, "%s", buffer); + } else { + snprintf(p, p_max_size, "%s", buffer); + } + remove_deleted_chars(p); } +/** + * Parses arg to find the command to be run, as well as special options + * like height, width, color, and update interval + * + * @param[out] obj stores the command and an execi_data structure (if applicable) + * @param[in] arg the argument to an ${exec*} object + * @param[in] execflag bitwise flag used to specify the exec variant we need to process + */ +void scan_exec_arg(struct text_object *obj, const char *arg, unsigned int execflag) +{ + const char *cmd = arg; + struct execi_data *ed; + + /* in case we have an execi object, we need to parse out the interval */ + if (execflag & EF_EXECI) { + ed = new execi_data; + int n; + + /* store the interval in ed->interval */ + if (sscanf(arg, "%f %n", &ed->interval, &n) <= 0) { + NORM_ERR("missing execi interval: ${execi* command}"); + delete ed; + ed = NULL; + return; + } + + /* set cmd to everything after the interval */ + cmd = strndup(arg + n, text_buffer_size.get(*state)); + } + + /* parse any special options for the graphical exec types */ + if (execflag & EF_BAR) { + cmd = scan_bar(obj, cmd, 100); +#ifdef BUILD_X11 + } else if (execflag & EF_GAUGE) { + cmd = scan_gauge(obj, cmd, 100); + } else if (execflag & EF_GRAPH) { + cmd = scan_graph(obj, cmd, 100); + if (!cmd) { + NORM_ERR("error parsing arguments to execgraph object"); + } +#endif /* BUILD_X11 */ + } + + /* finally, store the resulting command, or an empty string if something went wrong */ + if (execflag & EF_EXEC) { + obj->data.s = strndup(cmd ? cmd : "", text_buffer_size.get(*state)); + } else if (execflag & EF_EXECI) { + ed->cmd = strndup(cmd ? cmd : "", text_buffer_size.get(*state)); + obj->data.opaque = ed; + } +} + +/** + * Register an exec_cb object using the command that we have parsed + * + * @param[out] obj stores the callback handle + */ +void register_exec(struct text_object *obj) +{ + if (obj->data.s && obj->data.s[0]) { + obj->exec_handle = new conky::callback_handle( + conky::register_cb(1, true, obj->data.s)); + } else { + DBGP("unable to register exec callback"); + } +} + +/** + * Register an exec_cb object using the command that we have parsed. + * + * This version takes care of execi intervals. Note that we depend on + * obj->thread, so be sure to run this function *after* setting obj->thread. + * + * @param[out] obj stores the callback handle + */ +void register_execi(struct text_object *obj) +{ + struct execi_data *ed = (struct execi_data *)obj->data.opaque; + + if (ed && ed->cmd && ed->cmd[0]) { + uint32_t period = std::max(lround(ed->interval/active_update_interval()), 1l); + obj->exec_handle = new conky::callback_handle( + conky::register_cb(period, !obj->thread, ed->cmd)); + } else { + DBGP("unable to register execi callback"); + } +} + +/** + * Get the results of an exec_cb object (command output) + * + * @param[in] obj holds an exec_handle, assuming one was registered + * @param[out] p the string in which we store command output + * @param[in] p_max_size the maximum size of p... + */ void print_exec(struct text_object *obj, char *p, int p_max_size) { - auto cb = conky::register_cb(1, true, obj->data.s); - fill_p(cb->get_result_copy().c_str(), obj, p, p_max_size); -} - -void print_execi(struct text_object *obj, char *p, int p_max_size) -{ - struct execi_data *ed = (struct execi_data *)obj->data.opaque; - - if (!ed) - return; - - uint32_t period = std::max(lround(ed->interval/active_update_interval()), 1l); - - auto cb = conky::register_cb(period, !obj->thread, ed->cmd); - - fill_p(cb->get_result_copy().c_str(), obj, p, p_max_size); + if (obj->exec_handle) { + fill_p((*obj->exec_handle)->get_result_copy().c_str(), obj, p, p_max_size); + } } +/** + * Get the results of a graphical (bar, gauge, graph) exec_cb object + * + * @param[in] obj hold an exec_handle, assuming one was registered + * @return a value between 0.0 and 100.0 + */ double execbarval(struct text_object *obj) { - auto cb = conky::register_cb(1, true, obj->data.s); - return get_barnum(cb->get_result_copy().c_str()); -} - -double execi_barval(struct text_object *obj) -{ - struct execi_data *ed = (struct execi_data *)obj->data.opaque; - - if (!ed) - return 0; - - uint32_t period = std::max(lround(ed->interval/active_update_interval()), 1l); - - auto cb = conky::register_cb(period, !obj->thread, ed->cmd); - - return get_barnum(cb->get_result_copy().c_str()); + if (obj->exec_handle) { + return get_barnum((*obj->exec_handle)->get_result_copy().c_str()); + } else { + return 0.0; + } } +/** + * Free up any dynamically allocated data + * + * @param[in] obj holds the data that we need to free up + */ void free_exec(struct text_object *obj) { free_and_zero(obj->data.s); + delete obj->exec_handle; + obj->exec_handle = NULL; } +/** + * Free up any dynamically allocated data, specifically for execi objects + * + * @param[in] obj holds the data that we need to free up + */ void free_execi(struct text_object *obj) { struct execi_data *ed = (struct execi_data *)obj->data.opaque; + /* if ed is NULL, there is nothing to do */ if (!ed) return; + delete obj->exec_handle; + obj->exec_handle = NULL; + free_and_zero(ed->cmd); delete ed; + ed = NULL; obj->data.opaque = NULL; } diff --git a/src/exec.h b/src/exec.h index 5b27da3c..30ff05ac 100644 --- a/src/exec.h +++ b/src/exec.h @@ -33,15 +33,52 @@ #include "text_object.h" -void scan_exec_arg(struct text_object *, const char *); -void scan_execi_arg(struct text_object *, const char *); -void scan_execi_bar_arg(struct text_object *, const char *); -void scan_execi_gauge_arg(struct text_object *, const char *); -void scan_execgraph_arg(struct text_object *, const char *); +/** + * A callback that executes a command and stores the output as a std::string. + * + * Important note: if more than one exec callback uses the same command, + * then only ONE callback is actually stored. This saves space. However, + * suppose we have the following ${exec} objects in our conky.text: + * + * ${exec ~/bin/foo.sh} + * ${execi 10 ~/bin/foo.sh} + * + * To the callback system, these are identical! Furthermore, the callback + * with the smallest period/interval is the one that is stored. So the execi + * command will in fact run on every update interval, rather than every + * ten seconds as one would expect. + */ +class exec_cb: public conky::callback { + typedef conky::callback Base; + + protected: + virtual void work(); + + public: + exec_cb(uint32_t period, bool wait, const std::string &cmd) + : Base(period, wait, Base::Tuple(cmd)) + {} +}; + +/** + * Flags used to identify the different types of exec commands during + * parsing by scan_exec_arg(). These can be used individually or combined. + * For example, to parse an ${execgraph} object, we pass EF_EXEC | EF_GRAPH + * as the last argument to scan_exec_arg(). + */ +enum { + EF_EXEC = (1 << 0), + EF_EXECI = (1 << 1), + EF_BAR = (1 << 2), + EF_GRAPH = (1 << 3), + EF_GAUGE = (1 << 4) +}; + +void scan_exec_arg(struct text_object *, const char *, unsigned int); +void register_exec(struct text_object *); +void register_execi(struct text_object *); void print_exec(struct text_object *, char *, int); -void print_execi(struct text_object *, char *, int); double execbarval(struct text_object *); -double execi_barval(struct text_object *); void free_exec(struct text_object *); void free_execi(struct text_object *); diff --git a/src/text_object.h b/src/text_object.h index d6418ec2..9aa2924e 100644 --- a/src/text_object.h +++ b/src/text_object.h @@ -33,6 +33,7 @@ #include "config.h" /* for the defines */ #include "specials.h" /* enum special_types */ #include "update-cb.hh" +#include "exec.h" /* text object callbacks */ struct obj_cb { @@ -87,12 +88,24 @@ public: : Base(period, true, Base::Tuple(fn)) {} }; -typedef conky::callback_handle legacy_cb_handle; +typedef conky::callback_handle legacy_cb_handle; +typedef conky::callback_handle exec_cb_handle; + +/** + * This is where Conky collects information on the conky.text objects in your config + * + * During startup and reload, objects are parsed and callbacks are set. Note that + * there are currently two types of callback: obj_cb (old style) and + * conky::callback (new style). On each update interval, generate_text_internal() + * in conky.cc traverses the list of text_objects and calls the old callbacks. + * The new style callbacks are run separately by conky::run_all_callbacks(). + */ struct text_object { struct text_object *next, *prev; /* doubly linked list of text objects */ struct text_object *sub; /* for objects parsing text into objects */ struct text_object *ifblock_next; /* jump target for ifblock objects */ + union { void *opaque; /* new style generic per object data */ char *s; /* some string */ @@ -102,11 +115,16 @@ struct text_object { void *special_data; long line; - struct obj_cb callbacks; - bool parse; //if this true then data.s should still be parsed - bool thread; //if this true then data.s should be set by a seperate thread + bool parse; /* if true then data.s should still be parsed */ + bool thread; /* if true then data.s should be set by a seperate thread */ - legacy_cb_handle *cb_handle; + struct obj_cb callbacks; + + /* Each _cb_handle is a std::shared_ptr with very tight restrictions on + * construction. For now, it is necessary to store them here as regular + * pointers so we can instantiate them later. */ + exec_cb_handle *exec_handle; + legacy_cb_handle *cb_handle; }; /* text object list helpers */