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