Merge pull request 'reduce click capture latency' (#1) from codex/instant-click-capture into main
Template tests / tests (push) Failing after 16s
Template tests / tests (push) Failing after 16s
Reviewed-on: #1
This commit is contained in:
+19
-5
@@ -23,6 +23,7 @@ const { encodePng } = require('../core/png');
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const CLICK_DEBOUNCE_MS = 700;
|
const CLICK_DEBOUNCE_MS = 700;
|
||||||
|
const CLICK_CAPTURE_HIDE_DELAY_MS = 25;
|
||||||
|
|
||||||
function hasBinary(name) {
|
function hasBinary(name) {
|
||||||
try {
|
try {
|
||||||
@@ -241,6 +242,7 @@ class CaptureService {
|
|||||||
guideId: this.session.guideId,
|
guideId: this.session.guideId,
|
||||||
mode: mode === 'region' ? 'fullscreen' : mode,
|
mode: mode === 'region' ? 'fullscreen' : mode,
|
||||||
delayMs: 0,
|
delayMs: 0,
|
||||||
|
hideWindowDelayMs: trigger === 'click' ? CLICK_CAPTURE_HIDE_DELAY_MS : null,
|
||||||
refocus: false, // don't steal focus from the app the user is documenting
|
refocus: false, // don't steal focus from the app the user is documenting
|
||||||
});
|
});
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
@@ -285,7 +287,7 @@ while ($true) {
|
|||||||
$s = [W.U]::GetAsyncKeyState(0x01) -band 0x8000
|
$s = [W.U]::GetAsyncKeyState(0x01) -band 0x8000
|
||||||
if ($s -and -not $down) { Write-Output CLICK }
|
if ($s -and -not $down) { Write-Output CLICK }
|
||||||
$down = [bool]$s
|
$down = [bool]$s
|
||||||
Start-Sleep -Milliseconds 40
|
Start-Sleep -Milliseconds 10
|
||||||
}`;
|
}`;
|
||||||
this.clickWatcher = spawn('powershell.exe', ['-NoProfile', '-Command', ps], { stdio: ['ignore', 'pipe', 'ignore'] });
|
this.clickWatcher = spawn('powershell.exe', ['-NoProfile', '-Command', ps], { stdio: ['ignore', 'pipe', 'ignore'] });
|
||||||
this.clickWatcher.stdout.on('data', (chunk) => {
|
this.clickWatcher.stdout.on('data', (chunk) => {
|
||||||
@@ -365,12 +367,14 @@ while ($true) {
|
|||||||
* Hide the app window while `fn` runs so screenshots show the user's work,
|
* Hide the app window while `fn` runs so screenshots show the user's work,
|
||||||
* not StepForge itself. Restores visibility afterwards.
|
* not StepForge itself. Restores visibility afterwards.
|
||||||
*/
|
*/
|
||||||
async withWindowHidden(fn, { refocus = true } = {}) {
|
async withWindowHidden(fn, { refocus = true, pauseMs = 350 } = {}) {
|
||||||
const win = this.getWindow();
|
const win = this.getWindow();
|
||||||
const wasVisible = win && !win.isDestroyed() && win.isVisible() && !win.isMinimized();
|
const wasVisible = win && !win.isDestroyed() && win.isVisible() && !win.isMinimized();
|
||||||
if (wasVisible) {
|
if (wasVisible) {
|
||||||
win.hide();
|
win.hide();
|
||||||
await new Promise((r) => setTimeout(r, 350)); // let the compositor repaint
|
if (pauseMs > 0) {
|
||||||
|
await new Promise((r) => setTimeout(r, pauseMs)); // let the compositor repaint
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await fn();
|
return await fn();
|
||||||
@@ -390,13 +394,23 @@ while ($true) {
|
|||||||
* Take a screenshot and append it to the guide as a new image step.
|
* Take a screenshot and append it to the guide as a new image step.
|
||||||
* Adds a click-marker annotation at the cursor position when enabled.
|
* Adds a click-marker annotation at the cursor position when enabled.
|
||||||
*/
|
*/
|
||||||
async shoot({ guideId, mode = 'fullscreen', delayMs = null, hideWindow = true, refocus = true }) {
|
async shoot({
|
||||||
|
guideId,
|
||||||
|
mode = 'fullscreen',
|
||||||
|
delayMs = null,
|
||||||
|
hideWindow = true,
|
||||||
|
refocus = true,
|
||||||
|
hideWindowDelayMs = null,
|
||||||
|
}) {
|
||||||
const delay = delayMs == null ? this.settings.get('capture.delayMs') || 0 : delayMs;
|
const delay = delayMs == null ? this.settings.get('capture.delayMs') || 0 : delayMs;
|
||||||
if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));
|
if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
let grabbed;
|
let grabbed;
|
||||||
try {
|
try {
|
||||||
grabbed = hideWindow
|
grabbed = hideWindow
|
||||||
? await this.withWindowHidden(() => this.grab(mode), { refocus })
|
? await this.withWindowHidden(() => this.grab(mode), {
|
||||||
|
refocus,
|
||||||
|
pauseMs: hideWindowDelayMs == null ? 350 : hideWindowDelayMs,
|
||||||
|
})
|
||||||
: await this.grab(mode);
|
: await this.grab(mode);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { ok: false, reason: err.message };
|
return { ok: false, reason: err.message };
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
|
const CaptureService = require('../../app/capture');
|
||||||
|
|
||||||
|
function makeService() {
|
||||||
|
const store = {
|
||||||
|
addStep() {
|
||||||
|
throw new Error('not used in this test');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const settings = {
|
||||||
|
get(key) {
|
||||||
|
if (key === 'capture.mode') return 'fullscreen';
|
||||||
|
if (key === 'capture.delayMs') return 0;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return new CaptureService({
|
||||||
|
store,
|
||||||
|
settings,
|
||||||
|
getWindow: () => null,
|
||||||
|
notify: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('click-triggered session capture uses the low-latency hide pause', async () => {
|
||||||
|
const service = makeService();
|
||||||
|
service.session = { guideId: 'guide-1', paused: false, count: 0, intervalSec: 0 };
|
||||||
|
|
||||||
|
let seenOptions = null;
|
||||||
|
service.shoot = async (options) => {
|
||||||
|
seenOptions = options;
|
||||||
|
return { ok: true, step: { stepId: 'step-1' } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await service.sessionCapture('click');
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(service.session.count, 1);
|
||||||
|
assert.deepEqual(seenOptions, {
|
||||||
|
guideId: 'guide-1',
|
||||||
|
mode: 'fullscreen',
|
||||||
|
delayMs: 0,
|
||||||
|
hideWindowDelayMs: 25,
|
||||||
|
refocus: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user