Files
omarchy-rdp-service/Model.js
Johan a90363eeee Default monitor isolation on and make it self-healing
Fresh installs previously started with isolation off until someone
noticed and flipped the switch by hand, since the toggle read raw
file-existence with no persisted intent behind it. Now a small state
file (colocated in Omarchy's toggles dir, which is guaranteed to exist,
avoiding an mkdir-p race a plugin-private directory would hit) records
the user's actual choice, defaulting to isolated-on the first time the
widget ever loads. Every poll reconciles the toggle-flag file against
that intent and against the current isolated-workspace setting, so
external drift (or a workspace-number change) gets corrected within
one poll interval instead of silently sticking.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MLfSWFEMyY85ZDWEYicdaF
2026-09-08 16:19:00 +01:00

138 lines
4.4 KiB
JavaScript

// 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 dotjuice.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
}
}
// Isolation intent is on unless the state file explicitly says otherwise —
// covers a fresh install (no file yet, handled by the caller) and a
// corrupt/unexpected file the same way: default to the safer "isolated" side.
function parseIsolationState(raw) {
try {
var obj = JSON.parse(String(raw || ""))
return { enabled: obj.enabled !== false }
} catch (e) {
return { enabled: true }
}
}
if (typeof module !== "undefined") {
module.exports = {
escapeTomlString: escapeTomlString,
unescapeTomlString: unescapeTomlString,
parseConfig: parseConfig,
serializeConfig: serializeConfig,
isolationLuaContent: isolationLuaContent,
parseClients: parseClients,
parseActiveWorkspaceId: parseActiveWorkspaceId,
parseIsolationState: parseIsolationState
}
}