mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-30 19:59:38 +02:00
[eric] dictation: a denied watcher stays listed in the Input Monitoring pane instead of vanishing (ENG-360)
This commit is contained in:
@@ -19,13 +19,14 @@ if !hidGranted && !isProbe {
|
||||
print(hidGranted ? "p granted" : "p denied")
|
||||
fflush(stdout)
|
||||
|
||||
// Without the grant the tap is DEAF, not absent: tapCreate happily returns a port that never
|
||||
// delivers an event, which is exactly how a dead fn key passed for a live one. Refuse to run in
|
||||
// that state so that "watcher alive" means "fn works" and nothing downstream has to guess.
|
||||
// Denied is REPORTED and then we keep going on purpose. Exiting here looks tidier, but a running
|
||||
// tap is the only thing that makes macOS list the app under Input Monitoring, so quitting removes
|
||||
// the very switch the user is being sent to flip (caught the hard way: "it just takes me to
|
||||
// Settings and nothing else"). The ambiguity that started all this is already gone, because the
|
||||
// parent trusts the reported permission and never the fact that this process is alive.
|
||||
if !hidGranted {
|
||||
print("e no-permission")
|
||||
fflush(stdout)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
var fnDown = false
|
||||
|
||||
@@ -245,6 +245,9 @@ function installVoiceHotkey(getMainWindow) {
|
||||
if (combo.special === 'fn') send(line === 'd' ? 'voice:hold-down' : 'voice:hold-up');
|
||||
} else if (line.startsWith('e')) {
|
||||
console.log('[voice] fn watcher error:', line);
|
||||
// The watcher deliberately stays alive when denied (it keeps the app listed in the
|
||||
// Input Monitoring pane), so liveness proves nothing and this line is the signal.
|
||||
if (line.includes('no-permission')) notifyPrimaryUnusable('input-monitoring-denied');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -52,13 +52,17 @@ test('the watcher reports its permission instead of leaving it to be inferred',
|
||||
'permission must be stated on stdout, not guessed from the process still being alive');
|
||||
});
|
||||
|
||||
test('a watcher without the grant refuses to run, so alive means working', () => {
|
||||
const guard = swiftSrc.split('if !hidGranted {')[1] || '';
|
||||
test('a denied watcher reports it and STAYS ALIVE, or the Settings pane has nothing to flip', () => {
|
||||
// Exiting on denial looked tidy and silently removed the only mechanism that lists the app under
|
||||
// Input Monitoring, so "Open Settings" led to a pane with no OpenSwarm row in it.
|
||||
const guard = swiftSrc.split('if !hidGranted {')[1].split('}')[0];
|
||||
assert.match(guard, /e no-permission/);
|
||||
assert.match(guard, /exit\(0\)/);
|
||||
const guardAt = swiftSrc.indexOf('if !hidGranted {');
|
||||
assert.ok(guardAt > -1 && guardAt < swiftSrc.indexOf('guard armTap()'),
|
||||
'the refusal must come before the tap, or a deaf tap still gets created');
|
||||
assert.ok(!/exit\(/.test(guard), 'must not exit: a live tap is what keeps the app listed in the pane');
|
||||
});
|
||||
|
||||
test('denial is carried by the reported line, never by liveness', () => {
|
||||
assert.match(hotkeySrc, /line\.includes\('no-permission'\)\) notifyPrimaryUnusable\('input-monitoring-denied'\)/,
|
||||
'with the process staying alive, the stdout report is the only honest signal');
|
||||
});
|
||||
|
||||
test('the grant is REQUESTED on the intent path and never on the boot probe', () => {
|
||||
|
||||
@@ -37,11 +37,14 @@ test('live protocol: armed watcher survives pokes and exits on EOF', { skip: pro
|
||||
let out = ''; p.stdout.on('data', (c) => { out += String(c); });
|
||||
let exitCode = null; p.on('exit', (c) => { exitCode = c; });
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
for (let waited = 0; !out.includes('r') && exitCode === null && waited < 4000; waited += 100) await sleep(100);
|
||||
const ready = () => out.includes('t ok') || out.includes('p denied');
|
||||
for (let waited = 0; !ready() && exitCode === null && waited < 4000; waited += 100) await sleep(100);
|
||||
try {
|
||||
// No boot marker = no grant, or a Gatekeeper-wedged machine hanging fresh binaries at
|
||||
// _dyld_start (both seen live); asserting against a process that never ran proves nothing.
|
||||
if (exitCode !== null || !out.includes('r')) return;
|
||||
// The re-arm protocol can only be exercised with the grant. This used to be inferred from the
|
||||
// process still being alive, which is precisely the ambiguity the watcher now reports its way
|
||||
// out of: ask it, do not guess. (A Gatekeeper-wedged machine hanging a fresh binary at
|
||||
// _dyld_start still lands in the same skip, since it never reports at all.)
|
||||
if (exitCode !== null || out.includes('p denied') || !out.includes('t ok')) return;
|
||||
p.stdin.write('r\n'); p.stdin.write('r\n');
|
||||
await sleep(500);
|
||||
assert.equal(exitCode, null, 'pokes must not kill the watcher');
|
||||
|
||||
Reference in New Issue
Block a user