Dev/mihomo control (#339)

* Add Mihomo Control plugin

Monitor and control a Mihomo (Clash Meta) external controller from the
bar: live traffic graph, proxy mode, proxy-group management with latency
tests, and core restart. Works locally (127.0.0.1) or remotely via
host/port/secret settings.

* Add Chinese (Simplified) translation

* Add test-all latency button

Adds a Test all button next to the Proxy groups heading that runs latency
tests on every group sequentially, with a single summary notification.

* Unify traffic and proxy groups section styles

Give both sections the same card look: muted medium-weight heading on the
left, secondary info on the right, and proxy-group cards restyled as
bordered sub-cards inside the section.

* Polish proxy group cards

Show the selected proxy next to the bold group name (ECS > ecs-proxy),
move the expand chevron to the type/members row where the list opens,
and drop the list glyph from the card header.

* Decouple heights of paired group cards

Keep each card in a two-column row at its natural height instead of
stretching the neighbor when one is expanded.

* Use thumbnail generator to generate thumbnail

* mihomo-control: theme-aware panel colors

Use theme roles for traffic and latency colors so they stay readable in
light mode, and keep the status chip's green/amber/red identity with
darker light-mode shades.
This commit is contained in:
MA Dingjie
2026-08-12 09:34:17 -04:00
committed by GitHub
parent 86e04099bc
commit 222fec4875
13 changed files with 1601 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
--!nonstrict
-- Mihomo Control — bar widget.
--
-- Shows live download/upload rates from the service's streamed /traffic data.
-- Left click toggles the control panel; the tooltip holds the connection
-- status, mode, totals, connection count and the current selection of every
-- proxy group.
local PLUGIN_ID = "mdj2812/mihomo-control"
local function format_rate(bytes_per_second)
local value = tonumber(bytes_per_second) or 0
if value >= 1024 * 1024 * 1024 then
return string.format("%.2f GB/s", value / (1024 * 1024 * 1024))
end
if value >= 1024 * 1024 then
return string.format("%.1f MB/s", value / (1024 * 1024))
end
if value >= 1024 then
return string.format("%.1f KB/s", value / 1024)
end
return string.format("%d B/s", value)
end
local function render()
local conn = noctalia.state.get("mihomo.connection") or {}
local config = noctalia.state.get("mihomo.config") or {}
local traffic = noctalia.state.get("mihomo.traffic") or {}
local connections = noctalia.state.get("mihomo.connections") or {}
local groups = noctalia.state.get("mihomo.groups") or {}
local online = conn.status == "online"
local mode = noctalia.tr("panel.mode_" .. (config.mode or "rule"))
local icon_path = "icon-offline.png"
if online then
icon_path = "icon-online.png"
elseif conn.status == "connecting" then
icon_path = "icon-connecting.png"
end
local container = barWidget.isVertical() and ui.column or ui.row
barWidget.render(container({ gap = 6, align = "center" }, {
ui.image({ path = icon_path, width = 16, height = 16 }),
ui.label({ text = mode, fontSize = 12, color = "on_surface", fontWeight = "medium" }),
}))
local status_value = "Offline"
if online then
local version = tostring(conn.version or "")
status_value = version ~= ""
and noctalia.tr("widget.status_online_with_version", { version = version })
or noctalia.tr("widget.status_online_plain")
elseif conn.status == "connecting" then
status_value = noctalia.tr("widget.status_connecting")
elseif tostring(conn.error or "") ~= "" then
status_value = noctalia.tr("widget.status_offline_with_error", { error = conn.error })
else
status_value = noctalia.tr("widget.status_offline_plain")
end
local tooltip = {
{ key = noctalia.tr("widget.status"), value = status_value },
{ key = noctalia.tr("widget.mode"), value = noctalia.tr("panel.mode_" .. (config.mode or "rule")) },
{ key = noctalia.tr("widget.down"), value = `▼ {format_rate(traffic.down)}` },
{ key = noctalia.tr("widget.up"), value = `▲ {format_rate(traffic.up)}` },
{ key = noctalia.tr("widget.connections"), value = tostring(connections.count or 0) },
}
for _, group in ipairs(type(groups) == "table" and groups or {}) do
if #tooltip < 14 then
table.insert(tooltip, {
key = tostring(group.name or ""),
value = tostring(group.now or "—"),
})
end
end
barWidget.setTooltip(tooltip)
end
for _, key in {
"mihomo.connection",
"mihomo.config",
"mihomo.traffic",
"mihomo.connections",
"mihomo.groups",
} do
noctalia.state.watch(key, function(_value)
render()
end)
end
function update()
noctalia.setUpdateInterval(1000)
render()
end
function onClick()
noctalia.togglePanel(PLUGIN_ID .. ":panel")
end
render()