banner
Previous Page
PCLinuxOS Magazine
PCLinuxOS
Article List
Disclaimer
Next Page

Monitor Your System With Conky, Part Two


by Peter Kelly (critter)



Last month we learned the basics of conky, but now we are looking for WOW, so we will add Lua and Cairo.

Lua and Cairo

Now Lua and Cairo may sound like that nice couple you met on vacation and exchanged your e-mail addresses with, but that's not this couple.

Lua is an open source scripting language, developed and everything by the Pontifical Catholic University of Rio de Janeiro in Brazil and is spelled Lua, with a capital 'L'. Cairo is an advanced 2D graphics library. Conky can interface to Lua through a couple of hooks, and implements a few Lua specific functions.

What all of this means is that you need to provide a Lua script to Conky that perhaps uses one or more functions from the Cairo graphics libraries. Don't worry, it really isn't all that difficult. If you really don't want to go further, then there are plenty of conky configuration files that use Lua scripts available on-line, ready for you to use for your own purposes. But read on, you just might learn something you like.


Why Lua and Cairo?

Time to explain what we are trying to do here. Conky on its own can produce some fine output, but is limited to its own pre-defined functions. By adding the ability to use another scripting language, we gain access to many of the abilities of that language. Lua is an advanced and feature rich scripting language. Its addition removes many of the limitations of the basic Conky. How much it does this really depends on how far you want to go. Also, Lua is able to import external libraries of functions, and the Cairo graphical libraries feature some very useful routines using a consistent drawing model. Both Lua and Cairo are big subjects to approach, and here I will only just scratch the surface. I'll show the basic usage with some examples, and leave you to explore further on your own.

Conky doesn't need Lua and Lua doesn't need Cairo but Conky + Lua + Cairo = WOW.


Using Lua with Conky

Lua is a scripting language, and it reads a list of instructions in a plain text file known, not surprisingly, as a script. Conky therefore needs access to a Lua script, and this is achieved by adding these two lines before the TEXT section of the conky configuration file. The first line is:

lua_load the_path_to_your_lua_script (Lua scripts usually end in .lua)

This tells Conky which Lua script it should load. Obviously you need to provide a path to an actual Lua script. The script I shall use for purposes of demonstration will be

/home/user/demo.lua

The second line is:

lua_draw_hook_pre conky_demo_mag

This line tells Conky which function from the script to execute. The name of the function in the script should begin with conky_ as above but you may omit it here.

lua_draw_hook_pre demo_mag works just as well.

Also change:

update_interval 5 to update_interval 1 or a suitable value that gives a good response.

And to give us a decent display area add this line

minimum_size 400 400

As always, the ways to achieve Lua integration with Conky are many and varied, but this method is fairly simple and has worked for me.

If you add just those first two lines to your template file, leave the text section blank and then re-save the file to perhaps .conkyrc_lua, it will work. You can, of course, add more stuff to the TEXT section and have both Conky and Lua output together, but for now I'll keep things simple.

Now you need a Lua script named demo.lua The Cairo libraries create their graphics by copying a source graphic onto a Cairo surface using masks and paths to produce the final output. That is not a very accurate description of the Cairo drawing model, but it is simplistic enough for us to use while we investigate further.

The first demo uses a text graphic as the source. Extra spaces are ignored in Lua scripts, so use spaces as required for readability. This is the basic Lua script, with reference numbers added.


1 require 'cairo'
2 function conky_demo_mag()
3 if conky_window == nil then return end
4 local cs = cairo_xlib_surface_create(conky_window.display, conky_window.drawable, conky_window.visual, conky_window.width, conky_window.height)
5 cr = cairo_create(cs)
6 -- Start of output
7 cairo_select_font_face (cr, "Liberation Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
8 cairo_set_font_size (cr, 24)
9 cairo_set_source_rgba (cr,1,0,0,1)
10 cairo_move_to (cr,80,200)
11 cairo_show_text (cr,"PCLinuxOS Magazine")
12 cairo_stroke (cr)
13 -- End of output
14 cairo_destroy(cr)
15 cairo_surface_destroy(cs)
16 cr=nil
17 end

This will simply output a line of text. For most purposes, you will only need to change the code between lines 6 and 13.

Line 1 tells Lua that the Cairo libraries are required to run this script.

Line 2 starts the definition of the function that we will call from Conky.

Line 3 checks that the Conky window exists, and if not, exits the function and, as there is nothing after the function, also exits the script.

Line 4 is more interesting. local means that what follows is local to this function only and will not be changed by other parts of the script, even if there is a name conflict. After this, a Cairo surface object is defined with properties that will: use the Conky window as a display, allow us to draw on to the surface on the Conky window, make the graphics visible, and make the surface the same width and height as the Conky window.

This allows us to use coordinates relative to Conky. The surface type is assigned to the object cs, which is how we reference it.

Line 5 a surface of type cs is created and assigned to the variable cr.

Line 6 is a comment and will be ignored. Anything after “--” is considered a comment.

Line 7 sets the font to be used on surface cr. The font face is 'Liberation Sans.' The font slant is given by the cairo constant CAIRO_FONT_SLANT_NORMAL. Another option is CAIRO_FONT_SLANT_ITALIC. The font weight is given by the Cairo constant CAIRO_FONT_WEIGHT_NORMAL. Another option is CAIRO_FONT_WEIGHT_BOLD.

Line 8 Sets the font size.

Line 9 sets the color. The format is red, green, blue, alpha and uses values in the range from 0 to 1 instead of the 0 to 255 that we are more used to. Lua can do the conversion for us on the fly by using a statement such as cairo_set_source_rgba (cr,234/255,0,107/255,1).

Line 10 moves the insertion point.

Line 11 sets the text to be shown.

Line 12 finally draws the text.

Line 13 is another comment line.

Lines 14 – 16 clean things up before leaving the function.



Running conky -d .conkyrc-lua we get this (above). Well, it works but there is no WOW factor there. Nothing we couldn't do without all this extra effort.

Note that there may be a slight delay when using Lua and Cairo before anything appears on the Conky window, particularly when parsing conky variables (as you'll see later). You may see a few warnings appear in the terminal until Conky and Lua get their stuff together. This is normal.

Change the part of the script between the two comment lines to read:

cairo_select_font_face (cr, "Liberation Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL)
cairo_set_font_size (cr, 24)
cairo_set_source_rgba (cr,1,0,0,1)
cairo_move_to (cr,80,150)
cairo_rotate (cr, -math.pi / 6)
cairo_show_text (cr,"PCLinuxOS Magazine")
cairo_set_font_size (cr, 36)
cairo_set_source_rgba (cr,1,1,0,1)
cairo_move_to (cr,210,210)
cairo_rotate (cr, math.pi / 4)
cairo_show_text (cr,"Rocks!")
cairo_set_font_size (cr, 18)
cairo_set_source_rgba (cr,19/255,197/255,11/255,1)
cairo_move_to (cr,333,210)
cairo_rotate (cr, -3 * math.pi / 4)
cairo_show_text (cr,"Linux Rules OK")
cairo_stroke (cr)



Note that the cairo_stroke (cr) call is required only once for the three blocks of text, but that is not always the case. If in doubt, include the line. Run the Conky again and you should get a display similar to the one above. Progress!

But then again, I could have just created images of rotated text in almost any graphics application and displayed them with the 'normal' Conky as images.

The big step comes when you can display 'live' text and have it appear like this, as opposed to displaying static text. You can display text that comes from interrogating the system, text that, for instance, reports the current battery charge level. It's quite easy to do.

So that's what we'll do do next.

First I'd better explain some of those new or changed lines. I've added several lines, but most are repetitions of previous lines.

The first line that is new is

cairo_rotate (cr, -math.pi / 6).

This rotates the output by an amount equal to the result of '-math.pi / 6'. Lua uses radians for angles, starts at 3 o'clock or due east from the centre of a circle and rotates clockwise. Lua has a good range of mathematical functions and -math.pi returns the negated value of pi (π). As there are 2π radians in a circle, this equates to 30º counter-clockwise. Most people are happier using degrees, so we can get Lua to do our calculations for us.

cairo_rotate (cr,-30 * (math.pi / 180))

Tip: to make sure that you are getting results you expect you can use the print command to echo the output to the terminal

print (-30 * (math.pi / 180))

This is a general purpose debugging method but is invaluable when using math.

The second line of text has an effective rotation of 15º as the rotations are cumulative. To position the text where I have, it was necessary to use coordinates of 210,210 (which I found by trial and error), but this is obviously not 210,210 in a window of 400 pixels square, that would be the approximate centre. To retain a sensible coordinate system, it is necessary to rotate back to 0º, apply the translation and then rotate as required. This then becomes

cairo_rotate (cr, 30 * (math.pi / 180))     -- Rotate back to where we were
cairo_move_to (cr, 283,40)
cairo_rotate (cr, 15 * (math.pi / 180))

These are much more reasonable coordinates.

We can also tidy up the script a little by assigning the value of the Cairo constants to variables in our template before the beginning of the output, i.e. before line 6.

f_noslant=CAIRO_FONT_SLANT_NORMAL
f_italic=CAIRO_FONT_SLANT_ITALIC
f_nobold=CAIRO_FONT_WEIGHT_NORMAL
f_bold=CAIRO_FONT_WEIGHT_BOLD

Then call them like this:

cairo_select_font_face (cr, "Liberation Sans", f_noslant, f_nobold);

To get the text from a conky variable into Lua, use the conky_parse() function. Insert these lines into the Lua script just before the cairo_stroke (cr) line.

cairo_select_font_face (cr, "Junkyard", f_noslant, f_bold)
cairo_set_font_size (cr, 38)
cairo_set_source_rgba (cr,19/255,197/255,208/255,1)
cairo_rotate (cr, 120 * (math.pi / 180))
cairo_move_to (cr,130,400)
cairo_rotate (cr, -60 * (math.pi / 180))
cairo_show_text (cr,"Battery Charge "_conky_parse("${battery_percent}"_"%"))

To concatenate the various parts of the output line we use two periods _



Now, when you see a screenshot like the one below, you will know how it was done.




Drawing

We've played around with some text in Lua, but Cairo is a powerful library of graphic functions. We can use Cairo to draw lines, arcs and rectangles. Cairo is also capable of drawing more advanced items, such as curves.

Cairo curves are cubic Bezier curves, sometimes called paths or splines. They have a start point and an end point and various intermediate control points through which they smoothly flow but don't necessarily touch. The positions of the control points determine the shape of the curve and for this reason they are known as parametric curves. Bezier curves have infinite scalability, and as a graphical element, were initially developed for the production of car bodies. These are probably not so useful in Conky, and since they are quite complex topics, I shall not discuss curves in this article.


Lines

To draw a line, you use cairo_move_to to establish the start point, and the cairo_line_to function to draw a line segment. The cairo_line_to function can be repeated to draw multiple line segments, and the cairo_close_path function used to complete a polygon. The thickness of the line can be controlled by cairo_set_line_width, and the width is distributed equally about the start or apex point. The width cannot be changed within multiple line segments. The line width remains constant between invocations of the cairo_stroke function. For open ended lines, the line ending can be one of

CAIRO_LINE_CAP_BUTT, CAIRO_LINE_CAP_ROUND or CAIRO_LINE_CAP_SQUARE

and the joints at the vertices of unclosed lines segments take the form of

CAIRO_LINE_JOIN_MITER, CAIRO_LINE_JOIN_BEVEL or CAIRO_LINE_JOIN_ROUND

You might like to assign these to variables in your template file. If you draw a closed polygon, you can make it filled by replacing cairo_stroke by cairo_fill. Then the line width is reset to one. The following will draw a regular hexagon.

cairo_set_line_width (cr,4)
cairo_move_to (cr,300,200)
cairo_line_to (cr, 250,113)
cairo_line_to (cr, 150,113)
cairo_line_to (cr, 100,200)
cairo_line_to (cr, 150,287)
cairo_line_to (cr, 250,287)
cairo_close_path (cr)
cairo_stroke (cr)

That was a brief look at using lines in Lua with the Cairo libraries.

To do something more useful, as Cairo can draw arcs as well as lines, I'm going to build a pair of speedometer type gauges for the two cores of my processor. But first, we need to look at using arcs.


Arcs

An arc is part of a circle or, if the start and end point coincide, a full circle. Arcs can also be mixed with lines to draw compound shapes. To draw an arc, you need to specify the centre coordinates, the radius, and the start and end angles of the arc. It is also necessary to control the width, color and end type of the arc, although there are defaults.

There are two cairo arc commands:

cairo_arc   draws the clockwise
cairo_arc_negative   draws the arc counterclockwise

They both require the following arguments: surface reference (we are referencing our surface with cr), centre point x,y coordinates, start angle in radians, and end angle in radians.



That's it. Arcs are easy as they accept angles. If you know what it is you are monitoring, then you can make your Conky display unobtrusive. For this one I re-sized the Conky window to accommodate a graphic and then put these lines in my Lua template.

cairo_set_source_rgba (cr,220/255,218/255,213/255,0.2)
cairo_set_line_width (cr, 10)
cairo_arc (cr,390,100,90,-90 * (math.pi/180),45 * (math.pi/180))
cairo_stroke (cr)
cairo_arc (cr,390,100,70,-90 * (math.pi/180),105 * (math.pi/180))
cairo_stroke (cr)
cairo_arc (cr,390,100,50,-90 * (math.pi/180),72 * (math.pi/180))
cairo_stroke (cr)
cairo_set_line_width (cr, 3)
cairo_move_to (cr, 390,55)
cairo_line_to (cr, 390,0 )
cairo_stroke (cr)

I added a little transparency to the arcs. The angular values are random for the demo, but they could easily be parsed from Conky variables.


A new type of gauge

Tip: Use an editor that highlights matching parentheses for this (such as Kate or Geany).

First up is the pointer. I will only show the code for one core. The pointer has a fixed centre point about which it swings according to the value it is gauging. The lowest point is in the lower left quadrant and the highest in the lower right. I have chosen a value of 45º below the horizontal for both, which is equal to -225º for 0% and 45º for 100%, making up a total swing of 270º or 2.7º for each 1% of CPU activity. Lines don't understand angles, so we need to use some math to calculate the endpoints.

I am going to store the values I need in variables, and this allows us to globally change things more easily:

The CPU work rate is found by this: cpu1=((conky_parse(“cpu cpu1”)))

The angle becomes angle1=((cpu1 * 2.7) -225)

The centre point is set as ctrx1,ctrx2=100,200 This sets both x and y values

The length of the pointer id p_len p_len=60

The x coordinate of the end of the pointer is the length of the pointer multiplied by the cosine of the angle, the y value by the sine. Lua will do the math for us.

xval1=ctrx1 + (p_len * (math.cos ((angle1 * (math.pi / 180)))))
yval1=ctry1 + (p_len * (math.sin ((angle1 * (math.pi / 180)))))

After we have set the color, width and end type for the line we are done.

cairo_set_source_rgba (cr,1,1,1,1)
cairo_set_line_width (cr,4)
cairo_set_line_cap (cr,CAIRO_LINE_CAP_ROUND)
ctrx1,ctry1=100,200
cpu1=(conky_parse("${cpu cpu1}"))
cairo_move_to (cr, ctrx1,ctry1)
angle1=((cpu1 * 2.7) -225)
xval1=ctrx1 + (80 * (math.cos ((angle1 * (math.pi / 180)))))
yval1=ctry1 + (80 * (math.sin ((angle1 * (math.pi / 180)))))
cairo_line_to (cr, xval1, yval1)
cairo_stroke (cr)

Now for the outer arc of the gauge. This should appear before the code for the line in the Lua script, as we want the line to appear 'above' the arc. The colours that I want to use are: green, amber and red to represent the normal, caution and danger levels. I also want a black background. The arc centre will be the same as the pointer centre, so that variable can be re-used. The start angle is -225, the caution start angle I decided should be 75% of cpu usage and the danger start angle 90%. The danger end angle is obviously 100%. The line width needs to be greater, perhaps 14 pixels. As the centre point of the arc is the same as the centre point of the pointer and the pointer length is the same as the radius, we can re-use these values.

cairo_set_line_width (cr,14)
cairo_set_source_rgba (cr,0,0,0,0.3)
cairo_arc (cr,ctrx1,ctry1,p_len + 7,0,(2*math.pi))
cairo_fill (cr)
cairo_set_source_rgba (cr,27/255,124/255,16/255,1)
cairo_arc (cr,ctrx1,ctry1,p_len,-225 * (math.pi/180),((75 * 2.7)-225) * (math.pi/180))
cairo_stroke (cr)
cairo_set_source_rgba (cr,226/255,152/255,22/255,1)
cairo_arc (cr,ctrx1,ctry1,p_len,((75 * 2.7)-225) * (math.pi/180),((90 * 2.7) * (math.pi/180))
cairo_stroke (cr)
cairo_set_source_rgba (cr,217/255,8/255,0,1)
cairo_arc (cr,ctrx1,ctry1,p_len,((90 * 2.7) * (math.pi/180),45 * (math.pi/180))
cairo_stroke (cr)



Rectangles

Although lines can be used to draw rectangles, they are such a common object that cairo has a special function to create them, cairo_rectangle. The arguments you supply are the x,y coordinates of the bottom left corner, the width and the height. As you can imagine this makes drawing bar gauges a relatively easy task.

cairo_select_font_face (cr, "Liberation Sans", f_noslant, f_nobold)
cairo_set_font_size (cr, 18)
core1=(conky_parse("${cpu cpu1}"))
core2=(conky_parse("${cpu cpu2}"))
cairo_translate (cr, 100,200)
cairo_set_source_rgba (cr,1,1,1,1)
cairo_rectangle (cr,0,0,30,-100)
cairo_stroke (cr)
cairo_set_source_rgba (cr,0,1,0,1)
cairo_rectangle (cr,0,0,30,-core1)
cairo_fill (cr)
cairo_set_source_rgba (cr,1,1,1,1)
cairo_move_to (cr,-5,25)
cairo_show_text (cr,"core1")
cairo_move_to (cr,22,-25)
cairo_rotate (cr, -90 * (math.pi / 180))
cairo_show_text (cr,core1_" %")



I have used a new function here to move the insertion point cairo_translate.

This is by no means the extent of the capabilities of the Lua/Cairo partnership. That limit is your imagination which I hope to have fired up a little by these brief introductions.



Previous Page              Top              Next Page