From fc1c390a7f2bfa505c4bffbb0285ee082a0829ef Mon Sep 17 00:00:00 2001 From: Cleboost Date: Mon, 27 Jul 2026 19:56:18 +0200 Subject: [PATCH] feat(wifi-hotspot): add NetworkManager hotspot control plugin (#120) * feat(wifi-hotspot): add NetworkManager hotspot control plugin Expose bar toggle, panel, and IPC to start/stop a Wi-Fi hotspot and list connected clients. * refactor(hotspot): rename plugin directory to hotspot Align the folder name with the cleboost/hotspot plugin id. * refactor(hotspot): align plugin id and state keys with hotspot slug Update manifest id, IPC references, and shared state keys after the directory rename. --- hotspot/README.md | 65 +++++ hotspot/panel.luau | 208 +++++++++++++++ hotspot/plugin.toml | 63 +++++ hotspot/service.luau | 502 +++++++++++++++++++++++++++++++++++ hotspot/thumbnail.webp | Bin 0 -> 57032 bytes hotspot/translations/en.json | 75 ++++++ hotspot/widget.luau | 75 ++++++ 7 files changed, 988 insertions(+) create mode 100644 hotspot/README.md create mode 100644 hotspot/panel.luau create mode 100644 hotspot/plugin.toml create mode 100644 hotspot/service.luau create mode 100644 hotspot/thumbnail.webp create mode 100644 hotspot/translations/en.json create mode 100644 hotspot/widget.luau diff --git a/hotspot/README.md b/hotspot/README.md new file mode 100644 index 0000000..b801d42 --- /dev/null +++ b/hotspot/README.md @@ -0,0 +1,65 @@ +# Wi-Fi Hotspot + +Start and stop a NetworkManager Wi-Fi hotspot from the bar, inspect connected +devices in a panel, and control the service over IPC. + +## Plugin + +| Field | Value | +| --- | --- | +| ID | `cleboost/hotspot` | +| Entries | Bar widget: `toggle`; panel: `panel`; service: `hotspot` | + +## Requirements + +Install `nmcli` from NetworkManager, plus `iw` and `ip` from iproute2, on +`PATH`. The active user must be allowed to manage NetworkManager connections +(the default on most desktop setups). + +## Usage + +1. Open the plugin settings and set the hotspot name and password. +2. Add the `toggle` widget to a bar. +3. Left-click the widget to open the panel with the connected device list. +4. Right-click the widget to start or stop the hotspot. + +```sh +noctalia msg panel-toggle cleboost/hotspot:panel +``` + +Starting a hotspot may disconnect the Wi-Fi adapter from its current network +while the access point is active. + +## Settings + +| Setting | Type | Default | Description | +| --- | --- | --- | --- | +| `ssid` | `string` | `Noctalia Hotspot` | Network name broadcast to clients. | +| `password` | `string` | *(empty)* | WPA password. Must be at least 8 characters before starting. | +| `interface` | `string` | *(empty)* | Optional Wi-Fi interface such as `wlan0`. Auto-detected when empty. | +| `refresh_interval` | `int` | `5` | Seconds between status and client-list refreshes. | + +## IPC + +```sh +noctalia msg plugin cleboost/hotspot:hotspot all enable +noctalia msg plugin cleboost/hotspot:hotspot all disable +noctalia msg plugin cleboost/hotspot:hotspot all toggle +noctalia msg plugin cleboost/hotspot:hotspot all refresh +``` + +The `enable`, `disable`, and `toggle` events change the hotspot state. +`refresh` and `status` re-read NetworkManager without changing it. IPC events +take no payload. + +## Notes + +- The plugin uses `nmcli device wifi hotspot` to create the access point and + `nmcli connection down` to stop the active AP profile. +- Connected devices are discovered with `iw station dump` and `ip neigh`. +- The plugin makes no network requests and writes no user content to disk. +- Notifications use the host `notification-show` IPC with Wi-Fi glyphs (`wifi`, + `wifi-off`, `alert-circle`) because `noctalia.notify()` does not accept a + custom icon yet. +- Hotspots started outside Noctalia are detected when NetworkManager reports an + active `802-11-wireless` connection in `ap` mode. diff --git a/hotspot/panel.luau b/hotspot/panel.luau new file mode 100644 index 0000000..2097d45 --- /dev/null +++ b/hotspot/panel.luau @@ -0,0 +1,208 @@ +--!nonstrict + +local opened = false +local render + +local status = noctalia.state.get("hotspot_status") or { + active = false, + busy = false, + available = true, + clients = {}, + clientCount = 0, + error = "", +} + +local function sendCommand(action) + noctalia.state.set("hotspot_command", { action = action }) +end + +local function phaseDetails() + if status.busy == true then + if status.active == true then + return noctalia.tr("panel.status.stopping_title"), noctalia.tr("panel.status.stopping_message"), "on_surface_variant", "loader-2" + end + return noctalia.tr("panel.status.starting_title"), noctalia.tr("panel.status.starting_message"), "on_surface_variant", "loader-2" + end + + if status.active == true then + return noctalia.tr("panel.status.active_title"), noctalia.tr("panel.status.active_message", { + ssid = status.ssid or "?", + interface = status.interface or "?", + }), "tertiary", "router" + end + + if status.available == false then + local message = status.error + if message == nil or message == "" then + message = noctalia.tr("error.missing_dependencies") + end + return noctalia.tr("panel.status.error_title"), message, "error", "alert-circle" + end + + return noctalia.tr("panel.status.inactive_title"), noctalia.tr("panel.status.inactive_message"), "on_surface_variant", "wifi-off" +end + +local function clientRow(client) + local subtitle = client.mac + if client.ip ~= nil and client.ip ~= "" then + subtitle = client.ip .. " · " .. client.mac + end + + local trailing = nil + if client.signal ~= nil then + trailing = ui.label({ + text = tostring(client.signal) .. " dBm", + fontSize = 11, + color = "on_surface_variant", + }) + end + + return ui.row({ + align = "center", + gap = 10, + paddingH = 14, + paddingV = 10, + fill = "surface_variant/0.35", + radius = 10, + }, { + ui.glyph({ name = "device-mobile", size = 18, color = "primary" }), + ui.column({ gap = 2, flexGrow = 1 }, { + ui.label({ text = client.ip ~= "" and client.ip or client.mac, fontWeight = "medium", maxLines = 1 }), + ui.label({ text = subtitle, fontSize = 11, color = "on_surface_variant", maxLines = 1 }), + }), + trailing, + }) +end + +render = function() + local busy = status.busy == true + local active = status.active == true + local canToggle = status.available ~= false and not busy + local stateTitle, stateMessage, stateColor, stateGlyph = phaseDetails() + + local actionText = noctalia.tr("panel.action.start") + local actionGlyph = "player-play" + local actionVariant = "primary" + + if active then + actionText = noctalia.tr("panel.action.stop") + actionGlyph = "player-stop" + actionVariant = "destructive" + elseif busy then + actionText = active and noctalia.tr("panel.action.stopping") or noctalia.tr("panel.action.starting") + actionGlyph = "loader-2" + end + + local children = { + ui.row({ align = "center", justify = "space_between", gap = 8 }, { + ui.row({ align = "center", gap = 9, flexGrow = 1 }, { + ui.glyph({ name = "router", size = 18, color = "primary" }), + ui.label({ text = noctalia.tr("title"), fontSize = 16, fontWeight = "bold" }), + }), + ui.button({ + glyph = "refresh", + variant = "ghost", + tooltip = noctalia.tr("panel.refresh"), + enabled = not busy, + onClick = "onRefresh", + }), + ui.button({ + glyph = "close", + variant = "ghost", + tooltip = noctalia.tr("panel.close"), + onClick = "onCloseClicked", + }), + }), + ui.row({ + align = "center", + gap = 10, + fill = "surface_variant/0.45", + radius = 10, + padding = 12, + }, { + ui.glyph({ name = stateGlyph, size = 18, color = stateColor }), + ui.column({ gap = 3, flexGrow = 1 }, { + ui.label({ text = stateTitle, fontWeight = "medium", color = stateColor }), + ui.label({ text = stateMessage, color = "on_surface_variant", maxLines = 3 }), + }), + }), + } + + if status.gateway ~= nil and status.gateway ~= "" then + table.insert(children, ui.label({ + text = noctalia.tr("panel.gateway", { address = status.gateway }), + fontSize = 11, + color = "on_surface_variant", + })) + end + + table.insert(children, ui.button({ + text = actionText, + glyph = actionGlyph, + variant = actionVariant, + controlSize = "lg", + enabled = canToggle, + onClick = "onToggle", + })) + + table.insert(children, ui.row({ align = "center", justify = "space_between" }, { + ui.label({ + text = noctalia.tr("panel.clients_title"), + fontWeight = "medium", + color = "on_surface_variant", + }), + ui.label({ + text = tostring(status.clientCount or #(status.clients or {})), + fontSize = 11, + color = "on_surface_variant", + }), + })) + + local clients = status.clients or {} + if #clients == 0 then + table.insert(children, ui.label({ + text = active and noctalia.tr("panel.no_clients") or noctalia.tr("panel.clients_inactive"), + color = "on_surface_variant", + maxLines = 2, + padding = 12, + })) + else + local listChildren = {} + for _, client in ipairs(clients) do + table.insert(listChildren, clientRow(client)) + end + table.insert(children, ui.scroll({ flexGrow = 1, gap = 8 }, listChildren)) + end + + panel.render(ui.column({ flexGrow = 1, gap = 12 }, children)) +end + +noctalia.state.watch("hotspot_status", function(value) + if type(value) ~= "table" then return end + status = value + if opened then render() end +end) + +function onOpen(_context) + opened = true + status = noctalia.state.get("hotspot_status") or status + sendCommand("refresh") + render() +end + +function onClose() + opened = false +end + +function onCloseClicked() + panel.close() +end + +function onToggle() + if status.busy == true or status.available == false then return end + sendCommand("toggle") +end + +function onRefresh() + sendCommand("refresh") +end diff --git a/hotspot/plugin.toml b/hotspot/plugin.toml new file mode 100644 index 0000000..f501c3c --- /dev/null +++ b/hotspot/plugin.toml @@ -0,0 +1,63 @@ +id = "cleboost/hotspot" +name = "Wi-Fi Hotspot" +version = "1.0.0" +plugin_api = 9 +author = "Cleboost" +license = "MIT" +icon = "router" +description = "Start and stop a Wi-Fi hotspot from the bar, with connected device list and IPC control." +tags = ["network", "bar", "panel", "service", "utility", "system", "indicator"] +dependencies = ["nmcli", "iw", "ip"] + +[[setting]] +key = "ssid" +type = "string" +label_key = "settings.ssid.label" +description_key = "settings.ssid.description" +default = "Noctalia Hotspot" + +[[setting]] +key = "password" +type = "string" +label_key = "settings.password.label" +description_key = "settings.password.description" +default = "" + +[[setting]] +key = "interface" +type = "string" +label_key = "settings.interface.label" +description_key = "settings.interface.description" +default = "" + +[[setting]] +key = "refresh_interval" +type = "int" +label_key = "settings.refresh_interval.label" +description_key = "settings.refresh_interval.description" +default = 5 +min = 2 +max = 60 + +[[service]] +id = "hotspot" +entry = "service.luau" + +[[widget]] +id = "toggle" +entry = "widget.luau" + + [[widget.setting]] + key = "glyph" + type = "glyph" + label_key = "settings.glyph.label" + default = "router" + +[[panel]] +id = "panel" +entry = "panel.luau" +width = 420 +height = 520 +placement = "attached" +position = "top_right" +open_near_click = true diff --git a/hotspot/service.luau b/hotspot/service.luau new file mode 100644 index 0000000..c4232e8 --- /dev/null +++ b/hotspot/service.luau @@ -0,0 +1,502 @@ +--!nonstrict + +local STATUS_KEY = "hotspot_status" +local COMMAND_KEY = "hotspot_command" + +local busy = false +local revision = 0 +local available = noctalia.commandExists("nmcli") +local iwAvailable = noctalia.commandExists("iw") +local ipAvailable = noctalia.commandExists("ip") +local refreshing = false +local refreshAgain = false + +local status = { + active = false, + busy = false, + available = available, + error = "", + revision = 0, + ssid = "", + interface = "", + connection = "", + gateway = "", + clients = {}, + clientCount = 0, +} + +local refresh +local setActive + +local function publish(errorMessage) + revision += 1 + status.busy = busy + status.available = available + status.revision = revision + status.error = errorMessage or status.error or "" + status.clientCount = #(status.clients or {}) + noctalia.state.set(STATUS_KEY, status) +end + +local function shellQuote(value) + return "'" .. tostring(value):gsub("'", "'\\''") .. "'" +end + +local function showNotification(title, body, icon, urgency) + local payload = noctalia.json.encode({ + app_name = noctalia.tr("title"), + summary = title, + body = body or "", + icon = icon, + urgency = urgency, + }) + if payload == nil then + if urgency == "critical" then + noctalia.notifyError(title, body) + else + noctalia.notify(title, body) + end + return + end + + noctalia.runAsync("noctalia msg notification-show " .. shellQuote(payload)) +end + +local function runCommand(command, callback, timeoutMs) + local started = noctalia.runAsync(command, callback, timeoutMs or 15000) + if not started and type(callback) == "function" then + callback({ + exitCode = -1, + stdout = "", + stderr = noctalia.tr("error.command_start"), + timedOut = false, + }) + end + return started +end + +local function validateSettings() + local ssid = noctalia.string.trim(noctalia.getConfig("ssid") or "") + if ssid == "" then + return false, noctalia.tr("error.missing_ssid") + end + + local password = tostring(noctalia.getConfig("password") or "") + if #password < 8 then + return false, noctalia.tr("error.password_too_short") + end + + return true, ssid, password +end + +local function parseNmcliTriple(stdout, filterType) + local entries = {} + for line in tostring(stdout or ""):gmatch("[^\n]+") do + local name, entryType, extra = line:match("^([^:]+):([^:]+):([^:]*)$") + if entryType == filterType and name ~= nil and name ~= "" then + table.insert(entries, { name = name, device = extra or "" }) + end + end + return entries +end + +local function pickWifiInterface(callback) + local configured = noctalia.string.trim(noctalia.getConfig("interface") or "") + if configured ~= "" then + callback(configured) + return + end + + runCommand("nmcli -t -f DEVICE,TYPE,STATE device status", function(result) + local devices = parseNmcliTriple(result.stdout, "wifi") + callback(devices[1] and devices[1].name or nil) + end) +end + +local function parseIwStations(stdout) + local clients = {} + local current = nil + + for line in tostring(stdout or ""):gmatch("[^\n]+") do + local mac = line:match("^Station ([%x:]+)") + if mac ~= nil then + current = { mac = mac:lower(), ip = "", signal = nil } + table.insert(clients, current) + elseif current ~= nil then + local signal = line:match("signal:%s*([%-%d]+)") + if signal ~= nil then + current.signal = tonumber(signal) + end + end + end + + return clients +end + +local function parseIpNeigh(stdout) + local byMac = {} + for line in tostring(stdout or ""):gmatch("[^\n]+") do + local ip, mac = line:match("^([%d%.]+)%s+lladdr%s+([%x:]+)") + if ip ~= nil and mac ~= nil then + byMac[mac:lower()] = ip + end + end + return byMac +end + +local function mergeClients(stations, neighByMac) + local merged = {} + local seen = {} + + for _, client in ipairs(stations) do + table.insert(merged, { + mac = client.mac, + ip = neighByMac[client.mac] or client.ip or "", + signal = client.signal, + }) + seen[client.mac] = true + end + + for mac, ip in pairs(neighByMac) do + if not seen[mac] then + table.insert(merged, { mac = mac, ip = ip, signal = nil }) + end + end + + table.sort(merged, function(left, right) + if left.ip ~= right.ip then + if left.ip == "" then return false end + if right.ip == "" then return true end + return left.ip < right.ip + end + return left.mac < right.mac + end) + + return merged +end + +local function fetchClients(iface, callback) + if iface == nil or iface == "" then + callback({}) + return + end + + local stations = {} + local neighByMac = {} + local remaining = 0 + + local function finish() + remaining -= 1 + if remaining > 0 then return end + callback(mergeClients(stations, neighByMac)) + end + + if iwAvailable then + remaining += 1 + runCommand("iw dev " .. shellQuote(iface) .. " station dump", function(result) + if result.exitCode == 0 then + stations = parseIwStations(result.stdout) + end + finish() + end) + end + + if ipAvailable then + remaining += 1 + runCommand("ip neigh show dev " .. shellQuote(iface), function(result) + if result.exitCode == 0 then + neighByMac = parseIpNeigh(result.stdout) + end + finish() + end) + end + + if remaining == 0 then + callback({}) + end +end + +local function inspectConnection(entry, callback) + local command = "nmcli -g 802-11-wireless.mode,802-11-wireless.ssid,IP4.ADDRESS connection show " + .. shellQuote(entry.name) + runCommand(command, function(result) + if result.exitCode ~= 0 then + callback(nil) + return + end + + local mode, ssid, gateway + local index = 0 + for line in tostring(result.stdout or ""):gmatch("[^\n]+") do + index += 1 + local value = noctalia.string.trim(line) + if index == 1 then mode = value + elseif index == 2 then ssid = value + elseif index == 3 then gateway = value end + end + + if mode ~= "ap" then + callback(nil) + return + end + + callback({ + active = true, + connection = entry.name, + interface = entry.device, + ssid = ssid or "", + gateway = gateway or "", + }) + end) +end + +local function findActiveHotspot(callback) + runCommand("nmcli -t -f NAME,TYPE,DEVICE connection show --active", function(result) + if result.exitCode ~= 0 then + local detail = noctalia.string.trim(result.stderr or "") + callback(nil, detail ~= "" and detail or noctalia.tr("error.status_failed")) + return + end + + local entries = parseNmcliTriple(result.stdout, "802-11-wireless") + if #entries == 0 then + callback(nil) + return + end + + local index = 1 + local function nextEntry() + local entry = entries[index] + if entry == nil then + callback(nil) + return + end + + inspectConnection(entry, function(info) + if info ~= nil then + callback(info) + return + end + index += 1 + nextEntry() + end) + end + + nextEntry() + end) +end + +local function completeRefresh(callback, active) + refreshing = false + if callback then callback(active) end + if refreshAgain then + refreshAgain = false + refresh() + end +end + +refresh = function(callback) + if not available then + status.active = false + status.clients = {} + publish(noctalia.tr("error.missing_dependencies")) + if callback then callback(false) end + return + end + + if refreshing then + refreshAgain = true + if callback then callback(status.active) end + return + end + + refreshing = true + + findActiveHotspot(function(info, err) + if err ~= nil then + status.active = false + status.clients = {} + publish(err) + completeRefresh(callback, false) + return + end + + if info == nil then + status.active = false + status.ssid = "" + status.interface = "" + status.connection = "" + status.gateway = "" + status.clients = {} + publish() + completeRefresh(callback, false) + return + end + + status.active = true + status.ssid = info.ssid + status.interface = info.interface + status.connection = info.connection + status.gateway = info.gateway + + fetchClients(info.interface, function(clients) + status.clients = clients + publish() + completeRefresh(callback, true) + end) + end) +end + +local function finishToggle(expectedActive, result) + if result.exitCode ~= 0 then + busy = false + local detail = noctalia.string.trim(result.stderr or "") + if detail == "" then detail = noctalia.string.trim(result.stdout or "") end + if detail == "" then detail = noctalia.tr("error.command_failed") end + publish(detail) + showNotification(noctalia.tr("title"), detail, "alert-circle", "critical") + refresh() + return + end + + refresh(function(currentActive) + busy = false + publish() + + if currentActive ~= expectedActive then + showNotification(noctalia.tr("title"), noctalia.tr("error.state_mismatch"), "alert-circle", "critical") + elseif currentActive then + showNotification( + noctalia.tr("notification.enabled.title"), + noctalia.tr("notification.enabled.body", { ssid = status.ssid or noctalia.getConfig("ssid") or "" }), + "wifi", + "normal" + ) + else + showNotification( + noctalia.tr("notification.disabled.title"), + noctalia.tr("notification.disabled.body"), + "wifi-off", + "normal" + ) + end + end) +end + +local function startHotspot() + local ok, ssidOrError, password = validateSettings() + if not ok then + busy = false + publish(ssidOrError) + showNotification(noctalia.tr("title"), ssidOrError, "alert-circle", "critical") + return + end + + pickWifiInterface(function(iface) + if iface == nil or iface == "" then + busy = false + local message = noctalia.tr("error.no_wifi_device") + publish(message) + showNotification(noctalia.tr("title"), message, "alert-circle", "critical") + return + end + + local command = table.concat({ + "nmcli device wifi hotspot", + "ifname " .. shellQuote(iface), + "ssid " .. shellQuote(ssidOrError), + "password " .. shellQuote(password), + }, " ") + + runCommand(command, function(result) + finishToggle(true, result) + end, 30000) + end) +end + +local function stopHotspot() + if status.connection ~= nil and status.connection ~= "" then + runCommand("nmcli connection down " .. shellQuote(status.connection), function(result) + finishToggle(false, result) + end) + return + end + + findActiveHotspot(function(info) + if info == nil then + busy = false + status.active = false + status.clients = {} + publish() + showNotification( + noctalia.tr("notification.disabled.title"), + noctalia.tr("notification.disabled.body"), + "wifi-off", + "normal" + ) + return + end + + runCommand("nmcli connection down " .. shellQuote(info.connection), function(result) + finishToggle(false, result) + end) + end) +end + +setActive = function(nextActive) + if busy or not available then return end + + if nextActive then + local ok, message = validateSettings() + if not ok then + publish(message) + showNotification(noctalia.tr("title"), message, "alert-circle", "critical") + return + end + end + + busy = true + publish() + + if nextActive then + startHotspot() + else + stopHotspot() + end +end + +local function handleCommand(action) + if action == "toggle" then + setActive(not status.active) + elseif action == "enable" then + setActive(true) + elseif action == "disable" then + setActive(false) + elseif action == "refresh" or action == "status" then + refresh() + end +end + +noctalia.state.watch(COMMAND_KEY, function(command) + if type(command) ~= "table" or type(command.action) ~= "string" then return end + handleCommand(command.action) +end) + +publish(available and "" or noctalia.tr("error.missing_dependencies")) +refresh() + +function update() + local seconds = tonumber(noctalia.getConfig("refresh_interval")) or 5 + noctalia.setUpdateInterval(math.max(2, seconds) * 1000) + if not busy then + refresh() + end +end + +function onConfigChanged() + update() +end + +function onIpc(event, _payload) + handleCommand(event) +end diff --git a/hotspot/thumbnail.webp b/hotspot/thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..77b53679f20e3dacaf3e475d0c0c10449bf2c907 GIT binary patch literal 57032 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 z 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᥮cZJ_?RQ^}X*1F1@T6S^y-Bw+Wj+Z&M=X(D- z`o?A0uiS0a^{k*&ZM%8Z42GRO=QqS1pL3J{LcfC9*693A-_3j6p@aQp_n9-i znfB)kl{bD9nq9j;vT1%&NnL7Coh{SZi>94dG#H++KYUdc@PEM%!wD;kyr)PUyI^!X z-$lXf)~@A~q>g{+6IrmmC|CQu;1t9BQ_n;%O!#y`UPWw8z*;5khzy$**S@ExW_Y+X z|G50K%lX<9<@;PW+)}3PyK%f@`Dq)k?Y|!XX$>&V%03=E|Lhw%i*l6^fz>W?{j4oJ z*2JCC{}Z9JyNSX2rq2n-H#=132nq>r;h537iLGYyp+DLM_huJvJ5|^F$tq{pDYw#> zyPPJTU#uykv*Y%`-E7j)zN!6LErRlDw|;DWCB10s=9S^gB<2_1y_7X6{dQ!D$kPAu zFP^`>CfY3KdjIJp-rUJOzHiumY8~d9@4%(I^RQ0jnrWN+Zs^o|h~#T6+^Z{?!dh<3 z^;f63ChgN4xftPSwexb<4ppbwv(L1N2M1C%$xQ0o`71(>shN(T%UwrJ6Cb+>@9B#B~G)PK*zXhkC}_> z_wz_G7oJ_VPKfcHT~EitgS)0*w6ja;kiX#GwQKJxrJ^T#omqK*Bg1dZ-adDl`DW3I zZjl*(wEnV$nWk>O%(p20-o&Qzf{X*(HP;vJG&cUx?&JC~tCdas$~@(78&g@dK6v-N z^!D#tdr4S0&!I@3ciDdSd3tAGZD{dd6wVlN(^{}f>R^z0!=70or3yHnLoDYV&Eg_+`taqn-PBWWn48lupUweJY=FFP2J=WYNt{-pwsW{i$pZC*rwv~y2#oN0< z9Spw4Pr|<3%s6uP_T7mWZ`uam_>z9~$GzDGO>PR-(^D9WZyRdg(ec?bqdQyT`V{XO z3?YUYIxh1saNd#K@a2^Fg5N2#4Q4s5O4`q9&T%r1**t6gdpBl@5}Zn;#<1EoJkM;e~+trads0^Z(!r=rwjJppYiNpF>{j3Cri%5>nmR` o%Gy|OoLakO0^>EdHAj0EGW^}HY4?sVRcHOD)}WIg#8Ltn0Gh)=j{pDw literal 0 HcmV?d00001 diff --git a/hotspot/translations/en.json b/hotspot/translations/en.json new file mode 100644 index 0000000..55a19ef --- /dev/null +++ b/hotspot/translations/en.json @@ -0,0 +1,75 @@ +{ + "error": { + "command_failed": "The NetworkManager command failed.", + "command_start": "Noctalia could not start the hotspot command.", + "missing_dependencies": "nmcli is required to manage the hotspot.", + "missing_ssid": "Set a hotspot name in the plugin settings.", + "no_wifi_device": "No Wi-Fi adapter was found.", + "password_too_short": "The hotspot password must be at least 8 characters.", + "state_mismatch": "The requested hotspot state could not be confirmed.", + "status_failed": "NetworkManager status could not be read." + }, + "notification": { + "disabled": { + "body": "The Wi-Fi hotspot has been turned off.", + "title": "Hotspot disabled" + }, + "enabled": { + "body": "Clients can connect to {ssid}.", + "title": "Hotspot enabled" + } + }, + "panel": { + "action": { + "start": "Start hotspot", + "starting": "Starting…", + "stop": "Stop hotspot", + "stopping": "Stopping…" + }, + "clients_inactive": "Start the hotspot to see connected devices here.", + "clients_title": "Connected devices", + "close": "Close", + "gateway": "Gateway: {address}", + "no_clients": "No device is connected right now.", + "refresh": "Refresh", + "status": { + "active_message": "{ssid} is broadcasting on {interface}.", + "active_title": "Hotspot active", + "error_title": "Unavailable", + "inactive_message": "Configure the name and password in settings, then start the hotspot.", + "inactive_title": "Hotspot off", + "starting_message": "NetworkManager is creating the access point…", + "starting_title": "Starting", + "stopping_message": "NetworkManager is shutting down the access point…", + "stopping_title": "Stopping" + } + }, + "settings": { + "glyph": { + "label": "Bar glyph" + }, + "interface": { + "description": "Optional Wi-Fi interface name. Leave empty to use the first adapter NetworkManager reports.", + "label": "Wi-Fi interface" + }, + "password": { + "description": "WPA password shared with connecting devices. Must be at least 8 characters.", + "label": "Password" + }, + "refresh_interval": { + "description": "How often the service refreshes hotspot status and the connected device list, in seconds.", + "label": "Refresh interval" + }, + "ssid": { + "description": "Network name broadcast by the hotspot.", + "label": "Hotspot name" + } + }, + "title": "Wi-Fi Hotspot", + "tooltip": { + "active": "{ssid} is active · {count} device(s) connected · click for panel · right-click to stop", + "changing": "Changing hotspot state…", + "inactive": "Hotspot is off · click for panel · right-click to start", + "unavailable": "NetworkManager is not available" + } +} diff --git a/hotspot/widget.luau b/hotspot/widget.luau new file mode 100644 index 0000000..cc81a94 --- /dev/null +++ b/hotspot/widget.luau @@ -0,0 +1,75 @@ +--!nonstrict + +local PANEL_ID = "cleboost/hotspot:panel" +local COMMAND_KEY = "hotspot_command" + +local status = noctalia.state.get("hotspot_status") or { + active = false, + busy = false, + available = true, + clients = {}, + clientCount = 0, +} + +local glyph = noctalia.getConfig("glyph") or "router" + +local function sendCommand(action) + noctalia.state.set(COMMAND_KEY, { action = action }) +end + +local function render() + if status.available == false then + barWidget.setGlyph("wifi-off") + barWidget.setGlyphColor("error") + barWidget.setTooltip(status.error ~= "" and status.error or noctalia.tr("tooltip.unavailable")) + elseif status.busy == true then + barWidget.setGlyph("loader-2") + barWidget.setGlyphColor("primary") + barWidget.setTooltip(noctalia.tr("tooltip.changing")) + elseif status.active == true then + barWidget.setGlyph(glyph) + barWidget.setGlyphColor("tertiary") + local count = status.clientCount or #(status.clients or {}) + barWidget.setText(count > 0 and tostring(count) or "") + barWidget.setTooltip(noctalia.tr("tooltip.active", { + ssid = status.ssid or "?", + count = tostring(count), + })) + else + barWidget.setGlyph(glyph) + barWidget.setGlyphColor("on_surface_variant") + barWidget.setText("") + barWidget.setTooltip(noctalia.tr("tooltip.inactive")) + end +end + +noctalia.state.watch("hotspot_status", function(value) + if type(value) == "table" then + status = value + render() + end +end) + +render() + +function onClick() + noctalia.togglePanel(PANEL_ID) +end + +function onRightClick() + if status.available == false then + noctalia.notifyError(noctalia.tr("title"), status.error or noctalia.tr("error.missing_dependencies")) + return + end + if status.busy == true then return end + sendCommand("toggle") +end + +function onMiddleClick() + sendCommand("refresh") +end + +function onConfigChanged() + glyph = noctalia.getConfig("glyph") or "router" + render() +end