Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/network.js

Raw
// network.js — pure iwd D-Bus (busctl --json) shaping. NO I/O.
// Dual-mode: node `require` (tests) + QML import ("network.js" as Network).
// Same pattern as audio.js / brightness.js / clip.js. No `.pragma library`.
//
// iwd exposes a full D-Bus API (net.connman.iwd). `busctl --json=short` emits
// structured JSON, so there is NO text/ANSI parsing — we JSON.parse two calls:
//   ObjectManager.GetManagedObjects -> devices, stations, networks, known nets
//   Station.GetOrderedNetworks      -> [path, signalStrength] strongest-first
// Network names arrive already decoded; signal is int dBm*100 (e.g. -6800).

// --- busctl JSON helpers ---------------------------------------------------

// busctl --json wraps a reply as { "type": "...", "data": [ <value> ] }.
// Unwrap to the single payload value. Accepts a raw string or a parsed object.
function unwrap(raw) {
    if (raw === undefined || raw === null) return null;
    var obj = typeof raw === "string" ? JSON.parse(raw) : raw;
    if (obj && Object.prototype.hasOwnProperty.call(obj, "data")
        && Array.isArray(obj.data)) {
        return obj.data.length > 0 ? obj.data[0] : null;
    }
    return obj;
}

// A D-Bus property value is { "type": "x", "data": <value> }. Pull data or fb.
function prop(iface, name, fb) {
    if (iface && iface[name] && iface[name].data !== undefined) return iface[name].data;
    return fb;
}

// --- Signal -----------------------------------------------------------------

// signalBars(centiDbm) — iwd reports signal as dBm*100. Map to 0..4 bars using
// the same thresholds iwctl itself renders. Non-finite => 0.
function signalBars(centiDbm) {
    var v = Number(centiDbm);
    if (!isFinite(v)) return 0;
    if (v >= -6000) return 4;
    if (v >= -6700) return 3;
    if (v >= -7400) return 2;
    return 1;
}

// dbmFromCenti(centiDbm) — iwd's dBm*100 -> rounded dBm. Non-finite => null.
function dbmFromCenti(centiDbm) {
    var v = Number(centiDbm);
    if (!isFinite(v)) return null;
    return Math.round(v / 100);
}

// --- Parsers (JSON -> plain data) ------------------------------------------

// parseManaged(rawGmo) -> {
//   devicePath, device:{name,powered,mode}|null,
//   station:{state,scanning,connectedNetwork}|null,
//   networks: { <path>: {ssid,security,connected,known} },
//   knownBySsid: { <ssid>: <knownNetworkPath> } }
// Picks the FIRST station-mode device.
function parseManaged(rawGmo) {
    var d = unwrap(rawGmo) || {};
    var out = {
        devicePath: "",
        device: null,
        station: null,
        networks: {},
        knownBySsid: {}
    };
    for (var path in d) {
        if (!Object.prototype.hasOwnProperty.call(d, path)) continue;
        var ifaces = d[path];

        var dev = ifaces["net.connman.iwd.Device"];
        if (dev && prop(dev, "Mode", "") === "station" && out.devicePath === "") {
            out.devicePath = path;
            out.device = {
                name: prop(dev, "Name", ""),
                powered: !!prop(dev, "Powered", false),
                mode: "station"
            };
            var st = ifaces["net.connman.iwd.Station"];
            if (st) {
                out.station = {
                    state: prop(st, "State", ""),
                    scanning: !!prop(st, "Scanning", false),
                    connectedNetwork: prop(st, "ConnectedNetwork", "")
                };
            }
        }

        var net = ifaces["net.connman.iwd.Network"];
        if (net) {
            out.networks[path] = {
                ssid: prop(net, "Name", ""),
                security: prop(net, "Type", ""),
                connected: !!prop(net, "Connected", false),
                // iwd exposes a KnownNetwork property ONLY when the net is known.
                known: Object.prototype.hasOwnProperty.call(net, "KnownNetwork")
            };
        }

        var kn = ifaces["net.connman.iwd.KnownNetwork"];
        if (kn) {
            var kssid = prop(kn, "Name", "");
            if (kssid !== "") out.knownBySsid[kssid] = path;
        }
    }
    return out;
}

// parseOrdered(rawGon) -> [{ path, signal:centiDbm }] in iwd order (the API
// already lists the connected network first, then by descending strength).
function parseOrdered(rawGon) {
    var arr = unwrap(rawGon);
    var out = [];
    if (!Array.isArray(arr)) return out;
    for (var i = 0; i < arr.length; i++) {
        var pair = arr[i];
        if (!Array.isArray(pair) || pair.length < 2) continue;
        out.push({ path: pair[0], signal: pair[1] });
    }
    return out;
}

// networkRows(managed, ordered) -> view rows, in iwd order, joining the signal
// (from ordered) with name/type/known/connected (from managed). Carries the
// D-Bus object `path` so the service can call Connect on the right object.
//   [{ id, ssid, security, signal:0..4, connected, known, needsPassphrase, path }]
function networkRows(managed, ordered) {
    var nets = (managed && managed.networks) ? managed.networks : {};
    var list = Array.isArray(ordered) ? ordered : [];
    var rows = [];
    var used = {};

    function identity(net) {
        return String(net.ssid || "") + "\u0000" + String(net.security || "");
    }
    function append(path, net, rawSignal, atFront) {
        if (!net || used[path]) return;
        var row = {
            id: networkId(net.ssid),
            ssid: net.ssid,
            security: net.security,
            signal: rawSignal === null ? 0 : signalBars(rawSignal),
            signalDbm: rawSignal === null ? null : dbmFromCenti(rawSignal),
            connected: net.connected,
            known: net.known,
            needsPassphrase: needsPassphrase(net.security, net.known),
            path: path
        };
        used[path] = true;
        if (atFront) rows.unshift(row); else rows.push(row);
    }

    for (var i = 0; i < list.length; i++) {
        var orderedRow = list[i];
        var path = orderedRow.path;
        var net = nets[path];
        if (!net && (orderedRow.ssid || orderedRow.security)) {
            var wanted = identity(orderedRow);
            for (var candidate in nets) {
                if (!used[candidate] && identity(nets[candidate]) === wanted) {
                    path = candidate;
                    net = nets[candidate];
                    break;
                }
            }
        }
        append(path, net, orderedRow.signal, false);
    }

    // ObjectManager and Station snapshots can race. Retain unmatched managed
    // rows rather than flashing an empty model; a missing connected row leads.
    for (var managedPath in nets)
        if (!used[managedPath] && nets[managedPath].connected)
            append(managedPath, nets[managedPath], null, true);
    for (var fallbackPath in nets)
        if (!used[fallbackPath])
            append(fallbackPath, nets[fallbackPath], null, false);
    return rows;
}

// --- Shaping / view helpers ------------------------------------------------

// needsPassphrase(security, known) — secured AND iwd has no stored secret.
function needsPassphrase(security, known) {
    return security !== "open" && !known;
}

// signalRole(bars) — theme token; weak/medium/strong.
function signalRole(bars) {
    if (bars <= 1) return "ui.minor";
    if (bars <= 3) return "ui.accent";
    return "content.accent";
}

// --- Nerd Font md-wifi_strength glyphs (astral codepoints). Built from
// codepoints to avoid hand-written surrogate pairs. ---
function fromCodePoint(n) {
    if (n < 0x10000) return String.fromCharCode(n);
    var c = n - 0x10000;
    return String.fromCharCode(0xD800 + (c >> 10), 0xDC00 + (c & 0x3FF));
}
var WIFI_GLYPHS = [
    fromCodePoint(0xF092F), // 0: wifi-strength-outline
    fromCodePoint(0xF091F), // 1
    fromCodePoint(0xF0922), // 2
    fromCodePoint(0xF0925), // 3
    fromCodePoint(0xF0928)  // 4
];

// signalGlyph(bars) — nerd-font wifi glyph by strength.
function signalGlyph(bars) {
    var b = bars;
    if (b < 0) b = 0;
    if (b > 4) b = 4;
    return WIFI_GLYPHS[b];
}

// securityLabel(sec) — human label for the security token.
function securityLabel(sec) {
    if (sec === "open") return "open";
    if (sec === "psk") return "wpa";
    if (sec === "8021x") return "enterprise";
    return sec ? sec : "";
}

// networkId(ssid) — stable id (ssid itself; never empty).
function networkId(ssid) {
    var s = (ssid === undefined || ssid === null) ? "" : String(ssid);
    return s.length > 0 ? s : "hidden-network";
}

// --- Connection detail (StationDiagnostic.GetDiagnostics) ------------------
// Only the CONNECTED network has these; iwd exposes no per-BSS detail for
// other scanned networks. The expand drawer shows bars/security for any row
// and these extra fields only for the connected one.

// bandLabel(freqMHz) — Wi-Fi band from centre frequency. "" if unknown.
function bandLabel(freqMHz) {
    var f = Number(freqMHz);
    if (!isFinite(f) || f <= 0) return "";
    if (f >= 5925) return "6 GHz";
    if (f >= 4900) return "5 GHz";
    if (f >= 2400) return "2.4 GHz";
    return "";
}

// rateLabel(raw) — iwd Rx/TxBitrate is in units of 100 kbit/s. -> "135 Mbps".
// "" for missing/zero so the view can omit it.
function rateLabel(raw) {
    var v = Number(raw);
    if (!isFinite(v) || v <= 0) return "";
    return Math.round(v / 10) + " Mbps";
}

// connectionDetail(rawDiag) -> { bssid, band, channel, rssi, rxRate, txRate,
// mode } | null. rawDiag is the StationDiagnostic.GetDiagnostics a{sv} reply.
// Returns null when there is no connected BSS (disconnected / no diagnostics).
function connectionDetail(rawDiag) {
    var d = unwrap(rawDiag);
    if (!d || typeof d !== "object") return null;
    var bssid = prop(d, "ConnectedBss", "");
    if (!bssid) return null;
    return {
        bssid: String(bssid),
        band: bandLabel(prop(d, "Frequency", 0)),
        channel: Number(prop(d, "Channel", 0)) || null,
        rssi: Number(prop(d, "RSSI", 0)) || null,
        rxRate: rateLabel(prop(d, "RxBitrate", 0)),
        txRate: rateLabel(prop(d, "TxBitrate", 0)),
        mode: String(prop(d, "RxMode", "") || "")
    };
}

if (typeof module !== 'undefined' && module.exports) {
    module.exports = {
        unwrap: unwrap,
        prop: prop,
        signalBars: signalBars,
        dbmFromCenti: dbmFromCenti,
        parseManaged: parseManaged,
        parseOrdered: parseOrdered,
        networkRows: networkRows,
        needsPassphrase: needsPassphrase,
        signalRole: signalRole,
        signalGlyph: signalGlyph,
        securityLabel: securityLabel,
        networkId: networkId,
        bandLabel: bandLabel,
        rateLabel: rateLabel,
        connectionDetail: connectionDetail
    };
}