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

Merge pull request #192 from marcpayne/exec-cb-refactor

Refactor the exec callback system
This commit is contained in:
Brenden Matthews 2016-01-10 13:33:39 -08:00
commit d2d72e2abd
10 changed files with 447 additions and 256 deletions

View File

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

View File

@ -1118,8 +1118,8 @@
<option>command</option>
</term>
<listitem>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.
<para /></listitem>
</varlistentry>
@ -1128,12 +1128,14 @@
<command>
<option>execbar</option>
</command>
<option>(height),(width)</option>
<option>command</option>
</term>
<listitem>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.
<listitem>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.
<para /></listitem>
</varlistentry>
<varlistentry>
@ -1141,12 +1143,15 @@
<command>
<option>execgauge</option>
</command>
<option>(height),(width)</option>
<option>command</option>
</term>
<listitem>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.
<listitem>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.
<para /></listitem>
</varlistentry>
<varlistentry>
@ -1154,33 +1159,61 @@
<command>
<option>execgraph</option>
</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>
<listitem>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`.
<para /></listitem>
<listitem>
<para>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.</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>
<term>
<command>
<option>execi</option>
</command>
<option>interval command</option>
<option>interval</option>
<option>command</option>
</term>
<listitem>Same as exec but with specific interval. Interval
can't be less than update_interval in configuration. See
also $texeci
<listitem>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.
<para /></listitem>
</varlistentry>
<varlistentry>
@ -1188,9 +1221,11 @@
<command>
<option>execibar</option>
</command>
<option>interval command</option>
<option>interval</option>
<option>(height),(width)</option>
<option>command</option>
</term>
<listitem>Same as execbar, except with an interval
<listitem>Same as execbar, but with an interval.
<para /></listitem>
</varlistentry>
<varlistentry>
@ -1198,10 +1233,11 @@
<command>
<option>execigauge</option>
</command>
<option>interval command</option>
<option>interval</option>
<option>(height),(width)</option>
<option>command</option>
</term>
<listitem>Same as execgauge, but takes an interval arg and
gauges values.
<listitem>Same as execgauge, but with an interval.
<para /></listitem>
</varlistentry>
<varlistentry>
@ -1209,11 +1245,16 @@
<command>
<option>execigraph</option>
</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>
<listitem>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 (' ').
<listitem>Same as execgraph, but with an interval.
<para /></listitem>
</varlistentry>
<varlistentry>
@ -1224,8 +1265,8 @@
<option>command</option>
</term>
<listitem>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
@ -1243,12 +1284,12 @@
<command>
<option>execpi</option>
</command>
<option>interval command</option>
<option>interval</option>
<option>command</option>
</term>
<listitem>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.
<listitem>Same as execp, but with an interval. Note that
the output from the $execpi command is still parsed
and evaluated at every interval.
<para /></listitem>
</varlistentry>
<varlistentry>
@ -3769,13 +3810,14 @@
<command>
<option>texeci</option>
</command>
<option>interval command</option>
<option>interval</option>
<option>command</option>
</term>
<listitem>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
@ -3789,7 +3831,8 @@
<command>
<option>texecpi</option>
</command>
<option>interval command</option>
<option>interval</option>
<option>command</option>
</term>
<listitem>Same as execpi, except the command is run inside
a thread.

View File

@ -275,15 +275,13 @@ conky::simple_config_setting<bool> 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? */

View File

@ -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);

View File

@ -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: <command>")
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: <interval> <command>")
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->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: <interval> <command>")
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.free = &free_exec;
END OBJ(execgauge, 0)
scan_exec_arg(obj, arg);
END OBJ_ARG(execibar, 0, "execibar needs arguments: <interval> [height],[width] <command>")
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.free = &free_exec;
#ifdef BUILD_X11
END OBJ(execgraph, 0)
scan_execgraph_arg(obj, arg);
END OBJ_ARG(execigauge, 0, "execigauge needs arguments: <interval> [height],[width] <command>")
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: <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.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: <interval> <command> [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: <interval> <command>")
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: <interval> <command>")
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);
}

View File

@ -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 <cmath>
#include <mutex>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.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 {
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<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()
{
pid_t childpid;
@ -120,37 +120,45 @@ void exec_cb::work()
std::shared_ptr<FILE> 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<std::mutex> 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* <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 */
/**
* 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* <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)
{
auto cb = conky::register_cb<exec_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<exec_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<exec_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<exec_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;
}

View File

@ -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<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_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 *);

View File

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

View File

@ -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();

View File

@ -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<int, int> 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<size_t pos, typename... Elements>
struct hash_tuple {