--[[ conky_widget — render Cairo + pestañas clickeables. Estructura: - Estado: current_tab (1=Sistema, 2=Red, 3=Docker; Sistema por defecto). - conky_draw (lua_draw_hook_post): mantiene los historicos y dibuja la barra de pestañas y el panel activo. - conky_mouse (lua_mouse_hook): cambia de pestaña y lanza apps al clicar botones. Los datos del sistema se obtienen llamando a conky_parse("${...}") desde Lua, de modo que se pueden colocar con libertad mediante Cairo. La pestaña Sistema reproduce el panel del widget previo: graficas de area (CPU, RAM, CPU temp, GPU, GPU temp, VRAM, disk I/O) que se vuelven rojas al superar su umbral, mas las barras de uso de los cuatro discos. Los valores de temperatura/GPU se obtienen de metric.sh (nvidia-smi + coretemp hwmon). Geometria fija (la ventana mide W x H segun conky.conf). ]] require 'cairo' 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 = 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 = 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" -- Helper de metricas (temps + GPU), portado del widget previo ----------------- local MET = os.getenv("HOME") .. "/.config/conky/conky_widget/metric.sh" -- Pestañas (Sistema por defecto) --------------------------------------------- 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 = { { label = "Wireshark", bin = "wireshark", pkg = "wireshark", cmd = "f=$(ls -t /var/log/pktcap/*.pcapng 2>/dev/null | head -1); " .. "if [ -n \"$f\" ]; then wireshark -r \"$f\"; else wireshark; fi" }, { label = "ntopng", bin = "ntopng", pkg = "ntopng", cmd = "xdg-open http://localhost:3000" }, { label = "nethogs", bin = "nethogs", pkg = "nethogs", cmd = "x-terminal-emulator -e bash -c 'sudo nethogs " .. NIF .. "; read -p \"Enter para cerrar...\" _'" }, } local LAUNCH = os.getenv("HOME") .. "/.config/conky/conky_widget/lua/launch.sh" -- Historicos para los graficos en vivo --------------------------------------- local GH = 58 local KEYS = { "cpu", "ram", "cputemp", "gputil", "gputemp", "vram", "diskio", "down", "up" } local hist = {} for _, k in ipairs(KEYS) do hist[k] = {} for i = 1, GH do hist[k][i] = 0 end end local function push(t, v) table.remove(t, 1) t[#t + 1] = v end -- Paleta (Nord, como el widget previo) --------------------------------------- local function hex(s) return { tonumber(s:sub(1, 2), 16) / 255, tonumber(s:sub(3, 4), 16) / 255, tonumber(s:sub(5, 6), 16) / 255, } end local COL = { bg = hex("0e0f12"), panel = hex("1b1d23"), tab_active = hex("5e81ac"), tab_inactive = hex("2e3440"), text = hex("d8dee9"), snow = hex("eceff4"), white = { 1, 1, 1 }, dim = hex("7b8394"), teal = hex("8fbcbb"), green = hex("a3be8c"), cyan = hex("88c0d0"), blue = hex("81a1c1"), frost = hex("5e81ac"), yellow = hex("ebcb8b"), orange = hex("d08770"), purple = hex("b48ead"), red = hex("bf616a"), down = hex("88c0d0"), up = hex("d08770"), } -- Helpers de lectura de datos ------------------------------------------------ local function str(expr) local s = conky_parse(expr) if s == nil then return "" end return (s:gsub("^%s+", ""):gsub("%s+$", "")) end local function num(expr) return tonumber(str(expr)) or 0 end -- Longitud visible en caracteres (code points UTF-8), no bytes. Necesario para -- alinear texto que contiene flechas (↓ ↑) en fuente monoespaciada. local function vlen(s) local _, n = s:gsub("[^\128-\191]", "") return n end -- Convierte un texto de tasa de conky ("1.2MiB", "300KiB", "5B") a KiB/s ------ local function rate_kib(s) local n, unit = s:match("([%d%.]+)%s*([KMGTPi]*)B?") n = tonumber(n) or 0 unit = unit or "" if unit:find("M") then return n * 1024 elseif unit:find("G") then return n * 1024 * 1024 elseif unit:find("K") then return n else return n / 1024 end end -- Paquetes por intervalo y protocolo: delta de los contadores de /proc/net/snmp. -- g_proto guarda los paquetes del ultimo intervalo (≈ por segundo con -- update_interval=1). Se actualiza una vez por frame desde conky_draw. local g_proto = { tcp_in = 0, tcp_out = 0, udp_in = 0, udp_out = 0, icmp_in = 0, icmp_out = 0 } local snmp_prev = nil local function update_proto() local f = io.open("/proc/net/snmp", "r") if not f then return end local hdr, cur = {}, {} for line in f:lines() do local proto, rest = line:match("^(%w+):%s+(.+)$") if proto then if hdr[proto] == nil then local cols, i = {}, 0 for tok in rest:gmatch("%S+") do i = i + 1; cols[tok] = i end hdr[proto] = cols else local vals = {} for tok in rest:gmatch("%S+") do vals[#vals + 1] = tok end cur[proto] = vals end end end f:close() local function get(proto, col) local cols = hdr[proto]; local vals = cur[proto] if not cols or not vals then return 0 end local idx = cols[col]; if not idx then return 0 end return tonumber(vals[idx]) or 0 end local c = { tcp_in = get("Tcp", "InSegs"), tcp_out = get("Tcp", "OutSegs"), udp_in = get("Udp", "InDatagrams"), udp_out = get("Udp", "OutDatagrams"), icmp_in = get("Icmp", "InMsgs"), icmp_out = get("Icmp", "OutMsgs"), } if snmp_prev then for k, v in pairs(c) do local d = v - (snmp_prev[k] or v) g_proto[k] = (d < 0) and 0 or d end end snmp_prev = c end -- Top hosts remotos por numero de conexiones TCP/UDP establecidas (ss) -------- local function top_remotes() local out = str("${execi 3 ss -tun state established 2>/dev/null}") local counts = {} for line in out:gmatch("[^\n]+") do if not line:match("^Netid") and not line:match("^State") then local ip = line:match("([%d%.]+):%d+%s*$") or line:match("%[([%x:]+)%]:%d+%s*$") -- Excluir loopback: no son hosts "remotos" if ip and not ip:match("^127%.") and ip ~= "::1" then counts[ip] = (counts[ip] or 0) + 1 end end end local arr = {} for ip, c in pairs(counts) do arr[#arr + 1] = { ip = ip, c = c } end table.sort(arr, function(a, b) return a.c > b.c end) return arr end -- Helpers de dibujo ---------------------------------------------------------- local function setcol(cr, c, a) cairo_set_source_rgba(cr, c[1], c[2], c[3], a or 1.0) end local function rrect(cr, x, y, w, h, r) cairo_new_sub_path(cr) cairo_arc(cr, x + w - r, y + r, r, -math.pi / 2, 0) cairo_arc(cr, x + w - r, y + h - r, r, 0, math.pi / 2) cairo_arc(cr, x + r, y + h - r, r, math.pi / 2, math.pi) cairo_arc(cr, x + r, y + r, r, math.pi, 1.5 * math.pi) cairo_close_path(cr) end local function text(cr, x, y, s, c, size, bold) cairo_select_font_face(cr, "DejaVu Sans Mono", CAIRO_FONT_SLANT_NORMAL, bold and CAIRO_FONT_WEIGHT_BOLD or CAIRO_FONT_WEIGHT_NORMAL) cairo_set_font_size(cr, size or 12) setcol(cr, c or COL.text) cairo_move_to(cr, x, y) cairo_show_text(cr, s) end -- Texto alineado a la derecha (avance mono ~0.6*tamaño) ---------------------- local function rtext(cr, xr, y, s, c, size, bold) text(cr, xr - #s * (size or 12) * 0.6, y, s, c, size, bold) end local function bar(cr, x, y, w, h, frac, c) if frac < 0 then frac = 0 elseif frac > 1 then frac = 1 end setcol(cr, COL.tab_inactive); rrect(cr, x, y, w, h, 3); cairo_fill(cr) setcol(cr, c); rrect(cr, x, y, math.max(2, w * frac), h, 3); cairo_fill(cr) end -- Grafico sobre un panel redondeado. Cada serie con s.fill=true se dibuja como -- area (relleno translucido) ademas de la linea; si no, solo linea. local function graph(cr, x, y, w, h, series) setcol(cr, COL.panel); rrect(cr, x, y, w, h, 4); cairo_fill(cr) -- Maximo comun a todas las series (fijo si se indica) local maxv = 1 for _, s in ipairs(series) do if s.max then if s.max > maxv then maxv = s.max end else for i = 1, #s.data do if s.data[i] > maxv then maxv = s.data[i] end end end end for _, s in ipairs(series) do local n = #s.data local function px(i) return x + (i - 1) / (n - 1) * w end local function py(i) return y + h - (s.data[i] / maxv) * (h - 4) - 2 end -- Color de alerta cuando el valor actual supera el umbral local col = s.c if s.warn and s.data[n] >= s.warn then col = COL.red end if s.fill then cairo_move_to(cr, px(1), y + h) for i = 1, n do cairo_line_to(cr, px(i), py(i)) end cairo_line_to(cr, px(n), y + h) cairo_close_path(cr) setcol(cr, col, 0.25); cairo_fill(cr) end -- 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 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 / 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 + 2, TAB_TOP, tw - 4, TAB_H, 4); cairo_fill(cr) local lbl = TABS[i] 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 for i = 1, 3 do local bx = 8 + (i - 1) * (bw + 4) setcol(cr, COL.tab_inactive); rrect(cr, bx, BTN_Y, bw, BTN_H, 6); cairo_fill(cr) setcol(cr, COL.tab_active, 0.9); cairo_set_line_width(cr, 1) rrect(cr, bx, BTN_Y, bw, BTN_H, 6); cairo_stroke(cr) local lbl = BTNS[i].label text(cr, bx + bw / 2 - #lbl * 10 * 0.6 / 2, BTN_Y + 17, lbl, COL.text, 10) end end -- Panel: Red ----------------------------------------------------------------- local function draw_red(cr) 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) text(cr, 90, y + 10, str("${downspeed " .. NIF .. "}"), COL.text, 13, true); y = y + 18 text(cr, 12, y + 10, "Up", COL.up, 12) text(cr, 90, y + 10, str("${upspeed " .. NIF .. "}"), COL.text, 13, true); y = y + 20 graph(cr, 12, y, W - 24, 56, { { data = hist.down, c = COL.down }, { data = hist.up, c = COL.up }, }) y = y + 62 -- Paquetes por segundo y protocolo (delta de /proc/net/snmp) text(cr, 12, y + 10, "PAQUETES/s", COL.snow, 10, true) rtext(cr, W - 10, y + 10, "in / out", COL.dim, 9); y = y + 15 local function prow(label, c, ipkt, opkt) text(cr, 14, y + 10, label, c, 10) rtext(cr, W - 10, y + 10, ipkt .. " / " .. opkt, COL.text, 10); y = y + 13 end prow("TCP", COL.cyan, g_proto.tcp_in, g_proto.tcp_out) prow("UDP", COL.green, g_proto.udp_in, g_proto.udp_out) prow("ICMP", COL.yellow, g_proto.icmp_in, g_proto.icmp_out) y = y + 4 -- Top hosts remotos por numero de conexiones establecidas text(cr, 12, y + 10, "TOP REMOTOS", COL.snow, 10, true) rtext(cr, W - 10, y + 10, "conex.", COL.dim, 9); y = y + 15 local arr = top_remotes() if #arr == 0 then text(cr, 14, y + 10, "sin conexiones establecidas", COL.dim, 9); y = y + 13 else for i = 1, math.min(4, #arr) do text(cr, 14, y + 10, arr[i].ip, COL.text, 10) rtext(cr, W - 10, y + 10, tostring(arr[i].c), COL.green, 10); y = y + 13 end end y = y + 4 local conns = str("${execi 2 ss -tun state established 2>/dev/null | tail -n +2 | wc -l}") text(cr, 12, y + 10, "Conexiones activas: " .. conns, COL.text, 10); y = y + 13 text(cr, 12, y + 9, "Total ↓ " .. str("${totaldown " .. NIF .. "}") .. " ↑ " .. str("${totalup " .. NIF .. "}"), COL.dim, 9) draw_buttons(cr) text(cr, 12, BTN_Y + BTN_H + 18, "Wireshark abre el buffer de captura mas reciente.", COL.dim, 9) end -- Panel: Sistema (portado del widget previo) --------------------------------- local function draw_sys(cr) local x = 10 local gw = W - 20 local y = CONTENT_TOP local function row(label, value, lc, gh) text(cr, x, y + 11, label, lc, 11, true) if value and value ~= "" then rtext(cr, W - 10, y + 11, value, COL.snow, 10) end y = y + 15 return gh end local cputemp = str("${execi 3 " .. MET .. " cpu_temp}") local gputemp = str("${execi 2 " .. MET .. " gpu_temp}") -- CPU local gh = row("CPU " .. math.floor(num("${cpu cpu0}")) .. "%", str("${freq_g}") .. "GHz " .. cputemp .. "°C", COL.teal, 26) graph(cr, x, y, gw, gh, { { data = hist.cpu, c = COL.green, max = 100, fill = true, warn = 85 } }); y = y + gh + 4 -- RAM gh = row("RAM " .. math.floor(num("${memperc}")) .. "%", str("${mem}") .. " / " .. str("${memmax}"), COL.green, 26) graph(cr, x, y, gw, gh, { { data = hist.ram, c = COL.green, max = 100, fill = true, warn = 85 } }); y = y + gh + 4 -- CPU TEMP gh = row("CPU TEMP " .. cputemp .. "°C", "", COL.yellow, 20) graph(cr, x, y, gw, gh, { { data = hist.cputemp, c = COL.yellow, max = 100, fill = true, warn = 80 } }); y = y + gh + 4 -- GPU gh = row("GPU " .. str("${execi 2 " .. MET .. " gpu_util}") .. "%", gputemp .. "°C", COL.cyan, 26) graph(cr, x, y, gw, gh, { { data = hist.gputil, c = COL.cyan, max = 100, fill = true, warn = 90 } }); y = y + gh + 4 -- GPU TEMP gh = row("GPU TEMP " .. gputemp .. "°C", "", COL.yellow, 20) graph(cr, x, y, gw, gh, { { data = hist.gputemp, c = COL.yellow, max = 100, fill = true, warn = 80 } }); y = y + gh + 4 -- VRAM gh = row("VRAM " .. str("${execi 2 " .. MET .. " gpu_memp}") .. "%", str("${execi 2 " .. MET .. " gpu_memi}"), COL.purple, 26) graph(cr, x, y, gw, gh, { { data = hist.vram, c = COL.purple, max = 100, fill = true, warn = 92 } }); y = y + gh + 4 -- DISK I/O gh = row("DISK I/O", str("${diskio}"), COL.orange, 26) graph(cr, x, y, gw, gh, { { data = hist.diskio, c = COL.purple, fill = true } }); y = y + gh + 6 -- NET (red: download + upload) como área. Cabecera con las flechas -- coloreadas igual que su línea (↓ cian = down, ↑ naranja = up) para -- distinguir las dos series de un vistazo. text(cr, x, y + 11, "NET", COL.blue, 11, true) local dn_s = "↓ " .. str("${downspeed " .. NIF .. "}") local up_s = "↑ " .. str("${upspeed " .. NIF .. "}") local up_x = W - 10 - vlen(up_s) * 10 * 0.6 local dn_x = up_x - 10 - vlen(dn_s) * 10 * 0.6 text(cr, dn_x, y + 11, dn_s, COL.down, 10) text(cr, up_x, y + 11, up_s, COL.up, 10) y = y + 15 gh = 26 graph(cr, x, y, gw, gh, { { data = hist.down, c = COL.down, fill = true }, { data = hist.up, c = COL.up, fill = true }, }); y = y + gh + 6 -- Uso de discos text(cr, x, y + 10, "USO DE DISCOS", COL.snow, 10, true); y = y + 16 local disks = { "/", "/mnt/1tb", "/mnt/2tb", "/mnt/16tb" } for _, m in ipairs(disks) do local p = num("${fs_used_perc " .. m .. "}") text(cr, x, y + 9, m, COL.green, 10) rtext(cr, W - 10, y + 9, str("${fs_used " .. m .. "}") .. "/" .. str("${fs_size " .. m .. "}") .. " " .. math.floor(p) .. "%", COL.dim, 9) y = y + 12 bar(cr, x, y, gw, 6, p / 100, p > 90 and COL.red or COL.green); y = y + 11 end end -- Panel: Docker -------------------------------------------------------------- local function draw_docker(cr) 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 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 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 " " 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 local cs = cairo_xlib_surface_create(conky_window.display, conky_window.drawable, conky_window.visual, conky_window.width, conky_window.height) local cr = cairo_create(cs) -- Mantener todos los historicos vivos en cualquier pestaña. push(hist.cpu, num("${cpu cpu0}")) push(hist.ram, num("${memperc}")) push(hist.cputemp, num("${execi 3 " .. MET .. " cpu_temp}")) push(hist.gputil, num("${execi 2 " .. MET .. " gpu_util}")) push(hist.gputemp, num("${execi 2 " .. MET .. " gpu_temp}")) push(hist.vram, num("${execi 2 " .. MET .. " gpu_memp}")) push(hist.diskio, rate_kib(str("${diskio}"))) push(hist.down, num("${downspeedf " .. NIF .. "}")) push(hist.up, num("${upspeedf " .. NIF .. "}")) update_proto() 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) elseif current_tab == 3 then draw_docker(cr) elseif current_tab == 4 then draw_procesos(cr) else draw_devices(cr) end cairo_destroy(cr) cairo_surface_destroy(cs) end -- Lanza una app con fallback a notify-send si falta el binario --------------- local function shell_quote(s) return "'" .. s:gsub("'", "'\\''") .. "'" end local function launch(b) os.execute(string.format("%s %s %s %s &", shell_quote(LAUNCH), shell_quote(b.bin), shell_quote(b.pkg), shell_quote(b.cmd))) end -- Hook de raton: cambia de pestaña y lanza apps ------------------------------ function conky_mouse(event) if type(event) ~= "table" then return end if event.type ~= "button_down" then return end if event.button ~= nil and event.button ~= 1 then return end local x = event.x or 0 local y = event.y or 0 -- Barra de pestañas if y >= TAB_TOP and y <= TAB_TOP + TAB_H then local idx = math.floor(x / (W / NTABS)) + 1 if idx >= 1 and idx <= NTABS then current_tab = idx end return end -- Botones de la pestaña Red (ahora es la pestaña 2) if current_tab == 2 and y >= BTN_Y and y <= BTN_Y + BTN_H then local bw = (W - 24) / 3 for i = 1, 3 do local bx = 8 + (i - 1) * (bw + 4) if x >= bx and x <= bx + bw then launch(BTNS[i]) return 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