From 2b10a25ee03d345d17cf9eae16405c168a75abf8 Mon Sep 17 00:00:00 2001 From: weinguyen Date: Sun, 9 Aug 2026 02:40:32 +0700 Subject: [PATCH] Add procmon plugin with live process panel (#301) * Add procmon plugin with live process panel New Noctalia plugin: bar widget shows CPU/RAM usage and process count; floating panel has a sortable, searchable process table with per-process kill. Background service samples ps and /proc stats and publishes via plugin state. * Throttle data-driven panel renders Rebuild table at most every 500ms to stay within the panel update CPU budget. User interactions still render immediately; only state watches and the tick go through maybeRender(). * Update procmon deps and fix timestamp precision Document head, grep, and cat as required commands. Correct refresh interval default to 1000 ms. Sample refreshedAtMs in milliseconds. --- procmon/README.md | 60 +++++ procmon/panel.luau | 460 +++++++++++++++++++++++++++++++++++ procmon/plugin.toml | 77 ++++++ procmon/service.luau | 251 +++++++++++++++++++ procmon/thumbnail.webp | Bin 0 -> 67326 bytes procmon/translations/en.json | 59 +++++ procmon/widget.luau | 56 +++++ 7 files changed, 963 insertions(+) create mode 100644 procmon/README.md create mode 100644 procmon/panel.luau create mode 100644 procmon/plugin.toml create mode 100644 procmon/service.luau create mode 100644 procmon/thumbnail.webp create mode 100644 procmon/translations/en.json create mode 100644 procmon/widget.luau diff --git a/procmon/README.md b/procmon/README.md new file mode 100644 index 0000000..f6b8340 --- /dev/null +++ b/procmon/README.md @@ -0,0 +1,60 @@ +# Process Monitor + +A bottom-style process monitor for Noctalia: live CPU/RAM/swap bars and a +sortable, searchable process table with a per-process kill action, all in a +panel. Great for spotting a runaway process or a zombie and killing it without +opening a terminal. + +## Plugin + +| Field | Value | +| ------- | -------------------------------------------------------- | +| ID | `weinguyen/procmon` | +| Entries | Bar widget: `widget`; panel: `panel`; service: `service` | + +## Requirements + +Install `ps`, `kill`, `head`, `grep` and `cat` on `PATH` (all are present on +virtually every Linux distribution). + +## Usage + +Add the **Process Monitor** widget to a bar. It shows the current CPU and RAM +usage (plus the process count). Click the widget to toggle the process panel: + +```sh +noctalia msg panel-toggle weinguyen/procmon:panel +``` + +The panel shows CPU, RAM and swap bars with the 1/5/15-minute load averages, +then a process table. Sort by the column dropdown (PID, CPU%, MEM%, RSS, +COMMAND) and flip asc/desc with the arrow button. Type in the filter box to +match a process by name, user or PID. Click the ✕ button on a row to run the +configured kill command against that PID (default `kill -TERM`). Zombie +processes are tinted with the error color so they stand out. + +The table refreshes on the interval set in the `refresh_interval` setting. The +panel re-renders automatically as new samples arrive, so the view stays live +while it is open. + +## Settings + +| Setting | Type | Default | Description | +| ------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------ | +| `refresh_interval` | `int` | `1000` | Process resample interval in milliseconds. (default 1000) | +| `sort_by` | `select` | `cpu` | Initial sort column when the panel opens (`cpu`, `mem`, `pid`, `cmd`). | +| `kill_command` | `string` | `kill -TERM` | Command run against a selected PID; the PID is appended. Empty falls back to `kill -TERM`. | +| `show_count` | `bool` | `true` | Bar widget also shows the total process count. | + +## Notes + +- **Spawns processes.** A background service runs `ps` on every refresh + interval and `runAsync` runs the configured `kill_command` when a row's ✕ is + clicked. There is no confirmation dialog, so check the PID before clicking. +- The bar widget only renders data the service publishes; it never runs + commands. The panel runs the configured `kill_command` when a row's ✕ is + clicked. +- Requires `plugin_api = 12`. CPU%, RAM%, swap and the 1/5/15-minute load + averages are sampled from `/proc` (`/proc/stat`, `/proc/meminfo`, + `/proc/loadavg`) by the service, so they work with no separate system-monitor + dependency. diff --git a/procmon/panel.luau b/procmon/panel.luau new file mode 100644 index 0000000..daf0e81 --- /dev/null +++ b/procmon/panel.luau @@ -0,0 +1,460 @@ +--!nonstrict +-- Process Monitor — panel. Renders the process table + live system bars. +-- Sort/filter state lives here (a view concern); data comes from the service. + +local sortKey = "cpu" +local sortDir = "desc" +local filter = "" + +-- Per-column display unit: "pct" shows percent, "raw" shows the absolute +-- figure. Clicking the %CPU/%MEM header toggles it. MEM raw = RSS in MB; CPU +-- raw = cumulative CPU time (ps time=), a real number distinct from the %. +-- (ponytail: a per-interval CPU% needs two /proc/pid/stat samples and a diff, +-- too heavy for the update budget - the cumulative time is a free proxy.) +local cpuUnit = "pct" +local memUnit = "pct" + +-- Cap how many rows we render so a single rebuild stays under the panel +-- update CPU budget. Rows are shown in sorted order, so the highest-sorting +-- processes are always visible. (200 rows * ~7 ui nodes blew the budget.) +local MAX_ROWS = 80 + +-- Cheap fingerprint of everything the table shows, so the 1s tick only rebuilds +-- the heavy UI tree when the data actually changed. Rebuilding 200 rows every +-- second exceeded the panel update CPU budget; sampling the first SIG_SAMPLE +-- rows (ps output is pid-ordered) catches CPU/mem/rss movement without the cost. +local SIG_SAMPLE = 40 +local lastSig = nil + +-- Throttle data-driven renders. The service can sample faster than the panel can +-- rebuild its table, and every state.set fires a watch: at 250ms that was ~16 +-- full rebuilds/sec of 80 rows, which blew the panel update CPU budget and got +-- the panel disabled. User interactions (toggle/filter/sort) still render() +-- immediately; only the data watches and the tick go through maybeRender(). +local lastRenderMs = 0 +local MIN_RENDER_MS = 500 + +-- Kill column is a fixed width at the end of every row; all other columns are +-- flexGrow shares so the header and data rows always align and never overflow. +local KILL_W = 30 + +local function cfg(key) + return noctalia.getConfig(key) +end + +local function st(key) + return noctalia.state.get(key) +end + +-- Draft value while the Refresh slider is being dragged; committed to state on +-- release (audio-switcher pattern). The service watches "refreshMs" and re-arms +-- its sampler timer, which is what actually changes the fetch speed. Declared +-- after cfg/st (Luau: forward refs to a later local compile as globals -> nil). +local refreshDraft = nil +local function refreshMs() + return tonumber(st("refreshMs") or cfg("refresh_interval")) or 1000 +end +local function commitRefresh() + if refreshDraft then + noctalia.state.set("refreshMs", refreshDraft) + refreshDraft = nil + end +end + +local function signature() + local stats = st("stats") or {} + local cpuPct = stats.cpu and stats.cpu.usagePercent or 0 + local ramPct = stats.ram and stats.ram.usagePercent or 0 + local procs = st("procs") or {} + local n = #procs + local s = tostring(n) .. "|" .. filter .. "|" .. sortKey .. sortDir + .. "|" .. tostring(cpuPct) .. "|" .. tostring(ramPct) + .. "|" .. cpuUnit .. memUnit + local upto = n < SIG_SAMPLE and n or SIG_SAMPLE + for i = 1, upto do + local p = procs[i] + if p then + s = s .. "|" .. tostring(p.pid) .. ":" .. tostring(math.floor((p.cpu or 0) * 10)) + .. ":" .. tostring(math.floor((p.mem or 0) * 10)) + .. ":" .. tostring(math.floor((p.rssMb or 0) / 16)) + end + end + return s +end + +local function tr(key, subst) + return noctalia.tr(key, subst) +end + +local function fmtMem(mb) + if mb == nil then + return "—" + end + if mb >= 1024 then + return string.format("%.1fG", mb / 1024) + end + return string.format("%.0fM", mb) +end + +local function trunc(s, n) + if #s <= n then + return s + end + return s:sub(1, n - 1) .. "…" +end + +local function killPid(pid) + local cmd = cfg("kill_command") + if cmd == "" then + cmd = "kill -TERM" + end + noctalia.runAsync(cmd .. " " .. pid) +end + +-- ── stats bars ────────────────────────────────────────────────────────────── + +local function statBar(label, glyph, pct, sub) + local pct0 = math.max(0, math.min(100, pct or 0)) + return ui.row({ gap = 8, align = "center", flexGrow = 1 }, { + ui.glyph({ name = glyph, size = 16 }), + ui.label({ text = label, fontSize = 11, color = "on_surface_variant" }), + ui.progress({ progress = pct0 / 100, flexGrow = 1, height = 8 }), + ui.label({ text = sub, fontSize = 11, textAlign = "end" }), + }) +end + +local function renderStats() + local stats = st("stats") or {} + local cpu = stats.cpu + local ram = stats.ram + local swap = stats.swap + + local swapPct = 0 + local swapSub = "—" + if swap and swap.totalMb and swap.totalMb > 0 then + swapPct = swap.usedMb / swap.totalMb * 100 + swapSub = string.format("%.0f/%.0fM", swap.usedMb, swap.totalMb) + end + + local loadSub = "—" + if stats.loadAvg then + loadSub = string.format("%.2f %.2f %.2f", stats.loadAvg[1], stats.loadAvg[2], stats.loadAvg[3]) + end + + -- The render path must never throw: any stats field may be nil (seed + -- stats, or a sample that skipped a field), so guard every format arg. + local cpuSub = (cpu and cpu.usagePercent ~= nil) and string.format("%.0f%%", cpu.usagePercent) or "—" + local ramSub = "—" + if ram and ram.usedMb ~= nil and ram.totalMb ~= nil then + ramSub = string.format("%.0f/%.0fM", ram.usedMb, ram.totalMb) + end + + return ui.column({ gap = 4, fill = "surface_variant/0.35", radius = 10, paddingV = 8, paddingH = 10 }, { + statBar("CPU", "cpu", cpu and cpu.usagePercent, cpuSub), + statBar("RAM", "memory", ram and ram.usagePercent, ramSub), + ui.row({ gap = 12, align = "center" }, { + statBar("SWAP", "archive", swapPct, swapSub), + ui.label({ text = tr("panel.load") .. ": " .. loadSub, fontSize = 11, color = "on_surface_variant", flexGrow = 1 }), + }), + }) +end + +-- ── process rows ──────────────────────────────────────────────────────────── + +-- Column layout, shared by header and data rows so they line up. +-- { flexGrow, textAlign } for each column; cmd is the wide flexible one. +local COLS = { + { 1.0, "end" }, -- PID + { 1.0, "end" }, -- CPU + { 1.0, "end" }, -- MEM + { 1.2, "end" }, -- RSS + { 0.7, "center" }, -- STAT + { 5.0, "start" }, -- COMMAND +} + +-- ui.select drives sorting. Order must match the settings.sort_by options. +local SORT_KEYS = { "cpu", "mem", "pid", "cmd" } +local function sortIndex(key) + for i, k in ipairs(SORT_KEYS) do + if k == key then + return i - 1 + end + end + return 0 +end + +local function toggleCpuUnit() + cpuUnit = cpuUnit == "pct" and "raw" or "pct" + render() +end + +local function toggleMemUnit() + memUnit = memUnit == "pct" and "raw" or "pct" + render() +end + +local function dataRow(p) + local stat = p.stat or "?" + local statColor = stat:find("Z") and "error" or "on_surface" + local memCell = memUnit == "raw" and fmtMem(p.rssMb) or string.format("%.1f", p.mem) + local cpuCell = cpuUnit == "raw" and (p.cpuTime or "—") or string.format("%.1f", p.cpu) + local cells = { + { tostring(p.pid) }, + { cpuCell }, + { memCell }, + { fmtMem(p.rssMb) }, + { stat, statColor }, + } + + local children = {} + for i, c in ipairs(cells) do + table.insert(children, ui.label({ + text = c[1], + flexGrow = COLS[i][1], + textAlign = COLS[i][2], + fontSize = 11, + maxLines = 1, + color = c[2] or "on_surface", + })) + end + + -- COMMAND column (flexGrow wide) + table.insert(children, ui.label({ + text = trunc(p.cmd or "?", 80), + flexGrow = COLS[6][1], + textAlign = "start", + fontSize = 11, + maxLines = 1, + })) + + -- Kill button (fixed trailing width) + table.insert(children, ui.button({ + glyph = "square-x", + glyphSize = 14, + variant = "ghost", + controlSize = "sm", + width = KILL_W, + tooltip = tr("panel.kill_tip", { pid = p.pid }), + onClick = function() + killPid(p.pid) + end, + })) + + return ui.row({ key = tostring(p.pid), gap = 6, align = "center" }, children) +end + +-- Header: clickable cells use ui.row onClick (keeps layout, so the grid stays +-- aligned with the data rows). CPU/MEM headers toggle their column's display +-- unit (% vs actual). Built fresh on every render so titles track the mode. +local HEADER_LABELS = { "panel.col.pid", "panel.col.cpu", "panel.col.mem", "panel.col.rss", "panel.col.stat", "panel.col.cmd" } +local function buildHeader() + local children = {} + for i, col in ipairs(COLS) do + local label = { + text = tr(HEADER_LABELS[i]), + fontSize = 11, + maxLines = 1, + color = "on_surface_variant", + } + if i == 2 then -- %CPU header toggles percent vs raw + label.text = cpuUnit == "raw" and tr("panel.col.cpu_raw") or tr("panel.col.cpu") + children[#children + 1] = ui.row({ + flexGrow = col[1], + align = "center", + justify = col[2], + onClick = toggleCpuUnit, + }, { ui.label(label) }) + elseif i == 3 then -- %MEM header toggles percent vs MB + label.text = memUnit == "raw" and tr("panel.col.mem_raw") or tr("panel.col.mem") + children[#children + 1] = ui.row({ + flexGrow = col[1], + align = "center", + justify = col[2], + onClick = toggleMemUnit, + }, { ui.label(label) }) + else + label.flexGrow = col[1] + label.textAlign = col[2] + children[#children + 1] = ui.label(label) + end + end + -- trailing spacer matching the kill column + children[#children + 1] = ui.spacer({ width = KILL_W }) + return children +end + +-- ── main render ───────────────────────────────────────────────────────────── + +function render() + local procs = st("procs") or {} + local f = filter:lower() + + local list = {} + for _, p in ipairs(procs) do + if f ~= "" then + local cmd = (p.cmd or ""):lower() + local user = (p.user or ""):lower() + local pid = tostring(p.pid) + if not (cmd:find(f, 1, true) or user:find(f, 1, true) or pid:find(f, 1, true)) then + continue + end + end + table.insert(list, p) + end + + table.sort(list, function(a, b) + local av, bv + if sortKey == "pid" then + av, bv = a.pid, b.pid + elseif sortKey == "mem" then + av, bv = a.mem, b.mem + elseif sortKey == "rss" then + av, bv = a.rssMb, b.rssMb + elseif sortKey == "cmd" then + av, bv = (a.cmd or ""), (b.cmd or "") + else + av, bv = a.cpu, b.cpu + end + if av == bv then + return a.pid < b.pid + end + if sortDir == "asc" then + return av < bv + end + return av > bv + end) + + -- Bound the rendered rows so a 500-process box stays responsive. + local shown = list + if #list > MAX_ROWS then + shown = {} + for i = 1, MAX_ROWS do + shown[i] = list[i] + end + end + + local body + if #shown == 0 then + body = ui.row({ align = "center", justify = "center", flexGrow = 1 }, { + ui.label({ text = tr("panel.no_processes"), color = "on_surface_variant" }), + }) + else + local itemRows = {} + for _, p in ipairs(shown) do + table.insert(itemRows, dataRow(p)) + end + body = ui.scroll({ flexGrow = 1, gap = 2, align = "stretch" }, itemRows) + end + + local refreshedAt = (st("refreshedAtMs") or 0) / 1000 + local refreshedStr = refreshedAt > 0 and os.date("%H:%M:%S", refreshedAt) or "—" + local err = st("err") + + panel.render(ui.column({ padding = 12, gap = 8, flexGrow = 1, align = "stretch" }, { + ui.row({ align = "center", justify = "space_between" }, { + ui.label({ text = tr("panel.title"), fontSize = 15, fontWeight = "bold" }), + ui.label({ text = tr("panel.refreshed_at", { time = refreshedStr }), fontSize = 11, color = "on_surface_variant" }), + }), + renderStats(), + ui.row({ gap = 6, align = "center" }, { + ui.input({ + key = "filter", + value = filter, + placeholder = tr("panel.search_placeholder"), + controlSize = "sm", + flexGrow = 1, + onChange = function(v) + filter = v + render() + end, + }), + ui.select({ + options = { + tr("settings.sort_by.options.cpu"), + tr("settings.sort_by.options.mem"), + tr("settings.sort_by.options.pid"), + tr("settings.sort_by.options.cmd"), + }, + selectedIndex = sortIndex(sortKey), + width = 110, + controlSize = "sm", + onChange = function(idx) + sortKey = SORT_KEYS[idx + 1] + render() + end, + }), + ui.button({ + glyph = sortDir == "asc" and "arrow-up" or "arrow-down", + tooltip = sortDir == "asc" and tr("panel.sort_asc") or tr("panel.sort_desc"), + controlSize = "sm", + variant = "ghost", + onClick = function() + sortDir = sortDir == "asc" and "desc" or "asc" + render() + end, + }), + }), + ui.row({ gap = 6, align = "center" }, buildHeader()), + body, + ui.row({ gap = 10, align = "center" }, { + ui.label({ text = tr("panel.count", { n = #list }), fontSize = 11, color = "on_surface_variant" }), + ui.row({ gap = 8, align = "center", flexGrow = 1 }, { + ui.label({ text = tr("panel.refresh"), fontSize = 11, color = "on_surface_variant" }), + ui.slider({ + min = 500, max = 5000, step = 500, value = math.max(500, math.min(5000, refreshMs())), + flexGrow = 1, controlSize = "sm", + onChange = function(v) refreshDraft = math.floor(tonumber(v) or 0) end, + onDragEnd = commitRefresh, + }), + ui.label({ text = refreshMs() .. "ms", fontSize = 11, textAlign = "end", width = 48 }), + }), + ui.label({ text = err, fontSize = 11, color = "error" }), + }), + })) + lastSig = signature() + end + +-- Data-driven render: only rebuild when the visible data actually changed AND +-- enough time has passed since the last rebuild. Defined after render/signature +-- (Luau: forward refs to a later local compile as globals -> nil). +local function maybeRender() + local now = noctalia.nowMs() + if now - lastRenderMs < MIN_RENDER_MS then + return + end + if signature() ~= lastSig then + lastRenderMs = now + render() + end +end + +noctalia.state.watch("procs", maybeRender) +noctalia.state.watch("stats", maybeRender) +noctalia.state.watch("refreshedAtMs", maybeRender) +noctalia.state.watch("err", maybeRender) +noctalia.state.watch("refreshMs", function() + -- Track the fetch speed on the panel's own tick so the table stays live when + -- the user drops the interval below the 1s second-tick floor. + noctalia.setUpdateInterval(refreshMs()) + render() +end) + +-- Periodic re-render on the panel tick (anilist/nix-monitor pattern). Belt and +-- suspenders on top of state.watch: even if a cross-entry watch is missed in +-- some shell versions, the list refreshes on its own. Track the service refresh +-- interval so the panel keeps up when the user drops it for more real-time +-- updates. setWantsSecondTicks is safe at this API level (9router/mawaqit use it +-- at plugin_api 3). +noctalia.setUpdateInterval(refreshMs()) +panel.setWantsSecondTicks(true) + +function update() + maybeRender() +end + +function onOpen(_context) + sortKey = cfg("sort_by") or "cpu" + sortDir = "desc" + filter = "" + render() +end diff --git a/procmon/plugin.toml b/procmon/plugin.toml new file mode 100644 index 0000000..e0d8f59 --- /dev/null +++ b/procmon/plugin.toml @@ -0,0 +1,77 @@ +id = "weinguyen/procmon" +name = "Process Monitor" +version = "0.3.0" +plugin_api = 12 +author = "weinguyen" +license = "MIT" +icon = "cpu" +description = "A bottom-style process monitor: live CPU/RAM/swap bars and a sortable, searchable process table with per-process kill." +tags = ["bar", "panel", "service", "system", "utility"] +dependencies = ["ps", "kill", "head", "grep", "cat"] + +[[setting]] +key = "refresh_interval" +type = "int" +label_key = "settings.refresh_interval.label" +description_key = "settings.refresh_interval.description" +default = 1000 +min = 250 +max = 30000 + +[[setting]] +key = "sort_by" +type = "select" +label_key = "settings.sort_by.label" +description_key = "settings.sort_by.description" +default = "cpu" +options = [ + { value = "cpu", label_key = "settings.sort_by.options.cpu" }, + { value = "mem", label_key = "settings.sort_by.options.mem" }, + { value = "pid", label_key = "settings.sort_by.options.pid" }, + { value = "cmd", label_key = "settings.sort_by.options.cmd" }, +] + +[[setting]] +key = "kill_command" +type = "string" +label_key = "settings.kill_command.label" +description_key = "settings.kill_command.description" +default = "kill -TERM" + +[[widget]] +id = "widget" +entry = "widget.luau" + + [[widget.setting]] + key = "glyph" + type = "glyph" + label_key = "settings.glyph.label" + description_key = "settings.glyph.description" + default = "cpu" + + [[widget.setting]] + key = "show_count" + type = "bool" + label_key = "settings.show_count.label" + description_key = "settings.show_count.description" + default = true + + [[widget.setting]] + key = "icon_only" + type = "bool" + label_key = "settings.icon_only.label" + description_key = "settings.icon_only.description" + default = false + +[[panel]] +id = "panel" +entry = "panel.luau" +width = 620 +height = 660 +placement = "floating" +position = "center" +open_near_click = true + +[[service]] +id = "service" +entry = "service.luau" diff --git a/procmon/service.luau b/procmon/service.luau new file mode 100644 index 0000000..385af7b --- /dev/null +++ b/procmon/service.luau @@ -0,0 +1,251 @@ +--!nonstrict +-- Process Monitor — background sampler (service). +-- +-- Runs `ps` every refresh_interval ms, parses the rows, and publishes the +-- process list plus a lightweight CPU/RAM summary through the plugin state +-- channel. The bar widget and panel are pure subscribers. +-- +-- Structure matches the proven audio-switcher/drive-health service pattern: +-- noctalia.setUpdateInterval(...) + function update() + a guarded runAsync. +-- +-- Root-cause hardening (added 0.1.5): +-- * Every step logs via noctalia.log so runtime failures are visible in the +-- shell's plugin log instead of the silent "service stopped responding". +-- * The runAsync callback body runs under pcall: an internal error can no +-- longer take the service VM down and trip the watchdog; it logs instead. +-- * refreshedAtMs uses os.time()*1000 (milliseconds) so the sampler has no +-- dependency on noctalia.nowMs(). +-- * stats is derived from the ps output itself (summed %cpu/%mem/rss), so the +-- bars work without noctalia.systemStats(). + +local function cfg(key) + return noctalia.getConfig(key) +end + +local function setState(key, val) + noctalia.state.set(key, val) +end + +local function st(key) + return noctalia.state.get(key) +end + +local function log(msg) + noctalia.log("[procmon/service] " .. msg) +end + +-- `args` is placed last so the fixed fields are positional and everything +-- after them is the full command line (which may contain spaces). The shell +-- pre-sorts by CPU and truncates to the top 120 rows so the Luau parse stays +-- tiny: parsing every process blew the async callback's CPU budget even with a +-- single-pass gmatch. btm shows a screenful anyway, so top-120 is plenty. +-- The trailing ##STAT/##MEM sections carry real total CPU/RAM from /proc so the +-- bars read the machine's actual usage -- ps %cpu is a per-process lifetime +-- average, so summing the top rows and capping at 100 always pegged the bar. +local PS_CMD = "ps -eo pid=,user=,%cpu=,%mem=,rss=,stat=,time=,args= --sort=-%cpu | head -n 120" + +-- Real total CPU/RAM come from /proc in a SEPARATE, tiny command so the async +-- callback that parses it stays far under the CPU budget. Folding the markers +-- into the ps command made one callback scan the whole ~15KB buffer two extra +-- times (string.match over the full output) and tipped it over the razor-thin +-- budget. Each callback now parses only its own small output. +local STATS_CMD = "grep '^cpu ' /proc/stat; echo '##MEM'; grep -E 'MemTotal:|MemAvailable:|SwapTotal:|SwapFree:' /proc/meminfo; echo '##LOAD'; cat /proc/loadavg" + +-- Single-pass row pattern. ps -eo with trailing `=` emits no header. Six +-- space-delimited fields then `([^\n]*)` grabs the rest of the line (args, which +-- may contain spaces). Note: `.*` would cross newlines in Lua (dot matches `\n`), +-- collapsing every remaining row into the first one -- so args must be `[^\n]*`. +-- One gmatch pass, no per-token tables, no trim gsubs. ppid/etime are dropped +-- (not shown in the panel) to keep captures to a minimum for the CPU budget. +local ROW = "(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+([^\n]*)" + +-- Upper bound on how long `ps` may take before we give up on a sample. +local PS_TIMEOUT_MS = 5000 + +local function parse(raw) + local list, n = {}, 0 + for pidS, user, cpuS, memS, rssS, stat, timeS, args in raw:gmatch(ROW) do + local pid = tonumber(pidS) + if pid ~= nil then + n += 1 + list[n] = { + pid = pid, + user = user, + cpu = tonumber(cpuS) or 0, + mem = tonumber(memS) or 0, + rssMb = (tonumber(rssS) or 0) / 1024, + stat = stat, + cpuTime = timeS or "0:00", + cmd = args, + } + end + end + return list +end + +-- Real total CPU% from /proc/stat deltas between consecutive samples. ps %cpu is +-- a lifetime average, so we diff the kernel's cumulative jiffies instead. +local prevCpu = nil -- { busy, total } + +local function sampleCpu(raw) + local line = raw:match("^cpu%s+([^\n]+)") + if not line then + return nil + end + local t = {} + for n in line:gmatch("%d+") do + t[#t + 1] = tonumber(n) + end + if #t < 4 then + return nil + end + -- cpu user nice system idle iowait irq softirq steal ... ; idle=iowait=0 load. + local total, busy = 0, 0 + for i = 1, 8 do + local v = t[i] or 0 + total += v + if i ~= 4 and i ~= 5 then + busy += v + end + end + if not prevCpu then + prevCpu = { busy = busy, total = total } + return nil -- need one more sample to diff + end + local db = busy - prevCpu.busy + local dt = total - prevCpu.total + prevCpu = { busy = busy, total = total } + if dt <= 0 then + return 0 + end + local pct = db / dt * 100 + if pct < 0 then + pct = 0 + elseif pct > 100 then + pct = 100 + end + return math.floor(pct + 0.5) +end + +local function sampleSwap(raw) + local sec = raw:match("##MEM\n(.+)") or "" + local totalKb = tonumber(sec:match("SwapTotal:%s*(%d+)") or "") + if not totalKb or totalKb <= 0 then + return nil + end + local freeKb = tonumber(sec:match("SwapFree:%s*(%d+)") or "") + local usedKb = totalKb - (freeKb or 0) + if usedKb < 0 then + usedKb = 0 + end + return math.floor(usedKb / totalKb * 100 + 0.5), math.floor(usedKb / 1024), math.floor(totalKb / 1024) +end + +-- Load average (1/5/15 min) from /proc/loadavg. +local function sampleLoad(raw) + local line = raw:match("##LOAD\n([^\n]+)") + if not line then + return nil + end + local a = {} + for n in line:gmatch("%d+%.%d+") do + a[#a + 1] = tonumber(n) + end + if #a >= 3 then + return { a[1], a[2], a[3] } + end + return nil +end +local function sampleRam(raw) + local sec = raw:match("##MEM\n(.+)") or "" + local totalKb = tonumber(sec:match("MemTotal:%s*(%d+)") or "") + local availKb = tonumber(sec:match("MemAvailable:%s*(%d+)") or "") + if not totalKb or not availKb or totalKb <= 0 then + return nil + end + local usedKb = totalKb - availKb + if usedKb < 0 then + usedKb = 0 + end + return math.floor(usedKb / totalKb * 100 + 0.5), math.floor(usedKb / 1024), math.floor(totalKb / 1024) +end + +local sampling = false + +local function sample() + if sampling then + return -- one in-flight sample at a time + end + sampling = true + + noctalia.runAsync(PS_CMD, function(res) + local ok, perr = pcall(function() + sampling = false + if res == nil or res.timedOut or res.exitCode ~= 0 then + local detail = res and (res.stderr or res.stdout or "") or "no result" + setState("err", detail) + return + end + local procs = parse(res.stdout) + setState("procs", procs) + setState("refreshedAtMs", os.time() * 1000) + end) + if not ok then + sampling = false + log("ps callback error: " .. tostring(perr)) + end + end, PS_TIMEOUT_MS) + + noctalia.runAsync(STATS_CMD, function(res) + local ok, perr = pcall(function() + sampling = false + if res == nil or res.timedOut or res.exitCode ~= 0 then + return + end + local cpuPct = sampleCpu(res.stdout) + local ramPct, usedMb, totalMb = sampleRam(res.stdout) + local swapPct, swapUsedMb, swapTotalMb = sampleSwap(res.stdout) + local loadAvg = sampleLoad(res.stdout) + setState("stats", { + cpu = { usagePercent = cpuPct or 0 }, + ram = { usagePercent = ramPct or 0, usedMb = usedMb or 0, totalMb = totalMb or 0 }, + swap = { usagePercent = swapPct or 0, usedMb = swapUsedMb or 0, totalMb = swapTotalMb or 0 }, + loadAvg = loadAvg or { 0, 0, 0 }, + }) + setState("err", "") + end) + if not ok then + sampling = false + log("stats callback error: " .. tostring(perr)) + end + end, PS_TIMEOUT_MS) +end + +-- Seed placeholders so subscribers have something before the first sample. +setState("procs", {}) +setState("stats", { cpu = { usagePercent = 0 }, ram = { usagePercent = 0, usedMb = 0, totalMb = 0 } }) +setState("refreshedAtMs", 0) +setState("err", "") + +local function refreshInterval() + return tonumber(st("refreshMs") or cfg("refresh_interval")) or 1000 +end + +-- The panel's Refresh slider publishes its value here; re-arming the sampler +-- timer is how the fetch speed actually changes. Seeded from config so a +-- reload falls back to the user's saved setting. +setState("refreshMs", cfg("refresh_interval") or 1000) +noctalia.state.watch("refreshMs", function(v) + local n = tonumber(v) + if n and n > 0 then + noctalia.setUpdateInterval(n) + log("interval=" .. n) + end +end) + +noctalia.setUpdateInterval(refreshInterval()) +log("loaded, interval=" .. refreshInterval()) + +function update() + sample() +end diff --git a/procmon/thumbnail.webp b/procmon/thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..b614075f5fe511ab5fcd4a07902628d471605761 GIT binary patch literal 67326 zcmWIYbaVU0#>f!v>J$(bU=hK^z`&ruz`(GdnL(O~!PD6}-~=NB0|Nu&2@uI*z`&53 zS5g$@?xYYA8KuDffPs+#EYHA@m|R={QiB6CGBA9*22p!i7l#^r!kj6o#mNi|3?CR6 z7*vWPBBK}>7)2Nu7(~(`Yz+`Q3BtAkvCB&eN*EXz13>JekRWFU2F4Ty1_q6EBz6)K zJGr0;q`rrNfx#v>rxaut$UPw6@ucR31~V`)a4;}1$S@Q$1TnZXIDz~RQoz8Fzleds z;wu9K^8|!g(o6=1?X3(961NayDhn7G_*)nlww*_aA;P2}u_zI29t#5l15;WW1H-4~ z3=F)H3=F~-7#O&s!Ey`?3^?2diZmGpcZPh1e1>#}9EMZ|1qM%sJceWjJq85^BL)Ko zLk6?301E{URz`+`nM`vTwGJ@LFxhc3H99m}GB5~Cn^wcU_RaSr>;Gr|)BgYe`uH#Q zFaA&ee){j^Kj;6&f0k#sf60D*{n7t|zq9{2KQ8}v|Ni;!_kYz-*w1Lc@PFg~jsNWL z-TtTlPyW-rkNbE1-}wLPKl8usC-}eAf0+Ma?S^j(ckN{`}uv|NDRc|Hk-#_N~J|*#E5$;JcFl zPyXtD{{N|Ub83G7e*d<;q_x1^MtKa)y z_lK>_T*v>p|DwMoKM=p%-}m3;-_3v4zumvB|5g8N-@kvif44t6zyJQN|8xHv{^b3_ z_{aT6`RDrA^{e*Z`2V1G?Zf(?{NMk7+k2qC^}qDL?!U|bw0~Rw_WtMn=j#IhFa0= z`MLgd{bW7nsKb*b2(|=E^A!lf)(VH)mGK^~9z59{*w|m#|&!=a;IS}~f(md8|lZG7KH5J)w z0@H1N7%khqbl-i~>pgRAj_CUS)=fTjVc~3FiMvk(Ty*Acx9AOzp2(N-$LsEfr#kTw zGY`yoeDkWH56g{1`6;X%U!5Xq%lCM4>MRL;l=s-Bs?3`6Y!K^uIgbpn*&eZFjZ$qh z4$YA)%wOB}ZGY)jou79NO%n`Fdlt=XeZF1a$L$F(_XHi*>!wD3;;u)%JO4LPY~GsQ z$<0a5*Tr-B9=otN&pW(IX3}eiWKYON`|+!yxflg{bH zG@dQbg%_OH-zLyH>$!NRe(&jj?fF4J*BrdL{7O$4PtZn=Xq!Fy!U3We_1iuEKK%Z2 z#`42c4oIGQut6i_V0g4GOTwPwLw(Aw+oYq#3%?tGjBA&%R#>`sk7lf{o(=!gjJ&yX zch5Q!c+~QHZxH{VZAq4O%bZ^-dUeNy3KN;dsvoi|J5HQe-FRA_1;c? zUXO`&vCXM1_a+@IIm+k3Io*=={R0I-@xQ-)zO*|23tp$3q~_<_)Hb_!P1dc#J*Q1K zwZ`o$Z_(gfl6**tMS%0qBlDUZFP`Erzh1f;-r`uDv*gywo(GQQS`DT)yxx|NuPoaq zb$j17$FSA!llU zzX`cb6H-arbLgXWN09bxd#8h)JA-#@({Vhu;M$GTm&~TME2?BZ7Y~@JwAcE+VwZTu zOaD3dBUO?Tn~SemC;zGPy~5r8X7hzRQx~#1bjdYLRg~PYl&dVK+hpgumm-xK-EaMW z{SdB;7XJCMW7oExxtE$Z{#ibM;;G%gO}+eB7ajML-IJr0s?^tBnec~aZ7}Czv6Y?) z)|3C`Us>UCQKVDJGmfk5_1-Nu6Kq~?+rg5-x_+<7GVOm!6WumE_1&VhrT5bTd)s67 znz^B~T~_sH9K0B?bKP3g&Nb|wllu9=~7M22>)83rRAL@Ga$bd{_~t zcWC1CqNTbILi89VmTs6-bn1MzjY!)Ah4?8hX(3>>jgTTJYEw_5UJred% z%YL;fDcU@A*Z(iOPseq=`LU?itNfXvQL2=L)2? zV9mlRPsc*ZtwG1wwPOS1j;~Iv&C#Dzcv>fM*0y6OcWs;(J?Yu|H{Z%;{o=IMUT$yx zKXcy3CFb!Wzg~S^EUNeKe)FwODmHc=Hpi#)9>|PaU!n41Le*JQvt$3~?CrXm^f}6B z$6Eil2V&++g-zkw>8#jeb#hg|vTMwTjeM4k*A|~(`E{;1`p}2%f9)okb@|@N%6WBY zdFC5+&lA7S23KdEEvq_rbl!jOr8bsRHzoJ}bC`DYzz&rQ6~7|{T3b7N^`?lNXi$m! z{8l5u^O04m^Y+zqWmmA2%}-!xx^UJ1rh)zGJwd+$w`QaLq`rthO)esV zbD5_FHf9Sqzl;2&KHJSW`HtK1m(eTdzM9CQwC&Y~&hmFxirRlqoS_@>mucNq@fizi zr1x#kD_wYV)p!2RfYq;=&Mhu|$Nn~qZRY*f$(L^@uS?mQd*OGFAfr&_!?U^~lN*+t zxOpn7yuU$UnVzG_>m9+`$B$j}O35@co%WZZYX8YuOLuh`YR>VycR;k`#a-WRg^xHt ziu6eZf4+Cgt6*b$*t?n?6Le;1i*;uxImj-3ykzUPGn`M#KKy@s=1S?aTU9o_h4vZT zf$Z0!d0*!-rRfTmi3yfeUC`g{x#iSnVH4GdC;snz)>n3Y@BbEyOHIsm_nP^?Ox@l3 za#cwY zWIOFZ<0|V<>}7|}b#w>)QA%oz_AiXj=Snp8oFe@EcNV*)vrq6QLxGAcyJ@qp+;eqz zk^NtJDmIEc)F^D>=^NJ%-dM`dxa_usY(#Fgy0bknYU#SN z!(Sr5X|r|do}QvDd&EDSd*MIpKCj1pwP`CpW@gB0fBZO+X`&6+qE8+r30v=Jh0k32 zV71{-hL(R%6nXf!m`ZJ(`ssJatGO&ki|)F|ty&uKW5s8u>u1>;Zr98_sjHgU?B3Mb za%*j0uvD2rRLnyY>%RxRrqzj0Zq^X%7x=z<}K&Hp#z zhf+_*tlKA5vOT`{H)oPT_D!}ko9a70b)9=3cqUk9{*E)UA^EZCC7kvE8 z+#I#kanb+K?zZ-qGkg_aGTgf8yuyw5+k0_IL7$b4Uis@~Mb<2N$a^PY^^e7=F)k}^ z{8-$yTfk{+41;dYW&Lyh*7qbh%Rd&|-@kL?Lj2zf`>&0r#flpDl;>QCe9SNZ&N)Qe z?dQv*8`9=kFIH*XIaxu<@g?YU6?e+dd+Pu{{c(cii*`gHhyE0JG+YU;pW?49=IR5 z9Ppn%t8B;QzHU>Sm}a$`Vn?2H3)Sq-Uj6-n*vc;%?VuPcdq)OxmC&6Wf#S+2l^UC6lW&fDQH||qjWoS`oAP8t(OANq?U5IJUJO8 zcQESXR@1BJ(xjteif>+f`>Wx|ue_hGuT}gGN4cH-SAF)_^CyLWb(=(2PYz@^cqsRO zrRM_IFaFE)%dT?#3UUAVd(MfjhJJ;edp3XgX|cN{rnfrTcsW~^Lfr1X+vh3?`gu8? z`{3f(#_neJ_|`AE%DdA<*&}QW_Ldd1nC07FJsZ+}H8p(JW`WhW))c+lH8bwuiB}6f zdu31iUb@n_l{H!d71X&KinroDtpbohK3|HoS&n_oN;#^r^Ex}P1e!B%JdbL!VazKdZ>JEam6Dxj~6*K3(S&Kr^#eCJtSVCL018h6+BMEs^rlQ@3g>uD@rYrJc= z+_Tgw^v+wn>$@6lwr*0@u|IYdh4yV`!m~x4^*wG*U6fzD=4EC;M$_c5PfK~mS?uz5TF5-ROBkV+rHLDCL}~yYgGw zt_QFvCo$gNwK;;ZTlwtFt@#BpYm1h-Y`Vs&#&e}xD`@V_;vZt&p9??P+dN|RR9kw< z@z&*v^Xt~kn%=5weR!68n(D!mZ@zSP-|CWLb1y#2cE|kdBKytzzWaSH*PD0uv`1@; zy6N1O`)MbS&wu_r7f3f=cyL`axH!$TuhM~PeOKR)U4R$ zR6i{^-^5P5=EUW{o{Hrmbw5S_{!a*U(CY7LuQ_Gk;`}aOgt4@+aj%KM)rE2Ey|j5= zJA3V%*yXz?=lW)?l^5SyzGY|lR8|-~m$#Z>rE)oMyn#!t>YvQd>JGmwcqcr)n=#eo zTf>Z}4gzh(PIJP4zdp0>%?vZSq__*Iy-QC-aHme$nL250!6}E@u;W(o7NtBN&xyXc zcK`gDfaKJI;~c3A^f(e2ru!~-fB)B_GiJJ{Fx%aZ;HPt!7)-DAa9Hik@Sv@ta+Y_~>&rIl4;TqM zi%yKWV9>ecB{zpN|H<|jeavsasrDyKOFcjy8Gkurj^zV4t4u4 z-eeBHuA`;bdxy*1Kxys|zXOZ!&yY<&)IVc!%!#*KLYP+_^RDBrOZS%ht=TWS-uCS0 zjXzsm>ns)rxcTRbtG#o5{9XCjN-2Zt&xh~dYnaLQH}_us(sI!qU-BG-szeU+K7F~|U(3k1OfK<^!fURW1$#Q@39n+XJEA|c?#id|8wV9P zJ#RjgadquPuZROvGFz@jF&{Y6Cc5(X&s*ZVFK%2uL3Y;ch`8%Jb(wigKJ-eo9J>Bv z?Ov^wFC6)@<~5%=`^=DOmyeId#(uf=D-7o~oZEEcZpp#z%k4A$8UjmqE`D>cux#qH zi9WSLB@Ze8t0ee<|KnSbW{C=~{0Yi1r1=4ATE@|7h;)Ai+=^yiNRc8i+6+7Wc8FXGyf zm(rK+D=xJ^S34)1ZSte;Tl10<$~Uwc1ghH~QLi;!6J#2^YE46r;H21HZ!9BK8?G+A zBDpG}TtcKpG|bq&`q#!S@8(*h9uDZWoAK}GG`H1CbI!eZ!5Q(!Y~k#!D^E|UddPj` z<`%?q0U=FfXqTiHn`cgvme-|*#o?r+UQ;WjI#i$;ry{fU{8w_BQ1 za4mz*TIZz2k2H8qOxhQ{Vks_~`c3neO+3>H)00icj~APs$=M!UIG2H=WAg{&`VQfD zD(+W4Th6Lg$(_+>vT6z2%!Z9Jk@*L<=pMW5qnMuI6tO?XL*Tkl$@cR1HMl}!(Ps2i|TZS(1pc(078A1BqXyb|v| z(h^$1cxi%H)oj22vp+EAMSIA}zPqlco>p@|ceeJftM3mTVEFfcuTIYFG?V{d#C9Ht zm-rVqRb{5z=7N{jopp^?zaJjmcJ1EOORxM|Wc0Z1JUqc7J|(Mlh2~c!)5Y_vzipgX zU@Cq1vQ^s6xytM8kA_~2Or5pyqSPO&>vaki5|_81+d1{QN%r$grPEp2sy*~Y} z+M;Z8ZtAs*`1l;#$Gs+F$`^5=xw5|nwRT>$?O6SxN3`X8zgxBEzO7t0Dx&7jU7hZC zwIL<@bLBC9-SqU?FPi7>W0N!5G&xsV+9baAX^QMGlc48^c8PRGU*qIuW7jNszqfzW z%YE*Aj@dKyw{V|~xy$}zMYh3~!%rs)zBu-%ugxY~YIBGCa!=t2-t5=BZ%+wHS7?|h zbiQSItWT5a!@l+Q99wgI7yL}`=~DdfWPIgPPwk5d&zAf<_|5U+qje^$qqHYavuq0g z+PRluyU4W7ofcg)^=1TbG1XXlr(@f?nw+&4%6uB1yx+0+g2(6I9IQ-NeOA@ECzhYD z++-3ebtb2cquk+HSX1`RMTn-~27w;=Czp(6{VsR$#z`BOzUe|ATXKhrqX>I5#t$h={W#YYU{Q2T* zg^gWVszn?FZr@>;cfcn!b^2Ym=@qK-B`nvaZmeEWak4I_Az>od7IwADGF=9nBR7Pv z*96vV{#R5Vxm;~mjb^3pwikQQ*FRhp zd;i*ZX4M>-cDBnOF8#XIRo&jZdNKP9J(KpN2@5lrJ5Qf&xMl5q-7Kx@(0-m9Yfo@4 zs+ttaBR*>yJ3r$p@5x91@7N!6eEv~0#y;;`Ij(w{A6?yMHGMWv$gpw?U+BE;;q+Ty z&wbK3|DXFeBrT8TYfIl(JhF>R#*;~T@reiFX;Qo91q0g7|EHG56Akx1+!CKWi(R@s{`0b+s9ehPCb=RAj{$ z6|rx4qZ_NX*0x_LN;r9fc?*B#nPoT2{yv(ZG;hm`EpKCV+PAk{yXD}|aFHj6?)+RhY1g@Gwq-Les@tA#F;Tv%I_ux! z+R6n>OJd({m38}b?B?5LftrW5T3uA|G_kJ^$!I+$eO>9-%rh+SCg+@993ObFXT#=g zYGp^i<8y67*IU*s<_m>UrH@}sT9esZXmfdcB*S{&X^s2t?OO3}J?laq z+b)ITij#h^3eU>pFEDv(FkIs*71pt7$Xfn7oY$|T^?YY}Xugdxw|UN2ai{&um*{uJ zM0I{RdS>O-6_Y2Pe*bmdgY$g1HvM>af2F1FvhLR&52`p1Nngl&6rwg|^0YH!tu41=f1}SD zxbQ%s%=819uY9-3%2=j-3|04w{@>LkykhEI71qw!DyQ2%Nt0a+I+d-@2F~)ytrU;! z*_C;G)8YHS=h!s?J$pO)SC6c5T^Pk$7&Kvy~WWAEv0 z-I*t^P7u42GJCo4vAGZCE;KY$40y_woIf-C=9=<1iVY5YabINxCq;E^G`+_Xn)hPc z+4H^}j#GK1u8Qx!Q=S;R;9=Ee;{t}9_Y(5UJNK_1&;>$h?9Q+z-Emyz7 zCACJ<&s6;6w+~9(EM67&O+1(MCv7P#J-SLsfH{O_)?cXsbZ&N z_jH!ItdCl|?%dMTR_<@V#Jnlj{&LE*f3Kh*zpJjz`Y(^J%urku#dz}N(ds9aGjlImRJHclUmgVlVtUk&cSs*U3^p!%A^Lz%g0>g+I%ajdf zF|s5tDUP0$bf}40#Cu`&M)QNWs$=2+QZYu8O&+-G+2-weiy zODkp>Uaw4haD4jWhi{@I(%$Ij{uD92kCcC6&rU+?TBR2yUg8^@T&jZugGV~8(Zf@YHdEQ7`J+_R(E~0 zq@dNLxeGW$?s5CiIH$ioYn#}dSt_S~&+}`3oFDY)@LN}PwI;=!b=%|RgLPRF?x$+) z=yT5sw1|$9>VNQ|{OQ@lCp@&n8)ThdEqc@;VNh&z=1bGXquc#;Vwc=)Qe*VavYYjT z>22fBkcul+MYgB+EoM~hesn|p$CjxPVQ%q%V+-a_XL{VMwa07=TX_4N877VWY%VI3 z-I(=1rZb#e_~l8{)G5cmiro75>+W;)o9FjQeOEuEUDxtDo-@jg?fQ-9?`K^({aIK) z?o3;c(vQ1J@#YJ6aVA?gdmmdYr}N_>&k5h7Pc206MVDU)of*B${`!)O2^)lGF6t{> zt?0RYR_$_e-`S;W@=bG>9h>thqTa?_ZH0n_&!rjJdMEr9;yO%B9u}@zkoEsbh|0tr zYqFwpzR!PBy+cy}W;fpj_MnuH-vWCrS%Oy@9Qe1U_^!6?@%kM7a7pHDm%g<>&eW_s z!W&Z8k`ng)&bC=S+xLD@*e+Ff>*j*?av}G5*M%M@Z+)N1TJVG8a#MWFakCcrhJ|ai z{O|6IwHGLNzP9xDH|dotxy4Q&t4&`1=kwQlhhA*VJ7)S*C;zQ%k` zN{C-yu}o@qe#W&u2lneeGT9(I^Xo!Afe+yiGJ;op-r#0p^TSo&=yve@huz{A+>XsY z|4r0TJtxpV{zlo(Pn-w0AJ%SKE%Ge)Y{zUKEjKgQs?@f94hN1e%U)4ZXBEwN+R^@1 z?yZ@fO@FQ|Yprwk?0=@P&lPLFm~>gCZCkKB z`Kf@{DVxKN^>NZ&+ixUE@=cTQv9{07N~>P`UQ#P|r?Zong~`UN{j8E(64DYre$Uyz zT!j64kS4qf=a9Wycl56-)-ud*^K>yQ3-(UYh|N8tp8xLZ(>U+i&_15sm+xJAQgSmxQ~G_0 zZq%`sjWeI+m><58T)jo}&Ih0DjBSaheq1;iv`6Ps+*0>C!JI&bc&i`$N8?}LX_y)I z{$Z^w^TH23a?@K5*e!jTROF`9@r>C{F37t0xG>l1{O8)5T=mM^_Py%1Bf@|jiqa|-mR$o{>Gxog8{&{-ok5)CP?6<1-O{+P6bl-g=W3Lw*6Pc13 zM5ns{PkFuJL(APsxqN=jA20D4oaU@r?YmC%z1o_olei}7@0+dezsB2emWj8KM4M3Q z2ZiZQSwDN$Ysc!py|4fEHgElXW${%TxcK+1(D+iI(#T)ue{|xRom0H>7{gP3&-*L%B* zjJOocOs#LW))6>xC?r6(Q=Y%q%UE*2fm^QCYq> zW3I%P*sI2RPnU#Vl?%A_xWk~~WxmVq6(JcK#)(t3UO%}0n@j#&@uO+aB_5q?`|&C4 z@cp0M!4Hl5W0zlx<8u)W{P?csBlk=-rtgO9*pGEA7yA3H*y5o=+`W+Y#pgeMNWEdv z!=z&(HEVTzL4;&#(|0wSsg@oaXBD3CtA12@ZQk995?{)MY_>}8d$+s(MUmmkj_+3< zWOVJ!DT0|=3kKCs_)j+QuALl>Eb1}RYH?@efa5|`O@0^aLyBp3yj4xMT-su z*co@9kiFv6oNSm;g_*y$T$ldK>~_;6^zPAI*_yB2Msw1Gj!PW9yn}0F zk(B?&iLM{pMQ6|Zt>^nl)$7t@2IHV#D!*#(Sz2y+_06;Q=i?1$SIRys<||cS!S#E0 zR_AKfXJsb41aD*tAL#xwX&=Wu3xUbe$**T0XZM}zm^}O5m&FFJN}n=FKbfP6 z_nBg$UkfIGmYn_M&r6ppx^4N^mIsduRc!Q~>z_A`;cG2R$2rZ1_dlF^9~@f#_=nN= z3ibErEOx1<=U=i;micE=F}FbT^#bd2F(>ahO_;BIN1?vt!OvtZ<#Rjd*&bQCca>aD zlGEPKzP{L~^Yi7LkGt!RKi^MeX^lz`hs-R)r{AV z&ir-mjPex@^><80@_{;=nYK>3r1;0e=C;M*-KA=MYn5(@&nRqI%XV%~Oi%*X)5(?` zt!nNj<;NNQCg}NOi|-Y@AQf=z;>mw&TIT=JTYRFH`Q8LW>x^m{u{cke6?IL!W}SOp zsNrd@>ejrpIMwX`BhO7q4?H&oevB8M{W{4|Deh)Y*Y1Zld!3jMUlQ0568x!cgE9B? z3Ll#{H+I==Pd{?B@TN0spzEf)efez{-!*Kg4=DFu{P*3lm#^8M`S?zj(M%TiuSuq8h3oi?v>)KE39)=J0WGW)63(q{x^Rg?Jb|4@40i9kMUaZAH}75 zS9@(M4DOoCvM9RjSigDC?Ni&neZJdxF(XZHvCxUE1HzriK7ZCi%zDj8KOZmL_Z|ko{-hXFp$i2Pg>;^{hmJfTM6~)%eHQwPqf65p$UH1{vtd4kDo8Ml&iQ}2bE)8~cTCqK*Vv#!`IA~S#fAGholzTcG+T7$fZS_Ov*fzXUr?+U@q!gJZXMQPk3eiw6|=Fj`~WSnYaB}`^+fOtIm$O z?dxk|IXAG}zhkq?{9Wfn)^+>5`L&;lW`2HUetDkrrnmlkZ+cB@bAG0G`i)4$S>*=z zuHD?VXa35X@7)tozfQ12Rz>&Xx&6t|NdL;vmFWH zQ|=|)n3a4mtoX$KE(!f7l9T3lhx&@~g{Fi~WBPw^0@Kz*qS{e9v1c#4&dZFInJL5^ zZ1lXR>+}DslRkvVPJeq?u5Hiy`PUO8108=)a=4w8o3HTT{RxxClFi?rGkHZ;v-T>L z2mZRjX7^x6!Cb399)H3z&E_sS;GsXs&-SIX#H+>E*?Ax5e7^if{`m9$O>3rI5z>!i zsXezu;Gu`ypDeL6yq`PP9Y1=*DQMH0BT^0Qr*k}JtXb2gqRKDT>?3@Edwurv{xYVT z3f)fA*_`3iuYFB*R>^ayD&cxOBX`z}Ytttld+Ix_zeMr>gunvcw>CeX^rbbiMk>xO z37dZITlQRb-+9Yz*;{V$FZlLsJOAhTYqNh(Ir~+n|Giq9YESvCM?8kBIK=u>B|D_; z65bu#`@R0_qw4K0*LTcc!+b3Eg1kb!Udu*dt%r7px#yot__XANcr|C^wE}ULpBJTf zFllrxiMf#SJaWxD#~6*PMGx0XtAEp|+!4HUk>%&~^CpeUS0?NYFUWK=XA~7I{3CTJ z^|3~6`d`C0$M0O5cf(yPes){f8^alziU-5yUq}-aQomv#s`lmNhkwsMZuXk^dsEPa zFCT)W%%r*x|2aFykpK4473_QW=kpf!Y)XBV<73<-w(ZTVkf4*>ex(=Wo#D=Lc)(e} zn(*qC{5rimTa#FYcHPz86Vkqc&2GzYHnIKP|78UvC1d7EF1&t}W9$9TP7ADS+2bmf zJ^mIt&nvr2KRmYPNOP)IpT%Sj#!lli3vEJ$pCqck(mt(pe9h6CnYIhOEIryCc8c)Y zoDS7{D{@G*EBKL=uW0j4wq?`P@Bb{{@?WFnlr-aE>(tW=!?~4iYX+NAIiK(a>c`AqRUEHTm=^g`qr+nE1%q=uN})-aA9gF85fwUPJY(b6k6{f}B7HRm znK|}H7hS4dH`n5ou)k7a$epH{cNcDY>|L?8)|!8(VXA^s3&(G@WTn{`UpK1lv`X^+ zm>T=?sP2h3M{E7ex%mz94A~bu-0*5xx*%PVkLUl)$o_Squ`HQeC+)vm_x-ccQiG53 ztIwTku@~GEZC2ginBJYZSnC6~cR|g=;Fwpy0UU;O+p zea!oWMoiYN=VyC2{`;w$z@VEQ5Mo&Ytm z)SVYK`#z>EsF{+wn2lpcPvB9;mw%0Zv`g?GPxWX}x9gvIef@H;PtNC4&wst_Kg;r2 zCj0vm_57ncsZ)Qy2b>HK9?AG(v9jER6_f9>0?aQOPPSw9^BqxZLG_&chFT6dyC+>vd zQ?Y%I)}NBTlDAVg?;)?~v!>$aqmJAXCG0Eizx^b&Fj{`~EUsN5kL5ZHVm+U`sLqpr zyFlYavTsY`i|FtCVaL~X-7dPd@HLawu~+LS|GxRh@y@E7Q8R1i&$aabJ$-uCyDoO! zZHIaJj$5AQyt4dV_N;5+8D35=atsz^t}j?)^G`HkbIXRW_8VTNZHh|0FZk`?qQa`_ z3>`1FX8SL=bkdsZN!#|bySXQbBwW(6cR0s=%sZ}E>rD$M15-hU?(3VI`g5cuJii}0 zxa+*T%Bg-ExhF67M9pbyoTPIr>#B6K?97gHpL$Bm#Sh%=y!O{|&y&K^>s4XNvP-zb zLpCvdU@qic-XY@fP_u9Sl^Zjnn05X-s@NuZR_&>BQH~RzVq|fy&N|iL>;>rpwnFi! z1uq}>-O`&Nyzl+BzbwMLynie68=54FuUP+X`?1H)(MPhRnBM`c2%TAe#db>Tl z<39?_ezGC|TvzaUlh&^f75xs)S+Hm6`Xi+rH68!MWiDuO8+1RCIB?#_Bd;|*yf*)F zPhP}*HUEff2RaQeFLich+*0Lj+%wllOQPn-l9kY5SICJW^SzOmvbR6aq`G3^+p?Rd^t!n4Pw^#~)8EL7t$VXlmC^Kw2 z9kH;xz$5VYe%AP#=Ul^_a~H-N&uX%n{7F$;V4JZmQxm5^Wc=eeC#Bq`)AFAdfA4vt zF*nvCoBiuFj?xR!H*7_-FZuDVSM$DiVH!)<4T06~PBi;Jmv~>b>w8nv>8gJ>L<84$ z9B%lxT1e*lUhgB>+r+kYx?H|`nWNH+r_hqY|1Jx|d4uRjQ;XQ=dB(+8aUNd#(OR|s zS~W+b#evw9&$|ku4^4Sf^=PdPv#GVtXVs){7g#2=oMrrcK4Zl@{nskno?TFAk_^iT z{~IvJwXJGaE8FgCu>n^S?jB9J)jR3v%ar9SoQfqB+0Hf2c=kKWVAaGUZ~obq-a4m! ziUe2?^_4u#5SiAf1n zSf8<4eE(Nb(X7-RH@2ixRR7v%$y3}2@ZSUms z!UAV$c;27b_BHyhv*wbCZFk+6k@RBUaT6t5$9Nq8Y^gO6iC?wx+onKaQykE1?IsWCUty4c$Ocz+frueAo$R^oIY{nXVIrYBA>~RJ> zQ{;D5)L%0>^0?IO;M}RgqAn)<=HH|~D;?1Yyv%S)?Sa$7@Sl06Yg{UP3x(4I_h0T* zQ;s^-e9}XRx8><)ihbf(yI(-`$v$Tj;Y05> zExNP1KYxz&y@*Gxl1yC8mibpc9=lonX4Z!n+roC~9hmR)Yma?abLYwC80UG17Rm%G zm7U1uUh{d;ltq8FeXfUoyqNQ$HOP(o>yit(495RDwy>2yl|J^c^ufz}jJu}q-@Z@m zJnxqikAn8S6^h%H8Mk0d+VT~Bm!hhBU3F(%itZ>e;Ca{bwQ@1L%B{1=zs?0JXIS$MeLPg=gOpK;c8 zWl4?qD>4@T{Hvk*?w8wT>r2iuI~CS0-FnCWYfa(;kyHGxwNp$VZu&jpl+>aZ{*i|@ z<4qcw+$85I=X~m2QgpCNP0G?gMA5sD^Tht(iTgS~gnMkMvV5=AEfjS7v=PUmm9m1r zwI4_bJ*@dNgDZ!*D&3*d%*A)Q@=S)sE@%DpJ)3^a%C*0s+Hfw^Ui9YSjOf`W;*K1cxdr)eg8X^M}=SR zdvg2SF}Z~u&yVdYeV-Ddp%u~Zlj(G=!>&o;sJiJU&6DrMf{GV-e_VXb)#wo$zw)O3 z9aH{H-gU5N!>2vzKQ~RNEMXDwzj`z6h(#gui#^TzE}m@?e^bz=B9dx-cOLsXrV|H_ za5G;KcoGvDU>7jw>4Dm#2|uIFHVN ztQAu{=AFWJ|GlT}^mESNPhTv)9~@ZyN>}gsG0ru;>o;#Rm+i{G_0!bBaOJ%lrMIQG zo~}Eu@%q4SWyN1g-%f1Wx+FyJ#G(sZ|7=b;KC`Ui{`%7m6OsPO8;w?r&k?Q_ZxZcm70K#UiFfU?POmMMUp4<6L+w=Eqosc5~My(AS9RuuRoDZE5JE5LL46;`1c%LJ9ZtM<7~|5)02i)Z$M347)`P5iCCJ>!qs zlf679p}c;2qAxTmJvuFK-adZmFjLU-C#PjtnnTot89lFgv1$F<@G5lI^v=nPbIx@6 zt!Y=gz|t>d{o?o<-R6t`CjMAyw)*o-S<^D$wW+?T{AXg|~94Opy@ zFL*Tn%msySlN6U(zLwQK8?l&4Uah7uPbiapF6*DUujbS!Cm(og`QG9Bw3eS&cCU?Q zT)5gce@R-agh83?^O*}8c00YA=@|4mcY2HZ?HwYao>MKw^PNv*HE}IiZ|Zs>cS^ti zp@rdQTh9D$X?|joC^W&>Vd8_S^S?g4)w`=?hxUj0_hp=`p4?8*3@s6TJvs4H+WS@W z=e`bV=@;^?^_YM7Q>N4YR`#F!Z?r@_jeJ)4`*ra1?YpHMCTbk|-Ou%kp(G%;_m<)w zz29ovM4m^ezQ34!LFh+P-;^6VZ|~1Ov7kcx^sh|Lf43ucwfW9$_>{f>gx;U3h9wdq zPqa%LS9rdQ{}B*yWO4ejSRs!?7j>K$3h;dB^OCr6OyA_``f{~OxmJ#qL0xPtXAOS! z*G!1pFH+uQr=a4#ve#;I{`O8k@wSc~3b!)b&PXffrk9*Hlk|(|KCH4id)A`cGr#wR zd!6}fr+vFa|L^02E4DJqsx_{=*z@UQiPD=4FU4&Ozi$0qSewe{xZ`j1gN1Pwp)apg zJt>6j?>p6ox~%H?H;1MB z9goP#a5r|AZmIivdn0)lu6c1~%8A_zgID|vKW6lgyQzK4L$R2$nbxuMelMx{$RM#r zX?<$aK2|+j{YJw#P3i&mlbbsiY`q^A!t(IUQMs*?JL3O*$WxlQ>1&HGM_w+|gIKAg za~an}ZzOKt*jldft6Z4T_=m08uYzMc&bK}Zn5Dg9$*ski2OZ|Is;X7`vn=O-In|Dny_LkP{x?SE}lTYjG&C{scnHQ&8vHy?5cK73LiQCqA zyxqd_e8D;8nUg*kFpL1Q&(JB9jGC<>+>VS)qUl*M^YZu*&1A3UUX@Ke`?7saW3W!#;T^DxSe)%oZQjy z(&0aM`DU4z%pC>3VV5K3HRtTgTeXY*wBD@ExYF`e`U5xX*x&YBn%WW_<-cqD%J>e;S(x;g%D=mC?Pu6< z?#I#TAKxGNH-T03WTx>;+ds?;i`?ZyW>2f#v`h9_p#PiJ7;C3#jZ5B!suk)Swen2) z#mye>S@<(+_LOh^8nu6!*Gt`Ic`qIxxUr()$B`nb;@YF@8fI$#nvlMm@xtoB&V2U8 z9KGx^-JBLOuMjt#bXzXKg(q7>x?E9 zTG>te&_ChAcG1^8```A(T0Y^<+vJBjqY-BsLh_wlqAS2G1pl5)d6lf zmOoF<$DD~TOA#vZuYIBC6mGqJ3d@T#r=Mq6p4=DW#J#k3apb10Ia7qzZ19(PX8W&X zd6&=0FHBG8SwB2(T2pdE>eS1FPk%1{(ijnQB>BKTsY_cY+21evJZ+2Ex}@N|V>O$_ zH}kCJoZP+8AQOU9?C*9CA=uF3R=J@;mQi`nx&fCLN zzqrW!J?nU$ujO+}!q)6#w=L(HHU3F>y1sQ{*dJaQzbCyl>#S$qaCJNGd?Z~z;AT(4 zjg=Q)?htrb;%vJsa!DfV#pt5*_jZY8a~JTpJ8+%VJDR6EjmK0=E~#&2#p!cTSJ}I- zO;pUwvcD;{_EXrxv~?@|uUWjf>k+Q`(?IOVl`o5zauqK6wtNlC_JuEAR4k6#?HiL; zYihdr>^gR4zuu5t-aD_gy9Z3{U2n7RGq+-*c6EIP!+Tfm!_q$m@9ulcd(vEC-*v~9 z4WG(1cKv_*HYShV|7-tz#dy|DNmetYPA|`zm|DGu{jdBaCYFg@hfeSMqqjSKf%Crd znG3GJydZI?R^ZzA7`iEEcbMBjMT+1_O-Rnn2f?BJ$f6T5tdF+RPwu%W;lZv0* z^%;hW=Q2K7gj$ww(W&JP`ut{wPhq?dkFV4v0~uwFX&cT5Kh&%^d*x}{N7e@~-_1(? ze7>N`a>IVFV{((5Urnk~n|M_}CeJkR>pbnF69bA?e(JmasYEca-2RcA&h2^M3jeD_ zOD|+o(`LW@9=%>%Yr&T7$J@Q5L+&ZqeL&p+R6lQ%88efuMmeWc3Fn&*rA_4?+#u;-j@bvLJA z_W$ORIg*pEu$)qgh;^KKQe?;MFwvEFXE5x(aN*q6qH70qO_Xd-Y!~%d@j>GD1c^sa zQtNczY%w(cS2y?aLj5Oaw?#Iy7(A#_O`LBrMR|Vklg%7S=~91d0$LlG93LKedt28# zWpCK)+{%Ubw`sB8mP(XmNwIu%_}}wiKP{tbHZMO~&S$gZ|DhwY=BYmpO+5VP$<(Ls zln?F6(p27iCp%qA&OP~9ZGvL8p7hL*i3w#>KThOJSULIP@mIC%QwwTSBnp|P-Z-7v zU3uWC@2*M3%Y%;dv|NwBwp^o#)A{4|WflEDT*98bepYvR%kwAf`&7GYmmOGpe9@H- z#h~AUg~4*aZdCri#U0rm;khlCzpSDDi}_qZF%wU{=gYW^W4`!gzF*R5{`u=PwQ3aSdv|n! zQjQ2?{n_I;);#|C`BsVl_b!894tA%$)V=(d6o2+`=-s=4jAzyVeAAn^x%^G(zT@-N zYwmjlU$48;*>x!Hxzdf=H}O_y1ys$xFTdL_Y0u zslVLE`!_c7W!+qI^I)sL>)D>$CpJw}bv&oPWcyKvy-!Z;fynfSVJyje zC8mFV_&0QpqxhVhvz$LOi*4iBJ5M>Cke_HKTbO2cwf5ZIJIh(7a#cx)-262s!9M2g znt&i}4bH!FSXZ8#YQ5e+Xa5#o1<9Ec-p2Rox)pC1sXDLnVdAz2FaD;zO^#^UbL@5E zyU*T*;qkG4xh#7B)6*tgyQ^#>xv%NkZTAG{4Z{Dn_e`4M+BP@QREaTA^Nx`z_o4mw z%%mrX^BnYFH$BAs$od&9)p;gUcQkffnpbQS7p1kW(WC11#d!g<&+eb^$DFZgLtUy~ z_7Mh2u^CM>JIxY~7u0siyQGD~dyr&qZde4@xF`J{d-pC`y{QI=q7M6?c4r#`m@C%u+V0Oam zpag!Cuo*YEeNDew_4b;_VaeS*x0OB@%sp50o-zHp`2&N>`Es^*IcJB&eZDqz+j{Ge zYv0ZU6ifVX&|F`+^;EpTu2Z{OtS#-%X};`rHJMa={h>(Fv!~l`$LYCz*ckjkL)-pv z#M=5vNwc%&n9aW}oxOo^LBi71Ue@I&@2YUg%<0-H5o9tuf+?|9C0^Ty>&8m<=gF@7 z=S=dym{@U4WR2x7{*MAxo4&{Hz8-q7f?06+i?@99kA%rHm{rewRX5f#V2La6AQrJz)7vg)&v5PBe}k#Z>;jMcEJsbPH(FN~L@K40 zNIpupOu93D|CaXsp_NDj3_qO9 zd1Jl0&F#b0Xw$G4H;(Upa`0An-qnQ?-u_ojK(1`QzYXSIh>vUc<&#r&u(fHvi|*EbVTgT z#c4jD1pPdguQ25C@&543GvL^hl`4~!^~0A$9SLumbgn+mkFlJ;+%=ggALu@ZLT(IC+ZCWBJth4lBpTa%6lWCi|Pn zC8nb$0ZWAA^{*WIf5gnSbzZoLS*e`&@7_ZVxjcLH<}!)oYo&x_wIp8-cbFK-BUtm0 zp{hokN2sN<=Y;u$5dNykkI(jAxgcpEb&?^*^YzY`Pvn)RCaA013QKHz=Du-eoXVsK z@x`u_`VY>q=5n#xsc$sl_1;swXX8WeOD1UN|K0i{Szk5e-k07#w-1R=5aId8csHQ- zKv4CKIX-cdiVt+oxV1`E=evx?gs1F_gzN$}d4+veGArB>%-ok@I=@dKj>W!c>Yhx^ z(4CcxC*RDluq*!e`QYyR&Nge58B)x)|H{dcxxeux=N5;%aep69(Vr7>=jHV?#~rm2 z7VdHudj9Ok+HnX~j( zu8(wA6I^;QculIwq+^{yKaQ0gDABsUl-EY(X5m-SuFuQguen>2_cQ)hM(*@#-&vL+ zR)Oli&W)L8g&CBee@d3D()s1Uc4c{Gv`Ius^U@OzdbOIq&r4R?A352c>b?Bo4~6Fw ztrs13<@w@#MJjFGjnmutK0FOvckQ#TSf{?b^^Py09I|cmAD+8XIZOB3{2J}Buy_3r zLRvRFSbocoPx86{sy+Das?gP)#)A9><)Po6+zatmSHIaR`S+;H4f#*|v$_J6#Kf<5 zp7_nq{>IJcrOt{%K4S*1O`ca4=Ht9|v30igUrq#}`#Pw3z*MyfES9 zmPPA>mrQYE2;w^saCG&~R6CBJ2QFm2U0EC?8!RyQxI@LXRK>X4En%i-Dz^v*&Q(Zz zX&5hOBi&xtmtJ2vW4#OeyZi52&$@29pVgqOSD2YIdyDMR&!*u{)pFhjrIWLxi>3ZW zB`n$XCSH9_obd|I7174g*LE$J@I1G|qu=Tt1H*#+sbP^j?Z0yODd*as|F@$iwe7;S z1p$WI`(M2+{9)nad0>8`J)h|&D>)X6CeF`Bi_9Eu?_u~MEWFRd5ZENXo6sLF|n|7sa0`AY0) zTho5XEPBlcw+(ZQZ%MYKtccvwq{=Xj$QN-Mt)} z10R3XxKqAw{e{)H9__y0QP41R>&qXksw+0z=KXt|kpHa2Ccw8yfVXCfWy~|-wgt1# zJa}SoAnl{cbD4@g_gExd7Hr?L`lr65yx$eizE=x(t-7^|g*`ZS>MP0QWz0)2yD90u zzasX$F6@B$=?k|nnf%faW^`jXmbBB3QQ&xQ*-73HXB@-cX)N)4BK@?BW4fJZwP0vX z3hQ6>vjTUN4jr>Pbwku|a@V`XYZ<#-#6H;-9Zjfz)b=Xj1n=Gb4sGW+7`#okD;duD zS+l^OJALKx*3GO*J^nf#3?cX27Ic_RZ5DRVU-rX7O{Q@H^W@!TI$tJFxfqu^*Sm3s z^NP<3U9XOZRPX(fcAxc%#^=8*&nMp2zt|_PC*5CT=Ql%D{Lb8sff@@6-|I-~oASK8 z9o4NGxb@+`iTsSK6xyr!srb?>=dY4IWYS(SK6kOBY)q`_uHyf4NpkUnzq=^ zoY5*tXGiSXXQJ^xesh!?{El17|78KsVx_YI!K;%FD$H#AzMhr+am|iJ68Rb8r8mS~ zdQUZ(tUtHVDzrFa%^3xiD@&K|UJ|N2w_(|BC9U2qEX%w0OK`;gVX4@pQa{Tsp92~rpq5aUR?HT_3yyzIT62o@d?oIu_Wp7&t+{y# z8P5&0FT2T}{kMN5b3@>X0G)-o57Z9E@NVSVda>lkb)z3s#6N>jaCKvN_8|^GHleSh-D!*h~vhMsuxGdb>y7DxN1v&-{MTm4`1^rXt(>xqqB$0i2uypkyA`b*Q$^yoGvRmBbU{mKjT4iv=u$Curh zvB>Db&Bs#nPPk56zNgz>ed<}s43E!2Rxi(o)|n@=u8jy^t=zRJ}@n zckQOJ#ewXFzb{-~dY4J#g0DNOf3;WTteD7SX}FVjLD{tWA2qjkYccA|^hn#c zUa)&3Hs?)uL$d?N>+O>dD^7d)uA;~IbK83{*W?=w3=c1J&XenOsyEfTVb#?}f=ehLg|ib?n>2v?J#!&qK$ci)R*_Ro{ATskv3O7Tz#kaCFa^>c!c|>}DSgd{r#0`*ZTajH`A*@n?74K9$Q7 z+!x2a?q|#zk^OUgq`xw!OVzI`zN5BtyUXcUDHjBCOXU5Y6&Bpt9de;RerI*+^22@3 zrxxlL+=>uiQ*_Ydmt@Nqoff%*o>hB$ug(rH4w_&Z%dByJf0bFuQstXsrrvg+Qas%E z-{DkG&g66cYcqA;-&tzg6*IH8zTSIkcGo1{$yytPR!E3fN!*QIac@~s&zcX%D{sG3 z%-v~T+vn3MwW&>tMuwPBf1T*d=RXcQb69L7lI;WM_($xl>-xNN z;{)^6(!L=@t&DZ2Ymc6^mOJ@+efO8o8jiE(z5LB=dW|8&W8A84j7H(K-yzWIy|6exqO}|QJ9m{PfTYW_Hg(F{_yhXHl z`xoUO|27w_>k*mx`;mmQKxMg0>i)}|U#5KJO|bnaW>%@w$(^V5Y*j;t@TxOQa{t|R zotLBVeCEnJJ=VQ8F8uFn()aH-f1}j-_HQGf|8D(%kvk5netDF{s`QV2V)D)R2eV2w zwwO=7oN$l#=<%zk*DA3kh>CAmptm>Z#Z?6>=bqhfvyyMGQ0_b>UO#iz{u3%Ic9>>t z_F5qsC$8YFGk@j2z*A@TJkDA%cSigA#Fscy74roYipPHt!Ph3 zl@;0c;-zqN{6&WF#k))Erh3VI&|I~5(|+}}%!g*N7rfg0DxD*|Xvgx?{t=wxq{E>xE z{_Iia2;IK>e9T?-0|x%pezU)?OnSie)Lvb`Cc@TntH}JeGxP2*8c{e|yDKJD*U->;G%sKU9}L zIra7@@veiew|}L`els{JGmY=Js>F#cZfe?btJ@bXs8HYN)_f-Nf`F6kHba z{fzz6W}{(oHEw6R@x+kFstuZ}^WSZ2-R5J?EmxxDdfC=A>tXbUDO^t#<{5>adncx( zYI*tcl4k-JkDSrTRdCw3z3@cTsj}ATQ&~5g`>8Yjey)-|y?c5=+=@**Y8J+1shv6S zuSoSyiU0K3ycI4rX*&}qy7)N+PwSR`vMz2_#>Pg2_dDi3`M)hDz*aMB4cFYaJj?sn zz5H}CX<#zdVU&@+sAJ?zyvXeOq#Tt+RNZ=mWjQ?RkwS6CU5; zj&J+ay>YQGYn=Cs^V?3HZ>mu^yYyCl=h%WxzC5!gFk*fQT;QWcka<+KW<-|de17>bw_g2s?s&LwDwNx zmWi)-co^`LQ8Rz?o}1>=S!&+4SBe`;yqpjjpR5w2IoYvPuEap#THsOjzFjMtye*a; zh}v~-k>Y12x7f(?+MLb$xArQ}XqIk)vTW`%StccGq zz25j@y5}_OY2o}EJ|8^evrH(gDbGlA`CM=LyRTY}K1909xEZdl`+ko>bNN@Mj6!!F zrRiy}K31@->{%+=IOpX+ZletdQEl2i9r zR;sid%Db@aV8tE1Rms{{?$}=DPUCsP@gsHq!MdJha{s)Ne+KMjzjuGaq0Wu_POXlJ zP3P563qH*MC3ev+m*?6q?B6NYE&up&Vbo2vToK-?KX+$*t{1X+zJE&HBL1+3lu3+! z#@ikC{1(i9KSTSI)KL%p&)mnk-YnWDXsgv~Xi+97xx(?SNcT&7kliKM1hxIs7tUZkf6#K4rG)IounnsPk7VD;l5o0q_|M!27h2Ue zNILCpecI>~nH0*oGVOMbc>i5{J=?1 zB)7$}wFEu6B6jD_#TeT=PHP{=|8C*&Jf_~X`Tu+c&IF;?kv=Pbbmu)yDb9b8lD?;J zPs=lf#G;pHYmDF6A4#0^azo@fqui{I$3EU&#i}9Cb7i{FloD>9;-wpo?u>c$D9xbW z@kvJLlzRsMe|xK4TxeZY;uW)KlAcStLH<`3et%Q-`OPm^PT8o&Ec!K3Wp3|@RjRgI zlqO7>AC>rYX^rXj-0NjJ@9!s{{-STTuzA;$O^gZWTmp^>EWVugHNEl0${C+d=)Ru1 z%rtuEpEPr)b$qT}ix=~ieG$#S>#57pXVsr(#QQIZbK&jVvjx~4jE+`&Z+h`X(V}x& z(1hY$rHeN2jIoy4ygu_9-;GPA6%VH;ZMgU8j>rz3umuOVE??-ISl-QGY{Q|x^Y^x` z(FuY6$3LyT$8b(~2go9Z5{{;+7HwXTiv7LT)ntuY~s(-&{ykG*|Ea=xq8Hrr!UlTyAcV+gg|_RB_U zQ%hXEjF5sKxB2EnouvkD5gVF>od2Xo{a(f*c-z-N_fV#y_`2YTEvpvRi7GPd#TmS> z6=b|}wSX_y+eJrW(*AAcc{l%AmXvpH;`Y8|<6%4@_1%UYFXuLWQk^_Y>0RiVgBAOw z^%veewn;WYfmv)Zmsx6m=#!xTiSi9yHxo2 zZ{KgrkI#MHU1o0)oun|yA&BFsgLwNImiJX#!*rJ#{!f$FFqdXa-eWm=g)oo1<6Q}6 zwGM@qM^-3bd$#?j>>0so7E`%Gvz7HL!*{K-xRTuW**?SS;HoL-7oI-Q_4?fW+IzLN zp9E%RYS5@__s0XD6H4152a~KQsnN#`w)R^xpBM^ zb5#lHdCe+(?&cuisBYF9;hWw3&iK#Q^e5`SvqXO%UpDbegW`U##J2Qlr>p0kFyTEh zai8PjUdDIqhx*nTB?bA19!lx!l_|PDsU^5|rup;#-OVxZaZ7|gvYZRvaAJ*h?~&W8 zzb*<}G3>T5;N8BhZJX($*HNc>4XRRI(>uCcyk8Zs+#qOt^3jYX$Jm0rr|YmDd$aug z6c^cfTs@s9Uq942S>t{$`{(}qFSlN@Q`5L#R%N7T+Sz1exVwkvoLjT`doEX|xo=lL zyT70Nar@k_m8G{^&#z#&mwEM-&*k+Qu}wFMbVChpO)|_h)A{fBb)NL+F8i~Km$dKZ zE-GDKTk-8&#hN(*-Y@H1w*TX2nRRH^?yDE&3?kbb{>?3y z#CdFPa>hlw$xQNGB7P<=%Uf`^?Q^Y1pSq?*T`VuNaWQFlcOvrU?{6DrI!|&hNZ9Xn z=l;sMuO{#W?dW9r+J0pDL8koY6N4SEZ(=`LSnK*l>dZHbDfyTGz7#2qiEqq!v{G=N zgxAhp?-Xk%36-&Ce@pWG&?b|?tLT>AdvMuDi{jj8rD}|oz3Xf)roHRYKXOrfr{X=a zAhjFbzb-{S_`a^>|FzCIj`>#)-P|}K=jWkiobE=eV~*+k$a!(C!&srcRp=Xbej_VJv)qr{wd# z=_=QaRAqL_7e4Sc`MoT&!KlLi-Cduz>=W+%&+1)QX_clcsbcW4bcMLkvcIqOZzV`9 z+O~A(vunv;K0N;B%xu#ats~pDe)j!9r+YPL&n&w?Sti7p?a?2(W4yT;JAJgWg%^SY2A89d1uvI;T!$- zvx?t1z?gixL^HWM-+$_fz9(1yZE4Qf!F|qT5|2aW*Pari+|0KA$znQ_yVZH#yqAjg z`fmM3;pgpbZaxYdR^3e(KBE2bcFmuBpIq@gt+jF$y(tc6d@D6mCGxf>P2KrIMnLg% zR7>7VtGKPzzj^Q1wsdzho|d^J*w*-C^}$PxJiXgqMN8x} z7Txy~wp(cR&+XXqsI@j{-n=$g z+p+d$ccYt=JbFDOpD>ziyZzhByDDvp_s)Zlu5F()og=nP!c;RRzt;D%JU7$hdV8Lm zfocCH?0#_lrHD|B&9}#!E{il>SkbjEdEKwy`z{z>V+eh4N2no`$GK4Sor7`d9=@ZE zHRh{T^h}fg#W%}!8Le?WdP(?-JRiG3*WupyjZwcI`du!R6v*j#6y?_ZQS18lGL^YU znCE)moXTIur1ZP+l8>nO4>$YFWgS6lt=hxREj;w($43+E0>7yWA4`^>>*3h@eZQiw z_@!&}mha13FL*3_^W4;u-%3CH_-v#qv`-$iyZoX!*^!z5X?tQ^xw4i)(>0c60zuV=J98zB=v-6+%B)# zvU{0BX56O6j0_R+=NV&Z>i2JjNq9lfAWjN{n&5O44nmgK1wI$@vVNPD7-*fS$4&m)63*@ zbsKf0*K%CT5l(JPOM2L2nmO-4Pk7E>)g{G8Ox`tRai5Ld5NjjU|$yo{ENGp?EIm zxFwSJcD*dBd8!bRG_|B^XHh}IyZ1g4779u25jFZUD%?veJRAEjmAttmz!amgD(+Rv z%x$6DH&onv!z-ieefiJt4V5z|{5*7kt!Z^1@9zq}Y5glEf8X{Zb%n#Ky9Je3UoEbD zA|-XRu~D}u(I?jOmzL#<#gAsJ5BMND?Q&=2>L2s?wW7*nX%Z&Zy)xpny_boC`@0GC&_<7+#?_Qy<{(0hzmzSUbFIGV6$bj{g;` z@gm#y)*EgpJGCf$Lr>nCD9)L`#V%e6?~q;bOjk$Y$H|UF_4)h_<|p#a{!g#8i=XgX zcKti0iwQjHt81F&-+u0L1}o;@edasgdR6rvW4^XRXtls+zIz?VSqk_j zCq1;@@=^6(VRGZmME+y%^Fme?nfXi;4(O_XRTZ9=~^fK4$nS0GrM2^$r}d+15x{RR^FWVR!6!0QnL#$Kj-)6qVXBQ#lI^z z2mHMDd4r33_wF+aN2Fs7O*(xq$z?ta>Hb=N=)3!KZqJVN@Tw)uTMWLuyyLdr@2W(4 zb&%$|nVYPqRi3^1ODS>Q-FaJ>_r^)aYTG3+>-{)Y<;H!l?}&5a!B)`}qnc&WRZ)=@ z=N>-y)@9jYVVNTABq{&Ko9%k|IVaOt$?c0S2h^X*_VaZ-!hC0%nYf$ChL0D{KU`2a z^GmQJvs6Ue-Z0xf#nVSLgr=m{|6)JsJagyMQuo6O7p0zx7QB#re{j)5<*9Y+OZ6@v z&y;vv&Uux0_Ia(?wfEP(I^CP2#qi>UebbxvR|gx}nm=c4lu!Qu^1r!JR++05qvVs# zPY=xfHJK-S=J7Ol*U9C7OF7x5wk+=s7Jth9b!mjzl9t?y$LGAd@I&gahU7Q%{YO{1 zT;^u!yIu9{fb(am=wI=hxph-Jr$&BzXdu*Hu$#@+VdeTNIj`L{DL>Y|Z4FqJ84_zS zp=^12`y`vQCx7X#5_e5scuOym+3A&j%p|*incWuEuvC4a8FTQg_Nv!#qauJ*Ni zrtq~T?&MT+syq3}TrsD-`?T+N0cEbGMyn^K3ct&YRlo4^M`eioy@U5|-#6X&(%@6L z6PGc^%1Pf-;up8G+P0bgbrGs*sq8s)NY&t>9f$T6n-j~KRj-{)j4`|^eOW}ELGi@T z&KK8hjUOx#VxB1>yxmsr$<)2S3j{4=m?ZVK3g7x%`)3I75uZU57cLK)pKy&uX-eR@ap;rqTJJ}kC;5IJvLcH%jE46?ei58W_vaVuUK&`*XC)_ z%Y~MKEXV2q-nkWz zH*zugzWO*xphffaY2SmTTRbf+#I?)KcQe{nTzOuidz( z&+XfU<$^~l(>Rx!l+Nf%sQ>!hp4ql^@s4-0>*X*0Ke6rJ-Jh?X-DFzyu5ZpQL$@RB z-7c&;#@c7@JyL4mPe1Ii=!P@fWx*+~3OjE3E?u*J@A9Rh=Eo0~>MZ~M=ltnfzk2(v zg8LJTcTd>Ns=j4u!%gdrzH0y7ZE6Ewb}e`(eL3xj;>n$!uV&1vG z@p{HH8_Sg5uBO7{O}r&+$<53=Ci!ij_x#J%SA3E!=lqx5KGJoqMrQJct_czwbXQtP z*c29>fADyxdYYR!inLN)31Ss4iUc+f0sQj+EV`fs|q{^=J*tvyz!{n*rvCzg8n|Kg}}+2F>$ z-Da<@);g;-fm;jw3Ft!`sZ6n+59K7-f=PpU+M4j zxcBl>>U_m^2GbC!M@mQ4Gm3Vu4Xm3sNi-@;aHnu^X8{KT~g(QS(i& zir=EQ!RGNa=K7WU<*wc`sb_un)-54b>iLIk=KgDLJ&!)0eq6qxUgUOTO6IdSnp-;_ z#LqS_VU4}#e(K5-+q!Q7SM*)j*biR%IA_Q8GjFGVe#89w!GFGuP0z&g{zetdWI1S=RaoF}q8 zug8h~aqnImBjvtI*Zi3!@ydNh+82|~{ZV;XlOeS)HIC1{a?RCgUK=k2eEGBOVyx2U zI}36*J{8=&M*HO^QU5s#y5(KAT_%Dx{f1e~j9gP(4HH!M-hA2D$5`6oS#56xRk&V0+V;!6;L%C@TiLT}C2FjMRqu1u`6_fL8rN?A zeXe1V&adbBq5*=sxfb3({?^X=Ecrp-z~p?|d?vqZo6fEIvA{FkbAiLny#gsN({31B-^j_nm(6{ei+bz41cK7X3){~LF z0xq*lMeVNZescPH|FE?EFGY93Z_AAqJT4G5YVEFX`f~8pTE^(PCCam6nPq-`5Q(he z4>j5!>dmYkZ+L#4q;k5^_LFDx7q9M}Dex`&+;7=Ot6JX8`F60s!f{8y{J*+oe+243 z#Pfwkn{+0viOkaTv1D0k)$rk=arD*Ww{-Qs$Ne~aD02HAk)Y1+vEr3c_k}y0=a+q# zi8_+h7IymMp;x<~cBU|1tL}-DSbp9&GBnJFtu2N%hojkX?)<#zx8E0)MxN04_5bVN z+VqW1s)ui1OA{3CQDph|HGj(0`GVVcg-cHuv>yH1ao6$l=5!Zd(bcc{?%Z9cU2%Np zq1kVqUUy=z+p)h+G^Li=aX6Hqg0JAzV9uMT4TzR_bnlhQk7i8TT) zfpKK@3su_Z;sAvXPBNdOV0QyE@F`Kv-Z#Plo`{G7&oUFXiWPkyfwt% zTj_b~0n1~N>X*(KxjakyAv4Kvd4>CpYEQOrG2gQ1Uwm&e^V!+b^^P}~?U^hpj<@=0 z=T*FpvR*UmEZgOCSqtXAeR}eRwRFMMZ|nW`pJ^zKSG=||_4STTL8p#bTCVizuTEii z|L!OEVxswH_Kw%~Su6MKa^bK3)A7EFe}~v!b>X;A%_)-+ba$!o?3nS;H2*zYi;lCXq#=Zr{#Xpq!r7>n09ZlS3R*gQQ>gG#GjEt z;ZM9y9XYmJaJl&}$t6*@7wwLe{JX@>@temKjlIurES?}f$5Eu{ys_*t6{e(_RSMCs z_cqXr%{4lZL`EyS(CW>jdFRfv5Tv529 zWo4uHQ|-mk=Ciklgs%O@^e_5hxmUgHv#cX;J2+QF7jAc`ZEWTB);zWNa>gPnHG{Vc zgl%6x-`2UpLby-l?k)b=?MCl7!|eF6a&^48cS8>y@%xPO%H;^EJ=sq=)2f{Ioo$CSu3BqFV;MQ?Jc^ z*x;X=^S2;#<)z1w4?;^aPd~B=PV%0zsbZ#lpU&5R*`@Zv%2O?V4tb zR=)S;tG_6H-n-|8#l6*L!IIZ13OKg9{;B1j+Rv`-|6xv6bpAe-^-%&hvW{NQa!;Md z`sTovTRvgN!Fge&`I$eOllg7eWnFtK+!9uw7ygjJ*jmR`=X%D@hiUN}w3%jcA1IJG zX*_j#!-3t)F4pAD3;X%mVsH1)(3nd9^+n&`U6^gKWW|@?tyiQE9w}KT(a)tg{qv>1 z{4KLTX|DOy`BSJ=ZR_$YQcs-r_h(G32!56xH;qe&n?KEDYToC%%*Cgx)_i%R^Wb03 zsV_36FXWyGR!-O6?Xc2DtEMLEirn2C_LV#CN1pxGx*%1jKxyiR$*bGnwpzNfdhb@V zE&h0jdGYt+(oMeXC)67}X1lSKeVBRUVVSVa;?IxRD?WKJ%;7dnZ4e1h*Vrz3L&I~` z?ssk*CnnBR^jUSAQKcjO_eE7Li`D8@w#rKPPq8h|&KI4y_1FiSDGrgdwp?3MRdDZH zbCkpRw@q*F&6xMD!q({1+M9tproDQ*xFF=a;!74!xf3}}OFW&P@0)&T&Y@KoEc8P5 zJm%%h`u_BAwD!9#w)PDR=O1^y$Gc+p@tp?khTV4c+kX68T@v8=c!R|<$!D$)1PDvL`qUi=l`q3KglQT2KG ztj^U1YT8_R5)c2dNy|=}m{Yh>V@W%cBui+RYp-W~EUW%fDGEOZokplK;zaPaD?Pj~EYOn>kC&*syPt0m93 z6{WQ}oS7{e!hI_&&ijXI|Finl?%ebC*X}JZ3k~MsGcmlZF=z9Ym67~b=CTn zY|~l2y6)D^;~FcLm(11BOuw-)ve`5?ZK0T5*6%|ho}8ZHl8!r0`x(ER88BH%(|K*) z*@f3;g(fRc%uZ#~(N)oAz2^I(SM#`tXrOV<_o-@}2JZLQhS)Q8>{{F6RxBvCB_EIJ$5y*_ zF4bMyWjHO~+keiBX;YEsecoXz#1B-S^?F#LQ?j^x_Hj&DXOdI*FJwCVII=wID z_k_er-7TfOPfD$3bE-EAM>|iRoUm+{uFdVZ4vstaPny__<;~}Y*i5?mb$->o;OZjA z9ScfV@7U8CaMt+4cJ-d!c@Le}&DF6yWNwz!Tx&RI?)JN0_t>grmG@iRbmQE0%kY0A z1KVnchrieHJPiA>=BtK_PoSdV^HXfPo7umxbGN_WRC4LXgwS2|DbuO1&Cn#Uv#33U( zyR37Lv+;Sq3448CZcI9`VzyD_fm8>Pn;Y#i)4KCit*>}x`Tv?)b3@$jAXma`xrfV( zD|oNV%rp^kGJN{$x0h@GrW4xFr)TIUA6sGGqHF(d@62OT30BWfZ~x_SD5?C1L*u*; z-uX5E6<+lI?2}cGW0g}9{`*%##Gr4^dA$W~{afDsnGz?_>gRdFB8AIwe^FU2a$TW@=B@@-OYiSu6cf5x6UzwTOoWb5$Ib^Te` zwPvr$*}|tgIqa;bwPuUgJ&hF2&}E)~&*8m}t8+lZYQOB#8?I7TfwETmT-6)ZKP{hY z#kTFL?4CcYIX*5+*4<9I^oF(Z;4Fa_UvRZk548Qw|Vh$nC_gY;0eDkNsjaku?E*zEgpBitfR2cisO-cTNhTf6rUJh%0mTNyGe8F$)_77)v}{`BTa+B+Rc2ATL&WaYmi28}Oq$ClE>Uavq5Ao<)BY^WfAw3|M)z-G z_RKwXC*B}lU1!OOXKPDmT@|cs^q=!+N4it~PT~FW54<~ftLlDT5LYU`+VJ0$nfGf7 zt@pDOCa-;BzNsQa>T3CyC%IAu`EptUllIL$(D_DF`0IQz=cK49FVC*tIOAF1ImL^O zYrNhbP}|ph!u;xlh|@;7dyS5C-eu=Dcx9Xtl>YK(>gq{VtkcBjzT~OwICG~txb=GQ zwV$`WAE>eB^yyS8oOzY^Fh{_t>B-q2&u0sD{i(h?-#O>>EeQ|ZNS~Q;n_L(#NsFZ> zZi(@d%3RQQ)OU5MTzt<}`;_QUYq#88Ha{dz+|=%vVQ+`#t}m-1mYHa-ZsDJF!)&#N z182a}G?DmEpLs8?XbI4Dntg8FtqI4YTibVr^6LA(Vt&5-=I*)ad)R-ToIhiyZN)Cd zo-KNM1sulj=id&TcaK4J&K)tI?p;5Qy)Dbv{(P2w-K@2txyyR))STy8X+smb%fy#I&6mzw=G659L2+u!bNDePT3>$7RS z@R7H%Ml-l`#TMF4_FWMv7_JkU!7-I#WB%k-;E5V$`hWe zxEr@bD( zohAm=GcQ!}n0ePs^ymEHeJ5zGNA(vfi8dq1b>=q{_%j%s2E=%_Utc&0CLeipFQFgP$+VuDC@3|yY%6MM< zjgeqYLnHSo8B3Y9%|e>Dcl#9c+-%*e5O?VB%O6%t9OvBpd&BTG(|s}FABMf&tIM}< z(sMbjBzyU9n}PwCxS6iAq(-pLL@u|}M*?HNJF;h5nH{v>{g#n=E6>)BO&6m7xQABx zlz%C@@O4sdQ{@L6)0D2?zS|14yS;xD?0uW05pyKcGV!tEGT+Q4h8^=|74P@EcL=rL zdKIaCNi>?h%52i40w>wE$DHrny7yoPm&`WhqY^w7UT5##+b?}2QlV?}W0$1@v-O$I z>V3AkqsGwjIPFm9k(?aU&~2@4#l=zDPj0lX+OnYR%#IIjQ~%`LSY^?D|BFhsE^~XI zjKcj}Gn97tvtH--F-tkJt<55v;d=H?&TwgW0hbd-cPBC&bVvzZ9U&lay>O?I?GASB zz2~p`ZBX!CmhryQ_=IA|ET6AN`&S?8|9bG6*DI}?kLDj;Ch|Qkr(-|&>VqGfKiGYc zR+bW(Dfh=a%sQ9#$;TV(>&lsb{4j~!%x!0|R*pg5a#OnB*+t<|A)#&JZ`-Fwx%F;P zu(tYlD4QuMT6uBdz79TTvxdfLS^pFN9a2+x)-}hHgF|al#nwB$&y*E2v!zx>DWq-9 zw|iCgYQ`&`F6AqeysdYg@t!AfM|8r=n-i|Ct1_(z4iAVi)_z2%r*6k+pAeNI(0>F7-F|vz5eRT%1Mgbd)hi= zbWdzqHshVv`xVLRItoe$;)03=e@s+g+I+UHE5!5rk&0a(SdO1)4G67NPrZ0CZi;<_ zP3>$>9g8*R>~i)>O!2+*`pTMntM_C&zNyIya}j6JPzow|^Zxw5Ji&X)=Xz6)Mnz2( z$~@|b>V!w#$Fmtu*UyhqRFR${3Cb z4qwzu_GYecu#lO3*Qudbisk(Kd2SO-rnRhR?`WRZIW<_9GX_%XU?ARM@%@|?wC*}&m}=wL0QY+ zJ{KcxGZl`mR8~12ofL5`ppuLAD2LRA2HWp?)4%sV*wUf(Auug2@=}K3@!lL>)dkxf zS*tQ#H=R55;od{_1?NnY8*e2Ug|j<#U;6fMszPeN&d)$0u1oiWKQnM`)Gk`{z45Ze z*TtT}!RN%yUA4W$_HTKquX1c*)HIkZ}|s@+If?|O8B@qX4bdF$2j(`Z!JH&WUtZKjdQXgX;DX-;x-k6X9)b`R}a`=WoC7vfJ}?+IGJ3 z#<%l2`t%jA`iaPyKik}DFyVE^>6`2SdsVYauzhgxxnY-bM6>Selt~j#8Y-!HEelFf zc>7Lsv6zk8u76wYSDsk&cAr;;PtwEM+K?Bi^BbM@4JtjOYY(`_?+<;inwb83$`6a{ zo;*zR=cSyQV01mH_dL_(GH1i&zcMEG^}a0LvS_uT|E6g-bj+7#%kFygdV-e;>&Mi* zvQCL8&&01yN1T@~ynJoF@y|M~`KK=Qe|P5z@XQaIU=?;XbI1Q_-;SJ^-Sc{b-l=y= z(rXS~Txrqo+~xE5|7UmR70Zu*Q%laMRb0}x_sr{_B<{^O58c?XAlt{|e42+lcU0#b zPgA~M_y10v)BJ0FI!9!%3(rcOJLeVWPk!!MnWsJJ`@+yx38q|uGu3Kh`o~xn$G-UY zIzmg{ImzN>7;l-@Hoa+=?npX!zDw39pZo7k`6Q*X%TK4qyuO|4zw+3^0|hyknKd?P zf8<)3aQ?bN>~z*^U60l+H}2J&aOLy-V|#o5RQq^({Ax;_e5AUHTf}dYqK${;og&^$ z)le;+#}_X1eJ)U$DDQ9fJ?_4F4coWHDFMYrQMay6RxP*nU}^l=7@dCZ3Abf(y&s>% zg44&j*cx94Gi*swy`Jp!R-*mDcd?~K_4OaqE-im~KHxz1`ish`Q-rhM9rRh=8L zb_?kLW!|BG@#oz;nfnhde7(B1_g(5=J^zBO-(N^PUAHOV4brDdpE*F~{?>q}~flB+fQoa{bK9H61%7w>V}stLzgn-8o6~Sm*Nk`d4qCb*H(= zt$*3OVs+cG)Yku;B9GKG9OkayZ_xRZZ}r4A%Vm!EYF^+z8M^M`%rkqqVj@*1SCsQ# z3Jto#HBl}>Iq0)cmj6js3l_QSikDNrWw6RFJQ>rlleZv+CEC?=|E#OaetFMIPc35l zyYIT?ap_NUm053OJ=+wJd||C6XGh`1WwFmcsZZJ%&G)z`?R}2Ia^A)z@{RUeCFl0f z*}$;t7~4NJd*R&d*Z}oUQ_d#8lkGps{PW$G9mZ8JKJ)iaGJUuAzVCmnr*BmPSYJwd z-@RrvS82iT!vFi(Mm_EXkoBjx&2Hw*(#D+Db* zIP>H`{@t%i^uB$s*xVqy{QmNVM(MNDMdvmAfAvlL?q)Azhs`cK8z0YY7iPWodspJ3 z>e}Sj=F4mi*ZRJiY3nxs MRZ2b2uuU~RGJj+!MQ(d&Iy9;8v~0K- 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 z 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

cI;X|29}i!->!9D& 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 literal 0 HcmV?d00001 diff --git a/procmon/translations/en.json b/procmon/translations/en.json new file mode 100644 index 0000000..06793c6 --- /dev/null +++ b/procmon/translations/en.json @@ -0,0 +1,59 @@ +{ + "settings": { + "refresh_interval": { + "label": "Refresh interval (ms)", + "description": "How often the process list is resampled." + }, + "sort_by": { + "label": "Default sort column", + "description": "Column the process table is sorted by when the panel opens.", + "options": { + "cpu": "CPU", + "mem": "Memory", + "pid": "PID", + "cmd": "Command" + } + }, + "kill_command": { + "label": "Kill command", + "description": "Command run against a selected PID (the PID is appended). Empty uses kill -TERM." + }, + "show_count": { + "label": "Show process count", + "description": "Append the number of processes to the bar widget text." + }, + "glyph": { + "label": "Widget icon", + "description": "Glyph shown in the bar widget." + }, + "icon_only": { + "label": "Icon only", + "description": "Show only a CPU icon in the bar widget; details move to the tooltip." + } + }, + "widget": { + "tooltip": "CPU {cpu}% RAM {ram}% · {n} processes" + }, + "panel": { + "title": "Processes", + "refreshed_at": "Updated {time}", + "refresh": "Refresh", + "load": "Load", + "search_placeholder": "Filter by name, user or PID", + "sort_asc": "asc", + "sort_desc": "desc", + "col": { + "pid": "PID", + "cpu": "CPU%", + "cpu_raw": "CPU", + "mem": "MEM%", + "mem_raw": "MEM", + "rss": "RSS", + "stat": "S", + "cmd": "COMMAND" + }, + "count": "{n} processes", + "no_processes": "No matching processes", + "kill_tip": "Kill {pid}" + } +} diff --git a/procmon/widget.luau b/procmon/widget.luau new file mode 100644 index 0000000..3340d56 --- /dev/null +++ b/procmon/widget.luau @@ -0,0 +1,56 @@ +--!nonstrict +-- Process Monitor — bar widget. Full mode shows "CPU% RAM% · count" text; icon-only +-- mode shows just a CPU icon colored by load, with the details in the tooltip so the +-- bar stays tiny. Clicking opens the panel. + +local function cfg(key) + return noctalia.getConfig(key) +end + +local function st(key) + return noctalia.state.get(key) +end + +local function tr(key, subst) + return noctalia.tr(key, subst) +end + +function update() + local stats = st("stats") + local cpu = stats and stats.cpu and stats.cpu.usagePercent + local ram = stats and stats.ram and stats.ram.usagePercent + local count = #(st("procs") or {}) + local cpuInt = math.floor(cpu or 0) + local tooltip = tr("widget.tooltip", { cpu = cpuInt, ram = math.floor(ram or 0), n = count }) + + barWidget.setGlyph(cfg("glyph") or "cpu") + barWidget.setTooltip(tooltip) + + if cfg("icon_only") then + barWidget.setText("") + -- color the icon by CPU load so a glance reads the machine state + if cpuInt >= 80 then + barWidget.setGlyphColor("error") + elseif cpuInt >= 50 then + barWidget.setGlyphColor("warning") + else + barWidget.setGlyphColor("on_surface") + end + return + end + + barWidget.setGlyphColor("on_surface") + local text = string.format("CPU %d%% RAM %d%%", cpuInt, math.floor(ram or 0)) + if cfg("show_count") then + text = text .. " · " .. count + end + barWidget.setText(text) +end + +noctalia.state.watch("stats", update) +noctalia.state.watch("procs", update) +noctalia.setUpdateInterval(1000) + +function onClick() + noctalia.togglePanel("weinguyen/procmon:panel") +end