429 lines
17 KiB
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
|