dsh-click 0.3.23 → 0.3.25
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/CHANGELOG.md +12 -0
- package/LICENSE +0 -0
- package/README-es.md +152 -105
- package/README-hi.md +152 -105
- package/README-pt.md +152 -105
- package/README-zh.md +152 -105
- package/README.md +154 -110
- package/SECURITY.md +0 -0
- package/THIRD_PARTY_NOTICES.md +0 -0
- package/cordis.patch.yml +4 -0
- package/lib/index.js +9 -4
- package/lib/types/actions.d.ts +0 -0
- package/lib/types/actions.d.ts.map +0 -0
- package/lib/types/config.d.ts +10 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/events.d.ts +0 -0
- package/lib/types/events.d.ts.map +0 -0
- package/lib/types/index.d.ts +0 -0
- package/lib/types/index.d.ts.map +0 -0
- package/lib/types/observe.d.ts +3 -2
- package/lib/types/observe.d.ts.map +1 -1
- package/lib/types/platform/runner.d.ts +0 -0
- package/lib/types/platform/runner.d.ts.map +0 -0
- package/lib/types/platform/selection.d.ts +0 -0
- package/lib/types/platform/selection.d.ts.map +0 -0
- package/lib/types/platform/types.d.ts +0 -0
- package/lib/types/platform/types.d.ts.map +0 -0
- package/lib/types/sanitize.d.ts +0 -0
- package/lib/types/sanitize.d.ts.map +0 -0
- package/lib/types/tools.d.ts +0 -0
- package/lib/types/tools.d.ts.map +0 -0
- package/lib/types/version.d.ts +1 -1
- package/lib/types/version.d.ts.map +0 -0
- package/lib/types/vision/grounding.d.ts +0 -0
- package/lib/types/vision/grounding.d.ts.map +0 -0
- package/lib/types/vision/index.d.ts +0 -0
- package/lib/types/vision/index.d.ts.map +0 -0
- package/lib/types/vision/ocr.d.ts +0 -0
- package/lib/types/vision/ocr.d.ts.map +0 -0
- package/lib/types/vision.d.ts +0 -0
- package/lib/types/vision.d.ts.map +0 -0
- package/native/win32/dsh-click-helper.ps1 +145 -69
- package/package.json +6 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-click native helper (Windows PowerShell 5.1).
|
|
1
|
+
# dsh-click native helper (Windows PowerShell 5.1).
|
|
2
2
|
#
|
|
3
3
|
# One request = one process: JSON request on stdin, JSON response on stdout
|
|
4
4
|
# (exit code 0 even for handled failures; the error envelope is the response).
|
|
@@ -57,6 +57,87 @@ public static class DshClickWin32 {
|
|
|
57
57
|
[DllImport("user32.dll")]
|
|
58
58
|
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
59
59
|
|
|
60
|
+
// ---- dsh-click input patch: real keyboard injection + reliable focus ----
|
|
61
|
+
[DllImport("user32.dll")]
|
|
62
|
+
public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
|
63
|
+
|
|
64
|
+
[DllImport("user32.dll")]
|
|
65
|
+
public static extern IntPtr SetFocus(IntPtr hWnd);
|
|
66
|
+
|
|
67
|
+
[DllImport("user32.dll")]
|
|
68
|
+
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
69
|
+
|
|
70
|
+
[DllImport("user32.dll")]
|
|
71
|
+
public static extern bool IsIconic(IntPtr hWnd);
|
|
72
|
+
|
|
73
|
+
[DllImport("kernel32.dll")]
|
|
74
|
+
public static extern uint GetCurrentThreadId();
|
|
75
|
+
|
|
76
|
+
[DllImport("user32.dll")]
|
|
77
|
+
public static extern short VkKeyScanW(char ch);
|
|
78
|
+
|
|
79
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
80
|
+
public struct KEYBDINPUT {
|
|
81
|
+
public ushort wVk;
|
|
82
|
+
public ushort wScan;
|
|
83
|
+
public uint dwFlags;
|
|
84
|
+
public uint time;
|
|
85
|
+
public IntPtr dwExtraInfo;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
[StructLayout(LayoutKind.Explicit, Size = 40)]
|
|
89
|
+
public struct INPUT {
|
|
90
|
+
[FieldOffset(0)] public uint type;
|
|
91
|
+
[FieldOffset(8)] public KEYBDINPUT ki;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
95
|
+
public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
|
96
|
+
|
|
97
|
+
public static uint SendUnicodeText(string text) {
|
|
98
|
+
if (string.IsNullOrEmpty(text)) return 0;
|
|
99
|
+
var list = new List<INPUT>();
|
|
100
|
+
foreach (char c in text) {
|
|
101
|
+
INPUT down = new INPUT();
|
|
102
|
+
down.type = 1;
|
|
103
|
+
down.ki.wScan = (ushort)c;
|
|
104
|
+
down.ki.dwFlags = 0x0004;
|
|
105
|
+
list.Add(down);
|
|
106
|
+
INPUT up = new INPUT();
|
|
107
|
+
up.type = 1;
|
|
108
|
+
up.ki.wScan = (ushort)c;
|
|
109
|
+
up.ki.dwFlags = 0x0004 | 0x0002;
|
|
110
|
+
list.Add(up);
|
|
111
|
+
}
|
|
112
|
+
return SendInput((uint)list.Count, list.ToArray(), Marshal.SizeOf(typeof(INPUT)));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public static uint SendVirtualKey(ushort vk, bool up) {
|
|
116
|
+
INPUT inp = new INPUT();
|
|
117
|
+
inp.type = 1;
|
|
118
|
+
inp.ki.wVk = vk;
|
|
119
|
+
inp.ki.dwFlags = up ? 0x0002u : 0u;
|
|
120
|
+
return SendInput(1, new INPUT[] { inp }, Marshal.SizeOf(typeof(INPUT)));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public static bool BringToFront(IntPtr hWnd) {
|
|
124
|
+
if (IsIconic(hWnd)) ShowWindow(hWnd, 9);
|
|
125
|
+
if (GetForegroundWindow() == hWnd) return true;
|
|
126
|
+
IntPtr fg = GetForegroundWindow();
|
|
127
|
+
uint ignored;
|
|
128
|
+
uint fgThread = GetWindowThreadProcessId(fg, out ignored);
|
|
129
|
+
uint targetThread = GetWindowThreadProcessId(hWnd, out ignored);
|
|
130
|
+
uint myThread = GetCurrentThreadId();
|
|
131
|
+
bool a1 = AttachThreadInput(myThread, fgThread, true);
|
|
132
|
+
bool a2 = AttachThreadInput(myThread, targetThread, true);
|
|
133
|
+
SetForegroundWindow(hWnd);
|
|
134
|
+
SetFocus(hWnd);
|
|
135
|
+
if (a2) AttachThreadInput(myThread, targetThread, false);
|
|
136
|
+
if (a1) AttachThreadInput(myThread, fgThread, false);
|
|
137
|
+
System.Threading.Thread.Sleep(40);
|
|
138
|
+
return GetForegroundWindow() == hWnd;
|
|
139
|
+
}
|
|
140
|
+
|
|
60
141
|
[StructLayout(LayoutKind.Sequential)]
|
|
61
142
|
public struct POINT { public int X; public int Y; }
|
|
62
143
|
|
|
@@ -396,7 +477,7 @@ function Find-ElementByRuntimeId($windowElement, $runtimeIdText) {
|
|
|
396
477
|
$ids = @()
|
|
397
478
|
foreach ($part in ($runtimeIdText -split '\.')) { $ids += [int]$part }
|
|
398
479
|
if ($ids.Count -eq 0) { return $null }
|
|
399
|
-
$condition =
|
|
480
|
+
$condition = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::RuntimeIdProperty, [int[]]$ids)
|
|
400
481
|
try {
|
|
401
482
|
return $windowElement.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $condition)
|
|
402
483
|
} catch {
|
|
@@ -449,45 +530,30 @@ function Post-Click($hwnd, $cx, $cy, $button) {
|
|
|
449
530
|
[void][DshClickWin32]::PostMessage($hwnd, $up, [IntPtr]0, $lparam)
|
|
450
531
|
}
|
|
451
532
|
|
|
452
|
-
function Get-
|
|
453
|
-
$
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
$
|
|
466
|
-
$
|
|
467
|
-
$
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
$map['RIGHT'] = @(0x27, $true)
|
|
474
|
-
$map['CTRL'] = @(0x11, $false)
|
|
475
|
-
$map['CONTROL'] = @(0x11, $false)
|
|
476
|
-
$map['SHIFT'] = @(0x10, $false)
|
|
477
|
-
$map['ALT'] = @(0x12, $false)
|
|
478
|
-
$map['MENU'] = @(0x12, $false)
|
|
479
|
-
$map['WIN'] = @(0x5B, $false)
|
|
480
|
-
$map['WINDOWS'] = @(0x5B, $false)
|
|
481
|
-
foreach ($n in 1..12) { $map["F$n"] = @((0x6F + $n), $true) }
|
|
482
|
-
foreach ($c in 0..9) { $map["$c"] = @((0x30 + $c), $true) }
|
|
483
|
-
foreach ($code in 65..90) {
|
|
484
|
-
$map[[string][char]$code] = @((0x41 + ($code - 65)), $true)
|
|
485
|
-
}
|
|
486
|
-
return $map
|
|
533
|
+
function Get-VirtualKey([string]$token) {
|
|
534
|
+
$names = @{
|
|
535
|
+
'ENTER' = 0x0D; 'RETURN' = 0x0D; 'TAB' = 0x09; 'ESC' = 0x1B; 'ESCAPE' = 0x1B;
|
|
536
|
+
'SPACE' = 0x20; 'BACKSPACE' = 0x08; 'DELETE' = 0x2E; 'DEL' = 0x2E; 'INSERT' = 0x2D;
|
|
537
|
+
'HOME' = 0x24; 'END' = 0x23; 'PAGEUP' = 0x21; 'PGUP' = 0x21; 'PAGEDOWN' = 0x22; 'PGDN' = 0x22;
|
|
538
|
+
'UP' = 0x26; 'DOWN' = 0x28; 'LEFT' = 0x25; 'RIGHT' = 0x27;
|
|
539
|
+
'CTRL' = 0x11; 'CONTROL' = 0x11; 'SHIFT' = 0x10; 'ALT' = 0x12; 'MENU' = 0x12;
|
|
540
|
+
'WIN' = 0x5B; 'WINDOWS' = 0x5B; 'LWIN' = 0x5B; 'RWIN' = 0x5C; 'APPS' = 0x5D;
|
|
541
|
+
'CAPSLOCK' = 0x14; 'NUMLOCK' = 0x90; 'SCROLLLOCK' = 0x91; 'PRINTSCREEN' = 0x2C;
|
|
542
|
+
'PAUSE' = 0x13; 'COMMA' = 0xBC; 'PERIOD' = 0xBE; 'SLASH' = 0xBF; 'SEMICOLON' = 0xBA;
|
|
543
|
+
'QUOTE' = 0xDE; 'LBRACKET' = 0xDB; 'RBRACKET' = 0xDD; 'BACKSLASH' = 0xDC;
|
|
544
|
+
'MINUS' = 0xBD; 'PLUS' = 0xBB; 'TILDE' = 0xC0
|
|
545
|
+
}
|
|
546
|
+
if ($names.ContainsKey($token)) { return [int]$names[$token] }
|
|
547
|
+
if ($token -match '^F([1-9]|1[0-2])$') { return 0x6F + [int]$Matches[1] }
|
|
548
|
+
if ($token.Length -eq 1) {
|
|
549
|
+
$scan = [DshClickWin32]::VkKeyScanW([char]$token)
|
|
550
|
+
if ($scan -ne -1) { return [int]($scan -band 0xFF) }
|
|
551
|
+
return [int][char]$token
|
|
552
|
+
}
|
|
553
|
+
throw "unknown key '$token'"
|
|
487
554
|
}
|
|
488
555
|
|
|
489
556
|
function Post-KeyCombo($hwnd, $keysText) {
|
|
490
|
-
$map = Get-KeyMap
|
|
491
557
|
$tokens = @()
|
|
492
558
|
foreach ($token in ($keysText -split '\+')) {
|
|
493
559
|
$trimmed = $token.Trim()
|
|
@@ -495,39 +561,37 @@ function Post-KeyCombo($hwnd, $keysText) {
|
|
|
495
561
|
$tokens += $trimmed.ToUpperInvariant()
|
|
496
562
|
}
|
|
497
563
|
if ($tokens.Count -eq 0) { throw 'empty key combination' }
|
|
564
|
+
$modifierNames = @('CTRL', 'CONTROL', 'SHIFT', 'ALT', 'MENU', 'WIN', 'WINDOWS', 'LWIN', 'RWIN')
|
|
498
565
|
$modifiers = @()
|
|
499
566
|
$mainKeys = @()
|
|
500
567
|
foreach ($token in $tokens) {
|
|
501
|
-
if ($
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
568
|
+
if ($modifierNames -contains $token) { $modifiers += $token } else { $mainKeys += $token }
|
|
569
|
+
}
|
|
570
|
+
$focused = $false
|
|
571
|
+
try { $focused = [DshClickWin32]::BringToFront($hwnd) } catch { $focused = $false }
|
|
572
|
+
if (-not $focused) {
|
|
573
|
+
# Last resort: posted messages (only works for windows that accept posted input).
|
|
574
|
+
foreach ($modifier in $modifiers) {
|
|
575
|
+
[void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr](Get-VirtualKey $modifier), [IntPtr]0)
|
|
509
576
|
}
|
|
577
|
+
foreach ($key in $mainKeys) {
|
|
578
|
+
$vk = Get-VirtualKey $key
|
|
579
|
+
[void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$vk, [IntPtr]0)
|
|
580
|
+
[void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$vk, [IntPtr]0)
|
|
581
|
+
}
|
|
582
|
+
for ($i = $modifiers.Count - 1; $i -ge 0; $i--) {
|
|
583
|
+
[void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr](Get-VirtualKey $modifiers[$i]), [IntPtr]0)
|
|
584
|
+
}
|
|
585
|
+
return
|
|
510
586
|
}
|
|
511
|
-
foreach ($modifier in $modifiers) {
|
|
512
|
-
$entry = $map[$modifier]
|
|
513
|
-
[void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$entry[0], [IntPtr]0)
|
|
514
|
-
}
|
|
587
|
+
foreach ($modifier in $modifiers) { [void][DshClickWin32]::SendVirtualKey([uint16](Get-VirtualKey $modifier), $false) }
|
|
515
588
|
foreach ($key in $mainKeys) {
|
|
516
|
-
$
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
[void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$entry[0], [IntPtr]0)
|
|
520
|
-
[void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$entry[0], [IntPtr]0)
|
|
521
|
-
} else {
|
|
522
|
-
$charCode = [int][char]$key
|
|
523
|
-
[void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$charCode, [IntPtr]0)
|
|
524
|
-
[void][DshClickWin32]::PostMessage($hwnd, 0x102, [IntPtr]$charCode, [IntPtr]0)
|
|
525
|
-
[void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$charCode, [IntPtr]0)
|
|
526
|
-
}
|
|
589
|
+
$vk = [uint16](Get-VirtualKey $key)
|
|
590
|
+
[void][DshClickWin32]::SendVirtualKey($vk, $false)
|
|
591
|
+
[void][DshClickWin32]::SendVirtualKey($vk, $true)
|
|
527
592
|
}
|
|
528
593
|
for ($i = $modifiers.Count - 1; $i -ge 0; $i--) {
|
|
529
|
-
|
|
530
|
-
[void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$entry[0], [IntPtr]0)
|
|
594
|
+
[void][DshClickWin32]::SendVirtualKey([uint16](Get-VirtualKey $modifiers[$i]), $true)
|
|
531
595
|
}
|
|
532
596
|
}
|
|
533
597
|
|
|
@@ -799,14 +863,26 @@ function Invoke-OpType($opArgs) {
|
|
|
799
863
|
throw "type failed and rollback $(if ($restored) { 'restored the original text' } else { 'could NOT restore the original text' }): $($_.Exception.Message)"
|
|
800
864
|
}
|
|
801
865
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
866
|
+
# dsh-click input patch: real keyboard input (SendInput) instead of posted WM_CHAR.
|
|
867
|
+
$center = Get-ElementCenter $element
|
|
868
|
+
$windowRect = Get-WindowRectInfo $hwnd
|
|
869
|
+
$client = Get-ClientPoint $hwnd $windowRect $center.x $center.y
|
|
870
|
+
Post-Click $hwnd $client.x $client.y 'left'
|
|
871
|
+
$focused = $false
|
|
872
|
+
try { $focused = [DshClickWin32]::BringToFront($hwnd) } catch { $focused = $false }
|
|
873
|
+
if ($focused) {
|
|
874
|
+
$sent = [DshClickWin32]::SendUnicodeText([string]$request.text)
|
|
875
|
+
if ($sent -gt 0) {
|
|
876
|
+
return (Get-ActionOutcome $hwnd 'type' 'posted' $null "typed $($sent / 2) chars via SendInput")
|
|
806
877
|
}
|
|
807
|
-
return (Get-ActionOutcome $hwnd 'type' 'posted' $null 'typed via posted WM_CHAR (no value pattern on the element)')
|
|
808
878
|
}
|
|
809
|
-
|
|
879
|
+
if (-not $focusFallback) {
|
|
880
|
+
throw "element '$($request.elementId)' exposes no value pattern and the window could not be focused - refusing to type into it"
|
|
881
|
+
}
|
|
882
|
+
foreach ($ch in [char[]][string]$request.text) {
|
|
883
|
+
[void][DshClickWin32]::PostMessage($hwnd, 0x102, [IntPtr][int]$ch, [IntPtr]0)
|
|
884
|
+
}
|
|
885
|
+
return (Get-ActionOutcome $hwnd 'type' 'posted' $null 'typed via posted WM_CHAR (SendInput unavailable)')
|
|
810
886
|
}
|
|
811
887
|
|
|
812
888
|
function Invoke-OpScroll($opArgs) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-click",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"description": "Cross-platform native desktop control tools for DeepSeek Harness (Windows first): screen_shot, screen_read (accessibility tree + pixel description for text-only models), click/type/scroll/key, and app_list/app_launch — every mutating action gated by approval, never stealing foreground focus, with stale-observation rejection and process-identity checks",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"@deepseek-ai/dsh-subprocess": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0 || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0",
|
|
116
116
|
"@deepseek-ai/dsh-tools": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0 || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0",
|
|
117
117
|
"@deepseek-ai/dsh-user-approval": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0 || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0",
|
|
118
|
-
"@deepseek-ai/schemastery": "^3.18.5
|
|
118
|
+
"@deepseek-ai/schemastery": "^3.18.5"
|
|
119
119
|
},
|
|
120
120
|
"dependencies": {},
|
|
121
121
|
"devDependencies": {
|
|
@@ -131,13 +131,13 @@
|
|
|
131
131
|
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
|
|
132
132
|
"@deepseek-ai/dsh-tools": "0.2.1-rc.2",
|
|
133
133
|
"@deepseek-ai/dsh-user-approval": "0.2.1-rc.2",
|
|
134
|
-
"@deepseek-ai/schemastery": "^3.18.5
|
|
135
|
-
"@types/node": "^22.
|
|
136
|
-
"@vitest/coverage-v8": "3.2.
|
|
134
|
+
"@deepseek-ai/schemastery": "^3.18.5",
|
|
135
|
+
"@types/node": "^22.20.5",
|
|
136
|
+
"@vitest/coverage-v8": "3.2.7",
|
|
137
137
|
"oxlint": "0.18.1",
|
|
138
138
|
"vitest": "^3.2.0",
|
|
139
139
|
"@deepseek-ai/dsh-working-directory": "0.2.1-rc.2",
|
|
140
|
-
"tsdown": "^0.
|
|
140
|
+
"tsdown": "^0.23.1",
|
|
141
141
|
"typescript": "^5.9.0"
|
|
142
142
|
},
|
|
143
143
|
"scripts": {
|