feat(native): C# input/capture sidecar replacing the PowerShell helper
Implements ADR-008. native/input-helper is a self-contained .NET 9 console exe speaking the identical JSON-over-stdin/stdout protocol as the old PowerShell helper (ping/cursor/runtime/focus/click/scroll/bounds/capture), so the InputHelperService interface is unchanged. - Win32 interop compiled once (native exe), not per call. - PerMonitorV2 DPI via manifest so click/capture coordinates stay correct on mixed-DPI multi-monitor setups. - capture returns base64 PNG bytes inline (imageBase64) instead of writing a temp file per frame; the client handles both base64 and the PowerShell path. - InputHelperClient prefers the exe and falls back to the embedded PowerShell helper when the exe is absent, so the app still runs without the .NET build. - main.ts resolves the exe (INPUT_HELPER_EXE env -> packaged resources/input-helper -> native/input-helper/bin/publish). electron-builder ships it via extraResources. - npm run helper:build; README documents the build + fallback. Verified end-to-end through the compiled client: sidecar spawns, runtime info and a base64 primary-screen capture return correctly. Build stays green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+21
-1
@@ -1,5 +1,6 @@
|
||||
import { app, BrowserWindow, Menu, desktopCapturer, globalShortcut, nativeImage, screen, type NativeImage } from "electron";
|
||||
import fs from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import http, { type Server } from "node:http";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -70,6 +71,25 @@ function getInputHelperService() {
|
||||
return inputHelperService;
|
||||
}
|
||||
|
||||
// Locate the compiled C# input/capture sidecar (ADR-008). Falls back to null so
|
||||
// the service uses the embedded PowerShell helper when the exe was never built.
|
||||
function resolveInputHelperExePath(): string | null {
|
||||
const candidates = [
|
||||
process.env.INPUT_HELPER_EXE,
|
||||
path.join(process.resourcesPath, "input-helper", "InputHelper.exe"),
|
||||
path.join(app.getAppPath(), "native", "input-helper", "bin", "publish", "InputHelper.exe"),
|
||||
].filter((candidate): candidate is string => Boolean(candidate));
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
if (existsSync(candidate)) return candidate;
|
||||
} catch {
|
||||
// Unreadable path; try the next candidate.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getRepositoryContext() {
|
||||
if (!repositoryContext) {
|
||||
throw new Error("Repository context has not been initialized.");
|
||||
@@ -1032,7 +1052,7 @@ function initializeAppLifecycle() {
|
||||
artifactStoreRepository = repositoryContext.artifactStoreRepository;
|
||||
reviewSamplesRepository = repositoryContext.reviewSamplesRepository;
|
||||
scannerLearningRepository = repositoryContext.scannerLearningRepository;
|
||||
inputHelperService = createInputHelperService({ userDataPath });
|
||||
inputHelperService = createInputHelperService({ userDataPath, exePath: resolveInputHelperExePath() });
|
||||
|
||||
registerIpcHandlers({
|
||||
focusMainWindow: () => focusMainWindow(),
|
||||
|
||||
Reference in New Issue
Block a user