Skip to content

Using Lua scripts (Part 08): Functions

Brenden Matthews edited this page May 1, 2024 · 5 revisions

viii: 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")
require("cairo_xlib")

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

		-- Remember that we need to make this value negative at some point because
		-- we are drawing up.
		brec_height = bar_height + border_width
		cairo_rectangle(cr, border_bottom_left_x, border_bottom_left_y, brec_width, -brec_height)
		cairo_stroke(cr)
	end

	cairo_destroy(cr)
	cairo_surface_destroy(cs)
	cr = nil
end

We can run the script from Conky by adding these lines to the conky.conf.

conky.config = {
	lua_load = '/path-to-file/filename.lua',
	lua_draw_hook_post  = 'main',
};

conky.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 has code repetition 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. They keep 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 conky.conf.

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 imagine that whenever you call a function, the script is simply 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. 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 didn't 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 the result returned by the multiply function is assigned to a string called "result". print (result) --> 32.

A function doesn't 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

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.
end

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

	-- Remember that we need to make this value negative at some point because
	-- we are drawing up.
	brec_height = bar_height + border_width
	cairo_rectangle(cr, border_bottom_left_x, border_bottom_left_y, brec_width, -brec_height)
	cairo_stroke(cr)
end

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:

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, separating 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, 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, mg, mb, 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, 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, mg, mb,
		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:

  • 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).

It takes the first string in its "string list" (the list inside the curly brackets following the function's 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 :D.

Clone this wiki locally