1
0
mirror of https://github.com/Llewellynvdm/conky.git synced 2024-11-19 11:35:12 +00:00

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.
This commit is contained in:
Marc Payne 2016-01-09 10:19:48 -07:00
parent 2fab2d3820
commit adae1f6293
6 changed files with 417 additions and 240 deletions

View File

@ -99,8 +99,8 @@
<option>default_bar_height</option> <option>default_bar_height</option>
</command> </command>
</term> </term>
<listitem>Specify a default height for bars. This is particularly useful for execbar and <listitem>Specify a default height for bars. If not specified,
execibar as they do not take size arguments. the default value is 6.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -109,8 +109,13 @@
<option>default_bar_width</option> <option>default_bar_width</option>
</command> </command>
</term> </term>
<listitem>Specify a default width for bars. This is particularly useful for execbar and <listitem>Specify a default width for bars. If not specified,
execibar as they do not take size arguments. 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.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -128,8 +133,8 @@
<option>default_gauge_height</option> <option>default_gauge_height</option>
</command> </command>
</term> </term>
<listitem>Specify a default height for gauges. This is particularly useful for execgauge <listitem>Specify a default height for gauges. If not specified,
and execigauge as they do not take size arguments the default value is 25.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -138,8 +143,8 @@
<option>default_gauge_width</option> <option>default_gauge_width</option>
</command> </command>
</term> </term>
<listitem>Specify a default width for gauges. This is particularly useful for execgauge <listitem>Specify a default width for gauges. If not specified,
and execigauge as they do not take size arguments the default value is 40.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -148,8 +153,8 @@
<option>default_graph_height</option> <option>default_graph_height</option>
</command> </command>
</term> </term>
<listitem>Specify a default height for graphs. This is particularly useful for execgraph <listitem>Specify a default height for graphs. If not specified,
and execigraph as they do not take size arguments the default value is 25.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -158,8 +163,13 @@
<option>default_graph_width</option> <option>default_graph_width</option>
</command> </command>
</term> </term>
<listitem>Specify a default width for graphs. This is particularly useful for execgraph <listitem>Specify a default width for graphs. If not specified,
and execigraph as they do not take size arguments 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.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>

View File

@ -1118,8 +1118,8 @@
<option>command</option> <option>command</option>
</term> </term>
<listitem>Executes a shell command and displays the output <listitem>Executes a shell command and displays the output
in conky. warning: this takes a lot more resources than in conky. Warning: this takes a lot more resources than
other variables. I'd recommend coding wanted behaviour in C other variables. I'd recommend coding wanted behaviour in C/C++
and posting a patch. and posting a patch.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
@ -1128,12 +1128,14 @@
<command> <command>
<option>execbar</option> <option>execbar</option>
</command> </command>
<option>(height),(width)</option>
<option>command</option> <option>command</option>
</term> </term>
<listitem>Same as exec, except if the first value return is <listitem>Same as exec, except if the first value returned is
a value between 0-100, it will use that number for a bar. a value between 0-100, it will use that number to draw a
The size for bars can be controlled via the horizontal bar. The height and width parameters are optional,
default_bar_size config setting. and default to the default_bar_height and default_bar_width
config settings, respectively.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -1141,12 +1143,15 @@
<command> <command>
<option>execgauge</option> <option>execgauge</option>
</command> </command>
<option>(height),(width)</option>
<option>command</option> <option>command</option>
</term> </term>
<listitem>Same as exec, except if the first value returned <listitem>Same as exec, except if the first value returned is
is a value between 0-100, it will use that number for a a value between 0-100, it will use that number to draw a
gauge. The size for gauges can be controlled via the round gauge (much like a vehicle speedometer). The height and
default_gauge_size config setting. width parameters are optional, and default to the
default_gauge_height and default_gauge_width config settings,
respectively.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -1154,33 +1159,61 @@
<command> <command>
<option>execgraph</option> <option>execgraph</option>
</command> </command>
<option>(-t) (-l) command</option> <option>command</option>
<option>(height),(width)</option>
<option>(gradient color 1)</option>
<option>(gradient color 2)</option>
<option>(scale)</option>
<option>(-t)</option>
<option>(-l)</option>
</term> </term>
<listitem>Same as execbar, but graphs values. Uses a <listitem>
logaritmic scale when the log option (-l switch) is given <para>Draws a horizontally scrolling graph with values
(to see small numbers). Values still have to be between 0 from 0-100 plotted on the vertical axis. All parameters
and 100. The size for graphs can be controlled via the following the command are optional. Gradient colors can
default_graph_size config setting. Takes the switch '-t' to be specified as hexadecimal values with no 0x or #
use a temperature gradient, which makes the gradient values prefix. Use the -t switch to enable a temperature
change depending on the amplitude of a particular graph gradient, so that small values are "cold" with color 1
value (try it and see). If -t or -l is your first argument, and large values are "hot" with color 2. Without the -t
you may need to preceed it by a space (' '). You may also use switch, the colors produce a horizontal gradient
double-quotes around the exec argument should you need to execute a spanning the width of the graph. The scale parameter
command with spaces. For example, ${execgraph "date +'%S'"} to execute defines the maximum value of the graph. Use the -l
`date +'%S'` and graph the result. Without quotes, it would simply switch to enable a logarithmic scale, which helps to see
print the result of `date`. small values. The default size for graphs can be
<para /></listitem> controlled via the default_graph_height and
default_graph_width config settings.</para>
<para>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.</para>
<para>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.</para>
<para><command>${execgraph ~/seconds.sh 50,200 FF0000
FFFF00 60 -t}</command></para>
</listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term> <term>
<command> <command>
<option>execi</option> <option>execi</option>
</command> </command>
<option>interval command</option> <option>interval</option>
<option>command</option>
</term> </term>
<listitem>Same as exec but with specific interval. Interval <listitem>Same as exec, but with a specific interval in
can't be less than update_interval in configuration. See seconds. The interval can't be less than the update_interval
also $texeci in your configuration. See also $texeci.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -1188,9 +1221,11 @@
<command> <command>
<option>execibar</option> <option>execibar</option>
</command> </command>
<option>interval command</option> <option>interval</option>
<option>(height),(width)</option>
<option>command</option>
</term> </term>
<listitem>Same as execbar, except with an interval <listitem>Same as execbar, but with an interval.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -1198,10 +1233,11 @@
<command> <command>
<option>execigauge</option> <option>execigauge</option>
</command> </command>
<option>interval command</option> <option>interval</option>
<option>(height),(width)</option>
<option>command</option>
</term> </term>
<listitem>Same as execgauge, but takes an interval arg and <listitem>Same as execgauge, but with an interval.
gauges values.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -1209,11 +1245,16 @@
<command> <command>
<option>execigraph</option> <option>execigraph</option>
</command> </command>
<option>interval (-t) (-l) command</option> <option>interval</option>
<option>command</option>
<option>(height),(width)</option>
<option>(gradient color 1)</option>
<option>(gradient color 2)</option>
<option>(scale)</option>
<option>(-t)</option>
<option>(-l)</option>
</term> </term>
<listitem>Same as execgraph, but takes an interval arg and <listitem>Same as execgraph, but with an interval.
graphs values. If -t or -l is your first argument, you may
need to preceed it by a space (' ').
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -1224,8 +1265,8 @@
<option>command</option> <option>command</option>
</term> </term>
<listitem>Executes a shell command and displays the output <listitem>Executes a shell command and displays the output
in conky. warning: this takes a lot more resources than in conky. Warning: this takes a lot more resources than
other variables. I'd recommend coding wanted behaviour in C other variables. I'd recommend coding wanted behaviour in C/C++
and posting a patch. This differs from $exec in that it and posting a patch. This differs from $exec in that it
parses the output of the command, so you can insert things parses the output of the command, so you can insert things
like ${color red}hi!${color} in your script and have it like ${color red}hi!${color} in your script and have it
@ -1243,12 +1284,12 @@
<command> <command>
<option>execpi</option> <option>execpi</option>
</command> </command>
<option>interval command</option> <option>interval</option>
<option>command</option>
</term> </term>
<listitem>Same as execp but with specific interval. <listitem>Same as execp, but with an interval. Note that
Interval can't be less than update_interval in the output from the $execpi command is still parsed
configuration. Note that the output from the $execpi and evaluated at every interval.
command is still parsed and evaluated at every interval.
<para /></listitem> <para /></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -3769,13 +3810,14 @@
<command> <command>
<option>texeci</option> <option>texeci</option>
</command> </command>
<option>interval command</option> <option>interval</option>
<option>command</option>
</term> </term>
<listitem>Runs a command at an interval inside a thread and <listitem>Runs a command at an interval inside a thread and
displays the output. Same as $execi, except the command is displays the output. Same as $execi, except the command is
run inside a thread. Use this if you have a slow script to run inside a thread. Use this if you have a slow script to
keep Conky updating. You should make the interval slightly 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 example, if you have a script that take 5 seconds to
execute, you should make the interval at least 6 seconds. execute, you should make the interval at least 6 seconds.
See also $execi. This object will clean up the thread when See also $execi. This object will clean up the thread when
@ -3789,7 +3831,8 @@
<command> <command>
<option>texecpi</option> <option>texecpi</option>
</command> </command>
<option>interval command</option> <option>interval</option>
<option>command</option>
</term> </term>
<listitem>Same as execpi, except the command is run inside <listitem>Same as execpi, except the command is run inside
a thread. a thread.

View File

@ -742,69 +742,79 @@ struct text_object *construct_text_object(char *s, const char *arg,
scan_no_update(obj, arg); scan_no_update(obj, arg);
obj->callbacks.print = &print_no_update; obj->callbacks.print = &print_no_update;
obj->callbacks.free = &free_no_update; obj->callbacks.free = &free_no_update;
END OBJ(exec, 0) END OBJ_ARG(exec, 0, "exec needs arguments: <command>")
scan_exec_arg(obj, arg); scan_exec_arg(obj, arg, EF_EXEC);
obj->parse = false; obj->parse = false;
obj->thread = false; obj->thread = false;
register_exec(obj);
obj->callbacks.print = &print_exec; obj->callbacks.print = &print_exec;
obj->callbacks.free = &free_exec; obj->callbacks.free = &free_exec;
END OBJ(execp, 0) END OBJ_ARG(execi, 0, "execi needs arguments: <interval> <command>")
scan_exec_arg(obj, arg); 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: <command>")
scan_exec_arg(obj, arg, EF_EXEC);
obj->parse = true; obj->parse = true;
obj->thread = false; obj->thread = false;
register_exec(obj);
obj->callbacks.print = &print_exec; obj->callbacks.print = &print_exec;
obj->callbacks.free = &free_exec; obj->callbacks.free = &free_exec;
END OBJ(execbar, 0) END OBJ_ARG(execpi, 0, "execpi needs arguments: <interval> <command>")
scan_exec_arg(obj, arg); 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] <command>")
scan_exec_arg(obj, arg, EF_EXEC | EF_BAR);
register_exec(obj);
obj->callbacks.barval = &execbarval; obj->callbacks.barval = &execbarval;
obj->callbacks.free = &free_exec; obj->callbacks.free = &free_exec;
END OBJ(execgauge, 0) END OBJ_ARG(execibar, 0, "execibar needs arguments: <interval> [height],[width] <command>")
scan_exec_arg(obj, arg); 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] <command>")
scan_exec_arg(obj, arg, EF_EXEC | EF_GAUGE);
register_exec(obj);
obj->callbacks.gaugeval = &execbarval; obj->callbacks.gaugeval = &execbarval;
obj->callbacks.free = &free_exec; obj->callbacks.free = &free_exec;
#ifdef BUILD_X11 END OBJ_ARG(execigauge, 0, "execigauge needs arguments: <interval> [height],[width] <command>")
END OBJ(execgraph, 0) scan_exec_arg(obj, arg, EF_EXECI | EF_GAUGE);
scan_execgraph_arg(obj, arg); register_execi(obj);
obj->callbacks.gaugeval = &execbarval;
obj->callbacks.free = &free_execi;
END OBJ_ARG(execgraph, 0, "execgraph needs arguments: <command> [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.graphval = &execbarval;
obj->callbacks.free = &free_exec; obj->callbacks.free = &free_exec;
#endif /* BUILD_X11 */ END OBJ_ARG(execigraph, 0, "execigraph needs arguments: <interval> <command> [height],[width] [color1] [color2] [scale] [-t|-l]")
END OBJ_ARG(execibar, 0, "execibar needs arguments") scan_exec_arg(obj, arg, EF_EXECI | EF_GRAPH);
scan_execi_bar_arg(obj, arg); register_execi(obj);
obj->callbacks.barval = &execi_barval; obj->callbacks.graphval = &execbarval;
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;
obj->callbacks.free = &free_execi; obj->callbacks.free = &free_execi;
#endif /* BUILD_X11 */ #endif /* BUILD_X11 */
END OBJ_ARG(execi, 0, "execi needs arguments") END OBJ_ARG(texeci, 0, "texeci needs arguments: <interval> <command>")
scan_execi_arg(obj, arg); scan_exec_arg(obj, arg, EF_EXECI);
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);
obj->parse = false; obj->parse = false;
obj->thread = true; obj->thread = true;
obj->callbacks.print = &print_execi; register_execi(obj);
obj->callbacks.print = &print_exec;
obj->callbacks.free = &free_execi; obj->callbacks.free = &free_execi;
END OBJ_ARG(texecpi, 0, "texecpi needs arguments") END OBJ_ARG(texecpi, 0, "texecpi needs arguments: <interval> <command>")
scan_execi_arg(obj, arg); scan_exec_arg(obj, arg, EF_EXECI);
obj->parse = true; obj->parse = true;
obj->thread = true; obj->thread = true;
obj->callbacks.print = &print_execi; register_execi(obj);
obj->callbacks.print = &print_exec;
obj->callbacks.free = &free_execi; obj->callbacks.free = &free_execi;
END OBJ(fs_bar, &update_fs_stats) END OBJ(fs_bar, &update_fs_stats)
init_fs_bar(obj, arg); init_fs_bar(obj, arg);
@ -2071,7 +2081,6 @@ void free_text_objects(struct text_object *root)
if(root && root->prev) { if(root && root->prev) {
for (obj = root->prev; obj; obj = root->prev) { for (obj = root->prev; obj; obj = root->prev) {
root->prev = obj->prev; root->prev = obj->prev;
if (obj->callbacks.free) { if (obj->callbacks.free) {
(*obj->callbacks.free)(obj); (*obj->callbacks.free)(obj);
} }

View File

@ -30,30 +30,17 @@
#include "conky.h" #include "conky.h"
#include "core.h" #include "core.h"
#include "exec.h"
#include "logging.h" #include "logging.h"
#include "specials.h" #include "specials.h"
#include "text_object.h" #include "text_object.h"
#include "update-cb.hh"
#include <cmath>
#include <mutex>
#include <stdio.h> #include <stdio.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include <cmath>
#include <mutex>
#include "update-cb.hh"
namespace {
class exec_cb: public conky::callback<std::string, std::string> {
typedef conky::callback<std::string, std::string> 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 { struct execi_data {
float interval; float interval;
@ -85,6 +72,7 @@ static FILE* pid_popen(const char *command, const char *mode, pid_t *child) {
} else { } else {
return NULL; return NULL;
} }
*child = fork(); *child = fork();
if (*child == -1) { if (*child == -1) {
close(parentend); close(parentend);
@ -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); 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) _exit(EXIT_FAILURE); //child should die here, (normally execl will take care of this but it can fail)
} }
return fdopen(parentend, mode); 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<exec_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() void exec_cb::work()
{ {
pid_t childpid; pid_t childpid;
@ -136,6 +136,7 @@ void exec_cb::work()
std::lock_guard<std::mutex> l(result_mutex); std::lock_guard<std::mutex> l(result_mutex);
result = buf; result = buf;
} }
//remove backspaced chars, example: "dog^H^H^Hcat" becomes "cat" //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 //string has to end with \0 and it's length should fit in a int
#define BACKSPACE 8 #define BACKSPACE 8
@ -151,6 +152,13 @@ static void remove_deleted_chars(char *string){
} }
} }
/**
* 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) static inline double get_barnum(const char *buf)
{ {
double barnum; double barnum;
@ -165,125 +173,177 @@ static inline double get_barnum(const char *buf)
"therefore it will be ignored"); "therefore it will be ignored");
return 0.0; return 0.0;
} }
return barnum; return barnum;
} }
void scan_exec_arg(struct text_object *obj, const char *arg) /**
{ * Store command output in p. For execp objects, we process the output
/* XXX: do real bar parsing here */ * in case it contains special commands like ${color}
scan_bar(obj, "", 100); *
obj->data.s = strndup(arg ? arg : "", text_buffer_size.get(*state)); * @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
void scan_execi_arg(struct text_object *obj, const char *arg) * @param[in] p_max_size the maximum size of p...
{ */
struct execi_data *ed;
int n;
ed = new execi_data;
if (sscanf(arg, "%f %n", &ed->interval, &n) <= 0) {
NORM_ERR("${execi* <interval> 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 */
void fill_p(const char *buffer, struct text_object *obj, char *p, int p_max_size) { 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); 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); 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* <interval> 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<exec_cb>(
conky::register_cb<exec_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<exec_cb>(
conky::register_cb<exec_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) void print_exec(struct text_object *obj, char *p, int p_max_size)
{ {
auto cb = conky::register_cb<exec_cb>(1, true, obj->data.s); if (obj->exec_handle) {
fill_p(cb->get_result_copy().c_str(), obj, p, p_max_size); fill_p((*obj->exec_handle)->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<exec_cb>(period, !obj->thread, ed->cmd);
fill_p(cb->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) double execbarval(struct text_object *obj)
{ {
auto cb = conky::register_cb<exec_cb>(1, true, obj->data.s); if (obj->exec_handle) {
return get_barnum(cb->get_result_copy().c_str()); return get_barnum((*obj->exec_handle)->get_result_copy().c_str());
} } else {
return 0.0;
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<exec_cb>(period, !obj->thread, ed->cmd);
return get_barnum(cb->get_result_copy().c_str());
} }
/**
* 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) void free_exec(struct text_object *obj)
{ {
free_and_zero(obj->data.s); 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) void free_execi(struct text_object *obj)
{ {
struct execi_data *ed = (struct execi_data *)obj->data.opaque; struct execi_data *ed = (struct execi_data *)obj->data.opaque;
/* if ed is NULL, there is nothing to do */
if (!ed) if (!ed)
return; return;
delete obj->exec_handle;
obj->exec_handle = NULL;
free_and_zero(ed->cmd); free_and_zero(ed->cmd);
delete ed; delete ed;
ed = NULL;
obj->data.opaque = NULL; obj->data.opaque = NULL;
} }

View File

@ -33,15 +33,52 @@
#include "text_object.h" #include "text_object.h"
void scan_exec_arg(struct text_object *, const char *); /**
void scan_execi_arg(struct text_object *, const char *); * A callback that executes a command and stores the output as a std::string.
void scan_execi_bar_arg(struct text_object *, const char *); *
void scan_execi_gauge_arg(struct text_object *, const char *); * Important note: if more than one exec callback uses the same command,
void scan_execgraph_arg(struct text_object *, const char *); * 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<std::string, std::string> {
typedef conky::callback<std::string, std::string> 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_exec(struct text_object *, char *, int);
void print_execi(struct text_object *, char *, int);
double execbarval(struct text_object *); double execbarval(struct text_object *);
double execi_barval(struct text_object *);
void free_exec(struct text_object *); void free_exec(struct text_object *);
void free_execi(struct text_object *); void free_execi(struct text_object *);

View File

@ -33,6 +33,7 @@
#include "config.h" /* for the defines */ #include "config.h" /* for the defines */
#include "specials.h" /* enum special_types */ #include "specials.h" /* enum special_types */
#include "update-cb.hh" #include "update-cb.hh"
#include "exec.h"
/* text object callbacks */ /* text object callbacks */
struct obj_cb { struct obj_cb {
@ -87,12 +88,24 @@ public:
: Base(period, true, Base::Tuple(fn)) : Base(period, true, Base::Tuple(fn))
{} {}
}; };
typedef conky::callback_handle<legacy_cb> legacy_cb_handle;
typedef conky::callback_handle<legacy_cb> legacy_cb_handle;
typedef conky::callback_handle<exec_cb> 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 {
struct text_object *next, *prev; /* doubly linked list of text objects */ struct text_object *next, *prev; /* doubly linked list of text objects */
struct text_object *sub; /* for objects parsing text into objects */ struct text_object *sub; /* for objects parsing text into objects */
struct text_object *ifblock_next; /* jump target for ifblock objects */ struct text_object *ifblock_next; /* jump target for ifblock objects */
union { union {
void *opaque; /* new style generic per object data */ void *opaque; /* new style generic per object data */
char *s; /* some string */ char *s; /* some string */
@ -102,10 +115,15 @@ struct text_object {
void *special_data; void *special_data;
long line; long line;
struct obj_cb callbacks; bool parse; /* if true then data.s should still be parsed */
bool parse; //if this true then data.s should still be parsed bool thread; /* if true then data.s should be set by a seperate thread */
bool thread; //if this true then data.s should be set by a seperate thread
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; legacy_cb_handle *cb_handle;
}; };