Fix QML scope error that disabled all camera movement, and close the lint gap that hid it #3
@@ -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 = [
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ]]
|
||||
|
||||
Reference in New Issue
Block a user