// Pure helpers for the hypr-rdp plugin: TOML-ish config patching and // hyprctl JSON parsing. No QML/Quickshell APIs here so this stays testable // with plain node/qjs. function escapeTomlString(value) { return String(value === undefined || value === null ? "" : value) .replace(/\\/g, "\\\\") .replace(/"/g, "\\\"") } function unescapeTomlString(value) { return String(value || "") .replace(/\\"/g, "\"") .replace(/\\\\/g, "\\") } // Reads only the keys we ever show/edit (bind, username, password) from a // hypr-rdp config.toml. Everything else in the file is left completely // alone by serializeConfig below, comments included. function parseConfig(text) { var result = { bind: "", username: "", password: "" } var lines = String(text || "").split("\n") var re = /^\s*(bind|username|password)\s*=\s*"((?:[^"\\]|\\.)*)"\s*$/ for (var i = 0; i < lines.length; i++) { var m = lines[i].match(re) if (m) result[m[1]] = unescapeTomlString(m[2]) } return result } // Line-level patch: replaces an existing `key = "..."` line in place, // appends a new one if the key was never set, and never touches any other // line (comments, capture_mode, bitrate, etc. survive untouched). function serializeConfig(originalText, patch) { var lines = String(originalText || "").split("\n") var keys = ["bind", "username", "password"] var seen = {} for (var i = 0; i < lines.length; i++) { for (var k = 0; k < keys.length; k++) { var key = keys[k] if (patch[key] === undefined) continue var re = new RegExp("^\\s*" + key + "\\s*=") if (re.test(lines[i])) { lines[i] = key + " = \"" + escapeTomlString(patch[key]) + "\"" seen[key] = true } } } var toAppend = [] for (var k2 = 0; k2 < keys.length; k2++) { var key2 = keys[k2] if (patch[key2] !== undefined && !seen[key2]) { toAppend.push(key2 + " = \"" + escapeTomlString(patch[key2]) + "\"") } } var out = lines.join("\n") if (toAppend.length > 0) { if (out.length > 0 && out.charAt(out.length - 1) !== "\n") out += "\n" out += toAppend.join("\n") + "\n" } return out } // Content for the toggle flag file under // ~/.local/state/omarchy/toggles/hypr/. Its mere presence pins the // hypr-rdp headless output's workspace so Hyprland's normal // auto-assignment never reuses it; deleting the file removes the pin. function isolationLuaContent(workspaceId, outputName) { return "-- Managed by the jandieman.hypr-rdp bar widget's isolation\n" + "-- toggle. Do not hand-edit -- this file is overwritten or removed\n" + "-- automatically when the toggle is flipped.\n" + "--\n" + "-- Keeps the hypr-rdp headless monitor's workspace from being reused\n" + "-- by normal window/workspace auto-assignment, so it never silently\n" + "-- steals one of your visible workspaces while RDP is active.\n" + "hl.workspace_rule({ workspace = \"" + workspaceId + "\", monitor = \"" + outputName + "\", default = true, persistent = true })\n" } function parseClients(raw, workspaceId) { var clients try { clients = raw ? JSON.parse(String(raw)) : [] } catch (e) { clients = [] } if (!Array.isArray(clients)) return [] var result = [] for (var i = 0; i < clients.length; i++) { var c = clients[i] if (c && c.workspace && c.workspace.id === workspaceId) { result.push({ title: String(c.title || c.initialTitle || "Untitled"), cls: String(c["class"] || c.initialClass || "") }) } } return result } function parseActiveWorkspaceId(raw) { try { var obj = JSON.parse(String(raw || "")) return typeof obj.id === "number" ? obj.id : -1 } catch (e) { return -1 } } if (typeof module !== "undefined") { module.exports = { escapeTomlString: escapeTomlString, unescapeTomlString: unescapeTomlString, parseConfig: parseConfig, serializeConfig: serializeConfig, isolationLuaContent: isolationLuaContent, parseClients: parseClients, parseActiveWorkspaceId: parseActiveWorkspaceId } }