@intentic/desktop 1.176.2 → 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 CHANGED
@@ -1,7 +1,8 @@
1
1
  # @intentic/desktop
2
2
 
3
- Drive a desktop from Node: capture the screen, move the pointer, click, type, press chords, scroll and drag —
4
- on Windows and Linux, with **no native modules**.
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** — `Get-Process` already knows every process with a main window and its title; the
45
- P/Invoke is only for the two things it does not carry, the window rectangle and which window is in front.
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.
@@ -1,4 +1,4 @@
1
- import type { WindowInfo } from "./types.js";
1
+ import { type WindowInfo } from "./types.js";
2
2
  export declare const windowsApps: {
3
3
  windows: () => Promise<WindowInfo[]>;
4
4
  focusWindow: (id: string) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"apps-windows.d.ts","sourceRoot":"","sources":["../src/apps-windows.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA6C7C,eAAO,MAAM,WAAW;IACpB,OAAO,QAAY,OAAO,CAAC,UAAU,EAAE,CAAC;IAExC,WAAW,OAAa,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;IAU9C,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"}
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"}
@@ -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 = @(Get-Process | Where-Object { $_.MainWindowHandle -ne 0 -and $_.MainWindowTitle -ne '' } | ForEach-Object {
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($_.MainWindowHandle, [ref]$r);
23
- [pscustomobject]@{
24
- id = [string]$_.MainWindowHandle; title = $_.MainWindowTitle; app = $_.ProcessName;
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 = ($_.MainWindowHandle -eq $fg)
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
- `if ([IntenticWin]::IsIconic($h)) { [void][IntenticWin]::ShowWindow($h, ${SW_RESTORE}) }; ` +
37
- `[void][IntenticWin]::SetForegroundWindow($h);`);
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)};`);
@@ -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;AAW/B,MAAM,IAAI,GAAG;;;;;;;;;;;;;CAaZ,CAAC;AAIF,MAAM,UAAU,GAAG,CAAC,CAAC;AAErB,MAAM,IAAI,GAAG;;;;;;;;;;;;CAYZ,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;IAE7F,WAAW,EAAE,KAAK,EAAE,EAAU,EAAiB,EAAE;QAC7C,MAAM,UAAU,CACZ,6BAA6B,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK;YAChD,0EAA0E,UAAU,OAAO;YAC3F,+CAA+C,CACtD,CAAC;IACN,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"}
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.176.2",
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://gitlab.com/radarsu/intentic.git",
10
- "directory": "_libs/desktop"
9
+ "url": "git+https://github.com/intentic/intentic.git",
10
+ "directory": "_computers/desktop"
11
11
  },
12
12
  "files": [
13
13
  "dist"