Compare commits

..

2 Commits

Author SHA1 Message Date
egutierrez 6224120f7d merge: pixel-perfect + reloj + pestañas Procesos/Devices (v0.2.0) 2026-06-06 13:20:08 +02:00
Egutierrez e4fdd0199d feat: reloj permanente, pestañas Procesos y Devices, líneas pixel-perfect
- Gráficos de línea pixel-perfect: ANTIALIAS_NONE + ancho 1px + coords
  ancladas al centro del pixel, para trazos nítidos sobre la rejilla.
- Reloj + fecha (formato europeo) en una franja superior común a todas las
  pestañas; la barra de pestañas y el contenido bajan para hacerle sitio.
- Nueva pestaña Procesos: nº de procesos (total + en ejecución), hilos,
  carga 1/5/15 min y tablas TOP CPU / RAM / I/O. Cada fila muestra el PID
  (clicable, abre htop -p) separado del nombre.
- Nueva pestaña Devices: almacenamiento (lsblk sin loops), interfaces de
  red físicas e IP, y dispositivos USB (lsusb).
- Docker con detalle: contador running/total y, por contenedor, nombre +
  imagen + estado abreviado (Up 33h / Up 2d) coloreado según salud.
- Fix: el header de Docker se dibujaba con baseline directa y solapaba la
  barra de pestañas; ahora usa el mismo offset que el resto de paneles.
- metric.sh: helpers nproc_count/running, nthreads, load_avg, top_cpu/ram/io,
  disk_list, usb_list, net_ifaces, docker_list, docker_count.
- Ventana 545 -> 575 px de alto. app.md a v0.2.0.
2026-06-06 13:20:08 +02:00
4 changed files with 212 additions and 40 deletions
+12 -6
View File
@@ -2,9 +2,9 @@
name: conky_widget
lang: lua
domain: infra
version: 0.1.0
description: "Widget de escritorio Conky con pestañas clickeables (Red / Sistema / Docker) y botones que lanzan herramientas de análisis de red."
tags: [conky, widget, desktop, monitoring, network, lua, launcher-buttons]
version: 0.2.0
description: "Widget de escritorio Conky con 5 pestañas clickeables (Sistema / Red / Docker / Procesos / Devices), reloj permanente, gráficos de línea pixel-perfect y botones que lanzan herramientas de análisis de red."
tags: [conky, widget, desktop, monitoring, network, processes, devices, lua, launcher-buttons]
uses_functions: []
uses_types: []
framework: "conky"
@@ -31,13 +31,19 @@ el estado del PC en vivo en la esquina superior derecha del monitor primario.
El contenido está organizado en tres pestañas clickeables y, en la pestaña de
red, incluye botones que abren herramientas de análisis de paquetes.
Una franja superior con **reloj + fecha** (formato europeo) es común a todas las
pestañas. Los gráficos de línea se dibujan sin antialiasing y con las coordenadas
ancladas al pixel (`pixel-perfect`).
## Pestañas
| Pestaña | Contenido |
|---|---|
| **Red** | Velocidad de bajada/subida de `enp5s0`, gráfico histórico en vivo (60 s), conexiones TCP/UDP establecidas, total descargado/subido, y botones [Wireshark] [ntopng] [nethogs]. |
| **Sistema** | CPU total + barras por core, RAM, Swap, uso de disco `/`, temperatura, load average y uptime. |
| **Docker** | Número de contenedores en marcha / totales y lista de nombres activos (lectura sin sudo). |
| **Sistema** | Gráficos de área en vivo: CPU, RAM, CPU temp, GPU, GPU temp, VRAM, disco I/O y red, más barras de uso de los cuatro discos. Las series se vuelven rojas al superar su umbral. |
| **Red** | Velocidad de bajada/subida de `enp5s0`, gráfico histórico en vivo, paquetes/s por protocolo, top hosts remotos, conexiones establecidas, total descargado/subido, y botones [Wireshark] [ntopng] [nethogs]. |
| **Docker** | Contador `running/total` y, por contenedor, nombre + imagen + estado abreviado (`Up 33h`, `Up 2d`), coloreado según salud. |
| **Procesos** | Nº de procesos (total + en ejecución), hilos, carga 1/5/15 min, y tablas TOP CPU / TOP RAM / TOP I/O. Cada fila muestra el PID (clicable: abre `htop -p <pid>`) y el nombre. |
| **Devices** | Dispositivos de almacenamiento (`lsblk`, sin loops), interfaces de red físicas con su IP, y dispositivos USB (`lsusb`). |
## Cómo funciona
+1 -1
View File
@@ -16,7 +16,7 @@ conky.config = {
gap_y = 50, -- separa del panel superior de XFCE
minimum_width = 290,
maximum_width = 290,
minimum_height = 545,
minimum_height = 575,
-- Ventana ('dock' = recibe clicks Y el WM no lo mueve con Alt+drag; 'normal' lo dejaba mover; 'desktop' no recibe clicks) ----------
own_window = true,
+162 -33
View File
@@ -23,11 +23,14 @@ pcall(require, 'cairo_xlib') -- conky >= 1.12 separa el modulo cairo_xlib
-- Geometria compartida entre dibujo y eventos de raton -----------------------
local W = 290
local H = 545
local TAB_TOP = 4
local H = 575
local HDR_TOP = 4 -- franja del reloj (comun a todas las pestañas)
local HDR_H = 30
local TAB_TOP = HDR_TOP + HDR_H -- la barra de pestañas baja bajo el reloj
local TAB_H = 24
local CONTENT_TOP = TAB_TOP + TAB_H + 4 -- inicio del area de cada panel
local BTN_H = 26
local BTN_Y = 322 -- fila de botones de la pestaña Red
local BTN_Y = CONTENT_TOP + 276 -- fila de botones de la pestaña Red (offset original del panel)
-- Interfaz de red a monitorizar (enp5s0 es la fisica activa) ------------------
local NIF = "enp5s0"
@@ -36,9 +39,15 @@ local NIF = "enp5s0"
local MET = os.getenv("HOME") .. "/.config/conky/conky_widget/metric.sh"
-- Pestañas (Sistema por defecto) ---------------------------------------------
local TABS = { "Sistema", "Red", "Docker" }
local TABS = { "Sistema", "Red", "Docker", "Procs", "Devs" }
local NTABS = #TABS
local current_tab = 1
-- Filas clicables de la pestaña Procesos (se rellenan en cada frame de dibujo).
-- Al clicar una fila se abre el comando PROC_CLICK con el PID de esa fila.
local g_proc_rows = {}
local PROC_CLICK = "kitty -e htop -p %s" -- %s = PID
-- Botones de la pestaña Red. bin se comprueba antes de lanzar; si falta,
-- launch.sh avisa con notify-send.
local BTNS = {
@@ -253,28 +262,45 @@ local function graph(cr, x, y, w, h, series)
cairo_close_path(cr)
setcol(cr, col, 0.25); cairo_fill(cr)
end
cairo_set_line_width(cr, 1.3); setcol(cr, col)
-- Lineas pixel-perfect: sin antialias y con las coordenadas ancladas al
-- centro del pixel (floor + 0.5) para que el trazo de 1px caiga nitido
-- sobre la rejilla en lugar de difuminarse entre dos columnas.
cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE)
cairo_set_line_width(cr, 1.0); setcol(cr, col)
for i = 1, n do
if i == 1 then cairo_move_to(cr, px(i), py(i)) else cairo_line_to(cr, px(i), py(i)) end
local sx = math.floor(px(i)) + 0.5
local sy = math.floor(py(i)) + 0.5
if i == 1 then cairo_move_to(cr, sx, sy) else cairo_line_to(cr, sx, sy) end
end
cairo_stroke(cr)
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT)
end
end
-- Barra de pestañas ----------------------------------------------------------
local function draw_tabs(cr)
local tw = W / 3
for i = 1, 3 do
local tw = W / NTABS
for i = 1, NTABS do
local x = (i - 1) * tw
local active = (i == current_tab)
setcol(cr, active and COL.tab_active or COL.tab_inactive)
rrect(cr, x + 3, TAB_TOP, tw - 6, TAB_H, 5); cairo_fill(cr)
rrect(cr, x + 2, TAB_TOP, tw - 4, TAB_H, 4); cairo_fill(cr)
local lbl = TABS[i]
text(cr, x + tw / 2 - #lbl * 12 * 0.6 / 2, TAB_TOP + 17, lbl,
active and COL.white or COL.dim, 12, active)
text(cr, x + tw / 2 - #lbl * 10 * 0.6 / 2, TAB_TOP + 16, lbl,
active and COL.white or COL.dim, 10, active)
end
end
-- Reloj + fecha: franja superior comun a todas las pestañas (formato europeo)
local function draw_clock(cr)
local hora = str("${time %H:%M:%S}")
local fecha = str("${time %d/%m/%Y}")
local dia = str("${time %A}")
text(cr, 12, HDR_TOP + 22, hora, COL.snow, 22, true)
rtext(cr, W - 10, HDR_TOP + 13, dia, COL.cyan, 10)
rtext(cr, W - 10, HDR_TOP + 26, fecha, COL.dim, 11)
end
-- Botones de la pestaña Red --------------------------------------------------
local function draw_buttons(cr)
local bw = (W - 24) / 3
@@ -290,7 +316,7 @@ end
-- Panel: Red -----------------------------------------------------------------
local function draw_red(cr)
local y = 46
local y = CONTENT_TOP
text(cr, 12, y + 10, "Interfaz " .. NIF, COL.dim, 11); y = y + 20
text(cr, 12, y + 10, "Down", COL.down, 12)
@@ -344,7 +370,7 @@ end
local function draw_sys(cr)
local x = 10
local gw = W - 20
local y = 38
local y = CONTENT_TOP
local function row(label, value, lc, gh)
text(cr, x, y + 11, label, lc, 11, true)
@@ -421,25 +447,118 @@ end
-- Panel: Docker --------------------------------------------------------------
local function draw_docker(cr)
local y = 50
local running = str("${execi 3 docker ps -q 2>/dev/null | wc -l}")
local total = str("${execi 10 docker ps -aq 2>/dev/null | wc -l}")
text(cr, 12, y, "Contenedores", COL.text, 12, true)
rtext(cr, W - 10, y, running .. " up / " .. total .. " total", COL.dim, 10)
y = y + 22
local y = CONTENT_TOP
local count = str("${execi 3 " .. MET .. " docker_count}") -- "running/total"
text(cr, 12, y + 14, "Contenedores", COL.snow, 12, true)
rtext(cr, W - 10, y + 14, count .. " up", COL.green, 10)
y = y + 28
local names = str("${execi 3 docker ps --format '{{.Names}}' 2>/dev/null | head -28}")
if names == "" then
text(cr, 16, y, "ninguno en marcha", COL.dim, 10)
local list = str("${execi 3 " .. MET .. " docker_list}")
if list == "" then
text(cr, 16, y + 10, "ninguno en marcha", COL.dim, 10)
return
end
for line in names:gmatch("[^\n]+") do
text(cr, 16, y, "" .. line, COL.green, 10)
y = y + 15
for line in list:gmatch("[^\n]+") do
local name, status, image = line:match("^(.-)|(.-)|(.+)$")
if name then
local up = status:match("^Up") ~= nil
local bad = status:match("UNHEALTHY") ~= nil
text(cr, 14, y + 9, "" .. name:sub(1, 16), up and COL.green or COL.dim, 10)
rtext(cr, W - 10, y + 9, status:sub(1, 14),
bad and COL.red or (up and COL.cyan or COL.dim), 9)
y = y + 12
text(cr, 24, y + 9, image:sub(1, 34), COL.dim, 9)
y = y + 14
end
if y > H - 14 then break end
end
end
-- Panel: Procesos ------------------------------------------------------------
local function draw_procesos(cr)
local y = CONTENT_TOP
g_proc_rows = {} -- se rellena con las filas clicables de TOP
local total = str("${execi 2 " .. MET .. " nproc_count}")
local running = str("${execi 2 " .. MET .. " nproc_running}")
local threads = str("${execi 3 " .. MET .. " nthreads}")
local load = str("${execi 2 " .. MET .. " load_avg}")
text(cr, 12, y + 11, "Procesos", COL.snow, 12, true)
rtext(cr, W - 10, y + 11, total .. " (" .. running .. " run)", COL.green, 11); y = y + 17
text(cr, 12, y + 11, "Hilos", COL.dim, 10)
rtext(cr, W - 10, y + 11, threads, COL.text, 10); y = y + 14
text(cr, 12, y + 11, "Carga 1/5/15m", COL.dim, 10)
rtext(cr, W - 10, y + 11, load, COL.text, 10); y = y + 18
-- Tabla "<pid> <valor> <nombre>" por linea. El PID se pinta como enlace
-- (azul) y la fila completa queda registrada en g_proc_rows para abrir
-- PROC_CLICK al clicar.
local function toplist(titulo, key, unidad, col)
text(cr, 12, y + 10, titulo, COL.snow, 10, true)
rtext(cr, W - 10, y + 10, unidad, COL.dim, 9); y = y + 14
local out = str("${execi 3 " .. MET .. " " .. key .. "}")
if out == "" then
text(cr, 16, y + 9, "sin datos", COL.dim, 9); y = y + 13
else
for line in out:gmatch("[^\n]+") do
local pid, val, name = line:match("^%s*(%d+)%s+([%d%.]+)%s+(.+)$")
if pid and val and name then
local ry = y
text(cr, 16, y + 9, pid, COL.blue, 10) -- PID = enlace
text(cr, 72, y + 9, name:sub(1, 13), COL.text, 10)
rtext(cr, W - 10, y + 9, val, col, 10)
g_proc_rows[#g_proc_rows + 1] = { y0 = ry, y1 = ry + 13, pid = pid }
y = y + 13
end
if y > H - 12 then break end
end
end
y = y + 4
end
toplist("TOP CPU", "top_cpu", "%cpu", COL.teal)
toplist("TOP RAM", "top_ram", "%mem", COL.green)
toplist("TOP I/O", "top_io", "KB/s", COL.orange)
end
-- Panel: Devices -------------------------------------------------------------
local function draw_devices(cr)
local y = CONTENT_TOP
text(cr, 12, y + 10, "ALMACENAMIENTO", COL.snow, 10, true); y = y + 15
local disks = str("${execi 10 " .. MET .. " disk_list}")
for line in disks:gmatch("[^\n]+") do
local name, size, model = line:match("^(%S+)%s+(%S+)%s*(.*)$")
if name then
text(cr, 16, y + 9, name .. " " .. (size or ""), COL.cyan, 10)
if model and model ~= "" then rtext(cr, W - 10, y + 9, model:sub(1, 16), COL.dim, 9) end
y = y + 13
end
end
y = y + 6
text(cr, 12, y + 10, "RED", COL.snow, 10, true); y = y + 15
local nets = str("${execi 5 " .. MET .. " net_ifaces}")
for line in nets:gmatch("[^\n]+") do
local ifc, addr = line:match("^(%S+)%s+(%S+)$")
if ifc then
text(cr, 16, y + 9, ifc, COL.green, 10)
rtext(cr, W - 10, y + 9, addr or "", COL.text, 9); y = y + 13
end
end
y = y + 6
text(cr, 12, y + 10, "USB", COL.snow, 10, true); y = y + 15
local usb = str("${execi 10 " .. MET .. " usb_list}")
if usb == "" then
text(cr, 16, y + 9, "sin dispositivos USB", COL.dim, 9); y = y + 13
else
for line in usb:gmatch("[^\n]+") do
text(cr, 16, y + 9, "" .. line:sub(1, 40), COL.text, 9); y = y + 12
if y > H - 12 then break end
end
end
end
-- Hook de dibujo principal ---------------------------------------------------
function conky_draw()
if conky_window == nil then return end
@@ -462,13 +581,13 @@ function conky_draw()
setcol(cr, COL.bg, 0.86); rrect(cr, 0, 0, W, H, 10); cairo_fill(cr)
draw_clock(cr)
draw_tabs(cr)
if current_tab == 1 then
draw_sys(cr)
elseif current_tab == 2 then
draw_red(cr)
else
draw_docker(cr)
if current_tab == 1 then draw_sys(cr)
elseif current_tab == 2 then draw_red(cr)
elseif current_tab == 3 then draw_docker(cr)
elseif current_tab == 4 then draw_procesos(cr)
else draw_devices(cr)
end
cairo_destroy(cr)
@@ -496,8 +615,8 @@ function conky_mouse(event)
-- Barra de pestañas
if y >= TAB_TOP and y <= TAB_TOP + TAB_H then
local idx = math.floor(x / (W / 3)) + 1
if idx >= 1 and idx <= 3 then current_tab = idx end
local idx = math.floor(x / (W / NTABS)) + 1
if idx >= 1 and idx <= NTABS then current_tab = idx end
return
end
@@ -512,4 +631,14 @@ function conky_mouse(event)
end
end
end
-- Pestaña Procesos: clic en una fila TOP abre el proceso (PROC_CLICK con su PID)
if current_tab == 4 then
for _, r in ipairs(g_proc_rows) do
if y >= r.y0 and y <= r.y1 then
os.execute(string.format(PROC_CLICK .. " >/dev/null 2>&1 &", r.pid))
return
end
end
end
end
+37
View File
@@ -12,5 +12,42 @@ case "$1" in
cpu_temp) for h in /sys/class/hwmon/hwmon*; do
[ "$(cat "$h/name" 2>/dev/null)" = coretemp ] && { cat "$h/temp1_input"; break; }
done | awk '{printf "%d", $1/1000}' ;;
# --- Pestaña Procesos ---
nproc_count) ps -e --no-headers 2>/dev/null | wc -l ;;
nproc_running) awk '/^procs_running/{print $2}' /proc/stat 2>/dev/null ;;
nthreads) ps -eLf --no-headers 2>/dev/null | wc -l ;;
load_avg) awk '{printf "%s %s %s", $1, $2, $3}' /proc/loadavg 2>/dev/null ;;
# "pid %valor nombre" por linea, mayor primero
top_cpu) ps -eo pid=,pcpu=,comm= --sort=-pcpu 2>/dev/null | head -n 6 ;;
top_ram) ps -eo pid=,pmem=,comm= --sort=-pmem 2>/dev/null | head -n 6 ;;
# I/O por proceso: requiere pidstat (paquete sysstat); si falta, vacio.
# Las columnas kB_rd/s y kB_wr/s se cuentan desde el final (Command = ultimo
# campo, iodelay = NF-1, kB_ccwr/s = NF-2, kB_wr/s = NF-3, kB_rd/s = NF-4),
# robusto frente a que la primera columna sea epoch o UID.
top_io) command -v pidstat >/dev/null 2>&1 \
&& pidstat -d 1 1 2>/dev/null \
| awk 'NF>=6 && $NF!="Command" && $0 !~ /Average|Linux|^[[:space:]]*$/ \
{ io=$(NF-4)+$(NF-3); if (io>0) printf "%s %.0f %s\n", $(NF-5), io, $NF }' \
| sort -k2 -rn | head -n 5 \
|| echo "" ;;
# --- Pestaña Devices ---
# -e 7,11 excluye loops (snaps) y cdrom; solo discos reales
disk_list) lsblk -dno NAME,SIZE,MODEL -e 7,11 2>/dev/null | head -n 8 ;;
usb_list) lsusb 2>/dev/null \
| sed -E 's/^Bus [0-9]+ Device [0-9]+: ID [0-9a-fA-F]+:[0-9a-fA-F]+ //' \
| grep -viE 'root hub' | head -n 10 ;;
# Interfaces fisicas/reales (excluye docker, bridges, veth, virbr)
net_ifaces) ip -br -4 addr 2>/dev/null \
| awk '$1 !~ /^(docker|br-|veth|virbr)/ && ($2=="UP" || $1=="lo") {printf "%s %s\n", $1, $3}' \
| head -n 6 ;;
# --- Pestaña Docker ---
# "nombre|estado|imagen" por contenedor. Estado y imagen abreviados.
docker_list) docker ps --format '{{.Names}}|{{.Status}}|{{.Image}}' 2>/dev/null \
| sed -E 's/Up ([0-9]+) seconds?/Up \1s/; s/Up ([0-9]+) minutes?/Up \1m/; s/Up ([0-9]+) hours?/Up \1h/; s/Up ([0-9]+) days?/Up \1d/; s/Up ([0-9]+) weeks?/Up \1w/; s/Up ([0-9]+) months?/Up \1mo/; s/Up Less than a second/Up 0s/; s/ \(healthy\)//; s/ \(unhealthy\)/ BAD/' \
| head -n 14 ;;
docker_count) printf "%s/%s" "$(docker ps -q 2>/dev/null | wc -l)" "$(docker ps -aq 2>/dev/null | wc -l)" ;;
*) echo 0 ;;
esac