@ycheung5/pi-computer-use 0.0.0-stage → 0.1.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.
Files changed (92) hide show
  1. package/GLOSSARY.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +165 -2
  4. package/assets/accessibility.cs +208 -0
  5. package/assets/browser-indicators.cs +37 -0
  6. package/assets/capture.cs +215 -0
  7. package/assets/clipboard.cs +197 -0
  8. package/assets/controls.cs +249 -0
  9. package/assets/indicator-preview.png +0 -0
  10. package/assets/indicators.cs +252 -0
  11. package/assets/node-LICENSE.txt +2732 -0
  12. package/assets/ocr.cs +86 -0
  13. package/assets/ownership.cs +59 -0
  14. package/assets/pointer.cs +229 -0
  15. package/assets/puppeteer-LICENSE.txt +202 -0
  16. package/assets/readme-banner.svg +33 -0
  17. package/assets/stop.cs +197 -0
  18. package/assets/text.cs +249 -0
  19. package/assets/wgc.cs +167 -0
  20. package/assets/window-management.cs +217 -0
  21. package/assets/window-marker.cs +23 -0
  22. package/assets/window-references.cs +80 -0
  23. package/assets/windows.cs +127 -0
  24. package/dist/accessibility.d.ts +29 -0
  25. package/dist/accessibility.js +258 -0
  26. package/dist/browser-actions.d.ts +21 -0
  27. package/dist/browser-actions.js +213 -0
  28. package/dist/browser-indicators.d.ts +23 -0
  29. package/dist/browser-indicators.js +69 -0
  30. package/dist/browser-profiles.d.ts +10 -0
  31. package/dist/browser-profiles.js +174 -0
  32. package/dist/browser.d.ts +11 -0
  33. package/dist/browser.js +2071 -0
  34. package/dist/capture.d.ts +74 -0
  35. package/dist/capture.js +199 -0
  36. package/dist/client.d.ts +38 -0
  37. package/dist/client.js +331 -0
  38. package/dist/desktop.d.ts +20 -0
  39. package/dist/desktop.js +42 -0
  40. package/dist/extension.d.ts +4 -0
  41. package/dist/extension.js +409 -0
  42. package/dist/launcher.d.ts +6 -0
  43. package/dist/launcher.js +85 -0
  44. package/dist/native.d.ts +11 -0
  45. package/dist/native.js +145 -0
  46. package/dist/ocr.d.ts +52 -0
  47. package/dist/ocr.js +246 -0
  48. package/dist/ownership.d.ts +4 -0
  49. package/dist/ownership.js +24 -0
  50. package/dist/pointer.d.ts +108 -0
  51. package/dist/pointer.js +174 -0
  52. package/dist/protocol.d.ts +15 -0
  53. package/dist/protocol.js +1 -0
  54. package/dist/runtime.d.ts +8 -0
  55. package/dist/runtime.js +72 -0
  56. package/dist/settings.d.ts +85 -0
  57. package/dist/settings.js +449 -0
  58. package/dist/stop.d.ts +24 -0
  59. package/dist/stop.js +156 -0
  60. package/dist/text.d.ts +100 -0
  61. package/dist/text.js +183 -0
  62. package/dist/waits.d.ts +19 -0
  63. package/dist/waits.js +262 -0
  64. package/dist/window-management.d.ts +25 -0
  65. package/dist/window-management.js +167 -0
  66. package/dist/worker.d.ts +1 -0
  67. package/dist/worker.js +540 -0
  68. package/docs/README.md +54 -0
  69. package/docs/adr/0001-preserve-reference-behavior.md +9 -0
  70. package/docs/adr/0002-window-lifetime-references.md +11 -0
  71. package/docs/adr/0003-exclusive-desktop-control.md +11 -0
  72. package/docs/adr/0004-pi-first-acceptance.md +9 -0
  73. package/docs/adr/0005-default-apps-and-workspace-reuse.md +7 -0
  74. package/docs/agents/domain.md +9 -0
  75. package/docs/agents/issue-tracker.md +10 -0
  76. package/docs/agents/triage-labels.md +9 -0
  77. package/docs/browser.md +113 -0
  78. package/docs/implementation-plan.md +139 -0
  79. package/docs/indicators.md +53 -0
  80. package/docs/native-interop-research.md +17 -0
  81. package/docs/ownership.md +25 -0
  82. package/docs/pi-extension-research.md +57 -0
  83. package/docs/pointer.md +65 -0
  84. package/docs/runtime.md +42 -0
  85. package/docs/settings.md +33 -0
  86. package/docs/spec.md +161 -0
  87. package/docs/stopping.md +83 -0
  88. package/docs/text.md +79 -0
  89. package/docs/tickets.md +84 -0
  90. package/docs/validation.md +1372 -0
  91. package/docs/windows.md +171 -0
  92. package/package.json +134 -4
@@ -0,0 +1,249 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Diagnostics;
4
+ using System.Threading;
5
+ using System.Runtime.InteropServices;
6
+ using System.Windows.Automation;
7
+
8
+ partial class WindowsBridge {
9
+ [StructLayout(LayoutKind.Sequential)] struct ControlPoint { public int X,Y; }
10
+ [StructLayout(LayoutKind.Sequential)] struct ControlGuiInfo { public uint Size,Flags; public IntPtr Active,Focus,Capture,MenuOwner,MoveSize,Caret; public Rect CaretRect; }
11
+ [DllImport("user32.dll")] static extern bool GetCursorPos(out ControlPoint point);
12
+ [DllImport("user32.dll",EntryPoint="GetWindowLongPtrW")] static extern IntPtr ControlStyle(IntPtr window,int index);
13
+ [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr window,uint command);
14
+ [DllImport("user32.dll")] static extern bool GetGUIThreadInfo(uint thread,ref ControlGuiInfo info);
15
+ [DllImport("user32.dll",EntryPoint="PostMessageW",SetLastError=true)] static extern bool ControlPost(IntPtr window,uint message,IntPtr wParam,IntPtr lParam);
16
+ [DllImport("user32.dll",EntryPoint="SendMessageTimeoutW",CharSet=CharSet.Unicode,SetLastError=true)] static extern IntPtr ControlText(IntPtr window,uint message,UIntPtr wParam,string text,uint flags,uint timeout,out UIntPtr result);
17
+ [DllImport("user32.dll")] static extern int GetScrollPos(IntPtr window,int bar);
18
+
19
+ // Shared in-process observer: never calls UIA and never captures text.
20
+ // Tagged agent input and physically suppressed input are excluded.
21
+ // Foreign injected input still invalidates the action and restoration.
22
+ static readonly UIntPtr OwnedInputMarker=new UIntPtr(0x50494355);
23
+ sealed class InteractionGuard {
24
+ delegate IntPtr Hook(int code,IntPtr message,IntPtr data);
25
+ [StructLayout(LayoutKind.Sequential)] struct Mouse {public ControlPoint Point;public uint Data,Flags,Time;public UIntPtr Extra;}
26
+ [StructLayout(LayoutKind.Sequential)] struct Key {public uint Code,Scan,Flags,Time;public UIntPtr Extra;}
27
+ [StructLayout(LayoutKind.Sequential)] struct Message {public IntPtr Window;public uint Id;public UIntPtr W;public IntPtr L;public uint Time;public ControlPoint Point;public uint Private;}
28
+ [DllImport("user32.dll",SetLastError=true)] static extern IntPtr SetWindowsHookEx(int kind,Hook callback,IntPtr module,uint thread);
29
+ [DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr hook);
30
+ [DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr hook,int code,IntPtr message,IntPtr data);
31
+ [DllImport("user32.dll")] static extern int GetMessage(out Message message,IntPtr window,uint first,uint last);
32
+ [DllImport("user32.dll")] static extern bool TranslateMessage(ref Message message);
33
+ [DllImport("user32.dll")] static extern IntPtr DispatchMessage(ref Message message);
34
+ [DllImport("kernel32.dll",CharSet=CharSet.Unicode)] static extern IntPtr GetModuleHandle(string name);
35
+ static readonly object gate=new object();
36
+ static readonly ManualResetEvent ready=new ManualResetEvent(false);
37
+ static readonly Hook mouseCallback=OnMouse,keyCallback=OnKey;
38
+ static bool started;static volatile bool available;static long activity;
39
+ readonly long baseline;
40
+ readonly bool requiresProtection;
41
+ static EventWaitHandle protection,cancellation;
42
+ static Process protector;
43
+ static string protectionName;
44
+ static bool Protected() {lock(gate)return protection!=null && !protector.HasExited && protection.WaitOne(0);}
45
+ static IntPtr OnMouse(int code,IntPtr message,IntPtr data) {
46
+ IntPtr consumed=CallNextHookEx(IntPtr.Zero,code,message,data);
47
+ if(code>=0) {
48
+ Mouse input=(Mouse)Marshal.PtrToStructure(data,typeof(Mouse));bool ours=(input.Flags & 1)!=0 && input.Extra==OwnedInputMarker;
49
+ if((input.Flags & 1)==0 && consumed!=IntPtr.Zero && Protected())ours=true;
50
+ if(!ours)Interlocked.Increment(ref activity);
51
+ }
52
+ return consumed;
53
+ }
54
+ static IntPtr OnKey(int code,IntPtr message,IntPtr data) {
55
+ IntPtr consumed=CallNextHookEx(IntPtr.Zero,code,message,data);
56
+ if(code>=0){Key input=(Key)Marshal.PtrToStructure(data,typeof(Key));if(!((input.Flags & 0x10)!=0 && input.Extra==OwnedInputMarker) && !((input.Flags & 0x10)==0 && consumed!=IntPtr.Zero && Protected()))Interlocked.Increment(ref activity);}
57
+ return consumed;
58
+ }
59
+ static void Ensure() {
60
+ lock(gate)if(!started){started=true;Thread thread=new Thread(delegate(){
61
+ IntPtr mouse=SetWindowsHookEx(14,mouseCallback,GetModuleHandle(null),0),key=SetWindowsHookEx(13,keyCallback,GetModuleHandle(null),0);
62
+ available=mouse!=IntPtr.Zero && key!=IntPtr.Zero;ready.Set();
63
+ try {if(available){Message message;while(GetMessage(out message,IntPtr.Zero,0,0)>0){TranslateMessage(ref message);DispatchMessage(ref message);}}}
64
+ finally {available=false;if(mouse!=IntPtr.Zero)UnhookWindowsHookEx(mouse);if(key!=IntPtr.Zero)UnhookWindowsHookEx(key);}
65
+ });thread.IsBackground=true;thread.Start();}
66
+ if(!ready.WaitOne(1000) || !available)throw new InvalidOperationException("USER_ACTIVITY_OBSERVER_UNAVAILABLE: no input sent");
67
+ }
68
+ public InteractionGuard(string cancelName="",int listenerPid=0){
69
+ if(cancelName.Length>0)lock(gate){
70
+ if(protectionName!=cancelName){
71
+ EventWaitHandle next=EventWaitHandle.OpenExisting(cancelName+"-exclusive");
72
+ EventWaitHandle nextCancellation=null;Process nextProcess=null;
73
+ try {nextCancellation=EventWaitHandle.OpenExisting(cancelName);nextProcess=Process.GetProcessById(listenerPid);IntPtr identity=nextProcess.Handle;}
74
+ catch{next.Dispose();if(nextCancellation!=null)nextCancellation.Dispose();if(nextProcess!=null)nextProcess.Dispose();throw;}
75
+ if(protection!=null)protection.Dispose();if(cancellation!=null)cancellation.Dispose();if(protector!=null)protector.Dispose();
76
+ protection=next;cancellation=nextCancellation;protector=nextProcess;protectionName=cancelName;
77
+ }
78
+ requiresProtection=true;
79
+ }
80
+ Ensure();baseline=Interlocked.Read(ref activity);
81
+ }
82
+ public bool Undisturbed {get {return available && (!requiresProtection || Protected()) && baseline==Interlocked.Read(ref activity);}}
83
+ public void Check(){if(requiresProtection && cancellation.WaitOne(0))throw new InvalidOperationException("COMPUTER_USE_STOPPED");if(requiresProtection && !Protected())throw new InvalidOperationException("EXCLUSIVE_CONTROL_LOST: no further input");if(!Undisturbed)throw new InvalidOperationException("USER_INPUT_CHANGED: intervening input stopped automation");}
84
+ public static bool Move(int x,int y) {
85
+ int left=GetSystemMetrics(76),top=GetSystemMetrics(77),width=GetSystemMetrics(78),height=GetSystemMetrics(79);
86
+ if(width<=0 || height<=0 || x<left || x>=left+width || y<top || y>=top+height)return false;
87
+ PointerInput input=new PointerInput {Type=0,Data=new InputUnion {Mouse=new MouseInput {
88
+ // Choose the first normalized coordinate for the pixel, not its midpoint:
89
+ // Windows can round the midpoint into the next physical pixel.
90
+ X=Math.Max(1,(int)(((long)(x-left)*65536+width-1)/width)),Y=Math.Max(1,(int)(((long)(y-top)*65536+height-1)/height)),
91
+ Flags=0xC001,Extra=OwnedInputMarker}}};
92
+ ControlPoint observed;
93
+ return SendInput(1,new PointerInput[]{input},Marshal.SizeOf(typeof(PointerInput)))==1 && GetCursorPos(out observed) && observed.X==x && observed.Y==y;
94
+ }
95
+ }
96
+
97
+ static HashSet<long> ControlPopups(uint owner,IntPtr target) {
98
+ HashSet<long> found=new HashSet<long>();
99
+ EnumWindows(delegate(IntPtr window,IntPtr ignored) { if(window!=target && IsWindowVisible(window) && WindowOwner(window)==owner && (GetWindow(window,4)!=IntPtr.Zero || (ControlStyle(window,-16).ToInt64() & 0x80000000L)!=0))found.Add(window.ToInt64());return true; },IntPtr.Zero);
100
+ return found;
101
+ }
102
+ static void RestoreControlState(Dictionary<string,object> result,IntPtr before,ControlPoint pointer,IntPtr window,HashSet<long> popups,bool keep,InteractionGuard interaction) {
103
+ ControlPoint now;
104
+ result["pointer"]=GetCursorPos(out now) && now.X==pointer.X && now.Y==pointer.Y?"unchanged":"left: movement was not proven to be ours";
105
+ if(!CurrentReferenceIntact()){result["foreground"]="left: lifetime proof lost";return;}
106
+ IntPtr after=GetForegroundWindow();
107
+ if(after==before){result["foreground"]="unchanged";return;}
108
+ if(window==IntPtr.Zero || after!=window){result["foreground"]="left: another window is in front";return;}
109
+ if(keep){result["foreground"]="kept (activate:true)";return;}
110
+ if(interaction==null || !interaction.Undisturbed){result["foreground"]="left: user activity or unavailable movement evidence";return;}
111
+ uint ignoredOwner;uint thread=GetWindowThreadProcessId(window,out ignoredOwner);
112
+ ControlGuiInfo info=new ControlGuiInfo {Size=(uint)Marshal.SizeOf(typeof(ControlGuiInfo))};
113
+ bool menu=GetGUIThreadInfo(thread,ref info) && (info.Flags & 0x1C)!=0;
114
+ HashSet<long> opened=ControlPopups(WindowOwner(window),window);opened.ExceptWith(popups);
115
+ if(menu || opened.Count>0){result["foreground"]="kept: action opened menu or dialog";return;}
116
+ if(before!=IntPtr.Zero && IsWindow(before) && interaction.Undisturbed && SetForegroundWindow(before))result["foreground"]="returned (provider activated intended target)";
117
+ else result["foreground"]="kept: restoration refused or prior window disappeared";
118
+ }
119
+ static bool NativeButton(AutomationElement element) {
120
+ IntPtr handle=new IntPtr(element.Current.NativeWindowHandle);if(handle==IntPtr.Zero)return false;
121
+ string name=element.Current.ClassName;
122
+ bool button=element.Current.ControlType==ControlType.Button || element.Current.ControlType==ControlType.CheckBox || element.Current.ControlType==ControlType.RadioButton;
123
+ if(!button || !(name.Equals("Button",StringComparison.OrdinalIgnoreCase) || name.StartsWith("WindowsForms10.BUTTON",StringComparison.OrdinalIgnoreCase)))return false;
124
+ BeginWindowDispatch();
125
+ if(!ControlPost(handle,0xF5,IntPtr.Zero,IntPtr.Zero))throw new InvalidOperationException("NATIVE_DISPATCH_FAILED: button message was refused");
126
+ UIntPtr ignored;SendMessageTimeout(handle,0,UIntPtr.Zero,IntPtr.Zero,2,500,out ignored);return true;
127
+ }
128
+ static bool NativeValue(AutomationElement element,string value) {
129
+ IntPtr handle=new IntPtr(element.Current.NativeWindowHandle);if(handle==IntPtr.Zero || element.Current.ControlType!=ControlType.Edit)return false;
130
+ string name=element.Current.ClassName;
131
+ if(!(name.Equals("Edit",StringComparison.OrdinalIgnoreCase) || name.StartsWith("WindowsForms10.EDIT",StringComparison.OrdinalIgnoreCase) || name.StartsWith("RichEdit",StringComparison.OrdinalIgnoreCase)))return false;
132
+ if((ControlStyle(handle,-16).ToInt64() & 0x800)!=0)throw new InvalidOperationException("READ_ONLY: control does not accept value changes");
133
+ UIntPtr ignored;
134
+ BeginWindowDispatch();
135
+ if(SendMessageTimeout(handle,0xB1,UIntPtr.Zero,new IntPtr(-1),2,500,out ignored)==IntPtr.Zero)throw new InvalidOperationException("NATIVE_DISPATCH_FAILED: edit selection was not confirmed");
136
+ BeginWindowDispatch();
137
+ if(ControlText(handle,0xC2,new UIntPtr(1),value,2,1000,out ignored)==IntPtr.Zero)throw new InvalidOperationException("NATIVE_DISPATCH_FAILED: edit message was not confirmed");
138
+ return true;
139
+ }
140
+ static Dictionary<string,object> ScrollPosition(IntPtr handle) {return new Dictionary<string,object>{{"vertical",GetScrollPos(handle,1)},{"horizontal",GetScrollPos(handle,0)}};}
141
+ // Match the reference's Windows wheel-lines setting and midpoint-to-even
142
+ // rounding and line-mode cap. Page mode retains reference step semantics;
143
+ // the stop lease is checked between calls rather than pretending a step cap
144
+ // bounds a synchronous provider call.
145
+ static int ScrollSteps(int delta,int lines,bool byPage) {
146
+ if(delta==0)return 0;
147
+ double scaled=Math.Abs((double)delta)*(byPage?1.0:lines)/120.0;
148
+ return (int)(byPage?Math.Max(1,Math.Round(scaled)):Math.Min(100,Math.Max(1,Math.Round(scaled))));
149
+ }
150
+ static bool NativeScroll(AutomationElement element,int x,int y,int countX,int countY,bool byPage,Dictionary<string,object> result,Action check) {
151
+ IntPtr handle=new IntPtr(element.Current.NativeWindowHandle);if(handle==IntPtr.Zero)return false;
152
+ long style=ControlStyle(handle,-16).ToInt64();
153
+ if((y!=0 && (style & 0x200000)==0) || (x!=0 && (style & 0x100000)==0))return false;
154
+ result["before"]=ScrollPosition(handle);
155
+ foreach(int axis in new int[]{1,0}) {
156
+ int delta=axis==0?x:y;if(delta==0)continue;
157
+ uint message=axis==0?0x114U:0x115U;int count=axis==0?countX:countY;
158
+ uint command=(delta>0?1U:0U)+(byPage?2U:0U);
159
+ for(int step=0;step<count;step++) { check();BeginWindowDispatch();UIntPtr dispatched;if(SendMessageTimeout(handle,message,new UIntPtr(command),IntPtr.Zero,2,500,out dispatched)==IntPtr.Zero)throw new InvalidOperationException("NATIVE_DISPATCH_UNCONFIRMED: scroll handler did not complete; observe before retrying");check(); }
160
+ check();BeginWindowDispatch();UIntPtr completed;SendMessageTimeout(handle,message,new UIntPtr(8),IntPtr.Zero,2,500,out completed);
161
+ }
162
+ UIntPtr ignored;SendMessageTimeout(handle,0,UIntPtr.Zero,IntPtr.Zero,2,500,out ignored);
163
+ result["after"]=ScrollPosition(handle);result["method"]="native_scroll";return true;
164
+ }
165
+ static void ControlScroll(AutomationElement element,IntPtr window,int x,int y,Dictionary<string,object> result,Action check) {
166
+ int lines=System.Windows.Forms.SystemInformation.MouseWheelScrollLines;
167
+ bool byPage=lines<0;int countX=ScrollSteps(x,lines,byPage),countY=ScrollSteps(y,lines,byPage);
168
+ AutomationElement candidate=element;
169
+ for(int level=0;candidate!=null && level<64;level++) {
170
+ check();if(candidate.Current.ProcessId!=(int)WindowOwner(window))break;
171
+ if(NativeScroll(candidate,x,y,countX,countY,byPage,result,check))return;
172
+ object raw;
173
+ if(candidate.TryGetCurrentPattern(ScrollPattern.Pattern,out raw)) {
174
+ ScrollPattern scroll=(ScrollPattern)raw;
175
+ if((x==0 || scroll.Current.HorizontallyScrollable) && (y==0 || scroll.Current.VerticallyScrollable)) {
176
+ result["before"]=new Dictionary<string,object>{{"vertical",scroll.Current.VerticalScrollPercent},{"horizontal",scroll.Current.HorizontalScrollPercent}};
177
+ ScrollAmount vertical=y>0?(byPage?ScrollAmount.LargeIncrement:ScrollAmount.SmallIncrement):(byPage?ScrollAmount.LargeDecrement:ScrollAmount.SmallDecrement);
178
+ ScrollAmount horizontal=x>0?(byPage?ScrollAmount.LargeIncrement:ScrollAmount.SmallIncrement):(byPage?ScrollAmount.LargeDecrement:ScrollAmount.SmallDecrement);
179
+ for(int step=0;step<countY;step++){check();BeginWindowDispatch();scroll.Scroll(ScrollAmount.NoAmount,vertical);check();}
180
+ for(int step=0;step<countX;step++){check();BeginWindowDispatch();scroll.Scroll(horizontal,ScrollAmount.NoAmount);check();}
181
+ result["after"]=new Dictionary<string,object>{{"vertical",scroll.Current.VerticalScrollPercent},{"horizontal",scroll.Current.HorizontalScrollPercent}};result["method"]="uia_scroll";return;
182
+ }
183
+ }
184
+ candidate=TreeWalker.ControlViewWalker.GetParent(candidate);
185
+ }
186
+ throw new InvalidOperationException("WCU_NEEDS_INPUT: no background scrolling pattern is available");
187
+ }
188
+ static object ExecuteControl(string action,Dictionary<string,object> args) {
189
+ Dictionary<string,object> result=Result(true,action);
190
+ IntPtr before=IntPtr.Zero,window=IntPtr.Zero;ControlPoint pointer=new ControlPoint();
191
+ AutomationElement element=null;HashSet<long> popups=new HashSet<long>();InteractionGuard interaction=null;
192
+ EventWaitHandle cancel=null;Process listener=null;bool quietScroll=action=="scroll" && Option(args,"_quietScroll",false);
193
+ Action check=delegate {CheckReferenceBeforeDispatch();if(cancel!=null && (cancel.WaitOne(0) || listener.HasExited))throw new InvalidOperationException("COMPUTER_USE_STOPPED: in-flight app effects cannot be undone; observe before further input");};
194
+ try {
195
+ if(quietScroll) {
196
+ listener=Process.GetProcessById(NumberOption(args,"_stopProcessId",0));IntPtr identity=listener.Handle;
197
+ cancel=EventWaitHandle.OpenExisting(TextOption(args,"_cancelEvent",""));check();
198
+ }
199
+ interaction=new InteractionGuard();before=GetForegroundWindow();GetCursorPos(out pointer);
200
+ PruneElements();element=ResolveObservedElement(TextOption(args,"elementId",""),args,out window);popups=ControlPopups(WindowOwner(window),window);
201
+ if(!element.Current.IsEnabled)throw new InvalidOperationException("ELEMENT_DISABLED: control is disabled");
202
+ ApplyVisibleIntent(args,window);
203
+ if(Option(args,"activate",false) && !ManagementActivate(window,WindowOwner(window)))throw new InvalidOperationException("TARGET_ACTIVATION_REFUSED: Windows did not grant foreground access");
204
+ object raw;check();
205
+ if(action=="focus") {ForgetTextFocus();BeginWindowDispatch();element.SetFocus();RememberTextFocus(window,new IntPtr(element.Current.NativeWindowHandle));result["method"]="uia_focus";}
206
+ else if(action=="set_value") {
207
+ if(element.TryGetCurrentPattern(ValuePattern.Pattern,out raw) && ((ValuePattern)raw).Current.IsReadOnly)throw new InvalidOperationException("READ_ONLY: control does not accept value changes");
208
+ if(NativeValue(element,TextOption(args,"value","")))result["method"]="native_edit";
209
+ else if(element.TryGetCurrentPattern(ValuePattern.Pattern,out raw)) {BeginWindowDispatch();((ValuePattern)raw).SetValue(TextOption(args,"value",""));result["method"]="uia_value";}
210
+ else throw new InvalidOperationException(Option(args,"fallbackType",true)?"WCU_NEEDS_INPUT: value pattern unavailable; guarded typing required":"UNSUPPORTED_PATTERN: value pattern unavailable");
211
+ } else if(action=="scroll") ControlScroll(element,window,NumberOption(args,"deltaX",0),NumberOption(args,"deltaY",480),result,check);
212
+ else {
213
+ string requested=TextOption(args,"action","default");
214
+ if(requested=="scroll_into_view") {if(!element.TryGetCurrentPattern(ScrollItemPattern.Pattern,out raw))throw new InvalidOperationException("UNSUPPORTED_PATTERN: ScrollItem unavailable");BeginWindowDispatch();((ScrollItemPattern)raw).ScrollIntoView();result["method"]="uia_scroll_into_view";}
215
+ else if(requested=="expand" || requested=="collapse") {if(!element.TryGetCurrentPattern(ExpandCollapsePattern.Pattern,out raw))throw new InvalidOperationException("UNSUPPORTED_PATTERN: ExpandCollapse unavailable");ExpandCollapsePattern expand=(ExpandCollapsePattern)raw;ExpandCollapseState desired=requested=="expand"?ExpandCollapseState.Expanded:ExpandCollapseState.Collapsed;if(expand.Current.ExpandCollapseState!=desired){BeginWindowDispatch();if(requested=="expand")expand.Expand();else expand.Collapse();}result["method"]="uia_"+requested;}
216
+ else if(NativeButton(element))result["method"]="native_button";
217
+ else if(element.TryGetCurrentPattern(InvokePattern.Pattern,out raw)){BeginWindowDispatch();((InvokePattern)raw).Invoke();result["method"]="uia_invoke";}
218
+ else if(element.TryGetCurrentPattern(TogglePattern.Pattern,out raw)){BeginWindowDispatch();((TogglePattern)raw).Toggle();result["method"]="uia_toggle";}
219
+ else if(element.TryGetCurrentPattern(SelectionItemPattern.Pattern,out raw)){BeginWindowDispatch();((SelectionItemPattern)raw).Select();result["method"]="uia_select";}
220
+ else if(element.TryGetCurrentPattern(ExpandCollapsePattern.Pattern,out raw)){BeginWindowDispatch();((ExpandCollapsePattern)raw).Expand();result["method"]="uia_expand";}
221
+ else {
222
+ // A semantic invoke is not permission to bypass a read-only control.
223
+ // The pointer click path has no semantic action option and can still
224
+ // select/focus read-only text with its ordinary click contract.
225
+ if(args.ContainsKey("action") && element.TryGetCurrentPattern(ValuePattern.Pattern,out raw) && ((ValuePattern)raw).Current.IsReadOnly)throw new InvalidOperationException("READ_ONLY: semantic invoke cannot escalate on a read-only control");
226
+ throw new InvalidOperationException(Option(args,"fallbackClick",true)?"WCU_NEEDS_INPUT: no supported background action; guarded click required":"UNSUPPORTED_PATTERN: no supported background action");
227
+ }
228
+ }
229
+ result["inputTargetHandle"]=window.ToInt64();result["inputTargetProcessId"]=WindowOwner(window);
230
+ result["verification"]="sent";result["verificationNote"]="Action dispatched; use a fresh observation or explicit wait to verify its effect.";
231
+ } catch(Exception error) {
232
+ result["ok"]=false;
233
+ if(error is ElementNotAvailableException || error.HResult==unchecked((int)0x80040201)) {observed.Remove(TextOption(args,"elementId",""));result["error"]="STALE_ELEMENT: control disappeared; observe again";}
234
+ else result["error"]=error.Message;
235
+ if(error.Message.StartsWith("WCU_NEEDS_INPUT",StringComparison.Ordinal))result["inputRequired"]=true;
236
+ }
237
+ finally {
238
+ try {
239
+ if(cancel!=null && (cancel.WaitOne(0) || listener.HasExited)) {result["pointer"]="unchanged";result["foreground"]="left: operation stopped";}
240
+ else RestoreControlState(result,before,pointer,window,popups,Option(args,"activate",false),interaction);
241
+ } finally {if(cancel!=null)cancel.Dispose();if(listener!=null)listener.Dispose();}
242
+ // This acknowledges settlement of synchronous native/UIA calls, never
243
+ // reversal of a previously dispatched app effect. Quiet scroll holds no
244
+ // physical key/button and never announces physical input presentation.
245
+ if(quietScroll)result["cleanupConfirmed"]=true;
246
+ }
247
+ return result;
248
+ }
249
+ }
Binary file
@@ -0,0 +1,252 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Drawing;
4
+ using System.Runtime.InteropServices;
5
+ using System.Windows.Forms;
6
+
7
+ partial class StopBridge {
8
+ class IndicatorWindow : Form {
9
+ readonly bool frame;
10
+ [StructLayout(LayoutKind.Sequential)] struct LayerPoint { public int X,Y; }
11
+ [StructLayout(LayoutKind.Sequential)] struct LayerSize { public int Width,Height; }
12
+ [StructLayout(LayoutKind.Sequential,Pack=1)] struct BlendFunction { public byte Operation,Flags,Alpha,Format; }
13
+ [DllImport("gdi32.dll",SetLastError=true)] static extern IntPtr CreateCompatibleDC(IntPtr dc);
14
+ [DllImport("gdi32.dll",SetLastError=true)] static extern IntPtr SelectObject(IntPtr dc,IntPtr value);
15
+ [DllImport("gdi32.dll",SetLastError=true)] static extern bool DeleteObject(IntPtr value);
16
+ [DllImport("gdi32.dll",SetLastError=true)] static extern bool DeleteDC(IntPtr dc);
17
+ [DllImport("user32.dll",SetLastError=true)] static extern bool UpdateLayeredWindow(IntPtr window,IntPtr screen,ref LayerPoint destination,ref LayerSize size,IntPtr source,ref LayerPoint origin,uint colorKey,ref BlendFunction blend,uint flags);
18
+ IntPtr frameDc,frameBitmap,framePrevious;
19
+ public bool IsFrame {get{return frame;}}
20
+ public string FrameError="";
21
+ public string Caption="";
22
+ public float DpiScale=1;
23
+ public IndicatorWindow(bool isFrame) {
24
+ frame=isFrame;FormBorderStyle=FormBorderStyle.None;ShowInTaskbar=false;TopMost=true;
25
+ StartPosition=FormStartPosition.Manual;AutoScaleMode=AutoScaleMode.None;
26
+ BackColor=Color.FromArgb(20,44,70);ForeColor=Color.White;Enabled=false;
27
+ SetStyle(ControlStyles.AllPaintingInWmPaint|ControlStyles.OptimizedDoubleBuffer|ControlStyles.UserPaint,true);
28
+ }
29
+ protected override bool ShowWithoutActivation { get { return true; } }
30
+ protected override CreateParams CreateParams { get { CreateParams value=base.CreateParams;value.ExStyle|=0x08000000|0x00000080|0x00000020|0x00080000;return value; } }
31
+ public void LayoutAt(Rectangle bounds,float scale) {
32
+ if(Bounds==bounds && DpiScale==scale)return;
33
+ DpiScale=scale;Bounds=bounds;
34
+ if(frame)BuildFrame();
35
+ Invalidate();
36
+ }
37
+ void ReleaseFrame() {
38
+ if(frameDc!=IntPtr.Zero) {if(framePrevious!=IntPtr.Zero)SelectObject(frameDc,framePrevious);if(frameBitmap!=IntPtr.Zero)DeleteObject(frameBitmap);DeleteDC(frameDc);}
39
+ else if(frameBitmap!=IntPtr.Zero)DeleteObject(frameBitmap);
40
+ frameDc=IntPtr.Zero;frameBitmap=IntPtr.Zero;framePrevious=IntPtr.Zero;
41
+ }
42
+ void BuildFrame() {
43
+ ReleaseFrame();FrameError="";
44
+ if(Width<2 || Height<2){FrameError="Indicator frame has invalid dimensions";return;}
45
+ try {
46
+ using(Bitmap image=new Bitmap(Width,Height,System.Drawing.Imaging.PixelFormat.Format32bppPArgb)) {
47
+ using(Graphics graphics=Graphics.FromImage(image)) {
48
+ graphics.Clear(Color.Transparent);
49
+ graphics.CompositingMode=System.Drawing.Drawing2D.CompositingMode.SourceCopy;
50
+ float scale=Math.Max(1f,Math.Min(2f,Width/1920f));
51
+ int core=(int)Math.Round(3*scale),glow=core+(int)Math.Round(27*scale);
52
+ for(int inset=0;inset<glow && Width-2*inset>1 && Height-2*inset>1;inset++) {
53
+ Color color=inset<core?Color.FromArgb(235,150,184,255):Color.FromArgb((int)(175*Math.Pow(1-(inset-core)/(double)(glow-core),2.2)),77,124,254);
54
+ using(Pen pen=new Pen(color,1))graphics.DrawRectangle(pen,inset,inset,Width-2*inset-1,Height-2*inset-1);
55
+ }
56
+ }
57
+ frameBitmap=image.GetHbitmap(Color.FromArgb(0));
58
+ }
59
+ frameDc=CreateCompatibleDC(IntPtr.Zero);
60
+ if(frameDc==IntPtr.Zero){FrameError="Indicator frame DC creation failed: "+Marshal.GetLastWin32Error();ReleaseFrame();return;}
61
+ framePrevious=SelectObject(frameDc,frameBitmap);
62
+ if(framePrevious==IntPtr.Zero || framePrevious==new IntPtr(-1)){FrameError="Indicator frame bitmap selection failed: "+Marshal.GetLastWin32Error();framePrevious=IntPtr.Zero;ReleaseFrame();}
63
+ } catch(Exception error) {FrameError="Indicator frame preparation failed: "+error.Message;ReleaseFrame();}
64
+ }
65
+ public bool PresentFrame() {
66
+ if(!frame || frameDc==IntPtr.Zero)return false;
67
+ LayerPoint destination=new LayerPoint{X=Left,Y=Top},origin=new LayerPoint();
68
+ LayerSize size=new LayerSize{Width=Width,Height=Height};
69
+ BlendFunction blend=new BlendFunction{Operation=0,Flags=0,Alpha=255,Format=1};
70
+ bool presented=UpdateLayeredWindow(Handle,IntPtr.Zero,ref destination,ref size,frameDc,ref origin,0,ref blend,2);
71
+ if(!presented)FrameError="Indicator frame presentation failed: "+Marshal.GetLastWin32Error();
72
+ else FrameError="";
73
+ return presented;
74
+ }
75
+ protected override void OnPaint(PaintEventArgs e) {
76
+ if(frame)return;
77
+ using(Pen pen=new Pen(Color.FromArgb(62,156,255),Math.Max(1,2*DpiScale)))e.Graphics.DrawRectangle(pen,1,1,Width-3,Height-3);
78
+ using(Font font=new Font("Segoe UI",Math.Max(9,10*DpiScale),FontStyle.Bold,GraphicsUnit.Point))
79
+ TextRenderer.DrawText(e.Graphics,Caption,font,ClientRectangle,ForeColor,TextFormatFlags.HorizontalCenter|TextFormatFlags.VerticalCenter|TextFormatFlags.EndEllipsis|TextFormatFlags.NoPrefix);
80
+ }
81
+ protected override void Dispose(bool disposing) {if(disposing)ReleaseFrame();base.Dispose(disposing);}
82
+
83
+ }
84
+ class Presentation : IDisposable {
85
+ [StructLayout(LayoutKind.Sequential)] struct Point { public int X,Y; }
86
+ [StructLayout(LayoutKind.Sequential)] struct CursorInfo { public int Size,Flags;public IntPtr Cursor;public Point Position; }
87
+ [StructLayout(LayoutKind.Sequential)] struct IconInfo { public bool Icon;public uint HotX,HotY;public IntPtr Mask,Color; }
88
+ [StructLayout(LayoutKind.Sequential)] struct BitmapInfo { public int Type,Width,Height,WidthBytes;public ushort Planes,BitsPixel;public IntPtr Bits; }
89
+ [DllImport("user32.dll")] static extern bool EnableWindow(IntPtr window,bool enabled);
90
+ [DllImport("user32.dll")] static extern bool SetLayeredWindowAttributes(IntPtr window,uint key,byte alpha,uint flags);
91
+ [DllImport("user32.dll")] static extern bool SetWindowDisplayAffinity(IntPtr window,uint affinity);
92
+ [DllImport("user32.dll")] static extern IntPtr MonitorFromPoint(Point point,uint flags);
93
+ [DllImport("shcore.dll")] static extern int GetDpiForMonitor(IntPtr monitor,int type,out uint x,out uint y);
94
+ [DllImport("user32.dll")] static extern bool GetCursorInfo(ref CursorInfo info);
95
+ [DllImport("user32.dll")] static extern bool GetIconInfo(IntPtr icon,out IconInfo info);
96
+ [DllImport("gdi32.dll",CharSet=CharSet.Unicode)] static extern int GetObject(IntPtr value,int bytes,out BitmapInfo info);
97
+ [DllImport("gdi32.dll")] static extern bool DeleteObject(IntPtr value);
98
+ [DllImport("user32.dll")] static extern IntPtr WindowFromPoint(Point point);
99
+ [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr window,out uint process);
100
+ readonly System.Windows.Forms.Timer timer;
101
+ readonly List<IndicatorWindow> frames=new List<IndicatorWindow>();
102
+ readonly List<IndicatorWindow> bars=new List<IndicatorWindow>();
103
+ IndicatorWindow pointer;
104
+ bool screenEnabled=true,pointerEnabled=true,remoteVisible=true,held,suspended,preview,renderChecked;
105
+ int captureSuspensions;
106
+ string previewPath;
107
+ bool? affinityApplied=null;
108
+ string caption="Pi is controlling the computer",position="top",layoutKey="";
109
+ int endDelay=800;
110
+ DateTime previewUntil=DateTime.MinValue,lastLayout=DateTime.MinValue;
111
+ public Presentation() {
112
+ timer=new System.Windows.Forms.Timer { Interval=40 };
113
+ timer.Tick+=delegate { Tick(); };timer.Start();
114
+ }
115
+ static bool Switch(Dictionary<string,object> values,string name,bool fallback) { object value;return values.TryGetValue(name,out value)?Convert.ToBoolean(value):fallback; }
116
+ public void Configure(Dictionary<string,object> values) {
117
+ screenEnabled=Switch(values,"indicatorEnabled",true);pointerEnabled=Switch(values,"cursorIndicatorEnabled",true);remoteVisible=Switch(values,"remoteIndicatorVisible",true);
118
+ object value;caption=values.TryGetValue("indicatorText",out value)?Convert.ToString(value):"Pi is controlling the computer";
119
+ position=values.TryGetValue("indicatorPosition",out value)?Convert.ToString(value):"top";
120
+ endDelay=values.TryGetValue("indicatorIdleMs",out value)?Convert.ToInt32(value):800;
121
+ layoutKey="";Refresh();
122
+ }
123
+ public void Begin() {
124
+ held=true;preview=false;Refresh();
125
+ if(!BorderReady)throw new InvalidOperationException("EXCLUSIVE_CONTROL_UNAVAILABLE: control border failed");
126
+ }
127
+ public bool BorderReady {get {return held && (suspended || (frames.Count>0 && frames.TrueForAll(frame=>frame.Visible && frame.FrameError.Length==0)));}}
128
+ public void Stop() { held=false;preview=false;Hide(); }
129
+ public void Suspend(bool value) { if(value)captureSuspensions++;suspended=value;Refresh(); }
130
+ public void Preview(bool show) {
131
+ if(!show) {
132
+ using(IndicatorWindow sample=new IndicatorWindow(false))
133
+ using(Bitmap pixels=new Bitmap(320,42)) {
134
+ sample.Caption=caption+" · Esc to stop";sample.LayoutAt(new Rectangle(0,0,320,42),1);sample.DrawToBitmap(pixels,new Rectangle(0,0,320,42));
135
+ if(previewPath!=null)try{System.IO.File.Delete(previewPath);}catch(System.IO.IOException){}
136
+ previewPath=System.IO.Path.Combine(System.IO.Path.GetTempPath(),"pi-cu-indicator-preview-"+Guid.NewGuid().ToString("N")+".png");
137
+ pixels.Save(previewPath,System.Drawing.Imaging.ImageFormat.Png);
138
+ renderChecked=true;
139
+ }
140
+ return;
141
+ }
142
+ preview=true;previewUntil=DateTime.UtcNow.AddMilliseconds(2000);Refresh();
143
+ }
144
+ bool Wanted() { return !suspended && (held || preview); }
145
+ static float MonitorScale(Screen screen) {
146
+ uint x,y;
147
+ try {
148
+ IntPtr monitor=MonitorFromPoint(new Point {X=screen.Bounds.Left+screen.Bounds.Width/2,Y=screen.Bounds.Top+screen.Bounds.Height/2},2);
149
+ if(GetDpiForMonitor(monitor,0,out x,out y)==0 && x>=48 && x<=768)return x/96f;
150
+ } catch(EntryPointNotFoundException) { } catch(DllNotFoundException) { }
151
+ return 1;
152
+ }
153
+ List<object> Layout() {
154
+ List<object> result=new List<object>();
155
+ foreach(Screen screen in Screen.AllScreens) {
156
+ float scale=MonitorScale(screen);Rectangle work=screen.WorkingArea;int height=(int)(42*scale),width=Math.Min(work.Width,(int)(600*scale));
157
+ int x=work.Left+(work.Width-width)/2,y=position=="bottom"?work.Bottom-height-8:work.Top+8;
158
+ result.Add(new Dictionary<string,object> {
159
+ {"device",screen.DeviceName},{"x",screen.Bounds.X},{"y",screen.Bounds.Y},{"width",screen.Bounds.Width},{"height",screen.Bounds.Height},
160
+ {"scale",scale},{"bar",new Dictionary<string,object>{{"x",x},{"y",y},{"width",width},{"height",height}}}
161
+ });
162
+ }
163
+ return result;
164
+ }
165
+ void Rebuild() {
166
+ if(layoutKey.Length>0 && (DateTime.UtcNow-lastLayout).TotalMilliseconds<500)return;
167
+ lastLayout=DateTime.UtcNow;
168
+ string key=position+"|"+caption+"|"+remoteVisible;
169
+ foreach(Screen screen in Screen.AllScreens)key+="|"+screen.DeviceName+screen.Bounds+screen.WorkingArea+MonitorScale(screen);
170
+ if(key==layoutKey)return;
171
+ layoutKey=key;
172
+ foreach(IndicatorWindow form in frames)form.Dispose();frames.Clear();
173
+ foreach(IndicatorWindow form in bars)form.Dispose();bars.Clear();
174
+ if(pointer!=null){pointer.Dispose();pointer=null;}
175
+ affinityApplied=null;
176
+ foreach(Screen screen in Screen.AllScreens) {
177
+ float scale=MonitorScale(screen);Rectangle work=screen.WorkingArea;int height=(int)(42*scale),width=Math.Min(work.Width,(int)(600*scale));
178
+ IndicatorWindow frame=new IndicatorWindow(true);frame.LayoutAt(screen.Bounds,scale);frames.Add(frame);
179
+ IndicatorWindow bar=new IndicatorWindow(false);bar.Caption=caption+" · Esc to stop";bar.LayoutAt(new Rectangle(work.Left+(work.Width-width)/2,position=="bottom"?work.Bottom-height-8:work.Top+8,width,height),scale);bars.Add(bar);
180
+ }
181
+ pointer=new IndicatorWindow(false);pointer.Caption="Pi";
182
+ }
183
+ void Apply(IndicatorWindow form,bool visible) {
184
+ if(!visible) { form.Hide();return; }
185
+ // A failed frame stays hidden until layout/configuration rebuilds it.
186
+ // Do not repeatedly show and hide a failing layered window on each tick.
187
+ if(form.IsFrame && form.FrameError.Length>0)return;
188
+ if(!form.Visible) {
189
+ form.Show();EnableWindow(form.Handle,false);
190
+ if(form.IsFrame) {if(!form.PresentFrame()){form.Hide();return;}}
191
+ else SetLayeredWindowAttributes(form.Handle,0,255,2);
192
+ if(!SetWindowDisplayAffinity(form.Handle,remoteVisible?0U:0x11U))affinityApplied=false;
193
+ else if(!affinityApplied.HasValue)affinityApplied=true;
194
+ }
195
+ }
196
+ static Size CursorExtent() {
197
+ CursorInfo cursor=new CursorInfo {Size=Marshal.SizeOf(typeof(CursorInfo))};IconInfo icon;
198
+ if(!GetCursorInfo(ref cursor) || !GetIconInfo(cursor.Cursor,out icon))return new Size(32,32);
199
+ try {
200
+ BitmapInfo bitmap;IntPtr source=icon.Color!=IntPtr.Zero?icon.Color:icon.Mask;
201
+ if(source!=IntPtr.Zero && GetObject(source,Marshal.SizeOf(typeof(BitmapInfo)),out bitmap)>0)
202
+ return new Size(Math.Max(16,bitmap.Width),Math.Max(16,icon.Color!=IntPtr.Zero?bitmap.Height:bitmap.Height/2));
203
+ } finally { if(icon.Color!=IntPtr.Zero)DeleteObject(icon.Color);if(icon.Mask!=IntPtr.Zero)DeleteObject(icon.Mask); }
204
+ return new Size(32,32);
205
+ }
206
+ void Refresh() {
207
+ if(!Wanted()) { Hide();return; }
208
+ Rebuild();
209
+ foreach(IndicatorWindow form in frames)Apply(form,screenEnabled || held);
210
+ foreach(IndicatorWindow form in bars)Apply(form,screenEnabled);
211
+ if(pointer!=null) {
212
+ System.Drawing.Point cursor=Cursor.Position;Screen screen=Screen.FromPoint(cursor);float scale=MonitorScale(screen);Size extent=CursorExtent();
213
+ int width=(int)(64*scale),height=(int)(32*scale),offset=(int)(8*scale);
214
+ int x=cursor.X+extent.Width+offset,y=cursor.Y+extent.Height+offset;
215
+ if(x+width>screen.WorkingArea.Right)x=cursor.X-width-offset;
216
+ if(y+height>screen.WorkingArea.Bottom)y=cursor.Y-height-offset;
217
+ x=Math.Max(screen.WorkingArea.Left,x);y=Math.Max(screen.WorkingArea.Top,y);
218
+ pointer.LayoutAt(new Rectangle(x,y,width,height),scale);Apply(pointer,pointerEnabled);
219
+ }
220
+ }
221
+ void Hide() {
222
+ foreach(IndicatorWindow form in frames)form.Hide();foreach(IndicatorWindow form in bars)form.Hide();if(pointer!=null)pointer.Hide();
223
+ }
224
+ void Tick() {
225
+ DateTime now=DateTime.UtcNow;
226
+ if(preview && now>=previewUntil)preview=false;
227
+ if(!Wanted()){Hide();return;}
228
+ Refresh();
229
+ }
230
+ public object Status() {
231
+ int visible=0;foreach(IndicatorWindow form in frames)if(form.Visible)visible++;foreach(IndicatorWindow form in bars)if(form.Visible)visible++;if(pointer!=null && pointer.Visible)visible++;
232
+ bool? hitTest=null;
233
+ foreach(IndicatorWindow form in bars)if(form.Visible) {
234
+ uint process;GetWindowThreadProcessId(WindowFromPoint(new Point {X=form.Left+form.Width/2,Y=form.Top+form.Height/2}),out process);
235
+ bool skipped=process!=(uint)System.Diagnostics.Process.GetCurrentProcess().Id;
236
+ hitTest=hitTest.HasValue?hitTest.Value && skipped:skipped;
237
+ }
238
+ return new Dictionary<string,object> {
239
+ {"held",held},{"renderChecked",renderChecked},{"previewPath",previewPath},{"preview",preview},{"suspended",suspended},{"visible",visible>0},{"visibleWindows",visible},
240
+ {"screenEnabled",screenEnabled},{"pointerEnabled",pointerEnabled},{"remoteVisible",remoteVisible},{"captureAffinityApplied",affinityApplied},
241
+ {"text",caption},{"position",position},{"endDelayMs",endDelay},{"operationIdleMs",0},{"previewDurationMs",2000},
242
+ {"nonActivating",true},{"clickThrough",true},{"layout",Layout()},
243
+ {"frameErrors",frames.FindAll(frame=>frame.FrameError.Length>0).ConvertAll(frame=>frame.FrameError)}
244
+ ,{"clickThroughHitTestPassed",hitTest},{"captureSuspensions",captureSuspensions}
245
+ };
246
+ }
247
+ public void Dispose() {
248
+ timer.Dispose();foreach(IndicatorWindow form in frames)form.Dispose();foreach(IndicatorWindow form in bars)form.Dispose();if(pointer!=null)pointer.Dispose();
249
+ if(previewPath!=null)try{System.IO.File.Delete(previewPath);}catch(System.IO.IOException){}
250
+ }
251
+ }
252
+ }