Introduce the platform adapter layer; extract window context behind it
Template tests / tests (pull_request) Failing after 33s
Template tests / tests (pull_request) Failing after 33s
Phase 3 groundwork of the improvement plan (PR 7 of the sequence). The Linux
work is a platform rewrite, so this first establishes the interface boundary
and moves an OS-specific piece behind it with Windows behavior preserved — no
new process.platform branches in shared code.
- app/platform/index.js is the single factory that selects a platform
implementation; shared code asks it for adapters and never inspects
process.platform itself.
- app/platform/interfaces.js documents the adapter contracts
(WindowContextProvider, ClickSource, PowerPolicy) and the explicit click-
source vocabulary.
- Extracted the foreground-window/element detection into per-OS adapters,
verbatim from text-intel.js:
app/platform/windows/window-context.js (PowerShell UIAutomation)
app/platform/linux/window-context.js (xprop)
app/platform/darwin/window-context.js (AppleScript)
text-intel.js now delegates to the injected provider and its three
platform-branching methods (and the now-dead child_process import) are gone.
- app/platform/linux/diagnostics.js detects session type, portal/PipeWire,
xinput, readable input devices, and the resulting click/screen-capture
profile, returning actionable messages for the UI. Exposed via a new
platform:capabilities IPC + preload method.
This is behavior-preserving: the Windows/macOS/Linux window-context code is
the same, just relocated behind the factory, and the capture pipeline is
untouched.
Tests: platform selection for every OS, provider validity + null-object for
unsupported OS, the shared service delegating to an injected provider, Linux
capability detection (x11/xinput, Wayland-without-PipeWire messaging, no-click
fallback, evdev), the capability facade, and a guard that text-intel no longer
branches on process.platform. 268 unit tests pass; startup smoke passes and
the click self-test is unchanged (stream source, markers 3/3, burst 8/8).
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
const { execFile } = require('node:child_process');
|
||||
|
||||
/**
|
||||
* Windows WindowContextProvider. Reads the foreground window (Win32) and, when
|
||||
* a click point is given, the UI Automation element under it. Best-effort:
|
||||
* resolves {} on any failure. Extracted verbatim from text-intel.js so the
|
||||
* shared code carries no `process.platform` branch.
|
||||
*/
|
||||
function createWindowsWindowContextProvider() {
|
||||
return {
|
||||
async collect(osPoint = null) {
|
||||
const hasPoint = osPoint && Number.isFinite(osPoint.x) && Number.isFinite(osPoint.y);
|
||||
const clickX = hasPoint ? Number(osPoint.x) : 0;
|
||||
const clickY = hasPoint ? Number(osPoint.y) : 0;
|
||||
const script = `
|
||||
$clickX = ${clickX};
|
||||
$clickY = ${clickY};
|
||||
$elementLabel = '';
|
||||
$elementRole = '';
|
||||
$elementClass = '';
|
||||
$elementProcessId = 0;
|
||||
$elementValue = '';
|
||||
if (${hasPoint ? '$true' : '$false'}) {
|
||||
try {
|
||||
Add-Type -AssemblyName UIAutomationClient,UIAutomationTypes,WindowsBase | Out-Null
|
||||
$point = New-Object System.Windows.Point($clickX, $clickY);
|
||||
$element = [System.Windows.Automation.AutomationElement]::FromPoint($point);
|
||||
if ($element) {
|
||||
$current = $element.Current;
|
||||
$elementLabel = $current.Name;
|
||||
$elementRole = $current.LocalizedControlType;
|
||||
$elementClass = $current.ClassName;
|
||||
$elementProcessId = $current.ProcessId;
|
||||
try {
|
||||
$valPattern = [System.Windows.Automation.ValuePattern]::Pattern;
|
||||
if ($element.GetSupportedPatterns() -contains $valPattern) {
|
||||
$elementValue = $element.GetCurrentPattern($valPattern).Current.Value;
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
Add-Type @"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
public static class Win32 {
|
||||
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
||||
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
|
||||
}
|
||||
"@;
|
||||
$hWnd = [Win32]::GetForegroundWindow();
|
||||
$sb = New-Object System.Text.StringBuilder 512;
|
||||
[void][Win32]::GetWindowText($hWnd, $sb, $sb.Capacity);
|
||||
$pid = 0;
|
||||
[void][Win32]::GetWindowThreadProcessId($hWnd, [ref]$pid);
|
||||
$proc = Get-Process -Id $pid -ErrorAction SilentlyContinue | Select-Object -First 1;
|
||||
$out = [ordered]@{
|
||||
appName = if ($proc) { $proc.ProcessName } else { '' };
|
||||
windowTitle = $sb.ToString();
|
||||
elementLabel = $elementLabel;
|
||||
elementRole = $elementRole;
|
||||
elementClass = $elementClass;
|
||||
elementValue = $elementValue;
|
||||
elementProcessId = $elementProcessId;
|
||||
pid = $pid;
|
||||
};
|
||||
$out | ConvertTo-Json -Compress;
|
||||
`;
|
||||
return new Promise((resolve) => {
|
||||
execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
|
||||
encoding: 'utf8',
|
||||
timeout: 4000,
|
||||
windowsHide: true,
|
||||
}, (err, stdout) => {
|
||||
if (err) { resolve({}); return; }
|
||||
try { resolve(JSON.parse(stdout.trim() || '{}')); } catch { resolve({}); }
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { createWindowsWindowContextProvider };
|
||||
Reference in New Issue
Block a user