Fix QML scope error that disabled all camera movement, and close the lint gap that hid it #3

Merged
branderson merged 4 commits from widget-resolve-linkctl into main 2026-10-07 23:52:29 -07:00
4 changed files with 70 additions and 6 deletions
Showing only changes of commit 82974f242d - Show all commits
+17
View File
@@ -23,6 +23,23 @@ function baseCmd(linkctl, device) {
return c;
}
/*
* Make a command string unique by appending a shell comment.
*
* Plasma's executable data engine keys its containers by the command string.
* DataEngine::connectSource hands back a pre-existing container and delivers
* its *cached* data rather than running the command again; the process only
* starts when the container is first created. So re-issuing an identical
* command can report success without the camera moving at all, which is
* exactly what a stick held at full deflection produces every tick.
*
* `#` starts a comment in sh, so the trailing counter changes the key the
* engine sees without changing what runs.
*/
function uniqueify(cmd, seq) {
return cmd + " # " + seq;
}
// Directories searched for the binary, in order. Mirrors the resolver in
// bin/linkctl-macro. $HOME is left for the shell to expand.
var SEARCH_DIRS = [
+11 -6
View File
@@ -31,7 +31,9 @@ PlasmoidItem {
// a login shell adds to PATH, so a bare name often cannot be run here
// even when it works in a terminal.
property string resolvedBin: ""
property string probeSource: ""
property bool resolving: false
// Incremented per command so no two command strings are ever identical.
property int seq: 0
readonly property bool ready: root.resolvedBin !== ""
readonly property int stepMax: Plasmoid.configuration.maxStep
readonly property int tickMs: Plasmoid.configuration.tickInterval
@@ -113,8 +115,8 @@ PlasmoidItem {
return;
}
root.statusText = "Looking for linkctl...";
root.probeSource = Logic.resolveCommand(root.configuredBin);
sh.exec(root.probeSource);
root.resolving = true;
sh.exec(Logic.resolveCommand(root.configuredBin));
}
function absorb(stdout) {
@@ -148,8 +150,8 @@ PlasmoidItem {
disconnectSource(source);
root.busy = false;
if (source === root.probeSource) {
root.probeSource = "";
if (root.resolving) {
root.resolving = false;
const found = String(out).trim().split("\n")[0].trim();
if (found !== "") {
root.resolvedBin = found;
@@ -171,7 +173,10 @@ PlasmoidItem {
function exec(cmd) {
root.busy = true;
connectSource(cmd);
root.seq += 1;
// Never reuse a command string: the engine would serve the
// previous run's cached output and not run anything.
connectSource(Logic.uniqueify(cmd, root.seq));
}
}
+15
View File
@@ -125,6 +125,21 @@ deep(ctx.absorb('{"pan":1}\nbroken {\n{"tilt":2}'), { pan: 1, tilt: 2 },
"absorb survives a malformed line between good ones");
deep(ctx.absorb('Pan: 12.3°'), {}, "absorb ignores human-readable output");
// --- uniqueify ---------------------------------------------------------
// Plasma's executable engine keys containers by command string and serves a
// pre-existing container's cached output instead of re-running, so repeating
// a command verbatim reports success without moving the camera.
eq(ctx.uniqueify("'linkctl' --force 'center'", 1),
"'linkctl' --force 'center' # 1", "uniqueify appends a comment");
eq(ctx.uniqueify("a", 1) === ctx.uniqueify("a", 2), false,
"successive sequence numbers differ");
// The suffix must be a shell comment so it cannot alter what runs.
eq(ctx.uniqueify("x", 7).includes(" # "), true, "suffix is a shell comment");
// A diagonal tick is two chained commands; the suffix must land at the end
// and so comment out nothing meaningful.
const tc = ctx.tickCommand(B, 1, 1, 6, false, false);
eq(ctx.uniqueify(tc, 3), tc + " # 3", "uniqueify appends after a chained command");
// --- resolveCommand ----------------------------------------------------
// Plasma runs applet commands through `sh -c` with plasmashell's environment,
// which never sources a login profile. A bare name therefore has to be
+27
View File
@@ -93,5 +93,32 @@ make_exe "$TMP/preferred/linkctl"
expect "prefers PATH over the search list" "$TMP/both" \
"$TMP/preferred:/usr/bin:/bin" "$TMP/preferred/linkctl"
# The uniqueness suffix exists only to change the string Plasma keys its
# command containers by. It must not change what the shell actually runs.
ask_logic() {
node -e '
const fs = require("fs"), vm = require("vm");
const src = fs.readFileSync(process.argv[1], "utf8");
const ctx = { Math, JSON, String };
vm.createContext(ctx);
vm.runInContext(src.replace(/^\s*\.pragma\s+library\s*$/m, ""), ctx);
const tick = ctx.tickCommand(ctx.baseCmd("echo", ""), 1, 1, 6, false, false);
process.stdout.write(process.argv[2] === "unique"
? ctx.uniqueify(tick, 42) : tick);
' "$LOGIC" "$1"
}
checks=$((checks + 1))
plain_out=$(sh -c "$(ask_logic plain)" 2>&1)
unique_out=$(sh -c "$(ask_logic unique)" 2>&1)
if [[ $plain_out == "$unique_out" && -n $plain_out ]]; then
printf 'ok %s\n' "uniqueness suffix does not change what the shell runs"
else
failures=$((failures + 1))
printf 'FAIL %s\n plain: %s\n unique: %s\n' \
"uniqueness suffix does not change what the shell runs" \
"$plain_out" "$unique_out"
fi
printf '\n%d checks, %d failures\n' "$checks" "$failures"
[[ $failures -eq 0 ]]