From 6823acaf3d67391ec1b95b6c223e5e09a446b1a4 Mon Sep 17 00:00:00 2001 From: mdj2812 Date: Wed, 12 Aug 2026 16:09:24 +0800 Subject: [PATCH 1/6] Add persistent proxy group reordering Let users arrange group cards with drag-and-drop while preserving their display order across polling and restarts. Co-authored-by: Cursor --- mihomo-control/README.md | 10 ++- mihomo-control/panel.luau | 57 ++++++++++++- mihomo-control/plugin.toml | 2 +- mihomo-control/service.luau | 123 +++++++++++++++++++++++++++- mihomo-control/translations/en.json | 1 + 5 files changed, 184 insertions(+), 9 deletions(-) diff --git a/mihomo-control/README.md b/mihomo-control/README.md index 13fbd34f..09217046 100644 --- a/mihomo-control/README.md +++ b/mihomo-control/README.md @@ -33,7 +33,9 @@ shows an overall latency (the selected member's, or the best tested one) right in its subtitle, visible without expanding. Member lists are collapsed by default; click a group header to expand it. Click a member to select it; the current selection is marked with a dot. Use **Test all** next to the Proxy -groups heading to run a latency test on every group at once. +groups heading to run a latency test on every group at once. Drag a card by +its ☰ grip and drop it into an insertion gap to reorder the groups. The custom +display order survives controller polling and plugin restarts. Add the **Mihomo: Rule** shortcut from Settings → Control Center shortcuts to quickly toggle between rule and global mode. @@ -64,12 +66,14 @@ noctalia msg plugin mdj2812/mihomo-control:service all cmd '{"op":"mode","mode": `refresh` re-polls version, config, connections and proxy groups. `cmd` accepts the same command tables the panel sends (`mode`, `select`, `delay_test`, -`delay_test_all`, `restart`, `close_connections`, `refresh`). +`delay_test_all`, `reorder_group`, `restart`, `close_connections`, `refresh`). ## Notes - The plugin only talks HTTP to the configured external controller. It spawns - no processes, runs no external commands, and writes no files. + no processes and runs no external commands. It writes only the custom + proxy-group display order (group names, never the secret) to its Noctalia + plugin data directory. Reordering does not modify the Mihomo configuration. - The bar widget and card use the Clash cat logo (`icon.png`), the official mascot of the Clash / mihomo project. In the bar widget the cat is tinted by connection status: green online, amber while connecting, red offline; the diff --git a/mihomo-control/panel.luau b/mihomo-control/panel.luau index 955edfa5..35f63a49 100644 --- a/mihomo-control/panel.luau +++ b/mihomo-control/panel.luau @@ -14,6 +14,7 @@ -- and a latency-test button. local MODES = { "rule", "global", "direct" } +local GROUP_DRAG_TYPE = "mihomo-proxy-group" local snapshot = { connection = {}, @@ -292,6 +293,21 @@ local function traffic_card() }) end +-- Thin insertion target before every group card, plus one after the final +-- card. The numeric value is interpreted against the row-major group order. +local function group_insertion_zone(index) + return ui.dropZone({ + key = "group-gap-" .. index, + accepts = { GROUP_DRAG_TYPE }, + value = tostring(index), + onDrop = "onGroupDropped", + height = 3, + radius = 6, + expandOnDrag = true, + hitSlop = 18, + }) +end + local function group_card(group) local name_text = group.name if group.hidden == true then @@ -317,6 +333,22 @@ local function group_card(group) ui.row({ gap = 6, align = "center", justify = "space_between", onClick = function() toggle_group(group.name) end }, { + -- The grip sits two levels below the card, so previewAncestor = 2 makes + -- the drag ghost and lifted layout item represent the complete card. + ui.dragSource({ + key = "group-grip-" .. group.name, + dragType = GROUP_DRAG_TYPE, + payload = group.name, + previewAncestor = 2, + liftFromLayout = true, + width = 20, + height = 20, + align = "center", + justify = "center", + tooltip = noctalia.tr("panel.drag_group"), + }, { + ui.glyph({ name = "menu-2", size = 14, color = "on_surface_variant" }), + }), ui.row({ gap = 4, align = "center", flexGrow = 1 }, name_children), ui.button({ glyph = "activity", @@ -381,6 +413,7 @@ local function group_card(group) end return ui.column({ + key = "group-card-" .. group.name, gap = 8, border = "outline", borderWidth = 1, @@ -390,6 +423,17 @@ local function group_card(group) }, children) end +local function group_slot(group, index) + return ui.column({ + key = "group-slot-" .. group.name, + gap = 3, + flexGrow = 1, + }, { + group_insertion_zone(index), + group_card(group), + }) +end + render = function() local groups = type(snapshot.groups) == "table" and snapshot.groups or {} @@ -417,14 +461,15 @@ render = function() }), } for i = 1, #groups, 2 do - local row_children = { group_card(groups[i]) } + local row_children = { group_slot(groups[i], i) } if groups[i + 1] then - table.insert(row_children, group_card(groups[i + 1])) + table.insert(row_children, group_slot(groups[i + 1], i + 1)) else table.insert(row_children, ui.box({ flexGrow = 1 })) end table.insert(group_children, ui.row({ gap = 10, align = "start" }, row_children)) end + table.insert(group_children, group_insertion_zone(#groups + 1)) table.insert(body, ui.column({ gap = 10, fill = "surface/0.5", radius = 12, padding = 12 }, group_children)) else table.insert(body, ui.column({ gap = 10, fill = "surface/0.5", radius = 12, padding = 12 }, { @@ -460,6 +505,14 @@ render = function() })) end +function onGroupDropped(payload, value) + local insertion_index = tonumber(value) + if type(payload) ~= "string" or payload == "" or insertion_index == nil then + return + end + send_command({ op = "reorder_group", group = payload, index = insertion_index }) +end + for _, key in { "mihomo.connection", "mihomo.config", diff --git a/mihomo-control/plugin.toml b/mihomo-control/plugin.toml index 8d135645..c860db70 100644 --- a/mihomo-control/plugin.toml +++ b/mihomo-control/plugin.toml @@ -8,7 +8,7 @@ id = "mdj2812/mihomo-control" name = "Mihomo Control" -version = "0.1.0" +version = "0.1.1" plugin_api = 9 author = "mdj2812" license = "MIT" diff --git a/mihomo-control/service.luau b/mihomo-control/service.luau index bd0f24b3..e821e017 100644 --- a/mihomo-control/service.luau +++ b/mihomo-control/service.luau @@ -9,6 +9,7 @@ -- { op = "select", group = "", proxy = "" } -- { op = "mode", mode = "rule" | "global" | "direct" } -- { op = "delay_test", group = "" } +-- { op = "reorder_group", group = "", index = } -- { op = "restart" } POST /restart (core re-execs) -- { op = "close_connections" } -- { op = "refresh" } @@ -95,6 +96,84 @@ local state = { delay = {}, } +-- Mihomo's /proxies response is a map and therefore has no stable user-facing +-- group order. Keep the panel's preferred order in the plugin data directory; +-- newly discovered groups that are not in this list are appended alphabetically. +local group_order = {} +local data_path = nil + +local function resolve_data_path() + if data_path ~= nil then + return data_path + end + local dir = noctalia.pluginDataDir() + if dir == nil then + return nil + end + data_path = dir .. "/state.json" + return data_path +end + +local function load_group_order() + local path = resolve_data_path() + if path == nil then + return + end + local raw = noctalia.readFile(path) + if raw == nil then + return + end + local ok, decoded = pcall(noctalia.json.decode, raw) + if not ok or type(decoded) ~= "table" or type(decoded.groupOrder) ~= "table" then + return + end + + local seen = {} + for _, name in ipairs(decoded.groupOrder) do + if type(name) == "string" and name ~= "" and not seen[name] then + seen[name] = true + table.insert(group_order, name) + end + end +end + +local function save_group_order() + local path = resolve_data_path() + if path == nil then + return + end + local ok, encoded = pcall(noctalia.json.encode, { groupOrder = group_order }) + if not ok or type(encoded) ~= "string" then + noctalia.log("[mihomo-control] failed to encode proxy-group order") + return + end + local written, err = noctalia.writeFile(path, encoded) + if not written then + noctalia.log("[mihomo-control] failed to save proxy-group order: " .. tostring(err or "unknown error")) + end +end + +local function apply_group_order(groups) + local rank = {} + for index, name in ipairs(group_order) do + rank[name] = index + end + table.sort(groups, function(a, b) + local a_rank = rank[a.name] + local b_rank = rank[b.name] + if a_rank ~= nil and b_rank ~= nil then + return a_rank < b_rank + end + if a_rank ~= nil then + return true + end + if b_rank ~= nil then + return false + end + return a.name < b.name + end) +end + local function publish(key) noctalia.state.set("mihomo." .. key, state[key]) end @@ -269,9 +348,7 @@ local function poll_proxies() }) end end - table.sort(groups, function(a, b) - return a.name < b.name - end) + apply_group_order(groups) state.groups = groups for _, group in groups do merge_delays(group.name) @@ -439,6 +516,38 @@ local function test_all_groups() next_test() end +-- Move one group to an insertion gap in the currently published order. Gaps +-- are numbered 1..#groups+1; removing an item shifts later gaps down by one. +local function reorder_group(group_name, insertion_index) + local from_index = nil + for index, group in ipairs(state.groups) do + if group.name == group_name then + from_index = index + break + end + end + + local insert_at = tonumber(insertion_index) + if from_index == nil or insert_at == nil then + return + end + + insert_at = math.floor(insert_at) + local moved = table.remove(state.groups, from_index) + if from_index < insert_at then + insert_at -= 1 + end + insert_at = math.max(1, math.min(insert_at, #state.groups + 1)) + table.insert(state.groups, insert_at, moved) + + group_order = {} + for _, group in ipairs(state.groups) do + table.insert(group_order, group.name) + end + save_group_order() + publish("groups") +end + local function handle_command(cmd) local op = cmd.op if op == "refresh" then @@ -490,6 +599,13 @@ local function handle_command(cmd) end return end + if op == "reorder_group" then + local group = tostring(cmd.group or "") + if group ~= "" then + reorder_group(group, cmd.index) + end + return + end if op == "delay_test_all" then test_all_groups() return @@ -597,6 +713,7 @@ end -- ── Init ──────────────────────────────────────────────────────────────────── reload_settings() +load_group_order() state.connection.host = settings.host state.connection.port = settings.port publish("connection") diff --git a/mihomo-control/translations/en.json b/mihomo-control/translations/en.json index 9c670859..d3faf269 100644 --- a/mihomo-control/translations/en.json +++ b/mihomo-control/translations/en.json @@ -42,6 +42,7 @@ "connections": "connections", "memory": "RAM", "proxy_groups": "Proxy groups", + "drag_group": "Drag to reorder", "no_groups": "No proxy groups found.", "delay_test": "Test latency", "test_all": "Test all", From 17acbf770a400284c24e31993740c511148ec391 Mon Sep 17 00:00:00 2001 From: mdj2812 Date: Thu, 13 Aug 2026 00:01:35 +0800 Subject: [PATCH 2/6] Add configurable Mihomo panel placement Let users choose whether the control panel opens centered or attached to the bar while preserving the existing floating default. Co-authored-by: Cursor --- mihomo-control/README.md | 12 +++++++++--- mihomo-control/plugin.toml | 22 ++++++++++++++++++++++ mihomo-control/translations/en.json | 8 ++++++++ mihomo-control/translations/zh-Hans.json | 9 +++++++++ mihomo-control/widget.luau | 9 ++++++++- 5 files changed, 56 insertions(+), 4 deletions(-) diff --git a/mihomo-control/README.md b/mihomo-control/README.md index 09217046..2acdfcd7 100644 --- a/mihomo-control/README.md +++ b/mihomo-control/README.md @@ -11,20 +11,25 @@ secret. | Field | Value | | ------- | --------------------------- | | ID | `mdj2812/mihomo-control` | -| Entries | Bar widget: `widget`; panel: `panel`; service: `service`; shortcut: `mode` | +| Entries | Bar widget: `widget`; panels: `panel`, `panel-attached`; service: `service`; shortcut: `mode` | ## Usage Add the **Mihomo Control** widget from the Add-widget picker. It shows the current proxy mode (rule / global / direct); hover it for the connection status, live download and upload rates, connection count and each proxy -group's current selection. Left-click the widget to toggle the control panel, -or open it with: +group's current selection. Left-click the widget to toggle the control panel +using the configured **Panel placement**. Both variants can also be opened +directly: ```sh noctalia msg panel-toggle mdj2812/mihomo-control:panel +noctalia msg panel-toggle mdj2812/mihomo-control:panel-attached ``` +The first command opens the centered floating panel; the second opens the +variant attached to the bar. + The panel lets you switch the proxy mode (rule / global / direct), restart the server, refresh the status, and manage every proxy group. Each group card lists all of its members with their latency — sourced from mihomo's health @@ -54,6 +59,7 @@ all communication with the external controller and streams the traffic data. | Allow insecure TLS | bool | off | Skip certificate verification for self-signed TLS controllers. | | Test URL | string | `https://www.gstatic.com/generate_204` | URL used for latency tests; empty uses each group's configured test URL. | | Refresh interval | int | `2` | Seconds between status polls (1–60); traffic rates stream in real time. | +| Panel placement | select | `floating` | Open the control panel centered on screen or attached to the bar widget. | ## IPC diff --git a/mihomo-control/plugin.toml b/mihomo-control/plugin.toml index c860db70..0ffbe531 100644 --- a/mihomo-control/plugin.toml +++ b/mihomo-control/plugin.toml @@ -72,11 +72,24 @@ advanced = true label_key = "settings.refresh_interval.label" description_key = "settings.refresh_interval.description" +[[setting]] +key = "panel_placement" +type = "select" +default = "floating" +label_key = "settings.panel_placement.label" +description_key = "settings.panel_placement.description" +options = [ + { value = "floating", label_key = "settings.panel_placement.options.floating" }, + { value = "attached", label_key = "settings.panel_placement.options.attached" }, +] + # ── Entries ────────────────────────────────────────────────────────────────── [[widget]] id = "widget" entry = "widget.luau" +# Panel placement is host-owned, so the widget selects between two entries +# backed by the same script according to the panel_placement setting. [[panel]] id = "panel" entry = "panel.luau" @@ -85,6 +98,15 @@ height = 720 placement = "floating" position = "center" +[[panel]] +id = "panel-attached" +entry = "panel.luau" +width = 560 +height = 720 +placement = "attached" +position = "auto" +open_near_click = true + [[service]] id = "service" entry = "service.luau" diff --git a/mihomo-control/translations/en.json b/mihomo-control/translations/en.json index d3faf269..a7281704 100644 --- a/mihomo-control/translations/en.json +++ b/mihomo-control/translations/en.json @@ -27,6 +27,14 @@ "refresh_interval": { "label": "Refresh interval", "description": "Seconds between status polls (1–60)." + }, + "panel_placement": { + "label": "Panel placement", + "description": "Choose whether the control panel opens centered on screen or attached to the bar widget.", + "options": { + "floating": "Floating", + "attached": "Attached" + } } }, "panel": { diff --git a/mihomo-control/translations/zh-Hans.json b/mihomo-control/translations/zh-Hans.json index 838ca6e7..bbe16bfb 100644 --- a/mihomo-control/translations/zh-Hans.json +++ b/mihomo-control/translations/zh-Hans.json @@ -27,6 +27,14 @@ "refresh_interval": { "label": "刷新间隔", "description": "状态轮询间隔(1–60 秒)。" + }, + "panel_placement": { + "label": "面板位置", + "description": "选择控制面板在屏幕中央浮动显示,或附着在栏组件旁。", + "options": { + "floating": "浮动", + "attached": "附着" + } } }, "panel": { @@ -42,6 +50,7 @@ "connections": "条连接", "memory": "内存", "proxy_groups": "策略组", + "drag_group": "拖动以重新排序", "no_groups": "未找到策略组。", "delay_test": "测试延迟", "test_all": "全部测试", diff --git a/mihomo-control/widget.luau b/mihomo-control/widget.luau index cd5011c4..bff9f455 100644 --- a/mihomo-control/widget.luau +++ b/mihomo-control/widget.luau @@ -8,6 +8,13 @@ local PLUGIN_ID = "mdj2812/mihomo-control" +local function panel_id() + if noctalia.getConfig("panel_placement") == "attached" then + return PLUGIN_ID .. ":panel-attached" + end + return PLUGIN_ID .. ":panel" +end + local function format_rate(bytes_per_second) local value = tonumber(bytes_per_second) or 0 if value >= 1024 * 1024 * 1024 then @@ -94,7 +101,7 @@ function update() end function onClick() - noctalia.togglePanel(PLUGIN_ID .. ":panel") + noctalia.togglePanel(panel_id()) end render() From 5d862760f1dfc9e23165689447fb4fbe03da83ff Mon Sep 17 00:00:00 2001 From: mdj2812 Date: Fri, 14 Aug 2026 01:38:08 +0800 Subject: [PATCH 3/6] Add configurable Mihomo widget presentation Provide a compact mode, scalable status-colored Mihomo glyph, and optional custom color while preserving the recognizable Clash silhouette. Co-authored-by: Cursor --- mihomo-control/README.md | 26 +-- mihomo-control/fonts/.gitignore | 2 + mihomo-control/fonts/README.md | 14 ++ mihomo-control/fonts/generate_mihomo_glyph.py | 155 ++++++++++++++++++ mihomo-control/fonts/mihomo-glyph.svg | 3 + mihomo-control/fonts/mihomo-glyph.ttf | Bin 0 -> 1428 bytes mihomo-control/icon-connecting.png | Bin 25303 -> 0 bytes mihomo-control/icon-offline.png | Bin 23950 -> 0 bytes mihomo-control/icon-online.png | Bin 24491 -> 0 bytes mihomo-control/plugin.toml | 35 ++++ mihomo-control/translations/en.json | 20 +++ mihomo-control/translations/zh-Hans.json | 20 +++ mihomo-control/widget.luau | 55 ++++++- 13 files changed, 311 insertions(+), 19 deletions(-) create mode 100644 mihomo-control/fonts/.gitignore create mode 100644 mihomo-control/fonts/README.md create mode 100644 mihomo-control/fonts/generate_mihomo_glyph.py create mode 100644 mihomo-control/fonts/mihomo-glyph.svg create mode 100644 mihomo-control/fonts/mihomo-glyph.ttf delete mode 100644 mihomo-control/icon-connecting.png delete mode 100644 mihomo-control/icon-offline.png delete mode 100644 mihomo-control/icon-online.png diff --git a/mihomo-control/README.md b/mihomo-control/README.md index 2acdfcd7..81ba60d4 100644 --- a/mihomo-control/README.md +++ b/mihomo-control/README.md @@ -15,12 +15,13 @@ secret. ## Usage -Add the **Mihomo Control** widget from the Add-widget picker. It shows the -current proxy mode (rule / global / direct); hover it for the connection -status, live download and upload rates, connection count and each proxy -group's current selection. Left-click the widget to toggle the control panel -using the configured **Panel placement**. Both variants can also be opened -directly: +Add the **Mihomo Control** widget from the Add-widget picker. By default it +shows the current proxy mode (rule / global / direct); disable **Show mode +label** for a compact icon-only widget. Its size and color presentation are +also configurable. Hover it for the connection status, live download and +upload rates, connection count and each proxy group's current selection. +Left-click the widget to toggle the control panel using the configured +**Panel placement**. Both variants can also be opened directly: ```sh noctalia msg panel-toggle mdj2812/mihomo-control:panel @@ -60,6 +61,10 @@ all communication with the external controller and streams the traffic data. | Test URL | string | `https://www.gstatic.com/generate_204` | URL used for latency tests; empty uses each group's configured test URL. | | Refresh interval | int | `2` | Seconds between status polls (1–60); traffic rates stream in real time. | | Panel placement | select | `floating` | Open the control panel centered on screen or attached to the bar widget. | +| Show mode label | bool | on | Show Rule/Global/Direct beside the icon; disable for a compact widget. | +| Icon size | int | `16` | Bar icon size in pixels (10–32). | +| Icon color mode | select | `status` | Color the Mihomo glyph by connection status or use a custom color. | +| Icon color | color | `primary` | Color used by the tintable Mihomo glyph. | ## IPC @@ -80,10 +85,10 @@ the same command tables the panel sends (`mode`, `select`, `delay_test`, no processes and runs no external commands. It writes only the custom proxy-group display order (group names, never the secret) to its Noctalia plugin data directory. Reordering does not modify the Mihomo configuration. -- The bar widget and card use the Clash cat logo (`icon.png`), the official - mascot of the Clash / mihomo project. In the bar widget the cat is tinted by - connection status: green online, amber while connecting, red offline; the - panel uses the neutral logo next to its own status indicator. +- The bar widget uses a tintable glyph traced from the Clash cat silhouette. By + default it is green online, amber while connecting and red offline; **Icon + color mode** can instead apply one custom color. The panel keeps the official + neutral logo next to its own status indicator. - The traffic rate uses mihomo's streaming `GET /traffic` endpoint; status, connections and proxy groups are polled every refresh interval. - The secret is stored in your Noctalia config and sent as the standard @@ -102,4 +107,5 @@ the same command tables the panel sends (`mode`, `select`, `delay_test`, - `widget.luau` — bar widget (rates + tooltip). - `panel.luau` — control panel. - `shortcut.luau` — rule/global mode toggle. +- `fonts/` — reviewable vector source and generated tintable Mihomo glyph font. - `translations/` — user-facing strings. diff --git a/mihomo-control/fonts/.gitignore b/mihomo-control/fonts/.gitignore new file mode 100644 index 00000000..43ae0e2a --- /dev/null +++ b/mihomo-control/fonts/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.py[cod] diff --git a/mihomo-control/fonts/README.md b/mihomo-control/fonts/README.md new file mode 100644 index 00000000..7343294b --- /dev/null +++ b/mihomo-control/fonts/README.md @@ -0,0 +1,14 @@ +# Mihomo Glyph + +`mihomo-glyph.ttf` contains one private-use glyph at `U+E001`, traced from the +bundled Mihomo cat image. It preserves the recognizable silhouette, eyes, mouth, +ears, and tail while allowing Noctalia to color it like text. + +The accompanying SVG is the reviewable vector source. Regenerate both assets +with: + +```sh +python3 fonts/generate_mihomo_glyph.py +``` + +Generation requires OpenCV and fontTools; neither is needed at runtime. diff --git a/mihomo-control/fonts/generate_mihomo_glyph.py b/mihomo-control/fonts/generate_mihomo_glyph.py new file mode 100644 index 00000000..ca172e80 --- /dev/null +++ b/mihomo-control/fonts/generate_mihomo_glyph.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Build a tintable icon font from the bundled Mihomo logo. + +Development dependencies: OpenCV and fontTools. The generated TTF has one +private-use glyph at U+E001. It is loaded by the widget at runtime; users do +not need either dependency. +""" + +from __future__ import annotations + +from pathlib import Path + +import cv2 +from fontTools.fontBuilder import FontBuilder +from fontTools.pens.ttGlyphPen import TTGlyphPen + + +ROOT = Path(__file__).resolve().parent +SOURCE = ROOT.parent / "icon.png" +SVG_OUTPUT = ROOT / "mihomo-glyph.svg" +FONT_OUTPUT = ROOT / "mihomo-glyph.ttf" +UNITS_PER_EM = 1000 +GLYPH_CODEPOINT = 0xE001 + + +def signed_area(points: list[tuple[int, int]]) -> float: + return sum( + x1 * y2 - x2 * y1 + for (x1, y1), (x2, y2) in zip(points, points[1:] + points[:1]) + ) / 2 + + +def load_contours() -> list[tuple[list[tuple[int, int]], bool]]: + image = cv2.imread(str(SOURCE), cv2.IMREAD_UNCHANGED) + if image is None or image.shape[2] != 4: + raise SystemExit(f"Could not read RGBA source image: {SOURCE}") + + blue, green, red, alpha = cv2.split(image) + white = (red > 220) & (green > 220) & (blue > 220) + mask = ((alpha > 16) & ~white).astype("uint8") * 255 + contours, hierarchy = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) + if hierarchy is None: + raise SystemExit("No contours found in Mihomo source image") + + x, y, width, height = cv2.boundingRect(mask) + scale = min(840 / width, 840 / height) + x_offset = (UNITS_PER_EM - width * scale) / 2 + y_offset = 80 + + result: list[tuple[list[tuple[int, int]], bool]] = [] + for index, contour in enumerate(contours): + if abs(cv2.contourArea(contour)) < 2: + continue + simplified = cv2.approxPolyDP(contour, 0.75, True) + points = [ + ( + round((int(point[0][0]) - x) * scale + x_offset), + round((y + height - int(point[0][1])) * scale + y_offset), + ) + for point in simplified + ] + if len(points) < 3: + continue + + is_hole = hierarchy[0][index][3] != -1 + area = signed_area(points) + should_be_clockwise = not is_hole + if (should_be_clockwise and area > 0) or (is_hole and area < 0): + points.reverse() + result.append((points, is_hole)) + + if not result: + raise SystemExit("No usable contours found in Mihomo source image") + return result + + +def build_glyph(contours: list[tuple[list[tuple[int, int]], bool]]): + pen = TTGlyphPen(None) + for points, _is_hole in contours: + pen.moveTo(points[0]) + for point in points[1:]: + pen.lineTo(point) + pen.closePath() + return pen.glyph() + + +def write_svg(contours: list[tuple[list[tuple[int, int]], bool]]) -> None: + paths = [] + for points, _is_hole in contours: + commands = [f"M {points[0][0]} {UNITS_PER_EM - points[0][1]}"] + commands.extend(f"L {x} {UNITS_PER_EM - y}" for x, y in points[1:]) + commands.append("Z") + paths.append(" ".join(commands)) + SVG_OUTPUT.write_text( + "\n".join( + [ + '', + f' ', + "", + "", + ] + ), + encoding="utf-8", + ) + + +def write_font(contours: list[tuple[list[tuple[int, int]], bool]]) -> None: + empty_pen = TTGlyphPen(None) + glyphs = { + ".notdef": empty_pen.glyph(), + "space": TTGlyphPen(None).glyph(), + "mihomo": build_glyph(contours), + } + + builder = FontBuilder(UNITS_PER_EM, isTTF=True) + builder.setupGlyphOrder([".notdef", "space", "mihomo"]) + builder.setupCharacterMap({32: "space", GLYPH_CODEPOINT: "mihomo"}) + builder.setupGlyf(glyphs) + builder.setupHorizontalMetrics({ + ".notdef": (UNITS_PER_EM, 0), + "space": (500, 0), + "mihomo": (UNITS_PER_EM, 0), + }) + builder.setupHorizontalHeader(ascent=1000, descent=-200) + builder.setupNameTable({ + "familyName": "Mihomo Glyph", + "styleName": "Regular", + "uniqueFontIdentifier": "Mihomo Glyph Regular 1.0", + "fullName": "Mihomo Glyph Regular", + "psName": "MihomoGlyph-Regular", + "version": "Version 1.0", + }) + builder.setupOS2( + sTypoAscender=1000, + sTypoDescender=-200, + usWinAscent=1000, + usWinDescent=200, + ) + builder.setupPost() + builder.setupMaxp() + builder.font["head"].created = 2082844800 + builder.font["head"].modified = 2082844800 + builder.font.recalcTimestamp = False + builder.save(FONT_OUTPUT) + + +def main() -> None: + contours = load_contours() + write_svg(contours) + write_font(contours) + print(FONT_OUTPUT) + + +if __name__ == "__main__": + main() diff --git a/mihomo-control/fonts/mihomo-glyph.svg b/mihomo-control/fonts/mihomo-glyph.svg new file mode 100644 index 00000000..b3cd260d --- /dev/null +++ b/mihomo-control/fonts/mihomo-glyph.svg @@ -0,0 +1,3 @@ + + + diff --git a/mihomo-control/fonts/mihomo-glyph.ttf b/mihomo-control/fonts/mihomo-glyph.ttf new file mode 100644 index 0000000000000000000000000000000000000000..08eccf35ee3b08f1fef8e82fbe388dc2afc19fa1 GIT binary patch literal 1428 zcmZ`&U1%It6h3p#+|2HHndvb5GpvLelDO%jZBj#AmB3P@R)k`SP#;9?+OE5i-RzPy zkbsneFG4{Cr6Bku)F%V=r@jdGMMQn_QK=6qKJ=*|>TBb7r+1SOo4I@M`M&R*bI+N% zcgZ0lpRSNl_QH#EkDvNs@e?BFDAe;82JO+Uzq60PKLB6q_P5@+`TF*|;6G#j+UvA0 zzI^KX4fsCAe4+=zo0so^|HPc>4K8gKzXc-a9W1&(ywHZd3;q*qVbIk?9rlsju|a*TSWL$&ouy=&eU?T-mi}X<4qx zWpE)jDjwv>ot-Dm3ih!)o9~pn>>{}$^f|e?GS1wK5jp9+N6Py5^amm5$Ue1&2Q>Q) z-P#_$-{&?kyWg4fkL{51&IO(l-om_f33ZRcPQW_wVz-?g@Z9eFo;i6G1pC}Y-1tje zr4Q*keTL7FN=s5+sJ5h9EvJ6Diji_|c3~t9Ba+t=mH7!~dg;^=ZFru_8 zK&~B6VLs&51OE22YA+EkSfX3M7gH z3FJtr$t8~XF@DZAit@~bVyPBSHKyW8t(2#9lvXNHQ1N_~NEsuRF4A6&BaD#EFq
    +T*z|810^=XSnP?o7fT{PRL zHmw~Rl^ux7=mZ_diygM>!T58ivPSFZVMwc}|Nrd0j*cCum7hy~B!@2MpDqqh4DwG% F{{fLP#x?)| literal 0 HcmV?d00001 diff --git a/mihomo-control/icon-connecting.png b/mihomo-control/icon-connecting.png deleted file mode 100644 index b48ff0d17e1f781ae81e12adbba61a531f65ce17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25303 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�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 diff --git a/mihomo-control/icon-online.png b/mihomo-control/icon-online.png deleted file mode 100644 index a4cfddb7d343a1192c175cb78744405c828d4cfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24491 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 diff --git a/mihomo-control/plugin.toml b/mihomo-control/plugin.toml index 0ffbe531..5de965d6 100644 --- a/mihomo-control/plugin.toml +++ b/mihomo-control/plugin.toml @@ -88,6 +88,41 @@ options = [ id = "widget" entry = "widget.luau" + [[widget.setting]] + key = "show_mode" + type = "bool" + default = true + label_key = "settings.show_mode.label" + description_key = "settings.show_mode.description" + + [[widget.setting]] + key = "icon_size" + type = "int" + default = 16 + min = 10 + max = 32 + label_key = "settings.icon_size.label" + description_key = "settings.icon_size.description" + + [[widget.setting]] + key = "icon_color_mode" + type = "select" + default = "status" + label_key = "settings.icon_color_mode.label" + description_key = "settings.icon_color_mode.description" + options = [ + { value = "status", label_key = "settings.icon_color_mode.options.status" }, + { value = "custom", label_key = "settings.icon_color_mode.options.custom" }, + ] + + [[widget.setting]] + key = "icon_color" + type = "color" + default = "primary" + label_key = "settings.icon_color.label" + description_key = "settings.icon_color.description" + visible_when = { key = "icon_color_mode", values = ["custom"] } + # Panel placement is host-owned, so the widget selects between two entries # backed by the same script according to the panel_placement setting. [[panel]] diff --git a/mihomo-control/translations/en.json b/mihomo-control/translations/en.json index a7281704..ac77cf8c 100644 --- a/mihomo-control/translations/en.json +++ b/mihomo-control/translations/en.json @@ -35,6 +35,26 @@ "floating": "Floating", "attached": "Attached" } + }, + "show_mode": { + "label": "Show mode label", + "description": "Show Rule, Global, or Direct beside the icon. Disable for a compact icon-only widget." + }, + "icon_size": { + "label": "Icon size", + "description": "Size of the bar icon in pixels (10–32)." + }, + "icon_color_mode": { + "label": "Icon color mode", + "description": "Color the Mihomo glyph by connection status or use one custom color.", + "options": { + "status": "Connection status", + "custom": "Custom" + } + }, + "icon_color": { + "label": "Icon color", + "description": "Color of the tintable Mihomo glyph." } }, "panel": { diff --git a/mihomo-control/translations/zh-Hans.json b/mihomo-control/translations/zh-Hans.json index bbe16bfb..e0d2e7b7 100644 --- a/mihomo-control/translations/zh-Hans.json +++ b/mihomo-control/translations/zh-Hans.json @@ -35,6 +35,26 @@ "floating": "浮动", "attached": "附着" } + }, + "show_mode": { + "label": "显示模式标签", + "description": "在图标旁显示“规则”“全局”或“直连”。关闭后仅显示图标,以获得更紧凑的组件。" + }, + "icon_size": { + "label": "图标大小", + "description": "栏图标的像素大小(10–32)。" + }, + "icon_color_mode": { + "label": "图标颜色模式", + "description": "根据连接状态为 Mihomo 字形着色,或使用单一自定义颜色。", + "options": { + "status": "连接状态", + "custom": "自定义" + } + }, + "icon_color": { + "label": "图标颜色", + "description": "可着色 Mihomo 字形的颜色。" } }, "panel": { diff --git a/mihomo-control/widget.luau b/mihomo-control/widget.luau index bff9f455..5338f828 100644 --- a/mihomo-control/widget.luau +++ b/mihomo-control/widget.luau @@ -7,6 +7,13 @@ -- proxy group. local PLUGIN_ID = "mdj2812/mihomo-control" +local MIHOMO_GLYPH = utf8.char(0xE001) +local mihomoGlyphFont = noctalia.loadFont("fonts/mihomo-glyph.ttf") +local STATUS_ICON_COLORS = { + online = "#27924f", + connecting = "#a35818", + offline = "#a72f2f", +} local function panel_id() if noctalia.getConfig("panel_placement") == "attached" then @@ -29,6 +36,11 @@ local function format_rate(bytes_per_second) return string.format("%d B/s", value) end +local function configured_icon_size() + local value = math.floor(tonumber(noctalia.getConfig("icon_size")) or 16) + return math.max(10, math.min(32, value)) +end + local function render() local conn = noctalia.state.get("mihomo.connection") or {} local config = noctalia.state.get("mihomo.config") or {} @@ -38,18 +50,39 @@ local function render() 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" + + local show_mode = noctalia.getConfig("show_mode") ~= false + local icon_size = configured_icon_size() + local icon + if mihomoGlyphFont ~= nil then + local color = STATUS_ICON_COLORS[conn.status] or STATUS_ICON_COLORS.offline + if noctalia.getConfig("icon_color_mode") == "custom" then + color = noctalia.getConfig("icon_color") or "primary" + end + icon = ui.label({ + text = MIHOMO_GLYPH, + fontFamily = mihomoGlyphFont, + fontSize = icon_size, + color = color, + baseline = "inkCentered", + maxLines = 1, + }) + else + icon = ui.image({ path = "icon.png", width = icon_size, height = icon_size }) + end + + local children = { icon } + if show_mode then + table.insert(children, ui.label({ + text = mode, + fontSize = 12, + color = "on_surface", + fontWeight = "medium", + })) 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" }), - })) + barWidget.render(container({ gap = show_mode and 6 or 0, align = "center" }, children)) local status_value = "Offline" if online then @@ -100,6 +133,10 @@ function update() render() end +function onConfigChanged() + render() +end + function onClick() noctalia.togglePanel(panel_id()) end From 4c503e20f522bab84a62521cd4c7173ca7f2b9c1 Mon Sep 17 00:00:00 2001 From: mdj2812 Date: Fri, 14 Aug 2026 01:39:37 +0800 Subject: [PATCH 4/6] Add selectable Mihomo widget labels Let users choose an icon-only widget, proxy mode, live traffic rates, or connection count without changing the detailed tooltip. Co-authored-by: Cursor --- mihomo-control/README.md | 15 ++++++------ mihomo-control/plugin.toml | 18 ++++++++++---- mihomo-control/translations/en.json | 17 ++++++++++---- mihomo-control/translations/zh-Hans.json | 17 ++++++++++---- mihomo-control/widget.luau | 30 ++++++++++++++++++++---- 5 files changed, 73 insertions(+), 24 deletions(-) diff --git a/mihomo-control/README.md b/mihomo-control/README.md index 81ba60d4..f1973e7c 100644 --- a/mihomo-control/README.md +++ b/mihomo-control/README.md @@ -16,12 +16,13 @@ secret. ## Usage Add the **Mihomo Control** widget from the Add-widget picker. By default it -shows the current proxy mode (rule / global / direct); disable **Show mode -label** for a compact icon-only widget. Its size and color presentation are -also configurable. Hover it for the connection status, live download and -upload rates, connection count and each proxy group's current selection. -Left-click the widget to toggle the control panel using the configured -**Panel placement**. Both variants can also be opened directly: +shows the current proxy mode (rule / global / direct). **Widget label** can +instead show nothing, download speed, upload speed, both rates, or the active +connection count. Its size and color presentation are also configurable. Hover +it for the connection status, live download and upload rates, connection count +and each proxy group's current selection. Left-click the widget to toggle the +control panel using the configured **Panel placement**. Both variants can also +be opened directly: ```sh noctalia msg panel-toggle mdj2812/mihomo-control:panel @@ -61,7 +62,7 @@ all communication with the external controller and streams the traffic data. | Test URL | string | `https://www.gstatic.com/generate_204` | URL used for latency tests; empty uses each group's configured test URL. | | Refresh interval | int | `2` | Seconds between status polls (1–60); traffic rates stream in real time. | | Panel placement | select | `floating` | Open the control panel centered on screen or attached to the bar widget. | -| Show mode label | bool | on | Show Rule/Global/Direct beside the icon; disable for a compact widget. | +| Widget label | select | `mode` | Show nothing, proxy mode, traffic rates, or connection count. | | Icon size | int | `16` | Bar icon size in pixels (10–32). | | Icon color mode | select | `status` | Color the Mihomo glyph by connection status or use a custom color. | | Icon color | color | `primary` | Color used by the tintable Mihomo glyph. | diff --git a/mihomo-control/plugin.toml b/mihomo-control/plugin.toml index 5de965d6..5ab1aaa9 100644 --- a/mihomo-control/plugin.toml +++ b/mihomo-control/plugin.toml @@ -89,11 +89,19 @@ id = "widget" entry = "widget.luau" [[widget.setting]] - key = "show_mode" - type = "bool" - default = true - label_key = "settings.show_mode.label" - description_key = "settings.show_mode.description" + key = "label_content" + type = "select" + default = "mode" + label_key = "settings.label_content.label" + description_key = "settings.label_content.description" + options = [ + { value = "none", label_key = "settings.label_content.options.none" }, + { value = "mode", label_key = "settings.label_content.options.mode" }, + { value = "download", label_key = "settings.label_content.options.download" }, + { value = "upload", label_key = "settings.label_content.options.upload" }, + { value = "traffic", label_key = "settings.label_content.options.traffic" }, + { value = "connections", label_key = "settings.label_content.options.connections" }, + ] [[widget.setting]] key = "icon_size" diff --git a/mihomo-control/translations/en.json b/mihomo-control/translations/en.json index ac77cf8c..942e39e4 100644 --- a/mihomo-control/translations/en.json +++ b/mihomo-control/translations/en.json @@ -36,9 +36,17 @@ "attached": "Attached" } }, - "show_mode": { - "label": "Show mode label", - "description": "Show Rule, Global, or Direct beside the icon. Disable for a compact icon-only widget." + "label_content": { + "label": "Widget label", + "description": "Choose what is shown beside the Mihomo icon.", + "options": { + "none": "None (icon only)", + "mode": "Proxy mode", + "download": "Download speed", + "upload": "Upload speed", + "traffic": "Download + upload", + "connections": "Connection count" + } }, "icon_size": { "label": "Icon size", @@ -97,7 +105,8 @@ "status_online_plain": "Online", "status_connecting": "Connecting", "status_offline_with_error": "Offline — {error}", - "status_offline_plain": "Offline" + "status_offline_plain": "Offline", + "connections_short": "{count} conn" }, "shortcut": { "label": "Mihomo: {mode}", diff --git a/mihomo-control/translations/zh-Hans.json b/mihomo-control/translations/zh-Hans.json index e0d2e7b7..ffa75112 100644 --- a/mihomo-control/translations/zh-Hans.json +++ b/mihomo-control/translations/zh-Hans.json @@ -36,9 +36,17 @@ "attached": "附着" } }, - "show_mode": { - "label": "显示模式标签", - "description": "在图标旁显示“规则”“全局”或“直连”。关闭后仅显示图标,以获得更紧凑的组件。" + "label_content": { + "label": "组件标签", + "description": "选择 Mihomo 图标旁显示的内容。", + "options": { + "none": "无(仅图标)", + "mode": "代理模式", + "download": "下载速度", + "upload": "上传速度", + "traffic": "下载 + 上传", + "connections": "连接数" + } }, "icon_size": { "label": "图标大小", @@ -97,7 +105,8 @@ "status_online_plain": "在线", "status_connecting": "连接中", "status_offline_with_error": "离线 — {error}", - "status_offline_plain": "离线" + "status_offline_plain": "离线", + "connections_short": "{count} 个连接" }, "shortcut": { "label": "Mihomo:{mode}", diff --git a/mihomo-control/widget.luau b/mihomo-control/widget.luau index 5338f828..877439eb 100644 --- a/mihomo-control/widget.luau +++ b/mihomo-control/widget.luau @@ -41,6 +41,28 @@ local function configured_icon_size() return math.max(10, math.min(32, value)) end +local function configured_label(mode, traffic, connections) + local content = noctalia.getConfig("label_content") or "mode" + if content == "none" then + return nil + end + if content == "download" then + return `▼ {format_rate(traffic.down)}` + end + if content == "upload" then + return `▲ {format_rate(traffic.up)}` + end + if content == "traffic" then + return `▼ {format_rate(traffic.down)} ▲ {format_rate(traffic.up)}` + end + if content == "connections" then + return noctalia.tr("widget.connections_short", { + count = math.max(0, math.floor(tonumber(connections.count) or 0)), + }) + end + return mode +end + local function render() local conn = noctalia.state.get("mihomo.connection") or {} local config = noctalia.state.get("mihomo.config") or {} @@ -51,7 +73,7 @@ local function render() local online = conn.status == "online" local mode = noctalia.tr("panel.mode_" .. (config.mode or "rule")) - local show_mode = noctalia.getConfig("show_mode") ~= false + local label_text = configured_label(mode, traffic, connections) local icon_size = configured_icon_size() local icon if mihomoGlyphFont ~= nil then @@ -72,9 +94,9 @@ local function render() end local children = { icon } - if show_mode then + if label_text ~= nil then table.insert(children, ui.label({ - text = mode, + text = label_text, fontSize = 12, color = "on_surface", fontWeight = "medium", @@ -82,7 +104,7 @@ local function render() end local container = barWidget.isVertical() and ui.column or ui.row - barWidget.render(container({ gap = show_mode and 6 or 0, align = "center" }, children)) + barWidget.render(container({ gap = label_text ~= nil and 6 or 0, align = "center" }, children)) local status_value = "Offline" if online then From cc3d0c074dd254cb6046321fb8b73c87e9521ab2 Mon Sep 17 00:00:00 2001 From: mdj2812 Date: Fri, 14 Aug 2026 01:50:26 +0800 Subject: [PATCH 5/6] Add first-group proxy widget labels Expose the selected proxy and best known latency from the first visible group as compact widget label choices. Co-authored-by: Cursor --- mihomo-control/README.md | 10 +++-- mihomo-control/plugin.toml | 2 + mihomo-control/translations/en.json | 7 ++- mihomo-control/translations/zh-Hans.json | 7 ++- mihomo-control/widget.luau | 55 +++++++++++++++++++++++- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/mihomo-control/README.md b/mihomo-control/README.md index f1973e7c..475b965a 100644 --- a/mihomo-control/README.md +++ b/mihomo-control/README.md @@ -18,9 +18,11 @@ secret. Add the **Mihomo Control** widget from the Add-widget picker. By default it shows the current proxy mode (rule / global / direct). **Widget label** can instead show nothing, download speed, upload speed, both rates, or the active -connection count. Its size and color presentation are also configurable. Hover -it for the connection status, live download and upload rates, connection count -and each proxy group's current selection. Left-click the widget to toggle the +connection count. It can also show the first visible proxy group's current +selection or its selected latency, falling back to that group's best known +member latency. Its size and color presentation are also configurable. Hover it +for the connection status, live download and upload rates, connection count and +each proxy group's current selection. Left-click the widget to toggle the control panel using the configured **Panel placement**. Both variants can also be opened directly: @@ -62,7 +64,7 @@ all communication with the external controller and streams the traffic data. | Test URL | string | `https://www.gstatic.com/generate_204` | URL used for latency tests; empty uses each group's configured test URL. | | Refresh interval | int | `2` | Seconds between status polls (1–60); traffic rates stream in real time. | | Panel placement | select | `floating` | Open the control panel centered on screen or attached to the bar widget. | -| Widget label | select | `mode` | Show nothing, proxy mode, traffic rates, or connection count. | +| Widget label | select | `mode` | Show nothing, mode, traffic, connections, or first-group proxy/latency. | | Icon size | int | `16` | Bar icon size in pixels (10–32). | | Icon color mode | select | `status` | Color the Mihomo glyph by connection status or use a custom color. | | Icon color | color | `primary` | Color used by the tintable Mihomo glyph. | diff --git a/mihomo-control/plugin.toml b/mihomo-control/plugin.toml index 5ab1aaa9..6abdde11 100644 --- a/mihomo-control/plugin.toml +++ b/mihomo-control/plugin.toml @@ -101,6 +101,8 @@ entry = "widget.luau" { value = "upload", label_key = "settings.label_content.options.upload" }, { value = "traffic", label_key = "settings.label_content.options.traffic" }, { value = "connections", label_key = "settings.label_content.options.connections" }, + { value = "proxy", label_key = "settings.label_content.options.proxy" }, + { value = "latency", label_key = "settings.label_content.options.latency" }, ] [[widget.setting]] diff --git a/mihomo-control/translations/en.json b/mihomo-control/translations/en.json index 942e39e4..b148ebc7 100644 --- a/mihomo-control/translations/en.json +++ b/mihomo-control/translations/en.json @@ -45,7 +45,9 @@ "download": "Download speed", "upload": "Upload speed", "traffic": "Download + upload", - "connections": "Connection count" + "connections": "Connection count", + "proxy": "First group selection", + "latency": "First group latency" } }, "icon_size": { @@ -106,7 +108,8 @@ "status_connecting": "Connecting", "status_offline_with_error": "Offline — {error}", "status_offline_plain": "Offline", - "connections_short": "{count} conn" + "connections_short": "{count} conn", + "latency_timeout": "timeout" }, "shortcut": { "label": "Mihomo: {mode}", diff --git a/mihomo-control/translations/zh-Hans.json b/mihomo-control/translations/zh-Hans.json index ffa75112..f7adba40 100644 --- a/mihomo-control/translations/zh-Hans.json +++ b/mihomo-control/translations/zh-Hans.json @@ -45,7 +45,9 @@ "download": "下载速度", "upload": "上传速度", "traffic": "下载 + 上传", - "connections": "连接数" + "connections": "连接数", + "proxy": "首个策略组所选代理", + "latency": "首个策略组延迟" } }, "icon_size": { @@ -106,7 +108,8 @@ "status_connecting": "连接中", "status_offline_with_error": "离线 — {error}", "status_offline_plain": "离线", - "connections_short": "{count} 个连接" + "connections_short": "{count} 个连接", + "latency_timeout": "超时" }, "shortcut": { "label": "Mihomo:{mode}", diff --git a/mihomo-control/widget.luau b/mihomo-control/widget.luau index 877439eb..137758a9 100644 --- a/mihomo-control/widget.luau +++ b/mihomo-control/widget.luau @@ -41,7 +41,42 @@ local function configured_icon_size() return math.max(10, math.min(32, value)) end -local function configured_label(mode, traffic, connections) +local function group_latency(group) + if group.now and group.now ~= "" then + for _, member in ipairs(type(group.members) == "table" and group.members or {}) do + if member.name == group.now and member.delay ~= nil then + return tonumber(member.delay) + end + end + end + + local best = nil + local any_tested = false + for _, member in ipairs(type(group.members) == "table" and group.members or {}) do + local delay = tonumber(member.delay) + if delay ~= nil then + any_tested = true + if delay > 0 and (best == nil or delay < best) then + best = delay + end + end + end + if best ~= nil then + return best + end + return any_tested and 0 or nil +end + +local function first_visible_group(groups) + for _, group in ipairs(type(groups) == "table" and groups or {}) do + if group.hidden ~= true then + return group + end + end + return nil +end + +local function configured_label(mode, traffic, connections, groups) local content = noctalia.getConfig("label_content") or "mode" if content == "none" then return nil @@ -60,6 +95,22 @@ local function configured_label(mode, traffic, connections) count = math.max(0, math.floor(tonumber(connections.count) or 0)), }) end + if content == "proxy" then + local group = first_visible_group(groups) + local selected = group and tostring(group.now or "") or "" + return selected ~= "" and selected or "—" + end + if content == "latency" then + local group = first_visible_group(groups) + local delay = group and group_latency(group) or nil + if delay == nil then + return "— ms" + end + if delay <= 0 then + return noctalia.tr("widget.latency_timeout") + end + return `{math.floor(delay)} ms` + end return mode end @@ -73,7 +124,7 @@ local function render() local online = conn.status == "online" local mode = noctalia.tr("panel.mode_" .. (config.mode or "rule")) - local label_text = configured_label(mode, traffic, connections) + local label_text = configured_label(mode, traffic, connections, groups) local icon_size = configured_icon_size() local icon if mihomoGlyphFont ~= nil then From 60bf92f5c6d92a6ad47a68426202ee43ea5ac46b Mon Sep 17 00:00:00 2001 From: mdj2812 Date: Fri, 14 Aug 2026 02:08:45 +0800 Subject: [PATCH 6/6] Improve Mihomo group drag targeting Let card-sized drag targets select the nearest insertion point and lift the full source slot so reordering previews are easier to follow. Co-authored-by: Cursor --- mihomo-control/README.md | 5 +++-- mihomo-control/panel.luau | 13 ++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/mihomo-control/README.md b/mihomo-control/README.md index 475b965a..d608a5a9 100644 --- a/mihomo-control/README.md +++ b/mihomo-control/README.md @@ -43,8 +43,9 @@ in its subtitle, visible without expanding. Member lists are collapsed by default; click a group header to expand it. Click a member to select it; the current selection is marked with a dot. Use **Test all** next to the Proxy groups heading to run a latency test on every group at once. Drag a card by -its ☰ grip and drop it into an insertion gap to reorder the groups. The custom -display order survives controller polling and plugin restarts. +its ☰ grip over another card to reorder the groups. The nearest insertion gap +opens to preview the resulting layout before drop. The custom display order +survives controller polling and plugin restarts. Add the **Mihomo: Rule** shortcut from Settings → Control Center shortcuts to quickly toggle between rule and global mode. diff --git a/mihomo-control/panel.luau b/mihomo-control/panel.luau index 35f63a49..08abd668 100644 --- a/mihomo-control/panel.luau +++ b/mihomo-control/panel.luau @@ -15,6 +15,7 @@ local MODES = { "rule", "global", "direct" } local GROUP_DRAG_TYPE = "mihomo-proxy-group" +local GROUP_DROP_HIT_SLOP = 128 local snapshot = { connection = {}, @@ -294,7 +295,9 @@ local function traffic_card() end -- Thin insertion target before every group card, plus one after the final --- card. The numeric value is interpreted against the row-major group order. +-- card. Its drag-only halo covers the neighboring card body; overlapping +-- targets resolve to the closest insertion point. expandOnDrag then opens a +-- full-card gap, previewing the resulting layout before the pointer is released. local function group_insertion_zone(index) return ui.dropZone({ key = "group-gap-" .. index, @@ -304,7 +307,7 @@ local function group_insertion_zone(index) height = 3, radius = 6, expandOnDrag = true, - hitSlop = 18, + hitSlop = GROUP_DROP_HIT_SLOP, }) end @@ -333,13 +336,13 @@ local function group_card(group) ui.row({ gap = 6, align = "center", justify = "space_between", onClick = function() toggle_group(group.name) end }, { - -- The grip sits two levels below the card, so previewAncestor = 2 makes - -- the drag ghost and lifted layout item represent the complete card. + -- Lift the complete slot, including its insertion zone, so no selector + -- line remains at the source position while the card is being dragged. ui.dragSource({ key = "group-grip-" .. group.name, dragType = GROUP_DRAG_TYPE, payload = group.name, - previewAncestor = 2, + previewAncestor = 3, liftFromLayout = true, width = 20, height = 20,