SimpleConkyScript/lua/loading.lua

429 lines
17 KiB
Lua

-- HorlogeSkynet's Lua script (for Conky) -- Loading module
-- <https://git.forestier.app/HorlogeSkynet/SimpleConkyScript.git>
local NULL = require('cjson').null
-- Special modules loading : References should be passed here by `main`.
local config, system, drawing = ...
local loading = {}
-- Populates references to configuration object sections.
local _content = config.get_entries('content')
local _settings = config.get_entries('settings')
-- Quick hack to stay compatible across multiple Lua versions.
local unpack = unpack or table.unpack
-- Specific function to load "top" processes.
-- `max_processes` allows you to specify the number of processes to be displayed.
function loading.top_processes(display, x_pos, y_pos, max_processes)
-- Enforce maximum number of processes validation to avoid a Conky warning.
if not max_processes or max_processes > 10 then max_processes = 10 end
-- Process details that will be fetched.
local TOP_ARGS = {
-- Conky only gives us the 16 first bytes of process names.
{ name = "name", length = 16 },
-- For 64-bits systems, let's assume PIDs can reach values with 7 digits (2^22).
{ name = "pid", length = 7 },
-- Float values (XXX.YY).
{ name = "cpu", length = 6 },
{ name = "mem", length = 6 },
{ name = "io_perc", length = 6 },
}
y_pos = drawing.text(
display, x_pos, y_pos,
"NAME PID CPU% MEM% IO%")
-- We apply the globally set padding between these 'text' objects.
y_pos = y_pos + _settings.display.entity_padding
local buffer, line_y
for i = 1, max_processes do
buffer = ''
for _, arg in ipairs(TOP_ARGS) do
buffer = (
buffer
.. string.format(
'%' .. arg.length .. 's',
conky_parse(string.format("${top %s %d}", arg.name, i)))
.. ' ')
end
-- Between detail lines, we apply a reduced padding to highlight the "list" aspect.
line_y = y_pos + (_settings.display.entity_padding / 2.)
y_pos = drawing.text(display, x_pos, line_y, string.sub(buffer, 1, -2))
end
return y_pos
end
-- Simple wrapper to `drawing.text` for temperature pretty-printing.
function loading.temperature(display, x_pos, y_pos, pre_text, value, threshold)
local number_value = tonumber(value)
if number_value then
value = string.format(
"%.1f%s%s",
number_value,
_settings.temperature.degree, _settings.temperature.unit)
-- When a threshold is specified, compare the values and add a warning symbol accordingly.
if threshold and number_value >= threshold then
value = string.format("%s %s", value, _settings.temperature.warning)
end
end
return drawing.text(
display, x_pos, y_pos,
string.format("%s temperature: %s", pre_text, value))
end
-- Simple wrapper to `drawing.text` for fan speed pretty-printing.
function loading.fan_speed(display, x_pos, y_pos, pre_text, value, threshold)
local number_value = tonumber(value)
if number_value then
value = string.format("%d RPM", number_value)
-- When a threshold is specified, compare the values and add a warning symbol accordingly.
if threshold and number_value >= threshold then
value = string.format("%s %s", value, _settings.temperature.warning)
end
end
return drawing.text(
display, x_pos, y_pos,
string.format("%s fan speed: %s", pre_text, value))
end
-- Generic function to load data and print them into a 'graph'.
-- `values` should be a valid `table`, containing data to be formatted.
-- `color` is fully-optional but may reference a `table` containing RGBA color details.
function loading.graph(display, x_pos, y_pos, width, var, arg, name, values, color)
local value = tonumber(conky_parse(string.format("${%s %s}", var, arg)))
-- Sometimes, at startup, the first non-zero 'value' evaluates to a really huge number.
-- See issue #340 (<https://github.com/brndnmtthws/conky/issues/340>).
-- The following statement is here to avoid a huge line drawn on 'graph' objects...
if math.abs(value) < 1.e+18 then
table.insert(values, value)
end
return drawing.graph(display, x_pos, y_pos, width, name, values, color)
end
-- Generic function to load data and print them into a 'text'.
function loading.text(display, x_pos, y_pos, var, arg, name, align_center)
local value = conky_parse(string.format("${%s %s}", var, arg))
return drawing.text_in_column(display, x_pos, y_pos, (name .. ": " .. value), align_center)
end
-- Generic function to load data and print them into a 'bar'.
function loading.bar(display, x_pos, y_pos, var, arg, name, show_percent)
-- Value between 0 and 1 as we get percentage values from Conky variables
local value = tonumber(conky_parse(string.format("${%s %s}", var, arg)))
return drawing.bar(display, x_pos, y_pos, value, name, show_percent)
end
-- Specific function to load data I/O from a device drive (two 'graph' objects).
function loading.drive_io_graphs(display, x_pos, y_pos, drive_name, values)
local SPECIAL_GRAPH_GAP = 10
local SPECIAL_GRAPH_WIDTH = (_settings.display.columns_width - SPECIAL_GRAPH_GAP) / 2.
local SPECIAL_GRAPH_X_POS = x_pos + SPECIAL_GRAPH_WIDTH + SPECIAL_GRAPH_GAP
-- Legend for the entities.
local text = drive_name .. " activity (R/W):"
-- Don't store the new `y_pos`, as graphs will be set aside.
loading.graph(
display, x_pos, y_pos, SPECIAL_GRAPH_WIDTH,
'diskio_read', drive_name,
text,
values.read,
_settings.colors.graph_in) -- Set color for "read" 'graph'.
y_pos = loading.graph(
display, SPECIAL_GRAPH_X_POS, y_pos, SPECIAL_GRAPH_WIDTH,
'diskio_write', drive_name,
'', -- No legend for this one.
values.write,
_settings.colors.graph_out) -- Set color for "write" 'graph'.
return y_pos
end
-- Specific function to load data I/O from a (up) network interface (two 'graph' objects and instant values as 'text').
function loading.network_interface_io_graphs(display, x_pos, y_pos, if_name, values)
local SPECIAL_GRAPH_GAP = 10
local SPECIAL_GRAPH_WIDTH = (_settings.display.columns_width - SPECIAL_GRAPH_GAP) / 2.
local SPECIAL_GRAPH_X_POS = x_pos + SPECIAL_GRAPH_WIDTH + SPECIAL_GRAPH_GAP
-- Legend for the entities.
local text = if_name .. " activity (DOWN/UP):"
-- Don't store the new `y_pos`, as graphs will be set aside.
loading.graph(
display, x_pos, y_pos, SPECIAL_GRAPH_WIDTH,
'downspeedf', if_name,
text,
values.download,
_settings.colors.graph_in) -- Set color for "download" 'graph'.
y_pos = loading.graph(
display, SPECIAL_GRAPH_X_POS, y_pos, SPECIAL_GRAPH_WIDTH,
'upspeedf', if_name,
'', -- No legend for this one.
values.upload,
_settings.colors.graph_out) -- Set color for "upload" 'graph'.
-- Now print the current speed values (latest inserted elements in the tables).
drawing.text(
display,
x_pos, y_pos,
string.format("%.1f KiB", values.download[#values.download]),
SPECIAL_GRAPH_WIDTH)
y_pos = drawing.text(
display,
SPECIAL_GRAPH_X_POS, y_pos,
string.format("%.1f KiB", values.upload[#values.upload]),
SPECIAL_GRAPH_WIDTH)
return y_pos
end
-- Specific function to load IP address of a (up) network interface ('text' objects).
function loading.network_interface_addresses(display, x_pos, y_pos, if_name, hide_v6)
-- Write down the interface name.
y_pos = drawing.text(display, x_pos, y_pos, (if_name .. " addresses:"))
-- We apply the globally set padding between these 'text' objects.
y_pos = y_pos + _settings.display.entity_padding
-- Retrieve IPv4 addresses.
local IPv4 = conky_parse(string.format("${addrs %s}", if_name))
if IPv4 and IPv4 ~= "0.0.0.0" then
local line_y
local addrs = string.gmatch(IPv4, "[0-9.]+")
for addr in addrs do
-- Between address lines, we apply a reduced padding to highlight the "list" aspect.
line_y = y_pos + (_settings.display.entity_padding / 2.)
y_pos = drawing.text(display, x_pos, line_y, "> " .. addr)
end
end
if not hide_v6 then
local IPv6 = conky_parse(string.format("${v6addrs %s}", if_name))
if IPv6 and IPv6 ~= "No Address" then
local line_y
local addrs = string.gmatch(IPv6, "[a-zA-Z0-9:]+")
for addr in addrs do
-- Between address lines, we apply a reduced padding to highlight the "list" aspect.
line_y = y_pos + (_settings.display.entity_padding / 2.)
y_pos = drawing.text(display, x_pos, line_y, "> " .. addr)
end
end
end
return y_pos
end
-- This function "simply" load each configured entry to be displayed.
function loading.load_entities(display, number_of_updates)
-- Initial position, each call updates the `y_pos` in order to place the following elements
local x_pos, y_pos = 0, 0
-- Fetch a copy of the user-defined value to avoid many dereferencing.
local ENTITY_PADDING = _settings.display.entity_padding
-- This closure will dispatch entities loading based on their `type`.
local function _load_entity(entity) -- luacheck: no max cyclomatic complexity
-- When the entity is marked as `disabled`, we simply skip it.
if entity.disabled then return end
-- When the entity specifies an interval timer, let's choose whether we should update its value or not.
-- For entities that support caching, value update may be disabled by setting `interval` to `null` in JSON.
local do_update = (
not entity.interval or
entity.interval == 0 or
(entity.interval ~= NULL and number_of_updates % entity.interval == 0))
-- Additional detail : Some statements below will "dirtily" add special fields their entity objects.
-- Such fields would have a key name preceded by an '_'.
-- Let's now switch on the entity specified `type`.
if entity.type == 'separator' then
y_pos = drawing.section_separation(
display, x_pos, y_pos,
entity.width)
elseif entity.type == 'text' then
y_pos = loading.text(
display, x_pos, y_pos,
entity.var, (entity.arg or ''),
entity.name,
entity.align_center)
elseif entity.type == 'bar' then
y_pos = loading.bar(
display, x_pos, y_pos,
entity.var, (entity.arg or ''),
entity.name,
entity.show_percent)
elseif entity.type == 'graph' then
-- Initialization.
if not entity._values then entity._values = {} end
y_pos = loading.graph(
display, x_pos, y_pos, _settings.display.columns_width,
entity.var, (entity.arg or ''),
entity.name,
entity._values)
elseif entity.type == 'clock' then
y_pos = drawing.clock(display, x_pos, y_pos)
elseif entity.type == 'date_time' then
y_pos = drawing.text_in_column(display, x_pos, y_pos, os.date(entity.format), entity.align_center)
elseif entity.type == 'brightness' then
-- Initialization (or update wanted).
if not entity._value or do_update then
entity._value = system.get_brightness_value(entity.cur_file, entity.max_file)
end
y_pos = drawing.bar(
display, x_pos, y_pos,
entity._value,
(entity.name or 'Brightness'),
entity.show_percent)
elseif entity.type == 'top_processes' then
y_pos = loading.top_processes(display, x_pos, y_pos, entity.max_processes)
elseif entity.type == 'partition_usage' then
y_pos = loading.bar(
display, x_pos, y_pos,
'fs_used_perc', entity.mount_point,
(entity.mount_point .. " usage"),
entity.show_percent)
elseif entity.type == 'drive_io' then
-- Initialization.
if not entity._values then entity._values = {read = {}, write = {}} end
y_pos = loading.drive_io_graphs(display, x_pos, y_pos, entity.drive, entity._values)
elseif entity.type == 'version' then
y_pos = drawing.text_in_column(
display, x_pos, y_pos,
string.format(
"%sConky v%s%s",
(entity.pre_text or ''), conky_version, (entity.post_text or '')),
entity.align_center)
elseif entity.type == 'command' then
-- Initialization (or update wanted).
if not entity._value or do_update then
entity._value = system.get_subcommand_output(entity.exec) or entity.on_error
end
y_pos = drawing.text_in_column(
display, x_pos, y_pos,
string.format(
"%s: %s%s%s",
entity.name,
(entity.pre_text or ''), entity._value, (entity.post_text or '')),
entity.align_center)
-- TODO v4.0: replace `string.sub` by a proper `==` (would remove support for `sensor_temperature` entity type).
elseif string.sub(entity.type, 0, 6) == 'sensor' then
-- Initialization (or update wanted).
if not entity._value or do_update then
entity._value = system.compute_avg_chip_value(
entity.chip,
entity.is_fan) or entity.on_error
end
if entity.is_fan then
y_pos = loading.fan_speed(display, x_pos, y_pos, entity.name, entity._value, entity.threshold)
else
y_pos = loading.temperature(display, x_pos, y_pos, entity.name, entity._value, entity.threshold)
end
elseif entity.type == 'drive_temperature' then
-- Initialization (or update wanted).
if not entity._value or do_update then
entity._value = system.get_drive_temp(entity.drive, entity.wake_up) or entity.on_error
end
y_pos = loading.temperature(display, x_pos, y_pos, entity.drive, entity._value, entity.threshold)
elseif entity.type == 'network_interface_io' then
-- Initialization.
if not entity._values then entity._values = {download = {}, upload = {}} end
-- Check whether the interface is actually up or not.
if not system.is_network_interface_up(entity.interface) then return end
y_pos = loading.network_interface_io_graphs(display, x_pos, y_pos, entity.interface, entity._values)
elseif entity.type == 'network_interface_addr' then
-- Check whether the interface is actually up or not.
if not system.is_network_interface_up(entity.interface) then return end
y_pos = loading.network_interface_addresses(display, x_pos, y_pos, entity.interface, entity.hide_v6)
-- Unknown type (at the moment ?) : Show an error message before skipping padding addition.
else
io.stderr:write(
string.format(
"An unknown (or missing) entity type has been found : \'%s\'.",
entity.type or ''))
return
end
-- Entity padding.
y_pos = y_pos + ENTITY_PADDING
end
-- Set the default color to use.
cairo_set_source_rgba(display, unpack(_settings.colors.default))
-- Set the font, and its size only one time.
cairo_select_font_face(
display,
_settings.font.family,
CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL)
cairo_set_font_size(display, _settings.font.size)
-- Load entities in the left column.
for _, entity in ipairs(_content.left or {}) do _load_entity(entity) end
-- Set the "cursor" position to draw on the middle column.
x_pos, y_pos = ((conky_window.width - _settings.display.columns_width) / 2.), 0
for _, entity in ipairs(_content.middle or {}) do _load_entity(entity) end
-- Set the "cursor" position to draw on the right column.
x_pos, y_pos = (conky_window.width - _settings.display.columns_width), 0
for _, entity in ipairs(_content.right or {}) do _load_entity(entity) end
end
return loading