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/common.cc b/src/common.cc index 116cd069..0cfdb493 100644 --- a/src/common.cc +++ b/src/common.cc @@ -275,15 +275,13 @@ conky::simple_config_setting no_buffers("no_buffers", true, true); void update_stuff(void) { - int i; - /* clear speeds, addresses and up status in case device was removed and * doesn't get updated */ #ifdef HAVE_OPENMP #pragma omp parallel for schedule(dynamic,10) #endif /* HAVE_OPENMP */ - for (i = 0; i < MAX_NET_INTERFACES; i++) { + for (int i = 0; i < MAX_NET_INTERFACES; ++i) { if (netstats[i].dev) { netstats[i].up = 0; netstats[i].recv_speed = 0.0; @@ -295,8 +293,10 @@ void update_stuff(void) } } + /* this is a stub on all platforms except solaris */ prepare_update(); + /* if you registered a callback with conky::register_cb, this will run it */ conky::run_all_callbacks(); /* XXX: move the following into the update_meminfo() functions? */ diff --git a/src/conky.cc b/src/conky.cc index 24a52827..7ffa60c7 100644 --- a/src/conky.cc +++ b/src/conky.cc @@ -909,19 +909,17 @@ static void generate_text(void) { char *p; unsigned int i, j, k; - special_count = 0; - /* update info */ - current_update_time = get_time(); + /* clears netstats info, calls conky::run_all_callbacks(), and changes + * some info.mem entries */ update_stuff(); - /* add things to the buffer */ - - /* generate text */ - + /* populate the text buffer; generate_text_internal() iterates through + * global_root_object (an instance of the text_object struct) and calls + * any callbacks that were set on startup by construct_text_object(). */ p = text_buffer; generate_text_internal(p, max_user_text.get(*state), global_root_object); 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 */ diff --git a/src/update-cb.cc b/src/update-cb.cc index e2098016..8be20031 100644 --- a/src/update-cb.cc +++ b/src/update-cb.cc @@ -77,6 +77,12 @@ namespace conky { return *a == *b; } + /* + * If a callback is not successfully inserted into the set, it must have + * the same hash as an existing callback. If this is so, merge the incoming + * callback with the one that prevented insertion. Keep the smaller of the + * two periods. + */ void callback_base::merge(callback_base &&other) { if(other.period < period) { @@ -87,10 +93,14 @@ namespace conky { unused = 0; } + /* + * Register a callback (i.e. insert it into the callbacks set) + */ callback_base::handle callback_base::do_register_cb(const handle &h) { const auto &p = callbacks.insert(h); + /* insertion failed; callback already exists */ if(not p.second) (*p.first)->merge(std::move(*h)); @@ -134,7 +144,10 @@ namespace conky { for(auto i = callback_base::callbacks.begin(); i != callback_base::callbacks.end(); ) { callback_base &cb = **i; + /* check whether enough update intervals have elapsed (up to period) */ if(cb.remaining-- == 0) { + /* run the callback as long as someone holds a pointer to it; + * if no one owns the callback, run it at most UNUSED_MAX times */ if(!i->unique() || ++cb.unused < UNUSED_MAX) { cb.remaining = cb.period-1; cb.run(); diff --git a/src/update-cb.hh b/src/update-cb.hh index e1bc021e..da70502c 100644 --- a/src/update-cb.hh +++ b/src/update-cb.hh @@ -56,13 +56,13 @@ namespace conky { semaphore sem_start; std::thread *thread; - const size_t hash; - uint32_t period; - uint32_t remaining; + const size_t hash; /* used to determined callback uniqueness */ + uint32_t period; /* how often to run a callback */ + uint32_t remaining; /* update intervals remaining until we can run a callback */ std::pair pipefd; - const bool wait; - bool done; - uint8_t unused; + const bool wait; /* whether or not to wait for a callback to finish */ + bool done; /* if true, callback is being stopped and destroyed */ + uint8_t unused; /* number of update intervals during which no one owns a callback */ callback_base(const callback_base &) = delete; callback_base& operator=(const callback_base &) = delete; @@ -159,6 +159,9 @@ namespace conky { )); } + /* + * Callback uniqueness is determined by the hash computed here. + */ namespace priv { template struct hash_tuple {