@intentic/desktop 1.176.3 → 1.209.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -4
- package/dist/apps-windows.d.ts +1 -1
- package/dist/apps-windows.d.ts.map +1 -1
- package/dist/apps-windows.js +58 -10
- package/dist/apps-windows.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @intentic/desktop
|
|
2
2
|
|
|
3
|
-
Drive a desktop from Node
|
|
4
|
-
|
|
3
|
+
Drive a desktop from Node, on Windows and Linux, with **no native modules**.
|
|
4
|
+
|
|
5
|
+
Capture the screen, move the pointer, click, type, press chords, scroll and drag.
|
|
5
6
|
|
|
6
7
|
```ts
|
|
7
8
|
import { desktop } from "@intentic/desktop";
|
|
@@ -41,8 +42,19 @@ one-line install for the specific thing that is absent.
|
|
|
41
42
|
|
|
42
43
|
**macOS** — capture works, input does not. The methods throw rather than silently doing nothing.
|
|
43
44
|
|
|
44
|
-
**Windows enumeration** — `
|
|
45
|
-
|
|
45
|
+
**Windows enumeration** — `EnumWindows` supplies every visible top-level window, including several owned by one
|
|
46
|
+
process; process lookup adds the app name, and the remaining P/Invokes supply bounds and foreground state.
|
|
47
|
+
`Get-Process.MainWindowHandle` is intentionally not used because it collapses a workspace and its dialog into
|
|
48
|
+
one row.
|
|
49
|
+
|
|
50
|
+
**Taking focus on Windows needs more than `SetForegroundWindow`.** Windows refuses that call from a process
|
|
51
|
+
that is not already in the foreground, and refuses it *quietly* — it flashes the taskbar button and leaves the
|
|
52
|
+
keyboard where it was, so the next `type` or `key` goes to whatever the person at that desk had open. A backend
|
|
53
|
+
running from a fresh `powershell.exe` misses every qualifying condition at once, and a machine whose
|
|
54
|
+
foreground-lock timeout has been raised (gaming and anti-focus-stealing utilities do this) closes the last of
|
|
55
|
+
them for good. So `focusWindow` briefly attaches its input queue to the foreground window's thread and the
|
|
56
|
+
target's, which is what earns the right, and then *checks* — it raises a `DesktopError` rather than returning
|
|
57
|
+
to a caller that is about to type into the wrong window.
|
|
46
58
|
|
|
47
59
|
**Wayland enumeration mostly cannot happen**, and that is a design decision rather than a gap: a compositor does
|
|
48
60
|
not let one client enumerate another's windows, the same protection that stops it synthesising input. The
|
|
@@ -61,3 +73,12 @@ silent misclicks.
|
|
|
61
73
|
`BackSpace`, `Page_Up`, plus `ctrl`/`alt`/`shift`/`super` and the aliases people actually type — `enter`, `esc`,
|
|
62
74
|
`win`, `cmd`) and each backend translates. Without it every caller would be platform-aware, which is the exact
|
|
63
75
|
coupling this package exists to remove.
|
|
76
|
+
|
|
77
|
+
## Key files
|
|
78
|
+
|
|
79
|
+
- [src/index.ts](src/index.ts) — the public surface.
|
|
80
|
+
- [src/input-linux.ts](src/input-linux.ts) / [src/input-windows.ts](src/input-windows.ts) — pointer, keys and text, per platform.
|
|
81
|
+
- [src/apps-linux.ts](src/apps-linux.ts) / [src/apps-windows.ts](src/apps-windows.ts) — windows, focus, launching and the clipboard, per platform.
|
|
82
|
+
- [src/screen.ts](src/screen.ts) — capturing the screen.
|
|
83
|
+
- [src/keys.ts](src/keys.ts) — chord and key-name parsing, shared by both platforms.
|
|
84
|
+
- [src/run.ts](src/run.ts) — how commands are invoked without a native module.
|
package/dist/apps-windows.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps-windows.d.ts","sourceRoot":"","sources":["../src/apps-windows.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"apps-windows.d.ts","sourceRoot":"","sources":["../src/apps-windows.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AA2E3D,eAAO,MAAM,WAAW;IACpB,OAAO,QAAY,OAAO,CAAC,UAAU,EAAE,CAAC;IAqBxC,WAAW,OAAa,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;IAiC9C,MAAM,WAAiB,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;IAK7C,aAAa,QAAY,OAAO,CAAC,MAAM,CAAC;IAExC,cAAc,SAAe,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;CAKtD,CAAC"}
|
package/dist/apps-windows.js
CHANGED
|
@@ -1,40 +1,88 @@
|
|
|
1
1
|
import { parseWindowsJson } from "./parse.js";
|
|
2
2
|
import { run } from "./run.js";
|
|
3
|
+
import { DesktopError } from "./types.js";
|
|
3
4
|
const SHIM = `
|
|
4
5
|
Add-Type -TypeDefinition @"
|
|
5
6
|
using System;
|
|
7
|
+
using System.Text;
|
|
6
8
|
using System.Runtime.InteropServices;
|
|
7
9
|
public class IntenticWin {
|
|
8
10
|
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
|
|
11
|
+
public delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
|
|
12
|
+
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc callback, IntPtr l);
|
|
13
|
+
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);
|
|
14
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetWindowTextLength(IntPtr h);
|
|
15
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetWindowText(IntPtr h, StringBuilder text, int length);
|
|
16
|
+
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint processId);
|
|
9
17
|
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r);
|
|
10
18
|
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
|
11
19
|
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
|
|
20
|
+
[DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
|
|
21
|
+
[DllImport("user32.dll")] public static extern bool AttachThreadInput(uint from, uint to, bool attach);
|
|
12
22
|
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int cmd);
|
|
13
23
|
[DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
|
|
24
|
+
[DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();
|
|
14
25
|
}
|
|
15
26
|
"@ -ErrorAction SilentlyContinue;
|
|
16
27
|
`;
|
|
17
28
|
const SW_RESTORE = 9;
|
|
29
|
+
const FOCUS_POLLS = 20;
|
|
30
|
+
const FOCUS_POLL_MS = 50;
|
|
18
31
|
const LIST = `
|
|
19
32
|
$fg = [IntenticWin]::GetForegroundWindow();
|
|
20
|
-
$items =
|
|
33
|
+
$items = [System.Collections.Generic.List[object]]::new();
|
|
34
|
+
$callback = [IntenticWin+EnumWindowsProc] {
|
|
35
|
+
param([IntPtr]$h, [IntPtr]$unused)
|
|
36
|
+
if (-not [IntenticWin]::IsWindowVisible($h)) { return $true }
|
|
37
|
+
$length = [IntenticWin]::GetWindowTextLength($h);
|
|
38
|
+
if ($length -le 0) { return $true }
|
|
39
|
+
$text = [System.Text.StringBuilder]::new($length + 1);
|
|
40
|
+
[void][IntenticWin]::GetWindowText($h, $text, $text.Capacity);
|
|
41
|
+
if ($text.Length -eq 0) { return $true }
|
|
42
|
+
[uint32]$processId = 0;
|
|
43
|
+
[void][IntenticWin]::GetWindowThreadProcessId($h, [ref]$processId);
|
|
44
|
+
$process = Get-Process -Id $processId -ErrorAction SilentlyContinue;
|
|
45
|
+
$app = if ($process) { $process.ProcessName } else { '' };
|
|
21
46
|
$r = New-Object IntenticWin+RECT;
|
|
22
|
-
[void][IntenticWin]::GetWindowRect($
|
|
23
|
-
[pscustomobject]@{
|
|
24
|
-
id = [string]$
|
|
47
|
+
[void][IntenticWin]::GetWindowRect($h, [ref]$r);
|
|
48
|
+
$items.Add([pscustomobject]@{
|
|
49
|
+
id = [string]($h.ToInt64()); title = $text.ToString(); app = $app;
|
|
25
50
|
x = $r.Left; y = $r.Top; width = ($r.Right - $r.Left); height = ($r.Bottom - $r.Top);
|
|
26
|
-
focused = ($
|
|
27
|
-
}
|
|
28
|
-
|
|
51
|
+
focused = ($h -eq $fg)
|
|
52
|
+
});
|
|
53
|
+
return $true
|
|
54
|
+
};
|
|
55
|
+
[void][IntenticWin]::EnumWindows($callback, [IntPtr]::Zero);
|
|
29
56
|
ConvertTo-Json -Compress -Depth 3 -InputObject $items;
|
|
30
57
|
`;
|
|
31
58
|
const powershell = (script) => run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `${SHIM}${script}`]);
|
|
32
59
|
export const windowsApps = {
|
|
33
60
|
windows: async () => parseWindowsJson((await powershell(LIST)).trim()),
|
|
34
61
|
focusWindow: async (id) => {
|
|
35
|
-
await powershell(`$h = [IntPtr]::new([int64]${JSON.stringify(id)});
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
const settled = await powershell(`$h = [IntPtr]::new([int64]${JSON.stringify(id)});
|
|
63
|
+
if ([IntenticWin]::IsIconic($h)) { [void][IntenticWin]::ShowWindow($h, ${SW_RESTORE}) };
|
|
64
|
+
if ([IntenticWin]::GetForegroundWindow() -ne $h) {
|
|
65
|
+
[uint32]$ignored = 0;
|
|
66
|
+
$mine = [IntenticWin]::GetCurrentThreadId();
|
|
67
|
+
$queues = @(
|
|
68
|
+
[IntenticWin]::GetWindowThreadProcessId([IntenticWin]::GetForegroundWindow(), [ref]$ignored),
|
|
69
|
+
[IntenticWin]::GetWindowThreadProcessId($h, [ref]$ignored)
|
|
70
|
+
) | Where-Object { $_ -ne 0 -and $_ -ne $mine } | Select-Object -Unique;
|
|
71
|
+
foreach ($queue in $queues) { [void][IntenticWin]::AttachThreadInput($mine, $queue, $true) };
|
|
72
|
+
try {
|
|
73
|
+
[void][IntenticWin]::BringWindowToTop($h);
|
|
74
|
+
[void][IntenticWin]::SetForegroundWindow($h);
|
|
75
|
+
} finally {
|
|
76
|
+
foreach ($queue in $queues) { [void][IntenticWin]::AttachThreadInput($mine, $queue, $false) };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for ($poll = 0; $poll -lt ${FOCUS_POLLS}; $poll++) {
|
|
80
|
+
if ([IntenticWin]::GetForegroundWindow() -eq $h) { Write-Output 'focused'; break };
|
|
81
|
+
Start-Sleep -Milliseconds ${FOCUS_POLL_MS};
|
|
82
|
+
}`);
|
|
83
|
+
if (settled.trim() !== `focused`) {
|
|
84
|
+
throw new DesktopError(`Windows would not give window ${id} the keyboard — it is gone, or something is holding the foreground (a UAC prompt, a full-screen app, or a locked session).`);
|
|
85
|
+
}
|
|
38
86
|
},
|
|
39
87
|
launch: async (target) => {
|
|
40
88
|
await powershell(`Start-Process -FilePath ${JSON.stringify(target)};`);
|
package/dist/apps-windows.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps-windows.js","sourceRoot":"","sources":["../src/apps-windows.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"apps-windows.js","sourceRoot":"","sources":["../src/apps-windows.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAmB,MAAM,YAAY,CAAC;AAW3D,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;CAuBZ,CAAC;AAIF,MAAM,UAAU,GAAG,CAAC,CAAC;AAIrB,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BZ,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,MAAc,EAAmB,EAAE,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,GAAG,IAAI,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;AAEjJ,MAAM,CAAC,MAAM,WAAW,GAAG;IACvB,OAAO,EAAE,KAAK,IAA2B,EAAE,CAAC,gBAAgB,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAqB7F,WAAW,EAAE,KAAK,EAAE,EAAU,EAAiB,EAAE;QAC7C,MAAM,OAAO,GAAG,MAAM,UAAU,CAC5B,6BAA6B,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;sFAC2B,UAAU;;;;;;;;;;;;;;;;yCAgBvD,WAAW;;2CAET,aAAa;eACzC,CACN,CAAC;QACF,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,IAAI,YAAY,CAClB,iCAAiC,EAAE,4HAA4H,CAClK,CAAC;QACN,CAAC;IACL,CAAC;IAID,MAAM,EAAE,KAAK,EAAE,MAAc,EAAiB,EAAE;QAC5C,MAAM,UAAU,CAAC,2BAA2B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3E,CAAC;IAGD,aAAa,EAAE,KAAK,IAAqB,EAAE,CAAC,MAAM,UAAU,CAAC,qBAAqB,CAAC;IAEnF,cAAc,EAAE,KAAK,EAAE,IAAY,EAAiB,EAAE;QAGlD,MAAM,UAAU,CAAC,yBAAyB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5E,CAAC;CACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentic/desktop",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.209.0",
|
|
4
4
|
"description": "Drive a desktop from Node: capture the screen, move the pointer, click, type and scroll — Windows and Linux, no native modules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://
|
|
10
|
-
"directory": "
|
|
9
|
+
"url": "git+https://github.com/intentic/intentic.git",
|
|
10
|
+
"directory": "_computers/desktop"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist"
|