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,99 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
|
||||
/**
|
||||
* Linux capture-capability diagnostics. Detects the session type, portal /
|
||||
* PipeWire availability, xinput, readable input devices, and the sandbox
|
||||
* situation, and turns them into an actionable capability profile the UI can
|
||||
* show instead of console-only failures.
|
||||
*
|
||||
* Pure detection with injectable probes so it is unit-testable without a real
|
||||
* desktop session.
|
||||
*/
|
||||
|
||||
function defaultHasBinary(name) {
|
||||
try {
|
||||
execFileSync('which', [name], { stdio: 'pipe' });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function detectSessionType(env = process.env) {
|
||||
const t = String(env.XDG_SESSION_TYPE || '').toLowerCase();
|
||||
if (t === 'wayland' || t === 'x11') return t;
|
||||
if (env.WAYLAND_DISPLAY) return 'wayland';
|
||||
if (env.DISPLAY) return 'x11';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function detectLinuxCapabilities({
|
||||
env = process.env,
|
||||
hasBinary = defaultHasBinary,
|
||||
existsSync = fs.existsSync,
|
||||
readdirSync = fs.readdirSync,
|
||||
} = {}) {
|
||||
const sessionType = detectSessionType(env);
|
||||
const isWayland = sessionType === 'wayland';
|
||||
|
||||
// XDG Desktop Portal + PipeWire are how Wayland screen capture works.
|
||||
const hasPortalBus = Boolean(env.DBUS_SESSION_BUS_ADDRESS);
|
||||
let hasPipeWire = false;
|
||||
try {
|
||||
hasPipeWire = hasBinary('pipewire') || existsSync(`/run/user/${process.getuid ? process.getuid() : ''}/pipewire-0`);
|
||||
} catch {
|
||||
hasPipeWire = hasBinary('pipewire');
|
||||
}
|
||||
|
||||
const hasXinput = hasBinary('xinput');
|
||||
const hasXprop = hasBinary('xprop');
|
||||
|
||||
// Readable /dev/input event nodes gate the evdev click fallback.
|
||||
let readableInputDevices = 0;
|
||||
try {
|
||||
for (const name of readdirSync('/dev/input')) {
|
||||
if (!/^event\d+$/.test(name)) continue;
|
||||
try { fs.accessSync(`/dev/input/${name}`, fs.constants.R_OK); readableInputDevices += 1; } catch { /* not readable */ }
|
||||
}
|
||||
} catch { /* /dev/input not present */ }
|
||||
|
||||
// Determine the click-capture profile for this session.
|
||||
let clickCapture;
|
||||
if (!isWayland && hasXinput) clickCapture = 'x11-xinput';
|
||||
else if (readableInputDevices > 0) clickCapture = isWayland ? 'evdev-wayland' : 'evdev-x11';
|
||||
else clickCapture = 'hotkey-or-interval-only';
|
||||
|
||||
const messages = [];
|
||||
if (isWayland && !hasPipeWire) {
|
||||
messages.push('Wayland screen capture needs PipeWire and the XDG Desktop Portal. Install pipewire and xdg-desktop-portal.');
|
||||
}
|
||||
if (isWayland && !hasPortalBus) {
|
||||
messages.push('No D-Bus session bus detected; the screen-share portal cannot be reached.');
|
||||
}
|
||||
if (!isWayland && !hasXinput) {
|
||||
messages.push('xinput not found: per-click capture with a marker is unavailable on X11 without it.');
|
||||
}
|
||||
if (clickCapture === 'hotkey-or-interval-only') {
|
||||
messages.push('No global click source available. Recording falls back to a hotkey or interval trigger.');
|
||||
}
|
||||
|
||||
return {
|
||||
os: 'linux',
|
||||
sessionType,
|
||||
isWayland,
|
||||
hasPortalBus,
|
||||
hasPipeWire,
|
||||
hasXinput,
|
||||
hasXprop,
|
||||
readableInputDevices,
|
||||
clickCapture,
|
||||
// Portal capture is the safe Wayland baseline; X11 can grab directly.
|
||||
screenCapture: isWayland ? 'wayland-portal' : 'x11-direct',
|
||||
messages,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { detectLinuxCapabilities, detectSessionType };
|
||||
Reference in New Issue
Block a user