You are on page 1of 47

Using Lua scripts in conky

PART 1 how does a lua script work?


in conky there are several lua related settings and objects primarily for using lua script there are 2 settings
lua_load lua_draw_hook

that come before TEXT lua_load is where you give conky the location of the script to use eg lua_load /home/username/scripts/script.lua so that one is pretty easy to understand the second line lua_draw_hook (can be lua_draw_hook_pre or lua_draw_hook_post) tells conky which function to run from the lua script that was loaded. This takes a little more explanation. A bare bones lua script for use in conky might look like this
--this is a lua script for use in conky require 'cairo' function conky_main() if conky_window == nil then return end local cs = cairo_xlib_surface_create(conky_window.display, conky_window.drawable, conky_window.visual, conky_window.width, conky_window.height) cr = cairo_create(cs) local updates=tonumber(conky_parse('${updates}')) if updates>5 then --############################## print ("hello world") --############################## end-- if updates>5 cairo_destroy(cr) cairo_surface_destroy(cs) cr=nil end-- end main function

line by line we have --this is a lua script for use in conky anything preceded by -- is a comment in lua you can write a longer comment section starting with --[[ and ending with ]] like so
--[[ this is a comment and it can span multiple lines until you end it with ]]

next we have require 'cairo' cairo is the name of the graphics library that lua will be using to get all those fancy graphics showing up in conky this line loads up that library

then function conky_main() this is our main function and the one we will set in the conkyrc for lua_draw_hook if you open a lua script and not sure which function to set in conkyrc, look for "conky_" in the function name the next 3 lines are standard setup lines and are ONLY required for the main function that will be called in the conkyrc
if conky_window == nil then return end local cs = cairo_xlib_surface_create(conky_window.display, conky_window.drawable, conky_window.visual, conky_window.width, conky_window.height) cr = cairo_create(cs)

This is creating the "surface" onto which the text and graphics will be drawn. If you want the lua script to draw anything to conky you need these setup lines. then we have lines about conky updates
local updates=tonumber(conky_parse('${updates}')) if updates>5 then

these lines are only important if you plan on reading cpu% in the lua script, but its generally a good idea to have these lines anyway (one less thing to cause an error) the first of these lines has a few things to note about it 1. the term "local" at the beginning (cover that later) 2. the use of strings the = sign in lua you hold information in strings by saying: string = information information can be other strings, plain text, numbers etc (more about that later) eg
string1=5 string2=9 string3=string1*string3

in this case string3 holds the value 45 string names cannot start with numbers and cannot contain spaces if a string lower down in the script has the same name as higher, the info in the first string just gets overwirtten eg
string1=7 print (string1) --> 7 string1=9 print (string1) --> 9

3. the use of tonumber if you want lua to do calculations or comparisons based on a number, it can be a good idea to make sure you are setting a string as a number by using tonumber(), particularly when you are taking numbers generated by conky objects or from other means. Just because the output of a conky object is a number DOES NOT mean that when it is set to a string lua will recognise it as a number! It can be a big pain to go tracking down an error to realise it was because the script was not setting an output as a number 4. the use of conky_parse you can get the output of any conky object into lua this way

eg
cpu=conky_parse("${cpu}") memory=conky_parse("${memperc}") home_used=conky_parse("${fs_used /home}")

you can even use things like if_ objects from conky using conky_parse, which can be useful as switches
internet=conky_parse("${if_up wlan0}1${else}0${endif}")

now the string "internet" will have a value of 1 or 0 depending on the outcome of if_up next line in our lua is if updates>5 then this if statement is closed lower in the script by an end end-- if updates>5 i always like to comment about what exactly the "end" ends as in many script the end that closes out "if updates>5 then" can be tricky to pick out and can be a long way down the script what we are saying is if updates (the value of which is set with conky_parse) is greater than 5 then do everything between the "then" and the closing "end" missing ends can be a pain to locate every "if" "for" "while" or other loop needs a matching end to close it! so next we have the body of the script, the stuff we want the lua script to do
--############################## print ("hello world") --##############################

i like to use obvious comment lines to differentiate between the setup parts of the functions and the the body of the functions so that i can navigate easier the command "print ()" will not show anything up in conky, but will be printed in the terminal always run conky from the terminal when testing out a lua script! print () can be a very useful way of finding error in the script if things aren't working then we come to closing out the function
end-- if updates>5 cairo_destroy(cr) cairo_surface_destroy(cs) cr=nil end-- end main function

these lines close out the if function and do some cleaning up one thing that lua can do is to eat up memory over time these lines help to avoid that. the final end ends the main function and your done. I have a script named blank.lua which is just that, a script set up with an empty main function, so i don't have to remember to type out all the setup lines each time

NOTE about writing the code remember! any examples i write are writte as if i were writing a real script (in fact as i go on i'll check my code examples to make sure they really do what i say they do ) this is how I write my code one point to make in particular is about the use of spaces in the code as you can see, i like to write everything tight together
red,green,blue,alpha=1,1,1,1

and use few spaces but in fact lua is very versatile/forgiving in terms of spaces and tabs for example
value=16 print (value) -->16 value = 16--with spaces print (value) -->16 value = 16--tabbed print (value) -->16 value = print (value) -->16

16--any kind of spacing you want

this also applies to things like the rectangle command


cairo_rectangle (cr,50,50,60,40) cairo_rectangle (cr, 50 ,50 ,60 ,40 ) cairo_rectangle (cr, 50 ,

50 ,60

,40 )

all give you the same rectangle however, its always a good idea to keep things neat and tidy! Last edited by mrpeachy (2012-01-27 19:36:21)

PART 2 displaying stuff in conky


so we have a blank main function we can set up our conkyrc to point to the script location with lua_load and we can activate the main function through lua_draw_hook BUT we need something inside our lua function for conky to show! what can lua display in conky? really 1 of 2 things 1. text 2. graphics such things as lines boxes bars circles etc lets look at how to get text onto the screen first, any plain text within the lua script must be contained within quotes "plain text" you can set strings to contain plain text like so
text="hello world" print (text) --> hello world (in terminal)

i like to do things in a "modular" way that is set everything to strings, then use those strings within the code I find this is easier to keep track of variables but is not necessary so i want to display the text "hello world" in conky... here are the lines to do it
font="Mono" font_size=12 text="hello world" xpos,ypos=100,100 red,green,blue,alpha=1,1,1,1 font_slant=CAIRO_FONT_SLANT_NORMAL font_face=CAIRO_FONT_WEIGHT_NORMAL ---------------------------------cairo_select_font_face (cr, font, font_slant, font_face); cairo_set_font_size (cr, font_size) cairo_set_source_rgba (cr,red,green,blue,alpha) cairo_move_to (cr,xpos,ypos) cairo_show_text (cr,text) cairo_stroke (cr)

you can see i set anything that might change to strings first, then used the strings within the code but it would work exactly the same way like this
cairo_select_font_face (cr, "mono", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size (cr, 12) cairo_set_source_rgba (cr,1,1,1,1) cairo_move_to (cr,100,100) cairo_show_text (cr,"hello world")

cairo_stroke (cr)

with all the settings typed directly into the code NOTE when i write lua scripts i usually stick to having one line do one things but you don't have to you can use a semi colon ; to separate operations on the same line for example
cairo_move_to (cr,100,100);cairo_show_text (cr,"hello world");cairo_stroke (cr)

but i find that this can make things harder to navigate when looking back through the script to track down an error for example using either of the above code examples we are going to: - print "hello world" - in white - using the mono font - at font size 12 - at coordinates 100,100 - normal face and slant (ie not bold or italic) but lets look closer at what and how those things are set. font names must be set within quotes but note in the first example, once the font has been set to a string, the string itself is called without quotes the same goes for plain text which must also be in quotes numbers do not require quotes as for font_size another thing to note is how i have set my strings xpos,ypos=100,100 i have set 2 different strings in the same line by using commas to separate them this is exactly the same as writing
xpos=100 ypos=100

the same goes for setting the color red,green,blua,alpha=1,1,1,1 is the same as
red=1 green=1 blua=1 alpha=1

either way is just as good NOTE if you are thinking of later on making a setup section in a different area of the script (at the top most likely) then you will have to use the string setting method and you might also want to make your string names unique if you remember, strings with the same names simply overwrite each other base on where they appear in the script so go for

text_1="hello world" instead of simply text="hello world" x and y when it comes to setting x and y values for text, the point you set will be the coordinates of the bottom left corner of the text and is set relative to the top and left edges of the conky window so x,y=100,100 will move 100 pixels down from the top of the window, 100 pixels across from the left then it will display the text above and to the right of that point color lua requires values for red green blue and alpha these values are all in the range of 0 to 1 So how do you get your values? in many script, you may see a separate function to turn hexadecimal color codes into rgba values I'll discuss other function in a separate part of the guide so for now lets look at the direct way. For example, you can open up gimp and use it to get a color you like:

here ive picked an orangey color and you can see in the window the various values for the color for this purpose look at the RGB values R = 246 G = 155 B = 11 these are almost the values we want, but instead of being between 0 and 1 they are between 0 and 255 one of the advantages of script is that you can do calculations, and in lua you can set a string to be the result of a calculation so we can set something like this to get our values in the correct range (0 to 1)
red=246/255 green=155/255 blue=11/255

alpha is a little different, simply an alpha of 0 is fully transparent, ie you wont see anything and an alpha of 1 == fully opaque, so set whatever value you want finally for font_face and font_slant
font_slant=CAIRO_FONT_SLANT_NORMAL

font_face=CAIRO_FONT_WEIGHT_NORMAL

these do not need to be set within quotes face can be NORMAL or BOLD slant can be NORMAL or ITALIC EXAMPLE we want to display our cpu usage percent in conky via a lua script to show
cpu usage: 23%

(or whatever cpu% is at the time) 1. we use conky_parse to get the cpu number remember that the string names i am using are just my examples, you can call them whatever
cpu_perc=conky_parse("${cpu}")

but our string "cpu_perc" is only the number value from conky, we need to add the other parts there are several ways to do this but probably easiest is to "stitch together" (or concatenate to be technical) the various elements we need. Like this:
cpu_text="cpu usage: "..cpu_perc.."%"

we are stitching some plain text "cpu usage: " to the string that contains the cpu value we want (cpu_perc) and ending with some more plain text "%" the use of a double period is how we put these elements together there are more complex ways to use text but again for later Last edited by mrpeachy (2012-01-17 20:45:24)

Part 3 - graphics
how to draw a straight line! so one of the basic things to be able to do is to draw a line there are several commands we need to consider for lines line thickness
cairo_set_line_width (cr,1)

NOTE, you can see here, and in other examples the general form that the cairo commands take cairo_something (cr,settings) we set the value of cr in our main function setup lines, and every time you use a cairo command, the first thing within the curved brackets will be "cr" line end cap
cairo_set_line_cap or cairo_set_line_cap or cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT) (cr, CAIRO_LINE_CAP_ROUND) (cr, CAIRO_LINE_CAP_SQUARE)

see here for more info http://cairographics.org/samples/set_line_cap/ the default is BUTT, so that is what you get if you don't setup the cap type and then commands we have seen before
cairo_set_source_rgba (cr,1,1,1,1)

and we need to specify where the line is going to start


cairo_move_to (cr,100,100)

we could do our setup like we did for our text


line_width=1 line_cap=CAIRO_LINE_CAP_BUTT red,green,blue,alpha=1,1,1,1 startx=100 starty=100 ---------------------------cairo_set_line_width (cr,line_width) cairo_set_line_cap (cr, line_cap) cairo_set_source_rgba (cr,red,green,blue,alpha) cairo_move_to (cr,startx,starty)

now we need to draw the line and there are 2 ways to do it you can specify the ending coordinates directly
cairo_line_to (cr,200,100)

or you can specify where the line should go relative to the start
cairo_rel_line_to (cr,100,0)

in this case we get the same result...

in the first example we start at 100,100 and tell cairo to draw a line to 200,100 in the second example we start at 100,100 and tell cairo to draw a line to a point 100 pixels to the right of where we started and 0 pixels down from where we started so both examples will give a horizontal line 100 pixels long once we have finished specifying the coordinates we then tell cairo to actually draw the line
cairo_stroke (cr)

i tend to use the absolute method, "line_to" rather than the relative method so altogether:
line_width=1 line_cap=CAIRO_LINE_CAP_BUTT red,green,blue,alpha=1,1,1,1 startx=100 starty=100 endx=200 endy=100 ---------------------------cairo_set_line_width (cr,line_width) cairo_set_line_cap (cr, line_cap) cairo_set_source_rgba (cr,red,green,blue,alpha) cairo_move_to (cr,startx,starty) cairo_line_to (cr,endx,endy) cairo_stroke (cr)

NOTE about line thickness we set a line from 100,100 to 200,100 but setting a line thickness greater than 1 will affect not only what the line looks like but where it appears to have been drawn if we set, for example, a line width of 10, then the line will be 5 pixels wide to one side of the base line, and 5 pixels wide to the other side that is, the top left corner of the line will actually be at 100,95 and the bottom left corner at 100,105 (remember that the larger the x and y numbers the more right and down they are) - the line will be more of a rectangle. making the line more interesting but with some math and tweaking we can easily make this line change in length relative to a conky object once you get the idea of using variables and strings to affect the coordinates and other values of drawn objects then you are a good way there and everything else is just the complexity of the interactions and what is being drawn for example we want to make a cpu usage indicator line
cpu_perc=tonumber(conky_parse("${cpu}"))

i'll use tonumber just to make sure... it can be easy to get lost in curved brackets when using compound commands like this this will output a number between 0 and 100 the line we have already drawn is 100 pixels long so its easy enough to do the following
cpu_perc=tonumber(conky_parse("${cpu}"))

line_width=1 line_cap=CAIRO_LINE_CAP_BUTT red,green,blue,alpha=1,1,1,1 startx=100 starty=100 endx=startx+cpu_perc endy=starty ---------------------------cairo_set_line_width (cr,line_width) cairo_set_line_cap (cr, line_cap) cairo_set_source_rgba (cr,red,green,blue,alpha) cairo_move_to (cr,startx,starty) cairo_line_to (cr,endx,endy) cairo_stroke (cr)

this is what is going to make the line move endx=startx+cpu_perc the horizontal end point of my line will change in relation to the value of cpu_perc as measure through the conky variable ${cpu} ive made a couple of other changes... instead of saying "endx=100+cpu_perc" i have put "endx=startx+cpu_perc" and instead of "endy=100" i have "endy=starty" using this method, all i have to do is edit startx and starty and those changes will follow on to the other strings when i am writing scripts i find it time saving to set as few "absolute" vales as possible gives you less things to find later on when you want to make changes you can do more with lines than just draw lines you can use lines to draw shapes you can then have these shapes as outlines or fill them in a triangle for example there are some other things to consider when using multiple lines one thing is how to join the lines together which is set by the following
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER) or cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL) or cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND)

see here for more details http://cairographics.org/samples/set_line_join/ MITER is the default so that is what you get when you dont set line join type we can also use this command
cairo_close_path (cr)

close path tells cairo that the lines should be joined together at the ends to make one continuous line this is what you get without close_path on the left and with close_path on the right

this is using the default MITER line join also

NOTE as soon as you set cairo_close_path the setting for line cap type becomes redundant, as there are no longer any line ends here is the code for the figure on the right above
line_width=20 line_cap=CAIRO_LINE_CAP_BUTT --we don't need this anymore after closing the path line_join=CAIRO_LINE_JOIN_MITER --but this will still affect how the lines look red,green,blue,alpha=1,1,1,1 startx=100 starty=100 pointx=startx+100 pointy=starty+100 endx=pointx-100 endy=pointy ---------------------------cairo_set_line_width (cr,line_width) cairo_set_line_cap (cr, line_cap) cairo_set_source_rgba (cr,red,green,blue,alpha) cairo_move_to (cr,startx,starty) cairo_line_to (cr,pointx,pointy) cairo_line_to (cr,endx,endy) cairo_line_to (cr,startx,starty) cairo_set_line_join (cr, line_join) cairo_close_path (cr) cairo_stroke (cr)

NOTE as you can see, you don't have to draw each individual line as you are instead drawing a "path" (imagine a pen being drawn on paper in one continuous line from point to point) you only need to use cairo_stroke at the end to draw along the path you set to see the lines it is also good to know that by using the close_path command we don't actually need to draw the third side of the triangle. When you use close_path cairo will draw a line from wherever you stop back to the start. if we want to fill in the triangle we use
cairo_fill (cr)

instead of
cairo_stroke (cr)

but with cairo_fill the setting for line width no longer applies. also with fill the line join type no longer applies The fill fills in only the triangle bound by the coordinates we set. ALSO with cairo_fill, we need not use the close_path command, as the fill command will automatically close the path in order to generate a boundary area to fill in... but it never hurts to use the close_path command and nothing bad will happen if you set redundant commands for example i could specify all 3 sides of the triangle, close_path and then cairo_fill and i will still get a filled in triangle! lua code can be quite forgiving here is stroke vs fill

and if you want the shape outlined and filled (for example in different colors) we would first use cairo_fill_preserve (instead of just cairo_fill), then set our second color and use cairo_stroke to draw the outline
cairo_set_line_width (cr,20) cairo_move_to (cr,100,100)--start point cairo_line_to (cr,200,200)--diagonal line down cairo_line_to (cr,100,200)--horizontal line cairo_close_path (cr)--draws vertical line back to start cairo_set_source_rgba (cr,1,1,1,1)--white cairo_fill_preserve (cr)--fills in the triangle in white cairo_set_source_rgba (cr,1,0,0,1)--red cairo_stroke (cr)--draws the triangle outline in red

Last edited by mrpeachy (2012-01-18 02:41:22)

part 4 -- drawing other things


there are a few things that cairo can draw other than lines namely: 1. rectangles 2. arcs (and circles) 3. curves rectangles we could use lines as in the triangle to draw ourselves a rectangle, but cairo has the ability to draw rectangles built in so might as well use it
cairo_rectangle (cr, x, y, width, height)

the x,y coordinates are the top left corner of the rectangle just like our triangle we can set up our shape like so
--settings line_width=5 top_left_x=20 top_left_y=20 rec_width=100 rec_height=50 red=1 green=0 blue=0 alpha=1 --draw it cairo_set_line_width (cr,line_width) cairo_rectangle (cr,top_left_x,top_left_y,rec_width,rec_height) cairo_set_source_rgba (cr,red,green,blue,alpha)

then use stroke to draw the outline


cairo_stroke (cr)

or
cairo_fill (cr)

to fill it in or if you want the rectangle filled with one color and line in a different colors we would first fill with cairo_fill_preserve, then set our second color and draw our line with cairo_stroke
--settings line_width=5 top_left_x=20 top_left_y=20 rec_width=100 rec_height=50 fill_red=1 fill_green=1 fill_blue=1 fill_alpha=1 line_red=1

line_green=0 line_blue=0 line_alpha=1 --draw it cairo_set_line_width (cr,line_width) cairo_rectangle (cr,top_left_x,top_left_y,rec_width,rec_height) cairo_set_source_rgba (cr,fill_red,fill_green,fill_blue,fill_alpha) cairo_fill_preserve (cr) cairo_set_source_rgba (cr,line_red,line_green,line_blue,line_alpha) cairo_stroke (cr)

NOTE with rectangles there is no point in setting line_cap like we did for lines as there are no "loose ends" to the rectangle there is also no need to use cairo_close_path, as it is closed automatically however setting how the lines join will affect the look of the rectangle when we use cairo_stroke
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND) cairo_stroke (cr)

will give rounded corners for example line width, line join type and end cap type do not affect the fill command 2. arc and circles there are 2 commands for arcs
cairo_arc (cr,center_x,center_y,radius,start_angle,end_angle) and cairo_arc_negative (cr,center_x,center_y,radius,start_angle,end_angle)

cairo_arc draws the arc clockwise while cairo_arc_negative draws the arc anticlockwise the first obstacle to drawing arcs is that you need to enter your start and end angles in radians rather than degrees. so we need to know how to convert between the two
radians=degrees*(pi/180)

lua has a number of built in math functions and one of them, math.pi, gives you the value of pi the other quirk about using the arc drawing command is that angle 0 isn't the top of the circle as you would expect rather angle 0 is the rightmost point in the circle (which i would call 90 degrees) these things don't matter if we only want to draw a complete circle, we just need the following
center_x=100 center_y=100 radius=50 start_angle=0 end_angle=2*math.pi--same thing as 360 degrees cairo_arc (cr,center_x,center_y,radius,start_angle,end_angle) cairo_stroke (cr)

NOTE whenever you use stroke to draw something you need to set a line_width. also with stroke, if you are going to see line ends then altering the line cap type will affect those ends. the other setting that can affect stroke is the line join type if creating a path from multiple elements arcs along with curves and lines can be all put together to form a single path in the example above you will also need to set color and alpha as before. Change cairo_stroke to cairo_fill to fill the circle in or use the previously described method for fill and line. I wont necessarily put these setup lines, or all the available options, into code examples from now on. BUT if we dont want a full circle then we need to worry about radians and setting angles to get an 1/4 circle, from the topmost point of the circle,clockwise to the rightmost point, we can do several things... my code would look like this
center_x=100 center_y=100 radius=50 start_angle=0 end_angle=90 cairo_arc (cr,center_x,center_y,radius,(start_angle-90)*(math.pi/180),(end_angle90)*(math.pi/180)) cairo_stroke (cr)

since i only want to enter angles as degrees i can put the conversion calculations into the arc command eg (start_angle-90)*(math.pi/180) first i want the top of my circe to be 0 degrees (or 360 if you like) so i have to compensate for the arc command quirk by subtracting 90 degrees from the value i set then i need to convert to radians by multiplying by (math.pi/180) cairo_stroke to get the line this in the code:
cairo_close_path (cr) cairo_stroke (cr)

will result in a straight line drawn from the end of the arc back to the beginning and cairo_fill (cr) instead of cairo_stroke would result in a filled in bump with a flat bottom here is cairo_stroke without close_path, cairo_stroke with close path and cairo_fill

using arc or arc_negative so if you give cairo_arc a start angle of 270 and an end angle of 90 you get the top half of a circle you could also set start=270, end=450 (360+90) and get the same things or start=-90, end=90 and get the same thing if you give cairo_arc_negatove a start angle of 270 and an end angle of 90 you get the bottom half of a circle this can be important for drawing paths, as you want your path to be a continuous progression (imagine drawing a line on some paper but not allowed to lift the pen). Also remember that using the close_path or fill commands (which close the path automatically) you draw a line from where you ended to where you started. You can get unexpected fill and stroke results if you piece together your path in a non continuous manner. The other time you may want to use are or arc negative is to make a ring meter go one way or another while you could achieve the effect just using, for example, arc (which naturally draws in a clockwise direction) for both, you have to think a bit harder about the math to get the ring going in the opposite direction 3 curves i'll leave curves for another time they can be quite tricky to set up and use Last edited by mrpeachy (2012-01-18 20:25:41)

PART 5 - making some indicators


make a cpu indicator bar we made a line that changes length according to the reading of cpu% from conky but to make that a bit more interesting we will make a vertical indicator bar to show cpu the bar will have a colored background and a different colored indicator it will also be resizable through settings and placeable so where to start? first we need to think about the order of operations in lua with cairo, things drawn lower down in the script will be drawn over the top of things drawn above so we are going to have a colored background, which will need to be behind the indicator, so we need to draw the background first. we are going to define some settings 1. where we want the indicator to be 2. how big we want the indicator 3. the colors for our background and indicator so our setting section might look like this
--SETTINGS FOR CPU INDICATOR BAR bar_bottom_left_x= 50 bar_bottom_left_y= 200 bar_width= 30 bar_height= 100 --set bar background colors, 1,0,0,1 = fully opaque red bar_bg_red=1 bar_bg_green=0 bar_bg_blue=0 bar_bg_alpha=1 --set indicator colors, 1,1,1,1 = fully opaque white bar_in_red=1 bar_in_green=1 bar_in_blue=1 bar_in_alpha=1

we want unique names so that there is no chance of names being duplicated elsewhere in the script and overwriting our settings (i usually have my string names tell me something about the information kept in the string). it can be a pain to type long string names over and over again but worse when you run the script and it doesnt work as expected because a string has been ovewritten drawing the background is then pretty simple, we will used a filled in rectangle the only thing to note is that by asking for the bottom coordinates, when we come to set the height of the rectangle, our height will be negative (because we are drawing up!) we are using the rectangle function, so we dont need to specify an end cap type and we are using fill so we dont need to specify line join type or line width
--draw background cairo_set_source_rgba (cr,bar_bg_red,bar_bg_green,bar_bg_blue,bar_bg_alpha) cairo_rectangle (cr,bar_bottom_left_x,bar_bottom_left_y,bar_width,-bar_height) cairo_fill (cr)

so we have a red rectangle, now we need to think about making the indicator bar first we need to get our reading into a string
value=tonumber(conky_parse("${cpu}"))

we are going to use the string value to affect the height of the indicator bar, but we have a further complication in that we want out to be able to set indicator size in our settings part so we need some math! if we wanted out indicator to be 100 pixels high we would just be able to substitute cpu reading for height, as cpu will give us numbers between 0 and 100 but say we wanted our indicator to be 200 pixels high or only 50 pixels high, we need to scale up or down our cpu readings so that the indicator bar grows and shrinks in the right proportions we know our maximum value for cpu is going to be 100 we need to divide the height we want by this maximum value to get the scale right if we set bar_height=200 and max_value=100 then scale=bar_height/max_value scale = 2, so that for every increase of 1 in cpu % reading the bar will move an additional 2 pixels up so at a cpu % of 100 the bar will be 200 pixels long, which is what we want so
indicator_height=scale*value

and this is the number we plug into our rectangle drawing command all together for the indicator
--draw indicator cairo_set_source_rgba (cr,bar_in_red,bar_in_green,bar_in_blue,bar_in_alpha)--set indicator color value=tonumber(conky_parse("${cpu}")) max_value=100 scale=bar_height/max_value indicator_height=scale*value cairo_rectangle (cr,bar_bottom_left_x,bar_bottom_left_y,bar_width,indicator_height) cairo_fill (cr)

and you have a working cpu indicator bar NOW what you could do is copy this code chunk over and over again, edit the settings and assign a different conky object to each instance for as many bars as you want set up some text to label each bar and your done. HOWEVER there is a better and easier way to get multiple bars than copying and pasting code we can turn our indicator bar drawing code into a function but more about functions in a later part! making a cpu indicator circle meter im picking cpu as my object of choice for no other reason than it is the one that changes

the most so you get to see your handy work in action making a circle meter is very similar to the bar, except we are using the output of cpu% from conky to change the angles passed to the arc drawing command we will have a ring meter with a background ring one color and an indicator ring of a different color we will make the ring sizable through settings we will have the ring show 0 cpu at the top and then as cpu increases the indicator bar will increase in size clockwise around the ring until at 100% it makes a complete circle and ends back at the top we are going clockwise, so we will use cairo_arc unlike the rectangle, we will be using cairo_stroke to draw our meter so caro_set_line_width must be set which will give us the width of our indicator lines setting the line cap type will have an effect on what the meter looks like but we are not joining any lines so there is no point setting line join type first you need to think about what kind of setting you want a reminder of how we draw an arc
cairo_arc (cr,center_x,center_y,radius,start_angle,end_angle) --SETTINGS --rings size ring_center_x=200 ring_center_y=200 ring_radius=50 ring_width=20 --colors --set background colors, 1,0,0,1 = fully opaque red ring_bg_red=1 ring_bg_green=0 ring_bg_blue=0 ring_bg_alpha=1 --set indicator colors, 1,1,1,1 = fully opaque white ring_in_red=1 ring_in_green=1 ring_in_blue=1 ring_in_alpha=1 --indicator value settings value=conky_parse("${cpu}") max_value=100

just as the rectangle, go ahead and draw the background ring like so
--draw background cairo_set_line_width (cr,ring_width) cairo_set_source_rgba (cr,ring_bg_red,ring_bg_green,ring_bg_blue,ring_bg_alpha) cairo_arc (cr,ring_center_x,ring_center_y,ring_radius,0,2*math.pi) cairo_stroke (cr)

NOTE about line_width line width when we are drawing a circle or arc works the same way as a straight line half the line width is drawn to one side of the base line, one half of the line width is drawn to the other side

so if you set a radius of 50 pixels and a line width of 20 pixels, then from the center point of the circle to the inner edge of the line will only be 40 pixels and it will be 60 pixels to the outer edge of the circle we have a red background circle now we have to work out how to apply the cpu% number held in the string "value" to making the indicator line move. in this case it is the arc's end angle that will be changing relative to cpu% again we need a bit of math to get the indicator arc moving in the right proportion
degrees=360 scale=degrees/max_value--ie for every 1% increase in cpu% the arc should move an additional 3.6 degrees end_angle=value*scale

or i could just write


end_angle=value*(360/max_value)

now we can plug everything into the arc drawing command like so
--draw indicator cairo_set_line_width (cr,ring_width) end_angle=value*(360/max_value) cairo_set_source_rgba (cr,ring_in_red,ring_in_green,ring_in_blue,ring_in_alpha) cairo_arc (cr,ring_center_x,ring_center_y,ring_radius,0,(end_angle90)*(math.pi/180)) cairo_stroke (cr)

HUH??? if you try the above code you will notice that something doesn't quite seem right i thought i would put this in to take a look at what to do when things go wrong the first thing i tried was adding a print line to the code like this:
--draw indicator cairo_set_line_width (cr,ring_width) end_angle=value*(360/max_value) print (end_angle) cairo_set_source_rgba (cr,ring_in_red,ring_in_green,ring_in_blue,ring_in_alpha) cairo_arc (cr,ring_center_x,ring_center_y,ring_radius,0,(end_angle90)*(math.pi/180)) cairo_stroke (cr)

was i calculating something wrong?? so i took a look at the terminal and watched what end_angle was being calculated as i also went to the conkyrc and put in ${cpu} under TEXT so i could watch cpu at the same time (i could have just put "print (value, end_angle)" and gotten both value in the terminal i suppose) but it looked as if the calculations were giving me the correct result, so it must be something in the cairo_arc command itself that was going wrong
cairo_arc (cr,ring_center_x,ring_center_y,ring_radius,0,(end_angle90)*(math.pi/180))

it was the start angle, which i had left at 0 i hadn't compensated for the arc command quirk so actually my 0 line was 90 degrees further around my circle than i thought it was! a quick modification later:
cairo_set_line_width (cr,ring_width) end_angle=value*(360/max_value) --print (end_angle) cairo_set_source_rgba (cr,ring_in_red,ring_in_green,ring_in_blue,ring_in_alpha) cairo_arc (cr,ring_center_x,ring_center_y,ring_radius,(-90)*(math.pi/180), (end_angle-90)*(math.pi/180)) cairo_stroke (cr)

and we have a circle meter Last edited by mrpeachy (2012-01-19 01:04:11)

PART 6 - some more drawing calculations and if statements


so we have our bar indicator ive moved the value setting and max_value setting up into the settings part of the code and put "bar_" in front of each just to keep to my naming convention
--SETTINGS FOR CPU INDICATOR BAR bar_bottom_left_x= 750 bar_bottom_left_y= 200 bar_width= 30 bar_height= 100 bar_value=tonumber(conky_parse("${cpu}")) bar_max_value=100 --set bar background colors, 1,0,0,1 = fully opaque red bar_bg_red=1 bar_bg_green=0 bar_bg_blue=0 bar_bg_alpha=1 --set indicator colors, 1,1,1,1 = fully opaque white bar_in_red=1 bar_in_green=1 bar_in_blue=1 bar_in_alpha=1 --END OF SETTINGS --DRAW BAR INDICATOR --draw background cairo_set_source_rgba (cr,bar_bg_red,bar_bg_green,bar_bg_blue,bar_bg_alpha) cairo_rectangle (cr,bar_bottom_left_x,bar_bottom_left_y,bar_width,-bar_height) cairo_fill (cr) --draw indicator cairo_set_source_rgba (cr,bar_in_red,bar_in_green,bar_in_blue,bar_in_alpha)--set indicator color scale=bar_height/bar_max_value indicator_height=scale*bar_value cairo_rectangle (cr,bar_bottom_left_x,bar_bottom_left_y,bar_width,indicator_height) cairo_fill (cr)

but we want to jazz it up a bit and add a few additional features 1. we want the ability to draw a border around our indicator - we want the border to be optional - we want the ability to set the border color and border line thickness 2. we want the ability to set an "alarm" value for the bar so that: - when the value we feed the bar bar reaches or goes past a certain point the bar changes to a different color - we want to be able to specify the alarm value and the color the bar changes to 1 - adding an border in our settings section we need some new options
bar_border=1 --set 1 for border or 0 for no border --set border color rgba border_red=0 border_green=1 border_blue=1 border_alpha=1 --set border thickness

border_width=10

i tend to use number switches for options like this, 1=yes i want it, 0=no i dont but you could just as well use text answers
bar_border="yes" --set "yes" for border or "no" for no border

the downside is that "Yes" is not the same as "yes" when we come to use a comparison later on this case specificity can be overcome by using the in built commands string.upper() or string.lower() but that would be a topic for another time for now we will use either 1 or 0 another aspect of the border is that it shouldn't cover up any part of the indicator bar and there are actually several ways i can think of achieving the desired border we could draw a filled in rectangle behind the indicator background rectangle our "border" rectangle would be larger than the indicator rectangles, so you would see its edges sticking out around the edges of the indicator or we could use the rectangle command with cairo_stroke so that we only draw the lines and dont fill it in or we could draw individual lines each of these methods has some positives and negatives but lets go with drawing a rectangle with cairo_stroke the tricky part here is how line_width is applied when drawing the rectangle

all 3 of these rectangles have been set a width of 60 and a height of 40 the difference you see is that they have line widths of 1,10 and 20 respectively we need to compensate for the encroachment of the border into the middle of our rectangle this is how i would do it first we need to set the x and y coordinates for our border relative to bar_bottom_left_x

and bar_bottom_left_y so that when we want to move our bar around we only need to edit bar_bottom_left_x and bar_bottom_left_y and all other strings change automatically in relation to those settings
border_bottom_left_x=bar_bottom_left_x-(border_width/2)--we [u]subtract[/u] because we are going to the [u]left[/u] border_bottom_left_y=bar_bottom_left_y+(border_width/2)--we [u]add[/u] because we are going [u]down[/u]

this starts our border rectangle to the left and lower than we start our bar to compensate for line_width but now we also have to make the border rectangle wider and bigger than our bar rectangle so that it surrounds the bar rectangle
brec_width=bar_width+border_width brec_height=bar_height+border_width

NOTE when i was writing the above lines, i thought that since i had named the width and length strings for the indicator rectangles bar_width and bar_height that i would call the width and height strings for the border rectangle border_width and border_height (even had these typed out). BUT i realised that i couldnt use "border_width" because i have already used it to name the string that holds the value of line_width for the border, so i had to come up with something else and we can set up our border and plug those strings into the rectangle drawing command like so
cairo_set_source_rgba (cr,border_red,border_green,border_blue,border_alpha) cairo_set_line_width (cr,border_width) border_bottom_left_x=bar_bottom_left_x-(border_width/2) border_bottom_left_y=bar_bottom_left_y+(border_width/2) brec_width=bar_width+border_width brec_height=bar_height+border_width--remember that we need to make the value of brec_height negative at some point because we are drawing up cairo_rectangle (cr,border_bottom_left_x,border_bottom_left_y,brec_width,brec_height)--we will set brec_height negative in this line cairo_stroke (cr)

where should we put this code? since we have done our calculations correctly, the border shouldnt interfere with our indicator, so you could have this code in several places, before we draw the background, affter the background and before the indicator or after the indicator. so we have a border but we havn't used the setting "bar_border" to allow us to optionally have the border or not. to do that we need an if statement IF STATEMENTS for example:
if bar_border==1 then --border drawing code end

this is the simplest form of the if statement if we set bar_border=1 in our settings then the border is drawn

or more specifically, if the string "bar_border" has a value of 1 then everything between then and end is done if we set bar_border to 0 (or if bar_border is set to anything other than 1 in this case) then the border doesn't get drawn (anything between then and end is skipped) EVERY IF NEEDS AN END you could also say that every if needs a then, but depending on how you set up your statement, there can be multiple thens within it NOTE we need a double equals sign (==) when we are making a comparison other options for comparisons are: > greater than >= greater than or equal to < less than <= less than or equal to ~= not equal to we'll get to a more complicated if later on Last edited by mrpeachy (2012-01-19 03:07:43)

PART 7 - adding our alarm color change feature and looking at more complex if statements
we were jazzing up our bar meter we added an optional border as feature 1 and we are going to have the bar change color for feature 2 to get the color change we i'll need to add a few more settings! most likely if you are going to set an alarm color, you'll think of red (as i did) so we would have to re-color our background bar also (which is also currently red) im feeling a little lazy so im going to go for some shorter string names, particularly for all the reds green blue and alphas coming up
--set alarm value, this is the value at which bar color will change alarm_value=80 --set alarm bar color, 1,0,0,1 = red fully opaque ar,ag,ab,aa=1,0,0,1

we already have a base bar color so we want the bar to be our base color unless the cpu% reading equals or goes above 80% we are going to be using another IF to get the colors to change as with most code, there are multiple ways we could go about doing things lets first remind ourselves of the code that is drawing the indicator part of the bar it is that part that we will be editing as it is that part which will change color
cairo_set_source_rgba (cr,bar_in_red,bar_in_green,bar_in_blue,bar_in_alpha)--set indicator color scale=bar_height/bar_max_value indicator_height=scale*bar_value cairo_rectangle (cr,bar_bottom_left_x,bar_bottom_left_y,bar_width,indicator_height) cairo_fill (cr)

to apply our if statement to colors we could do the following: remember, "bar_value" is the name of the string holds the value of cpu% and we set "alarm_value" above to 80
if bar_value<alarm_value then cairo_set_source_rgba (cr,bar_in_red,bar_in_green,bar_in_blue,bar_in_alpha) else cairo_set_source_rgba (cr,ar,ag,ab,aa) end

hopefully it should be quite self explanatory what is going on in the above code if the string named "bar_value" has a value of less than 80 then the color is set to the base indicator color (which we have set to white in settings) -all code between then and else is done if the value of "bar_value" is not less than 80 (ie equal to or greater than 80) then color is set to the alarm color -all the code between then and else is skipped and everything between else and end is done this would have also worked
if bar_value>=80 then

cairo_set_source_rgba (cr,ar,ag,ab,aa) else cairo_set_source_rgba (cr,bar_in_red,bar_in_green,bar_in_blue,bar_in_alpha) end

how do we know its working? we think that we have our code right and we expect that if our cpu% gets to 80% or more that our bar color will change from white to red but its always a good idea to check that things are working the way we expect. We could do something on the computer that would drive the actual cpu% reading up to 80 or above but there is an easier way in our settings we have the line that sets the value of bar_value
bar_value=tonumber(conky_parse("${cpu}"))

just edit that line and "hard code" bar_value to a test value like this
bar_value=80--tonumber(conky_parse("${cpu}"))

i dont like to completely replace the line with our test value as it simply harder to reinstate the line afterwards with the test value in place and the rest of the line commented out, once we are sure our feature is working we just delete the test value and the comments and everything is back to being operational now we like the color change, and we want to implement another color change we want the bar to be one color when cpu% is low, say green up to 50% then we want the color to change to a different color, yellow for example, between 50% and 80% then if our reading gets to 80 or over it goes to red so back to the settings we already have our alarm (or high cpu usage) value and color
--set value for first color change, low cpu usage to mid cpu usage mid_value=50 --set "low" cpu usage color and alpha, ie bar color below 50% - 0,1,0,1=fully opaque green lr,lg,lb,la=0,1,0,1 --set "mid" cpu usage color, between 50 and 79 - 1,1,0,1=fully opaque yellow mr,mg,mb,ma=1,1,0,1

while we could reuse our base bar color settings, and just change the numbers to green or yellow
bar_in_red=1 bar_in_green=1 bar_in_blue=1 bar_in_alpha=1

since we are making some significant changes to how our colors are working i would just go ahead and delete them and replace them with the new low mid and alarm values i tend to like to stick to a single naming convention also which helps me to remember

what i have named strings (and maybe because of some mild OCD)) but again, thats just me, you can call your string whatever you want to! so now we need to think about how to set up our if statement when cpu is between 0 and 49 we want green when cpu is between 50 and 79 we want yellow and when cpu is 80 or above we want red so lets put them into a statement
if bar_value>=mid_value then --ie if value is greater or equal to 50 cairo_set_source_rgba (cr,mr,mg,mb,ma)--yellow (mid color) elseif bar_value>=alarm_value then --ie if bar_value is greater or equal to 80 cairo_set_source_rgba (cr,ar,ag,ab,aa)--red (alarm color) else cairo_set_source_rgba (cr,lr,lg,lb,la)--green (low color) end

we use an elseif, because it is no longer an either or situation, there are 3 possibilities we need to sort out test the setup by setting bar_value to some test value we set bar_value=49 and we get a green bar, and it is green for any number below 50 we set bar_value=50 and we get a yellow bar then try bar_value=80 and the bar is still yellow! and it is yellow all the way up to 100 our statement seems entirely logical, but this is one of the pitfalls you can get stuck in to understand it we need to think about how our if statement works and we can see that the problem is in the order of our comparisons
if bar_value>=mid_value then --ie if value is greater or equal to 50 cairo_set_source_rgba (cr,mr,mg,mb,ma)--yellow (mid color)

this first line does indeed match any number from 50 to 100 and so any cpu vale from 50 to 100 will result in a yellow bar. the script comes to the line, takes the value of "bar_value" calculates if it is greater or equal to the value of mid_value if it passes the comparison, the script runs the corresponding code and then its done with the if statement only if bar_vale is less than 50 then the first comparison will fail and the script will move on to the next comparison but of course this comparison will fail also as if a value is not greater or equal to 50 then it wont be greater or equal to 80 once past the if and the following elseif the script automatically runs the code following else which doesnt require a comparison again there are plenty of ways we can fix this we can try and reorder our comparisons like so
if bar_value>=alarm_value then --ie if value is greater or equal to 80 cairo_set_source_rgba (cr,ar,ag,ab,aa)--red (alarm color) elseif bar_value>=mid_value then --ie if bar_value is greater or equal to 50 cairo_set_source_rgba (cr,mr,mg,mb,ma)--yellow (mid color) else

cairo_set_source_rgba (cr,lr,lg,lb,la)--green (low color) end

take a value of say 90 check 1) is 90 greater or equal to 80? yes -> pass the first comparison and run the code to set color to red done! set a value of 60 check 1) is 60 greater or equal to 80? no -> fail the first comparison, move onto the next check 2) is 60 greater or equal to 50? yes -> pass the second comparison and run the code to set color to yellow done! set a value of 48 check 1) is 48 greater or equal to 80? no -> fail the first comparison, move onto the next check 2) is 48 greater or equal to 50? no -> fail the second comparison, move onto the next no more checks, run code following else and set color to green done! Last edited by mrpeachy (2012-01-20 00:10:45)

PART 8 - turn our code into a function


take the example of the bar indicator with all the features we added and the entire lua script might look something like this
--[[this lua script draws vertical bar indicators]] require 'cairo' function conky_main() if conky_window == nil then return end local cs = cairo_xlib_surface_create(conky_window.display, conky_window.drawable, conky_window.visual, conky_window.width, conky_window.height) cr = cairo_create(cs) local updates=tonumber(conky_parse('${updates}')) if updates>5 then -################################################################################## ####################### --SETTINGS FOR INDICATOR BAR bar_bottom_left_x= 100 bar_bottom_left_y= 100 bar_width= 30 bar_height= 100 bar_value=tonumber(conky_parse("${cpu}")) bar_max_value=100 --set bar background colors, 0.5,0.5,0.5,1 = fully opaque grey bar_bg_red=0.5 bar_bg_green=0.5 bar_bg_blue=0.5 bar_bg_alpha=1 --bar border settings bar_border=1 --set 1 for border or 0 for no border --set border color rgba border_red=0 border_green=1 border_blue=1 border_alpha=1 --set border thickness border_width=10 --color change --set value for first color change, low cpu usage to mid cpu usage mid_value=50 --set "low" cpu usage color and alpha, ie bar color below 50% - 0,1,0,1=fully opaque green lr,lg,lb,la=0,1,0,1 --set "mid" cpu usage color, between 50 and 79 - 1,1,0,1=fully opaque yellow mr,mg,mb,ma=1,1,0,1 --set alarm value, this is the value at which bar color will change alarm_value=80 --set alarm bar color, 1,0,0,1 = red fully opaque ar,ag,ab,aa=1,0,0,1 --end of settings --draw bar --draw background cairo_set_source_rgba (cr,bar_bg_red,bar_bg_green,bar_bg_blue,bar_bg_alpha) cairo_rectangle (cr,bar_bottom_left_x,bar_bottom_left_y,bar_width,-bar_height) cairo_fill (cr)

--draw indicator if bar_value>=alarm_value then --ie if value is greater or equal to 50 cairo_set_source_rgba (cr,ar,ag,ab,aa)--yellow elseif bar_value>=mid_value then --ie if bar_value is greater or equal to 80 cairo_set_source_rgba (cr,mr,mg,mb,ma)--red else cairo_set_source_rgba (cr,lr,lg,lb,la)--green end scale=bar_height/bar_max_value indicator_height=scale*bar_value cairo_rectangle (cr,bar_bottom_left_x,bar_bottom_left_y,bar_width,indicator_height) cairo_fill (cr) --draw border cairo_set_source_rgba (cr,border_red,border_green,border_blue,border_alpha) cairo_set_line_width (cr,border_width) border_bottom_left_x=bar_bottom_left_x-(border_width/2) border_bottom_left_y=bar_bottom_left_y+(border_width/2) brec_width=bar_width+border_width brec_height=bar_height+border_width--remember that we need to make this value negative at some point because we are drawing up cairo_rectangle (cr,border_bottom_left_x,border_bottom_left_y,brec_width,brec_height) cairo_stroke (cr) -################################################################################## ####################### end-- if updates>5 cairo_destroy(cr) cairo_surface_destroy(cs) cr=nil end-- end main function

we can run the script from conky by adding these lines to the conkyrc
lua_load /path-to-file/filename.lua lua_draw_hook_post main TEXT

but as it currently stands we only get one indicator bar out of it, currently set to display cpu% there are plenty of other conky objects that we might want use our indicator bar with. one thing we could do is copy all the code required to draw the bar, all the settings and all the drawing code, and paste it over and over, duplicating the code within the main lua function for as many bars as you want. then you would go to each repetition of code, edit the settings (a minimum of setting a different conky object and new coordinates) there is nothing wrong with this approach, plenty of my script have code repetitions in them and our code isnt all that long (we would have about 60 code lines per bar)... BUT if our code was longer, and even for code this length, if we wanted lots of bars we would end up with a pretty large main function with settings and drawing code all mixed together. This can make harder work of editing when you want to make a change.

FUNCTIONS are a simple way to execute the same piece of code over and over again without increasing your line count and so keeping your main function free from clutter which makes it easier to navigate and edit. before we go about editing our lua script to turn the bar code into a function, i'll do my best to explain what functions are all about! if you look at other pre made lua scripts out there you will see that there are almost always other functions in the script in addition to the main function that is called in the conkyrc. Functions can be as simple or as complex as you need the function to be. for use in conky, other functions are written outside of the main function either above or below. due to the way lua scripts work, writing other functions below the main function isnt a problem. From a programming point of view it would be more "proper" to have your other functions above the main. BUT many times you want to enter settings into the lua script, and more often than not these settings will go into the main function which is one reason why you may not want to bury your main function below the other functions. all your other functions do not need the conky setup lines that the main function needs, you will be calling these other function from within the main function, so image that whenever you call a function, the script is simple substituting the function call with the code it contains, so that in essence the code exists within the main function so all you need for other functions is to -start the function -give it a name -set out what the function is going to do -end the function here is an example of a simple function
function multiply(number) return number*2 end

this function accepts a number and returns the result of that number multiplied by 2 (for the sake of brevity i wont write out all the setup code for the main function just remember you need all the elements i described in part 1 of this how to for the main function if you want lua to display stuff in conky) so how do we use the function i just wrote? lets say that we put it under the main function we would have a script like so
function conky_main() *setup lines* --####################### result=multiply(16) print (result) --####################### *close out lines end-- main function function multiply(number) return number*2

end

the round brackets are very important here this is how we send information to our other functions i am using the function inside main like this result=multiply(16) So I am sending the multiply function the number 16 inside the curved brackets The multiply function takes the number 16 and sets a string called "number" to that value function multiply(number) so hopefully you can see the relationship between what is written between curved brackets when sending information to functions This string could be called anything i wanted (as long as it didnt start with a number or have spaces) there is nothing special about calling it "number" it could just as easily be "baboons" but as ive said elsewhere i think its a good idea for the string name to reflect the contents of that string the function takes the value held in "number", multiplies it by 2 and returns the result in this case result=multiply(16) the result returned by the multiply function is assigned to a string called "result"
print(result) --> 32

A function doesnt have to return a value, they can be used for as many different purposes as you can think of SO lets work on getting our bar drawing code into its own function -think of what we are going to call the function lets call it "indicator_bar" so we can start our new function, and we will go and start the function below our main function we might as well put our "end" in there too, and comment it so we dont forget what its for
function indicator_bar() --############################ --insert function code --############################ end--of bar_indicator function

now we need to think about what code are going to put into our function? this is one reason why i wrote my initial code so that there was a separate settings part where we assigned all the input values to strings and a separate drawing part where we manipulated our strings and plugged them into the commands that are responsible for getting the actual bar graphics onto the screen. the point of making the function is that we want to be able to send our function different sets of settings and have the function output the graphics for each set SO we will retain the setup portion for each bar within the main conky function and put the bar drawing code into the "indicator_bar". we can just go ahead and cut the code out of the main conky function and paste it into

the "indicator_bar" function once separated, the code would look something like this:
function conky_main() *setup lines* --####################### --SETTINGS FOR INDICATOR BAR bar_bottom_left_x= 100 bar_bottom_left_y= 100 bar_width= 30 bar_height= 100 bar_value=tonumber(conky_parse("${cpu}")) bar_max_value=100 --set bar background colors, 0.5,0.5,0.5,1 = fully opaque grey bar_bg_red=0.5 bar_bg_green=0.5 bar_bg_blue=0.5 bar_bg_alpha=1 --bar border settings bar_border=1 --set 1 for border or 0 for no border --set border color rgba border_red=0 border_green=1 border_blue=1 border_alpha=1 --set border thickness border_width=10 --color change --set value for first color change, low cpu usage to mid cpu usage mid_value=50 --set "low" cpu usage color and alpha, ie bar color below 50% - 0,1,0,1=fully opaque green lr,lg,lb,la=0,1,0,1 --set "mid" cpu usage color, between 50 and 79 - 1,1,0,1=fully opaque yellow mr,mg,mb,ma=1,1,0,1 --set alarm value, this is the value at which bar color will change alarm_value=80 --set alarm bar color, 1,0,0,1 = red fully opaque ar,ag,ab,aa=1,0,0,1 --end of settings --####################### *close out lines end-- main function function indicator_bar() --############################ --draw bar --draw background cairo_set_source_rgba(cr,bar_bg_red,bar_bg_green,bar_bg_blue,bar_bg_alpha) cairo_rectangle (cr,bar_bottom_left_x,bar_bottom_left_y,bar_width,-bar_height) cairo_fill (cr) --draw indicator if bar_value>=alarm_value then --ie if value is greater or equal to 50 cairo_set_source_rgba (cr,ar,ag,ab,aa)--yellow elseif bar_value>=mid_value then --ie if bar_value is greater or equal to 80 cairo_set_source_rgba (cr,mr,mg,mb,ma)--red else

cairo_set_source_rgba (cr,lr,lg,lb,la)--green end scale=bar_height/bar_max_value indicator_height=scale*bar_value cairo_rectangle (cr,bar_bottom_left_x,bar_bottom_left_y,bar_width,indicator_height) cairo_fill (cr) --draw border cairo_set_source_rgba (cr,border_red,border_green,border_blue,border_alpha) cairo_set_line_width (cr,border_width) border_bottom_left_x=bar_bottom_left_x-(border_width/2) border_bottom_left_y=bar_bottom_left_y+(border_width/2) brec_width=bar_width+border_width brec_height=bar_height+border_width--remember that we need to make this value negative at some point because we are drawing up cairo_rectangle (cr,border_bottom_left_x,border_bottom_left_y,brec_width,brec_height) cairo_stroke (cr) --############################ end--of bar_indicator function

the next thing is to think about how we get the settings information inside the "conky_main" function to the bar drawing code inside the "indicator_bar" function we do this using curly brackets
--we call the function in the main function like so indicator_bar(information to send to the function) --and we need to set up the function to accept the information function indicator_bar(information that was sent to the function)

there are a couple of ways to ago about this (as always) but for this example we will be sending and receiving strings so what strings does the drawing code in indicator_bar need? the same strings that we set in the settings part, we can write them in one line between our curved brackets seperating each string from the next by a comma these are the strings that "indicator_bar" needs to receive to work properly so we can write the following:
function indicator_bar(bar_bottom_left_x,bar_bottom_left_y,bar_width,bar_height,bar_value,b ar_max_value,bar_bg_red,bar_bg_green,bar_bg_blue,bar_bg_alpha,bar_border,border_re d,border_green,border_blue,border_alpha,border_width,mid_value,lr,lg,lb,la,mr,mg,m b,ma,alarm_value,ar,ag,ab,aa)

that is our indicator_bar function finished. NOTE the first code line in this function is a color setup line
cairo_set_source_rgba(cr,bar_bg_red,bar_bg_green,bar_bg_blue,bar_bg_alpha)

and the first string this line needs is "bar_bg_red" in our function the code takes the value for "bar_bg_red" directly from the string of the same name found in the curved brackets following the function name

the same goes for all the other string values that the code needs so when we call the function inside conky_main, these are the things that we need to send
indicator_bar(bar_bottom_left_x,bar_bottom_left_y,bar_width,bar_height,bar_value,b ar_max_value,bar_bg_red,bar_bg_green,bar_bg_blue,bar_bg_alpha,bar_border,border_re d,border_green,border_blue,border_alpha,border_width,mid_value,lr,lg,lb,la,mr,mg,m b,ma,alarm_value,ar,ag,ab,aa)

NOTE when sending the information in the function call above there are several ways we can go about it 1. we can input our values directly into the function call like so
indicator_bar(100,100,30,100,tonumber(conky_parse("$ {cpu}")),100,0.5,0.5,0.5,1,1,0,1,1,1,10,50,0,1,0,1,1,1,0,1,80,1,0,0,1)

we send this to indicator_bar it takes the first string in its "string list" (the list inside the curly brackets following the functions name) and sets that string to the value of the first thing in the "received list" which is the list of values received from the function call bar_bottom_left_x=100 then it sets each subsequent string in its "string list" to each subsequent value the "received list" next, 2nd entry in "string list" = 2nd entry in "received list" bar_bottom_left_y=100 next, 3rd entry in "string list" = 3rd entry in "received list" bar_width=30 SO every string in the functions "string list" requires a matching value in the list of things the function received from the function call if we miss out a value (or put in an extra value) between () in the function call, the function just keeps on assigning vales in the order they are received leading to strings being assigned the wrong values if we send 9 values in the function call but there are 10 strings to be set in the functions "string list" then the 10th string will have no value and be set to nil trying to perform operations on strings that have a value of nil is a sure way to an error i'll continue with function in a next part, i think i have already over-explained everything Last edited by mrpeachy (2012-01-21 17:11:59)

PART 9 sending information to functions


I left off after describing how to send indicator bar setup information from the main conky (conky_main) function to a separate function called indicator_bar which takes the information sent to it and draws a bar indicator based on those settings we did this in conky_main
indicator_bar(100,100,30,100,tonumber(conky_parse("$ {cpu}")),100,0.5,0.5,0.5,1,1,0,1,1,1,10,50,0,1,0,1,1,1,0,1,80,1,0,0,1)

putting all our settings right into the function call while this way works just fine there are some drawbacks to doing it this way first of all its just hard to keep track of what each number means and second its easy to miss something out (or miss out a comma which are necessary to seperate values) as I described earlier a missed entry (or comma) when sending information this way will lead to your script giving an error i prefer to define my strings, and then duplicate each string name in the function call for example using the multiply function it would look like this
number=16 result=multiply(number)

now we dont have to touch our function call change the number we are sending to the function, we only need change the value we are assigning to the string called "number" so for our indicator bar setup we would just take the settings lines we already have, and add the call at the end
--SETTINGS FOR INDICATOR BAR bar_bottom_left_x= 100 bar_bottom_left_y= 100 bar_width= 30 bar_height= 100 bar_value=tonumber(conky_parse("${cpu}")) bar_max_value=100 --set bar background colors, 0.5,0.5,0.5,1 = fully opaque grey bar_bg_red=0.5 bar_bg_green=0.5 bar_bg_blue=0.5 bar_bg_alpha=1 --bar border settings bar_border=1 --set 1 for border or 0 for no border --set border color rgba border_red=0 border_green=1 border_blue=1 border_alpha=1 --set border thickness border_width=10 --color change --set value for first color change, low cpu usage to mid cpu usage mid_value=50 --set "low" cpu usage color and alpha, ie bar color below 50% - 0,1,0,1=fully

opaque green lr,lg,lb,la=0,1,0,1 --set "mid" cpu usage color, between 50 and 79 - 1,1,0,1=fully opaque yellow mr,mg,mb,ma=1,1,0,1 --set alarm value, this is the value at which bar color will change alarm_value=80 --set alarm bar color, 1,0,0,1 = red fully opaque ar,ag,ab,aa=1,0,0,1 --end of settings, call drawing function [b]indicator_bar(bar_bottom_left_x,bar_bottom_left_y,bar_width,bar_height,bar_valu e,bar_max_value,bar_bg_red,bar_bg_green,bar_bg_blue,bar_bg_alpha,bar_border,border _red,border_green,border_blue,border_alpha,border_width,mid_value,lr,lg,lb,la,mr,m g,mb,ma,alarm_value,ar,ag,ab,aa)[/b]

you set values to all your strings and each string matches each entry in the function call list so that the values are transfered directly between the two writing your code this spells out everything nice and simply which allows easy entry of settings you dont have to remember what numbers mean what, you dont have to worry about commas and you are less likely to miss out a value (you would have to delete a whole setting line to do that) the down side to this is that it takes a lot more lines of code this way. ANOTHER WAY you can do something half way between these two methods mentioned so far which takes advantage of the fact that lua doesnt mind having its code split onto multipl lines AS LONG AS the information is bounded either by curved brackets () when sending info to functions or curly brackets {} when defining a table take the rectangle drawing command
cairo_rectangle (cr,50,50,50,-100)

this is just like the function we made, except you dont see the rectangle drawing code anywhere in the script (it's a built in function). we call the function "cairo_rectangle" and send it the information it needs inside curved brackets () BUT once the curved brackets are opened (the opening bracket has to be on the same line as the function call) we can then split up our entries on to seperate lines
cairo_rectangle (cr, 50, 50, 50, -100)

we have to make sure that we have still have commas seperating the entries but entering data this way has the advantage that we can write comments on each line and everything still works
cairo_rectangle (cr, 50,--rectangle x position 50,--rectangle y position 50,--rectangle width -100--rectangle height

we could do this just the same with out indicator bar function you can see below i have split some entries onto seperate lines but kept others together on the same line where it makes sense
indicator_bar( 750,400,--position x,y 30,--bar width 100,--bar height tonumber(conky_parse("${cpu}")),--value to send bar 100,--max value 0.5,0.5,0.5,1,--bar background color rgba - grey 1,--want border, 1=yes,0=no 0,1,1,1,--border color rgba - light blue 10,--border width 50,--mid value, point of first color change 0,1,0,1,--low cpu% color rgba - green 1,1,0,1,--mid cpu% color rgba - yellow 80,--alarm value, point of second color change 1,0,0,1--alarm color rgba - red )

A MORE COMPLEX USE OF FUNCTIONS the functions i have given as examples are pretty straightforward set up the function, call it and give it the info it needs, the function outputs something for example a number or some graphics but the use of functions can be more complex here is an example we are going to have our main conky function, and two additional functions : one function is going to be for drawing arcs the second function will convert degrees into radians and adjust for the cairo_arc quirk so that 0 degrees is the top of the circle our angle conversion function might look like this
function arad(degrees) --compensate for cairo_arc quirk degrees=degrees-90 --convert degrees to radians radians=degrees*(math.pi/180) --return angle return radians end--function arad

NOTE this line degrees=degrees-90 i do this quite often, as others do in their scripts im taking the value of the string "degrees" as it was set inside () subtracting 90 from it but putting the result of that operation into a string also called "degrees" as mentioned earlier, strings of the same name overwrite each other and the second instance of a string called "degrees" will indeed overwrite the first instance BUT not until the original "degrees" string is used in the calculation

as long as you know that you will no longer need the value held in the first "degrees" string after using it in, for example, a calculation then reusing the string name just makes it so that you dont have to keep thinking u new string names! The arc drawing function might look like this ill use nice short string names here mx will be for middle x position my will be for middle y position rad for radius sa for start angle ea for end angle (i will be giving angles in degrees when i call the function) i want all my circles to be white with a line thickness of 1, so im going to set color,alpha and line_width in the function itself
function circle(mx,my,rad,sa,ea) cairo_set_source_rgba (cr,1,1,1,1) cairo_set_line_width (cr,1) cairo_arc (cr,mx,my,rad,arad(sa),arad(ea)) cairo_stroke (cr) end--function circle

the important line to note is this one cairo_arc (cr,mx,my,rad,arad(sa),arad(ea)) i can use the arad function inside the cairo_arc command to convert the degree values we send "circle" into adjusted radian values to use with cairo_arc then last of all we have our conky_main function
function conky_main () --conky function setup lines --########################## circle(100,100,50,90,270) --########################## --cony function close out lines end-- of conky main

if we think about the flow of information, we send "circle" the information in the function call inside "conky_main" cirlce recieves the list of values and sets its list of strings to those vales in order it uses the strings my,mx and rad directly but it ships out sa and ea to "arad" arad recieves the value of sa, puts that value into a string called degrees and converts the value to adjusted radians and sends this new value back to "circle" where is is used as the start value directly in the cairo_arc command then arad receives the value of ea and returns the result directly into cairo_arc the chain of functions using functions could keep hoing for as many functions as you could write and you can of course use as many functions within any other function as you want next part will introduce tables Last edited by mrpeachy (2012-01-21 20:15:15)

Part 10, sending info to functions using tables


Tables in Lua are extremely useful and versatile, so im going to focus on using them to send information. We have a bar meter drawing function separate to our main function and i've been through a few methods of getting the information the function needs from the main function via the curved brackets () in the function call to the function itself. i have so far shown how we can do this by sending strings --call function domsomething in main and send info dosomething(1,0,0,1,"mono",12) --dosomething receives info and sets it to strings function dosomething(red,green,blue,alpha,font,foontsize) this is a nice straightforward way of passing the information BUT it has some drawbacks you have to send EVERYTHING the function needs EVERY time you call the function if you don't send it everything and miss something out, or put it in the wrong order then you will get unexpected behaviour from the function as it sets its strings to the wrong values or, more likely, you will get a nil error. We can set tables to get around this this way does requires some extra work initially but once set up, you have the ability to, for example, specify default values for strings in advance and then only send the function the required information. Say you you you you wanted 5 bar indicators want them all to have grey backgrounds want them all to be the same height and width want the indicator to be green from 0 to 45, then yellow up to 75 and then red

the only thing that will be different between each bar will be the value it displays and the x,y coordinates so. possibly we want our bars all to line up in a row, so we may only need to change the x while keeping y the same so to cut down on typing and save time all we want to send to the function is the value to display and the x coordinate of the meter when function become complicated, the list of things the function needs to operate can become very large. In addition, the script may allow you to do several different things (eg draw a square or a circle) so you would only want to send the information that the specific part of the script (if you want to draw a circle you dont want to have to specify settings for a square that you wont use!) USING TABLES There are different kinds of tables.... indexed tables, "dictionary" tables and mixed tables (which have both)) for this use of sending information we will be using a "dictionary" tables which usually take this general form" name of table =

open the table with curly brackets { enter data separated by commas close the table with }
table={red=1,green=0,blue=0,alpha=1,font="mono",fontsize=12}

as i mentioned before once you have your table open table={ you can space out and use multiple lines to enter the data on and allow for the use of comments... as long as you use commas in the right place!
table={--set table and open red=1, green=0, blue=0, alpha=1,--alpha of 0 = full transparent, 1=full opaque font="mono", fontsize=12, }--closes table

NOTE - it isnt a good idea to call a table "table" just as it is a bad idea to call a string "string" just as with strings, give your tables unique names with the table name giving some information about what that table contains. now when we want to send info to our "dosomething" function we can do this i'll be using a better name for the table below
--setup function domsomething in main and call function dosettings={--set table and open red=1, green=0, blue=0, alpha=1,--alpha of 0 = full transparent, 1=full opaque font="mono", fontsize=12, }--closes table dosomething(dosettings) -------------------------------------------------------------------------------dosomething receives the table, and puts the info into another table function dosomething(settings)

NOTE the name of the table you put your settings into (dosettings in the above) and the table name you set for the function itself to use (settings in the above example) don't have to be the same function "dosomething" just puts the info inside table "dosettings" and puts it into table "settings" in the same order NOW WE HAVE TO GET THE INFO OUT this is where the extra work comes in when sending the strings, the strings were immediately available to the function when sending tables we need to get the info out and into strings this will all happen in the "dosomething" function

in the function we have a table called "settings" that contains the info we need because:
function dosomething(settings)

there are several ways we can get the info out of a dictionary type table 1. using a period this method takes the form
tablename.stringname

we know our tablename, "settings" and when we look at what was set in the table, we see that we were setting strings
dosettings={--set table and open red=1, green=0, blue=0, alpha=1,--alpha of 0 = full transparent, 1=full opaque font="mono", fontsize=12, }--closes table

so red,green,blue,alpha,font and fontsize are the names of the strings within the table so, we want to get our color values out of the table and into strings... we would do the following:
redvalue=settings.red greenvalue=settings.green bluevalue=settings.blue alphavalue=settings.alpha

NOW: string "redvalue" has a value of 1 string "greenvalue" has a value of 0 string "bluevalue" has a value of 0 string "alphavalue" has a value of 1 and we can use these string in the function like so:
cairo_set_source_rgba (cr,redvalue,greenvalue,bluevalue,alphavalue)

BUT we said earlier tha we didnt want to have to send "dosomething" colorvalues every time we want to use the function. when we call the function we only want to specify the font and fontsize
--in main function call dosomething dosettings={--open fontsize=12, font="Sans", }--close

NOTE - with dictionary type tables, the order in which you enter your data doesn't matter HOWEVER "dosomething" still needs color and alpha values if we have the line inside "dosomething"

redvalue=settings.red

redvalue will be "nil" and we will get an error when we try and use it to set the color
Conky: llua_do_call: function conky_test execution failed: /home/benjamin/lua/codetest.lua:11: error in function 'cairo_set_source_rgba'. argument #2 is 'nil'; 'number' expected.

this error message pops up in the terminal ALWAYS RUN CONKY FROM TERMINAL WHEN TESTING! its quite helpful... it tells us the name of the file that is causing the problem, it tells us the line number in that file where the error occurs and it tells us where in that line the error is here is my code, lines 10 and 11
red=nil cairo_set_source_rgba (cr,red,1,1,1)

and we can see the error on line 11, the second data position within the set source brackets () WE NEED TO CATCH THE NIL BEFORE ITS USED the error occurred on line 11 (when we tried to use the string) not on line 10 (where the string was set to nill) we can catch nil values using IF statements
redvalue=settings.red if redvalue==nil then redvalue=1 end

you will usually find these short statements written on a single like like so
redvalue=settings.red if redvalue==nil then redvalue=1 end

so the above is where our default settings come into play if, when calling "dosomething" you don't want to specify color values instead you want to default to white if you dont set "red" in the function call in the main function then in "dosomething" settings.red will be nill and so redvalue will be nil. If redvalue is nil then the IF evaluation "if redvalue==nil then" is passed and the code inside the If is executed, setting redvalue to 1. To expand that idea a bit we could set up defualt settings in the main script then use them in the function
function conky_main --setup lines --set default values for color, alpha, font and fontsize default_red=1 default_green=1 default_blue=1 default_alpha=1 default_font="mono"

default_fontsize=12 --setup and call dosomething function dosettings={ fontsize=14 } domseomthing(dosettings) --closeout lines end-- of main function ------------------------------------------------------------------------function dosomething(settings) redvalue=settings.red if redvalue==nil then redvalue=default_red end greenvalue=settings.green if greenvalue==nil then greenvalue=default_green end bluevalue=settings.blue if bluevalue==nil then bluevalue=default_blue end alphavalue=settings.alpha if alphavalue==nil then alphavalue=defualt_alpha end fontvalue=settings.font if fontvalue==nil then fontvalue=default_font end fontsizevalue=settings.fontsize if fontsizevalue==nil then fontsizevalue=default_fontsize end --use strings to do something end--of dosomething function

if we ant the default we simply dont send the value when in the call if we do set them in the call, we set "fontsize=14" then when we look at the relevant lines in dosomething:
fontsizevalue=settings.fontsize if fontsizevalue==nil then fontsizevalue=default_fontsize end

settings.fontsize is not equal to nil, it is equal to 14 the IF evaluation is not passed and the code inside the IF is not executed so when we use fontsizevalue in the function it retains its value of 14. Ill go ahead and post at this point but will likely add more to this post Last edited by mrpeachy (2012-01-27 19:46:13)