From d2287159e09db6aee66bf202a9b54f13c451fa79 Mon Sep 17 00:00:00 2001 From: nightwatch75 <30896720+nightwatch75@users.noreply.github.com> Date: Sat, 8 Aug 2026 16:56:26 +0200 Subject: [PATCH] Add nightwatch75/dns-switcher 0.1.1 (#292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch the system DNS between popular providers, custom servers, or the ISP default, from a panel on the bar — no reconnect, no captive-portal re-run. Detection reads the active connection's own ipv4.dns/ipv4.ignore-auto-dns instead of guessing, so a manually configured resolver (LAN ones included) shows as its provider and DHCP-assigned DNS shows as Default (ISP). A singleton service entry owns detection/apply so multiple bars share one engine. Every bar gesture (left/right/scroll) is a manifest [widget.actions] default, resolved through noctalia's own IPC registry, so any of them can be remapped from the bar's own gesture settings without touching the plugin. The panel also carries a DNS lookup tester: resolve a name against the active provider's own address with dig/nslookup, to confirm a switch took effect or that a provider blocks a domain. Co-authored-by: nightwatch75 Co-authored-by: Claude Sonnet 5 --- dns-switcher/README.md | 124 ++++++++ dns-switcher/dns-switcher.luau | 78 +++++ dns-switcher/panel.luau | 345 +++++++++++++++++++++ dns-switcher/plugin.toml | 127 ++++++++ dns-switcher/service.luau | 497 ++++++++++++++++++++++++++++++ dns-switcher/thumbnail.webp | Bin 0 -> 73880 bytes dns-switcher/translations/en.json | 75 +++++ 7 files changed, 1246 insertions(+) create mode 100644 dns-switcher/README.md create mode 100644 dns-switcher/dns-switcher.luau create mode 100644 dns-switcher/panel.luau create mode 100644 dns-switcher/plugin.toml create mode 100644 dns-switcher/service.luau create mode 100644 dns-switcher/thumbnail.webp create mode 100644 dns-switcher/translations/en.json diff --git a/dns-switcher/README.md b/dns-switcher/README.md new file mode 100644 index 0000000..00fe714 --- /dev/null +++ b/dns-switcher/README.md @@ -0,0 +1,124 @@ +# DNS Switcher + +A [noctalia](https://github.com/noctalia-dev/noctalia) v5 bar plugin: switch the system DNS +between popular providers, your own servers, or the ISP default — from a panel +on the bar, no reconnect. Based on +[Ronin-CK's v4 DNS Switcher](https://github.com/noctalia-dev/legacy-v4-plugins), +rebuilt on the v5 Luau plugin API. + +## Plugin + +| Field | Value | +| --- | --- | +| ID | `nightwatch75/dns-switcher` | +| Entries | Bar widget: `dns-switcher`; panel: `panel`; service: `service` | + +## Features + +- **Instant, no-drop switching** — one `nmcli con mod` + `nmcli device + reapply` on the active connection profile; the network never disconnects +- **Pre-configured providers** (Google, Cloudflare, OpenDNS, AdGuard, Quad9) + plus up to 5 custom servers (`Name = address`, e.g. `Pi-hole = 192.168.1.5`) +- **Detection**, not guessing — reads the connection's own `ipv4.dns` / + `ipv4.ignore-auto-dns`, so a manually configured resolver (LAN ones + included) shows as its provider, DHCP-assigned DNS shows as *Default (ISP)* +- **DNS lookup tester** at the bottom of the panel: resolve any name against + the currently active provider's own address with `dig`/`nslookup`, to + confirm a switch took effect or check whether a provider blocks a domain +- **Fully rebindable gestures** — left click, right click and scroll are + declared in the manifest (`[widget.actions]`), so any of them can be + remapped from the bar's own gesture settings; scroll cycles providers +- **Singleton service** — one engine regardless of how many bars/monitors + show the widget; widget and panel are pure renderers over its shared state +- Live footer (connection name + active resolver IPs, with a copy button), + glyph-only mode for compact bars + +## Usage + +Add the `dns-switcher` widget from Noctalia's widget picker. Default gestures: + +| Action | Effect | +|--------------|--------------------------------------------------| +| Left click | Open/close the provider panel | +| Right click | Reset to the connection default (ISP) | +| Scroll | Cycle to the next/previous configured provider | + +All three are bar-level defaults and can be remapped from *Settings → Bar*. +The panel itself, and the plugin's settings page, also open from the CLI: + +```sh +noctalia msg panel-toggle nightwatch75/dns-switcher:panel +noctalia msg settings-open-plugin nightwatch75/dns-switcher +``` + +## Settings + +| Setting | Type | Default | Description | +| --- | --- | --- | --- | +| `providers` | `string` | `google,cloudflare,opendns,adguard,quad9` | Comma-separated built-in provider ids shown in the panel. Empty = none. | +| `custom_1` … `custom_5` | `string` | *(empty)* | One custom resolver each: `Name = address`, one or two IPv4 addresses. | +| `poll_seconds` | `int` | `10` | How often the active DNS is re-read with `nmcli` (2–120). | +| `privilege_command` | `string` | *(empty)* | Prefix to run `nmcli` changes as root (`pkexec`, `sudo -n`) — see *Privileges*. | +| `show_label` (widget) | `bool` | `true` | Show the provider name next to the glyph. | + +## IPC + +```sh +noctalia msg plugin nightwatch75/dns-switcher:service all apply cloudflare +noctalia msg plugin nightwatch75/dns-switcher:service all poll +noctalia msg plugin nightwatch75/dns-switcher:service all cycle next +``` + +`apply` takes a built-in id, `default` (ISP), or `custom:`; `poll` +forces an immediate re-check; `cycle next`/`cycle prev` step to the +neighbouring provider (what scroll sends). + +## Requirements + +- noctalia v5.0.0-beta.6 or newer for the core plugin; the gesture remapping + and lookup tester need a newer build still (`plugin_api = 22`) +- NetworkManager (`networkmanager`, provides `nmcli`) with an active connection +- Permission to modify system connections (see *Privileges* below) +- `dig` (bind-tools/dnsutils) or `nslookup`, optional — only the lookup + tester needs one of them; the rest of the plugin works without either + +## Privileges + +*Privilege command* is **empty by default**: NetworkManager's polkit policy +usually lets active local sessions modify system connections without a +password. If you get a "not authorized" error, set it to `pkexec` (shows +noctalia's own polkit prompt) or `sudo -n` with a matching sudoers rule: + +``` +# /etc/sudoers.d/nmcli-dns +youruser ALL=(root) NOPASSWD: /usr/bin/nmcli +``` + +Or grant it via a polkit rule and keep the setting empty: + +```js +// /etc/polkit-1/rules.d/50-nmcli-dns.rules +polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.NetworkManager.settings.modify.system" || + action.id == "org.freedesktop.NetworkManager.network-control") && + subject.isInGroup("wheel")) { + return polkit.Result.YES; + } +}); +``` + +Both widen what the account can do to NetworkManager system-wide — apply +your usual judgement on shared machines. + +## Notes + +- IPv4 DNS only, like the v4 plugin. +- Targets the first active wifi/ethernet connection (falling back to the + first active non-loopback one); a VPN's own DNS is not touched. +- Custom servers are five separate `string` settings rather than one list, + because Noctalia's list editor has no in-place row edit — a `string` + field does. A server name may not contain `=`. + +## License + +MIT. diff --git a/dns-switcher/dns-switcher.luau b/dns-switcher/dns-switcher.luau new file mode 100644 index 0000000..69a485f --- /dev/null +++ b/dns-switcher/dns-switcher.luau @@ -0,0 +1,78 @@ +--!nonstrict +-- dns-switcher — bar widget: renders the state published by the service +-- entry (service.luau). +-- +-- Click mapping is declared in plugin.toml's [widget.actions] (plugin_api >= +-- 14), not hard-coded here, so the user can remap it from the bar's own +-- gesture settings like any built-in widget: +-- Left click — panel-toggle nightwatch75/dns-switcher:panel +-- Right click — plugin nightwatch75/dns-switcher:service all apply default +-- Scroll up/down — plugin nightwatch75/dns-switcher:service all cycle next/prev +-- +-- No middle-click binding: every bar widget carries a built-in `middle` +-- binding that opens its own settings, and that default is exactly what's +-- wanted here. + +local STATE_KEY = "dns_state" +local GLYPH_UNKNOWN = "globe" + +local snapshot = nil -- last state published by the service + +local function tr(key, args) + return noctalia.tr(key, args) +end + +local function setLabel(text) + if noctalia.getConfig("show_label") == false then + barWidget.setText("") + else + barWidget.setText(text) + end +end + +local function paint(glyph, role, label) + barWidget.setGlyph(glyph) + barWidget.setGlyphColor(role) + barWidget.setColor(role) + setLabel(label) +end + +local function render() + local s = snapshot + if s == nil or (s.current == nil and s.error == nil) then + paint(GLYPH_UNKNOWN, "on_surface", tr("status_checking")) + barWidget.clearTooltip() + return + end + if s.changing == true then + paint("refresh", "secondary", tr("status_switching")) + barWidget.setTooltip(tr("tooltip_switching")) + return + end + if s.error ~= nil then + paint(GLYPH_UNKNOWN, "error", s.error) + barWidget.clearTooltip() + return + end + local role = s.current.id ~= "default" and "primary" or "on_surface" + paint(s.current.glyph, role, s.current.label) + local servers = s.current.ip + if servers == "" then + servers = s.servers ~= "" and s.servers or tr("status_auto") + end + barWidget.setTooltip({ + { key = tr("tooltip_provider"), value = s.current.label }, + { key = tr("tooltip_servers"), value = servers }, + { key = tr("tooltip_actions"), value = tr("tooltip_hints") }, + }) +end + +noctalia.state.watch(STATE_KEY, function(value) + if type(value) == "table" then + snapshot = value + render() + end +end) + +snapshot = noctalia.state.get(STATE_KEY) +render() diff --git a/dns-switcher/panel.luau b/dns-switcher/panel.luau new file mode 100644 index 0000000..2741d3c --- /dev/null +++ b/dns-switcher/panel.luau @@ -0,0 +1,345 @@ +--!nonstrict +-- dns-switcher — provider panel. Pure renderer over the shared state: the +-- service entry (service.luau) publishes "dns_state" and executes the +-- "apply_request" entries this panel emits. Picking a provider applies it +-- immediately (one nmcli change, no reactivation). + +local STATE_KEY = "dns_state" +local REQUEST_KEY = "apply_request" + +local RESOLVE_TIMEOUT_MS = 4000 + +-- Read out of the plugin's own manifest (readFile resolves a relative path +-- against the plugin directory), so the header cannot drift from the version +-- the store shows. Empty when unreadable — a missing version is not worth an +-- error line in the panel. +local pluginVersion = (function() + local text = noctalia.readFile("plugin.toml") + if type(text) ~= "string" then + return "" + end + return ("\n" .. text):match('\nversion%s*=%s*"([^"]+)"') or "" +end)() + +local snapshot = nil -- last state published by the service + +-- DNS lookup tester (bottom of the panel). Entirely panel-local: it neither +-- reads nor writes the shared state, since it never changes what DNS is +-- configured — it only asks "does a name resolve through the DNS that IS +-- configured right now", which is exactly the question this plugin otherwise +-- has no way to answer. +local resolveQuery = "" +local resolveBusy = false +local resolveError = nil +local resolveResult = nil -- { tool, serverIp?, serverLabel?, lines? (dig), raw? (nslookup) } + +local render + +local function tr(key, args) + return noctalia.tr(key, args) +end + +local function trim(value) + return (value:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function shellQuote(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +-- The nonce is monotonic across writers (widget instances and the panel): +-- each seeds from the last request already in the shared state. +local function requestApply(entry) + if snapshot == nil or snapshot.changing == true then + return + end + local prev = noctalia.state.get(REQUEST_KEY) + local nonce = (type(prev) == "table" and tonumber(prev.nonce) or 0) + 1 + noctalia.state.set(REQUEST_KEY, { nonce = nonce, id = entry.id, label = entry.label, ip = entry.ip }) +end + +-- A conservative hostname shape (letters/digits/dot/hyphen, no leading dot or +-- hyphen, 253 chars max — the DNS wire-format limit). shellQuote() below is +-- the actual safety net; this only keeps an obviously-wrong query from ever +-- reaching a shell as a "valid enough" no-op. +local function isValidHostname(name) + return name ~= "" and #name <= 253 and name:match("^[%w][%w%.%-]*$") ~= nil +end + +-- Name + first address of the currently active provider, if it has an +-- address of its own (built-in/custom entries do; "Default (ISP)" does not). +-- nil means "ask the system resolver", not "no server" — the lookup still +-- runs either way. +local function activeServerInfo() + if snapshot == nil or snapshot.current == nil or type(snapshot.current.ip) ~= "string" then + return nil + end + local ip = snapshot.current.ip:match("%S+") + if ip == nil then + return nil + end + return { ip = ip, label = snapshot.current.label } +end + +local function parseDigShort(stdout) + local lines = {} + for line in stdout:gmatch("[^\n]+") do + local clean = trim(line) + if clean ~= "" then + table.insert(lines, clean) + end + end + return lines +end + +-- Prefers `dig +short` (one line per answer, trivial to parse); falls back to +-- raw `nslookup` output, shown verbatim, when dig is not installed. Both are +-- pointed at the active provider's own address when it has one, so the +-- answer reflects that resolver specifically rather than whatever the system +-- resolver layer (systemd-resolved, etc.) does with it. +local function runResolve() + local name = trim(resolveQuery) + if not isValidHostname(name) then + resolveError = tr("resolve_invalid") + resolveResult = nil + render() + return + end + + local server = activeServerInfo() + local useDig = noctalia.commandExists("dig") + local useNslookup = not useDig and noctalia.commandExists("nslookup") + if not useDig and not useNslookup then + resolveError = tr("resolve_no_tool") + resolveResult = nil + render() + return + end + + resolveBusy = true + resolveError = nil + resolveResult = nil + render() + + local tool = useDig and "dig" or "nslookup" + local cmd + if useDig then + cmd = "dig +time=3 +tries=1 +short " + .. (server ~= nil and ("@" .. shellQuote(server.ip) .. " ") or "") + .. shellQuote(name) + else + cmd = "nslookup " .. shellQuote(name) .. (server ~= nil and (" " .. shellQuote(server.ip)) or "") + end + + local ok = noctalia.runAsync(cmd, function(result) + resolveBusy = false + if result.timedOut then + resolveError = tr("resolve_timeout") + elseif tool == "dig" then + local lines = parseDigShort(result.stdout or "") + if #lines == 0 then + resolveError = tr("resolve_empty") + else + resolveResult = { + tool = tool, + serverIp = server ~= nil and server.ip or nil, + serverLabel = server ~= nil and server.label or nil, + lines = lines, + } + end + else + local raw = trim(result.stdout or "") + if raw == "" then + resolveError = tr("resolve_empty") + else + resolveResult = { + tool = tool, + serverIp = server ~= nil and server.ip or nil, + serverLabel = server ~= nil and server.label or nil, + raw = raw, + } + end + end + render() + end, RESOLVE_TIMEOUT_MS) + if not ok then + resolveBusy = false + resolveError = tr("resolve_spawn_failed") + render() + end +end + +-- A row names its provider and the addresses it would set, so picking one is not +-- a guess about what it does. The ISP default has no fixed addresses -- whatever +-- the LAN hands out -- so it stays a bare label, and the footer shows what is +-- actually in use. A button carries one run of text, so the two are joined with a +-- separator rather than styled apart. +local function providerRow(entry) + local active = snapshot.current ~= nil and snapshot.current.id == entry.id + local text = entry.label + if entry.ip ~= nil and entry.ip ~= "" then + text = text .. " · " .. entry.ip + end + return ui.button({ + key = "dns-" .. entry.id .. (active and "-on" or ""), + glyph = entry.glyph, + text = text, + variant = active and "primary" or "ghost", + contentAlign = "start", + onClick = function() + if not active then + requestApply(entry) + end + end, + }) +end + +local function statusFooter() + if snapshot.changing == true then + return ui.label({ text = tr("status_switching"), fontSize = 11, color = "secondary" }) + end + local servers = snapshot.servers + if servers == nil or servers == "" then + servers = tr("status_auto") + end + local caption = servers + if snapshot.conName ~= nil and snapshot.conName ~= "" then + caption = snapshot.conName .. " · " .. servers + end + return ui.row({ gap = 6, align = "center" }, { + ui.label({ text = caption, fontSize = 11, color = "on_surface_variant", flexGrow = 1 }), + ui.button({ glyph = "copy", variant = "ghost", tooltip = tr("tip_copy"), onClick = "onCopyServers" }), + }) +end + +-- Bottom-of-panel lookup tester: a name, a button, and whatever the active +-- resolver (or the system one, with no provider address of its own) answers. +local function resolveSection() + local children = { + ui.separator({}), + ui.label({ key = "resolve-title", text = tr("resolve_title"), fontSize = 12, fontWeight = "semibold", color = "on_surface" }), + ui.row({ key = "resolve-row", gap = 6, align = "center" }, { + ui.input({ + key = "resolve-input", + value = resolveQuery, + placeholder = tr("resolve_placeholder"), + flexGrow = 1, + onChange = function(value) + resolveQuery = value + end, + onSubmit = function(value) + resolveQuery = value + runResolve() + end, + }), + ui.button({ + key = "resolve-go" .. (resolveBusy and "-off" or ""), + glyph = "search", + variant = "primary", + enabled = not resolveBusy, + tooltip = tr("tip_resolve"), + onClick = function() + runResolve() + end, + }), + }), + } + + if resolveBusy then + table.insert(children, ui.label({ key = "resolve-status", text = tr("resolve_busy"), fontSize = 11, color = "secondary" })) + elseif resolveError ~= nil then + table.insert(children, ui.label({ key = "resolve-status", text = resolveError, fontSize = 11, color = "error", maxLines = 2 })) + elseif resolveResult ~= nil then + local via + if resolveResult.serverIp ~= nil then + via = tr("resolve_via", { name = resolveResult.serverLabel or resolveResult.serverIp, server = resolveResult.serverIp }) + else + via = tr("resolve_via_system") + end + local body = resolveResult.lines ~= nil and table.concat(resolveResult.lines, ", ") or resolveResult.raw + -- The answer first, then which server gave it — the label is set + -- apart with a slightly larger font since it names what answered. + table.insert(children, ui.label({ key = "resolve-body", text = body, fontSize = 14, color = "primary", maxLines = 6 })) + table.insert(children, ui.label({ key = "resolve-via", text = via, fontSize = 13, fontWeight = "medium", color = "on_surface_variant" })) + end + + return ui.column({ key = "resolve", gap = 6 }, children) +end + +render = function() + local children = { + ui.row({ gap = 8, align = "center" }, { + ui.label({ + key = "title", + text = tr("title"), + fontSize = 16, + fontWeight = "bold", + color = "on_surface", + }), + -- Version off the manifest, small and dimmed: it answers "which + -- build am I running" without competing with the title. The spacer + -- rather than a flexGrow title keeps the two together on the left. + ui.label({ key = "version", text = pluginVersion, fontSize = 10, color = "on_surface_variant" }), + ui.spacer({ key = "gap", flexGrow = 1 }), + ui.button({ glyph = "settings", variant = "ghost", tooltip = tr("tip_settings"), onClick = "onOpenSettings" }), + ui.button({ glyph = "close", variant = "ghost", tooltip = tr("tip_close"), onClick = "onClosePanel" }), + }), + } + + if snapshot == nil or snapshot.list == nil then + table.insert(children, ui.label({ text = tr("status_checking"), color = "on_surface_variant" })) + elseif snapshot.error ~= nil then + table.insert(children, ui.label({ text = snapshot.error, color = "error" })) + else + local rows = {} + for _, entry in ipairs(snapshot.list) do + table.insert(rows, providerRow(entry)) + end + table.insert(children, ui.scroll({ flexGrow = 1, gap = 4 }, rows)) + table.insert(children, statusFooter()) + end + + -- Always present, independent of detection state: it asks a question + -- about a name, not about which provider is active. + table.insert(children, resolveSection()) + + panel.render(ui.column({ flexGrow = 1, gap = 12, align = "stretch" }, children)) +end + +function onOpen(_context) + snapshot = noctalia.state.get(STATE_KEY) + render() +end + +noctalia.state.watch(STATE_KEY, function(value) + if type(value) == "table" then + snapshot = value + render() + end +end) + +function onCopyServers() + if snapshot == nil then + return + end + local text = snapshot.servers + if (text == nil or text == "") and snapshot.current ~= nil then + text = snapshot.current.ip + end + if text == nil or text == "" then + return + end + noctalia.copyToClipboard(text, "text/plain") + noctalia.notify(tr("title"), tr("copied", { ip = text })) +end + +-- Opens the settings window on this plugin's own page (the host supplies the +-- plugin id, so a plugin can only ever open its own). It closes the panel on +-- the way, which is why nothing is rendered afterwards. +function onOpenSettings() + noctalia.openSettings() +end + +function onClosePanel() + panel.close() +end diff --git a/dns-switcher/plugin.toml b/dns-switcher/plugin.toml new file mode 100644 index 0000000..fbe8193 --- /dev/null +++ b/dns-switcher/plugin.toml @@ -0,0 +1,127 @@ +# DNS Switcher — switch the system DNS (NetworkManager) from the bar. +# Based on Ronin-CK's v4 "DNS Switcher", rebuilt on the v5 Luau API: the bar +# widget shows the active provider and toggles a panel listing the configured +# providers; picking one applies it immediately via `nmcli con mod` + +# `nmcli device reapply` (no reactivation, the connection never drops). + +id = "nightwatch75/dns-switcher" +name = "DNS Switcher" +version = "0.1.1" +plugin_api = 17 +author = "nightwatch75" +license = "MIT" +# dig (bind-tools/dnsutils) is preferred for the panel's lookup tester; +# nslookup is the fallback when dig is missing. Neither is required for the +# core switch/apply feature, only for that one panel section. +dependencies = ["networkmanager", "dig", "nslookup"] +tags = ["bar", "panel", "service", "network", "privacy"] +icon = "world" +description = "Switch the system DNS between popular providers, custom servers, or the ISP default (NetworkManager)." + +# Plugin-level settings: shared by the widget engine (detection/apply) and +# the panel (provider list). Custom servers are five `Name = 1.2.3.4 5.6.7.8` +# string fields, custom_1..custom_5; see the comment on them for why. + +[[setting]] +key = "providers" +type = "string" +label_key = "settings.providers.label" +description_key = "settings.providers.description" +default = "google,cloudflare,opendns,adguard,quad9" + +# Five separate string settings rather than one list, because Noctalia's list +# editor shows an existing row as a static label with remove/up/down buttons: a +# typo means deleting the row and typing it again. A string renders as a text +# field, so each server can be corrected in place. Five covers any realistic +# number of custom resolvers. +# +# The keys are new names on purpose, not a retyped custom_dns. A stored value +# whose type no longer matches its declaration makes Noctalia reject every +# settings write, so the whole file stops saving; a key that simply no longer +# exists is just a warning. That is what 0.0.8 was released to fix. +[[setting]] +key = "custom_1" +type = "string" +label_key = "settings.custom_1.label" +description_key = "settings.custom.description" +default = "" + +[[setting]] +key = "custom_2" +type = "string" +label_key = "settings.custom_2.label" +description_key = "settings.custom.description" +default = "" + +[[setting]] +key = "custom_3" +type = "string" +label_key = "settings.custom_3.label" +description_key = "settings.custom.description" +default = "" + +[[setting]] +key = "custom_4" +type = "string" +label_key = "settings.custom_4.label" +description_key = "settings.custom.description" +default = "" + +[[setting]] +key = "custom_5" +type = "string" +label_key = "settings.custom_5.label" +description_key = "settings.custom.description" +default = "" + +[[setting]] +key = "poll_seconds" +type = "int" +label_key = "settings.poll_seconds.label" +description_key = "settings.poll_seconds.description" +default = 10 +min = 2 +max = 120 + +[[setting]] +key = "privilege_command" +type = "string" +label_key = "settings.privilege_command.label" +description_key = "settings.privilege_command.description" +default = "" +advanced = true + +[[service]] +id = "service" +entry = "service.luau" + +[[panel]] +id = "panel" +entry = "panel.luau" +# Wide enough for the longest built-in row, "OpenDNS · 208.67.222.222 +# 208.67.220.220", without eliding the addresses. Height grew in 0.0.13 to +# fit the lookup tester below the provider list without starving it. +width = 430 +height = 460 +placement = "attached" +open_near_click = true + +[[widget]] +id = "dns-switcher" +entry = "dns-switcher.luau" + + # Declared here rather than hard-coded in onClick/onRightClick, so the user + # can remap any of them from the bar's own gesture settings. "right"/ + # "scroll_*" are the exact IPC lines service.luau's onIpc documents. + [widget.actions] + left = "panel-toggle nightwatch75/dns-switcher:panel" + right = "plugin nightwatch75/dns-switcher:service all apply default" + scroll_up = "plugin nightwatch75/dns-switcher:service all cycle next" + scroll_down = "plugin nightwatch75/dns-switcher:service all cycle prev" + + [[widget.setting]] + key = "show_label" + type = "bool" + label_key = "settings.show_label.label" + description_key = "settings.show_label.description" + default = true diff --git a/dns-switcher/service.luau b/dns-switcher/service.luau new file mode 100644 index 0000000..066ae92 --- /dev/null +++ b/dns-switcher/service.luau @@ -0,0 +1,497 @@ +--!nonstrict +-- dns-switcher — singleton DNS engine (detection + apply). +-- +-- Runs once regardless of how many bars show the widget. The widget and the +-- panel are pure renderers wired through the plugin's shared state: +-- engine publishes "dns_state" = { nonce, current?, servers, conName, +-- changing, error?, list } +-- UI entries send "apply_request" = { nonce, id, label?, ip? } +-- +-- Detection reads the active connection's ipv4.dns / ipv4.ignore-auto-dns +-- profile settings (manual DNS is matched against the providers, otherwise +-- shown as "Custom"); without a manual DNS the state is the ISP default. +-- Applying runs `nmcli con mod … && nmcli device reapply ` — +-- reapply pushes the change onto the live connection without reactivating +-- it, so the network never drops. The privilege command is empty by default: +-- NetworkManager's polkit policy lets active local sessions modify system +-- connections on most desktop distros. + +local STATE_KEY = "dns_state" +local REQUEST_KEY = "apply_request" + +local BUILTIN = { + { id = "google", label = "Google", ip = "8.8.8.8 8.8.4.4", glyph = "brand-google" }, + { id = "cloudflare", label = "Cloudflare", ip = "1.1.1.1 1.0.0.1", glyph = "cloud" }, + { id = "opendns", label = "OpenDNS", ip = "208.67.222.222 208.67.220.220", glyph = "world" }, + { id = "adguard", label = "AdGuard", ip = "94.140.14.14 94.140.15.15", glyph = "shield-check" }, + { id = "quad9", label = "Quad9", ip = "9.9.9.9 149.112.112.112", glyph = "lock" }, +} +local GLYPH_DEFAULT = "router" -- ISP / connection default +local GLYPH_UNKNOWN = "globe" -- unrecognized manual DNS +local GLYPH_CUSTOM = "server" -- user-defined servers + +local current = nil -- provider entry detected as active; nil = still checking +local lastSeen = "" -- runtime resolver IPs from the last successful poll +local conName = "" -- active connection name, shown in the panel footer +local errMsg = nil -- sticky error label (no nmcli / no connection) +local pollTicks = 0 +local changing = false +local checkInFlight = false +local nmcliMissing = false +local stateNonce = 0 + +local function cfg(key) + return noctalia.getConfig(key) +end + +local function tr(key, args) + return noctalia.tr(key, args) +end + +local function trim(value) + return (value:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function shellQuote(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +local function pollSeconds() + return math.max(2, tonumber(cfg("poll_seconds")) or 10) +end + +local function isValidIp(ip) + local a, b, c, d = ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$") + if a == nil then + return false + end + for _, part in ipairs({ a, b, c, d }) do + if #part > 3 or tonumber(part) > 255 then + return false + end + end + return true +end + +-- One or two space-separated IPv4 addresses, same shape nmcli accepts. +local function validDnsSpec(spec) + local count = 0 + for token in spec:gmatch("%S+") do + count += 1 + if count > 2 or not isValidIp(token) then + return false + end + end + return count > 0 +end + +-- Custom servers: five separate `Name = 1.2.3.4 5.6.7.8` string settings rather +-- than one list, so a wrong address can be corrected in the field instead of +-- deleted and retyped -- Noctalia's list editor has no per-row edit. Slot order +-- is panel order. The keys are listed as literals so `noctalia plugins lint` can +-- still match them against the manifest. +local CUSTOM_KEYS = { "custom_1", "custom_2", "custom_3", "custom_4", "custom_5" } + +-- The raw slots are the cache signature, so an invalid entry is logged when a +-- slot actually changes rather than on every poll. +local customCacheSig = nil +local customCacheList = {} +local function customProviders() + local rows = {} + for _, key in ipairs(CUSTOM_KEYS) do + local row = cfg(key) + if type(row) == "string" and trim(row) ~= "" then + table.insert(rows, row) + end + end + local sig = table.concat(rows, "\n") + if sig == customCacheSig then + return customCacheList + end + customCacheSig = sig + customCacheList = {} + for _, row in ipairs(rows) do + -- The name is everything before the first '='; the rest is the address + -- list. A name may therefore not contain '=' itself — such a row fails + -- the address check below and is skipped with a log line. + local name, spec = row:match("^([^=]*)=(.*)$") + local cleanName = trim(name or "") + local cleanSpec = trim(spec or "") + if cleanName ~= "" and validDnsSpec(cleanSpec) then + table.insert(customCacheList, { id = "custom:" .. cleanName, label = cleanName, ip = cleanSpec, glyph = GLYPH_CUSTOM }) + elseif trim(row) ~= "" then + noctalia.log("dns-switcher: ignoring invalid custom server row '" .. row .. "'") + end + end + return customCacheList +end + +local function enabledBuiltins() + local raw = cfg("providers") + if type(raw) ~= "string" then + return BUILTIN + end + -- Cleared setting = no built-ins: only custom servers and the ISP default. + if trim(raw) == "" then + return {} + end + local wanted = {} + for id in raw:gmatch("[^,%s]+") do + wanted[id:lower()] = true + end + local list = {} + for _, provider in ipairs(BUILTIN) do + if wanted[provider.id] then + table.insert(list, provider) + end + end + return list +end + +local function defaultEntry() + return { id = "default", label = tr("status_default"), ip = "", glyph = GLYPH_DEFAULT } +end + +-- Panel order: enabled built-ins, then custom servers, then the ISP default. +local function providerList() + local list = {} + for _, provider in ipairs(enabledBuiltins()) do + table.insert(list, provider) + end + for _, provider in ipairs(customProviders()) do + table.insert(list, provider) + end + table.insert(list, defaultEntry()) + return list +end + +local function publish() + stateNonce += 1 + noctalia.state.set(STATE_KEY, { + nonce = stateNonce, + current = current, + servers = lastSeen, + conName = conName, + changing = changing, + error = errMsg, + list = providerList(), + }) +end + +-- Picks the active connection: prefer wifi/ethernet, else the first +-- non-loopback entry. Emits KEY=value lines parsed by the poll callback; +-- UUID (colon-free) identifies the connection, DEV drives the reapply. +local DETECT_CMD = [[ +ACT=$(LC_ALL=C nmcli -t -f TYPE,DEVICE,UUID,NAME connection show --active 2>/dev/null) +LINE=$(printf '%s\n' "$ACT" | grep -E '^(802-11-wireless|802-3-ethernet):' | head -n 1) +[ -n "$LINE" ] || LINE=$(printf '%s\n' "$ACT" | grep -v '^loopback:' | head -n 1) +[ -n "$LINE" ] || { echo 'ERR=noconn'; exit 0; } +DEV=$(printf '%s' "$LINE" | cut -d: -f2) +UUID=$(printf '%s' "$LINE" | cut -d: -f3) +echo "NAME=$(printf '%s' "$LINE" | cut -d: -f4-)" +echo "CFG=$(LC_ALL=C nmcli -g ipv4.dns connection show uuid "$UUID" 2>/dev/null)" +echo "AUTO=$(LC_ALL=C nmcli -g ipv4.ignore-auto-dns connection show uuid "$UUID" 2>/dev/null)" +echo "RUN=$(nmcli -g IP4.DNS device show "$DEV" 2>/dev/null | tr '\n' ' ')" +]] + +local function updateDnsState(stdout) + local fields = {} + for line in stdout:gmatch("[^\n]+") do + local key, value = line:match("^(%u+)=(.*)$") + if key ~= nil then + fields[key] = value + end + end + + if fields.ERR == "noconn" then + current = nil + errMsg = tr("err_no_connection") + return + end + errMsg = nil + conName = fields.NAME or "" + + local runtime = {} + for token in (fields.RUN or ""):gmatch("%d+%.%d+%.%d+%.%d+") do + if isValidIp(token) then + table.insert(runtime, token) + end + end + lastSeen = table.concat(runtime, " ") + + -- Manual DNS lives in the profile (ipv4.dns + ignore-auto-dns yes); + -- anything else is the connection default, whatever the LAN hands out. + local manual = (fields.AUTO == "yes") + local cfgIps = {} + for token in (fields.CFG or ""):gmatch("%d+%.%d+%.%d+%.%d+") do + if isValidIp(token) then + table.insert(cfgIps, token) + end + end + if not manual or #cfgIps == 0 then + current = defaultEntry() + return + end + + -- Customs take precedence over built-ins, so a custom entry that reuses + -- a public IP (e.g. a forwarder) keeps its own label. + local lookup = {} + for _, provider in ipairs(BUILTIN) do + for ip in provider.ip:gmatch("%S+") do + lookup[ip] = provider + end + end + for _, provider in ipairs(customProviders()) do + for ip in provider.ip:gmatch("%S+") do + lookup[ip] = provider + end + end + for _, ip in ipairs(cfgIps) do + if lookup[ip] ~= nil then + current = lookup[ip] + return + end + end + current = { + id = "unknown", + label = tr("status_custom", { ip = cfgIps[1] }), + ip = table.concat(cfgIps, " "), + glyph = GLYPH_UNKNOWN, + } +end + +local function pollNow() + if checkInFlight or changing or nmcliMissing then + return + end + checkInFlight = true + local ok = noctalia.runAsync(DETECT_CMD, function(result) + checkInFlight = false + if result.exitCode == 0 and not result.timedOut then + updateDnsState(result.stdout) + elseif current == nil then + errMsg = tr("status_no_nmcli") + end + publish() + end, 4000) + if not ok then + checkInFlight = false + end +end + +local function apply(provider) + if changing then + return + end + -- No "already active" short-circuit: `current` is only ever refreshed by + -- the async poll, so it can be stale by up to a full pollSeconds() window + -- (shorter but still nonzero right after another apply/cycle). Skipping + -- here on a stale match would silently drop a legitimate request instead + -- of just doing one harmless idempotent nmcli round trip. + if provider.ip ~= "" and not validDnsSpec(provider.ip) then + noctalia.notifyError(tr("title"), tr("err_invalid_ip", { ip = provider.ip })) + return + end + + -- Safety net mirroring the v4 plugin: the spec is already validated, the + -- gsub guarantees nothing shell-relevant ever reaches the command line. + local safeIp = provider.ip:gsub("[^%d%. ]", "") + local mods + if safeIp == "" then + mods = 'ipv4.dns "" ipv4.ignore-auto-dns no' + else + mods = 'ipv4.dns "' .. safeIp .. '" ipv4.ignore-auto-dns yes' + end + local inner = 'ACT=$(LC_ALL=C nmcli -t -f TYPE,DEVICE,UUID connection show --active 2>/dev/null); ' + .. [[LINE=$(printf '%s\n' "$ACT" | grep -E '^(802-11-wireless|802-3-ethernet):' | head -n 1); ]] + .. [=[[ -n "$LINE" ] || LINE=$(printf '%s\n' "$ACT" | grep -v '^loopback:' | head -n 1); ]=] + .. [=[[ -n "$LINE" ] || exit 9; ]=] + .. 'DEV=$(printf \'%s\' "$LINE" | cut -d: -f2); ' + .. 'UUID=$(printf \'%s\' "$LINE" | cut -d: -f3); ' + .. 'nmcli con mod "$UUID" ' .. mods .. ' && nmcli device reapply "$DEV"' + + local priv = cfg("privilege_command") + if type(priv) ~= "string" then + priv = "" + end + priv = trim(priv) + local cmd = inner + if priv ~= "" then + cmd = priv .. " sh -c " .. shellQuote(inner) + end + + changing = true + publish() + -- 60s budget so an eventual polkit password prompt can be answered. + local ok = noctalia.runAsync(cmd, function(result) + changing = false + if result.exitCode == 0 and not result.timedOut then + noctalia.notify(tr("title"), tr("applied", { name = provider.label })) + elseif result.timedOut then + noctalia.notifyError(tr("title"), tr("err_timeout")) + elseif result.exitCode == 9 then + noctalia.notifyError(tr("title"), tr("err_no_connection")) + elseif result.exitCode == 126 then + noctalia.notifyError(tr("title"), tr("err_auth_dismissed")) + else + local detail = trim(result.stderr or "") + if #detail > 200 then + detail = detail:sub(1, 200) .. "…" + end + local body = tr("err_apply_failed") + if detail:lower():find("not authorized") or detail:lower():find("insufficient") then + body = tr("err_not_authorized") + elseif detail ~= "" then + body = body .. "\n" .. detail + end + noctalia.notifyError(tr("title"), body) + end + publish() + pollNow() + end, 60000) + if not ok then + changing = false + noctalia.notifyError(tr("title"), tr("err_spawn")) + publish() + end +end + +-- Resolves an apply request against the current provider list (so config +-- edits win over stale request payloads), falling back to the request's own +-- label/ip for entries that just left the list. +local function applyById(id, label, ip) + for _, entry in ipairs(providerList()) do + if entry.id == id then + apply(entry) + return true + end + end + if id == "default" or (type(ip) == "string" and validDnsSpec(ip)) then + apply({ id = id, label = label or id, ip = id == "default" and "" or ip }) + return true + end + return false +end + +-- Steps the active provider to its neighbour in providerList() (wrapping at +-- either end). Backs the bar widget's scroll_up/scroll_down gesture default; +-- with no detected current provider yet, "next" starts at the first entry +-- rather than doing nothing. +local function cycleTo(direction) + local list = providerList() + if #list == 0 then + return + end + local index = 1 + if current ~= nil then + for i, entry in ipairs(list) do + if entry.id == current.id then + index = i + break + end + end + index = index + (direction == "prev" and -1 or 1) + if index < 1 then + index = #list + elseif index > #list then + index = 1 + end + end + apply(list[index]) +end + +-- Apply requests from the widget/panel. The nonce is monotonic across +-- writers (each seeds from the last request) and guards against replaying a +-- stale request after a hot reload of this script. +local handledNonce = 0 +do + local pendingReq = noctalia.state.get(REQUEST_KEY) + if type(pendingReq) == "table" and type(pendingReq.nonce) == "number" then + handledNonce = pendingReq.nonce + end +end +noctalia.state.watch(REQUEST_KEY, function(req) + if type(req) ~= "table" or type(req.nonce) ~= "number" or req.nonce <= handledNonce then + return + end + handledNonce = req.nonce + if nmcliMissing then + return + end + applyById(req.id, req.label, req.ip) +end) + +-- Scriptable switching: +-- noctalia msg plugin nightwatch75/dns-switcher:service all apply +-- where is a provider id ("google", "default", "custom:"…); +-- "poll" forces an immediate re-detection; "cycle next"/"cycle prev" steps to +-- the neighbouring provider in the panel's own order (this is what the bar +-- widget's scroll_up/scroll_down gesture defaults send, [widget.actions] in +-- plugin.toml). +function onIpc(event, payload) + if nmcliMissing then + return + end + if event == "poll" then + pollNow() + elseif event == "apply" then + local id = type(payload) == "string" and trim(payload) or "" + if not applyById(id) then + noctalia.notifyError(tr("title"), tr("err_unknown_provider", { id = id })) + end + elseif event == "cycle" then + local direction = type(payload) == "string" and trim(payload) or "next" + cycleTo(direction) + end +end + +-- plugin_api >= 17. A hot reload (editing this file) tears this VM down and a +-- fresh one starts moments later, which republishes its own state on load — +-- nothing to do for "reload". On disable/uninstall/shutdown mid-apply, +-- though, nothing ever republishes again: without this, every widget/panel +-- instance reading the shared state would stay frozen on "changing" forever. +-- The in-flight nmcli command is not ours to cancel either way — runAsync +-- hands back no killable handle, and it is idempotent (con mod + reapply), so +-- letting it finish in the background is harmless. This only stops +-- describing it as in progress. The DNS choice itself is never touched here: +-- it lives in the NetworkManager connection profile, independent of whether +-- this plugin is enabled at all. +function onExit(_signal, reason) + if reason == "reload" then + return + end + if changing then + changing = false + publish() + end +end + +function onConfigChanged() + -- Settings edits reshape the provider list and may relabel the current + -- entry; re-publish and re-check right away. + publish() + pollNow() +end + +function update() + if nmcliMissing then + return + end + pollTicks += 1 + if pollTicks >= pollSeconds() then + pollTicks = 0 + pollNow() + end +end + +noctalia.setUpdateInterval(1000) +if not noctalia.commandExists("nmcli") then + nmcliMissing = true + errMsg = tr("status_no_nmcli") + current = nil + publish() + noctalia.notifyError(tr("title"), tr("err_no_nmcli")) +else + publish() + pollNow() +end diff --git a/dns-switcher/thumbnail.webp b/dns-switcher/thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..b86d5cb2fd9cc78b4423ebc4d999d8dbc55e4fe2 GIT binary patch literal 73880 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 literal 0 HcmV?d00001 diff --git a/dns-switcher/translations/en.json b/dns-switcher/translations/en.json new file mode 100644 index 0000000..7495c51 --- /dev/null +++ b/dns-switcher/translations/en.json @@ -0,0 +1,75 @@ +{ + "applied": "DNS switched to {name}", + "copied": "DNS servers copied: {ip}", + "err_apply_failed": "Failed to apply the DNS settings", + "err_auth_dismissed": "Authorization dismissed", + "err_invalid_ip": "Invalid DNS address: {ip}", + "err_no_connection": "No active NetworkManager connection", + "err_no_nmcli": "nmcli not found — install NetworkManager", + "err_not_authorized": "NetworkManager refused the change (not authorized). Set the Privilege command setting to 'pkexec' or 'sudo -n'.", + "err_spawn": "Could not run nmcli", + "err_timeout": "Timed out applying the DNS change (authorization prompt left unanswered?)", + "err_unknown_provider": "Unknown provider id: {id}", + "resolve_busy": "Resolving…", + "resolve_empty": "No records found", + "resolve_invalid": "Enter a valid hostname", + "resolve_no_tool": "Neither dig nor nslookup is installed", + "resolve_placeholder": "hostname, e.g. example.com", + "resolve_spawn_failed": "Could not run the lookup", + "resolve_timeout": "Timed out", + "resolve_title": "DNS lookup", + "resolve_via": "via {name} ({server})", + "resolve_via_system": "via system resolver", + "settings": { + "custom": { + "description": "One custom resolver, written 'Name = address', with one or two IPv4 addresses: 'Pi-hole = 192.168.1.5' or 'NextDNS = 45.90.28.0 45.90.30.0'. The panel lists the slots in order and skips the empty ones; a row that is not a valid address is skipped and logged." + }, + "custom_1": { + "label": "Custom server 1" + }, + "custom_2": { + "label": "Custom server 2" + }, + "custom_3": { + "label": "Custom server 3" + }, + "custom_4": { + "label": "Custom server 4" + }, + "custom_5": { + "label": "Custom server 5" + }, + "poll_seconds": { + "description": "How often the active DNS is re-read with nmcli.", + "label": "Poll interval (seconds)" + }, + "privilege_command": { + "description": "Prefix to run nmcli changes as root (e.g. 'pkexec', 'sudo -n'). Empty (default) runs nmcli directly — NetworkManager's polkit policy allows this for active local sessions on most desktop distros.", + "label": "Privilege command" + }, + "providers": { + "description": "Comma-separated ids included in the scroll cycle: google, cloudflare, opendns, adguard, quad9. Empty = none (custom servers and ISP default only).", + "label": "Built-in providers" + }, + "show_label": { + "description": "Show the provider name next to the glyph (off = glyph only).", + "label": "Show provider name" + } + }, + "status_auto": "automatic", + "status_checking": "Checking…", + "status_custom": "Custom ({ip})", + "status_default": "Default (ISP)", + "status_no_nmcli": "nmcli unavailable", + "status_switching": "Switching…", + "tip_close": "Close", + "tip_copy": "Copy the active DNS servers", + "tip_resolve": "Resolve this name with the active DNS", + "tip_settings": "Plugin settings", + "title": "DNS Switcher", + "tooltip_actions": "Actions", + "tooltip_hints": "click: panel · right: reset to ISP · scroll: cycle", + "tooltip_provider": "DNS", + "tooltip_servers": "Servers", + "tooltip_switching": "Applying the DNS change — a password prompt may appear" +}